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 a hybrid mode. The sample project is called hellovr. To get the source code and build, refer to Getting Started.

Contents

Loading Wave VR Libraries

Because Wave VR SDK is implemented as native C++, the app needs to access it in 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 app’s native library to the Wave VR SDK libraries.

The jninative module rely the libwvr_api.so library which is Wave VR provided. To package the libraries with the app, 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 skipped.  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/ControllerAxes.cpp \
scene/Picture.cpp \
scene/ControllerCube.cpp \
scene/Sphere.cpp \
scene/Floor.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 need to add them.

Registering the Main Function

The following is a pair of pseudo code examples to explain the relation between Java and native code.

// Example in MainActivity.java
public class MainActivity extends VRActivity {

    @Override
    protected void onCreate(Bundle icicle) {
        init();
        super.onCreate(icicle);
    }

    // Pass this acitivty instance to native
    @SuppressWarnings("unused")
    public native void init();
}
// Example in jni.cpp
#include <wvr/wvr.h>

int main(int argc, char *argv[]) {
    return 0;
}

JNIEXPORT void JNICALL Java_com_htc_vr_samples_wvr_1hellovr_MainActivity_init(JNIEnv * env, jobject obj) {
    LOGI("register WVR main here.");
    WVR_RegisterMain(main);
}

When child class MainActivity is launched, onCreate is called during the establishing stage of the lifetime of the Activity. An initialization interface can be called through the JNI (Java Native Interface). 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**).

After that, the parent class method onCreate() is called. 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 registered main(). Therefore, main() is not running in the 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_Default};
    WVR_RenderError pError = WVR_RenderInit(&param);

    // Must initialize input device request
    WVR_InputAttribute array[] = {
        {WVR_InputId_Alias1_Menu, WVR_InputType_Button, WVR_AnalogType_None},
        {WVR_InputId_Alias1_Grip, WVR_InputType_Button, WVR_AnalogType_None},
        {WVR_InputId_Alias1_Touchpad, WVR_InputType_Button | WVR_InputType_Analog, WVR_AnalogType_2D},
        {WVR_InputId_Alias1_Trigger, WVR_InputType_Button | WVR_InputType_Analog, WVR_AnalogType_1D},
    };
    WVR_SetInputRequest(WVR_DeviceType_Controller_Right, array, sizeof(array) / sizeof(*array));
    WVR_SetInputRequest(WVR_DeviceType_Controller_Left, array, sizeof(array) / sizeof(*array));

    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.

If developer would like to user Input Device, developer should call WVR_SetInputRequest() after WVR_Init(). Otherwise, the following APIs (WVR_GetInputDeviceState, WVR_GetInputButtonState, WVR_GetInputTouchState and WVR_GetInputAnalogAxis) can’t work. And WVR_InputEvent type also can’t be received through WVR_PollEventQueue.

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

When an app 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.