Search Unity

Main.exe not launching game on Rift?

Discussion in 'AR/VR (XR) Discussion' started by xhonzi, Jun 22, 2015.

  1. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    Asked at Oculus Forums- not getting anywhere- https://forums.oculus.com/viewtopic.php?f=37&t=23952

    My game is only starting on the Rift when I run the _DirectToRift.exe. DirectToRift is no longer built by Unity, but I have used an old copy.

    I'm using Unity 4.6.5 and Oculus integration 0.6.0 (and Windows Runtime 0.6.0). I see this issue with Dx9 (no longer supported by Oculus) or Dx11.

    My game was recently updated from Unity 4.3.x and Oculus 0.4.4 to 4.6.5 and 0.6.0. New projects seem to run just fine.

    From what I've been able to tell, the OculusInitPlugin.dll is in the plugins directory and is ready to go. This is a 'native plugin' and should override Unity's own 'UnitySetGraphicsDevice' method, and put the image on the Rift if it's detected. Just like _DirectToRift, but without the extra exe.

    _DirectToRift works very well- always on the Rift when connected- always on main monitor when Rift is not connected. Just looking for the same functionality with main.exe.

    Any way to troubleshoot / force OculusInitPlugin.dll to do its job?

    Light reading: http://docs.unity3d.com/Manual/NativePluginInterface.html
     
    Last edited: Jun 23, 2015
  2. thep3000

    thep3000

    Unity Technologies

    Joined:
    Aug 9, 2013
    Posts:
    400
    OculusInitPlugin is only loaded on Unity 4.x, there was some special code on that branch that loads OculusInitPlugin very early to take the place of _DirectToRift.

    We weren't planning on supporting this in Unity 5 since we now have the builtin VR integration for Oculus which initializes everything properly internally.

    Have you tried upgrading to 5.1 Unity VR? If there are some reasons you can't upgrade we'd like to solve them rather than focusing on the legacy integration.
     
  3. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    We have several issues with Unity 5. Our game is kind of a hulking beast and it didn't like Unity 5. Sorry- I know those aren't technical terms.

    The biggest reason is just cost- we have a finished game that we released last year, and we now have a patch with added Oculus support. We're already a little over budget with the patch as is, and we just can't afford to go through the Unity 5 update, and then the native support update as well.
     
    Last edited: Jun 23, 2015
  4. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    From what I know, 4.6 is the only version that supports OculusInitPlugin. At least, that's what the OVRShim / OVRUnityVersionChecker leads me to believe:
    public static bool hasPreInitSupport
    {
    get
    {
    return (version.major == 4 && version >= "4.6.3p2");
    }
    }​

    I understand your reluctance to troubleshoot "legacy integration", but hopefully you can understand our reluctance too. If all else fails, we will ship with the _DirectToRift.exe.

    Any help in figuring out why it's failing to work correctly in our game would be awesome. As I think I said somewhere, it does work on fresh projects- just not our game.
     
    Last edited: Jun 23, 2015
  5. thep3000

    thep3000

    Unity Technologies

    Joined:
    Aug 9, 2013
    Posts:
    400
    Ah, you're on 4.6, missed that the first time. I understand reluctance to upgrading then.

    Best I can do is give you all the details I can on this particular issue.

    Here's details on when the OculusInitPlugin is loaded (on Unity 4.x - 4.6.3p2 and up):
    • In the editor, at build time:
      • We must be able to load OculusPlugin.dll
      • We must be able to find the following classes: OVRManager, OVRDisplay, OVRCameraRig
      • Active target platform must be android or standalone
    • If all of the above succeeded during build, we set a flag in the built data that says we should try to load OculusInitPlugin. If this flag *isn't* set, we never try to load the plugin
    • When you launch the game, before gfx device is created, if the above flag is set:
      • We load OculusInitPlugin.dll , and we call UnitySetGraphicsDevice on it as you expected
    Somethings that could go wrong:
    • Wrong bit-ness of dlls being loaded (32-bit vs. 64-bit) .. but I'd expect something in the logs
    • Some of the above classes not found, leading to silent fail
    If nothing above leads to a solution, you can try:
    • Make your own plugin with the same exported function, attach a debugger with a breakpoint there, see if you get hit. Then we know if the we're actually loading it or not.
    If you can narrow it down based on this, let us know.
     
  6. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    Okay, thanks for the tips. I'll give them a shot and report back.

    When you say "find the classes" do you mean they have to be objects in the first scene? Or just need to be discoverable as classes?

    Any way I can check that flag directly?
     
  7. thep3000

    thep3000

    Unity Technologies

    Joined:
    Aug 9, 2013
    Posts:
    400
    They just need to exist.. don't need to be objects in the scene.

    No, it's internal sorry.
     
  8. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    I only had time to do a small check- I checked that OculusInitPlugin.dll is included with my builds- it is. It matches the 32 bit version from the SDK- so that's good. I temporarily renamed the file in the build and didn't see any change to startup... so I think it's not getting called. If the flag was set, would startup crash if it tried to load the .dll and couldn't? Maybe I need to do more than rename it. :/
     
    Last edited: Jul 8, 2015
  9. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    Okay, I finally got back to this. So, the making my own .dll was a bit over my head, but I grabbed a buddy and we worked through it. Using Visual Studio, we wrote a UnitySetGraphicsDevice plugin, named it OculusInitPlugin.dll and set it in my build, set the breakpoint and then launched the game as the debug command. Pretty sweet.

    I'm somewhat disappointed to report that it entered the breakpoint just like you'd expect. Seems like Unity is calling the dll just fine, but there must be some logic in the dll itself that is failing us. I'll go back to Oculus with this info and see if they can help me further.
     
    Last edited: Jul 14, 2015
  10. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    I assume what's going on is that it can somehow use device or deviceType to determine which monitor is the Rift, and then it sends the game to that monitor. If I can determine the device/deviceType which signifies Rift, can you, thep3000, comment on how monitor selection would work?
     
  11. thep3000

    thep3000

    Unity Technologies

    Joined:
    Aug 9, 2013
    Posts:
    400
    For extended mode it uses EnumDisplayDevices while looking for specific DeviceID. For direct mode I think it talks directly to the driver.. I don't know details here. Both the OculusInitPlugin / DirectToRift.exe do the same thing after that to my knowledge -- which is shim some rendering functions. Actual source code for the shimming piece is part of the Oculus SDK (mirror here). I don't have source to OculusInitPlugin so I'm not sure how they differ.. Oculus will have to help you there.

    All of this fragile shimming business is thankfully gone in 5.1 .. but that doesn't help you here :(
     
  12. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    Hmm... very interesting. I think this helps- thank you.
     
  13. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    Okay, this got me further into it- but sadly not to an actual solution.

    After reading here https://developer.oculus.com/documentation/pcsdk/0.5/concepts/dg-device-mgmt/ and slightly between the lines elsewhere and doing my own experimentation, this is how I think OculusInitPlugin works:

    UnitySetGraphicsDevice runs, which actual just runs EnumDisplayDevice() on each of the displays. My PC has about 7 of them, including two graphics cards. For each display, it gets the DeviceName, which is something like '\\.\DISPLAY1\Monitor0' which tells us almost nothing.

    This is where the guessing starts- OculusInitPlugin talks to the Oculus Runtime, which knows which Display/Monitor the Rift is. It gets the monitor name ('\\.\DISPLAY1\Monitor0' perhaps) from the Runtime- and if it matches the one that it's currently looking at, it sets it as the active monitor and gets on with it.

    Why this doesn't work for our game? I'm not sure. I assume _DirectToRift is doing something very similar, and that works fine.

    With my hacky dll, I can loop through each of the displays and check their names, but they're never named "rift" or anything, just DisplayX\MonitorY.

    thep, can you comment on a way to cast from the "void* device" passed into UnitySetGraphicsDevice to the LPCWSTR that EnumDisplayDevices takes? I tried a simple cast, but it blows up:

    ...
    LPCWSTR lpd = static_cast<LPCWSTR>(device);
    if (EnumDisplayDevices(lpd,0,&dd2,0))
    ...​

    Again, I come back to thinking there's some logic in OculusInitPlugin that is causing us to fail out of the block.

    Thoughts?

    My current plugin code, for the curious:
    Code (C++):
    1.  
    2. #include "UnityPluginInterface.h"
    3. #include <windows.h>
    4.  
    5. #include <stdio.h>
    6. #include <vector>
    7.  
    8.  
    9. // --------------------------------------------------------------------------
    10. // UnitySetGraphicsDevice
    11.  
    12. static int g_DeviceType = -1;
    13.  
    14.  
    15. extern "C" void EXPORT_API UnitySetGraphicsDevice (void* device, int deviceType, int eventType)
    16. {
    17. if (eventType == 0 && deviceType != 4)
    18. {
    19. LPCWSTR lpd = static_cast<LPCWSTR>(device);
    20. //string test = lpd.DeviceString;
    21.  
    22. DISPLAY_DEVICE dd;
    23. memset(&dd, 0, sizeof(DISPLAY_DEVICE));
    24. dd.cb = sizeof(dd);
    25.  
    26. DISPLAY_DEVICE dd2;
    27. memset(&dd2, 0, sizeof(DISPLAY_DEVICE));
    28. dd2.cb = sizeof(dd2);
    29.  
    30. int i = 0;
    31. while (EnumDisplayDevices(NULL, i, &dd, 0))
    32. {
    33. printf(("Device Name: %s Device String: %s"), dd.DeviceName, dd.DeviceString);
    34.  
    35. if (EnumDisplayDevices(dd.DeviceName, 0, &dd2, 0))
    36. {
    37. printf(("Monitor Name: %s Monitor String: %s"), dd2.DeviceName, dd2.DeviceString);
    38. }
    39.  
    40. i++;
    41. }
    42. }
    43. }
    44.  
     
    Last edited: Jul 15, 2015
  14. thep3000

    thep3000

    Unity Technologies

    Joined:
    Aug 9, 2013
    Posts:
    400
    The device being passed into UnitySetGraphicsDevice is typically the D3D device ptr, but for the special "init plugin" call, null is passed in because it happens before the gfx device is even created (because ouclus needs to shim the d3d device create function, etc). Regardless, it's not a string and shouldn't be casted as such.

    I'd guess that the initplugin dll is working as it is supposed to. You said it is working in a blank project? This leads me to think it is some configuration issue (ex. playersettings) with your project. Have you considered creating a new project (since it works) and bringing in pieces of the old project?

    One other thing to try: make sure in playersettings your app has DX11 force exclusive mode enabled in playersettings. Once _DirectToRift.exe finds the correct display it starts unity with the -adapter commandline argument providing the display index. I don't think this happens with the init plugin because, well unity is already launched at this point -- so I think it selects the exclusive window.

    Actually I think it does it based on the DeviceID field, fyi.
     
  15. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    The link I posted to the Oculus SDK documentation suggested DeviceName, but you're right DeviceId is far more useful.

    So I have this now, and it works great. All I need to do (probably) is tell Unity to draw to the display/monitor that I've detected. How is that done?

    ----
    Code (C++):
    1.  
    2. #include "UnityPluginInterface.h"
    3. #include <windows.h>
    4. #include <string>
    5. #include <stdio.h>
    6. #include <vector>
    7.  
    8. using namespace std;
    9.  
    10.  
    11. // --------------------------------------------------------------------------
    12. // UnitySetGraphicsDevice
    13.  
    14. static int g_DeviceType = -1;
    15.  
    16.  
    17. extern "C" void EXPORT_API UnitySetGraphicsDevice (void* device, int deviceType, int eventType)
    18. {
    19.     if (eventType == 0 && deviceType != 4)
    20.     {
    21.         LPCWSTR lpd = static_cast<LPCWSTR>(device);
    22.         //string test = lpd.DeviceString;
    23.  
    24.         DISPLAY_DEVICE dd;
    25.         memset(&dd, 0, sizeof(DISPLAY_DEVICE));
    26.         dd.cb = sizeof(dd);
    27.  
    28.         DISPLAY_DEVICE dd2;
    29.         memset(&dd2, 0, sizeof(DISPLAY_DEVICE));
    30.         dd2.cb = sizeof(dd2);
    31.  
    32.         string id;
    33.  
    34.         int i = 0;
    35.         while (EnumDisplayDevices(NULL, i, &dd, 0))
    36.         {
    37.             printf(("Device Name: %s Device String: %s"), dd.DeviceName, dd.DeviceString);
    38.  
    39.             if (EnumDisplayDevices(dd.DeviceName, 0, &dd2, 0))
    40.             {
    41.                 printf(("Monitor Name: %s Monitor String: %s"), dd2.DeviceID, dd2.DeviceString);
    42.                 id = dd2.DeviceID;
    43.                 if (id.find("OVR") != std::string::npos) //contains "OVR"
    44.                 {
    45.                     printf("This is a Rift");
    46.                 }
    47.             }
    48.  
    49.             i++;
    50.         }
    51.     }  
    52. }
    53.  
     
    Last edited: Jul 15, 2015
  16. thep3000

    thep3000

    Unity Technologies

    Joined:
    Aug 9, 2013
    Posts:
    400
    You can either relaunch unity with -adapter <index> commandline args, or it's technically possible to do it at this point via driver shimming. I think what oculus did here is shim the D3D11CreateDevice call and override it with the correct adapter / output. But their code is already be doing this.. not sure that reinventing the wheel here is the right approach. Consider the alternate suggestions if possible.
     
  17. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    I agree 100%. This exercise started as an attempt to figure out what configuration is required to make it work. I've checked everything I or Oculus can think of. I've asked to see the source for that section of the plugin, or even just to be told what ifs are going on there, but I haven't been able to get that information.

    Yeah, I've checked the DX11 exclusive mode. One of the first things I checked- The OVRShim scripts sets it during a pre-build, so I think it's pretty much always set.

    I haven't gone down the path of adding our content to a new project, though I have thought of it. Our project is a beast and not very modular. I'm afraid I would spend 100+ hours doing something like that, and still not have any answers when I'm done.
     
  18. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    Again, I agree. This would be my preference. I think the only way to go down that path is to get more transparency from Oculus. I have been engaged with them on this subject for a over a month and don't have a lot to show for it yet. I've reached out to another Oculus team member, and will hopefully get the support I need there.

    Until then, I am preparing to go down an alternate path.
     
  19. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    @thep3000-
    Is there a way to determine which adapter the unity game was launched on? Apart from reading the command line args, which seems doable.
     
  20. xhonzi

    xhonzi

    Joined:
    Apr 2, 2013
    Posts:
    205
    This seems to be working well for me.

    Code (C++):
    1. #include "UnityPluginInterface.h"
    2. #include <windows.h>
    3. #include <string>
    4. #include <stdio.h>
    5. #include <vector>
    6. #include <iostream>
    7.  
    8. using namespace std;
    9.  
    10. static int g_DeviceType = -1;
    11.  
    12. long width;
    13. long height;
    14. string devicename;
    15.  
    16. BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor,        //Called by USGD after finding the Rift
    17.   HDC      hdcMonitor,
    18.   LPRECT   lprcMonitor,
    19.   LPARAM   dwData)
    20. {
    21.   MONITORINFOEX info;                     //Set up info for each monitor
    22.   info.cbSize = sizeof(info);
    23.   if (GetMonitorInfo(hMonitor, &info))            //Get info for this monitor
    24.   {
    25.     string thisDevice = info.szDevice;
    26.     if (devicename.find(thisDevice) != std::string::npos) //If this is the monitor we identified earlier...
    27.     {                           //Calculate and save the resolution
    28.       width = std::abs(info.rcMonitor.left - info.rcMonitor.right);
    29.       height = std::abs(info.rcMonitor.top - info.rcMonitor.bottom);
    30.     }
    31.   }
    32.   return TRUE;  // continue enumerating
    33. }
    34.  
    35. extern "C" void EXPORT_API UnitySetGraphicsDevice (void* device, int deviceType, int eventType)
    36. {
    37.   if (eventType == kGfxDeviceEventInitialize && deviceType != kGfxRendererNull) //Only do this is we're initing a new device, and it's not null
    38.   {
    39.     int space;
    40.     string fullcommand;
    41.     string exe;
    42.     string arguments;
    43.     LPSTR pCommandLine = ::GetCommandLine();
    44.     fullcommand = pCommandLine;                   //Cast to string
    45.     space = fullcommand.find(" ");                  //Find the first space
    46.     exe = fullcommand.substr(0, space);               //Get the exe name from the rest of the command
    47.     arguments = fullcommand.substr(space + 1, std::string::npos); //Get the args from the rest of the command
    48.     if (arguments.find("adapter") == std::string::npos)       //only run the rest if adapter wasn't passed in an argument
    49.     {
    50.       DISPLAY_DEVICE dd;                      //Create 2 dd devices, for each of the Enum calls below
    51.       memset(&dd, 0, sizeof(DISPLAY_DEVICE));
    52.       dd.cb = sizeof(dd);
    53.  
    54.       DISPLAY_DEVICE dd2;
    55.       memset(&dd2, 0, sizeof(DISPLAY_DEVICE));
    56.       dd2.cb = sizeof(dd2);
    57.  
    58.       string launchHeight;                  //These strings will be used to set the resolution
    59.       string launchWidth;
    60.  
    61.       string id;
    62.  
    63.       int i = 0;
    64.       while (EnumDisplayDevices(NULL, i, &dd, 0))       //While there's an adapter at this 'i'
    65.       {
    66.         if (EnumDisplayDevices(dd.DeviceName, 0, &dd2, 0))  //Get the device info of it.
    67.         {
    68.           id = dd2.DeviceID;                //Cast to string
    69.           if (id.find("OVR") != std::string::npos)    //If contains "OVR"
    70.           {
    71.             devicename = dd2.DeviceName;
    72.             if (arguments.find("screen-height") == std::string::npos)   //only run if screen-height wasn't passed in as an argument, that way users can override
    73.             {
    74.               EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, 0);    //Checks the resolution of each monitor, but saves it to globals if it's the monitor we want
    75.               launchHeight = to_string(height);
    76.               launchWidth = to_string(width);               //Add these arguments to the arguments string
    77.               arguments = arguments + " -screen-height " + launchHeight + " -screen-width " + launchWidth;
    78.             }
    79.             string newArgs = arguments + " -adapter " + std::to_string(i);  //Append the adapter arg to the passed in args
    80.             LPCSTR newArgs2 = newArgs.c_str();                //Cast to LPCSTR
    81.             LPCSTR newExe = exe.c_str();                  //Cast to LPCSTR
    82.  
    83.             ShellExecute(NULL, "open", newExe, newArgs2, NULL, SW_SHOWNORMAL);  //Restart game with new args
    84.             ExitProcess(0);                         //Kill this instance of the game
    85.           }
    86.         }
    87.         i++;
    88.       }
    89.     }
    90.   }
    91. }
     
    futurlab_xbox likes this.
  21. thep3000

    thep3000

    Unity Technologies

    Joined:
    Aug 9, 2013
    Posts:
    400
    Nice, glad to hear you found an acceptable solution.