VRActivity with Native Code

To develop a VR application with a hybrid mode (mixed with java and ndk), it is recommended that the Activity of the application inherits the VRActivity provided in the Wave VR SDK.

In this tutorial, you will learn how to create an application with hybrid mode. The sample project is called hellovr. To get the source code and build, you can refer to Getting Started.

Contents

Creating the Activity

To design a VR application for Android, several things need to be defined in AndroidManifest.xml:

  • Define the activity with android:screenOrientation="landscape". The screen orientation must be set to landscape because the screen will be split into two halves: left eye and right eye.
  • Disable the TitleBar and set Fullscreen in android:theme. The application must be run in full screen. No window elements should be visible.
  • Add the category “com.htc.intent.category.VRAPP” in the <intent-filter> to enable Wave VR Home to find the application and list it.
  • Add the meta-data “com.htc.vr.content.NumDoFHmd” in the <application> and fill info about your which HMD tracking mode your app supports. The default value is 6DoF which means APP supports tracking HMD rotation and position (six degrees of freedom). Please check below sample for detail.
  • Add the meta-data “com.htc.vr.content.NumDoFController” in the <application> and fill info about your APP supports tracking mode of controller, default value is 6DoF means APP supports 6DoF controller tracking. Please check below sample for detail.
  • Add the meta-data “com.htc.vr.content.NumController” in the <application> and fill the value of how many controllers the application can support.
  • Add Internet permission “android.permission.INTERNET” in uses-permission for the client-server socket communication.
  • Set android:minSdkVersion as 24 which is the minimum android version Wave VR supports.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.htc.vr.samples.hellovr">

    <application
        android:label="@string/main_activity">
        <activity android:name="MainActivity"
            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
            android:launchMode="singleTask"
            android:configChanges="orientation|keyboardHidden"
            android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="com.htc.intent.category.VRAPP" />
            </intent-filter>
        </activity>
        <meta-data android:name="com.htc.vr.content.NumDoFHmd" android:value="6DoF"/>
        <meta-data android:name="com.htc.vr.content.NumDoFController" android:value="6DoF"/>
        <!--Please consider the DoF support of HMD and controller individually for your content.-->
        <!--Set value “3DoF” if your content only considers the rotation pose. -->
        <!--Set value “6DoF” if your content considers both rotation and position pose. -->
        <!--Set value “3,6DoF” if your content is capable of supporting both 3 and 6 DoF playing. -->
        <meta-data android:name="com.htc.vr.content.NumController" android:value="2"/>  <!--fill the value of how many controllers the application can support.-->
    </application>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:glEsVersion="0x00030000" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="25"/>
</manifest>

Note

The hellovr application also needs the camera permission “android.permission.CAMERA” because the CameraSeeThrough feature is demonstrated in this sample. CameraSeeThrough enables the user see the outside world without removing the HMD.

Loading Wave VR libraries

Wave VR SDK is implemented as native C++, so the application needs to access it using a JNI layer. In hellovr, the JNI layer is a library named libjninative.so. To load the JNI library, call System.loadLibrary() in the static initialization block of the MainActivity class.

import com.htc.vr.sdk.VRActivity;

public class MainActivity extends VRActivity {
    static {
        System.loadLibrary("jninative");
    }
}

Next, link the application’s native library to the Wave VR SDK libraries.

Copy the Wave VR SDK libraries into the project. In hellovr, the libraries are copied to src/main/jniLibs. And the headers are copied to src/main/jni/include. To package the libraries with the application, build them as the PREBUILT_SHARED_LIBRARY in the Android.mk.

VR_SDK_LIB := $(firstword  \
    $(realpath $(VR_SDK_ROOT)/jni/$(TARGET_ARCH_ABI))

include $(CLEAR_VARS)
LOCAL_MODULE := wvr_api
LOCAL_SRC_FILES := $(VR_SDK_LIB)/libwvr_api.so
include $(PREBUILT_SHARED_LIBRARY)

# others are skiped.  Please continue to include all of them

And build the sample code files with

COMMON_INCLUDES := \
    $(LOCAL_PATH)/include \
    $(VR_SDK_ROOT)/include \
    $(LOCAL_PATH)/object \
    $(LOCAL_PATH)/scene \
    $(LOCAL_PATH)/shared \
    $(LOCAL_PATH)

COMMON_FILES := \
    hellovr.cpp \
    Context.cpp \
    shared/Matrices.cpp \
    object/Texture.cpp \
    object/VertexArrayObject.cpp \
    object/FrameBufferObject.cpp \
    object/Shader.cpp \
    object/Object.cpp \
    scene/SkyBox.cpp \
    scene/SeaOfCubes.cpp \
    scene/SeeThrough.cpp \
    scene/ControllerAxes.cpp \
    scene/Picture.cpp \
    scene/RenderModel.cpp \
    scene/Clock.cpp

include $(CLEAR_VARS)
LOCAL_MODULE    := common
LOCAL_C_INCLUDES := $(COMMON_INCLUDES)
LOCAL_SRC_FILES := $(COMMON_FILES)
LOCAL_CFLAGS    := -g
LOCAL_LDLIBS    := -llog -ljnigraphics -landroid -lEGL -lGLESv3
LOCAL_SHARED_LIBRARIES := wvr_api
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := jninative
LOCAL_C_INCLUDES := $(COMMON_INCLUDES)
LOCAL_CFLAGS    := -g
LOCAL_LDLIBS    := -llog -landroid
LOCAL_SRC_FILES := \
    jni.cpp
LOCAL_SHARED_LIBRARIES := common wvr_api
include $(BUILD_SHARED_LIBRARY)

The LOCAL_SHARED_LIBRARIES variable must be set to wvr_api. Once this is set, the other prebuilt libraries will be linked. You don’t have to add them all.

Registering the main function

Open the jni.cpp file.

#include <wvr/wvr.h>

int main(int argc, char *argv[]) {

    return 0;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    WVR_RegisterMain(main);
    return JNI_VERSION_1_6;
}

When the MainActivity is launched, JNI_OnLoad() is called by System.loadLibrary(jninative). At this moment, the main() callback is registered to the VR system by WVR_RegisterMain. The main function must take this form: int (*)(int, char**).

In onCreate() of VRActivity, the VR server will be launched, and the VR client will prepare an OpenGL surface view. Then the activity waits for the response from the VR server.

When the VR server is ready, a new thread will start to run and call the main() that was registered. Therefore, main() is not run in Activity’s main thread.

Implementing the VR work

The major steps for VR work appear in main().

// jni.cpp
#include <hellovr.h>

int main(int argc, char *argv[]) {
    LOGENTRY();
    MainApplication *app = new MainApplication();

    if (!app->initVR()) {
        app->shutdownVR();
        return 1;
    }

    if (!app->initGL()) {
        app->shutdownGL();
        app->shutdownVR();
        return 1;
    }

    while (1) {
        if (app->handleInput())
            break;

        if (app->renderFrame()) {
            LOGE("Unknown render error.  Quit.");
            break;
        }

        app->updateHMDMatrixPose();
    }

    app->shutdownGL();
    app->shutdownVR();

    return 0;
}

The MainApplication class is the hellovr main application.

// hellovr.cpp

bool MainApplication::initVR() {
    // Loading the WVR Runtime
    WVR_InitError eError = WVR_InitError_None;
    eError = WVR_Init(WVR_AppType_VRContent);

    if (eError != WVR_InitError_None)
        return false;

    // Must initialize render runtime before all OpenGL code.
    WVR_RenderInitParams_t param = {WVR_GraphicsApiType_OpenGL, WVR_RenderConfig_Timewarp_Asynchronous};
    WVR_RenderError pError = WVR_RenderInit(&param);

    return true;
}

void MainApplication::shutdownVR() {
    WVR_Quit();
}

First, invoke WVR_Init(). This must be done prior to any VR operation. WVR_Init() will initialize the VR system and return the enum WVR_InitError to know what error occurs.

Next, invoke WVR_RenderInit() before any OpenGL operations. The WVR_RenderInit() will initialize a render environment. After that, you can continue to initialize content.

The while loop will break if the Activity is destroyed or if there is an error. And the while loop calls HandleInput(), RenderFrame(), and updateHMDMatrixPose(), which are explained in the next chapter, Developing with the SDK.

When an application exits, invoke WVR_Quit() to release the resources allocated to the Wave VR runtime. After main() is returned, the activity will be finished too. If WVR_Quit() isn’t called when the application exits, WVR_Init() of the same Activity process may generate an error the next time it is invoked.

Note

WVR_Init() can be run in different processes. For example, if there are two VR applications, one can run in the background while the other one runs in the foreground.