Skip to content

Commit

Permalink
feat(LinuxFramebuffer): Allow set DRM output Connector (#14878)
Browse files Browse the repository at this point in the history
* feat(LinuxFramebuffer):  Allow set DRM output Connector

* fix: Code formatting

* fix: Address review

* fix: Address review
  • Loading branch information
workgroupengineering authored Mar 11, 2024
1 parent acc3c3c commit fc709df
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 53 deletions.
25 changes: 25 additions & 0 deletions src/Linux/Avalonia.LinuxFramebuffer/DrmConnectorType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Avalonia.LinuxFramebuffer;

/// <summary>
/// specific the type of connector is HDMI-A, DVI, DisplayPort, etc.
/// </summary>
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,
}
16 changes: 15 additions & 1 deletion src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Media;

namespace Avalonia.LinuxFramebuffer
{
/// <summary>
/// DRM Output Options
/// </summary>
public class DrmOutputOptions
{
/// <summary>
Expand All @@ -28,5 +30,17 @@ public class DrmOutputOptions
/// If NULL preferred mode will be used.
/// </summary>
public PixelSize? VideoMode { get; set; }

/// <summary>
/// Specific whether our connector is HDMI-A, DVI, DisplayPort, etc.
/// If NULL preferred connector will be used.
/// </summary>
public DrmConnectorType? ConnectorType { get; init; }

/// <summary>
/// Specific whether connector id using for <see cref="ConnectorType"/>
/// If NULL preferred connector id will be used
/// </summary>
public uint? ConnectorType_Id { get; init; }
}
}
50 changes: 26 additions & 24 deletions src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
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;
using static Avalonia.LinuxFramebuffer.Output.LibDrm;

namespace Avalonia.LinuxFramebuffer.Output
{
[DebuggerDisplay("{Name}")]
public unsafe class DrmConnector
{
private static string[] KnownConnectorTypes =
Expand All @@ -25,18 +27,21 @@ public unsafe class DrmConnector
internal uint EncoderId { get; }
internal List<uint> EncoderIds { get; } = new List<uint>();
public List<DrmModeInfo> Modes { get; } = new List<DrmModeInfo>();
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; c<conn->count_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
Expand Down Expand Up @@ -77,42 +82,38 @@ public DrmEncoder(drmModeEncoder encoder, drmModeCrtc[] crtcs)
}
}
}




public unsafe class DrmResources
{
public List<DrmConnector> Connectors { get; }= new List<DrmConnector>();
public List<DrmConnector> Connectors { get; } = new List<DrmConnector>();
internal Dictionary<uint, DrmEncoder> Encoders { get; } = new Dictionary<uint, DrmEncoder>();
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++)
{
var crtc = drmModeGetCrtc(fd, res->crtcs[c]);
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()
Expand All @@ -133,38 +134,39 @@ void Print(int off, string s)
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}");
}
}

Expand Down
70 changes: 42 additions & 28 deletions src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,42 @@ public SharedContextGraphics(IPlatformGraphicsContext context)

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<DrmConnector> 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;
Expand All @@ -72,12 +85,12 @@ public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutpu
.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);
}
Expand Down Expand Up @@ -112,15 +125,15 @@ uint GetFbIdForBo(IntPtr bo)
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,
Expand All @@ -137,12 +150,12 @@ uint GetFbIdForBo(IntPtr bo)
}

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;
Expand All @@ -159,7 +172,7 @@ uint GetCrtc()
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;
}

Expand All @@ -171,7 +184,7 @@ uint GetCrtc()
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(
Expand All @@ -197,7 +210,7 @@ uint GetCrtc()
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();
Expand All @@ -208,30 +221,30 @@ uint GetCrtc()
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)
{
Expand All @@ -240,7 +253,7 @@ public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
"This platform backend can only create render targets for its primary context");
return CreateGlRenderTarget();
}

class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly DrmOutput _parent;
Expand Down Expand Up @@ -269,7 +282,7 @@ public void Dispose()
{
_parent._deferredContext.GlInterface.Flush();
_parent._eglSurface.SwapBuffers();

var nextBo = gbm_surface_lock_front_buffer(_parent._gbmTargetSurface);
if (nextBo == IntPtr.Zero)
{
Expand All @@ -292,7 +305,8 @@ public void Dispose()
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)
{
Expand Down

0 comments on commit fc709df

Please sign in to comment.