Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize Rendering Push Performance Using MIT-SHM #17118

Open
wants to merge 43 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5be7d3b
Begin define some of the type to X11 XShm
lindexi Sep 25, 2024
54a24f9
Add libc define
lindexi Sep 25, 2024
a35f4b8
Try fix comment.
lindexi Sep 25, 2024
2894603
Copy XShm define code
lindexi Sep 25, 2024
1b7fe0f
Update the comment in XShm.cs
lindexi Sep 25, 2024
220f175
Try define the IFramebufferRenderTarget but fail.
lindexi Sep 25, 2024
d5a6c43
Add the XShmCompletionEvent
lindexi Sep 25, 2024
8b775e3
Try to get the XShmCompletionEvent without DeferredDisplayEvents
lindexi Sep 25, 2024
9e245be
Try open the softrender mode tode debug the FramebufferRenderTarget
lindexi Sep 26, 2024
869dce0
Receive the completion for xshm
lindexi Sep 26, 2024
54b1c81
Check the xshm support before add
lindexi Sep 26, 2024
a3c3527
Pass the visual and depth argument
lindexi Sep 26, 2024
42de87c
Remove X11ShmImageManager from X11ShmFramebufferContext
lindexi Sep 26, 2024
15b2253
Finish create X11ShmImage.
lindexi Sep 26, 2024
585509a
Fix lost ShmSeg
lindexi Sep 26, 2024
7bba7b5
Remove the presentationQueue in X11ShmImageManager
lindexi Sep 26, 2024
eb26283
Add the PresentationCount
lindexi Sep 26, 2024
e3d9ba3
If the depth is not 32, we should do some conversion to make the XShm…
lindexi Sep 26, 2024
a0a03a8
Finish send render
lindexi Sep 26, 2024
26d5761
Finish dispose the xshm image
lindexi Sep 26, 2024
c6c884d
Move types into matching files
lindexi Sep 26, 2024
654122b
Revert "Try open the softrender mode tode debug the FramebufferRender…
lindexi Sep 26, 2024
3691ea3
Try to use xshm in linux
lindexi Sep 26, 2024
156696a
Add xshm log
lindexi Sep 26, 2024
1a1fdf3
Dispose X11ShmImage AvailableQueue when X11ShmImageManager Disposed
lindexi Sep 26, 2024
2a8cbdf
Send render by XShmPutImage
lindexi Sep 26, 2024
0f01872
Try to output all event
lindexi Sep 26, 2024
330141b
Fix using display without lock
lindexi Sep 29, 2024
97e3673
Force use XSHM
lindexi Sep 29, 2024
f652e8a
Revert "Force use XSHM"
lindexi Sep 29, 2024
1609f47
Using `_x11.Display` instead of `_x11.DeferredDisplay`
lindexi Sep 29, 2024
a4a409f
Remove the Render property in X11ShmFramebufferContext
lindexi Sep 29, 2024
e6d57f5
Disable the log of Xshm and remove the console message
lindexi Sep 29, 2024
0bd00df
Pass the ShouldRenderOnUiThread to XShm
lindexi Sep 29, 2024
da52ddf
Fix thread safe issues, the GetOrCreateImage in X11ShmImageManager wi…
lindexi Sep 29, 2024
3dee126
Re-define the XShmCompletionEvent
lindexi Sep 29, 2024
1a04014
Fix compile fail in net standard
lindexi Sep 29, 2024
17e1bf9
Add the MaxXShmSwapchainFrameCount property to X11ShmFramebufferContext
lindexi Sep 29, 2024
9641de1
Add synchronously wait for current presentation count than max swapch…
lindexi Sep 29, 2024
968607f
Update the XShm log
lindexi Sep 29, 2024
ef01609
Make Analyzer happy
lindexi Sep 29, 2024
6ac7b94
Bring the LastSize to the lock to fix the thread safe issues
lindexi Sep 29, 2024
ed65472
Extract the ClearAvailableQueue in X11ShmImageManager
lindexi Sep 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions samples/RenderDemo/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using System.Collections.Generic;

namespace RenderDemo
{
Expand Down Expand Up @@ -29,6 +30,14 @@ static AppBuilder BuildAvaloniaApp()
.With(new Win32PlatformOptions
{
OverlayPopups = true,
})
.With(new X11PlatformOptions()
{
UseXShmFramebuffer = true,
RenderingMode = new List<X11RenderingMode>()
{
X11RenderingMode.Software,
}
})
.UsePlatformDetect()
.LogToTrace();
Expand Down
34 changes: 34 additions & 0 deletions src/Avalonia.X11/LibC.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Runtime.InteropServices;

namespace Avalonia.X11;

internal static class LibC
{
[DllImport("libc", SetLastError = true)]
public static extern IntPtr shmat(int shmid, IntPtr shmaddr, int shmflg);

[DllImport("libc", SetLastError = true)]
public static extern int shmdt(IntPtr shmaddr);

/// <summary>
/// create key if key does not exist
/// </summary>
public const int IPC_CREAT = 01000;

/// <summary>
/// private key
/// </summary>
public const int IPC_PRIVATE = 0;

/// <summary>
/// Remove the IPC object
/// </summary>
public const int IPC_RMID = 0;

[DllImport("libc", SetLastError = true)]
public static extern int shmget(int key, IntPtr size, int shmflg);

[DllImport("libc", SetLastError = true)]
public static extern int shmctl(int shmid, int cmd, IntPtr buf);
}
2 changes: 2 additions & 0 deletions src/Avalonia.X11/X11Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ public class X11PlatformOptions
/// </summary>
public bool? UseRetainedFramebuffer { get; set; }

public bool? UseXShmFramebuffer { get; set; }

public X11PlatformOptions()
{
try
Expand Down
5 changes: 3 additions & 2 deletions src/Avalonia.X11/X11Structs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,8 @@ internal enum XWindowClass {
InputOnly = 2
}

internal enum XEventName {
internal enum XEventName
{
KeyPress = 2,
KeyRelease = 3,
ButtonPress = 4,
Expand Down Expand Up @@ -772,7 +773,7 @@ internal enum XEventName {
ClientMessage = 33,
MappingNotify = 34,
GenericEvent = 35,
LASTEvent
LASTEvent
}

[Flags]
Expand Down
43 changes: 43 additions & 0 deletions src/Avalonia.X11/X11Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib;
using static Avalonia.X11.XShmExtensions.XShm;
using Avalonia.Input.Platform;
using System.Runtime.InteropServices;
using Avalonia.Dialogs;
using Avalonia.Platform.Storage.FileIO;
using Avalonia.X11.XShmExtensions;

// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
Expand Down Expand Up @@ -195,6 +197,42 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent, bool ov
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle,
depth, _platform.Options.UseRetainedFramebuffer ?? false)
};

if (_platform.Options.UseXShmFramebuffer is true)
{
TryInsertX11ShmFramebufferSurface();

void TryInsertX11ShmFramebufferSurface()
{
if (depth != 32)
{
// If the depth is not 32, we should do some conversion to make the XShmPutImage work. But the conversion is slowly, so we should not use XShmPutImage when the depth is not 32.
return;
}

var status = XShmQueryExtension(_x11.DeferredDisplay);
if (status == 0)
{
// XShmQueryExtension failed
return;
}

status = XShmQueryVersion(_x11.DeferredDisplay, out var major, out var minor, out var pixmaps);

if (status == 0)
{
// XShmQueryExtension failed
return;
}

// The reason for using `_x11.Display` instead of `_x11.DeferredDisplay` is that XSHM requires pushing to a Display that can receive Events, in order to obtain the XShmCompletionEvent when rendering is complete.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeferredDisplay should be used for rendering since compositor runs on a separate thread. You can get events from that connection too as previously discussed in #16690 (reply in thread) (see DeferredDisplayEvents class)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kekekeks The DeferredDisplay do not call the XSelectInput, and I do not know how to recevice the XShmCompletionEvent from DeferredDisplay. Thank you.

// Do not use `_renderHandle`, because the `_renderHandle` was introduced to fix gl, and the XShm should receive the events from the `_handle`. In other words, the XShm should only be use when disable the gl, which means the `_renderHandle` is equal to `_handle`.
var x11ShmFramebufferSurface =
new X11ShmFramebufferSurface(this, _x11.Display, _handle, visual, depth, platform.Options.ShouldRenderOnUIThread);
_x11ShmFramebufferSurface = x11ShmFramebufferSurface;
surfaces.Insert(0, x11ShmFramebufferSurface);
}
}

if (egl != null)
surfaces.Insert(0,
Expand Down Expand Up @@ -611,6 +649,10 @@ private void OnEvent(ref XEvent ev)
return;
HandleKeyEvent(ref ev);
}
else if ((int) ev.type == _x11ShmFramebufferSurface?.XShmCompletionType)
{
_x11ShmFramebufferSurface.OnXShmCompletionEvent(ev);
}
}

private Thickness? GetFrameExtents()
Expand Down Expand Up @@ -798,6 +840,7 @@ private static RawInputModifiers TranslateModifiers(XModifierMask state)
new PixelSize(MaxWindowDimension, MaxWindowDimension));

private double _scaling = 1;
private X11ShmFramebufferSurface? _x11ShmFramebufferSurface;

private void ScheduleInput(RawInputEventArgs args, ref XEvent xev)
{
Expand Down
5 changes: 5 additions & 0 deletions src/Avalonia.X11/XLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ public static extern int XChangeActivePointerGrab(IntPtr display, EventMask even
[DllImport(libX11)]
public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image,
int srcx, int srcy, int destx, int desty, uint width, uint height);

[DllImport(libX11)]
public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, XImage* image,
int srcx, int srcy, int destx, int desty, uint width, uint height);

[DllImport(libX11)]
public static extern int XSync(IntPtr display, bool discard);

Expand Down
14 changes: 14 additions & 0 deletions src/Avalonia.X11/XShmExtensions/X11ShmDebugLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Diagnostics;
using Avalonia.Logging;

namespace Avalonia.X11.XShmExtensions;

internal static class X11ShmDebugLogger
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger.TryGet(LogEventLevel.Information, "X11")?.Log(...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kekekeks Thank you. I intend for the X11ShmDebugLogger to be used by Avalonia framework developers when investigating the XShm module, rather than by application developers using the Avalonia framework. Application developers should not be misled by the debug output from XShm. I'm not sure if this expectation is reasonable.

{
[Conditional("Disable")] // Do not open it unless you need to debug XShm
public static void WriteLine(string message)
{
Logger.TryGet(LogEventLevel.Debug, LogArea.X11Platform)?.Log(null, message);
}
}
55 changes: 55 additions & 0 deletions src/Avalonia.X11/XShmExtensions/X11ShmFramebufferContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Avalonia.Logging;
using ShmSeg = System.UInt64;

namespace Avalonia.X11.XShmExtensions;

class X11ShmFramebufferContext
{
public X11ShmFramebufferContext(X11Window x11Window, IntPtr display, IntPtr windowXId, IntPtr visual, int depth, bool shouldRenderOnUiThread)
{
X11Window = x11Window;
Display = display;
WindowXId = windowXId;
Visual = visual;
Depth = depth;
ShouldRenderOnUiThread = shouldRenderOnUiThread;
}

public X11Window X11Window { get; }

public IntPtr Display { get; }

public IntPtr WindowXId { get; }

public IntPtr Visual { get; }
public int Depth { get; }
public bool ShouldRenderOnUiThread { get; }

/// <summary>
/// The maximum number of XShmSwapchain frame count.
/// </summary>
public int MaxXShmSwapchainFrameCount => 2;

public void OnXShmCompletion(ShmSeg shmseg)
{
X11ShmDebugLogger.WriteLine($"[X11ShmFramebufferContext] OnXShmCompletion");
if (_shmImageDictionary.Remove(shmseg, out var image))
{
image.ShmImageManager.OnXShmCompletion(image);
}
else
{
// Unexpected case, all the X11ShmImage should be registered in the dictionary
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this, $"[X11ShmFramebufferContext][OnXShmCompletion] [Warn] Can not find shmseg={shmseg} in Dictionary!!!");
}
}

public void RegisterX11ShmImage(X11ShmImage image)
{
_shmImageDictionary[image.ShmSeg] = image;
}

private readonly Dictionary<ShmSeg, X11ShmImage> _shmImageDictionary = new Dictionary<ShmSeg, X11ShmImage>();
}
41 changes: 41 additions & 0 deletions src/Avalonia.X11/XShmExtensions/X11ShmFramebufferSurface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using Avalonia.Controls.Platform.Surfaces;

using ShmSeg = System.UInt64;

namespace Avalonia.X11.XShmExtensions;

internal class X11ShmFramebufferSurface : IFramebufferPlatformSurface
{
public X11ShmFramebufferSurface(X11Window x11Window, IntPtr display, IntPtr windowHandle, IntPtr visual, int depth,
bool shouldRenderOnUiThread)
{
// From https://www.x.org/releases/X11R7.5/doc/Xext/mit-shm.html
// > The event type value that will be used can be determined at run time with a line of the form:
// > int CompletionType = XShmGetEventBase (display) + ShmCompletion;
const int ShmCompletion = 0;
XShmCompletionType = XShm.XShmGetEventBase(display) + ShmCompletion;

_context = new X11ShmFramebufferContext(x11Window, display, windowHandle, visual, depth, shouldRenderOnUiThread);
}

public int XShmCompletionType { get; }

private readonly X11ShmFramebufferContext _context;

public IFramebufferRenderTarget CreateFramebufferRenderTarget()
{
X11ShmDebugLogger.WriteLine("[X11ShmFramebufferSurface] CreateFramebufferRenderTarget");

return new X11ShmImageSwapchain(_context);
}

public unsafe void OnXShmCompletionEvent(XEvent @event)
{
var p = &@event;
var xShmCompletionEvent = (XShmCompletionEvent*)p;
ShmSeg shmseg = xShmCompletionEvent->shmseg;
X11ShmDebugLogger.WriteLine($"[X11ShmFramebufferSurface][OnXShmCompletionEvent] shmseg={shmseg}");
_context.OnXShmCompletion(shmseg);
}
}
87 changes: 87 additions & 0 deletions src/Avalonia.X11/XShmExtensions/X11ShmImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ShmSeg = System.UInt64;
using static Avalonia.X11.LibC;
using static Avalonia.X11.XShmExtensions.XShm;
using System.Runtime.InteropServices;

namespace Avalonia.X11.XShmExtensions;

internal unsafe class X11ShmImage : IDisposable
{
public X11ShmImage(PixelSize size, X11ShmImageManager x11ShmImageManager)
{
ShmImageManager = x11ShmImageManager;
// The XShmSegmentInfo struct will store in XImage, and it must pin the address.
IntPtr pXShmSegmentInfo = Marshal.AllocHGlobal(Marshal.SizeOf<XShmSegmentInfo>());
var pShmSegmentInfo = (XShmSegmentInfo*)pXShmSegmentInfo;
PShmSegmentInfo = pShmSegmentInfo;

var context = x11ShmImageManager.Context;
var display = context.Display;
var visual = context.Visual;

const int ZPixmap = 2;

var width = size.Width;
var height = size.Height;

Size = size;

var depth = context.Depth;
Debug.Assert(depth is 32, "The PixelFormat must be Bgra8888, so that the depth should be 32.");

IntPtr data = IntPtr.Zero;

var shmImage = (XImage*)XShmCreateImage(display, visual, (uint)depth, ZPixmap, data, pShmSegmentInfo,
(uint)width, (uint)height);
ShmImage = shmImage;

var mapLength = new IntPtr(width * ByteSizeOfPixel * height);
var shmid = shmget(IPC_PRIVATE, mapLength, IPC_CREAT | 0777);
pShmSegmentInfo->shmid = shmid;

var shmaddr = shmat(shmid, IntPtr.Zero, 0);

if(shmaddr == new IntPtr(-1))
{
shmctl(shmid, IPC_RMID, IntPtr.Zero);
throw new InvalidOperationException("Failed to shmat");
}

pShmSegmentInfo->shmaddr = (char*)shmaddr.ToPointer();
shmImage->data = data = shmaddr;

XShmAttach(display, pShmSegmentInfo);

X11ShmDebugLogger.WriteLine($"[X11ShmImage] CreateX11ShmImage Size={Size} shmid={shmid:X} shmaddr={shmaddr}");
}

public X11ShmImageManager ShmImageManager { get; }

public XImage* ShmImage { get; set; }
public XShmSegmentInfo* PShmSegmentInfo { get; }
public IntPtr ShmAddr => new IntPtr(PShmSegmentInfo->shmaddr);

public const int ByteSizeOfPixel = 4;

public PixelSize Size { get; }

public ShmSeg ShmSeg => PShmSegmentInfo->shmseg;

public void Dispose()
{
var context = ShmImageManager.Context;
XShmDetach(context.Display, PShmSegmentInfo);

shmdt(ShmAddr);
shmctl(PShmSegmentInfo->shmid, IPC_RMID, IntPtr.Zero);

Marshal.FreeHGlobal(new IntPtr(PShmSegmentInfo));

X11ShmDebugLogger.WriteLine($"[X11ShmImage] Dispose");
}
}
Loading
Loading