diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmConnectorType.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmConnectorType.cs new file mode 100644 index 0000000000..4e2b3f3ffe --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmConnectorType.cs @@ -0,0 +1,25 @@ +namespace Avalonia.LinuxFramebuffer; + +/// +/// specific the type of connector is HDMI-A, DVI, DisplayPort, etc. +/// +public enum DrmConnectorType : uint +{ + None, + VGA, + DVI_I, + DVI_D, + DVI_A, + Composite, + S_Video, + LVDS, + Component, + DIN, + DisplayPort, + HDMI_A, + HDMI_B, + TV, + eDP, + Virtual, + DSI, +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index 3439f0edae..6ebb8f9656 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -1,8 +1,10 @@ -using Avalonia.LinuxFramebuffer.Output; using Avalonia.Media; namespace Avalonia.LinuxFramebuffer { + /// + /// DRM Output Options + /// public class DrmOutputOptions { /// @@ -28,5 +30,17 @@ namespace Avalonia.LinuxFramebuffer /// If NULL preferred mode will be used. /// public PixelSize? VideoMode { get; set; } + + /// + /// Specific whether our connector is HDMI-A, DVI, DisplayPort, etc. + /// If NULL preferred connector will be used. + /// + public DrmConnectorType? ConnectorType { get; init; } + + /// + /// Specific whether connector id using for + /// If NULL preferred connector id will be used + /// + public uint? ConnectorType_Id { get; init; } } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs index 850ea6d4a6..6febe6c26b 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs @@ -1,7 +1,8 @@ using System; -using System.IO; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; @@ -9,6 +10,7 @@ using static Avalonia.LinuxFramebuffer.Output.LibDrm; namespace Avalonia.LinuxFramebuffer.Output { + [DebuggerDisplay("{Name}")] public unsafe class DrmConnector { private static string[] KnownConnectorTypes = @@ -25,18 +27,21 @@ namespace Avalonia.LinuxFramebuffer.Output internal uint EncoderId { get; } internal List EncoderIds { get; } = new List(); public List Modes { get; } = new List(); + public DrmConnectorType ConnectorType { get; } + public uint ConnectorType_Id { get; } internal DrmConnector(drmModeConnector* conn) { Connection = conn->connection; Id = conn->connector_id; SizeMm = new Size(conn->mmWidth, conn->mmHeight); SubPixel = conn->subpixel; - for (var c = 0; c < conn->count_encoders;c++) + for (var c = 0; c < conn->count_encoders; c++) EncoderIds.Add(conn->encoders[c]); EncoderId = conn->encoder_id; - for(var c=0; ccount_modes; c++) + for (var c = 0; c < conn->count_modes; c++) Modes.Add(new DrmModeInfo(ref conn->modes[c])); - + ConnectorType = (DrmConnectorType)conn->connector_type; + ConnectorType_Id = conn->connector_type_id; if (conn->connector_type > KnownConnectorTypes.Length - 1) Name = $"Unknown({conn->connector_type})-{conn->connector_type_id}"; else @@ -77,19 +82,17 @@ namespace Avalonia.LinuxFramebuffer.Output } } } - - - + public unsafe class DrmResources { - public List Connectors { get; }= new List(); + public List Connectors { get; } = new List(); internal Dictionary Encoders { get; } = new Dictionary(); public DrmResources(int fd, bool connectorsForceProbe = false) { var res = drmModeGetResources(fd); if (res == null) throw new Win32Exception("drmModeGetResources failed"); - + var crtcs = new drmModeCrtc[res->count_crtcs]; for (var c = 0; c < res->count_crtcs; c++) { @@ -97,22 +100,20 @@ namespace Avalonia.LinuxFramebuffer.Output crtcs[c] = *crtc; drmModeFreeCrtc(crtc); } - + for (var c = 0; c < res->count_encoders; c++) { var enc = drmModeGetEncoder(fd, res->encoders[c]); Encoders[res->encoders[c]] = new DrmEncoder(*enc, crtcs); drmModeFreeEncoder(enc); } - + for (var c = 0; c < res->count_connectors; c++) { var conn = connectorsForceProbe ? drmModeGetConnector(fd, res->connectors[c]) : drmModeGetConnectorCurrent(fd, res->connectors[c]); Connectors.Add(new DrmConnector(conn)); drmModeFreeConnector(conn); } - - } internal void Dump() @@ -133,38 +134,39 @@ namespace Avalonia.LinuxFramebuffer.Output Print(2, "Modes"); foreach (var m in conn.Modes) Print(3, $"{m.Name} {(m.IsPreferred ? "PREFERRED" : "")}"); - - } } } - + public unsafe class DrmCard : IDisposable { public int Fd { get; private set; } public DrmCard(string path = null) { - if(path == null) + if (path == null) { var files = Directory.GetFiles("/dev/dri/"); - foreach(var file in files) + foreach (var file in files) { var match = Regex.Match(file, "card[0-9]+"); - if(match.Success) + if (match.Success) { Fd = open(file, 2, 0); - if(Fd != -1) break; - } + if (Fd != -1) + break; + } } - if(Fd == -1) throw new Win32Exception("Couldn't open /dev/dri/card[0-9]+"); + if (Fd == -1) + throw new Win32Exception("Couldn't open /dev/dri/card[0-9]+"); } - else + else { Fd = open(path, 2, 0); - if(Fd == -1) throw new Win32Exception($"Couldn't open {path}"); + if (Fd == -1) + throw new Win32Exception($"Couldn't open {path}"); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 1cea3ac9ba..ac87c9c796 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -39,29 +39,42 @@ namespace Avalonia.LinuxFramebuffer.Output public IPlatformGraphicsContext GetSharedContext() => _context; } - + public IPlatformGraphics PlatformGraphics { get; private set; } public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo, DrmOutputOptions options = null) { - if(options != null) + if (options != null) _outputOptions = options; Init(card, resources, connector, modeInfo); } - + public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions options = null) { - if(options != null) + if (options != null) _outputOptions = options; - + var card = new DrmCard(path); var resources = card.GetResources(connectorsForceProbe); + IEnumerable connectors = resources.Connectors; + + if (options?.ConnectorType is { } connectorType) + { + connectors = connectors.Where(c => c.ConnectorType == connectorType); + } + + if (options?.ConnectorType_Id is { } connectorType_Id) + { + connectors = connectors.Where(c => c.ConnectorType_Id == connectorType_Id); + } + var connector = - resources.Connectors.FirstOrDefault(x => x.Connection == DrmModeConnection.DRM_MODE_CONNECTED); - if(connector == null) + connectors.FirstOrDefault(x => x.Connection == DrmModeConnection.DRM_MODE_CONNECTED); + + if (connector == null) throw new InvalidOperationException("Unable to find connected DRM connector"); DrmModeInfo mode = null; @@ -72,12 +85,12 @@ namespace Avalonia.LinuxFramebuffer.Output .FirstOrDefault(x => x.Resolution.Width == options.VideoMode.Value.Width && x.Resolution.Height == options.VideoMode.Value.Height); } - + mode ??= connector.Modes.OrderByDescending(x => x.IsPreferred) .ThenByDescending(x => x.Resolution.Width * x.Resolution.Height) //.OrderByDescending(x => x.Resolution.Width * x.Resolution.Height) .FirstOrDefault(); - if(mode == null) + if (mode == null) throw new InvalidOperationException("Unable to find a usable DRM mode"); Init(card, resources, connector, mode); } @@ -112,15 +125,15 @@ namespace Avalonia.LinuxFramebuffer.Output if (data != IntPtr.Zero) return (uint)data.ToInt32(); - var w = gbm_bo_get_width(bo); + var w = gbm_bo_get_width(bo); var h = gbm_bo_get_height(bo); var stride = gbm_bo_get_stride(bo); var handle = gbm_bo_get_handle(bo).u32; var format = gbm_bo_get_format(bo); // prepare for the new ioctl call - var handles = new uint[] {handle, 0, 0, 0}; - var pitches = new uint[] {stride, 0, 0, 0}; + var handles = new uint[] { handle, 0, 0, 0 }; + var pitches = new uint[] { stride, 0, 0, 0 }; var offsets = new uint[4]; var ret = drmModeAddFB2(_card.Fd, w, h, format, handles, pitches, @@ -137,12 +150,12 @@ namespace Avalonia.LinuxFramebuffer.Output } gbm_bo_set_user_data(bo, new IntPtr((int)fbHandle), FbDestroyDelegate); - - + + return fbHandle; } - - + + void Init(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo) { FbDestroyDelegate = FbDestroyCallback; @@ -159,7 +172,7 @@ namespace Avalonia.LinuxFramebuffer.Output foreach (var encId in connector.EncoderIds) { if (resources.Encoders.TryGetValue(encId, out encoder) - && encoder.PossibleCrtcs.Count>0) + && encoder.PossibleCrtcs.Count > 0) return encoder.PossibleCrtcs.First().crtc_id; } @@ -171,7 +184,7 @@ namespace Avalonia.LinuxFramebuffer.Output var device = gbm_create_device(card.Fd); _gbmTargetSurface = gbm_surface_create(device, modeInfo.Resolution.Width, modeInfo.Resolution.Height, GbmColorFormats.GBM_FORMAT_XRGB8888, GbmBoFlags.GBM_BO_USE_SCANOUT | GbmBoFlags.GBM_BO_USE_RENDERING); - if(_gbmTargetSurface == IntPtr.Zero) + if (_gbmTargetSurface == IntPtr.Zero) throw new InvalidOperationException("Unable to create GBM surface"); _eglDisplay = new EglDisplay( @@ -197,7 +210,7 @@ namespace Avalonia.LinuxFramebuffer.Output var initialBufferSwappingColorA = _outputOptions.InitialBufferSwappingColor.A / 255.0f; using (_deferredContext.MakeCurrent(_eglSurface)) { - _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG, + _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG, initialBufferSwappingColorB, initialBufferSwappingColorA); _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); _eglSurface.SwapBuffers(); @@ -208,30 +221,30 @@ namespace Avalonia.LinuxFramebuffer.Output var connectorId = connector.Id; var mode = modeInfo.Mode; - + var res = drmModeSetCrtc(_card.Fd, _crtcId, fbId, 0, 0, &connectorId, 1, &mode); if (res != 0) throw new Win32Exception(res, "drmModeSetCrtc failed"); _mode = mode; _currentBo = bo; - + if (_outputOptions.EnableInitialBufferSwapping) { //Go trough two cycles of buffer swapping (there are render artifacts otherwise) - for(var c=0;c<2;c++) + for (var c = 0; c < 2; c++) using (CreateGlRenderTarget().BeginDraw()) { - _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG, + _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG, initialBufferSwappingColorB, initialBufferSwappingColorA); _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT); } } - + } public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => new RenderTarget(this); - + public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) { @@ -240,7 +253,7 @@ namespace Avalonia.LinuxFramebuffer.Output "This platform backend can only create render targets for its primary context"); return CreateGlRenderTarget(); } - + class RenderTarget : IGlPlatformSurfaceRenderTarget { private readonly DrmOutput _parent; @@ -269,7 +282,7 @@ namespace Avalonia.LinuxFramebuffer.Output { _parent._deferredContext.GlInterface.Flush(); _parent._eglSurface.SwapBuffers(); - + var nextBo = gbm_surface_lock_front_buffer(_parent._gbmTargetSurface); if (nextBo == IntPtr.Zero) { @@ -292,7 +305,8 @@ namespace Avalonia.LinuxFramebuffer.Output var cbHandle = GCHandle.Alloc(flipCb); var ctx = new DrmEventContext { - version = 4, page_flip_handler = Marshal.GetFunctionPointerForDelegate(flipCb) + version = 4, + page_flip_handler = Marshal.GetFunctionPointerForDelegate(flipCb) }; while (waitingForFlip) {