DrmOutput.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Runtime.InteropServices;
  6. using Avalonia.OpenGL;
  7. using Avalonia.OpenGL.Egl;
  8. using Avalonia.OpenGL.Surfaces;
  9. using Avalonia.Platform;
  10. using Avalonia.Platform.Interop;
  11. using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
  12. using static Avalonia.LinuxFramebuffer.Output.LibDrm;
  13. using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats;
  14. namespace Avalonia.LinuxFramebuffer.Output
  15. {
  16. public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface
  17. {
  18. private DrmOutputOptions _outputOptions = new();
  19. private DrmCard _card;
  20. public PixelSize PixelSize => _mode.Resolution;
  21. public double Scaling
  22. {
  23. get => _outputOptions.Scaling;
  24. set => _outputOptions.Scaling = value;
  25. }
  26. class SharedContextGraphics : IPlatformGraphics
  27. {
  28. private readonly IPlatformGraphicsContext _context;
  29. public SharedContextGraphics(IPlatformGraphicsContext context)
  30. {
  31. _context = context;
  32. }
  33. public bool UsesSharedContext => true;
  34. public IPlatformGraphicsContext CreateContext() => throw new NotSupportedException();
  35. public IPlatformGraphicsContext GetSharedContext() => _context;
  36. }
  37. public IPlatformGraphics PlatformGraphics { get; private set; }
  38. public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo,
  39. DrmOutputOptions options = null)
  40. {
  41. if(options != null)
  42. _outputOptions = options;
  43. Init(card, resources, connector, modeInfo);
  44. }
  45. public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions options = null)
  46. {
  47. if(options != null)
  48. _outputOptions = options;
  49. var card = new DrmCard(path);
  50. var resources = card.GetResources(connectorsForceProbe);
  51. var connector =
  52. resources.Connectors.FirstOrDefault(x => x.Connection == DrmModeConnection.DRM_MODE_CONNECTED);
  53. if(connector == null)
  54. throw new InvalidOperationException("Unable to find connected DRM connector");
  55. DrmModeInfo mode = null;
  56. if (options?.VideoMode != null)
  57. {
  58. mode = connector.Modes
  59. .FirstOrDefault(x => x.Resolution.Width == options.VideoMode.Value.Width &&
  60. x.Resolution.Height == options.VideoMode.Value.Height);
  61. }
  62. mode ??= connector.Modes.OrderByDescending(x => x.IsPreferred)
  63. .ThenByDescending(x => x.Resolution.Width * x.Resolution.Height)
  64. //.OrderByDescending(x => x.Resolution.Width * x.Resolution.Height)
  65. .FirstOrDefault();
  66. if(mode == null)
  67. throw new InvalidOperationException("Unable to find a usable DRM mode");
  68. Init(card, resources, connector, mode);
  69. }
  70. public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo)
  71. {
  72. Init(card, resources, connector, modeInfo);
  73. }
  74. [DllImport("libEGL.so.1")]
  75. static extern IntPtr eglGetProcAddress(string proc);
  76. private GbmBoUserDataDestroyCallbackDelegate FbDestroyDelegate;
  77. private drmModeModeInfo _mode;
  78. private EglDisplay _eglDisplay;
  79. private EglSurface _eglSurface;
  80. private EglContext _deferredContext;
  81. private IntPtr _currentBo;
  82. private IntPtr _gbmTargetSurface;
  83. private uint _crtcId;
  84. void FbDestroyCallback(IntPtr bo, IntPtr userData)
  85. {
  86. drmModeRmFB(_card.Fd, userData.ToInt32());
  87. }
  88. uint GetFbIdForBo(IntPtr bo)
  89. {
  90. if (bo == IntPtr.Zero)
  91. throw new ArgumentException("bo is 0");
  92. var data = gbm_bo_get_user_data(bo);
  93. if (data != IntPtr.Zero)
  94. return (uint)data.ToInt32();
  95. var w = gbm_bo_get_width(bo);
  96. var h = gbm_bo_get_height(bo);
  97. var stride = gbm_bo_get_stride(bo);
  98. var handle = gbm_bo_get_handle(bo).u32;
  99. var format = gbm_bo_get_format(bo);
  100. // prepare for the new ioctl call
  101. var handles = new uint[] {handle, 0, 0, 0};
  102. var pitches = new uint[] {stride, 0, 0, 0};
  103. var offsets = Array.Empty<uint>();
  104. var ret = drmModeAddFB2(_card.Fd, w, h, format, handles, pitches,
  105. offsets, out var fbHandle, 0);
  106. if (ret != 0)
  107. {
  108. // legacy fallback
  109. ret = drmModeAddFB(_card.Fd, w, h, 24, 32, stride, (uint)handle,
  110. out fbHandle);
  111. if (ret != 0)
  112. throw new Win32Exception(ret, $"drmModeAddFb failed {ret}");
  113. }
  114. gbm_bo_set_user_data(bo, new IntPtr((int)fbHandle), FbDestroyDelegate);
  115. return fbHandle;
  116. }
  117. void Init(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo)
  118. {
  119. FbDestroyDelegate = FbDestroyCallback;
  120. _card = card;
  121. uint GetCrtc()
  122. {
  123. if (resources.Encoders.TryGetValue(connector.EncoderId, out var encoder))
  124. {
  125. // Not sure why that should work
  126. return encoder.Encoder.crtc_id;
  127. }
  128. else
  129. {
  130. foreach (var encId in connector.EncoderIds)
  131. {
  132. if (resources.Encoders.TryGetValue(encId, out encoder)
  133. && encoder.PossibleCrtcs.Count>0)
  134. return encoder.PossibleCrtcs.First().crtc_id;
  135. }
  136. throw new InvalidOperationException("Unable to find CRTC matching the desired mode");
  137. }
  138. }
  139. _crtcId = GetCrtc();
  140. var device = gbm_create_device(card.Fd);
  141. _gbmTargetSurface = gbm_surface_create(device, modeInfo.Resolution.Width, modeInfo.Resolution.Height,
  142. GbmColorFormats.GBM_FORMAT_XRGB8888, GbmBoFlags.GBM_BO_USE_SCANOUT | GbmBoFlags.GBM_BO_USE_RENDERING);
  143. if(_gbmTargetSurface == IntPtr.Zero)
  144. throw new InvalidOperationException("Unable to create GBM surface");
  145. _eglDisplay = new EglDisplay(
  146. new EglDisplayCreationOptions
  147. {
  148. Egl = new EglInterface(eglGetProcAddress),
  149. PlatformType = 0x31D7,
  150. PlatformDisplay = device,
  151. SupportsMultipleContexts = true,
  152. SupportsContextSharing = true
  153. });
  154. var surface = _eglDisplay.EglInterface.CreateWindowSurface(_eglDisplay.Handle, _eglDisplay.Config, _gbmTargetSurface, new[] { EglConsts.EGL_NONE, EglConsts.EGL_NONE });
  155. _eglSurface = new EglSurface(_eglDisplay, surface);
  156. _deferredContext = _eglDisplay.CreateContext(null);
  157. PlatformGraphics = new SharedContextGraphics(_deferredContext);
  158. var initialBufferSwappingColorR = _outputOptions.InitialBufferSwappingColor.R / 255.0f;
  159. var initialBufferSwappingColorG = _outputOptions.InitialBufferSwappingColor.G / 255.0f;
  160. var initialBufferSwappingColorB = _outputOptions.InitialBufferSwappingColor.B / 255.0f;
  161. var initialBufferSwappingColorA = _outputOptions.InitialBufferSwappingColor.A / 255.0f;
  162. using (_deferredContext.MakeCurrent(_eglSurface))
  163. {
  164. _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG,
  165. initialBufferSwappingColorB, initialBufferSwappingColorA);
  166. _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
  167. _eglSurface.SwapBuffers();
  168. }
  169. var bo = gbm_surface_lock_front_buffer(_gbmTargetSurface);
  170. var fbId = GetFbIdForBo(bo);
  171. var connectorId = connector.Id;
  172. var mode = modeInfo.Mode;
  173. var res = drmModeSetCrtc(_card.Fd, _crtcId, fbId, 0, 0, &connectorId, 1, &mode);
  174. if (res != 0)
  175. throw new Win32Exception(res, "drmModeSetCrtc failed");
  176. _mode = mode;
  177. _currentBo = bo;
  178. if (_outputOptions.EnableInitialBufferSwapping)
  179. {
  180. //Go trough two cycles of buffer swapping (there are render artifacts otherwise)
  181. for(var c=0;c<2;c++)
  182. using (CreateGlRenderTarget().BeginDraw())
  183. {
  184. _deferredContext.GlInterface.ClearColor(initialBufferSwappingColorR, initialBufferSwappingColorG,
  185. initialBufferSwappingColorB, initialBufferSwappingColorA);
  186. _deferredContext.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
  187. }
  188. }
  189. }
  190. public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() => new RenderTarget(this);
  191. public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context)
  192. {
  193. if (context != _deferredContext)
  194. throw new InvalidOperationException(
  195. "This platform backend can only create render targets for its primary context");
  196. return CreateGlRenderTarget();
  197. }
  198. class RenderTarget : IGlPlatformSurfaceRenderTarget
  199. {
  200. private readonly DrmOutput _parent;
  201. public RenderTarget(DrmOutput parent)
  202. {
  203. _parent = parent;
  204. }
  205. public void Dispose()
  206. {
  207. // We are wrapping GBM buffer chain associated with CRTC, and don't free it on a whim
  208. }
  209. class RenderSession : IGlPlatformSurfaceRenderingSession
  210. {
  211. private readonly DrmOutput _parent;
  212. private readonly IDisposable _clearContext;
  213. public RenderSession(DrmOutput parent, IDisposable clearContext)
  214. {
  215. _parent = parent;
  216. _clearContext = clearContext;
  217. }
  218. public void Dispose()
  219. {
  220. _parent._deferredContext.GlInterface.Flush();
  221. _parent._eglSurface.SwapBuffers();
  222. var nextBo = gbm_surface_lock_front_buffer(_parent._gbmTargetSurface);
  223. if (nextBo == IntPtr.Zero)
  224. {
  225. // Not sure what else can be done
  226. Console.WriteLine("gbm_surface_lock_front_buffer failed");
  227. }
  228. else
  229. {
  230. var fb = _parent.GetFbIdForBo(nextBo);
  231. bool waitingForFlip = true;
  232. drmModePageFlip(_parent._card.Fd, _parent._crtcId, fb, DrmModePageFlip.Event, null);
  233. DrmEventPageFlipHandlerDelegate flipCb =
  234. (int fd, uint sequence, uint tv_sec, uint tv_usec, void* user_data) =>
  235. {
  236. waitingForFlip = false;
  237. };
  238. var cbHandle = GCHandle.Alloc(flipCb);
  239. var ctx = new DrmEventContext
  240. {
  241. version = 4, page_flip_handler = Marshal.GetFunctionPointerForDelegate(flipCb)
  242. };
  243. while (waitingForFlip)
  244. {
  245. var pfd = new pollfd {events = 1, fd = _parent._card.Fd};
  246. poll(&pfd, new IntPtr(1), -1);
  247. drmHandleEvent(_parent._card.Fd, &ctx);
  248. }
  249. cbHandle.Free();
  250. gbm_surface_release_buffer(_parent._gbmTargetSurface, _parent._currentBo);
  251. _parent._currentBo = nextBo;
  252. }
  253. _clearContext.Dispose();
  254. }
  255. public IGlContext Context => _parent._deferredContext;
  256. public PixelSize Size => _parent._mode.Resolution;
  257. public double Scaling => _parent.Scaling;
  258. public bool IsYFlipped { get; }
  259. }
  260. public IGlPlatformSurfaceRenderingSession BeginDraw()
  261. {
  262. return new RenderSession(_parent, _parent._deferredContext.MakeCurrent(_parent._eglSurface));
  263. }
  264. }
  265. public IGlContext CreateContext()
  266. {
  267. throw new NotImplementedException();
  268. }
  269. }
  270. }