diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 794abfa5..4c2c827d 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -813,6 +813,8 @@ + + diff --git a/Source/OpenTK/Platform/Egl/EglContext.cs b/Source/OpenTK/Platform/Egl/EglContext.cs index ee9d4939..575174ff 100644 --- a/Source/OpenTK/Platform/Egl/EglContext.cs +++ b/Source/OpenTK/Platform/Egl/EglContext.cs @@ -35,9 +35,9 @@ namespace OpenTK.Platform.Egl { #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); } } 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; } + + 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.ColorFormat, mode.Depth, mode.Stencil, mode.Samples, 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); 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, diff --git a/Source/OpenTK/Platform/Egl/EglUnixContext.cs b/Source/OpenTK/Platform/Egl/EglUnixContext.cs index 5ceca2d1..94eb2e43 100644 --- a/Source/OpenTK/Platform/Egl/EglUnixContext.cs +++ b/Source/OpenTK/Platform/Egl/EglUnixContext.cs @@ -35,9 +35,9 @@ namespace OpenTK.Platform.Egl { 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 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, int major, int minor, GraphicsContextFlags flags) @@ -70,10 +70,6 @@ namespace OpenTK.Platform.Egl protected override void Dispose(bool manual) { - if (GL != IntPtr.Zero) - { - X11.DL.Close(GL); - } if (ES1 != IntPtr.Zero) { X11.DL.Close(ES1); @@ -82,6 +78,10 @@ namespace OpenTK.Platform.Egl { X11.DL.Close(ES2); } + if (GL != IntPtr.Zero) + { + X11.DL.Close(GL); + } GL = ES1 = ES2 = IntPtr.Zero; diff --git a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs index 7f2bf590..06f43d61 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs @@ -32,12 +32,37 @@ using System.Runtime.InteropServices; 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 { 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)] - 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)] public static extern void ModeFreeConnector(IntPtr ptr); @@ -46,27 +71,31 @@ namespace OpenTK.Platform.Linux public static extern void ModeFreeEncoder(IntPtr ptr); [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)] - 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)] 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)] - unsafe public static extern int ModeSetCrtc(int fd, uint crtcId, uint bufferId, - uint x, uint y, uint* connectors, int count, ModeInfo* mode); + unsafe public static extern int ModeSetCrtc(int fd, int crtcId, int bufferId, + int x, int y, int* connectors, int count, ModeInfo* mode); } - enum ModeConnection : byte + enum ModeConnection { Connected = 1, Disconnected = 2, Unknown = 3 } - enum ModeSubPixel : byte + enum ModeSubPixel { Unknown = 1, HorizontalRgb = 2, @@ -76,35 +105,53 @@ namespace OpenTK.Platform.Linux 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)] unsafe struct ModeConnector { - public uint connector_id; - public uint encoder_id; - public uint connector_type; - public uint connector_type_id; + public int connector_id; + public int encoder_id; + public int connector_type; + public int connector_type_id; public ModeConnection connection; - public uint mmWidth, mmHeight; + public int mmWidth, mmHeight; public ModeSubPixel subpixel; public int count_modes; public ModeInfo* modes; public int count_props; - public uint *props; - public ulong *prop_values; + public int *props; + public long *prop_values; public int count_encoders; - public uint *encoders; + public int *encoders; } struct ModeCrtc { - public uint crtc_id; - public uint buffer_id; + public int crtc_id; + public int buffer_id; - public uint x, y; - public uint width, height; + public int x, y; + public int width, height; public int mode_valid; public ModeInfo mode; @@ -113,11 +160,11 @@ namespace OpenTK.Platform.Linux struct ModeEncoder { - public uint encoder_id; - public uint encoder_type; - public uint crtc_id; - public uint possible_crtcs; - public uint possible_clones; + public int encoder_id; + public int encoder_type; + public int crtc_id; + public int possible_crtcs; + public int possible_clones; } [StructLayout(LayoutKind.Sequential)] @@ -127,7 +174,7 @@ namespace OpenTK.Platform.Linux public ushort hdisplay, hsync_start, hsync_end, htotal, hskew; 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 type; @@ -138,13 +185,13 @@ namespace OpenTK.Platform.Linux unsafe struct ModeRes { public int count_fbs; - public uint* fbs; + public int* fbs; public int count_crtcs; - public uint* crtcs; + public int* crtcs; public int count_connectors; - public uint* connectors; + public int* connectors; public int count_encoders; - public uint* encoders; + public int* encoders; public int min_width, max_width; public int min_height, max_height; } diff --git a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs index 9225e442..4c11eda6 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -32,24 +32,57 @@ using System.Runtime.InteropServices; 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 { 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)] - 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)] - 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)] public static extern void DestroySurface(IntPtr surface); [DllImport(lib, EntryPoint = "gbm_device_is_format_supported", CallingConvention = CallingConvention.Cdecl)] [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 diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs index c571e6d8..a4df9710 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -33,7 +33,7 @@ using System.Text; namespace OpenTK.Platform.Linux { - class Libc + partial class Libc { const string lib = "libc"; diff --git a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs new file mode 100644 index 00000000..67963695 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs @@ -0,0 +1,65 @@ +#region License +// +// Poll.cs +// +// Author: +// Stefanos A. +// +// 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; + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs index a12fd06e..14f6fb82 100644 --- a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -35,53 +35,79 @@ using OpenTK.Graphics; 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; - public ModeEncoder* Encoder; - public ModeCrtc* Crtc; - public ModeInfo Mode + get { - get - { - if (Crtc == null) - throw new InvalidOperationException(); + if (Crtc == IntPtr.Zero) + 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) - throw new InvalidOperationException(); - - return (int)Crtc->crtc_id; + return (int)pCrtc->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 Dictionary DisplayIds = new Dictionary(); public LinuxDisplayDriver(int fd) { - FD = fd; - QueryDisplays(); + Debug.Print("[KMS] Creating LinuxDisplayDriver for fd:{0}", fd); + Debug.Indent(); + try + { + FD = fd; + QueryDisplays(); + } + finally + { + Debug.Unindent(); + } } void QueryDisplays() @@ -109,7 +135,7 @@ namespace OpenTK.Platform.Linux if (connector != null) { if (connector->connection == ModeConnection.Connected && - connector->count_modes > 0) + connector->count_modes > 0) { // Connector found! AddDisplay(connector); @@ -176,20 +202,28 @@ namespace OpenTK.Platform.Linux return; } - LinuxDisplay display = new LinuxDisplay(c, encoder, crtc); + LinuxDisplay display = new LinuxDisplay((IntPtr)c, (IntPtr)encoder, (IntPtr)crtc); if (!DisplayIds.ContainsKey(display.Id)) { + Debug.Print("[KMS] Adding display {0} as {1}", display.Id, AvailableDevices.Count); DisplayIds.Add(display.Id, AvailableDevices.Count); } - List modes = new List(); - for (int i = 0; i < display.Connector->count_modes; i++) + int mode_count = display.pConnector->count_modes; + Debug.Print("[KMS] Display supports {0} modes", mode_count); + List modes = new List(mode_count); + for (int i = 0; i < mode_count; i++) { - ModeInfo* mode = display.Connector->modes + i; - DisplayResolution res = GetDisplayResolution(mode); - modes.Add(res); + ModeInfo* mode = display.pConnector->modes + i; + if (mode != null) + { + 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(¤t_mode); // Note: since we are not running a display manager, we are free @@ -211,30 +245,27 @@ namespace OpenTK.Platform.Linux Primary = device; } - Debug.Print("[KMS] Detected display {0}", device); + Debug.Print("[KMS] Added DisplayDevice {0}", device); AvailableDevices.Add(device); } unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode) { - if (mode == null) - throw new ArgumentNullException(); - return new DisplayResolution( 0, 0, - mode->htotal, mode->vtotal, + mode->hdisplay, mode->vdisplay, 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) { - 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 && - mode->htotal == resolution.Width && - mode->vtotal == resolution.Height) + mode->hdisplay == resolution.Width && + mode->vdisplay == resolution.Height) { return mode; } @@ -250,10 +281,10 @@ namespace OpenTK.Platform.Linux { LinuxDisplay display = (LinuxDisplay)device.Id; ModeInfo* mode = GetModeInfo(display, resolution); - uint connector_id = display.Connector->connector_id; + int connector_id = display.pConnector->connector_id; 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; } return false; @@ -265,9 +296,9 @@ namespace OpenTK.Platform.Linux unsafe { LinuxDisplay display = (LinuxDisplay)device.Id; - uint connector_id = display.Connector->connector_id; 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; } } diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 30661205..11e5e80b 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -29,6 +29,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using OpenTK.Graphics; using OpenTK.Input; @@ -48,21 +49,62 @@ namespace OpenTK.Platform.Linux IJoystickDriver2 JoystickDriver; IDisplayDeviceDriver DisplayDriver; + const string gpu_path = "/dev/dri"; // card0, card1, ... + 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 - void SetupEgl() + int SetupDisplay(string gpu) { - // Todo: support multi-GPU systems - string gpu = "/dev/dri/card0"; - fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec); + Debug.Print("[KMS] Attempting to use gpu '{0}'.", gpu); + + int fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec); 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); @@ -87,6 +129,8 @@ namespace OpenTK.Platform.Linux 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); + + return fd; } #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) { - 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() { - lock (this) - { - DisplayDriver = DisplayDriver ?? new LinuxDisplayDriver(fd); - return DisplayDriver; - } + return DisplayDriver; } 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() diff --git a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs new file mode 100644 index 00000000..f42d1708 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs @@ -0,0 +1,297 @@ +#region License +// +// LinuxGraphicsContext.cs +// +// Author: +// thefiddler +// +// 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 + /// + /// Defines an IGraphicsContext implementation for the Linux KMS framebuffer. + /// For Linux/X11 and other Unix operating systems, use the more generic + /// instead. + /// + /// + /// Note: to display our results, we need to allocate a GBM framebuffer + /// and point the scanout address to that via Drm.ModeSetCrtc. + /// + 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); + } + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index ee3bf89a..16ae7716 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -47,7 +47,7 @@ namespace OpenTK.Platform.Linux Rectangle bounds; 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, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device) @@ -58,7 +58,8 @@ namespace OpenTK.Platform.Linux bounds = new Rectangle(0, 0, width, height); client_size = bounds.Size; - window = new LinuxWindowInfo(display); + window = new LinuxWindowInfo(display, fd, display_device.Id as LinuxDisplay); + if (!mode.Index.HasValue) { mode = new EglGraphicsMode().SelectGraphicsMode(window, mode, 0); @@ -69,7 +70,17 @@ namespace OpenTK.Platform.Linux SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; 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}]", diff --git a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs index 0a1f0933..b6df115a 100644 --- a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs +++ b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs @@ -28,15 +28,24 @@ #endregion using System; +using System.Diagnostics; using OpenTK.Platform.Egl; namespace OpenTK.Platform.Linux { 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) { + if (display_device == null) + throw new ArgumentNullException(); + + FD = fd; + DisplayDevice = display_device; // The window handle and surface handle must // be filled in manually once they are known. }