diff --git a/src/android/app/src/main/java/org/citra/citra_emu/vr/ui/VrUILayer.kt b/src/android/app/src/main/java/org/citra/citra_emu/vr/ui/VrUILayer.kt index 877edc920..ab0a9d17f 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/vr/ui/VrUILayer.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/vr/ui/VrUILayer.kt @@ -144,6 +144,7 @@ abstract class VrUILayer( onSurfaceCreated() } + /*** Debug/Testing */ fun writeBitmapToDisk(bmp: Bitmap, outName: String?) { val sdCard = activity.externalCacheDir if (sdCard != null && outName != null) { diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h index d88e3941d..fdf33f176 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.h @@ -78,6 +78,7 @@ public: /** Constructor. * @param position: position of the layer, in world space + * @param jni: the JNI environment. Should be attached to the current thread * @param activity object: reference to the current activity. Used to get * the class information for gameSurfaceClass * @param session a valid XrSession @@ -91,13 +92,14 @@ public: */ void SetSurface() const; - /** Called once-per-frame. Populates the given layer descriptor to show the + /** Called once-per-frame. Populates the layer list to show the * top and bottom panels as two separate layers. * * @param space the XrSpace this layer should be positioned with. The * center of the layer is placed in the center of the FOV. * @param layers the array of layers to populate - * @param layerCount the number of layers in the array + * @param layerCount the layer count passed to XrEndFrame. This is incremented by + * the number of layers added by this function. */ void Frame(const XrSpace& space, std::vector& layers, uint32_t& layerCount) const; diff --git a/src/android/app/src/main/jni/vr/layers/UILayer.cpp b/src/android/app/src/main/jni/vr/layers/UILayer.cpp index 8da6e9e60..0102f96e2 100644 --- a/src/android/app/src/main/jni/vr/layers/UILayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/UILayer.cpp @@ -1,16 +1,17 @@ -/******************************************************************************* +/*************************************************************************************************** -Filename : UILayer.cpp +Filename : UILayer.h -Content : Handles the projection of the "Game Surface" panels into XR. - Includes the "top" panel (stereo game screen) and the "bottom" - panel (mono touchpad). +Content : Utility class for creating interactive Android UI windows that + are displayed in the VR environment. Authors : Amanda M. Watson License : Licensed under GPLv3 or any later version. Refer to the license.txt file included. -*******************************************************************************/ +***************************************************************************************************/ + + #include "UILayer.h" @@ -31,12 +32,7 @@ License : Licensed under GPLv3 or any later version. namespace { /** Used to translate texture coordinates into the corresponding coordinates - * on the Android Activity Window. - * - * EmulationActivity still thinks its window - * (invisible) shows the game surface, so when we forward touch events to - * corresponding coordinates on the window, it will be as if the user touched - * the game surface. + * on the virtual Window. */ class AndroidWindowBounds { public: @@ -87,6 +83,7 @@ struct BoundsHandle { //----------------------------------------------------------------------------- // JNI functions + extern "C" JNIEXPORT void JNICALL Java_org_citra_citra_1emu_vr_ui_VrUILayer_00024Companion_nativeSetBounds(JNIEnv* env, jobject thiz, jlong handle, jint left, diff --git a/src/android/app/src/main/jni/vr/layers/UILayer.h b/src/android/app/src/main/jni/vr/layers/UILayer.h index 2784c9ce3..0a7d6e49d 100644 --- a/src/android/app/src/main/jni/vr/layers/UILayer.h +++ b/src/android/app/src/main/jni/vr/layers/UILayer.h @@ -1,16 +1,77 @@ +/*************************************************************************************************** + +Filename : UILayer.h + +Content : Utility class for creating interactive Android UI windows that + are displayed in the VR environment. + + The UILayer manages two things: + 1. a virtual display window that is created and managed by the + Android OS. This window is used to display the UI. + 2. A surface-backed XrSwapchain that the virtual window renders into. + + From Android's perspective, it's rendering a group of views to a secondary + display, while in reality it's rendering to a surface that is composited + into the VR environment. + + UILayer will translate clicks on the VR layer into touch events on the corresponding + Android window. + + This method is relatively efficient, but it must be used carefully. It + will only render the UI when the UI has changed; however, it will continue rendering + when the UI is not visible if there are updates. Additionally, while the UIs I am + using are small enough that < 10MiB in RAM/GPU memory respectively, it could be + a dumb, costly use of GPU resources to allocate huge, high-resolution UIs on init + that are rarely used. + + Therefore, be careful on the front-end: don't create UIs that animate/update while + they aren't visible, and don't create UIs that are too large. If these are needed, + use another technique. + + (note: if rendering expensive UIs for occasional use is ever necessary, note that, + because CitraVR is made of compositor layers on top of a compositor-rendered + background, there is 0 risk of juddering due to missed frame updates. Therefore, + you can get awaay with something most VR apps cannot: you can miss a bunch of + frames in order to do an expensive one-time operation on the render thread in the + frame loop -- like allocate a swapchain and construct a UI over a multi-frame period + (dropping all frames in the process) -- with 0 risk of judder. If you are certain + you can successfully de-init an expensive UI after it's used without any dangling + resources, just do that instead of having it persist from init. The + TryCreateSwapchain() paradigm is designed to support this, though you need to + add a de-init function) + +Authors : Amanda M. Watson +License : Licensed under GPLv3 or any later version. + Refer to the license.txt file included. + +***************************************************************************************************/ + +#pragma once + #include "../OpenXR.h" #include "../Swapchain.h" #include "../utils/Common.h" #include +/* +================================================================================ + + UILayer + +================================================================================ +*/ class UILayer { public: /** Constructor. - * @param position: position of the layer, in world space + * @param className: the class name of the Java class representing the UI layer. + * This class should subclass org.citra.citra_emu.ui.VrUILayer. + * @param position: position of the layer + * @param orientation: orientation of the layer + * @param jni: the JNI environment. Should be attached to the current thread * @param activity object: reference to the current activity. Used to get - * the class information for gameSurfaceClass + * the class information for UILayer * @param session a valid XrSession */ UILayer(const std::string& className, const XrVector3f&& position, @@ -18,18 +79,14 @@ public: const XrSession& session); ~UILayer(); - /** Called on resume. Sets the surface in the native rendering library. - * Overrides the normal surface passed by Citra - */ - void SetSurface() const; - - /** Called once-per-frame. Populates the given layer descriptor to show the - * top and bottom panels as two separate layers. + /** Called once-per-frame. Populates the given layer list with a single layer + * representing the UI layer. * * @param space the XrSpace this layer should be positioned with. The * center of the layer is placed in the center of the FOV. * @param layers the array of layers to populate - * @param layerCount the number of layers in the array + * @param layerCount the layer count passed to XrEndFrame. This is incremented by + * the number of layers added by this function. */ void Frame(const XrSpace& space, std::vector& layers, uint32_t& layerCount) const; @@ -66,6 +123,12 @@ public: */ void TryCreateSwapchain(); + /** Forwards the click event to the corresponding position on the Android UI window. + * @param pos2d the 2D position of the click in the Android display coordinate + * system. + * @param type the type of the click event. 0 for down, 1 for up, 2 for movement (while cursor + * is already pressed). + */ void SendClickToUI(const XrVector2f& pos2d, const int type); private: @@ -73,13 +136,12 @@ private: const XrSession& session); void Shutdown(); + bool mIsSwapchainCreated = false; const XrSession mSession; Swapchain mSwapchain; XrPosef mPanelFromWorld; - bool mIsSwapchainCreated = false; - //============================ // JNI objects JNIEnv* mEnv = nullptr;