From e296589b8f380c5c6c10ab56c43631e961a22948 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 1 Oct 2024 02:24:50 +0400 Subject: [PATCH] [fbdev] Added a mode that should not waste time in FBIO_WAITFORVSYNC (#17124) * [fbdev] Added a mode that should not waste time in FBIO_WAITFORVSYNC * Log --- .../Output/FbDevBackBuffer.cs | 81 ++++++++++++++++++- .../Output/FbDevOutputOptions.cs | 6 ++ .../Output/FbdevOutput.cs | 2 +- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs index b1e125f247..d8abd83197 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevBackBuffer.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Logging; using Avalonia.Platform; namespace Avalonia.LinuxFramebuffer.Output @@ -14,14 +15,76 @@ namespace Avalonia.LinuxFramebuffer.Output private readonly fb_var_screeninfo _varInfo; private readonly IntPtr _targetAddress; private readonly object _lock = new object(); + private readonly AsyncFbBlitter? _asyncBlit; - public FbDevBackBuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr targetAddress) + class AsyncFbBlitter : IDisposable + { + private readonly FbDevBackBuffer _fb; + private AutoResetEvent _signalToThread = new AutoResetEvent(false); + private ManualResetEvent _transferCompleted = new ManualResetEvent(true); + private Thread _thread; + private volatile bool _exit; + + public AsyncFbBlitter(FbDevBackBuffer fb) + { + _fb = fb; + _thread = new Thread(Worker) + { + IsBackground = true, + Name = "FbDevBackBuffer::AsyncBlitter", + Priority = ThreadPriority.Highest + }; + _thread.Start(); + } + + private void Worker() + { + while (true) + { + _signalToThread.WaitOne(); + if (_exit) + return; + try + { + _fb.BlitToDevice(); + } + catch(Exception e) + { + Logger.TryGet(LogEventLevel.Fatal, "FBDEV")?.Log(this, "Unable to update framebuffer: " + e); + } + + _transferCompleted.Set(); + } + } + + public void Dispose() + { + _exit = true; + _signalToThread.Set(); + _thread.Join(); + _signalToThread.Dispose(); + _transferCompleted.Dispose(); + } + + public void WaitForTransfer() => _transferCompleted.WaitOne(); + + public void BeginBlit() + { + _transferCompleted.Reset(); + _signalToThread.Set(); + } + } + + public FbDevBackBuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr targetAddress, + bool asyncBlit) { _fb = fb; _fixedInfo = fixedInfo; _varInfo = varInfo; _targetAddress = targetAddress; Address = Marshal.AllocHGlobal(RowBytes * Size.Height); + if (asyncBlit) + _asyncBlit = new AsyncFbBlitter(this); } @@ -32,6 +95,9 @@ namespace Avalonia.LinuxFramebuffer.Output Marshal.FreeHGlobal(Address); Address = IntPtr.Zero; } + + if (_asyncBlit != null) + _asyncBlit.Dispose(); } public static LockedFramebuffer LockFb(IntPtr address, fb_var_screeninfo varInfo, @@ -45,8 +111,15 @@ namespace Avalonia.LinuxFramebuffer.Output : PixelFormat.Bgra8888, dispose); } + private void BlitToDevice() + { + NativeUnsafeMethods.ioctl(_fb, FbIoCtl.FBIO_WAITFORVSYNC, null); + NativeUnsafeMethods.memcpy(_targetAddress, Address, new IntPtr(RowBytes * Size.Height)); + } + public ILockedFramebuffer Lock(Vector dpi) { + _asyncBlit?.WaitForTransfer(); Monitor.Enter(_lock); try { @@ -55,8 +128,10 @@ namespace Avalonia.LinuxFramebuffer.Output { try { - NativeUnsafeMethods.ioctl(_fb, FbIoCtl.FBIO_WAITFORVSYNC, null); - NativeUnsafeMethods.memcpy(_targetAddress, Address, new IntPtr(RowBytes * Size.Height)); + if (_asyncBlit != null) + _asyncBlit.BeginBlit(); + else + BlitToDevice(); } finally { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevOutputOptions.cs index 8336362213..b80187ebdb 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbDevOutputOptions.cs @@ -29,4 +29,10 @@ public class FbDevOutputOptions /// The initial scale factor to use /// public double Scaling { get; set; } = 1; + + /// + /// If set to true, FBIO_WAITFORVSYNC ioctl and following memcpy call will run on a dedicated thread + /// saving current one from doing nothing in a blocking call + /// + public bool? UseAsyncFrontBufferBlit { get; set; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs index a4bf4fdd47..da46a81aa8 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs @@ -181,7 +181,7 @@ namespace Avalonia.LinuxFramebuffer _lockedAtLeastOnce = true; properties = new FramebufferLockProperties(retained); return (_backBuffer ??= - new FbDevBackBuffer(_fd, _fixedInfo, _varInfo, _mappedAddress)) + new FbDevBackBuffer(_fd, _fixedInfo, _varInfo, _mappedAddress, _options.UseAsyncFrontBufferBlit == true)) .Lock(new Vector(96, 96) * Scaling); }