From e543888a7de59fd9195b294cefd3c2cf61f7486a Mon Sep 17 00:00:00 2001 From: Katter Date: Fri, 20 Sep 2024 18:41:53 +0500 Subject: [PATCH] Major x11 screen capture optimization --- .../Natives/LibXExt.cs | 2 +- .../SharedImageMemorySegment.cs | 11 ++++++--- .../SightKeeper.Application.Linux.csproj | 4 ++++ .../X11ScreenCapture.cs | 15 +++--------- .../WindowsScreenCapture.cs | 19 +++++---------- SightKeeper.Application/ScreenCapture.cs | 3 ++- .../SightKeeper.Application.csproj | 2 +- SightKeeper.Avalonia/Program.cs | 24 ------------------- 8 files changed, 25 insertions(+), 55 deletions(-) diff --git a/SightKeeper.Application.Linux/Natives/LibXExt.cs b/SightKeeper.Application.Linux/Natives/LibXExt.cs index f03c5d86..dc7975d4 100644 --- a/SightKeeper.Application.Linux/Natives/LibXExt.cs +++ b/SightKeeper.Application.Linux/Natives/LibXExt.cs @@ -12,7 +12,7 @@ internal static unsafe partial class LibXExt [LibraryImport(DllName)] [return: MarshalAs(UnmanagedType.Bool)] - public static partial bool XShmGetImage(IntPtr display, UIntPtr drawable, XImage* image, int x, int y, UIntPtr plane_mask); + public static partial bool XShmGetImage(IntPtr display, UIntPtr drawable, XImage* image, int x, int y, ulong plane_mask); [LibraryImport(DllName, SetLastError = true)] public static partial int XShmQueryExtension(IntPtr display); diff --git a/SightKeeper.Application.Linux/SharedImageMemorySegment.cs b/SightKeeper.Application.Linux/SharedImageMemorySegment.cs index 6d99ce9b..cc345d40 100644 --- a/SightKeeper.Application.Linux/SharedImageMemorySegment.cs +++ b/SightKeeper.Application.Linux/SharedImageMemorySegment.cs @@ -1,3 +1,4 @@ +using System.Numerics.Tensors; using System.Runtime.InteropServices; using SightKeeper.Application.Linux.Natives; using SightKeeper.Domain.Model; @@ -23,10 +24,14 @@ public unsafe SharedImageMemorySegment(nint display, Vector2 resolution) public unsafe void FetchData(int screen, Vector2 offset) { - const int allPlanes = ~0; - UIntPtr allPlanes2 = new(unchecked((uint)allPlanes)); + const ulong allPlanes = unchecked((ulong)~0); var drawable = (UIntPtr)LibX.XRootWindow(_display, screen); - LibXExt.XShmGetImage(_display, drawable, _image.ximage, offset.X, offset.Y, allPlanes2); + LibXExt.XShmGetImage(_display, drawable, _image.ximage, offset.X, offset.Y, allPlanes); + + // xlib doesn't use most significant byte but fills it with zeros + // because of that ImageSharp treats it as fully transparent Bgra32 + Span span = new(_image.data, Resolution.X * Resolution.Y); + TensorPrimitives.BitwiseOr(span, 0xFF_00_00_00, span); } public void Dispose() diff --git a/SightKeeper.Application.Linux/SightKeeper.Application.Linux.csproj b/SightKeeper.Application.Linux/SightKeeper.Application.Linux.csproj index afaf6df0..ac90798e 100644 --- a/SightKeeper.Application.Linux/SightKeeper.Application.Linux.csproj +++ b/SightKeeper.Application.Linux/SightKeeper.Application.Linux.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/SightKeeper.Application.Linux/X11ScreenCapture.cs b/SightKeeper.Application.Linux/X11ScreenCapture.cs index b2b86266..8016b6fe 100644 --- a/SightKeeper.Application.Linux/X11ScreenCapture.cs +++ b/SightKeeper.Application.Linux/X11ScreenCapture.cs @@ -1,28 +1,24 @@ using SightKeeper.Application.Linux.Natives; using SightKeeper.Domain.Model; using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.PixelFormats; namespace SightKeeper.Application.Linux; public sealed class X11ScreenCapture : ScreenCapture, IDisposable { - private static readonly IImageEncoder Encoder = new BmpEncoder(); - public X11ScreenCapture() { _display = LibX.XOpenDisplay(null); _screen = LibX.XDefaultScreen(_display); if (LibXExt.XShmQueryExtension(_display) == 0) { - LibX.XCloseDisplay(_display); + Dispose(); throw new Exception("xserver doesn't support shm"); } } - public unsafe Stream Capture(Vector2 resolution, Game? game) + public Image Capture(Vector2 resolution, Game? game) { if (_memorySegment?.Resolution != resolution) { @@ -30,12 +26,7 @@ public unsafe Stream Capture(Vector2 resolution, Game? game) _memorySegment = new SharedImageMemorySegment(_display, resolution); } _memorySegment.FetchData(_screen, new Vector2()); - MemoryStream stream = new(); - ShmImage image = new(); - Image.LoadPixelData(_memorySegment.Data, resolution.X, resolution.Y).Save(stream, Encoder); - XLibShm.DestroyImage(_display, &image); - stream.Position = 0; - return stream; + return Image.LoadPixelData(_memorySegment.Data, resolution.X, resolution.Y); } private readonly nint _display; diff --git a/SightKeeper.Application.Windows/WindowsScreenCapture.cs b/SightKeeper.Application.Windows/WindowsScreenCapture.cs index f9b86ddd..acc663f9 100644 --- a/SightKeeper.Application.Windows/WindowsScreenCapture.cs +++ b/SightKeeper.Application.Windows/WindowsScreenCapture.cs @@ -1,8 +1,5 @@ -using System.Drawing; -using System.Drawing.Imaging; -using Serilog.Events; -using SerilogTimings; -using SightKeeper.Domain.Model; +using SightKeeper.Domain.Model; +using Image = SixLabors.ImageSharp.Image; namespace SightKeeper.Application.Windows; @@ -13,20 +10,16 @@ public WindowsScreenCapture(ScreenBoundsProvider screenBoundsProvider) _screenBoundsProvider = screenBoundsProvider; } - public Stream Capture(Vector2 resolution, Game? game) + public Image Capture(Vector2 resolution, Game? game) { - var screenCenter = _screenBoundsProvider.MainScreenCenter; - var operation = Operation.At(LogEventLevel.Verbose).Begin("Screen capturing"); + throw new NotImplementedException(); + /*var screenCenter = _screenBoundsProvider.MainScreenCenter; using Bitmap windowsBitmap = new(resolution.X, resolution.Y); using var graphics = Graphics.FromImage(windowsBitmap); var halfResolution = resolution / 2; Point position = new(screenCenter.X - halfResolution.X, screenCenter.Y - halfResolution.Y); Size size = new(resolution.X, resolution.Y); - graphics.CopyFromScreen(position, Point.Empty, size); - MemoryStream stream = new(); - windowsBitmap.Save(stream, ImageFormat.Bmp); - operation.Complete(); - return stream; + graphics.CopyFromScreen(position, Point.Empty, size);*/ } private readonly ScreenBoundsProvider _screenBoundsProvider; diff --git a/SightKeeper.Application/ScreenCapture.cs b/SightKeeper.Application/ScreenCapture.cs index 7123b1a9..1bbb30e1 100644 --- a/SightKeeper.Application/ScreenCapture.cs +++ b/SightKeeper.Application/ScreenCapture.cs @@ -1,8 +1,9 @@ using SightKeeper.Domain.Model; +using SixLabors.ImageSharp; namespace SightKeeper.Application; public interface ScreenCapture { - Stream Capture(Vector2 resolution, Game? game); + Image Capture(Vector2 resolution, Game? game); } \ No newline at end of file diff --git a/SightKeeper.Application/SightKeeper.Application.csproj b/SightKeeper.Application/SightKeeper.Application.csproj index c0c0d208..04d27757 100644 --- a/SightKeeper.Application/SightKeeper.Application.csproj +++ b/SightKeeper.Application/SightKeeper.Application.csproj @@ -18,7 +18,7 @@ - + diff --git a/SightKeeper.Avalonia/Program.cs b/SightKeeper.Avalonia/Program.cs index bec12838..bfb4b7d7 100644 --- a/SightKeeper.Avalonia/Program.cs +++ b/SightKeeper.Avalonia/Program.cs @@ -1,12 +1,8 @@ using System; -using System.IO; using System.Threading.Tasks; using Avalonia; using Avalonia.Threading; using Serilog; -using SightKeeper.Application.Linux; -using SightKeeper.Domain.Model; -using SixLabors.ImageSharp; namespace SightKeeper.Avalonia; @@ -18,26 +14,6 @@ internal static class Program [STAThread] public static void Main(string[] args) { - X11ScreenCapture screenCapture = new(); - using var initial = screenCapture.Capture(new Vector2(640, 640), null); - if (File.Exists("Test.png")) - File.Delete("Test.png"); - Image.Load(initial).Save("Test.png"); - // warmup - for (int i = 0; i < 200; i++) - { - using var stream = screenCapture.Capture(new Vector2(640, 640), null); - } - DateTime start = DateTime.UtcNow; - const int samples = 1000; - for (int i = 0; i < samples; i++) - { - using var stream = screenCapture.Capture(new Vector2(640, 640), null); - } - var end = DateTime.UtcNow - start; - Console.WriteLine($"Elapsed {end.TotalMilliseconds / samples}ms per capture"); - Console.ReadKey(true); - return; SetupLogger(); AppBuilder? appBuilder = null; try