nuclear@0: /************************************************************************************ nuclear@0: nuclear@0: Filename : OVR_Linux_Display.cpp nuclear@0: Content : Linux-specific Display declarations nuclear@0: Created : July 2, 2014 nuclear@0: Authors : James Hughes nuclear@0: nuclear@0: Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved. nuclear@0: nuclear@0: Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); nuclear@0: you may not use the Oculus VR Rift SDK except in compliance with the License, nuclear@0: which is provided at the time of installation or download, or which nuclear@0: otherwise accompanies this software in either electronic or hard copy form. nuclear@0: nuclear@0: You may obtain a copy of the License at nuclear@0: nuclear@0: http://www.oculusvr.com/licenses/LICENSE-3.2 nuclear@0: nuclear@0: Unless required by applicable law or agreed to in writing, the Oculus VR SDK nuclear@0: distributed under the License is distributed on an "AS IS" BASIS, nuclear@0: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. nuclear@0: See the License for the specific language governing permissions and nuclear@0: limitations under the License. nuclear@0: nuclear@0: *************************************************************************************/ nuclear@0: nuclear@0: #include "OVR_Linux_Display.h" nuclear@0: #include "../Kernel/OVR_Log.h" nuclear@0: nuclear@0: #include "../../../3rdParty/EDID/edid.h" nuclear@0: nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** Display enumeration Helpers nuclear@0: nuclear@0: namespace OVR { nuclear@0: nuclear@0: static const uint8_t edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff, nuclear@0: 0xff, 0xff, 0xff, 0x00 }; nuclear@0: nuclear@0: static const uint8_t edid_v1_descriptor_flag[] = { 0x00, 0x00 }; nuclear@0: nuclear@0: static const int DESCRIPTOR_DATA = 5; nuclear@0: static const int UNKNOWN_DESCRIPTOR = -1; nuclear@0: static const int DETAILED_TIMING_BLOCK = -2; nuclear@0: nuclear@0: // The following three functions were pulled from OVR_Linux_Display.cpp nuclear@0: // and modified slightly. nuclear@0: static int blockType(uint8_t* block) nuclear@0: { nuclear@0: if (!strncmp((const char*)edid_v1_descriptor_flag, (const char*)block, 2)) nuclear@0: { nuclear@0: if (block[2] != 0) nuclear@0: { nuclear@0: return UNKNOWN_DESCRIPTOR; nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: return block[3]; nuclear@0: } nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: return DETAILED_TIMING_BLOCK; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static char* getMonitorName(const uint8_t* block) nuclear@0: { nuclear@0: static char name[13]; nuclear@0: uint8_t const* ptr = block + DESCRIPTOR_DATA; nuclear@0: nuclear@0: for (int i = 0; i < 13; i++, ptr++) nuclear@0: { nuclear@0: if (*ptr == 0xa) nuclear@0: { nuclear@0: name[i] = 0; nuclear@0: return name; nuclear@0: } nuclear@0: nuclear@0: name[i] = *ptr; nuclear@0: } nuclear@0: nuclear@0: return name; nuclear@0: } nuclear@0: nuclear@0: // Returns -1 on failure, 0 otherwise. nuclear@0: static int parseEdid(uint8_t* edid, Linux::DisplayEDID& edidResult) nuclear@0: { nuclear@0: const int EDID_LENGTH = 0x80; nuclear@0: const int EDID_HEADER = 0x00; nuclear@0: const int EDID_HEADER_END = 0x07; nuclear@0: nuclear@0: // const int EDID_STRUCT_VERSION = 0x12; nuclear@0: // const int EDID_STRUCT_REVISION = 0x13; nuclear@0: nuclear@0: const int MONITOR_NAME = 0xfc; nuclear@0: // const int MONITOR_LIMITS = 0xfd; nuclear@0: const int MONITOR_SERIAL = 0xff; nuclear@0: nuclear@0: // const int ESTABLISHED_TIMING_1 = 0x23; nuclear@0: // const int ESTABLISHED_TIMING_2 = 0x24; nuclear@0: // const int MANUFACTURERS_TIMINGS = 0x25; nuclear@0: nuclear@0: const int DETAILED_TIMING_DESCRIPTIONS_START = 0x36; nuclear@0: const int DETAILED_TIMING_DESCRIPTION_SIZE = 18; nuclear@0: const int NO_DETAILED_TIMING_DESCRIPTIONS = 4; nuclear@0: nuclear@0: // const int DETAILED_TIMING_DESCRIPTION_1 = 0x36; nuclear@0: // const int DETAILED_TIMING_DESCRIPTION_2 = 0x48; nuclear@0: // const int DETAILED_TIMING_DESCRIPTION_3 = 0x5a; nuclear@0: // const int DETAILED_TIMING_DESCRIPTION_4 = 0x6c; nuclear@0: nuclear@0: const char* monitorName = "Unknown"; nuclear@0: uint8_t* block = NULL; nuclear@0: uint8_t checksum = 0; nuclear@0: nuclear@0: for (int i = 0; i < EDID_LENGTH; i++) nuclear@0: { nuclear@0: checksum += edid[i]; nuclear@0: } nuclear@0: nuclear@0: // Bad checksum, fail EDID nuclear@0: if (checksum != 0) nuclear@0: { nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: if (strncmp((const char*)edid + EDID_HEADER, nuclear@0: (const char*)edid_v1_header, EDID_HEADER_END + 1)) nuclear@0: { nuclear@0: // First bytes don't match EDID version 1 header nuclear@0: return -1; nuclear@0: } nuclear@0: nuclear@0: // Monitor name and timings nuclear@0: char serialNumber[14]; nuclear@0: memset(serialNumber, 0, 14); nuclear@0: nuclear@0: block = edid + DETAILED_TIMING_DESCRIPTIONS_START; nuclear@0: nuclear@0: for (int i = 0; i < NO_DETAILED_TIMING_DESCRIPTIONS; nuclear@0: i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) nuclear@0: { nuclear@0: nuclear@0: if (blockType(block) == MONITOR_NAME) nuclear@0: { nuclear@0: monitorName = getMonitorName(block); nuclear@0: } nuclear@0: nuclear@0: if (blockType(block) == MONITOR_SERIAL) nuclear@0: { nuclear@0: memcpy(serialNumber, block + 5, 13); nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: uint8_t vendorString[4] = {0}; nuclear@0: nuclear@0: vendorString[0] = (edid[8] >> 2 & 31) + 64; nuclear@0: vendorString[1] = (((edid[8] & 3) << 3) | (edid[9] >> 5)) + 64; nuclear@0: vendorString[2] = (edid[9] & 31) + 64; nuclear@0: nuclear@0: edidResult.ModelNumber = *(uint16_t*)&edid[10]; nuclear@0: edidResult.MonitorName = monitorName; nuclear@0: edidResult.VendorName = reinterpret_cast(vendorString); nuclear@0: edidResult.SerialNumber = serialNumber; nuclear@0: nuclear@0: // FIXME: Get timings as well nuclear@0: nuclear@0: // std::cout << "# EDID version " << static_cast(edid[EDID_STRUCT_VERSION]) nuclear@0: // << " revision " << static_cast(edid[EDID_STRUCT_REVISION]) nuclear@0: // << std::endl; nuclear@0: nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // Returns -1 in the case of failure, 0 otherwise. nuclear@0: // Parameters: nuclear@0: // data OUT This pointer is modified to point to the output from nuclear@0: // XRRGetOutputProperty. You *must* call XFree on this pointer. nuclear@0: // dataLen OUT The length of the data returned in 'data'. nuclear@0: static int getXRRProperty(struct _XDisplay* display, RROutput output, Atom atom, nuclear@0: uint8_t** data, int* dataLen) nuclear@0: { nuclear@0: unsigned long nitems; nuclear@0: unsigned long bytesAfter; nuclear@0: int actualFormat; nuclear@0: Atom actualType; nuclear@0: nuclear@0: int ret = XRRGetOutputProperty(display, output, atom, 0, 100, nuclear@0: False, False, AnyPropertyType, nuclear@0: &actualType, &actualFormat, &nitems, nuclear@0: &bytesAfter, data); nuclear@0: nuclear@0: if (None != ret) nuclear@0: { nuclear@0: *dataLen = nitems; nuclear@0: return 0; nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: return -1; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: static XRRModeInfo* findModeByXID(XRRScreenResources* screen, RRMode xid) nuclear@0: { nuclear@0: for (int m = 0; m < screen->nmode; ++m) nuclear@0: { nuclear@0: XRRModeInfo* mode = &screen->modes[m]; nuclear@0: if (xid == mode->id) nuclear@0: { nuclear@0: return mode; nuclear@0: } nuclear@0: } nuclear@0: return NULL; nuclear@0: } nuclear@0: nuclear@0: static int discoverExtendedRifts(OVR::Linux::DisplayDesc* descriptorArray, int inputArraySize, bool /*edidInfo*/) nuclear@0: { nuclear@0: int result = 0; nuclear@0: nuclear@0: struct _XDisplay* display = XOpenDisplay(NULL); nuclear@0: nuclear@0: if (display == NULL) nuclear@0: { nuclear@0: OVR::LogError("[Linux Display] Unable to open X Display!"); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: Atom EDIDAtom = XInternAtom(display, RR_PROPERTY_RANDR_EDID, False); nuclear@0: int numScreens = XScreenCount(display); nuclear@0: for (int i = 0; i < numScreens; ++i) nuclear@0: { nuclear@0: Window sr = XRootWindow(display, i); nuclear@0: XRRScreenResources* screen = XRRGetScreenResources(display, sr); nuclear@0: nuclear@0: for (int ii = 0; ii < screen->ncrtc; ++ii) nuclear@0: { nuclear@0: XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screen, screen->crtcs[ii]); nuclear@0: nuclear@0: if (0 == crtcInfo->noutput) nuclear@0: { nuclear@0: XRRFreeCrtcInfo(crtcInfo); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: bool foundOutput = false; nuclear@0: RROutput output = crtcInfo->outputs[0]; nuclear@0: for (int k = 0; k < crtcInfo->noutput; ++k) nuclear@0: { nuclear@0: XRROutputInfo* outputInfo = nuclear@0: XRRGetOutputInfo(display, screen, crtcInfo->outputs[k]); nuclear@0: nuclear@0: for (int kk = 0; kk < outputInfo->nmode; ++kk) nuclear@0: { nuclear@0: if (outputInfo->modes[kk] == crtcInfo->mode) nuclear@0: { nuclear@0: output = crtcInfo->outputs[k]; nuclear@0: foundOutput = true; nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: XRRFreeOutputInfo(outputInfo); nuclear@0: if (foundOutput) nuclear@0: { nuclear@0: break; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: if (!foundOutput) nuclear@0: { nuclear@0: XRRFreeCrtcInfo(crtcInfo); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screen, output); nuclear@0: if (RR_Connected != outputInfo->connection) nuclear@0: { nuclear@0: XRRFreeOutputInfo(outputInfo); nuclear@0: XRRFreeCrtcInfo(crtcInfo); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: // Read EDID associated with crtc. nuclear@0: uint8_t* data = NULL; nuclear@0: int dataLen = 0; nuclear@0: if (getXRRProperty(display, output, EDIDAtom, &data, &dataLen) != 0) nuclear@0: { nuclear@0: // Identify rifts based on EDID. nuclear@0: Linux::DisplayEDID edid; nuclear@0: parseEdid(data, edid); nuclear@0: XFree(data); nuclear@0: data = NULL; nuclear@0: nuclear@0: // TODO: Remove either this 3rdParty call to read EDID data nuclear@0: // or remove our own parsing of the EDID. Probably opt nuclear@0: // to remove our parsing. nuclear@0: MonitorInfo* mi = read_edid_data(display, output); nuclear@0: if (mi == NULL) nuclear@0: { nuclear@0: XRRFreeOutputInfo(outputInfo); nuclear@0: XRRFreeCrtcInfo(crtcInfo); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: if (edid.VendorName == "OVR") nuclear@0: { nuclear@0: if( result >= inputArraySize ) nuclear@0: { nuclear@0: delete mi; nuclear@0: XRRFreeOutputInfo(outputInfo); nuclear@0: XRRFreeCrtcInfo(crtcInfo); nuclear@0: return result; nuclear@0: } nuclear@0: nuclear@0: XRRModeInfo* modeInfo = findModeByXID(screen, crtcInfo->mode); nuclear@0: nuclear@0: int width = modeInfo->width; nuclear@0: int height = modeInfo->height; nuclear@0: nuclear@0: if ( crtcInfo->rotation == RR_Rotate_90 nuclear@0: || crtcInfo->rotation == RR_Rotate_270 ) nuclear@0: { nuclear@0: width = modeInfo->height; nuclear@0: height = modeInfo->width; nuclear@0: } nuclear@0: nuclear@0: int x = crtcInfo->x; nuclear@0: int y = crtcInfo->y; nuclear@0: nuclear@0: // Generate a device ID string similar Windows does it nuclear@0: char device_id[32]; nuclear@0: OVR_sprintf(device_id, 32, "%s%04d-%d", nuclear@0: mi->manufacturer_code, mi->product_code, nuclear@0: screen->crtcs[ii]); nuclear@0: nuclear@0: OVR::Linux::DisplayDesc& desc = descriptorArray[result++]; nuclear@0: desc.DisplayID = device_id; nuclear@0: desc.ModelName = edid.MonitorName; nuclear@0: desc.EdidSerialNumber = edid.SerialNumber; nuclear@0: desc.LogicalResolutionInPixels = Sizei(width, height); nuclear@0: desc.DesktopDisplayOffset = Vector2i(x, y); nuclear@0: nuclear@0: switch (mi->product_code) nuclear@0: { nuclear@0: case 3: desc.DeviceTypeGuess = HmdType_DK2; break; nuclear@0: case 2: desc.DeviceTypeGuess = HmdType_DKHDProto; break; nuclear@0: case 1: desc.DeviceTypeGuess = HmdType_DK1; break; nuclear@0: nuclear@0: default: nuclear@0: case 0: desc.DeviceTypeGuess = HmdType_Unknown; break; nuclear@0: } nuclear@0: nuclear@0: // Hard-coded defaults in case the device doesn't have the nuclear@0: // data itself. DK2 prototypes (0003) or DK HD Prototypes (0002). nuclear@0: if ( desc.DeviceTypeGuess == HmdType_DK2 nuclear@0: || desc.DeviceTypeGuess == HmdType_DKHDProto) nuclear@0: { nuclear@0: desc.LogicalResolutionInPixels = Sizei(1920, 1080); nuclear@0: desc.NativeResolutionInPixels = Sizei(1080, 1920); nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: desc.LogicalResolutionInPixels = Sizei(width, height); nuclear@0: desc.NativeResolutionInPixels = Sizei(width, height); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: delete mi; nuclear@0: mi = NULL; nuclear@0: } nuclear@0: nuclear@0: XRRFreeOutputInfo(outputInfo); nuclear@0: XRRFreeCrtcInfo(crtcInfo); nuclear@0: } nuclear@0: nuclear@0: XRRFreeScreenResources(screen); nuclear@0: } nuclear@0: nuclear@0: XCloseDisplay(display); nuclear@0: nuclear@0: return result; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** Display nuclear@0: nuclear@0: bool Display::Initialize() nuclear@0: { nuclear@0: // Nothing to initialize. OS X only supports compatibility mode. nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: bool Display::GetDriverMode(bool& driverInstalled, bool& compatMode, bool& hideDK1Mode) nuclear@0: { nuclear@0: driverInstalled = false; nuclear@0: compatMode = true; nuclear@0: hideDK1Mode = false; nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: bool Display::SetDriverMode(bool /*compatMode*/, bool /*hideDK1Mode*/) nuclear@0: { nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: DisplaySearchHandle* Display::GetDisplaySearchHandle() nuclear@0: { nuclear@0: return new Linux::LinuxDisplaySearchHandle(); nuclear@0: } nuclear@0: nuclear@0: bool Display::InCompatibilityMode( bool displaySearch ) nuclear@0: { nuclear@0: OVR_UNUSED( displaySearch ); nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: int Display::GetDisplayCount(DisplaySearchHandle* handle, bool extended, bool applicationOnly, bool edidInfo) nuclear@0: { nuclear@0: OVR_UNUSED4(handle, extended, applicationOnly, edidInfo); nuclear@0: nuclear@0: static int extendedCount = -1; nuclear@0: nuclear@0: Linux::LinuxDisplaySearchHandle* localHandle = (Linux::LinuxDisplaySearchHandle*)handle; nuclear@0: if (localHandle == NULL) nuclear@0: { nuclear@0: OVR::LogError("[Linux Display] No search handle passed into GetDisplayCount. Return 0 rifts."); nuclear@0: return 0; nuclear@0: } nuclear@0: nuclear@0: if (extendedCount == -1 || extended) nuclear@0: { nuclear@0: extendedCount = discoverExtendedRifts(localHandle->cachedDescriptorArray, Linux::LinuxDisplaySearchHandle::DescArraySize, edidInfo); nuclear@0: } nuclear@0: nuclear@0: localHandle->extended = true; nuclear@0: localHandle->extendedDisplayCount = extendedCount; nuclear@0: int totalCount = extendedCount; nuclear@0: nuclear@0: /// FIXME: Implement application mode for OS X. nuclear@0: localHandle->application = false; nuclear@0: localHandle->applicationDisplayCount = 0; nuclear@0: nuclear@0: localHandle->displayCount = totalCount; nuclear@0: nuclear@0: return totalCount; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: Ptr Display::GetDisplay( int index, DisplaySearchHandle* handle ) nuclear@0: { nuclear@0: Ptr result = NULL; nuclear@0: nuclear@0: if (index < 0) nuclear@0: { nuclear@0: OVR::LogError("[Linux Display] Invalid index given to GetDisplay."); nuclear@0: return NULL; nuclear@0: } nuclear@0: nuclear@0: Linux::LinuxDisplaySearchHandle* localHandle = (Linux::LinuxDisplaySearchHandle*)handle; nuclear@0: if (localHandle == NULL) nuclear@0: { nuclear@0: OVR::LogError("[Linux Display] No search handle passed into GetDisplay. Return 0 rifts."); nuclear@0: return NULL; nuclear@0: } nuclear@0: nuclear@0: if (localHandle->extended) nuclear@0: { nuclear@0: if (index >= 0 && index < (int)localHandle->extendedDisplayCount) nuclear@0: { nuclear@0: return *new Linux::LinuxDisplayGeneric(localHandle->cachedDescriptorArray[index]); nuclear@0: } nuclear@0: nuclear@0: index -= localHandle->extendedDisplayCount; nuclear@0: } nuclear@0: nuclear@0: if (localHandle->application) nuclear@0: { nuclear@0: OVR::LogError("[Linux Display] Mac does not support application displays."); nuclear@0: } nuclear@0: nuclear@0: return result; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: } // namespace OVR