move thread/JNI management out of VrApp so that all its objects de-init while JNIEnv is active

This commit is contained in:
amwatson 2024-02-16 17:57:39 -06:00
parent a8cd1e2bff
commit c68b499ad9
4 changed files with 105 additions and 68 deletions

View file

@ -30,15 +30,25 @@ License : Licensed under GPLv3 or any later version.
} \
} while (0)
XrInstance instance;
XrInstance instance = XR_NULL_HANDLE;
void OXR_CheckErrors(XrResult result, const char* function, bool failOnError) {
if (XR_FAILED(result)) {
if (instance == XR_NULL_HANDLE) {
if (failOnError) {
FAIL("OpenXR error: %s: \"%s\" (error code 0x%x)", function, "Instance is null",
result);
} else {
ALOGV("OpenXR error: {}: \"{}\" (error code 0x%x)", function, "Instance is null",
result);
}
} else {
char errorBuffer[XR_MAX_RESULT_STRING_SIZE];
xrResultToString(instance, result, errorBuffer);
if (failOnError) {
FAIL("OpenXR error: %s: %s\n", function, errorBuffer);
FAIL("OpenXR error: %s: \"%s\" (error code 0x%x)", function, errorBuffer, result);
} else {
ALOGV("OpenXR error: {}: {}\n", function, errorBuffer);
ALOGV("OpenXR error: {}: \"{}\" (error code 0x%x)", function, errorBuffer, result);
}
}
}
}
@ -51,9 +61,6 @@ void OXR_CheckErrors(XrResult result, const char* function, bool failOnErr
================================================================================
*/
namespace {
#define DECL_PFN(pfn) PFN_##pfn pfn = nullptr
#define INIT_PFN(pfn) OXR(xrGetInstanceProcAddr(instance, #pfn, (PFN_xrVoidFunction*)(&pfn)))
[[maybe_unused]] void XrEnumerateLayerProperties() {
XrResult result;
PFN_xrEnumerateApiLayerProperties xrEnumerateApiLayerProperties;

View file

@ -466,6 +466,7 @@ int32_t GameSurfaceLayer::Init(const XrSession& session, const jobject activityO
void GameSurfaceLayer::Shutdown() {
xrDestroySwapchain(mSwapchain.mHandle);
mSwapchain.mHandle = XR_NULL_HANDLE;
mEnv->DeleteGlobalRef(mVrGameSurfaceClass);
}

View file

@ -27,7 +27,8 @@ struct Message {
// 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
SHOW_ERROR_MESSAGE = 1, // payload 0 = show error message, 1 = hide error message
EXIT_NEEDED = 2 // payload ignored
};
Message() {}

View file

@ -150,42 +150,17 @@ uint32_t GetDefaultGameResolutionFactorForHmd(const VRSettings::HMDType& hmdType
class VRApp {
public:
VRApp(JavaVM* jvm, jobject activityObjectGlobalRef)
: mVm(jvm)
, mActivityObject(activityObjectGlobalRef)
, mIsStopRequested(false) {
assert(mVm != nullptr);
mThread = std::thread([this]() { MainLoop(); });
}
VRApp(jobject activityObjectGlobalRef)
: mActivityObject(activityObjectGlobalRef) {}
~VRApp() {
assert(mVm != nullptr);
// Note: this is in most cases already going to be true by the time the
// destructor is called, because it is set to true in onStop()
mIsStopRequested = true;
ALOGI("Waiting for VRApp thread to join");
if (mThread.joinable()) { mThread.join(); }
ALOGI("VRApp thread joined");
JNIEnv* jni;
if (mVm->AttachCurrentThread(&jni, nullptr) != JNI_OK) {
// on most of the android systems, calling exit() isn't like the end
// of the world. The reapers get to it within a few seconds
ALOGD("{}() ERROR: could not attach to JVM", __FUNCTION__);
exit(0);
}
jni->DeleteGlobalRef(mActivityObject);
}
~VRApp() { assert(mIsStopRequested); }
void MainLoop() {
void MainLoop(JNIEnv* jni) {
//////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////
JNIEnv* jni;
if (mVm->AttachCurrentThread(&jni, nullptr) != JNI_OK) {
FAIL("%s(): Could not attach to JVM", __FUNCTION__);
}
Init(jni);
//////////////////////////////////////////////////
@ -195,25 +170,15 @@ public:
while (!mIsStopRequested) { Frame(jni); }
//////////////////////////////////////////////////
// Shutdown
// Exit
//////////////////////////////////////////////////
ALOGI("::MainLoop() exiting");
mVm->DetachCurrentThread();
}
private:
void Init(JNIEnv* jni) {
// Gotta set this after the JNIEnv is attached, or else it'll be
// overwritten
prctl(PR_SET_NAME, (long)"CS::Main", 0, 0, 0);
if (gOpenXr == nullptr) {
gOpenXr = std::make_unique<OpenXr>();
const int32_t ret = gOpenXr->Init(mVm, mActivityObject);
if (ret < 0) { FAIL("OpenXR::Init() failed: error code %d", ret); }
}
vr::gSession = gOpenXr->mSession;
assert(gOpenXr != nullptr);
mInputStateStatic =
std::make_unique<InputStateStatic>(OpenXr::GetInstance(), gOpenXr->mSession);
@ -886,7 +851,8 @@ private:
}
}
void HandleSessionStateChangedEvent(JNIEnv *jni, const XrEventDataSessionStateChanged& newState) {
void HandleSessionStateChangedEvent(JNIEnv* jni,
const XrEventDataSessionStateChanged& newState) {
static XrSessionState lastState = XR_SESSION_STATE_UNKNOWN;
if (newState.state != lastState) {
ALOGV("{}(): Received XR_SESSION_STATE_CHANGED state {}->{} "
@ -1038,6 +1004,11 @@ private:
}
break;
}
case Message::Type::EXIT_NEEDED: {
ALOGD("Received EXIT_NEEDED message");
mIsStopRequested = true;
break;
}
default:
ALOGE("Unknown message type: %d", message.mType);
@ -1048,10 +1019,9 @@ private:
uint64_t mFrameIndex = 0;
std::thread mThread;
JavaVM* mVm;
jobject mActivityObject;
std::atomic<bool> mIsStopRequested = {false};
bool mIsStopRequested = false;
bool mIsXrSessionActive = false;
bool mHasFocus = false;
bool mIsKeyboardActive = false;
@ -1075,14 +1045,77 @@ private:
jmethodID mOpenSettingsMethodID = nullptr;
};
class VRAppThread {
public:
VRAppThread(JavaVM* jvm, JNIEnv* jni, jobject activityObject)
: mVm(jvm)
, mActivityObjectGlobalRef(jni->NewGlobalRef(activityObject)) {
assert(jvm != nullptr);
assert(activityObject != nullptr);
mThread = std::thread([this]() {ThreadFn();});
}
~VRAppThread() {
gMessageQueue.Post(Message(Message::Type::EXIT_NEEDED, 0));
// Note: this is in most cases already going to be true by the time the
// destructor is called, because it is set to true in onStop()
ALOGI("Waiting for VRAppThread to join");
if (mThread.joinable()) { mThread.join(); }
ALOGI("VRAppThread joined");
}
private:
void ThreadFn() {
assert(mVm != nullptr);
ALOGI("VRAppThread: starting");
JNIEnv* jni = nullptr;
if (mVm->AttachCurrentThread(&jni, nullptr) != JNI_OK) {
FAIL("%s(): Could not attach to mVm", __FUNCTION__);
}
// Gotta set this after the JNIEnv is attached, or else it'll be
// overwritten
prctl(PR_SET_NAME, (long)"CVR::Main", 0, 0, 0);
ThreadFnJNI(jni);
mVm->DetachCurrentThread();
ALOGI("VRAppThread: exited");
}
// All operations assume that the JNIEnv is attached
void ThreadFnJNI(JNIEnv* jni) {
assert(jni != nullptr);
assert(mActivityObjectGlobalRef != nullptr);
if (gOpenXr == nullptr) {
gOpenXr = std::make_unique<OpenXr>();
const int32_t ret = gOpenXr->Init(mVm, mActivityObjectGlobalRef);
if (ret < 0) { FAIL("OpenXR::Init() failed: error code %d", ret); }
}
vr::gSession = gOpenXr->mSession;
{ std::make_unique<VRApp>(mActivityObjectGlobalRef)->MainLoop(jni); }
ALOGI("::MainLoop() exited");
gOpenXr->Shutdown();
jni->DeleteGlobalRef(mActivityObjectGlobalRef);
mActivityObjectGlobalRef = nullptr;
}
JavaVM* mVm;
jobject mActivityObjectGlobalRef;
std::thread mThread;
};
struct VRAppHandle {
VRAppHandle(VRApp* _p)
VRAppHandle(VRAppThread* _p)
: p(_p) {}
VRAppHandle(jlong _l)
: l(_l) {}
union {
VRApp* p = nullptr;
VRAppThread* p = nullptr;
jlong l;
};
};
@ -1095,7 +1128,7 @@ Java_org_citra_citra_1emu_vr_VrActivity_nativeOnCreate(JNIEnv* env, jobject thiz
JavaVM* jvm;
env->GetJavaVM(&jvm);
auto ret = VRAppHandle(new VRApp(jvm, env->NewGlobalRef(thiz))).l;
auto ret = VRAppHandle(new VRAppThread(jvm, env, thiz)).l;
ALOGI("nativeOnCreate {}", ret);
return ret;
}
@ -1105,11 +1138,6 @@ Java_org_citra_citra_1emu_vr_VrActivity_nativeOnDestroy(JNIEnv* env, jobject thi
ALOGI("nativeOnDestroy {}", static_cast<long>(handle));
if (handle != 0) { delete VRAppHandle(handle).p; }
// Even though OpenXR is created on a different thread, this
// should be ok because thread exit is a fence, and the delete waits to
// join.
if (gOpenXr != nullptr) { gOpenXr->Shutdown(); }
}
extern "C" JNIEXPORT jint JNICALL