Added preliminary support for in-game error messages

This commit is contained in:
amwatson 2024-02-11 20:12:21 -06:00
parent 8a0da51a88
commit feedbba5fe
11 changed files with 129 additions and 69 deletions

View file

@ -27,4 +27,5 @@
-keep class org.citra.citra_emu.vr.VrActivity { *; }
-keep class org.citra.citra_emu.vr.ui.VrUILayer { *; }
-keep class org.citra.citra_emu.vr.ui.VrKeyboardLayer { *; }
-keep class org.citra.citra_emu.vr.ui.VrErrorMessageLayer { *; }
-keepattributes SourceFile,LineNumberTable

View file

@ -24,8 +24,8 @@ import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.vr.ErrorMessageLayer
import org.citra.citra_emu.vr.VrActivity
import org.citra.citra_emu.vr.ui.VrErrorMessageLayer
import java.lang.ref.WeakReference
import java.util.Date
@ -192,13 +192,16 @@ object NativeLibrary {
Log.error("[NativeLibrary] EmulationActivity not present")
return
}
if (ErrorMessageLayer.instance != null) {
ErrorMessageLayer.showErrorWindow(title, message)
} else if (emulationActivity !is VrActivity) {
if (emulationActivity !is VrActivity) {
Log.debug("[NativeLibrary] (2D) Core error: $title: $message")
val fragment = CoreErrorDialogFragment.newInstance(title, message)
fragment.show(emulationActivity.supportFragmentManager, "coreError")
} else {
Log.error("[NativeLibrary] Core error: $title: $message")
Log.debug("[NativeLibrary] (VR) Core error: $title: $message")
val vrErrorMessageLayer : VrErrorMessageLayer? = VrErrorMessageLayer.sVrErrorMessageLayer.get()
if (vrErrorMessageLayer == null || !vrErrorMessageLayer.showErrorMessage(title, message)) {
Log.error("[NativeLibrary] (could not show dialog) Core error: $title: $message")
}
}
}

View file

@ -1,14 +0,0 @@
package org.citra.citra_emu.vr;
public class ErrorMessageLayer {
public static ErrorMessageLayer instance = null;
public static void showErrorWindow(final String titleStr, final String mainMessageStr) {}
public void _showErrorWindow(final String titleStr, final String mainMessageStr) {}
public void hideErrorWindow() {}
private native void showErrorWindow(final boolean shouldShowError);
}

View file

@ -97,7 +97,7 @@ class VrActivity : EmulationActivity() {
if (isPressed) KeyEvent.ACTION_DOWN else KeyEvent.ACTION_UP, keycode
)
event.source = InputDevice.SOURCE_GAMEPAD
dispatchKeyEvent(event)
runOnUiThread { dispatchKeyEvent(event) }
}
fun forwardVRJoystick(x: Float, y: Float, joystickType: Int) {

View file

@ -0,0 +1,40 @@
package org.citra.citra_emu.vr.ui
import android.widget.Button
import android.widget.TextView
import org.citra.citra_emu.R
import org.citra.citra_emu.vr.VrActivity
import org.citra.citra_emu.vr.utils.VrMessageQueue
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicBoolean
class VrErrorMessageLayer(activity: VrActivity) : VrUILayer(activity, R.layout.vr_error_window) {
private var titleView : TextView? = null
private var messageView: TextView? = null
private var isSurfaceCreated : AtomicBoolean = AtomicBoolean(false)
fun showErrorMessage(title: String, message : String) : Boolean {
if (!isSurfaceCreated.get()) {
return false
}
titleView?.text = title
messageView?.text = message
VrMessageQueue.post(VrMessageQueue.MessageType.SHOW_ERROR_MESSAGE, 1)
return true
}
override fun onSurfaceCreated() {
super.onSurfaceCreated()
titleView = window!!.findViewById(R.id.title)
messageView = window!!.findViewById(R.id.main_message)
window!!.findViewById<Button>(R.id.abort_button).setOnClickListener { _ -> System.exit(0)}
window!!.findViewById<Button>(R.id.continue_button).setOnClickListener { _ -> VrMessageQueue.post(VrMessageQueue.MessageType.SHOW_ERROR_MESSAGE, 0) }
isSurfaceCreated.set(true)
sVrErrorMessageLayer = WeakReference(this)
}
companion object {
var sVrErrorMessageLayer : WeakReference<VrErrorMessageLayer?> = WeakReference<VrErrorMessageLayer?>(null)
}
}

View file

@ -3,7 +3,8 @@ package org.citra.citra_emu.vr.utils
object VrMessageQueue {
// Keep consistent with MessageQueue.h
enum class MessageType(val jniVal: Int) {
SHOW_KEYBOARD(0)
SHOW_KEYBOARD(0),
SHOW_ERROR_MESSAGE(1)
}
@JvmStatic
fun post(messageType: MessageType, payload: Long) {

View file

@ -45,8 +45,8 @@ XrAction CreateAction(XrActionSet actionSet, XrActionType type, const char* acti
return action;
}
XrActionSuggestedBinding
ActionSuggestedBinding(const XrInstance& instance, XrAction action, const char* bindingString) {
XrActionSuggestedBinding ActionSuggestedBinding(const XrInstance& instance, XrAction action,
const char* bindingString) {
XrActionSuggestedBinding asb;
asb.action = action;
XrPath bindingPath;
@ -107,8 +107,8 @@ InputStateStatic::InputStateStatic(const XrInstance& instance, const XrSession&
"squeeze_trigger", nullptr, 2, handSubactionPaths);
XrPath interactionProfilePath = XR_NULL_PATH;
OXR(xrStringToPath(
instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePath));
OXR(xrStringToPath(instance, "/interaction_profiles/oculus/touch_controller",
&interactionProfilePath));
// Create bindings for Quest controllers.
{

View file

@ -11,8 +11,6 @@ License : Licensed under GPLv3 or any later version.
***************************************************************************************************/
#include "UILayer.h"
#include "../vr_settings.h"
@ -151,8 +149,8 @@ bool GetRayIntersectionWithPanel(const XrPosef& panelFromWorld, const uint32_t p
}
// Uses a density for scaling and sets aspect ratio
XrVector2f
GetDensityScaleForSize(const int32_t texWidth, const int32_t texHeight, const float scaleFactor) {
XrVector2f GetDensityScaleForSize(const int32_t texWidth, const int32_t texHeight,
const float scaleFactor) {
const float density = GetDensitySysprop();
return XrVector2f{static_cast<float>(texWidth) / density,
(static_cast<float>(texHeight) / density)} *
@ -166,8 +164,7 @@ UILayer::UILayer(const std::string& className, const XrVector3f&& position,
const XrSession& session)
: mSession(session)
, mPanelFromWorld(XrPosef{XrMath::Quatf::Identity(), position})
, mEnv(env)
{
, mEnv(env) {
const int32_t initializationStatus = Init(className, activityObject, position, session);
if (initializationStatus < 0) {
FAIL("Could not initialize UILayer -- error '%d'", initializationStatus);

View file

@ -23,7 +23,12 @@ License : Licensed under GPLv3 or any later version.
* type.
*/
struct Message {
enum Type { SHOW_KEYBOARD = 0 };
// Note: Keep this in-sync with VrMessageQueue.java.
enum Type {
SHOW_KEYBOARD = 0, // payload 0 = hide keyboard, 1 = show keyboard
SHOW_ERROR_MESSAGE = 1 // payload 0 = show error message, 1 = hide error message
};
Message() {}
Message(const Type type)

View file

@ -71,7 +71,6 @@ void PrioritizeTid(const int tid) {
namespace {
constexpr XrPerfSettingsLevelEXT kGpuPerfLevel = XR_PERF_SETTINGS_LEVEL_BOOST_EXT;
std::chrono::time_point<std::chrono::steady_clock> gOnCreateStartTime;
std::atomic<bool> gShouldShowErrorMessage = {false};
std::unique_ptr<OpenXr> gOpenXr;
MessageQueue gMessageQueue;
@ -232,18 +231,15 @@ public:
// Create the cursor layer.
mCursorLayer = std::make_unique<CursorLayer>(gOpenXr->mSession);
#if defined(UI_LAYER)
mErrorMessageLayer = std::make_unique<UILayer>("org/citra/citra_emu/vr/ErrorMessageLayer",
XrVector3f{0, 0, -0.7}, jni, mActivityObject,
gOpenXr->mSession);
#endif
mErrorMessageLayer = std::make_unique<UILayer>(
"org/citra/citra_emu/vr/ui/VrErrorMessageLayer", XrVector3f{0, -0.1f, -1.0f},
XrQuaternionf{0, 0, 0, 1}, jni, mActivityObject, gOpenXr->mSession);
mKeyboardLayer = std::make_unique<UILayer>(
"org/citra/citra_emu/vr/ui/VrKeyboardLayer", XrVector3f{0, -0.4f, -0.5f},
XrMath::Quatf::FromEuler(0.0f, -MATH_FLOAT_PI / 4.0f, 0.0f), jni, mActivityObject,
gOpenXr->mSession);
//////////////////////////////////////////////////
// Intialize JNI methods
//////////////////////////////////////////////////
@ -422,7 +418,7 @@ private:
}
if (!mKeyboardLayer->IsSwapchainCreated()) { mKeyboardLayer->TryCreateSwapchain(); }
if (!mErrorMessageLayer->IsSwapchainCreated()) { mErrorMessageLayer->TryCreateSwapchain(); }
////////////////////////////////
// XrWaitFrame()
@ -499,13 +495,9 @@ private:
mGameSurfaceLayer->Frame(gOpenXr->mLocalSpace, layers, layerCount);
#if defined(UI_LAYER)
if (gShouldShowErrorMessage) {
XrCompositionLayerQuad quadLayer = {};
mErrorMessageLayer->Frame(gOpenXr->mLocalSpace, quadLayer);
layers[layerCount++].Quad = quadLayer;
if (mErrorMessageLayer->IsSwapchainCreated() && mShouldShowErrorMessage) {
mErrorMessageLayer->Frame(gOpenXr->mLocalSpace, layers, layerCount);
}
#endif
if (mKeyboardLayer->IsSwapchainCreated() && mIsKeyboardActive) {
mKeyboardLayer->Frame(gOpenXr->mLocalSpace, layers, layerCount);
@ -543,7 +535,17 @@ private:
const XrVector3f end = XrMath::Posef::Transform(
mInputStateFrame.mHandPositions[mInputStateFrame.mPreferredHand].pose,
XrVector3f{0, 0, -3.5f});
if (mKeyboardLayer->IsSwapchainCreated() && mIsKeyboardActive) {
// Hit-test panels in order of priority (and known depth)
if (mErrorMessageLayer->IsSwapchainCreated() && mShouldShowErrorMessage) {
shouldRenderCursor = mErrorMessageLayer->GetRayIntersectionWithPanel(
start, end, cursorPos2d, cursorPose3d);
if (triggerState.changedSinceLastSync) {
mErrorMessageLayer->SendClickToUI(cursorPos2d,
triggerState.currentState);
}
} else if (mKeyboardLayer->IsSwapchainCreated() && mIsKeyboardActive) {
shouldRenderCursor = mKeyboardLayer->GetRayIntersectionWithPanel(
start, end, cursorPos2d, cursorPose3d);
if (triggerState.changedSinceLastSync) {
@ -746,12 +748,12 @@ private:
switch (newState.state) {
case XR_SESSION_STATE_FOCUSED:
ALOGV("{}(): Received XR_SESSION_STATE_FOCUSED event", __func__);
if (!mHasFocus) { mEnv->CallVoidMethod(mActivityObject, mResumeGameMethodID); }
if (!mHasFocus && !mShouldShowErrorMessage) { ResumeEmulation(); }
mHasFocus = true;
break;
case XR_SESSION_STATE_VISIBLE:
ALOGV("{}(): Received XR_SESSION_STATE_VISIBLE event", __func__);
if (mHasFocus) { mEnv->CallVoidMethod(mActivityObject, mPauseGameMethodID); }
if (mHasFocus) { PauseEmulation(); }
mHasFocus = false;
break;
case XR_SESSION_STATE_READY:
@ -766,6 +768,16 @@ private:
}
}
void PauseEmulation() {
mEnv->CallVoidMethod(mActivityObject, mPauseGameMethodID);
mIsEmulationPaused = true;
}
void ResumeEmulation() {
mEnv->CallVoidMethod(mActivityObject, mResumeGameMethodID);
mIsEmulationPaused = false;
}
void PollEvents() {
XrEventDataBuffer eventDataBuffer = {};
@ -850,9 +862,28 @@ private:
ALOGD("Keyboard status changed: {} -> {}", mIsKeyboardActive,
shouldShowKeyboard);
}
ALOGD("Received SHOW_KEYBOARD message: {}, state change {} -> {}",
shouldShowKeyboard, mIsKeyboardActive, shouldShowKeyboard);
mIsKeyboardActive = shouldShowKeyboard;
ALOGD("Received SHOW_KEYBOARD message");
break;
}
case Message::Type::SHOW_ERROR_MESSAGE: {
const bool shouldShowErrorMessage = message.mPayload == 1;
ALOGD("Received SHOW_ERROR_MESSAGE message: {}, state change {} -> {}",
shouldShowErrorMessage, mShouldShowErrorMessage, shouldShowErrorMessage);
mShouldShowErrorMessage = shouldShowErrorMessage;
if (mShouldShowErrorMessage && !mIsEmulationPaused) {
ALOGD("Pausing emulation due to error message");
PauseEmulation();
mIsEmulationPaused = true;
}
if (!mShouldShowErrorMessage && mIsEmulationPaused && mHasFocus) {
ALOGD("Resuming emulation after error message");
ResumeEmulation();
mIsEmulationPaused = false;
}
break;
}
@ -863,19 +894,21 @@ private:
}
}
uint64_t mFrameIndex = 0;
std::thread mThread;
JNIEnv* mEnv;
JavaVM* mVm;
jobject mActivityObject;
std::atomic<bool> mIsStopRequested = {false};
bool mIsXrSessionActive = false;
bool mHasFocus = false;
bool mIsKeyboardActive = false;
std::unique_ptr<CursorLayer> mCursorLayer;
#if defined(UI_LAYER)
std::unique_ptr<UILayer> mErrorMessageLayer;
#endif
uint64_t mFrameIndex = 0;
std::thread mThread;
JNIEnv* mEnv;
JavaVM* mVm;
jobject mActivityObject;
std::atomic<bool> mIsStopRequested = {false};
bool mIsXrSessionActive = false;
bool mHasFocus = false;
bool mIsKeyboardActive = false;
bool mShouldShowErrorMessage = false;
bool mIsEmulationPaused = false;
std::unique_ptr<CursorLayer> mCursorLayer;
std::unique_ptr<UILayer> mErrorMessageLayer;
std::unique_ptr<GameSurfaceLayer> mGameSurfaceLayer;
std::unique_ptr<PassthroughLayer> mPassthroughLayer;
std::unique_ptr<UILayer> mKeyboardLayer;
@ -928,11 +961,6 @@ Java_org_citra_citra_1emu_vr_VrActivity_nativeOnDestroy(JNIEnv* env, jobject thi
if (gOpenXr != nullptr) { gOpenXr->Shutdown(); }
}
extern "C" JNIEXPORT void JNICALL Java_org_citra_citra_1emu_vr_ErrorMessageLayer_showErrorWindow(
JNIEnv* env, jobject thiz, jboolean should_show_error) {
gShouldShowErrorMessage = should_show_error;
}
extern "C" JNIEXPORT jint JNICALL
Java_org_citra_citra_1emu_vr_utils_VRUtils_getHMDType(JNIEnv* env, jclass clazz) {
return static_cast<jint>(VRSettings::HmdTypeFromStr(VRSettings::GetHMDTypeStr()));

View file

@ -2,7 +2,6 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="400dp"
android:layout_gravity="center_horizontal"
android:padding="20dp"
android:background="@drawable/vr_menu_background"