mirror of
https://github.com/amwatson/CitraVR.git
synced 2024-09-20 03:11:40 +02:00
Added preliminary support for in-game error messages
This commit is contained in:
parent
8a0da51a88
commit
feedbba5fe
11 changed files with 129 additions and 69 deletions
1
src/android/app/proguard-rules.pro
vendored
1
src/android/app/proguard-rules.pro
vendored
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue