ovr_sdk

annotate LibOVR/Src/Displays/OVR_Linux_Display.cpp @ 0:1b39a1b46319

initial 0.4.4
author John Tsiombikas <nuclear@member.fsf.org>
date Wed, 14 Jan 2015 06:51:16 +0200
parents
children
rev   line source
nuclear@0 1 /************************************************************************************
nuclear@0 2
nuclear@0 3 Filename : OVR_Linux_Display.cpp
nuclear@0 4 Content : Linux-specific Display declarations
nuclear@0 5 Created : July 2, 2014
nuclear@0 6 Authors : James Hughes
nuclear@0 7
nuclear@0 8 Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved.
nuclear@0 9
nuclear@0 10 Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License");
nuclear@0 11 you may not use the Oculus VR Rift SDK except in compliance with the License,
nuclear@0 12 which is provided at the time of installation or download, or which
nuclear@0 13 otherwise accompanies this software in either electronic or hard copy form.
nuclear@0 14
nuclear@0 15 You may obtain a copy of the License at
nuclear@0 16
nuclear@0 17 http://www.oculusvr.com/licenses/LICENSE-3.2
nuclear@0 18
nuclear@0 19 Unless required by applicable law or agreed to in writing, the Oculus VR SDK
nuclear@0 20 distributed under the License is distributed on an "AS IS" BASIS,
nuclear@0 21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
nuclear@0 22 See the License for the specific language governing permissions and
nuclear@0 23 limitations under the License.
nuclear@0 24
nuclear@0 25 *************************************************************************************/
nuclear@0 26
nuclear@0 27 #include "OVR_Linux_Display.h"
nuclear@0 28 #include "../Kernel/OVR_Log.h"
nuclear@0 29
nuclear@0 30 #include "../../../3rdParty/EDID/edid.h"
nuclear@0 31
nuclear@0 32 #include <X11/Xlib.h>
nuclear@0 33 #include <X11/Xutil.h>
nuclear@0 34 #include <X11/extensions/Xrandr.h>
nuclear@0 35 #include <X11/Xatom.h>
nuclear@0 36
nuclear@0 37 //-------------------------------------------------------------------------------------
nuclear@0 38 // ***** Display enumeration Helpers
nuclear@0 39
nuclear@0 40 namespace OVR {
nuclear@0 41
nuclear@0 42 static const uint8_t edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff,
nuclear@0 43 0xff, 0xff, 0xff, 0x00 };
nuclear@0 44
nuclear@0 45 static const uint8_t edid_v1_descriptor_flag[] = { 0x00, 0x00 };
nuclear@0 46
nuclear@0 47 static const int DESCRIPTOR_DATA = 5;
nuclear@0 48 static const int UNKNOWN_DESCRIPTOR = -1;
nuclear@0 49 static const int DETAILED_TIMING_BLOCK = -2;
nuclear@0 50
nuclear@0 51 // The following three functions were pulled from OVR_Linux_Display.cpp
nuclear@0 52 // and modified slightly.
nuclear@0 53 static int blockType(uint8_t* block)
nuclear@0 54 {
nuclear@0 55 if (!strncmp((const char*)edid_v1_descriptor_flag, (const char*)block, 2))
nuclear@0 56 {
nuclear@0 57 if (block[2] != 0)
nuclear@0 58 {
nuclear@0 59 return UNKNOWN_DESCRIPTOR;
nuclear@0 60 }
nuclear@0 61 else
nuclear@0 62 {
nuclear@0 63 return block[3];
nuclear@0 64 }
nuclear@0 65 }
nuclear@0 66 else
nuclear@0 67 {
nuclear@0 68 return DETAILED_TIMING_BLOCK;
nuclear@0 69 }
nuclear@0 70 }
nuclear@0 71
nuclear@0 72 static char* getMonitorName(const uint8_t* block)
nuclear@0 73 {
nuclear@0 74 static char name[13];
nuclear@0 75 uint8_t const* ptr = block + DESCRIPTOR_DATA;
nuclear@0 76
nuclear@0 77 for (int i = 0; i < 13; i++, ptr++)
nuclear@0 78 {
nuclear@0 79 if (*ptr == 0xa)
nuclear@0 80 {
nuclear@0 81 name[i] = 0;
nuclear@0 82 return name;
nuclear@0 83 }
nuclear@0 84
nuclear@0 85 name[i] = *ptr;
nuclear@0 86 }
nuclear@0 87
nuclear@0 88 return name;
nuclear@0 89 }
nuclear@0 90
nuclear@0 91 // Returns -1 on failure, 0 otherwise.
nuclear@0 92 static int parseEdid(uint8_t* edid, Linux::DisplayEDID& edidResult)
nuclear@0 93 {
nuclear@0 94 const int EDID_LENGTH = 0x80;
nuclear@0 95 const int EDID_HEADER = 0x00;
nuclear@0 96 const int EDID_HEADER_END = 0x07;
nuclear@0 97
nuclear@0 98 // const int EDID_STRUCT_VERSION = 0x12;
nuclear@0 99 // const int EDID_STRUCT_REVISION = 0x13;
nuclear@0 100
nuclear@0 101 const int MONITOR_NAME = 0xfc;
nuclear@0 102 // const int MONITOR_LIMITS = 0xfd;
nuclear@0 103 const int MONITOR_SERIAL = 0xff;
nuclear@0 104
nuclear@0 105 // const int ESTABLISHED_TIMING_1 = 0x23;
nuclear@0 106 // const int ESTABLISHED_TIMING_2 = 0x24;
nuclear@0 107 // const int MANUFACTURERS_TIMINGS = 0x25;
nuclear@0 108
nuclear@0 109 const int DETAILED_TIMING_DESCRIPTIONS_START = 0x36;
nuclear@0 110 const int DETAILED_TIMING_DESCRIPTION_SIZE = 18;
nuclear@0 111 const int NO_DETAILED_TIMING_DESCRIPTIONS = 4;
nuclear@0 112
nuclear@0 113 // const int DETAILED_TIMING_DESCRIPTION_1 = 0x36;
nuclear@0 114 // const int DETAILED_TIMING_DESCRIPTION_2 = 0x48;
nuclear@0 115 // const int DETAILED_TIMING_DESCRIPTION_3 = 0x5a;
nuclear@0 116 // const int DETAILED_TIMING_DESCRIPTION_4 = 0x6c;
nuclear@0 117
nuclear@0 118 const char* monitorName = "Unknown";
nuclear@0 119 uint8_t* block = NULL;
nuclear@0 120 uint8_t checksum = 0;
nuclear@0 121
nuclear@0 122 for (int i = 0; i < EDID_LENGTH; i++)
nuclear@0 123 {
nuclear@0 124 checksum += edid[i];
nuclear@0 125 }
nuclear@0 126
nuclear@0 127 // Bad checksum, fail EDID
nuclear@0 128 if (checksum != 0)
nuclear@0 129 {
nuclear@0 130 return -1;
nuclear@0 131 }
nuclear@0 132
nuclear@0 133 if (strncmp((const char*)edid + EDID_HEADER,
nuclear@0 134 (const char*)edid_v1_header, EDID_HEADER_END + 1))
nuclear@0 135 {
nuclear@0 136 // First bytes don't match EDID version 1 header
nuclear@0 137 return -1;
nuclear@0 138 }
nuclear@0 139
nuclear@0 140 // Monitor name and timings
nuclear@0 141 char serialNumber[14];
nuclear@0 142 memset(serialNumber, 0, 14);
nuclear@0 143
nuclear@0 144 block = edid + DETAILED_TIMING_DESCRIPTIONS_START;
nuclear@0 145
nuclear@0 146 for (int i = 0; i < NO_DETAILED_TIMING_DESCRIPTIONS;
nuclear@0 147 i++, block += DETAILED_TIMING_DESCRIPTION_SIZE)
nuclear@0 148 {
nuclear@0 149
nuclear@0 150 if (blockType(block) == MONITOR_NAME)
nuclear@0 151 {
nuclear@0 152 monitorName = getMonitorName(block);
nuclear@0 153 }
nuclear@0 154
nuclear@0 155 if (blockType(block) == MONITOR_SERIAL)
nuclear@0 156 {
nuclear@0 157 memcpy(serialNumber, block + 5, 13);
nuclear@0 158 break;
nuclear@0 159 }
nuclear@0 160 }
nuclear@0 161
nuclear@0 162 uint8_t vendorString[4] = {0};
nuclear@0 163
nuclear@0 164 vendorString[0] = (edid[8] >> 2 & 31) + 64;
nuclear@0 165 vendorString[1] = (((edid[8] & 3) << 3) | (edid[9] >> 5)) + 64;
nuclear@0 166 vendorString[2] = (edid[9] & 31) + 64;
nuclear@0 167
nuclear@0 168 edidResult.ModelNumber = *(uint16_t*)&edid[10];
nuclear@0 169 edidResult.MonitorName = monitorName;
nuclear@0 170 edidResult.VendorName = reinterpret_cast<const char*>(vendorString);
nuclear@0 171 edidResult.SerialNumber = serialNumber;
nuclear@0 172
nuclear@0 173 // FIXME: Get timings as well
nuclear@0 174
nuclear@0 175 // std::cout << "# EDID version " << static_cast<int>(edid[EDID_STRUCT_VERSION])
nuclear@0 176 // << " revision " << static_cast<int>(edid[EDID_STRUCT_REVISION])
nuclear@0 177 // << std::endl;
nuclear@0 178
nuclear@0 179 return 0;
nuclear@0 180 }
nuclear@0 181
nuclear@0 182
nuclear@0 183 // Returns -1 in the case of failure, 0 otherwise.
nuclear@0 184 // Parameters:
nuclear@0 185 // data OUT This pointer is modified to point to the output from
nuclear@0 186 // XRRGetOutputProperty. You *must* call XFree on this pointer.
nuclear@0 187 // dataLen OUT The length of the data returned in 'data'.
nuclear@0 188 static int getXRRProperty(struct _XDisplay* display, RROutput output, Atom atom,
nuclear@0 189 uint8_t** data, int* dataLen)
nuclear@0 190 {
nuclear@0 191 unsigned long nitems;
nuclear@0 192 unsigned long bytesAfter;
nuclear@0 193 int actualFormat;
nuclear@0 194 Atom actualType;
nuclear@0 195
nuclear@0 196 int ret = XRRGetOutputProperty(display, output, atom, 0, 100,
nuclear@0 197 False, False, AnyPropertyType,
nuclear@0 198 &actualType, &actualFormat, &nitems,
nuclear@0 199 &bytesAfter, data);
nuclear@0 200
nuclear@0 201 if (None != ret)
nuclear@0 202 {
nuclear@0 203 *dataLen = nitems;
nuclear@0 204 return 0;
nuclear@0 205 }
nuclear@0 206 else
nuclear@0 207 {
nuclear@0 208 return -1;
nuclear@0 209 }
nuclear@0 210 }
nuclear@0 211
nuclear@0 212 static XRRModeInfo* findModeByXID(XRRScreenResources* screen, RRMode xid)
nuclear@0 213 {
nuclear@0 214 for (int m = 0; m < screen->nmode; ++m)
nuclear@0 215 {
nuclear@0 216 XRRModeInfo* mode = &screen->modes[m];
nuclear@0 217 if (xid == mode->id)
nuclear@0 218 {
nuclear@0 219 return mode;
nuclear@0 220 }
nuclear@0 221 }
nuclear@0 222 return NULL;
nuclear@0 223 }
nuclear@0 224
nuclear@0 225 static int discoverExtendedRifts(OVR::Linux::DisplayDesc* descriptorArray, int inputArraySize, bool /*edidInfo*/)
nuclear@0 226 {
nuclear@0 227 int result = 0;
nuclear@0 228
nuclear@0 229 struct _XDisplay* display = XOpenDisplay(NULL);
nuclear@0 230
nuclear@0 231 if (display == NULL)
nuclear@0 232 {
nuclear@0 233 OVR::LogError("[Linux Display] Unable to open X Display!");
nuclear@0 234 return 0;
nuclear@0 235 }
nuclear@0 236
nuclear@0 237 Atom EDIDAtom = XInternAtom(display, RR_PROPERTY_RANDR_EDID, False);
nuclear@0 238 int numScreens = XScreenCount(display);
nuclear@0 239 for (int i = 0; i < numScreens; ++i)
nuclear@0 240 {
nuclear@0 241 Window sr = XRootWindow(display, i);
nuclear@0 242 XRRScreenResources* screen = XRRGetScreenResources(display, sr);
nuclear@0 243
nuclear@0 244 for (int ii = 0; ii < screen->ncrtc; ++ii)
nuclear@0 245 {
nuclear@0 246 XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screen, screen->crtcs[ii]);
nuclear@0 247
nuclear@0 248 if (0 == crtcInfo->noutput)
nuclear@0 249 {
nuclear@0 250 XRRFreeCrtcInfo(crtcInfo);
nuclear@0 251 continue;
nuclear@0 252 }
nuclear@0 253
nuclear@0 254 bool foundOutput = false;
nuclear@0 255 RROutput output = crtcInfo->outputs[0];
nuclear@0 256 for (int k = 0; k < crtcInfo->noutput; ++k)
nuclear@0 257 {
nuclear@0 258 XRROutputInfo* outputInfo =
nuclear@0 259 XRRGetOutputInfo(display, screen, crtcInfo->outputs[k]);
nuclear@0 260
nuclear@0 261 for (int kk = 0; kk < outputInfo->nmode; ++kk)
nuclear@0 262 {
nuclear@0 263 if (outputInfo->modes[kk] == crtcInfo->mode)
nuclear@0 264 {
nuclear@0 265 output = crtcInfo->outputs[k];
nuclear@0 266 foundOutput = true;
nuclear@0 267 break;
nuclear@0 268 }
nuclear@0 269 }
nuclear@0 270 XRRFreeOutputInfo(outputInfo);
nuclear@0 271 if (foundOutput)
nuclear@0 272 {
nuclear@0 273 break;
nuclear@0 274 }
nuclear@0 275 }
nuclear@0 276
nuclear@0 277 if (!foundOutput)
nuclear@0 278 {
nuclear@0 279 XRRFreeCrtcInfo(crtcInfo);
nuclear@0 280 continue;
nuclear@0 281 }
nuclear@0 282
nuclear@0 283 XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screen, output);
nuclear@0 284 if (RR_Connected != outputInfo->connection)
nuclear@0 285 {
nuclear@0 286 XRRFreeOutputInfo(outputInfo);
nuclear@0 287 XRRFreeCrtcInfo(crtcInfo);
nuclear@0 288 continue;
nuclear@0 289 }
nuclear@0 290
nuclear@0 291 // Read EDID associated with crtc.
nuclear@0 292 uint8_t* data = NULL;
nuclear@0 293 int dataLen = 0;
nuclear@0 294 if (getXRRProperty(display, output, EDIDAtom, &data, &dataLen) != 0)
nuclear@0 295 {
nuclear@0 296 // Identify rifts based on EDID.
nuclear@0 297 Linux::DisplayEDID edid;
nuclear@0 298 parseEdid(data, edid);
nuclear@0 299 XFree(data);
nuclear@0 300 data = NULL;
nuclear@0 301
nuclear@0 302 // TODO: Remove either this 3rdParty call to read EDID data
nuclear@0 303 // or remove our own parsing of the EDID. Probably opt
nuclear@0 304 // to remove our parsing.
nuclear@0 305 MonitorInfo* mi = read_edid_data(display, output);
nuclear@0 306 if (mi == NULL)
nuclear@0 307 {
nuclear@0 308 XRRFreeOutputInfo(outputInfo);
nuclear@0 309 XRRFreeCrtcInfo(crtcInfo);
nuclear@0 310 continue;
nuclear@0 311 }
nuclear@0 312
nuclear@0 313 if (edid.VendorName == "OVR")
nuclear@0 314 {
nuclear@0 315 if( result >= inputArraySize )
nuclear@0 316 {
nuclear@0 317 delete mi;
nuclear@0 318 XRRFreeOutputInfo(outputInfo);
nuclear@0 319 XRRFreeCrtcInfo(crtcInfo);
nuclear@0 320 return result;
nuclear@0 321 }
nuclear@0 322
nuclear@0 323 XRRModeInfo* modeInfo = findModeByXID(screen, crtcInfo->mode);
nuclear@0 324
nuclear@0 325 int width = modeInfo->width;
nuclear@0 326 int height = modeInfo->height;
nuclear@0 327
nuclear@0 328 if ( crtcInfo->rotation == RR_Rotate_90
nuclear@0 329 || crtcInfo->rotation == RR_Rotate_270 )
nuclear@0 330 {
nuclear@0 331 width = modeInfo->height;
nuclear@0 332 height = modeInfo->width;
nuclear@0 333 }
nuclear@0 334
nuclear@0 335 int x = crtcInfo->x;
nuclear@0 336 int y = crtcInfo->y;
nuclear@0 337
nuclear@0 338 // Generate a device ID string similar Windows does it
nuclear@0 339 char device_id[32];
nuclear@0 340 OVR_sprintf(device_id, 32, "%s%04d-%d",
nuclear@0 341 mi->manufacturer_code, mi->product_code,
nuclear@0 342 screen->crtcs[ii]);
nuclear@0 343
nuclear@0 344 OVR::Linux::DisplayDesc& desc = descriptorArray[result++];
nuclear@0 345 desc.DisplayID = device_id;
nuclear@0 346 desc.ModelName = edid.MonitorName;
nuclear@0 347 desc.EdidSerialNumber = edid.SerialNumber;
nuclear@0 348 desc.LogicalResolutionInPixels = Sizei(width, height);
nuclear@0 349 desc.DesktopDisplayOffset = Vector2i(x, y);
nuclear@0 350
nuclear@0 351 switch (mi->product_code)
nuclear@0 352 {
nuclear@0 353 case 3: desc.DeviceTypeGuess = HmdType_DK2; break;
nuclear@0 354 case 2: desc.DeviceTypeGuess = HmdType_DKHDProto; break;
nuclear@0 355 case 1: desc.DeviceTypeGuess = HmdType_DK1; break;
nuclear@0 356
nuclear@0 357 default:
nuclear@0 358 case 0: desc.DeviceTypeGuess = HmdType_Unknown; break;
nuclear@0 359 }
nuclear@0 360
nuclear@0 361 // Hard-coded defaults in case the device doesn't have the
nuclear@0 362 // data itself. DK2 prototypes (0003) or DK HD Prototypes (0002).
nuclear@0 363 if ( desc.DeviceTypeGuess == HmdType_DK2
nuclear@0 364 || desc.DeviceTypeGuess == HmdType_DKHDProto)
nuclear@0 365 {
nuclear@0 366 desc.LogicalResolutionInPixels = Sizei(1920, 1080);
nuclear@0 367 desc.NativeResolutionInPixels = Sizei(1080, 1920);
nuclear@0 368 }
nuclear@0 369 else
nuclear@0 370 {
nuclear@0 371 desc.LogicalResolutionInPixels = Sizei(width, height);
nuclear@0 372 desc.NativeResolutionInPixels = Sizei(width, height);
nuclear@0 373 }
nuclear@0 374 }
nuclear@0 375
nuclear@0 376 delete mi;
nuclear@0 377 mi = NULL;
nuclear@0 378 }
nuclear@0 379
nuclear@0 380 XRRFreeOutputInfo(outputInfo);
nuclear@0 381 XRRFreeCrtcInfo(crtcInfo);
nuclear@0 382 }
nuclear@0 383
nuclear@0 384 XRRFreeScreenResources(screen);
nuclear@0 385 }
nuclear@0 386
nuclear@0 387 XCloseDisplay(display);
nuclear@0 388
nuclear@0 389 return result;
nuclear@0 390 }
nuclear@0 391
nuclear@0 392
nuclear@0 393 //-------------------------------------------------------------------------------------
nuclear@0 394 // ***** Display
nuclear@0 395
nuclear@0 396 bool Display::Initialize()
nuclear@0 397 {
nuclear@0 398 // Nothing to initialize. OS X only supports compatibility mode.
nuclear@0 399 return true;
nuclear@0 400 }
nuclear@0 401
nuclear@0 402 bool Display::GetDriverMode(bool& driverInstalled, bool& compatMode, bool& hideDK1Mode)
nuclear@0 403 {
nuclear@0 404 driverInstalled = false;
nuclear@0 405 compatMode = true;
nuclear@0 406 hideDK1Mode = false;
nuclear@0 407 return true;
nuclear@0 408 }
nuclear@0 409
nuclear@0 410 bool Display::SetDriverMode(bool /*compatMode*/, bool /*hideDK1Mode*/)
nuclear@0 411 {
nuclear@0 412 return false;
nuclear@0 413 }
nuclear@0 414
nuclear@0 415 DisplaySearchHandle* Display::GetDisplaySearchHandle()
nuclear@0 416 {
nuclear@0 417 return new Linux::LinuxDisplaySearchHandle();
nuclear@0 418 }
nuclear@0 419
nuclear@0 420 bool Display::InCompatibilityMode( bool displaySearch )
nuclear@0 421 {
nuclear@0 422 OVR_UNUSED( displaySearch );
nuclear@0 423 return true;
nuclear@0 424 }
nuclear@0 425
nuclear@0 426 int Display::GetDisplayCount(DisplaySearchHandle* handle, bool extended, bool applicationOnly, bool edidInfo)
nuclear@0 427 {
nuclear@0 428 OVR_UNUSED4(handle, extended, applicationOnly, edidInfo);
nuclear@0 429
nuclear@0 430 static int extendedCount = -1;
nuclear@0 431
nuclear@0 432 Linux::LinuxDisplaySearchHandle* localHandle = (Linux::LinuxDisplaySearchHandle*)handle;
nuclear@0 433 if (localHandle == NULL)
nuclear@0 434 {
nuclear@0 435 OVR::LogError("[Linux Display] No search handle passed into GetDisplayCount. Return 0 rifts.");
nuclear@0 436 return 0;
nuclear@0 437 }
nuclear@0 438
nuclear@0 439 if (extendedCount == -1 || extended)
nuclear@0 440 {
nuclear@0 441 extendedCount = discoverExtendedRifts(localHandle->cachedDescriptorArray, Linux::LinuxDisplaySearchHandle::DescArraySize, edidInfo);
nuclear@0 442 }
nuclear@0 443
nuclear@0 444 localHandle->extended = true;
nuclear@0 445 localHandle->extendedDisplayCount = extendedCount;
nuclear@0 446 int totalCount = extendedCount;
nuclear@0 447
nuclear@0 448 /// FIXME: Implement application mode for OS X.
nuclear@0 449 localHandle->application = false;
nuclear@0 450 localHandle->applicationDisplayCount = 0;
nuclear@0 451
nuclear@0 452 localHandle->displayCount = totalCount;
nuclear@0 453
nuclear@0 454 return totalCount;
nuclear@0 455 }
nuclear@0 456
nuclear@0 457
nuclear@0 458 Ptr<Display> Display::GetDisplay( int index, DisplaySearchHandle* handle )
nuclear@0 459 {
nuclear@0 460 Ptr<Display> result = NULL;
nuclear@0 461
nuclear@0 462 if (index < 0)
nuclear@0 463 {
nuclear@0 464 OVR::LogError("[Linux Display] Invalid index given to GetDisplay.");
nuclear@0 465 return NULL;
nuclear@0 466 }
nuclear@0 467
nuclear@0 468 Linux::LinuxDisplaySearchHandle* localHandle = (Linux::LinuxDisplaySearchHandle*)handle;
nuclear@0 469 if (localHandle == NULL)
nuclear@0 470 {
nuclear@0 471 OVR::LogError("[Linux Display] No search handle passed into GetDisplay. Return 0 rifts.");
nuclear@0 472 return NULL;
nuclear@0 473 }
nuclear@0 474
nuclear@0 475 if (localHandle->extended)
nuclear@0 476 {
nuclear@0 477 if (index >= 0 && index < (int)localHandle->extendedDisplayCount)
nuclear@0 478 {
nuclear@0 479 return *new Linux::LinuxDisplayGeneric(localHandle->cachedDescriptorArray[index]);
nuclear@0 480 }
nuclear@0 481
nuclear@0 482 index -= localHandle->extendedDisplayCount;
nuclear@0 483 }
nuclear@0 484
nuclear@0 485 if (localHandle->application)
nuclear@0 486 {
nuclear@0 487 OVR::LogError("[Linux Display] Mac does not support application displays.");
nuclear@0 488 }
nuclear@0 489
nuclear@0 490 return result;
nuclear@0 491 }
nuclear@0 492
nuclear@0 493
nuclear@0 494 } // namespace OVR