Opentk/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs
2014-07-16 14:28:27 +02:00

310 lines
10 KiB
C#

#region License
//
// LinuxDisplayDriver.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.Collections.Generic;
using System.Diagnostics;
using OpenTK;
using OpenTK.Graphics;
namespace OpenTK.Platform.Linux
{
// Stores platform-specific information about a display
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
{
get
{
if (Crtc == IntPtr.Zero)
throw new InvalidOperationException();
unsafe
{
return pCrtc->mode;
}
}
}
*/
public ModeInfo OriginalMode;
public int Id
{
get
{
if (Crtc == IntPtr.Zero)
throw new InvalidOperationException();
unsafe
{
return (int)pCrtc->crtc_id;
}
}
}
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<int, int> DisplayIds =
new Dictionary<int, int>();
public LinuxDisplayDriver(int fd)
{
Debug.Print("[KMS] Creating LinuxDisplayDriver for fd:{0}", fd);
Debug.Indent();
try
{
FD = fd;
QueryDisplays();
}
finally
{
Debug.Unindent();
}
}
void QueryDisplays()
{
unsafe
{
lock (this)
{
AvailableDevices.Clear();
DisplayIds.Clear();
ModeRes* resources = (ModeRes*)Drm.ModeGetResources(FD);
if (resources == null)
{
throw new NotSupportedException("[KMS] DRM ModeGetResources failed");
}
Debug.Print("[KMS] DRM found {0} connectors", resources->count_connectors);
// Search for a valid connector
ModeConnector* connector = null;
for (int i = 0; i < resources->count_connectors; i++)
{
connector = (ModeConnector*)Drm.ModeGetConnector(FD,
*(resources->connectors + i));
if (connector != null)
{
if (connector->connection == ModeConnection.Connected &&
connector->count_modes > 0)
{
// Connector found!
AddDisplay(connector);
}
else
{
// This is not the display we are looking for
Drm.ModeFreeConnector((IntPtr)connector);
connector = null;
}
}
}
if (AvailableDevices.Count == 0)
{
Debug.Print("[KMS] Failed to find any active displays");
}
}
}
}
unsafe void AddDisplay(ModeConnector* c)
{
// Find corresponding encoder
ModeEncoder* encoder = null;
for (int i = 0; i < c->count_encoders && encoder == null; i++)
{
ModeEncoder* e = (ModeEncoder*)Drm.ModeGetEncoder(
FD, *(c->encoders + i));
if (e != null)
{
if (e->encoder_id == c->encoder_id)
{
encoder = e;
}
else
{
Drm.ModeFreeEncoder((IntPtr)e);
}
}
}
if (encoder != null)
{
Debug.Print("[KMS] Encoder {0} found for connector {1}",
encoder->encoder_id, c->connector_id);
}
else
{
Debug.Print("[KMS] Failed to find encoder for connector {0}", c->connector_id);
return;
}
ModeCrtc* crtc = (ModeCrtc*)Drm.ModeGetCrtc(FD, encoder->crtc_id);
if (crtc != null)
{
Debug.Print("[KMS] CRTC {0} found for encoder {1}",
encoder->crtc_id, encoder->encoder_id);
}
else
{
Debug.Print("[KMS] Failed to find crtc {0} for encoder {1}",
encoder->crtc_id, encoder->encoder_id);
return;
}
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);
}
int mode_count = display.pConnector->count_modes;
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.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.pCrtc->mode;
DisplayResolution current = GetDisplayResolution(&current_mode);
// Note: since we are not running a display manager, we are free
// to choose the display layout for multiple displays ourselves.
// We choose the simplest layout: displays are laid out side-by-side
// from left to right. Primary display is the first display we encounter.
System.Drawing.Rectangle bounds =
new System.Drawing.Rectangle(
AvailableDevices.Count == 0 ? 0 : AvailableDevices[0].Bounds.Right,
0,
current.Width,
current.Height);
bool is_primary = AvailableDevices.Count == 0;
DisplayDevice device = new DisplayDevice(current, is_primary,
modes, bounds, display);
if (AvailableDevices.Count == 0)
{
Primary = device;
}
Debug.Print("[KMS] Added DisplayDevice {0}", device);
AvailableDevices.Add(device);
}
unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode)
{
return new DisplayResolution(
0, 0,
mode->hdisplay, mode->vdisplay,
32, // This is actually part of the framebuffer, not the DisplayResolution
mode->vrefresh);
}
unsafe static ModeInfo* GetModeInfo(LinuxDisplay display, DisplayResolution resolution)
{
for (int i = 0; i < display.pConnector->count_modes; i++)
{
ModeInfo* mode = display.pConnector->modes + i;
if (mode != null &&
mode->hdisplay == resolution.Width &&
mode->vdisplay == resolution.Height)
{
return mode;
}
}
return null;
}
#region IDisplayDeviceDriver
public override bool TryChangeResolution(DisplayDevice device, DisplayResolution resolution)
{
unsafe
{
LinuxDisplay display = (LinuxDisplay)device.Id;
ModeInfo* mode = GetModeInfo(display, resolution);
int connector_id = display.pConnector->connector_id;
if (mode != null)
{
return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0,
&connector_id, 1, mode) == 0;
}
return false;
}
}
public override bool TryRestoreResolution(DisplayDevice device)
{
unsafe
{
LinuxDisplay display = (LinuxDisplay)device.Id;
ModeInfo mode = display.OriginalMode;
int connector_id = display.pConnector->connector_id;
return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0,
&connector_id, 1, &mode) == 0;
}
}
#endregion
}
}