Main.exe not launching game on Rift?

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: Unity - Manual: Low-level native plug-in interface

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.

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.

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.

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.

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?

They just need to exist… don’t need to be objects in the scene.

No, it’s internal sorry.

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. :confused:

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.

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?

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 :frowning:

Hmm… very interesting. I think this helps- thank you.

Okay, this got me further into it- but sadly not to an actual solution.

After reading here Not Found| Oculus 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(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:

#include "UnityPluginInterface.h"
#include <windows.h>

#include <stdio.h>
#include <vector>


// --------------------------------------------------------------------------
// UnitySetGraphicsDevice

static int g_DeviceType = -1;


extern "C" void EXPORT_API UnitySetGraphicsDevice (void* device, int deviceType, int eventType)
{
if (eventType == 0 && deviceType != 4)
{
LPCWSTR lpd = static_cast<LPCWSTR>(device);
//string test = lpd.DeviceString;

DISPLAY_DEVICE dd;
memset(&dd, 0, sizeof(DISPLAY_DEVICE));
dd.cb = sizeof(dd);

DISPLAY_DEVICE dd2;
memset(&dd2, 0, sizeof(DISPLAY_DEVICE));
dd2.cb = sizeof(dd2);

int i = 0;
while (EnumDisplayDevices(NULL, i, &dd, 0))
{
printf(("Device Name: %s Device String: %s"), dd.DeviceName, dd.DeviceString);

if (EnumDisplayDevices(dd.DeviceName, 0, &dd2, 0))
{
printf(("Monitor Name: %s Monitor String: %s"), dd2.DeviceName, dd2.DeviceString);
}

i++;
}
}
}

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.

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?


#include "UnityPluginInterface.h"
#include <windows.h>
#include <string>
#include <stdio.h>
#include <vector>

using namespace std;


// --------------------------------------------------------------------------
// UnitySetGraphicsDevice

static int g_DeviceType = -1;


extern "C" void EXPORT_API UnitySetGraphicsDevice (void* device, int deviceType, int eventType)
{
    if (eventType == 0 && deviceType != 4)
    {
        LPCWSTR lpd = static_cast<LPCWSTR>(device);
        //string test = lpd.DeviceString;

        DISPLAY_DEVICE dd;
        memset(&dd, 0, sizeof(DISPLAY_DEVICE));
        dd.cb = sizeof(dd);

        DISPLAY_DEVICE dd2;
        memset(&dd2, 0, sizeof(DISPLAY_DEVICE));
        dd2.cb = sizeof(dd2);

        string id;

        int i = 0;
        while (EnumDisplayDevices(NULL, i, &dd, 0))
        {
            printf(("Device Name: %s Device String: %s"), dd.DeviceName, dd.DeviceString);

            if (EnumDisplayDevices(dd.DeviceName, 0, &dd2, 0))
            {
                printf(("Monitor Name: %s Monitor String: %s"), dd2.DeviceID, dd2.DeviceString);
                id = dd2.DeviceID;
                if (id.find("OVR") != std::string::npos) //contains "OVR"
                {
                    printf("This is a Rift");
                }
            }

            i++;
        }
    }  
}

You can either relaunch unity with -adapter 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.

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.

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.

@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.

This seems to be working well for me.

#include "UnityPluginInterface.h"
#include <windows.h>
#include <string>
#include <stdio.h>
#include <vector>
#include <iostream>

using namespace std;

static int g_DeviceType = -1;

long width;
long height;
string devicename;

BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor,        //Called by USGD after finding the Rift
  HDC      hdcMonitor,
  LPRECT   lprcMonitor,
  LPARAM   dwData)
{
  MONITORINFOEX info;                     //Set up info for each monitor
  info.cbSize = sizeof(info);
  if (GetMonitorInfo(hMonitor, &info))            //Get info for this monitor
  {
    string thisDevice = info.szDevice;
    if (devicename.find(thisDevice) != std::string::npos) //If this is the monitor we identified earlier...
    {                           //Calculate and save the resolution
      width = std::abs(info.rcMonitor.left - info.rcMonitor.right);
      height = std::abs(info.rcMonitor.top - info.rcMonitor.bottom);
    }
  }
  return TRUE;  // continue enumerating
}

extern "C" void EXPORT_API UnitySetGraphicsDevice (void* device, int deviceType, int eventType)
{
  if (eventType == kGfxDeviceEventInitialize && deviceType != kGfxRendererNull) //Only do this is we're initing a new device, and it's not null
  {
    int space;
    string fullcommand;
    string exe;
    string arguments;
    LPSTR pCommandLine = ::GetCommandLine();
    fullcommand = pCommandLine;                   //Cast to string
    space = fullcommand.find(" ");                  //Find the first space
    exe = fullcommand.substr(0, space);               //Get the exe name from the rest of the command
    arguments = fullcommand.substr(space + 1, std::string::npos); //Get the args from the rest of the command
    if (arguments.find("adapter") == std::string::npos)       //only run the rest if adapter wasn't passed in an argument
    { 
      DISPLAY_DEVICE dd;                      //Create 2 dd devices, for each of the Enum calls below
      memset(&dd, 0, sizeof(DISPLAY_DEVICE));
      dd.cb = sizeof(dd);

      DISPLAY_DEVICE dd2;
      memset(&dd2, 0, sizeof(DISPLAY_DEVICE));
      dd2.cb = sizeof(dd2);

      string launchHeight;                  //These strings will be used to set the resolution
      string launchWidth;

      string id;

      int i = 0;
      while (EnumDisplayDevices(NULL, i, &dd, 0))       //While there's an adapter at this 'i'
      {
        if (EnumDisplayDevices(dd.DeviceName, 0, &dd2, 0))  //Get the device info of it.
        {
          id = dd2.DeviceID;                //Cast to string
          if (id.find("OVR") != std::string::npos)    //If contains "OVR"
          { 
            devicename = dd2.DeviceName;
            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
            {
              EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, 0);    //Checks the resolution of each monitor, but saves it to globals if it's the monitor we want
              launchHeight = to_string(height);
              launchWidth = to_string(width);               //Add these arguments to the arguments string
              arguments = arguments + " -screen-height " + launchHeight + " -screen-width " + launchWidth;
            }
            string newArgs = arguments + " -adapter " + std::to_string(i);  //Append the adapter arg to the passed in args
            LPCSTR newArgs2 = newArgs.c_str();                //Cast to LPCSTR
            LPCSTR newExe = exe.c_str();                  //Cast to LPCSTR

            ShellExecute(NULL, "open", newExe, newArgs2, NULL, SW_SHOWNORMAL);  //Restart game with new args
            ExitProcess(0);                         //Kill this instance of the game
          }
        }
        i++;
      }
    }
  }
}
1 Like