Skip to content

Commit 55a379c

Browse files
authored
Merge pull request #21792 from unoplatform/linux_drm
DRM rendering support for FrameBuffer + GLES support for both FB and X11
2 parents 60b4c24 + 404ae78 commit 55a379c

File tree

48 files changed

+2428
-555
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2428
-555
lines changed

build/test-scripts/linux-skia-runtime-tests.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ if [ -f "$UNO_TESTS_FAILED_LIST" ]; then
1818
fi
1919

2020
cd $SamplesAppArtifactPath
21-
xvfb-run --auto-servernum --server-args='-screen 0 1280x1024x24' sh -c '{ fluxbox & } ; dotnet SamplesApp.Skia.Generic.dll --runtime-tests=$TEST_RESULTS_FILE'
21+
xvfb-run --auto-servernum --server-args='-screen 0 1280x1024x24' sh -c '{ fluxbox & } ; dotnet SamplesApp.Skia.Generic.dll --runtime-tests=$TEST_RESULTS_FILE' || true # sometimes we crash during app shutdown, so we're forcing a 0 exit code
2222

2323
## Export the failed tests list for reuse in a pipeline retry
2424
pushd $BUILD_SOURCESDIRECTORY/src/Uno.NUnitTransformTool

src/SamplesApp/SamplesApp.Shared/App.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ public static void ConfigureLogging()
492492

493493
// Display Skia related information
494494
builder.AddFilter("Uno.UI.Runtime.Skia", LogLevel.Debug);
495+
builder.AddFilter("Uno.WinUI.Runtime.Skia", LogLevel.Debug);
495496
builder.AddFilter("Uno.UI.Skia", LogLevel.Debug);
496497

497498
// builder.AddFilter("Uno.UI.Runtime.Skia", LogLevel.Trace);

src/SamplesApp/SamplesApp.Skia.Generic/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ private static void Run()
5757
.UseX11(hostBuilder => hostBuilder.PreloadMediaPlayer(true))
5858
.UseWin32(hostBuilder => hostBuilder.PreloadMediaPlayer(true))
5959
.UseWindows()
60-
.UseLinuxFrameBuffer()
60+
.UseLinuxFrameBuffer(hostBuilder => hostBuilder.XkbKeymap(new(layout: "us,ara", options: "grp:alt_shift_toggle")))
6161
.UseWindows(b => b
6262
.WpfApplication(() =>
6363
{

src/Uno.UI.Composition/Composition/SkiaExtensions.skia.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public static Size ToSize(this SKSize size)
2626
public static SKColor ToSKColor(this Color color)
2727
=> new SKColor(red: color.R, green: color.G, blue: color.B, alpha: color.A);
2828

29+
public static SKColor ToSKColor(this System.Drawing.Color color)
30+
=> new SKColor(red: color.R, green: color.G, blue: color.B, alpha: color.A);
31+
2932
public static SKColor ToSKColor(this Color color, double alphaMultiplier)
3033
=> new SKColor(red: color.R, green: color.G, blue: color.B, alpha: (byte)(color.A * alphaMultiplier));
3134

Lines changed: 116 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,129 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Drawing;
4+
using Windows.Graphics.Display;
25
using Uno.UI.Hosting;
36
using Uno.UI.Runtime.Skia.Linux.FrameBuffer;
47

58
namespace Uno.UI.Runtime.Skia;
69

7-
internal class FramebufferHostBuilder : IPlatformHostBuilder
10+
public class FramebufferHostBuilder : IPlatformHostBuilder
811
{
9-
public FramebufferHostBuilder()
12+
internal FramebufferHostBuilder()
1013
{
1114
}
1215

13-
public bool IsSupported
16+
bool IPlatformHostBuilder.IsSupported
1417
=> OperatingSystem.IsLinux();
1518

16-
public UnoPlatformHost Create(Func<Microsoft.UI.Xaml.Application> appBuilder, Type appType)
17-
=> new FrameBufferHost(appBuilder);
19+
UnoPlatformHost IPlatformHostBuilder.Create(Func<Microsoft.UI.Xaml.Application> appBuilder, Type appType)
20+
=> new FrameBufferHost(appBuilder, this);
21+
22+
/// <summary>
23+
/// Shows the mouse cursor as a small circle. If this method is not called,
24+
/// then by default, the cursor will be shown only after the first mouse
25+
/// event received from libinput and will not be shown if only touch events
26+
/// are received. This behavior is useful if you're using a touch screen
27+
/// and don't need to see a cursor.
28+
/// </summary>
29+
public FramebufferHostBuilder EnableMouseCursor(float radius, Color color)
30+
{
31+
MouseCursorRadius = radius;
32+
MouseCursorColor = color;
33+
ShowMouseCursor = true;
34+
return this;
35+
}
36+
37+
/// <summary>
38+
/// Hides the mouse cursor. If this method is not called,
39+
/// then by default, the cursor will be shown only after the first mouse
40+
/// event received from libinput and will not be shown if only touch events
41+
/// are received. This behavior is useful if you're using a touch screen
42+
/// and don't need to see a cursor.
43+
/// </summary>
44+
public FramebufferHostBuilder DisableMouseCursor()
45+
{
46+
ShowMouseCursor = false;
47+
return this;
48+
}
49+
50+
public FramebufferHostBuilder Orientation(DisplayOrientations orientation)
51+
{
52+
DisplayOrientation = orientation;
53+
return this;
54+
}
55+
56+
/// <summary>
57+
/// Determines if OpenGLES+EGL initialized with DRM+GBM should be used for hardware-accelerated rendering on the
58+
/// Linux Framebuffer target instead of software rendering. If not called, we try to create an OpenGLES context if possible.
59+
/// Otherwise, software rendering will be used.
60+
/// </summary>
61+
/// <param name="cardPath">The path to the DRM device file. If null, the first device found of the form /dev/dri/cardX will be used.</param>
62+
/// <param name="connectorChooser">A delegate that picks which of the available connectors to use. If not supplied, the first one found will be used.</param>
63+
/// <param name="gbmSurfaceColorFormat">
64+
/// The FourCC color format used for the GBM surface created for rendering
65+
/// (this is passed to gbm_surface_create). For more details on the FourCC
66+
/// format and valid values, see https://github.com/torvalds/linux/blob/master/include/uapi/drm/drm_fourcc.h
67+
/// </param>
68+
public FramebufferHostBuilder UseKMSDRM(string? cardPath = null, DRMFourCCColorFormat? gbmSurfaceColorFormat = null, DRMConnectorChooserDelegate? connectorChooser = null)
69+
{
70+
UseDRM = true;
71+
DRMCardPath = cardPath;
72+
GBMSurfaceColorFormat = gbmSurfaceColorFormat ?? DRMFourCCColorFormat.Argb8888;
73+
DRMConnectorChooser = connectorChooser;
74+
return this;
75+
}
76+
77+
/// <summary>
78+
/// Disables the usage of KMS/DRM for hardware acceleration and forces software rendering.
79+
/// </summary>
80+
public FramebufferHostBuilder DisableKMSDRM()
81+
{
82+
UseDRM = false;
83+
return this;
84+
}
85+
86+
/// <summary>
87+
/// Sets the RMLVO parameters to be passed to libxkbcommon's xkb_rule_names for keyboard keymap creation. If unset,
88+
/// the system default is used.
89+
/// For more details on RMLVO, see https://xkbcommon.org/doc/current/xkb-intro.html#RMLVO-intro
90+
/// and https://github.com/xkbcommon/libxkbcommon/blob/99e9b0fc558fb838a04c568bea033c52ffbe704b/include/xkbcommon/xkbcommon.h#L468
91+
/// </summary>
92+
public FramebufferHostBuilder XkbKeymap(XKBKeymapParams keymapParams)
93+
{
94+
KeymapParams = keymapParams;
95+
return this;
96+
}
97+
98+
internal XKBKeymapParams KeymapParams { get; private set; }
99+
100+
internal bool? ShowMouseCursor { get; private set; }
101+
102+
internal Color MouseCursorColor { get; private set; } = Color.FromArgb(255, 0, 0, 0);
103+
104+
internal float MouseCursorRadius { get; private set; } = 5;
105+
106+
internal DisplayOrientations DisplayOrientation { get; private set; } = DisplayOrientations.Landscape;
107+
108+
internal bool? UseDRM { get; private set; }
109+
110+
internal string? DRMCardPath { get; private set; }
111+
112+
internal DRMFourCCColorFormat GBMSurfaceColorFormat { get; private set; } = DRMFourCCColorFormat.Argb8888;
113+
114+
internal DRMConnectorChooserDelegate? DRMConnectorChooser { get; private set; }
115+
116+
public readonly record struct DRMFourCCColorFormat(char C1, char C2, char C3, char C4)
117+
{
118+
internal uint ToInt() => (uint)C1 | (uint)C2 << 8 | (uint)C3 << 16 | (uint)C4 << 24;
119+
120+
internal static DRMFourCCColorFormat Argb8888 { get; } = new('A', 'R', '2', '4');
121+
}
122+
123+
public readonly record struct DRMConnector(uint connectorType, uint connectorTypeId, uint connectorId, string connectorStringRepresentation);
124+
125+
/// <returns>The index of the chosen connector or -1.</returns>
126+
public delegate int DRMConnectorChooserDelegate(IReadOnlyList<DRMConnector> connector);
127+
128+
public readonly record struct XKBKeymapParams(string? model = null, string? rules = null, string? layout = null, string? variant = null, string? options = null);
18129
}
Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
using System.Collections.Generic;
2-
using System.Linq;
3-
using System.Text;
4-
using System.Threading.Tasks;
1+
using System;
52
using Uno.UI.Runtime.Skia;
6-
using Windows.UI.WebUI;
73

84
namespace Uno.UI.Hosting;
95

@@ -14,4 +10,19 @@ public static IUnoPlatformHostBuilder UseLinuxFrameBuffer(this IUnoPlatformHostB
1410
builder.AddHostBuilder(() => new FramebufferHostBuilder());
1511
return builder;
1612
}
13+
14+
public static IUnoPlatformHostBuilder UseLinuxFrameBuffer(this IUnoPlatformHostBuilder builder, Action<FramebufferHostBuilder> action)
15+
{
16+
builder.AddHostBuilder(() =>
17+
{
18+
var fbBuilder = new FramebufferHostBuilder();
19+
if (((IPlatformHostBuilder)fbBuilder).IsSupported)
20+
{
21+
action.Invoke(fbBuilder);
22+
}
23+
return fbBuilder;
24+
});
25+
26+
return builder;
27+
}
1728
}

src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/Devices/Input/FrameBufferInputProvider.cs

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
11
#nullable enable
22

33
using System;
4-
using System.Collections.Generic;
5-
using System.Diagnostics.CodeAnalysis;
64
using System.IO;
75
using System.Threading;
8-
using Uno.Extensions;
9-
using Windows.Devices.Input;
10-
using Windows.Foundation;
11-
using Windows.Graphics.Display;
12-
using Windows.System;
13-
using Windows.UI.Core;
14-
using Windows.UI.Input;
156
using Uno.UI.Runtime.Skia.Native;
167
using static Uno.UI.Runtime.Skia.Native.LibInput;
17-
using static Windows.UI.Input.PointerUpdateKind;
188
using static Uno.UI.Runtime.Skia.Native.libinput_event_type;
19-
using System.Runtime.CompilerServices;
20-
using Uno.Foundation.Extensibility;
9+
using System.Runtime.InteropServices;
2110
using Uno.Foundation.Logging;
2211
using Uno.UI.Runtime.Skia;
2312

@@ -72,8 +61,6 @@ private void Run()
7261
{
7362
_libDevFd = libinput_get_fd(_libInputContext);
7463

75-
var timeval = stackalloc IntPtr[2];
76-
7764
if (Directory.Exists("/dev/input"))
7865
{
7966
foreach (var f in Directory.GetFiles("/dev/input", "event*"))
@@ -83,32 +70,35 @@ private void Run()
8370
this.Log().Debug($"Opening input device {f}");
8471
}
8572

86-
libinput_path_add_device(_libInputContext, f);
73+
var ret = libinput_path_add_device(_libInputContext, f);
74+
if (ret == IntPtr.Zero)
75+
{
76+
this.LogError()?.Error($"{nameof(libinput_path_add_device)} failed on device {f}");
77+
}
8778
}
8879
}
8980

9081
while (!_cts.IsCancellationRequested)
9182
{
9283
IntPtr rawEvent;
93-
libinput_dispatch(_libInputContext);
84+
var ret = libinput_dispatch(_libInputContext);
85+
if (ret < 0)
86+
{
87+
throw new InvalidOperationException($"{nameof(libinput_dispatch)} failed with error {ret}");
88+
}
9489
while ((rawEvent = libinput_get_event(_libInputContext)) != IntPtr.Zero)
9590
{
9691
var type = libinput_event_get_type(rawEvent);
9792

98-
if (this.Log().IsEnabled(LogLevel.Trace))
99-
{
100-
this.Log().Trace($"Got event type (0x{rawEvent:X16}) {type}");
101-
}
93+
this.LogTrace()?.Trace($"Got event type (0x{rawEvent:X16}) {type}");
10294

103-
if (type >= LIBINPUT_EVENT_TOUCH_DOWN
104-
&& type <= LIBINPUT_EVENT_TOUCH_CANCEL
95+
if (type is >= LIBINPUT_EVENT_TOUCH_DOWN and <= LIBINPUT_EVENT_TOUCH_CANCEL
10596
&& TryGetPointers(out var pointers))
10697
{
10798
pointers!.ProcessTouchEvent(rawEvent, type);
10899
}
109100

110-
if (type >= LIBINPUT_EVENT_POINTER_MOTION
111-
&& type <= LIBINPUT_EVENT_POINTER_AXIS
101+
if (type is >= LIBINPUT_EVENT_POINTER_MOTION and <= LIBINPUT_EVENT_POINTER_AXIS
112102
&& TryGetPointers(out pointers))
113103
{
114104
pointers!.ProcessMouseEvent(rawEvent, type);
@@ -120,23 +110,37 @@ private void Run()
120110
}
121111

122112
libinput_event_destroy(rawEvent);
123-
libinput_dispatch(_libInputContext);
113+
var ret2 = libinput_dispatch(_libInputContext);
114+
if (ret2 != 0)
115+
{
116+
this.LogError()?.Error($"{nameof(libinput_dispatch)} failed with error {ret2}");
117+
}
124118
}
125119

126120
var pfd = new pollfd { fd = _libDevFd, events = 1 };
127-
#pragma warning disable CA1806 // Do not ignore method results
128-
Libc.poll(&pfd, (IntPtr)1, -1);
129-
#pragma warning restore CA1806 // Do not ignore method results
121+
var ret3 = Libc.poll(&pfd, 1, -1);
122+
if (ret3 < 0 && this.Log().IsEnabled(LogLevel.Error))
123+
{
124+
var errno = Marshal.GetLastWin32Error();
125+
var errnoStringPtr = Libc.strerror(errno);
126+
var errorString = Marshal.PtrToStringAnsi(errnoStringPtr);
127+
this.Log().Error($"{nameof(Libc.poll)} failed ({errno}) : {errorString}");
128+
}
130129
}
131130
}
132131

133132
public void Dispose()
134133
{
135134
if (_libDevFd != 0)
136135
{
137-
#pragma warning disable CA1806 // Do not ignore method results
138-
Libc.close(_libDevFd);
139-
#pragma warning restore CA1806 // Do not ignore method results
136+
var ret = Libc.close(_libDevFd);
137+
if (ret < 0 && this.Log().IsEnabled(LogLevel.Error))
138+
{
139+
var errno = Marshal.GetLastWin32Error();
140+
var errnoStringPtr = Libc.strerror(errno);
141+
var errorString = Marshal.PtrToStringAnsi(errnoStringPtr);
142+
this.Log().Error($"{nameof(Libc.close)} failed ({errno}) : {errorString}");
143+
}
140144
_libDevFd = 0;
141145

142146
_cts.Cancel();

0 commit comments

Comments
 (0)