[KMS] Added DRM/GBM framebuffer implementation

This commit is contained in:
thefiddler 2014-06-26 15:40:00 +02:00
parent 19b34446bb
commit 753032b844
12 changed files with 651 additions and 115 deletions

View file

@ -813,6 +813,8 @@
</Compile> </Compile>
<Compile Include="Platform\Linux\Bindings\Libc.cs" /> <Compile Include="Platform\Linux\Bindings\Libc.cs" />
<Compile Include="Platform\Linux\LinuxWindowInfo.cs" /> <Compile Include="Platform\Linux\LinuxWindowInfo.cs" />
<Compile Include="Platform\Linux\LinuxGraphicsContext.cs" />
<Compile Include="Platform\Linux\Bindings\Poll.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>

View file

@ -35,9 +35,9 @@ namespace OpenTK.Platform.Egl
{ {
#region Fields #region Fields
readonly RenderableFlags Renderable; protected readonly RenderableFlags Renderable;
protected EglWindowInfo WindowInfo;
EglWindowInfo WindowInfo;
IntPtr HandleAsEGLContext { get { return Handle.Handle; } set { Handle = new ContextHandle(value); } } IntPtr HandleAsEGLContext { get { return Handle.Handle; } set { Handle = new ContextHandle(value); } }
int swap_interval = 1; // Default interval is defined as 1 in EGL. int swap_interval = 1; // Default interval is defined as 1 in EGL.
@ -66,6 +66,14 @@ namespace OpenTK.Platform.Egl
{ {
Renderable = major > 1 ? RenderableFlags.ES2 : RenderableFlags.ES; Renderable = major > 1 ? RenderableFlags.ES2 : RenderableFlags.ES;
} }
RenderApi api = (Renderable & RenderableFlags.GL) != 0 ? RenderApi.GL : RenderApi.ES;
Debug.Print("[EGL] Binding {0} rendering API.", api);
if (!Egl.BindAPI(api))
{
Debug.Print("[EGL] Failed to bind rendering API. Error: {0}", Egl.GetError());
}
Mode = new EglGraphicsMode().SelectGraphicsMode(window, Mode = new EglGraphicsMode().SelectGraphicsMode(window,
mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples, mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples,
mode.AccumulatorFormat, mode.Buffers, mode.Stereo, mode.AccumulatorFormat, mode.Buffers, mode.Stereo,
@ -81,13 +89,6 @@ namespace OpenTK.Platform.Egl
HandleAsEGLContext = Egl.CreateContext(window.Display, config, shared != null ? shared.HandleAsEGLContext : IntPtr.Zero, attrib_list); HandleAsEGLContext = Egl.CreateContext(window.Display, config, shared != null ? shared.HandleAsEGLContext : IntPtr.Zero, attrib_list);
MakeCurrent(window); MakeCurrent(window);
RenderApi api = (Renderable & RenderableFlags.GL) != 0 ? RenderApi.GL : RenderApi.ES;
Debug.Print("[EGL] Binding rendering API {0}.", api);
if (!Egl.BindAPI(api))
{
Debug.Print("[EGL] Failed to bind rendering API. Error: {0}", Egl.GetError());
}
} }
public EglContext(ContextHandle handle, EglWindowInfo window, IGraphicsContext sharedContext, public EglContext(ContextHandle handle, EglWindowInfo window, IGraphicsContext sharedContext,

View file

@ -35,9 +35,9 @@ namespace OpenTK.Platform.Egl
{ {
class EglUnixContext : EglContext class EglUnixContext : EglContext
{ {
IntPtr GL = OpenTK.Platform.X11.DL.Open("libGL", X11.DLOpenFlags.Lazy);
IntPtr ES1 = OpenTK.Platform.X11.DL.Open("libGLESv1_CM", X11.DLOpenFlags.Lazy); IntPtr ES1 = OpenTK.Platform.X11.DL.Open("libGLESv1_CM", X11.DLOpenFlags.Lazy);
IntPtr ES2 = OpenTK.Platform.X11.DL.Open("libGLESv2", X11.DLOpenFlags.Lazy); IntPtr ES2 = OpenTK.Platform.X11.DL.Open("libGLESv2", X11.DLOpenFlags.Lazy);
IntPtr GL = OpenTK.Platform.X11.DL.Open("libGL", X11.DLOpenFlags.Lazy);
public EglUnixContext(GraphicsMode mode, EglWindowInfo window, IGraphicsContext sharedContext, public EglUnixContext(GraphicsMode mode, EglWindowInfo window, IGraphicsContext sharedContext,
int major, int minor, GraphicsContextFlags flags) int major, int minor, GraphicsContextFlags flags)
@ -70,10 +70,6 @@ namespace OpenTK.Platform.Egl
protected override void Dispose(bool manual) protected override void Dispose(bool manual)
{ {
if (GL != IntPtr.Zero)
{
X11.DL.Close(GL);
}
if (ES1 != IntPtr.Zero) if (ES1 != IntPtr.Zero)
{ {
X11.DL.Close(ES1); X11.DL.Close(ES1);
@ -82,6 +78,10 @@ namespace OpenTK.Platform.Egl
{ {
X11.DL.Close(ES2); X11.DL.Close(ES2);
} }
if (GL != IntPtr.Zero)
{
X11.DL.Close(GL);
}
GL = ES1 = ES2 = IntPtr.Zero; GL = ES1 = ES2 = IntPtr.Zero;

View file

@ -32,12 +32,37 @@ using System.Runtime.InteropServices;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
{ {
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void VBlankCallback(int fd,
int sequence,
int tv_sec,
int tv_usec,
IntPtr user_data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void PageFlipCallback(int fd,
int sequence,
int tv_sec,
int tv_usec,
IntPtr user_data);
class Drm class Drm
{ {
const string lib = "libdrm"; const string lib = "libdrm";
[DllImport(lib, EntryPoint = "drmHandleEvent", CallingConvention = CallingConvention.Cdecl)]
public static extern int HandleEvent(int fd, ref EventContext evctx);
[DllImport(lib, EntryPoint = "drmModeAddFB", CallingConvention = CallingConvention.Cdecl)]
public static extern int ModeAddFB(int fd, int width, int height, byte depth,
byte bpp, int pitch, int bo_handle,
out int buf_id);
[DllImport(lib, EntryPoint = "drmModeRmFB", CallingConvention = CallingConvention.Cdecl)]
public static extern int ModeRmFB(int fd, int bufferId);
[DllImport(lib, EntryPoint = "drmModeGetCrtc", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "drmModeGetCrtc", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ModeGetCrtc(int fd, uint crtcId); public static extern IntPtr ModeGetCrtc(int fd, int crtcId);
[DllImport(lib, EntryPoint = "drmModeFreeConnector", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "drmModeFreeConnector", CallingConvention = CallingConvention.Cdecl)]
public static extern void ModeFreeConnector(IntPtr ptr); public static extern void ModeFreeConnector(IntPtr ptr);
@ -46,27 +71,31 @@ namespace OpenTK.Platform.Linux
public static extern void ModeFreeEncoder(IntPtr ptr); public static extern void ModeFreeEncoder(IntPtr ptr);
[DllImport(lib, EntryPoint = "drmModeGetConnector", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "drmModeGetConnector", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ModeGetConnector(int fd, uint connector_id); public static extern IntPtr ModeGetConnector(int fd, int connector_id);
[DllImport(lib, EntryPoint = "drmModeGetEncoder", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "drmModeGetEncoder", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ModeGetEncoder(int fd, uint encoder_id); public static extern IntPtr ModeGetEncoder(int fd, int encoder_id);
[DllImport(lib, EntryPoint = "drmModeGetResources", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "drmModeGetResources", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ModeGetResources(int fd); public static extern IntPtr ModeGetResources(int fd);
[DllImport(lib, EntryPoint = "drmModePageFlip", CallingConvention = CallingConvention.Cdecl)]
public static extern int ModePageFlip(int fd, int crtc_id, int fb_id,
PageFlipFlags flags, IntPtr user_data);
[DllImport(lib, EntryPoint = "drmModeSetCrtc", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "drmModeSetCrtc", CallingConvention = CallingConvention.Cdecl)]
unsafe public static extern int ModeSetCrtc(int fd, uint crtcId, uint bufferId, unsafe public static extern int ModeSetCrtc(int fd, int crtcId, int bufferId,
uint x, uint y, uint* connectors, int count, ModeInfo* mode); int x, int y, int* connectors, int count, ModeInfo* mode);
} }
enum ModeConnection : byte enum ModeConnection
{ {
Connected = 1, Connected = 1,
Disconnected = 2, Disconnected = 2,
Unknown = 3 Unknown = 3
} }
enum ModeSubPixel : byte enum ModeSubPixel
{ {
Unknown = 1, Unknown = 1,
HorizontalRgb = 2, HorizontalRgb = 2,
@ -76,35 +105,53 @@ namespace OpenTK.Platform.Linux
None = 6 None = 6
} }
[Flags]
enum PageFlipFlags
{
FlipEvent = 0x01,
FlipAsync = 0x02,
FlipFlags = FlipEvent | FlipAsync
}
[StructLayout(LayoutKind.Sequential)]
struct EventContext
{
public int version;
public IntPtr vblank_handler;
public IntPtr page_flip_handler;
public static readonly int Version = 2;
}
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
unsafe struct ModeConnector unsafe struct ModeConnector
{ {
public uint connector_id; public int connector_id;
public uint encoder_id; public int encoder_id;
public uint connector_type; public int connector_type;
public uint connector_type_id; public int connector_type_id;
public ModeConnection connection; public ModeConnection connection;
public uint mmWidth, mmHeight; public int mmWidth, mmHeight;
public ModeSubPixel subpixel; public ModeSubPixel subpixel;
public int count_modes; public int count_modes;
public ModeInfo* modes; public ModeInfo* modes;
public int count_props; public int count_props;
public uint *props; public int *props;
public ulong *prop_values; public long *prop_values;
public int count_encoders; public int count_encoders;
public uint *encoders; public int *encoders;
} }
struct ModeCrtc struct ModeCrtc
{ {
public uint crtc_id; public int crtc_id;
public uint buffer_id; public int buffer_id;
public uint x, y; public int x, y;
public uint width, height; public int width, height;
public int mode_valid; public int mode_valid;
public ModeInfo mode; public ModeInfo mode;
@ -113,11 +160,11 @@ namespace OpenTK.Platform.Linux
struct ModeEncoder struct ModeEncoder
{ {
public uint encoder_id; public int encoder_id;
public uint encoder_type; public int encoder_type;
public uint crtc_id; public int crtc_id;
public uint possible_crtcs; public int possible_crtcs;
public uint possible_clones; public int possible_clones;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@ -127,7 +174,7 @@ namespace OpenTK.Platform.Linux
public ushort hdisplay, hsync_start, hsync_end, htotal, hskew; public ushort hdisplay, hsync_start, hsync_end, htotal, hskew;
public ushort vdisplay, vsync_start, vsync_end, vtotal, vscan; public ushort vdisplay, vsync_start, vsync_end, vtotal, vscan;
public uint vrefresh; // refresh rate * 1000 public int vrefresh; // refresh rate * 1000
public uint flags; public uint flags;
public uint type; public uint type;
@ -138,13 +185,13 @@ namespace OpenTK.Platform.Linux
unsafe struct ModeRes unsafe struct ModeRes
{ {
public int count_fbs; public int count_fbs;
public uint* fbs; public int* fbs;
public int count_crtcs; public int count_crtcs;
public uint* crtcs; public int* crtcs;
public int count_connectors; public int count_connectors;
public uint* connectors; public int* connectors;
public int count_encoders; public int count_encoders;
public uint* encoders; public int* encoders;
public int min_width, max_width; public int min_width, max_width;
public int min_height, max_height; public int min_height, max_height;
} }

View file

@ -32,24 +32,57 @@ using System.Runtime.InteropServices;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
{ {
using GbmDevice = IntPtr; // opaque pointer "struct gbm_device*" using Device = IntPtr; // opaque pointer "struct gbm_device*"
using Surface = IntPtr;
using BufferObject = IntPtr;
using BufferObjectHandle = IntPtr;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void DestroyUserDataCallback(BufferObject bo, IntPtr data);
class Gbm class Gbm
{ {
const string lib = "gbm"; const string lib = "gbm";
[DllImport(lib, EntryPoint = "gbm_bo_get_device", CallingConvention = CallingConvention.Cdecl)]
public static extern Device BOGetDevice(BufferObject bo);
[DllImport(lib, EntryPoint = "gbm_bo_get_handle", CallingConvention = CallingConvention.Cdecl)]
public static extern BufferObjectHandle BOGetHandle(BufferObject bo);
[DllImport(lib, EntryPoint = "gbm_bo_get_height", CallingConvention = CallingConvention.Cdecl)]
public static extern int BOGetHeight(BufferObject bo);
[DllImport(lib, EntryPoint = "gbm_bo_get_width", CallingConvention = CallingConvention.Cdecl)]
public static extern int BOGetWidth(BufferObject bo);
[DllImport(lib, EntryPoint = "gbm_bo_get_stride", CallingConvention = CallingConvention.Cdecl)]
public static extern int BOGetStride(BufferObject bo);
[DllImport(lib, EntryPoint = "gbm_bo_set_user_data", CallingConvention = CallingConvention.Cdecl)]
public static extern void BOSetUserData(BufferObject bo, IntPtr data, DestroyUserDataCallback callback);
[DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)]
public static extern GbmDevice CreateDevice(int fd); public static extern Device CreateDevice(int fd);
[DllImport(lib, EntryPoint = "gbm_device_get_fd", CallingConvention = CallingConvention.Cdecl)]
public static extern int DeviceGetFD(IntPtr gbm);
[DllImport(lib, EntryPoint = "gbm_surface_create", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "gbm_surface_create", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr CreateSurface(GbmDevice gbm, int width, int height, SurfaceFormat format, SurfaceFlags flags); public static extern Surface CreateSurface(Device gbm, int width, int height, SurfaceFormat format, SurfaceFlags flags);
[DllImport(lib, EntryPoint = "gbm_surface_destroy", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "gbm_surface_destroy", CallingConvention = CallingConvention.Cdecl)]
public static extern void DestroySurface(IntPtr surface); public static extern void DestroySurface(IntPtr surface);
[DllImport(lib, EntryPoint = "gbm_device_is_format_supported", CallingConvention = CallingConvention.Cdecl)] [DllImport(lib, EntryPoint = "gbm_device_is_format_supported", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsFormatSupported(GbmDevice gbm, SurfaceFormat format, SurfaceFlags usage); public static extern bool IsFormatSupported(Device gbm, SurfaceFormat format, SurfaceFlags usage);
[DllImport(lib, EntryPoint = "gbm_surface_lock_front_buffer", CallingConvention = CallingConvention.Cdecl)]
public static extern BufferObject LockFrontBuffer(Surface surface);
[DllImport(lib, EntryPoint = "gbm_surface_release_buffer", CallingConvention = CallingConvention.Cdecl)]
public static extern void ReleaseBuffer(Surface surface, BufferObject buffer);
} }
enum SurfaceFormat enum SurfaceFormat

View file

@ -33,7 +33,7 @@ using System.Text;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
{ {
class Libc partial class Libc
{ {
const string lib = "libc"; const string lib = "libc";

View file

@ -0,0 +1,65 @@
#region License
//
// Poll.cs
//
// Author:
// Stefanos A. <stapostol@gmail.com>
//
// Copyright (c) 2006-2014 Stefanos Apostolopoulos
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Runtime.InteropServices;
namespace OpenTK.Platform.Linux
{
partial class Libc
{
[DllImport(lib)]
public static extern int poll(ref PollFD fd, IntPtr fd_count, int timeout);
public static int poll(ref PollFD fd, int fd_count, int timeout)
{
return poll(ref fd, (IntPtr)fd_count, timeout);
}
}
[Flags]
enum PollFlags : short
{
In = 0x01,
Pri = 0x02,
Out = 0x04,
Error = 0x08,
Hup = 0x10,
Nval = 0x20,
}
[StructLayout(LayoutKind.Sequential)]
struct PollFD
{
public int fd;
public PollFlags events;
public PollFlags revents;
}
}

View file

@ -35,53 +35,79 @@ using OpenTK.Graphics;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
{ {
class LinuxDisplayDriver : DisplayDeviceBase // Stores platform-specific information about a display
class LinuxDisplay
{ {
unsafe class LinuxDisplay public IntPtr Connector;
public IntPtr Crtc;
public IntPtr Encoder;
unsafe public ModeConnector* pConnector { get { return (ModeConnector*)Connector; } }
unsafe public ModeCrtc* pCrtc { get { return (ModeCrtc*)Crtc; } }
unsafe public ModeEncoder* pEncoder { get { return (ModeEncoder*)Encoder; } }
/*
public ModeInfo Mode
{ {
public ModeConnector* Connector; get
public ModeEncoder* Encoder;
public ModeCrtc* Crtc;
public ModeInfo Mode
{ {
get if (Crtc == IntPtr.Zero)
{ throw new InvalidOperationException();
if (Crtc == null)
throw new InvalidOperationException();
return Crtc->mode; unsafe
{
return pCrtc->mode;
} }
} }
public ModeInfo OriginalMode; }
*/
public int Id public ModeInfo OriginalMode;
public int Id
{
get
{ {
get if (Crtc == IntPtr.Zero)
throw new InvalidOperationException();
unsafe
{ {
if (Crtc == null) return (int)pCrtc->crtc_id;
throw new InvalidOperationException();
return (int)Crtc->crtc_id;
} }
} }
public LinuxDisplay(ModeConnector* c, ModeEncoder* e, ModeCrtc* r)
{
Connector = c;
Encoder = e;
Crtc = r;
OriginalMode = Crtc->mode; // in case we change resolution later on
}
} }
public LinuxDisplay(IntPtr c, IntPtr e, IntPtr r)
{
Connector = c;
Encoder = e;
Crtc = r;
unsafe
{
OriginalMode = pCrtc->mode; // in case we change resolution later on
}
}
}
class LinuxDisplayDriver : DisplayDeviceBase
{
readonly int FD; readonly int FD;
readonly Dictionary<int, int> DisplayIds = readonly Dictionary<int, int> DisplayIds =
new Dictionary<int, int>(); new Dictionary<int, int>();
public LinuxDisplayDriver(int fd) public LinuxDisplayDriver(int fd)
{ {
FD = fd; Debug.Print("[KMS] Creating LinuxDisplayDriver for fd:{0}", fd);
QueryDisplays(); Debug.Indent();
try
{
FD = fd;
QueryDisplays();
}
finally
{
Debug.Unindent();
}
} }
void QueryDisplays() void QueryDisplays()
@ -109,7 +135,7 @@ namespace OpenTK.Platform.Linux
if (connector != null) if (connector != null)
{ {
if (connector->connection == ModeConnection.Connected && if (connector->connection == ModeConnection.Connected &&
connector->count_modes > 0) connector->count_modes > 0)
{ {
// Connector found! // Connector found!
AddDisplay(connector); AddDisplay(connector);
@ -176,20 +202,28 @@ namespace OpenTK.Platform.Linux
return; return;
} }
LinuxDisplay display = new LinuxDisplay(c, encoder, crtc); LinuxDisplay display = new LinuxDisplay((IntPtr)c, (IntPtr)encoder, (IntPtr)crtc);
if (!DisplayIds.ContainsKey(display.Id)) if (!DisplayIds.ContainsKey(display.Id))
{ {
Debug.Print("[KMS] Adding display {0} as {1}", display.Id, AvailableDevices.Count);
DisplayIds.Add(display.Id, AvailableDevices.Count); DisplayIds.Add(display.Id, AvailableDevices.Count);
} }
List<DisplayResolution> modes = new List<DisplayResolution>(); int mode_count = display.pConnector->count_modes;
for (int i = 0; i < display.Connector->count_modes; i++) Debug.Print("[KMS] Display supports {0} modes", mode_count);
List<DisplayResolution> modes = new List<DisplayResolution>(mode_count);
for (int i = 0; i < mode_count; i++)
{ {
ModeInfo* mode = display.Connector->modes + i; ModeInfo* mode = display.pConnector->modes + i;
DisplayResolution res = GetDisplayResolution(mode); if (mode != null)
modes.Add(res); {
Debug.Print("Mode {0}: {1}x{2} @{3}", i,
mode->hdisplay, mode->vdisplay, mode->vrefresh);
DisplayResolution res = GetDisplayResolution(mode);
modes.Add(res);
}
} }
ModeInfo current_mode = display.Mode; ModeInfo current_mode = display.pCrtc->mode;
DisplayResolution current = GetDisplayResolution(&current_mode); DisplayResolution current = GetDisplayResolution(&current_mode);
// Note: since we are not running a display manager, we are free // Note: since we are not running a display manager, we are free
@ -211,30 +245,27 @@ namespace OpenTK.Platform.Linux
Primary = device; Primary = device;
} }
Debug.Print("[KMS] Detected display {0}", device); Debug.Print("[KMS] Added DisplayDevice {0}", device);
AvailableDevices.Add(device); AvailableDevices.Add(device);
} }
unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode) unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode)
{ {
if (mode == null)
throw new ArgumentNullException();
return new DisplayResolution( return new DisplayResolution(
0, 0, 0, 0,
mode->htotal, mode->vtotal, mode->hdisplay, mode->vdisplay,
32, // This is actually part of the framebuffer, not the DisplayResolution 32, // This is actually part of the framebuffer, not the DisplayResolution
mode->vrefresh / 1000.0f); mode->vrefresh);
} }
unsafe static ModeInfo* GetModeInfo(LinuxDisplay display, DisplayResolution resolution) unsafe static ModeInfo* GetModeInfo(LinuxDisplay display, DisplayResolution resolution)
{ {
for (int i = 0; i < display.Connector->count_modes; i++) for (int i = 0; i < display.pConnector->count_modes; i++)
{ {
ModeInfo* mode = display.Connector->modes + i; ModeInfo* mode = display.pConnector->modes + i;
if (mode != null && if (mode != null &&
mode->htotal == resolution.Width && mode->hdisplay == resolution.Width &&
mode->vtotal == resolution.Height) mode->vdisplay == resolution.Height)
{ {
return mode; return mode;
} }
@ -250,10 +281,10 @@ namespace OpenTK.Platform.Linux
{ {
LinuxDisplay display = (LinuxDisplay)device.Id; LinuxDisplay display = (LinuxDisplay)device.Id;
ModeInfo* mode = GetModeInfo(display, resolution); ModeInfo* mode = GetModeInfo(display, resolution);
uint connector_id = display.Connector->connector_id; int connector_id = display.pConnector->connector_id;
if (mode != null) if (mode != null)
{ {
return Drm.ModeSetCrtc(FD, (uint)display.Id, 0, 0, 0, return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0,
&connector_id, 1, mode) == 0; &connector_id, 1, mode) == 0;
} }
return false; return false;
@ -265,9 +296,9 @@ namespace OpenTK.Platform.Linux
unsafe unsafe
{ {
LinuxDisplay display = (LinuxDisplay)device.Id; LinuxDisplay display = (LinuxDisplay)device.Id;
uint connector_id = display.Connector->connector_id;
ModeInfo mode = display.OriginalMode; ModeInfo mode = display.OriginalMode;
return Drm.ModeSetCrtc(FD, (uint)display.Id, 0, 0, 0, int connector_id = display.pConnector->connector_id;
return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0,
&connector_id, 1, &mode) == 0; &connector_id, 1, &mode) == 0;
} }
} }

View file

@ -29,6 +29,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input; using OpenTK.Input;
@ -48,21 +49,62 @@ namespace OpenTK.Platform.Linux
IJoystickDriver2 JoystickDriver; IJoystickDriver2 JoystickDriver;
IDisplayDeviceDriver DisplayDriver; IDisplayDeviceDriver DisplayDriver;
const string gpu_path = "/dev/dri"; // card0, card1, ...
public LinuxFactory() public LinuxFactory()
{ {
SetupEgl(); // Query all GPUs until we find one that has a connected display.
// This is necessary in multi-gpu systems, where only one GPU
// can output a signal.
// Todo: allow OpenTK to drive multiple GPUs
// Todo: allow OpenTK to run on an offscreen GPU
// Todo: allow the user to pick a GPU
var files = Directory.GetFiles(gpu_path);
foreach (var gpu in files)
{
if (Path.GetFileName(gpu).StartsWith("card"))
{
int test_fd = SetupDisplay(gpu);
if (test_fd >= 0)
{
try
{
DisplayDriver = new LinuxDisplayDriver(test_fd);
if (DisplayDriver.GetDisplay(DisplayIndex.Primary) != null)
{
fd = test_fd;
break;
}
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
Debug.Print("[KMS] GPU '{0}' is not connected, skipping.", gpu);
Libc.close(test_fd);
}
}
}
if (fd == 0)
{
Debug.Print("[Error] No valid GPU found, bailing out.");
throw new PlatformNotSupportedException();
}
} }
#region Private Members #region Private Members
void SetupEgl() int SetupDisplay(string gpu)
{ {
// Todo: support multi-GPU systems Debug.Print("[KMS] Attempting to use gpu '{0}'.", gpu);
string gpu = "/dev/dri/card0";
fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec); int fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec);
if (fd < 0) if (fd < 0)
{ {
throw new NotSupportedException("[KMS] No KMS-capable GPU available"); Debug.Print("[KMS] Failed to open gpu");
return fd;
} }
Debug.Print("[KMS] GPU '{0}' opened as fd:{1}", gpu, fd); Debug.Print("[KMS] GPU '{0}' opened as fd:{1}", gpu, fd);
@ -87,6 +129,8 @@ namespace OpenTK.Platform.Linux
throw new NotSupportedException("[KMS] Failed to initialize EGL display. Error code: " + error); throw new NotSupportedException("[KMS] Failed to initialize EGL display. Error code: " + error);
} }
Debug.Print("[KMS] EGL {0}.{1} initialized successfully on display {2:x}", major, minor, display); Debug.Print("[KMS] EGL {0}.{1} initialized successfully on display {2:x}", major, minor, display);
return fd;
} }
#endregion #endregion
@ -95,21 +139,17 @@ namespace OpenTK.Platform.Linux
public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device) public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device)
{ {
return new LinuxNativeWindow(display, gbm_device, x, y, width, height, title, mode, options, display_device); return new LinuxNativeWindow(display, gbm_device, fd, x, y, width, height, title, mode, options, display_device);
} }
public override IDisplayDeviceDriver CreateDisplayDeviceDriver() public override IDisplayDeviceDriver CreateDisplayDeviceDriver()
{ {
lock (this) return DisplayDriver;
{
DisplayDriver = DisplayDriver ?? new LinuxDisplayDriver(fd);
return DisplayDriver;
}
} }
public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags) public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags)
{ {
return new EglUnixContext(mode, (EglWindowInfo)window, shareContext, major, minor, flags); return new LinuxGraphicsContext(mode, (LinuxWindowInfo)window, shareContext, major, minor, flags);
} }
public override GraphicsContext.GetCurrentContextDelegate CreateGetCurrentGraphicsContext() public override GraphicsContext.GetCurrentContextDelegate CreateGetCurrentGraphicsContext()

View file

@ -0,0 +1,297 @@
#region License
//
// LinuxGraphicsContext.cs
//
// Author:
// thefiddler <stapostol@gmail.com>
//
// Copyright (c) 2006-2014
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using OpenTK.Graphics;
namespace OpenTK.Platform.Linux
{
/// \internal
/// <summary>
/// Defines an IGraphicsContext implementation for the Linux KMS framebuffer.
/// For Linux/X11 and other Unix operating systems, use the more generic
/// <see cref="OpenTK.Platform.Egl.EglUnixContext"/> instead.
/// </summary>
/// <remarks>
/// Note: to display our results, we need to allocate a GBM framebuffer
/// and point the scanout address to that via Drm.ModeSetCrtc.
/// </remarks>
class LinuxGraphicsContext : Egl.EglUnixContext
{
IntPtr bo, bo_next;
int fd;
bool is_flip_queued;
int swap_interval;
public LinuxGraphicsContext(GraphicsMode mode, LinuxWindowInfo window, IGraphicsContext sharedContext,
int major, int minor, GraphicsContextFlags flags)
: base(mode, window, sharedContext, major, minor, flags)
{
if (mode.Buffers < 1)
throw new ArgumentException();
fd = window.FD;
PageFlip = HandlePageFlip;
PageFlipPtr = Marshal.GetFunctionPointerForDelegate(PageFlip);
}
public override void SwapBuffers()
{
base.SwapBuffers();
bo_next = LockSurface();
int fb = GetFramebuffer(bo_next);
if (is_flip_queued)
{
WaitFlip(SwapInterval > 0);
if (is_flip_queued)
{
Debug.Print("[KMS] Dropping frame");
return;
}
}
QueueFlip(fb);
}
public override void Update(IWindowInfo window)
{
WaitFlip(true);
base.SwapBuffers();
bo = LockSurface();
int fb = GetFramebuffer(bo);
SetScanoutRegion(fb);
}
public override int SwapInterval
{
get
{
return swap_interval;
}
set
{
// We only support a SwapInterval of 0 (immediate)
// or 1 (vsynced).
// Todo: add support for SwapInterval of -1 (adaptive).
// This requires a small change in WaitFlip().
swap_interval = MathHelper.Clamp(value, 0, 1);
}
}
void WaitFlip(bool block)
{
PollFD fds = new PollFD();
fds.fd = fd;
fds.events = PollFlags.In;
EventContext evctx = new EventContext();
evctx.version = EventContext.Version;
evctx.page_flip_handler = PageFlipPtr;
int timeout = block ? -1 : 0;
while (is_flip_queued)
{
fds.revents = 0;
if (Libc.poll(ref fds, 1, timeout) < 0)
break;
if ((fds.revents & (PollFlags.Hup | PollFlags.Error)) != 0)
break;
if ((fds.revents & PollFlags.In) != 0)
Drm.HandleEvent(fd, ref evctx);
else
break;
}
// Page flip has taken place, update buffer objects
if (!is_flip_queued)
{
IntPtr gbm_surface = WindowInfo.Handle;
Gbm.ReleaseBuffer(gbm_surface, bo);
bo = bo_next;
}
}
void QueueFlip(int buffer)
{
LinuxWindowInfo wnd = WindowInfo as LinuxWindowInfo;
if (wnd == null)
throw new InvalidOperationException();
unsafe
{
int ret = Drm.ModePageFlip(fd, wnd.DisplayDevice.Id, buffer,
PageFlipFlags.FlipEvent, IntPtr.Zero);
if (ret < 0)
{
Debug.Print("[KMS] Failed to enqueue framebuffer flip. Error: {0}", ret);
}
is_flip_queued = true;
}
}
void SetScanoutRegion(int buffer)
{
LinuxWindowInfo wnd = WindowInfo as LinuxWindowInfo;
if (wnd == null)
throw new InvalidOperationException();
unsafe
{
ModeInfo* mode = wnd.DisplayDevice.pConnector->modes;
int connector_id = wnd.DisplayDevice.pConnector->connector_id;
int crtc_id = wnd.DisplayDevice.Id;
int x = 0;
int y = 0;
int connector_count = 1;
int ret = Drm.ModeSetCrtc(fd, crtc_id, buffer, x, y,
&connector_id, connector_count, mode);
if (ret != 0)
{
Debug.Print("[KMS] Drm.ModeSetCrtc{0}, {1}, {2}, {3}, {4:x}, {5}, {6:x}) failed. Error: {7}",
fd, crtc_id, buffer, x, y, (IntPtr)connector_id, connector_count, (IntPtr)mode, ret);
}
}
}
IntPtr LockSurface()
{
IntPtr gbm_surface = WindowInfo.Handle;
return Gbm.LockFrontBuffer(gbm_surface);
}
int GetFramebuffer(IntPtr bo)
{
if (bo == IntPtr.Zero)
goto fail;
int bo_handle = Gbm.BOGetHandle(bo).ToInt32();
if (bo_handle == 0)
{
Debug.Print("[KMS] Gbm.BOGetHandle({0:x}) failed.", bo);
goto fail;
}
int width = Gbm.BOGetWidth(bo);
int height = Gbm.BOGetHeight(bo);
int bpp = Mode.ColorFormat.BitsPerPixel;
int depth = Mode.Depth;
int stride = Gbm.BOGetStride(bo);
if (width == 0 || height == 0 || bpp == 0)
{
Debug.Print("[KMS] Invalid framebuffer format: {0}x{1} {2} {3} {4}",
width, height, stride, bpp, depth);
goto fail;
}
int buffer;
int ret = Drm.ModeAddFB(
fd, width, height,
(byte)depth, (byte)bpp, stride, bo_handle,
out buffer);
if (ret != 0)
{
Debug.Print("[KMS] Drm.ModeAddFB({0}, {1}, {2}, {3}, {4}, {5}, {6}) failed. Error: {7}",
fd, width, height, depth, bpp, stride, bo_handle, ret);
goto fail;
}
Gbm.BOSetUserData(bo, (IntPtr)buffer, DestroyFB);
return buffer;
fail:
Debug.Print("[Error] Failed to create framebuffer.");
return -1;
}
readonly IntPtr PageFlipPtr;
readonly PageFlipCallback PageFlip;
void HandlePageFlip(int fd,
int sequence,
int tv_sec,
int tv_usec,
IntPtr user_data)
{
is_flip_queued = false;
}
static readonly DestroyUserDataCallback DestroyFB = HandleDestroyFB;
static void HandleDestroyFB(IntPtr bo, IntPtr data)
{
IntPtr gbm = Gbm.BOGetDevice(bo);
int fb = data.ToInt32();
Debug.Print("[KMS] Destroying framebuffer {0}", fb);
if (fb != 0)
{
Drm.ModeRmFB(Gbm.DeviceGetFD(gbm), fb);
}
}
protected override void Dispose(bool manual)
{
if (manual)
{
// Reset the scanout region
LinuxWindowInfo wnd = WindowInfo as LinuxWindowInfo;
if (wnd != null)
{
unsafe
{
int connector_id = wnd.DisplayDevice.pConnector->connector_id;
ModeInfo mode = wnd.DisplayDevice.OriginalMode;
Drm.ModeSetCrtc(fd,
wnd.DisplayDevice.pCrtc->crtc_id,
wnd.DisplayDevice.pCrtc->buffer_id,
wnd.DisplayDevice.pCrtc->width,
wnd.DisplayDevice.pCrtc->height,
&connector_id,
1,
&mode);
}
}
}
base.Dispose(manual);
}
}
}

View file

@ -47,7 +47,7 @@ namespace OpenTK.Platform.Linux
Rectangle bounds; Rectangle bounds;
Size client_size; Size client_size;
public LinuxNativeWindow(IntPtr display, IntPtr gbm, public LinuxNativeWindow(IntPtr display, IntPtr gbm, int fd,
int x, int y, int width, int height, string title, int x, int y, int width, int height, string title,
GraphicsMode mode, GameWindowFlags options, GraphicsMode mode, GameWindowFlags options,
DisplayDevice display_device) DisplayDevice display_device)
@ -58,7 +58,8 @@ namespace OpenTK.Platform.Linux
bounds = new Rectangle(0, 0, width, height); bounds = new Rectangle(0, 0, width, height);
client_size = bounds.Size; client_size = bounds.Size;
window = new LinuxWindowInfo(display); window = new LinuxWindowInfo(display, fd, display_device.Id as LinuxDisplay);
if (!mode.Index.HasValue) if (!mode.Index.HasValue)
{ {
mode = new EglGraphicsMode().SelectGraphicsMode(window, mode, 0); mode = new EglGraphicsMode().SelectGraphicsMode(window, mode, 0);
@ -69,7 +70,17 @@ namespace OpenTK.Platform.Linux
SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout;
if (!Gbm.IsFormatSupported(gbm, format, usage)) if (!Gbm.IsFormatSupported(gbm, format, usage))
{ {
//format = SurfaceFormat.xrgba; Debug.Print("[KMS] Failed to find suitable surface format, using XRGB8888");
format = SurfaceFormat.XRGB8888;
}
// Note: we only support fullscreen windows on KMS.
// We implicitly override the requested width and height
// by the width and height of the DisplayDevice, if any.
if (display_device != null)
{
width = display_device.Width;
height = display_device.Height;
} }
Debug.Print("[KMS] Creating GBM surface on {0:x} with {1}x{2} {3} [{4}]", Debug.Print("[KMS] Creating GBM surface on {0:x} with {1}x{2} {3} [{4}]",

View file

@ -28,15 +28,24 @@
#endregion #endregion
using System; using System;
using System.Diagnostics;
using OpenTK.Platform.Egl; using OpenTK.Platform.Egl;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
{ {
class LinuxWindowInfo : EglWindowInfo class LinuxWindowInfo : EglWindowInfo
{ {
public LinuxWindowInfo(IntPtr display) public int FD { get; private set; }
public LinuxDisplay DisplayDevice { get; private set; }
public LinuxWindowInfo(IntPtr display, int fd, LinuxDisplay display_device)
: base(IntPtr.Zero, display, IntPtr.Zero) : base(IntPtr.Zero, display, IntPtr.Zero)
{ {
if (display_device == null)
throw new ArgumentNullException();
FD = fd;
DisplayDevice = display_device;
// The window handle and surface handle must // The window handle and surface handle must
// be filled in manually once they are known. // be filled in manually once they are known.
} }