//I/DEBUG   ( 2941): pid: 31621, tid: 31653, name: Thread-653  >>> OVRWindWheelActivity.Activities <<<
            //I/DEBUG   ( 2941): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x8
            //I/DEBUG   ( 2941):     r0 00000000  r1 00000000  r2 ffffffff  r3 3e86c40e
            //I/DEBUG   ( 2941):     r4 3e86c40e  r5 ff4fffc0  r6 00000000  r7 3f800000
            //I/DEBUG   ( 2941):     r8 e22fe448  r9 3f772ed9  sl e22fe488  fp 00000bb5
            //I/DEBUG   ( 2941):     ip f73a0710  sp e22fe310  lr f7380375  pc f40fab72  cpsr 800f0030
            //I/DEBUG   ( 2941):
            //I/DEBUG   ( 2941): backtrace:
            //I/DEBUG   ( 2941):     #00 pc 00008b72  /data/app/OVRWindWheelActivity.Activities-1/lib/arm/libmain.so (OVRWindWheelNDK___ovrMatrix4f_CreateRotation+99)
            //I/DEBUG   ( 2941):     #01 pc 00008e61  /data/app/OVRWindWheelActivity.Activities-1/lib/arm/libmain.so (OVRWindWheelNDK_VrCubeWorld_ovrRenderer_ovrRenderer_RenderFrame+376)
            //I/DEBUG   ( 2941):     #02 pc 000098eb  /data/app/OVRWindWheelActivity.Activities-1/lib/arm/libmain.so (OVRWindWheelNDK_VrCubeWorld_ovrAppThread_AppThreadFunction+810)


            void AppThreadFunction()
            {
                // 1778
                ConsoleExtensions.trace("enter pthread_create AppThreadFunction, call vrapi_DefaultInitParms");

                var java = default(ovrJava);
                java.Vm = this.JavaVm;
                java.Vm.AttachCurrentThread(java.Vm, out java.Env, null);
                java.ActivityObject = this.ActivityObject;
                // 1785

                var initParms = VrApi_Helpers.vrapi_DefaultInitParms(ref java);
                ConsoleExtensions.trace("AppThreadFunction, call vrapi_Initialize");
                VrApi.vrapi_Initialize(ref initParms);


                ConsoleExtensions.trace("AppThreadFunction, create ovrApp, call ovrEgl_CreateContext");

                this.appState = new ovrApp(ref java) { AppThread = this };


                // 1794
                this.appState.Egl.ovrEgl_CreateContext(null);


                ConsoleExtensions.trace("AppThreadFunction, call vrapi_GetHmdInfo, then ovrRenderer_Create");
                var hmdInfo = VrApi.vrapi_GetHmdInfo(ref java);
                this.appState.Renderer.ovrRenderer_Create(ref hmdInfo);


                this.headModelParms = VrApi_Helpers.vrapi_DefaultHeadModelParms();


                ConsoleExtensions.trace("AppThreadFunction, enter loop, call ovrMessageQueue_GetNextMessage");
                bool destroyed = false;
                while (!destroyed)
                {
                    //appState.trace60("enter frame, ovrMessageQueue_GetNextMessage");

                    #region ovrMessageQueue_GetNextMessage
                    var ok = true;
                    while (ok)
                    {
                        ovrMessage message;
                        var waitForMessages = appState.Ovr == null && !destroyed;

                        if (!this.MessageQueue.ovrMessageQueue_GetNextMessage(out message, waitForMessages)) break;
                        // 1812

                        // no switch for jsc?
                        if (message.Id == MESSAGE.MESSAGE_ON_CREATE)
                        {
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_CREATE");
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_START) { }
                        else if (message.Id == MESSAGE.MESSAGE_ON_RESUME)
                        {
                            appState.Resumed = true;
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_RESUME");
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_PAUSE) { appState.Resumed = false; }
                        else if (message.Id == MESSAGE.MESSAGE_ON_STOP) { }
                        else if (message.Id == MESSAGE.MESSAGE_ON_DESTROY) { appState.NativeWindow = null; destroyed = true; }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_CREATED)
                        {
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_SURFACE_CREATED");
                            var m0 = message[0];
                            appState.NativeWindow = (native_window.ANativeWindow)m0.Pointer;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_DESTROYED) { appState.NativeWindow = null; }
                        else if (message.Id == MESSAGE.MESSAGE_ON_KEY_EVENT) { appState.ovrApp_HandleKeyEvent((keycodes.AKEYCODE)(int)message[0], (input.AInputEventAction)(int)message[1]); }
                        else if (message.Id == MESSAGE.MESSAGE_ON_TOUCH_EVENT)
                        {
                            //ConsoleExtensions.tracei("AppThreadFunction, MESSAGE_ON_TOUCH_EVENT");
                            appState.ovrApp_HandleTouchEvent(message[0], message[1], message[2]);
                        }

                        appState.ovrApp_HandleVrModeChanges();
                    }
                    #endregion

                    // ok
                    //appState.trace60("ovrMessageQueue_GetNextMessage done, ovrApp_BackButtonAction");

                    // ok
                    appState.ovrApp_BackButtonAction();

                    //appState.trace60("ovrApp_BackButtonAction done (ok), ovrApp_HandleSystemEvents (leak?)");

                    appState.ovrApp_HandleSystemEvents();

                    //appState.trace60("ovrApp_HandleSystemEvents done");

                    // not ready yet?
                    // set by vrapi_EnterVrMode
                    if (appState.Ovr == null)
                    {
                        appState.trace60("Ovr == null");
                        continue;
                    }


                    #region VRAPI_FRAME_INIT_LOADING_ICON_FLUSH
                    if (!appState.Scene.CreatedScene)
                    {
                        // need to keep the enum typename?

                        var parms = VrApi_Helpers.vrapi_DefaultFrameParms(ref appState.Java, ovrFrameInit.VRAPI_FRAME_INIT_LOADING_ICON_FLUSH, 0);
                        parms.FrameIndex = appState.FrameIndex;
                        ConsoleExtensions.trace("vrapi_SubmitFrame VRAPI_FRAME_INIT_LOADING_ICON_FLUSH");
                        appState.Ovr.vrapi_SubmitFrame(ref parms);

                        //unistd.usleep(1000);

                        appState.Scene.ovrScene_Create();

                        // keep the loader on for a moment...
                        //unistd.usleep(1000);
                    }
                    #endregion


                    if (xmallinfo() > GLES3JNILib.safemodeMemoryLimitMB * 1024 * 1024)
                    {
                        // I/xNativeActivity(24473): \VrCubeWorld.AppThread.cs:71 mallinfo    maximum total allocated space:  1825611032
                        // https://news.ycombinator.com/item?id=9179833

                        // https://www.youtube.com/watch?v=se2KMs5qrqY
                        ConsoleExtensions.tracei64("safe mode before sleep ", appState.FrameIndex);

                        // slow down VR thread...
                        System.Threading.Thread.Sleep(5000);
                        //unistd.usleep(2000 * 1000);

                        //ConsoleExtensions.tracei64("safe mode after sleep ", appState.FrameIndex);

                        //continue;
                    }



                    //appState.tracei60("AppThreadFunction, FrameIndex ", (int)appState.FrameIndex);

                    // 1862


                    appState.FrameIndex++;


                    //appState.trace60("who is eating our memory?");
                    //System.Threading.Thread.Sleep(1000 / 15);
                    //continue;


                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedDisplayTime");
                    var predictedDisplayTime = appState.Ovr.vrapi_GetPredictedDisplayTime(appState.FrameIndex);
                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedTracking");

                    this.trackingOld = this.tracking;
                    this.tracking = appState.Ovr.vrapi_GetPredictedTracking(predictedDisplayTime);

                    // like step in physics?
                    appState.Simulation.ovrSimulation_AdvanceSimulation(predictedDisplayTime);

                    {
                        //var parms = appState.Renderer.ovrRenderer_RenderFrame(ref appState, ref tracking);
                        //var parms = appState.Renderer.ovrRenderer_RenderFrame(this, appState, ref tracking);
                        //var parms = this.appState.Renderer.ovrRenderer_RenderFrame(this, ref tracking);
                        var parms = this.appState.Renderer.ovrRenderer_RenderFrame(this);


                        //if (tracking.Status == trackingOld.Status)
                        //    appState.tracei60(" tracking.Status ", (int)tracking.Status);
                        //else
                        //    ConsoleExtensions.tracei(" tracking.Status ", (int)tracking.Status);

                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.x ", (int)(1000 * tracking.HeadPose.Pose.Orientation.x));
                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.y ", (int)(1000 * tracking.HeadPose.Pose.Orientation.y));
                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.z ", (int)(1000 * tracking.HeadPose.Pose.Orientation.z));
                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.w ", (int)(1000 * tracking.HeadPose.Pose.Orientation.w));
                        appState.Ovr.vrapi_SubmitFrame(ref parms);



                    }
                    // 1891
                }

                // 1896
                appState.Renderer.ovrRenderer_Destroy();

                // 1898
                appState.Scene.ovrScene_Destroy();
                appState.Egl.ovrEgl_DestroyContext();
                VrApi.vrapi_Shutdown();

                java.Vm.DetachCurrentThread(java.Vm);
            }
            // sent into vrapi_SubmitFrame
            // will use glMapBufferRange
            //public ovrFrameParms ovrRenderer_RenderFrame(ref ovrApp appState, ref ovrTracking tracking)
            public ovrFrameParms ovrRenderer_RenderFrame(ovrApp appState, ref ovrTracking tracking)
            {
                //ConsoleExtensions.tracei("enter ovrRenderer_RenderFrame, VRAPI_FRAME_INIT_DEFAULT");

                // can other processes/non ndk stream a surface to us?
                // local socket?
                // shared memory?
                // editn n continue?
                // 1049

                ovrFrameParms parms = VrApi_Helpers.vrapi_DefaultFrameParms(ref appState.Java, ovrFrameInit.VRAPI_FRAME_INIT_DEFAULT, 0u);
                parms.FrameIndex = appState.FrameIndex;
                parms.MinimumVsyncs = appState.MinimumVsyncs;


                #region InstanceTransformBuffer
                var sizeof_ovrMatrix4f = sizeof(ovrMatrix4f);
                gl3.glBindBuffer(gl3.GL_ARRAY_BUFFER, appState.Scene.InstanceTransformBuffer);
                var cubeTransforms = gl3.glMapBufferRange<ovrMatrix4f>(
                    gl3.GL_ARRAY_BUFFER, 0,
                    // do we need marshal.getsize?
                    NUM_INSTANCES * sizeof_ovrMatrix4f,

                    // the first gl3 members, the other are gl2 apis
                    gl3.GL_MAP_WRITE_BIT | gl3.GL_MAP_INVALIDATE_BUFFER_BIT
                );

                // 1057

                for (int i = 0; i < NUM_INSTANCES; i++)
                {
                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ovrMatrix4f_CreateRotation i ", i);

                    var rotation = VrApi_Helpers.ovrMatrix4f_CreateRotation(
                        appState.Scene.CubeRotations[i].x * appState.Simulation.CurrentRotation.x,
                        appState.Scene.CubeRotations[i].y * appState.Simulation.CurrentRotation.y,
                        appState.Scene.CubeRotations[i].z * appState.Simulation.CurrentRotation.z
                    );

                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ovrMatrix4f_CreateTranslation i ", i);

                    var translation = VrApi_Helpers.ovrMatrix4f_CreateTranslation(
                        appState.Scene.CubePositions[i].x,
                        appState.Scene.CubePositions[i].y,
                        appState.Scene.CubePositions[i].z
                    );

                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ovrMatrix4f_Multiply i ", i);
                    var transform = VrApi_Helpers.ovrMatrix4f_Multiply(ref translation, ref rotation);


                    var transpose = VrApi_Helpers.ovrMatrix4f_Transpose(ref transform);

                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ubeTransforms[i] = transpose ", i);
                    cubeTransforms[i] = transpose;
                }

                // 1070
                gl3.glUnmapBuffer(gl3.GL_ARRAY_BUFFER);
                gl3.glBindBuffer(gl3.GL_ARRAY_BUFFER, 0);
                #endregion


                // Calculate the center view matrix.
                //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, vrapi_DefaultHeadModelParms");
                var headModelParms = VrApi_Helpers.vrapi_DefaultHeadModelParms();

                //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, vrapi_GetCenterEyeViewMatrix");
                var centerEyeViewMatrix = VrApi_Helpers.vrapi_GetCenterEyeViewMatrix(ref headModelParms, ref tracking, default(ovrMatrix4f*));

                // 1077

                // NUM_EYES is length of RenderTextures
                for (int eye = 0; eye < NUM_EYES; eye++)
                {
                    // https://sites.google.com/a/jsc-solutions.net/work/knowledge-base/15-dualvr/20150618/ovrmatrix4f
                    var eyeViewMatrix = VrApi_Helpers.vrapi_GetEyeViewMatrix(ref headModelParms, ref centerEyeViewMatrix, eye);

                    fixed (ovrMatrix4f* ref_ProjectionMatrix = &appState.Renderer.ProjectionMatrix)
                    fixed (ovrRenderTexture* rt = &appState.Renderer.RenderTextures[appState.Renderer.BufferIndex, eye])
                    {
                        //// 1085
                        //appState.tracei60("ovrRenderer_RenderFrame, ovrRenderTexture_SetCurrent BufferIndex ", appState.Renderer.BufferIndex);
                        //appState.tracei60("ovrRenderer_RenderFrame, ovrRenderTexture_SetCurrent eye ", eye);

                        rt->ovrRenderTexture_SetCurrent();


                        gl3.glEnable(gl3.GL_SCISSOR_TEST);
                        gl3.glDepthMask(true);
                        gl3.glEnable(gl3.GL_DEPTH_TEST);
                        gl3.glDepthFunc(gl3.GL_LEQUAL);
                        gl3.glViewport(0, 0, rt->Width, rt->Height);
                        gl3.glScissor(0, 0, rt->Width, rt->Height);
                        //gl3.glClearColor(0.125f, 0.0f, 0.125f, 1.0f);
                        //gl3.glClearColor(0.9f, 0.0f, 0.125f, 1.0f);
                        gl3.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                        gl3.glClear(gl3.GL_COLOR_BUFFER_BIT | gl3.GL_DEPTH_BUFFER_BIT);

                        gl3.glUseProgram(appState.Scene.Program.Program);

                        // 1094

                        gl3.glUniformMatrix4fv(appState.Scene.Program.Uniforms[(int)ovrUniform_index.UNIFORM_VIEW_MATRIX], 1, true, (float*)&eyeViewMatrix);
                        gl3.glUniformMatrix4fv(appState.Scene.Program.Uniforms[(int)ovrUniform_index.UNIFORM_PROJECTION_MATRIX], 1, true, (float*)ref_ProjectionMatrix);

                        gl3.glBindVertexArray(appState.Scene.Cube.VertexArrayObject);
                        gl3.glDrawElementsInstanced(gl3.GL_TRIANGLES, appState.Scene.Cube.IndexCount, gl3.GL_UNSIGNED_SHORT, null, NUM_INSTANCES);
                        gl3.glBindVertexArray(0);
                        gl3.glUseProgram(0);

                        // 1104

                        // what happens if we dont?

                        #region Explicitly clear the border texels to black because OpenGL-ES does not support GL_CLAMP_TO_BORDER.
                        {
                            // Clear to fully opaque black.
                            gl3.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                            // bottom
                            gl3.glScissor(0, 0, rt->Width, 1);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                            // top
                            gl3.glScissor(0, rt->Height - 1, rt->Width, 1);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                            // left
                            gl3.glScissor(0, 0, 1, rt->Height);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                            // right
                            gl3.glScissor(rt->Width - 1, 0, 1, rt->Height);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                        }
                        #endregion

                        //// 1119
                        //appState.tracei60("ovrRenderer_RenderFrame, ovrRenderTexture_Resolve, glInvalidateFramebuffer");
                        rt->ovrRenderTexture_Resolve();


                        parms.Layers[(int)ovrFrameLayerType.VRAPI_FRAME_LAYER_TYPE_WORLD].Images[eye].TexId = rt->ColorTexture;
                        parms.Layers[(int)ovrFrameLayerType.VRAPI_FRAME_LAYER_TYPE_WORLD].Images[eye].TexCoordsFromTanAngles = appState.Renderer.TanAngleMatrix;
                        parms.Layers[(int)ovrFrameLayerType.VRAPI_FRAME_LAYER_TYPE_WORLD].Images[eye].HeadPose = tracking.HeadPose;
                    }
                }

                ovrRenderTexture.ovrRenderTexture_SetNone();

               
                appState.Renderer.BufferIndex = (appState.Renderer.BufferIndex + 1) % NUM_BUFFERS;

                // 1130
                appState.tracei60("exit ovrRenderer_RenderFrame BufferIndex", appState.Renderer.BufferIndex);
                //ConsoleExtensions.tracei("exit ovrRenderer_RenderFrame");
                return parms;
            }
            object AppThreadFunction()
            {
                // 1778
                ConsoleExtensions.trace("enter pthread_create AppThreadFunction, call vrapi_DefaultInitParms");

                var java = default(ovrJava);

                java.Vm = this.JavaVm;
                java.Vm.AttachCurrentThread(java.Vm, out java.Env, null);
                java.ActivityObject = this.ActivityObject;
                // 1785

                var initParms = VrApi_Helpers.vrapi_DefaultInitParms(ref java);

                ConsoleExtensions.trace("AppThreadFunction, call vrapi_Initialize");
                VrApi.vrapi_Initialize(ref initParms);


                ConsoleExtensions.trace("AppThreadFunction, create ovrApp, call ovrEgl_CreateContext");

                this.appState = new ovrApp(ref java);

                // 1794
                this.appState.Egl.ovrEgl_CreateContext(null);


                ConsoleExtensions.trace("AppThreadFunction, call vrapi_GetHmdInfo, then ovrRenderer_Create");
                var hmdInfo = VrApi.vrapi_GetHmdInfo(ref java);

                this.appState.Renderer.ovrRenderer_Create(ref hmdInfo);

                ConsoleExtensions.trace("AppThreadFunction, enter loop, call ovrMessageQueue_GetNextMessage");
                bool destroyed = false;

                while (!destroyed)
                {
                    #region ovrMessageQueue_GetNextMessage
                    var ok = true;
                    while (ok)
                    {
                        ovrMessage message;
                        var        waitForMessages = appState.Ovr == null && !destroyed;

                        if (!this.MessageQueue.ovrMessageQueue_GetNextMessage(out message, waitForMessages))
                        {
                            break;
                        }
                        // 1812

                        // no switch for jsc?
                        if (message.Id == MESSAGE.MESSAGE_ON_CREATE)
                        {
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_CREATE");
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_START)
                        {
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_RESUME)
                        {
                            appState.Resumed = true;
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_RESUME");
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_PAUSE)
                        {
                            appState.Resumed = false;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_STOP)
                        {
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_DESTROY)
                        {
                            appState.NativeWindow = null; destroyed = true;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_CREATED)
                        {
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_SURFACE_CREATED");
                            var m0 = message[0];
                            appState.NativeWindow = (native_window.ANativeWindow)m0.Pointer;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_DESTROYED)
                        {
                            appState.NativeWindow = null;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_KEY_EVENT)
                        {
                            appState.ovrApp_HandleKeyEvent((keycodes.AKEYCODE)(int) message[0], (input.AInputEventAction)(int) message[1]);
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_TOUCH_EVENT)
                        {
                            //ConsoleExtensions.tracei("AppThreadFunction, MESSAGE_ON_TOUCH_EVENT");
                            appState.ovrApp_HandleTouchEvent(message[0], message[1], message[2]);
                        }

                        appState.ovrApp_HandleVrModeChanges();
                    }
                    #endregion



                    appState.ovrApp_BackButtonAction();
                    appState.ovrApp_HandleSystemEvents();

                    // not ready yet?
                    // set by vrapi_EnterVrMode
                    if (appState.Ovr == null)
                    {
                        continue;
                    }


                    #region VRAPI_FRAME_INIT_LOADING_ICON_FLUSH
                    if (!appState.Scene.ovrScene_IsCreated())
                    {
                        // need to keep the enum typename?

                        var parms = VrApi_Helpers.vrapi_DefaultFrameParms(ref appState.Java, ovrFrameInit.VRAPI_FRAME_INIT_LOADING_ICON_FLUSH, 0);
                        parms.FrameIndex = appState.FrameIndex;
                        ConsoleExtensions.trace("vrapi_SubmitFrame VRAPI_FRAME_INIT_LOADING_ICON_FLUSH");
                        appState.Ovr.vrapi_SubmitFrame(ref parms);

                        //unistd.usleep(1000);

                        appState.Scene.ovrScene_Create();

                        // keep the loader on for a moment...
                        //unistd.usleep(1000);
                    }
                    #endregion


                    //appState.tracei60("AppThreadFunction, FrameIndex ", (int)appState.FrameIndex);

                    // 1862
                    appState.FrameIndex++;

                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedDisplayTime");
                    var predictedDisplayTime = appState.Ovr.vrapi_GetPredictedDisplayTime(appState.FrameIndex);
                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedTracking");

                    this.trackingOld = this.tracking;
                    this.tracking    = appState.Ovr.vrapi_GetPredictedTracking(predictedDisplayTime);

                    // like step in physics?
                    appState.Simulation.ovrSimulation_AdvanceSimulation(predictedDisplayTime);

                    {
                        //var parms = appState.Renderer.ovrRenderer_RenderFrame(ref appState, ref tracking);
                        //var parms = appState.Renderer.ovrRenderer_RenderFrame(this, appState, ref tracking);
                        //var parms = this.appState.Renderer.ovrRenderer_RenderFrame(this, ref tracking);
                        var parms = this.appState.Renderer.ovrRenderer_RenderFrame(this);

                        appState.tracei60("vrapi_SubmitFrame ", (int)appState.FrameIndex);

                        if (tracking.Status == trackingOld.Status)
                        {
                            appState.tracei60(" tracking.Status ", (int)tracking.Status);
                        }
                        else
                        {
                            ConsoleExtensions.tracei(" tracking.Status ", (int)tracking.Status);
                        }

                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.x ", (int)(1000 * tracking.HeadPose.Pose.Orientation.x));
                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.y ", (int)(1000 * tracking.HeadPose.Pose.Orientation.y));
                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.z ", (int)(1000 * tracking.HeadPose.Pose.Orientation.z));
                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.w ", (int)(1000 * tracking.HeadPose.Pose.Orientation.w));
                        appState.Ovr.vrapi_SubmitFrame(ref parms);
                    }
                    // 1891
                }

                // 1896
                appState.Renderer.ovrRenderer_Destroy();

                // 1898
                appState.Scene.ovrScene_Destroy();
                appState.Egl.ovrEgl_DestroyContext();
                VrApi.vrapi_Shutdown();

                java.Vm.DetachCurrentThread(java.Vm);
                return(null);
            }
            //I/DEBUG   ( 2941): pid: 31621, tid: 31653, name: Thread-653  >>> OVRWindWheelActivity.Activities <<<
            //I/DEBUG   ( 2941): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x8
            //I/DEBUG   ( 2941):     r0 00000000  r1 00000000  r2 ffffffff  r3 3e86c40e
            //I/DEBUG   ( 2941):     r4 3e86c40e  r5 ff4fffc0  r6 00000000  r7 3f800000
            //I/DEBUG   ( 2941):     r8 e22fe448  r9 3f772ed9  sl e22fe488  fp 00000bb5
            //I/DEBUG   ( 2941):     ip f73a0710  sp e22fe310  lr f7380375  pc f40fab72  cpsr 800f0030
            //I/DEBUG   ( 2941):
            //I/DEBUG   ( 2941): backtrace:
            //I/DEBUG   ( 2941):     #00 pc 00008b72  /data/app/OVRWindWheelActivity.Activities-1/lib/arm/libmain.so (OVRWindWheelNDK___ovrMatrix4f_CreateRotation+99)
            //I/DEBUG   ( 2941):     #01 pc 00008e61  /data/app/OVRWindWheelActivity.Activities-1/lib/arm/libmain.so (OVRWindWheelNDK_VrCubeWorld_ovrRenderer_ovrRenderer_RenderFrame+376)
            //I/DEBUG   ( 2941):     #02 pc 000098eb  /data/app/OVRWindWheelActivity.Activities-1/lib/arm/libmain.so (OVRWindWheelNDK_VrCubeWorld_ovrAppThread_AppThreadFunction+810)


            void AppThreadFunction()
            {
                // 1778
                ConsoleExtensions.trace("enter pthread_create AppThreadFunction, call vrapi_DefaultInitParms");

                var java = default(ovrJava);

                java.Vm = this.JavaVm;
                java.Vm.AttachCurrentThread(java.Vm, out java.Env, null);
                java.ActivityObject = this.ActivityObject;
                // 1785

                var initParms = VrApi_Helpers.vrapi_DefaultInitParms(ref java);

                ConsoleExtensions.trace("AppThreadFunction, call vrapi_Initialize");
                VrApi.vrapi_Initialize(ref initParms);


                ConsoleExtensions.trace("AppThreadFunction, create ovrApp, call ovrEgl_CreateContext");

                this.appState = new ovrApp(ref java)
                {
                    AppThread = this
                };


                // 1794
                this.appState.Egl.ovrEgl_CreateContext(null);


                ConsoleExtensions.trace("AppThreadFunction, call vrapi_GetHmdInfo, then ovrRenderer_Create");
                var hmdInfo = VrApi.vrapi_GetHmdInfo(ref java);

                this.appState.Renderer.ovrRenderer_Create(ref hmdInfo);


                this.headModelParms = VrApi_Helpers.vrapi_DefaultHeadModelParms();


                ConsoleExtensions.trace("AppThreadFunction, enter loop, call ovrMessageQueue_GetNextMessage");
                bool destroyed = false;

                while (!destroyed)
                {
                    //appState.trace60("enter frame, ovrMessageQueue_GetNextMessage");

                    #region ovrMessageQueue_GetNextMessage
                    var ok = true;
                    while (ok)
                    {
                        ovrMessage message;
                        var        waitForMessages = appState.Ovr == null && !destroyed;

                        if (!this.MessageQueue.ovrMessageQueue_GetNextMessage(out message, waitForMessages))
                        {
                            break;
                        }
                        // 1812

                        // no switch for jsc?
                        if (message.Id == MESSAGE.MESSAGE_ON_CREATE)
                        {
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_CREATE");
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_START)
                        {
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_RESUME)
                        {
                            appState.Resumed = true;
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_RESUME");
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_PAUSE)
                        {
                            appState.Resumed = false;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_STOP)
                        {
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_DESTROY)
                        {
                            appState.NativeWindow = null; destroyed = true;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_CREATED)
                        {
                            //ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_SURFACE_CREATED");
                            var m0 = message[0];
                            appState.NativeWindow = (native_window.ANativeWindow)m0.Pointer;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_DESTROYED)
                        {
                            appState.NativeWindow = null;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_KEY_EVENT)
                        {
                            appState.ovrApp_HandleKeyEvent((keycodes.AKEYCODE)(int) message[0], (input.AInputEventAction)(int) message[1]);
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_TOUCH_EVENT)
                        {
                            //ConsoleExtensions.tracei("AppThreadFunction, MESSAGE_ON_TOUCH_EVENT");
                            appState.ovrApp_HandleTouchEvent(message[0], message[1], message[2]);
                        }

                        appState.ovrApp_HandleVrModeChanges();
                    }
                    #endregion

                    // ok
                    //appState.trace60("ovrMessageQueue_GetNextMessage done, ovrApp_BackButtonAction");

                    // ok
                    appState.ovrApp_BackButtonAction();

                    //appState.trace60("ovrApp_BackButtonAction done (ok), ovrApp_HandleSystemEvents (leak?)");

                    appState.ovrApp_HandleSystemEvents();

                    //appState.trace60("ovrApp_HandleSystemEvents done");

                    // not ready yet?
                    // set by vrapi_EnterVrMode
                    if (appState.Ovr == null)
                    {
                        appState.trace60("Ovr == null");
                        continue;
                    }


                    #region VRAPI_FRAME_INIT_LOADING_ICON_FLUSH
                    if (!appState.Scene.CreatedScene)
                    {
                        // need to keep the enum typename?

                        var parms = VrApi_Helpers.vrapi_DefaultFrameParms(ref appState.Java, ovrFrameInit.VRAPI_FRAME_INIT_LOADING_ICON_FLUSH, 0);
                        parms.FrameIndex = appState.FrameIndex;
                        ConsoleExtensions.trace("vrapi_SubmitFrame VRAPI_FRAME_INIT_LOADING_ICON_FLUSH");
                        appState.Ovr.vrapi_SubmitFrame(ref parms);

                        //unistd.usleep(1000);

                        appState.Scene.ovrScene_Create();

                        // keep the loader on for a moment...
                        //unistd.usleep(1000);
                    }
                    #endregion


                    if (xmallinfo() > GLES3JNILib.safemodeMemoryLimitMB * 1024 * 1024)
                    {
                        // I/xNativeActivity(24473): \VrCubeWorld.AppThread.cs:71 mallinfo    maximum total allocated space:  1825611032
                        // https://news.ycombinator.com/item?id=9179833

                        // https://www.youtube.com/watch?v=se2KMs5qrqY
                        ConsoleExtensions.tracei64("safe mode before sleep ", appState.FrameIndex);

                        // slow down VR thread...
                        System.Threading.Thread.Sleep(5000);
                        //unistd.usleep(2000 * 1000);

                        //ConsoleExtensions.tracei64("safe mode after sleep ", appState.FrameIndex);

                        //continue;
                    }



                    //appState.tracei60("AppThreadFunction, FrameIndex ", (int)appState.FrameIndex);

                    // 1862


                    appState.FrameIndex++;


                    //appState.trace60("who is eating our memory?");
                    //System.Threading.Thread.Sleep(1000 / 15);
                    //continue;


                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedDisplayTime");
                    var predictedDisplayTime = appState.Ovr.vrapi_GetPredictedDisplayTime(appState.FrameIndex);
                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedTracking");

                    this.trackingOld = this.tracking;
                    this.tracking    = appState.Ovr.vrapi_GetPredictedTracking(predictedDisplayTime);

                    // like step in physics?
                    appState.Simulation.ovrSimulation_AdvanceSimulation(predictedDisplayTime);

                    {
                        //var parms = appState.Renderer.ovrRenderer_RenderFrame(ref appState, ref tracking);
                        //var parms = appState.Renderer.ovrRenderer_RenderFrame(this, appState, ref tracking);
                        //var parms = this.appState.Renderer.ovrRenderer_RenderFrame(this, ref tracking);
                        var parms = this.appState.Renderer.ovrRenderer_RenderFrame(this);


                        //if (tracking.Status == trackingOld.Status)
                        //    appState.tracei60(" tracking.Status ", (int)tracking.Status);
                        //else
                        //    ConsoleExtensions.tracei(" tracking.Status ", (int)tracking.Status);

                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.x ", (int)(1000 * tracking.HeadPose.Pose.Orientation.x));
                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.y ", (int)(1000 * tracking.HeadPose.Pose.Orientation.y));
                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.z ", (int)(1000 * tracking.HeadPose.Pose.Orientation.z));
                        //appState.tracei60(" tracking.HeadPose.Pose.Orientation.w ", (int)(1000 * tracking.HeadPose.Pose.Orientation.w));
                        appState.Ovr.vrapi_SubmitFrame(ref parms);
                    }
                    // 1891
                }

                // 1896
                appState.Renderer.ovrRenderer_Destroy();

                // 1898
                appState.Scene.ovrScene_Destroy();
                appState.Egl.ovrEgl_DestroyContext();
                VrApi.vrapi_Shutdown();

                java.Vm.DetachCurrentThread(java.Vm);
            }
Beispiel #5
0
            // sent into vrapi_SubmitFrame
            // will use glMapBufferRange
            //public ovrFrameParms ovrRenderer_RenderFrame(ref ovrApp appState, ref ovrTracking tracking)
            public ovrFrameParms ovrRenderer_RenderFrame(ovrApp appState, ref ovrTracking tracking)
            {
                //ConsoleExtensions.tracei("enter ovrRenderer_RenderFrame, VRAPI_FRAME_INIT_DEFAULT");

                // can other processes/non ndk stream a surface to us?
                // local socket?
                // shared memory?
                // editn n continue?
                // 1049

                ovrFrameParms parms = VrApi_Helpers.vrapi_DefaultFrameParms(ref appState.Java, ovrFrameInit.VRAPI_FRAME_INIT_DEFAULT, 0u);

                parms.FrameIndex    = appState.FrameIndex;
                parms.MinimumVsyncs = appState.MinimumVsyncs;


                #region InstanceTransformBuffer
                var sizeof_ovrMatrix4f = sizeof(ovrMatrix4f);
                gl3.glBindBuffer(gl3.GL_ARRAY_BUFFER, appState.Scene.InstanceTransformBuffer);
                var cubeTransforms = gl3.glMapBufferRange <ovrMatrix4f>(
                    gl3.GL_ARRAY_BUFFER, 0,
                    // do we need marshal.getsize?
                    NUM_INSTANCES * sizeof_ovrMatrix4f,

                    // the first gl3 members, the other are gl2 apis
                    gl3.GL_MAP_WRITE_BIT | gl3.GL_MAP_INVALIDATE_BUFFER_BIT
                    );

                // 1057

                for (int i = 0; i < NUM_INSTANCES; i++)
                {
                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ovrMatrix4f_CreateRotation i ", i);

                    var rotation = VrApi_Helpers.ovrMatrix4f_CreateRotation(
                        appState.Scene.CubeRotations[i].x * appState.Simulation.CurrentRotation.x,
                        appState.Scene.CubeRotations[i].y * appState.Simulation.CurrentRotation.y,
                        appState.Scene.CubeRotations[i].z * appState.Simulation.CurrentRotation.z
                        );

                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ovrMatrix4f_CreateTranslation i ", i);

                    var translation = VrApi_Helpers.ovrMatrix4f_CreateTranslation(
                        appState.Scene.CubePositions[i].x,
                        appState.Scene.CubePositions[i].y,
                        appState.Scene.CubePositions[i].z
                        );

                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ovrMatrix4f_Multiply i ", i);
                    var transform = VrApi_Helpers.ovrMatrix4f_Multiply(ref translation, ref rotation);


                    var transpose = VrApi_Helpers.ovrMatrix4f_Transpose(ref transform);

                    //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, ubeTransforms[i] = transpose ", i);
                    cubeTransforms[i] = transpose;
                }

                // 1070
                gl3.glUnmapBuffer(gl3.GL_ARRAY_BUFFER);
                gl3.glBindBuffer(gl3.GL_ARRAY_BUFFER, 0);
                #endregion


                // Calculate the center view matrix.
                //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, vrapi_DefaultHeadModelParms");
                var headModelParms = VrApi_Helpers.vrapi_DefaultHeadModelParms();

                //ConsoleExtensions.tracei("ovrRenderer_RenderFrame, vrapi_GetCenterEyeViewMatrix");
                var centerEyeViewMatrix = VrApi_Helpers.vrapi_GetCenterEyeViewMatrix(ref headModelParms, ref tracking, default(ovrMatrix4f *));

                // 1077

                // NUM_EYES is length of RenderTextures
                for (int eye = 0; eye < NUM_EYES; eye++)
                {
                    // https://sites.google.com/a/jsc-solutions.net/work/knowledge-base/15-dualvr/20150618/ovrmatrix4f
                    var eyeViewMatrix = VrApi_Helpers.vrapi_GetEyeViewMatrix(ref headModelParms, ref centerEyeViewMatrix, eye);

                    fixed(ovrMatrix4f *ref_ProjectionMatrix = &appState.Renderer.ProjectionMatrix)
                    fixed(ovrRenderTexture * rt = &appState.Renderer.RenderTextures[appState.Renderer.BufferIndex, eye])
                    {
                        //// 1085
                        //appState.tracei60("ovrRenderer_RenderFrame, ovrRenderTexture_SetCurrent BufferIndex ", appState.Renderer.BufferIndex);
                        //appState.tracei60("ovrRenderer_RenderFrame, ovrRenderTexture_SetCurrent eye ", eye);

                        rt->ovrRenderTexture_SetCurrent();


                        gl3.glEnable(gl3.GL_SCISSOR_TEST);
                        gl3.glDepthMask(true);
                        gl3.glEnable(gl3.GL_DEPTH_TEST);
                        gl3.glDepthFunc(gl3.GL_LEQUAL);
                        gl3.glViewport(0, 0, rt->Width, rt->Height);
                        gl3.glScissor(0, 0, rt->Width, rt->Height);
                        //gl3.glClearColor(0.125f, 0.0f, 0.125f, 1.0f);
                        //gl3.glClearColor(0.9f, 0.0f, 0.125f, 1.0f);
                        gl3.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                        gl3.glClear(gl3.GL_COLOR_BUFFER_BIT | gl3.GL_DEPTH_BUFFER_BIT);

                        gl3.glUseProgram(appState.Scene.Program.Program);

                        // 1094

                        gl3.glUniformMatrix4fv(appState.Scene.Program.Uniforms[(int)ovrUniform_index.UNIFORM_VIEW_MATRIX], 1, true, (float *)&eyeViewMatrix);
                        gl3.glUniformMatrix4fv(appState.Scene.Program.Uniforms[(int)ovrUniform_index.UNIFORM_PROJECTION_MATRIX], 1, true, (float *)ref_ProjectionMatrix);

                        gl3.glBindVertexArray(appState.Scene.Cube.VertexArrayObject);
                        gl3.glDrawElementsInstanced(gl3.GL_TRIANGLES, appState.Scene.Cube.IndexCount, gl3.GL_UNSIGNED_SHORT, null, NUM_INSTANCES);
                        gl3.glBindVertexArray(0);
                        gl3.glUseProgram(0);

                        // 1104

                        // what happens if we dont?

                        #region Explicitly clear the border texels to black because OpenGL-ES does not support GL_CLAMP_TO_BORDER.
                        {
                            // Clear to fully opaque black.
                            gl3.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                            // bottom
                            gl3.glScissor(0, 0, rt->Width, 1);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                            // top
                            gl3.glScissor(0, rt->Height - 1, rt->Width, 1);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                            // left
                            gl3.glScissor(0, 0, 1, rt->Height);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                            // right
                            gl3.glScissor(rt->Width - 1, 0, 1, rt->Height);
                            gl3.glClear(gl3.GL_COLOR_BUFFER_BIT);
                        }
                        #endregion

                        //// 1119
                        //appState.tracei60("ovrRenderer_RenderFrame, ovrRenderTexture_Resolve, glInvalidateFramebuffer");
                        rt->ovrRenderTexture_Resolve();


                        parms.Layers[(int)ovrFrameLayerType.VRAPI_FRAME_LAYER_TYPE_WORLD].Images[eye].TexId = rt->ColorTexture;
                        parms.Layers[(int)ovrFrameLayerType.VRAPI_FRAME_LAYER_TYPE_WORLD].Images[eye].TexCoordsFromTanAngles = appState.Renderer.TanAngleMatrix;
                        parms.Layers[(int)ovrFrameLayerType.VRAPI_FRAME_LAYER_TYPE_WORLD].Images[eye].HeadPose = tracking.HeadPose;
                    }
                }

                ovrRenderTexture.ovrRenderTexture_SetNone();


                appState.Renderer.BufferIndex = (appState.Renderer.BufferIndex + 1) % NUM_BUFFERS;

                // 1130
                appState.tracei60("exit ovrRenderer_RenderFrame BufferIndex", appState.Renderer.BufferIndex);
                //ConsoleExtensions.tracei("exit ovrRenderer_RenderFrame");
                return(parms);
            }
            object AppThreadFunction()
            {
                // 1778
                ConsoleExtensions.trace("enter pthread_create AppThreadFunction, call vrapi_DefaultInitParms");

                var java = default(ovrJava);
                java.Vm = this.JavaVm;
                java.Vm.AttachCurrentThread(java.Vm, out java.Env, null);
                java.ActivityObject = this.ActivityObject;
                // 1785

                var initParms = VrApi_Helpers.vrapi_DefaultInitParms(ref java);
                ConsoleExtensions.trace("AppThreadFunction, call vrapi_Initialize");
                VrApi.vrapi_Initialize(ref initParms);


                ConsoleExtensions.trace("AppThreadFunction, create ovrApp, call ovrEgl_CreateContext");
                var appState = new ovrApp(ref java);
                // 1794
                appState.Egl.ovrEgl_CreateContext(null);


                ConsoleExtensions.trace("AppThreadFunction, call vrapi_GetHmdInfo, then ovrRenderer_Create");
                var hmdInfo = VrApi.vrapi_GetHmdInfo(ref java);
                appState.Renderer.ovrRenderer_Create(ref hmdInfo);

                ConsoleExtensions.trace("AppThreadFunction, enter loop, call ovrMessageQueue_GetNextMessage");
                bool destroyed = false;
                while (!destroyed)
                {
                    #region ovrMessageQueue_GetNextMessage
                    var ok = true;
                    while (ok)
                    {
                        ovrMessage message;
                        var waitForMessages = appState.Ovr == null && !destroyed;

                        if (!this.MessageQueue.ovrMessageQueue_GetNextMessage(out message, waitForMessages)) break;
                        // 1812

                        // no switch for jsc?
                        if (message.Id == MESSAGE.MESSAGE_ON_CREATE)
                        {
                            ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_CREATE");
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_START) { }
                        else if (message.Id == MESSAGE.MESSAGE_ON_RESUME) { appState.Resumed = true; }
                        else if (message.Id == MESSAGE.MESSAGE_ON_PAUSE) { appState.Resumed = false; }
                        else if (message.Id == MESSAGE.MESSAGE_ON_STOP) { }
                        else if (message.Id == MESSAGE.MESSAGE_ON_DESTROY) { appState.NativeWindow = null; destroyed = true; }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_CREATED)
                        {
                            ConsoleExtensions.trace("AppThreadFunction, MESSAGE_ON_SURFACE_CREATED");
                            var m0 = message[0];
                            appState.NativeWindow = (native_window.ANativeWindow)m0.Pointer;
                        }
                        else if (message.Id == MESSAGE.MESSAGE_ON_SURFACE_DESTROYED) { appState.NativeWindow = null; }
                        else if (message.Id == MESSAGE.MESSAGE_ON_KEY_EVENT) { appState.ovrApp_HandleKeyEvent((keycodes.AKEYCODE)(int)message[0], (input.AInputEventAction)(int)message[1]); }
                        else if (message.Id == MESSAGE.MESSAGE_ON_TOUCH_EVENT)
                        {
                            //ConsoleExtensions.tracei("AppThreadFunction, MESSAGE_ON_TOUCH_EVENT");
                            appState.ovrApp_HandleTouchEvent(message[0], message[1], message[2]);
                        }

                        appState.ovrApp_HandleVrModeChanges();
                    }
                    #endregion



                    appState.ovrApp_BackButtonAction();
                    appState.ovrApp_HandleSystemEvents();

                    // not ready yet?
                    // set by vrapi_EnterVrMode
                    if (appState.Ovr == null)
                    {
                        continue;
                    }


                    #region VRAPI_FRAME_INIT_LOADING_ICON_FLUSH
                    if (!appState.Scene.ovrScene_IsCreated())
                    {
                        // need to keep the enum typename?

                        var parms = VrApi_Helpers.vrapi_DefaultFrameParms(ref appState.Java, ovrFrameInit.VRAPI_FRAME_INIT_LOADING_ICON_FLUSH, 0);
                        parms.FrameIndex = appState.FrameIndex;
                        ConsoleExtensions.trace("vrapi_SubmitFrame VRAPI_FRAME_INIT_LOADING_ICON_FLUSH");
                        appState.Ovr.vrapi_SubmitFrame(ref parms);

                        unistd.usleep(1000);

                        appState.Scene.ovrScene_Create();

                        // keep the loader on for a moment...
                        unistd.usleep(1000);
                    }
                    #endregion


                    //appState.tracei60("AppThreadFunction, FrameIndex ", (int)appState.FrameIndex);

                    // 1862
                    appState.FrameIndex++;

                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedDisplayTime");
                    var predictedDisplayTime = appState.Ovr.vrapi_GetPredictedDisplayTime(appState.FrameIndex);
                    //ConsoleExtensions.tracei("AppThreadFunction, vrapi_GetPredictedTracking");
                    var tracking = appState.Ovr.vrapi_GetPredictedTracking(predictedDisplayTime);

                    // like step in physics?
                    appState.Simulation.ovrSimulation_AdvanceSimulation(predictedDisplayTime);

                    {
                        //var parms = appState.Renderer.ovrRenderer_RenderFrame(ref appState, ref tracking);
                        var parms = appState.Renderer.ovrRenderer_RenderFrame(appState, ref tracking);

                        appState.tracei60("vrapi_SubmitFrame ", (int)appState.FrameIndex);
                        appState.tracei60(" tracking.Status ", (int)tracking.Status);
                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.x ", (int)(1000 * tracking.HeadPose.Pose.Orientation.x));
                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.y ", (int)(1000 * tracking.HeadPose.Pose.Orientation.y));
                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.z ", (int)(1000 * tracking.HeadPose.Pose.Orientation.z));
                        appState.tracei60(" tracking.HeadPose.Pose.Orientation.w ", (int)(1000 * tracking.HeadPose.Pose.Orientation.w));
                        appState.Ovr.vrapi_SubmitFrame(ref parms);
                    }
                    // 1891
                }

                // 1896
                appState.Renderer.ovrRenderer_Destroy();

                // 1898
                appState.Scene.ovrScene_Destroy();
                appState.Egl.ovrEgl_DestroyContext();
                VrApi.vrapi_Shutdown();

                java.Vm.DetachCurrentThread(java.Vm);
                return null;
            }