mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 10:00:27 +02:00
Added Android Meta Quest Support
This commit is contained in:
1 parent
49b8602543
commit
e52227baf5
67 files changed
+5662
-176
No files matched your search
@@ -0,0 +1,171 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(__ANDROID__)
|
||||
|
||||
#include "vr/openxr_android.h"
|
||||
|
||||
#include <SDL3/SDL_system.h>
|
||||
#include <jni.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define XR_USE_PLATFORM_ANDROID
|
||||
#include <openxr/openxr_platform.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
|
||||
namespace mkw::vr {
|
||||
namespace {
|
||||
|
||||
std::mutex g_mutex;
|
||||
JavaVM* g_vm = nullptr;
|
||||
jobject g_activity = nullptr; // global reference, never released: it lives as long as the process
|
||||
bool g_loader_initialized = false;
|
||||
XrInstanceCreateInfoAndroidKHR g_instance_create{XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR};
|
||||
|
||||
void Emit(const OpenXRLogCallback& logger, OpenXRLogLevel level, const std::string& message) noexcept {
|
||||
if (!logger) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
logger(level, message);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ResolveAndroidObjectsLocked(std::string* error) {
|
||||
if (g_vm != nullptr && g_activity != nullptr) {
|
||||
return true;
|
||||
}
|
||||
// SDL attaches the calling thread to the VM on demand and hands back the
|
||||
// activity as a local reference the caller owns.
|
||||
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
|
||||
auto activity = static_cast<jobject>(SDL_GetAndroidActivity());
|
||||
if (env == nullptr || activity == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "SDL has not published the Android activity yet";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
JavaVM* vm = nullptr;
|
||||
if (env->GetJavaVM(&vm) != JNI_OK || vm == nullptr) {
|
||||
env->DeleteLocalRef(activity);
|
||||
if (error != nullptr) {
|
||||
*error = "JNIEnv::GetJavaVM failed";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
g_vm = vm;
|
||||
g_activity = env->NewGlobalRef(activity);
|
||||
env->DeleteLocalRef(activity);
|
||||
if (g_activity == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "could not pin the Android activity";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool OpenXRAndroidInitializeLoader(OpenXRLogCallback logger, std::string* error) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (g_loader_initialized) {
|
||||
return true;
|
||||
}
|
||||
if (!ResolveAndroidObjectsLocked(error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PFN_xrInitializeLoaderKHR initialize_loader = nullptr;
|
||||
XrResult result = xrGetInstanceProcAddr(
|
||||
XR_NULL_HANDLE, "xrInitializeLoaderKHR",
|
||||
reinterpret_cast<PFN_xrVoidFunction*>(&initialize_loader));
|
||||
if (XR_FAILED(result) || initialize_loader == nullptr) {
|
||||
if (error != nullptr) {
|
||||
std::ostringstream message;
|
||||
message << "xrInitializeLoaderKHR is unavailable (" << static_cast<int>(result) << ')';
|
||||
*error = message.str();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// The activity is deliberately used as the loader context (an Activity is
|
||||
// a Context). Meta's runtime keys activity readiness off it; with a plain
|
||||
// application context the session parks in IDLE forever (DolphinXR finding).
|
||||
XrLoaderInitInfoAndroidKHR loader_init{XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR};
|
||||
loader_init.applicationVM = g_vm;
|
||||
loader_init.applicationContext = g_activity;
|
||||
result = initialize_loader(reinterpret_cast<const XrLoaderInitInfoBaseHeaderKHR*>(&loader_init));
|
||||
if (XR_FAILED(result)) {
|
||||
if (error != nullptr) {
|
||||
std::ostringstream message;
|
||||
message << "xrInitializeLoaderKHR failed (" << static_cast<int>(result) << ')';
|
||||
*error = message.str();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
g_instance_create.next = nullptr;
|
||||
g_instance_create.applicationVM = g_vm;
|
||||
g_instance_create.applicationActivity = g_activity;
|
||||
g_loader_initialized = true;
|
||||
Emit(logger, OpenXRLogLevel::Info, "OpenXR Android loader initialized against the SDL activity");
|
||||
return true;
|
||||
}
|
||||
|
||||
const void* OpenXRAndroidInstanceCreateNext() {
|
||||
std::lock_guard lock(g_mutex);
|
||||
return g_loader_initialized ? &g_instance_create : nullptr;
|
||||
}
|
||||
|
||||
bool OpenXRAndroidRegisterThread(OpenXRRuntime& runtime, OpenXRAndroidThreadType type) {
|
||||
if (!runtime.HasSession()) {
|
||||
return false;
|
||||
}
|
||||
const auto& extensions = runtime.EnabledExtensions();
|
||||
bool enabled = false;
|
||||
for (const auto& extension : extensions) {
|
||||
if (extension == XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME) {
|
||||
enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
PFN_xrSetAndroidApplicationThreadKHR set_thread = nullptr;
|
||||
if (!runtime.LoadFunction("xrSetAndroidApplicationThreadKHR", &set_thread) || set_thread == nullptr) {
|
||||
return false;
|
||||
}
|
||||
XrAndroidThreadTypeKHR xr_type = XR_ANDROID_THREAD_TYPE_APPLICATION_WORKER_KHR;
|
||||
switch (type) {
|
||||
case OpenXRAndroidThreadType::ApplicationMain:
|
||||
xr_type = XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR;
|
||||
break;
|
||||
case OpenXRAndroidThreadType::ApplicationWorker:
|
||||
xr_type = XR_ANDROID_THREAD_TYPE_APPLICATION_WORKER_KHR;
|
||||
break;
|
||||
case OpenXRAndroidThreadType::RendererMain:
|
||||
xr_type = XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR;
|
||||
break;
|
||||
case OpenXRAndroidThreadType::RendererWorker:
|
||||
xr_type = XR_ANDROID_THREAD_TYPE_RENDERER_WORKER_KHR;
|
||||
break;
|
||||
}
|
||||
const auto thread_id = static_cast<uint32_t>(syscall(SYS_gettid));
|
||||
XrResult result = set_thread(runtime.Session(), xr_type, thread_id);
|
||||
if (XR_FAILED(result) && type == OpenXRAndroidThreadType::RendererWorker) {
|
||||
// Some Quest runtime builds advertise the extension but reject the
|
||||
// renderer-worker type; the renderer-main hint still raises priority.
|
||||
result = set_thread(runtime.Session(), XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR, thread_id);
|
||||
}
|
||||
runtime.ObserveResult(result);
|
||||
return XR_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
} // namespace mkw::vr
|
||||
|
||||
#endif // defined(MKW_ENABLE_OPENXR) && defined(__ANDROID__)
|
||||
@@ -0,0 +1,450 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#if defined(MKW_ENABLE_OPENXR)
|
||||
|
||||
#include "vr/openxr_input.h"
|
||||
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
#include <SDL3/SDL_joystick.h>
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <sys/system_properties.h>
|
||||
#endif
|
||||
|
||||
namespace mkw::vr {
|
||||
namespace {
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Debug-only remote button presses for headset experiments driven over adb, so
|
||||
// a menu can be reached without someone wearing the headset:
|
||||
// adb shell setprop debug.wiicompiled.inject <sequence>:<button>
|
||||
// A new sequence number holds the button for kInjectHoldFrames XR frames.
|
||||
// Buttons: a, b, x, y, start, up, down, left, right. The property is unset in
|
||||
// normal use, so this costs one property read every few frames.
|
||||
constexpr uint32_t kInjectHoldFrames = 12;
|
||||
constexpr uint32_t kInjectPollFrames = 4;
|
||||
|
||||
struct InjectedPress {
|
||||
long sequence = -1;
|
||||
std::string button;
|
||||
uint32_t frames_left = 0;
|
||||
uint32_t poll_countdown = 0;
|
||||
};
|
||||
|
||||
InjectedPress& Injection() {
|
||||
static InjectedPress press;
|
||||
return press;
|
||||
}
|
||||
|
||||
void PollInjection() {
|
||||
InjectedPress& press = Injection();
|
||||
if (press.frames_left > 0) {
|
||||
--press.frames_left;
|
||||
}
|
||||
if (press.poll_countdown > 0) {
|
||||
--press.poll_countdown;
|
||||
return;
|
||||
}
|
||||
press.poll_countdown = kInjectPollFrames;
|
||||
char value[PROP_VALUE_MAX]{};
|
||||
if (__system_property_get("debug.wiicompiled.inject", value) <= 0) {
|
||||
return;
|
||||
}
|
||||
char* end = nullptr;
|
||||
const long sequence = std::strtol(value, &end, 10);
|
||||
if (end == value || *end != ':' || sequence == press.sequence) {
|
||||
return;
|
||||
}
|
||||
const bool first_read = press.sequence < 0;
|
||||
press.sequence = sequence;
|
||||
if (first_read) {
|
||||
return; // A value left over from an earlier run is not a new press.
|
||||
}
|
||||
press.button = end + 1;
|
||||
press.frames_left = kInjectHoldFrames;
|
||||
}
|
||||
|
||||
bool Injected(const char* button) {
|
||||
const InjectedPress& press = Injection();
|
||||
return press.frames_left > 0 && press.button == button;
|
||||
}
|
||||
#else
|
||||
void PollInjection() {}
|
||||
bool Injected(const char*) { return false; }
|
||||
#endif
|
||||
|
||||
constexpr uint32_t kHandCount = 2;
|
||||
|
||||
struct Binding {
|
||||
XrAction* action;
|
||||
const char* path;
|
||||
};
|
||||
|
||||
Sint16 ToAxis(float value) noexcept {
|
||||
const float clamped = std::clamp(value, -1.0f, 1.0f);
|
||||
return static_cast<Sint16>(std::lround(clamped * 32767.0f));
|
||||
}
|
||||
|
||||
// Trigger axes are reported by SDL gamepads on the positive half only.
|
||||
Sint16 ToTrigger(float value) noexcept {
|
||||
const float clamped = std::clamp(value, 0.0f, 1.0f);
|
||||
return static_cast<Sint16>(std::lround(clamped * 32767.0f));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OpenXRInput::OpenXRInput(OpenXRLogCallback logger) : m_logger(std::move(logger)) {}
|
||||
|
||||
OpenXRInput::~OpenXRInput() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
bool OpenXRInput::Create(OpenXRRuntime& runtime) {
|
||||
m_last_error.clear();
|
||||
if (m_created) {
|
||||
return true;
|
||||
}
|
||||
if (!runtime.IsInitialized() || !runtime.HasSession()) {
|
||||
m_last_error = "OpenXR input needs an initialized runtime with a session";
|
||||
return false;
|
||||
}
|
||||
m_runtime = &runtime;
|
||||
|
||||
if (!Check(xrStringToPath(runtime.Instance(), "/user/hand/left", &m_hand_paths[0]),
|
||||
"xrStringToPath(/user/hand/left)") ||
|
||||
!Check(xrStringToPath(runtime.Instance(), "/user/hand/right", &m_hand_paths[1]),
|
||||
"xrStringToPath(/user/hand/right)")) {
|
||||
Destroy();
|
||||
return false;
|
||||
}
|
||||
if (!CreateActions() || !SuggestBindings()) {
|
||||
Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
XrSessionActionSetsAttachInfo attach{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO};
|
||||
attach.countActionSets = 1;
|
||||
attach.actionSets = &m_action_set;
|
||||
if (!Check(xrAttachSessionActionSets(runtime.Session(), &attach), "xrAttachSessionActionSets")) {
|
||||
Destroy();
|
||||
return false;
|
||||
}
|
||||
m_created = true;
|
||||
if (!AttachVirtualGamepad()) {
|
||||
Log(OpenXRLogLevel::Warning,
|
||||
"SDL refused the virtual gamepad; OpenXR controllers will not reach the game");
|
||||
}
|
||||
Log(OpenXRLogLevel::Info, "OpenXR controller actions attached");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenXRInput::CreateActions() {
|
||||
XrActionSetCreateInfo set_info{XR_TYPE_ACTION_SET_CREATE_INFO};
|
||||
std::strncpy(set_info.actionSetName, "mkw_gameplay", XR_MAX_ACTION_SET_NAME_SIZE - 1);
|
||||
std::strncpy(set_info.localizedActionSetName, "Gameplay", XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE - 1);
|
||||
set_info.priority = 0;
|
||||
if (!Check(xrCreateActionSet(m_runtime->Instance(), &set_info, &m_action_set), "xrCreateActionSet")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct Spec {
|
||||
XrAction* action;
|
||||
const char* name;
|
||||
const char* localized;
|
||||
XrActionType type;
|
||||
};
|
||||
const std::array<Spec, 8> specs{{
|
||||
{&m_thumbstick, "thumbstick", "Thumbstick", XR_ACTION_TYPE_VECTOR2F_INPUT},
|
||||
{&m_thumbstick_click, "thumbstick_click", "Thumbstick Click", XR_ACTION_TYPE_BOOLEAN_INPUT},
|
||||
{&m_trigger, "trigger", "Trigger", XR_ACTION_TYPE_FLOAT_INPUT},
|
||||
{&m_squeeze, "squeeze", "Grip", XR_ACTION_TYPE_FLOAT_INPUT},
|
||||
{&m_button_primary, "button_primary", "A / X", XR_ACTION_TYPE_BOOLEAN_INPUT},
|
||||
{&m_button_secondary, "button_secondary", "B / Y", XR_ACTION_TYPE_BOOLEAN_INPUT},
|
||||
{&m_menu, "menu", "Menu", XR_ACTION_TYPE_BOOLEAN_INPUT},
|
||||
{&m_haptic, "haptic", "Haptic", XR_ACTION_TYPE_VIBRATION_OUTPUT},
|
||||
}};
|
||||
for (const Spec& spec : specs) {
|
||||
XrActionCreateInfo info{XR_TYPE_ACTION_CREATE_INFO};
|
||||
info.actionType = spec.type;
|
||||
std::strncpy(info.actionName, spec.name, XR_MAX_ACTION_NAME_SIZE - 1);
|
||||
std::strncpy(info.localizedActionName, spec.localized, XR_MAX_LOCALIZED_ACTION_NAME_SIZE - 1);
|
||||
info.countSubactionPaths = kHandCount;
|
||||
info.subactionPaths = m_hand_paths;
|
||||
if (!Check(xrCreateAction(m_action_set, &info, spec.action), spec.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenXRInput::SuggestBindings() {
|
||||
const auto suggest = [&](const char* profile, const std::vector<Binding>& bindings, bool required) {
|
||||
XrPath profile_path = XR_NULL_PATH;
|
||||
if (!Check(xrStringToPath(m_runtime->Instance(), profile, &profile_path), profile)) {
|
||||
return false;
|
||||
}
|
||||
std::vector<XrActionSuggestedBinding> suggested;
|
||||
suggested.reserve(bindings.size());
|
||||
for (const Binding& binding : bindings) {
|
||||
XrPath path = XR_NULL_PATH;
|
||||
if (XR_FAILED(xrStringToPath(m_runtime->Instance(), binding.path, &path))) {
|
||||
continue;
|
||||
}
|
||||
suggested.push_back({*binding.action, path});
|
||||
}
|
||||
XrInteractionProfileSuggestedBinding info{XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
|
||||
info.interactionProfile = profile_path;
|
||||
info.countSuggestedBindings = static_cast<uint32_t>(suggested.size());
|
||||
info.suggestedBindings = suggested.data();
|
||||
const XrResult result = xrSuggestInteractionProfileBindings(m_runtime->Instance(), &info);
|
||||
m_runtime->ObserveResult(result);
|
||||
if (XR_FAILED(result)) {
|
||||
std::ostringstream message;
|
||||
message << "xrSuggestInteractionProfileBindings(" << profile << ") failed (" << result << ')';
|
||||
if (required) {
|
||||
m_last_error = message.str();
|
||||
Log(OpenXRLogLevel::Error, m_last_error);
|
||||
return false;
|
||||
}
|
||||
Log(OpenXRLogLevel::Warning, message.str());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Meta Quest Touch controllers (Quest 2 / 3 / Pro all expose this profile).
|
||||
const std::vector<Binding> touch{
|
||||
{&m_thumbstick, "/user/hand/left/input/thumbstick"},
|
||||
{&m_thumbstick, "/user/hand/right/input/thumbstick"},
|
||||
{&m_thumbstick_click, "/user/hand/left/input/thumbstick/click"},
|
||||
{&m_thumbstick_click, "/user/hand/right/input/thumbstick/click"},
|
||||
{&m_trigger, "/user/hand/left/input/trigger/value"},
|
||||
{&m_trigger, "/user/hand/right/input/trigger/value"},
|
||||
{&m_squeeze, "/user/hand/left/input/squeeze/value"},
|
||||
{&m_squeeze, "/user/hand/right/input/squeeze/value"},
|
||||
{&m_button_primary, "/user/hand/left/input/x/click"},
|
||||
{&m_button_primary, "/user/hand/right/input/a/click"},
|
||||
{&m_button_secondary, "/user/hand/left/input/y/click"},
|
||||
{&m_button_secondary, "/user/hand/right/input/b/click"},
|
||||
{&m_menu, "/user/hand/left/input/menu/click"},
|
||||
{&m_haptic, "/user/hand/left/output/haptic"},
|
||||
{&m_haptic, "/user/hand/right/output/haptic"},
|
||||
};
|
||||
if (!suggest("/interaction_profiles/oculus/touch_controller", touch, true)) {
|
||||
return false;
|
||||
}
|
||||
// Minimal fallback so an unfamiliar runtime still offers a select and a menu.
|
||||
const std::vector<Binding> simple{
|
||||
{&m_button_primary, "/user/hand/right/input/select/click"},
|
||||
{&m_button_secondary, "/user/hand/left/input/select/click"},
|
||||
{&m_menu, "/user/hand/left/input/menu/click"},
|
||||
{&m_haptic, "/user/hand/left/output/haptic"},
|
||||
{&m_haptic, "/user/hand/right/output/haptic"},
|
||||
};
|
||||
suggest("/interaction_profiles/khr/simple_controller", simple, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenXRInput::AttachVirtualGamepad() {
|
||||
SDL_VirtualJoystickDesc desc;
|
||||
SDL_INIT_INTERFACE(&desc);
|
||||
desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
|
||||
desc.naxes = SDL_GAMEPAD_AXIS_COUNT;
|
||||
desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT;
|
||||
desc.button_mask = (1u << SDL_GAMEPAD_BUTTON_SOUTH) | (1u << SDL_GAMEPAD_BUTTON_EAST) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_WEST) | (1u << SDL_GAMEPAD_BUTTON_NORTH) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_START) | (1u << SDL_GAMEPAD_BUTTON_LEFT_STICK) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_RIGHT_STICK) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER) |
|
||||
(1u << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
|
||||
desc.axis_mask = (1u << SDL_GAMEPAD_AXIS_LEFTX) | (1u << SDL_GAMEPAD_AXIS_LEFTY) |
|
||||
(1u << SDL_GAMEPAD_AXIS_RIGHTX) | (1u << SDL_GAMEPAD_AXIS_RIGHTY) |
|
||||
(1u << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1u << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
|
||||
desc.name = "OpenXR Touch Controllers";
|
||||
const SDL_JoystickID id = SDL_AttachVirtualJoystick(&desc);
|
||||
if (id == 0) {
|
||||
m_last_error = std::string("SDL_AttachVirtualJoystick failed: ") + SDL_GetError();
|
||||
Log(OpenXRLogLevel::Warning, m_last_error);
|
||||
return false;
|
||||
}
|
||||
SDL_Joystick* joystick = SDL_OpenJoystick(id);
|
||||
if (joystick == nullptr) {
|
||||
m_last_error = std::string("SDL_OpenJoystick failed: ") + SDL_GetError();
|
||||
Log(OpenXRLogLevel::Warning, m_last_error);
|
||||
SDL_DetachVirtualJoystick(id);
|
||||
return false;
|
||||
}
|
||||
m_joystick_id = id;
|
||||
m_joystick = joystick;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenXRInput::DetachVirtualGamepad() {
|
||||
if (m_joystick != nullptr) {
|
||||
SDL_CloseJoystick(static_cast<SDL_Joystick*>(m_joystick));
|
||||
m_joystick = nullptr;
|
||||
}
|
||||
if (m_joystick_id != 0) {
|
||||
SDL_DetachVirtualJoystick(m_joystick_id);
|
||||
m_joystick_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenXRInput::Destroy() {
|
||||
DetachVirtualGamepad();
|
||||
if (m_action_set != XR_NULL_HANDLE) {
|
||||
// Destroying the set destroys every action created from it.
|
||||
xrDestroyActionSet(m_action_set);
|
||||
m_action_set = XR_NULL_HANDLE;
|
||||
}
|
||||
m_thumbstick = m_thumbstick_click = m_trigger = m_squeeze = XR_NULL_HANDLE;
|
||||
m_button_primary = m_button_secondary = m_menu = m_haptic = XR_NULL_HANDLE;
|
||||
m_hand_paths[0] = m_hand_paths[1] = XR_NULL_PATH;
|
||||
m_created = false;
|
||||
m_runtime = nullptr;
|
||||
}
|
||||
|
||||
void OpenXRInput::Sync(XrTime) {
|
||||
if (!m_created || m_runtime == nullptr || !m_runtime->IsSessionFocused()) {
|
||||
return;
|
||||
}
|
||||
XrActiveActionSet active{m_action_set, XR_NULL_PATH};
|
||||
XrActionsSyncInfo sync{XR_TYPE_ACTIONS_SYNC_INFO};
|
||||
sync.countActiveActionSets = 1;
|
||||
sync.activeActionSets = &active;
|
||||
const XrResult result = xrSyncActions(m_runtime->Session(), &sync);
|
||||
m_runtime->ObserveResult(result);
|
||||
if (XR_FAILED(result)) {
|
||||
if (!m_logged_sync_failure) {
|
||||
m_logged_sync_failure = true;
|
||||
std::ostringstream message;
|
||||
message << "xrSyncActions failed (" << result << ')';
|
||||
Log(OpenXRLogLevel::Warning, message.str());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_joystick == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto* joystick = static_cast<SDL_Joystick*>(m_joystick);
|
||||
|
||||
const auto boolean = [&](XrAction action, uint32_t hand) {
|
||||
XrActionStateGetInfo info{XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
info.action = action;
|
||||
info.subactionPath = m_hand_paths[hand];
|
||||
XrActionStateBoolean state{XR_TYPE_ACTION_STATE_BOOLEAN};
|
||||
return XR_SUCCEEDED(xrGetActionStateBoolean(m_runtime->Session(), &info, &state)) &&
|
||||
state.isActive == XR_TRUE && state.currentState == XR_TRUE;
|
||||
};
|
||||
const auto scalar = [&](XrAction action, uint32_t hand) {
|
||||
XrActionStateGetInfo info{XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
info.action = action;
|
||||
info.subactionPath = m_hand_paths[hand];
|
||||
XrActionStateFloat state{XR_TYPE_ACTION_STATE_FLOAT};
|
||||
if (XR_FAILED(xrGetActionStateFloat(m_runtime->Session(), &info, &state)) ||
|
||||
state.isActive != XR_TRUE) {
|
||||
return 0.0f;
|
||||
}
|
||||
return state.currentState;
|
||||
};
|
||||
const auto vector = [&](XrAction action, uint32_t hand) {
|
||||
XrActionStateGetInfo info{XR_TYPE_ACTION_STATE_GET_INFO};
|
||||
info.action = action;
|
||||
info.subactionPath = m_hand_paths[hand];
|
||||
XrActionStateVector2f state{XR_TYPE_ACTION_STATE_VECTOR2F};
|
||||
if (XR_FAILED(xrGetActionStateVector2f(m_runtime->Session(), &info, &state)) ||
|
||||
state.isActive != XR_TRUE) {
|
||||
return XrVector2f{0.0f, 0.0f};
|
||||
}
|
||||
return state.currentState;
|
||||
};
|
||||
|
||||
PollInjection();
|
||||
XrVector2f left = vector(m_thumbstick, 0);
|
||||
const XrVector2f right = vector(m_thumbstick, 1);
|
||||
if (Injected("up")) {
|
||||
left.y = 1.0f;
|
||||
} else if (Injected("down")) {
|
||||
left.y = -1.0f;
|
||||
} else if (Injected("left")) {
|
||||
left.x = -1.0f;
|
||||
} else if (Injected("right")) {
|
||||
left.x = 1.0f;
|
||||
}
|
||||
// OpenXR thumbsticks report +Y up; SDL gamepads report +Y down.
|
||||
SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_LEFTX, ToAxis(left.x));
|
||||
SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_LEFTY, ToAxis(-left.y));
|
||||
SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_RIGHTX, ToAxis(right.x));
|
||||
SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_RIGHTY, ToAxis(-right.y));
|
||||
SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, ToTrigger(scalar(m_trigger, 0)));
|
||||
SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, ToTrigger(scalar(m_trigger, 1)));
|
||||
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_SOUTH,
|
||||
boolean(m_button_primary, 1) || Injected("a"));
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_EAST,
|
||||
boolean(m_button_secondary, 1) || Injected("b"));
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_WEST,
|
||||
boolean(m_button_primary, 0) || Injected("x"));
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_NORTH,
|
||||
boolean(m_button_secondary, 0) || Injected("y"));
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_START, boolean(m_menu, 0) || Injected("start"));
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, boolean(m_thumbstick_click, 0));
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, boolean(m_thumbstick_click, 1));
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, scalar(m_squeeze, 0) > 0.5f);
|
||||
SDL_SetJoystickVirtualButton(joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, scalar(m_squeeze, 1) > 0.5f);
|
||||
}
|
||||
|
||||
void OpenXRInput::ApplyHaptic(uint32_t hand, float amplitude, XrDuration duration) {
|
||||
if (!m_created || m_runtime == nullptr || hand >= kHandCount || m_haptic == XR_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
XrHapticVibration vibration{XR_TYPE_HAPTIC_VIBRATION};
|
||||
vibration.amplitude = std::clamp(amplitude, 0.0f, 1.0f);
|
||||
vibration.duration = duration;
|
||||
vibration.frequency = XR_FREQUENCY_UNSPECIFIED;
|
||||
XrHapticActionInfo info{XR_TYPE_HAPTIC_ACTION_INFO};
|
||||
info.action = m_haptic;
|
||||
info.subactionPath = m_hand_paths[hand];
|
||||
if (vibration.amplitude <= 0.0f) {
|
||||
xrStopHapticFeedback(m_runtime->Session(), &info);
|
||||
return;
|
||||
}
|
||||
xrApplyHapticFeedback(m_runtime->Session(), &info,
|
||||
reinterpret_cast<const XrHapticBaseHeader*>(&vibration));
|
||||
}
|
||||
|
||||
bool OpenXRInput::Check(XrResult result, const char* operation) {
|
||||
if (m_runtime != nullptr) {
|
||||
m_runtime->ObserveResult(result);
|
||||
}
|
||||
if (XR_SUCCEEDED(result)) {
|
||||
return true;
|
||||
}
|
||||
std::ostringstream message;
|
||||
message << operation << " failed (" << result << ')';
|
||||
m_last_error = message.str();
|
||||
Log(OpenXRLogLevel::Error, m_last_error);
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenXRInput::Log(OpenXRLogLevel level, const std::string& message) const noexcept {
|
||||
if (!m_logger) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
m_logger(level, message);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mkw::vr
|
||||
|
||||
#endif // defined(MKW_ENABLE_OPENXR)
|
||||
@@ -27,12 +27,24 @@
|
||||
#include <thread>
|
||||
|
||||
#if defined(MKW_ENABLE_OPENXR)
|
||||
#include "vr/openxr_backend.h"
|
||||
#include "vr/openxr_input.h"
|
||||
#include "vr/openxr_runtime.h"
|
||||
#if defined(_WIN32)
|
||||
#include "vr/openxr_d3d12.h"
|
||||
#define MKW_OPENXR_GRAPHICS_BACKEND 1
|
||||
#elif defined(__ANDROID__)
|
||||
#include "vr/openxr_android.h"
|
||||
#include "vr/openxr_vulkan.h"
|
||||
#include <time.h>
|
||||
#define XR_USE_TIMESPEC
|
||||
#include <openxr/openxr_platform.h>
|
||||
#define MKW_OPENXR_GRAPHICS_BACKEND 1
|
||||
#else
|
||||
#include "vr/openxr_vulkan_backend.h"
|
||||
#define MKW_OPENXR_GRAPHICS_BACKEND 0
|
||||
#endif
|
||||
#else
|
||||
#define MKW_OPENXR_GRAPHICS_BACKEND 0
|
||||
#endif
|
||||
|
||||
namespace mkw::vr {
|
||||
@@ -40,6 +52,14 @@ namespace {
|
||||
|
||||
inline constexpr float kDegreesToRadians = 0.01745329252f;
|
||||
|
||||
// A standalone headset has no desktop to fall back to, so VR is on unless the
|
||||
// user's configuration turns it off. Desktop builds keep the opt-in default.
|
||||
#if defined(__ANDROID__)
|
||||
inline constexpr bool kVrEnabledDefault = true;
|
||||
#else
|
||||
inline constexpr bool kVrEnabledDefault = false;
|
||||
#endif
|
||||
|
||||
void ConfigurePolicy(bool enabled) noexcept {
|
||||
MkwVRPolicyReset();
|
||||
MkwVRPolicyConfig config{};
|
||||
@@ -54,7 +74,15 @@ void ConfigurePolicy(bool enabled) noexcept {
|
||||
MkwVRFirstPersonApplyConfiguredSettings();
|
||||
}
|
||||
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
|
||||
#if defined(_WIN32)
|
||||
using GraphicsBackend = OpenXRD3D12Backend;
|
||||
inline constexpr const char* kGraphicsBackendName = "D3D12";
|
||||
#else
|
||||
using GraphicsBackend = OpenXRVulkanBackend;
|
||||
inline constexpr const char* kGraphicsBackendName = "Vulkan";
|
||||
#endif
|
||||
|
||||
struct Quaternion {
|
||||
float x = 0.0f;
|
||||
@@ -240,17 +268,16 @@ public:
|
||||
last_error_.clear();
|
||||
}
|
||||
if (graphics_retained_) {
|
||||
SetError("OpenXR cannot be restarted after an unfenceable D3D12 submission");
|
||||
SetError(std::string("OpenXR cannot be restarted after an unfenceable ") +
|
||||
kGraphicsBackendName + " submission");
|
||||
return OpenXRStartupResult::Unavailable;
|
||||
}
|
||||
requested_ = RuntimeConfigFile::VrEnabled(false);
|
||||
requested_ = RuntimeConfigFile::VrEnabled(kVrEnabledDefault);
|
||||
ConfigurePolicy(requested_);
|
||||
if (!requested_) {
|
||||
return OpenXRStartupResult::Disabled;
|
||||
}
|
||||
if (aurora_config.desiredBackend != BACKEND_AUTO &&
|
||||
aurora_config.desiredBackend != BACKEND_D3D12) {
|
||||
SetError("OpenXR currently requires the D3D12 graphics backend on Windows");
|
||||
if (!BackendMatchesConfiguredGraphicsApi(aurora_config)) {
|
||||
return OpenXRStartupResult::Unavailable;
|
||||
}
|
||||
|
||||
@@ -259,27 +286,56 @@ public:
|
||||
level == OpenXRLogLevel::Warning ? "warning" : "info";
|
||||
RT_LOG(RT_TAG_RUNTIME) << "[openxr::" << name << "] " << message << std::endl;
|
||||
};
|
||||
#if defined(__ANDROID__)
|
||||
{
|
||||
std::string loader_error;
|
||||
if (!OpenXRAndroidInitializeLoader(logger_, &loader_error)) {
|
||||
SetError("OpenXR Android loader initialization failed: " + loader_error);
|
||||
return OpenXRStartupResult::Unavailable;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
runtime_ = std::make_unique<OpenXRRuntime>(logger_);
|
||||
backend_ = std::make_unique<OpenXRD3D12Backend>(logger_);
|
||||
backend_ = std::make_unique<GraphicsBackend>(logger_);
|
||||
|
||||
OpenXRConfig config{};
|
||||
config.application_name = aurora_config.appName != nullptr ? aurora_config.appName
|
||||
: "WiiCompiled";
|
||||
config.engine_name = "Aurora";
|
||||
config.resolution_scale = RuntimeConfigFile::VrRenderScale(1.0f);
|
||||
#if defined(_WIN32)
|
||||
config.required_extensions = {"XR_KHR_D3D12_enable"};
|
||||
config.optional_extensions = {"XR_KHR_win32_convert_performance_counter_time", "XR_FB_display_refresh_rate"};
|
||||
config.optional_extensions = {"XR_KHR_win32_convert_performance_counter_time",
|
||||
"XR_FB_display_refresh_rate"};
|
||||
#else
|
||||
// Either Vulkan binding extension is acceptable; the backend picks
|
||||
// whichever the runtime enabled, preferring enable2.
|
||||
config.required_extensions = {"XR_KHR_android_create_instance"};
|
||||
config.optional_extensions = {"XR_KHR_vulkan_enable2", "XR_KHR_vulkan_enable",
|
||||
"XR_KHR_convert_timespec_time",
|
||||
"XR_KHR_android_thread_settings",
|
||||
"XR_FB_display_refresh_rate"};
|
||||
config.instance_create_next = OpenXRAndroidInstanceCreateNext();
|
||||
#endif
|
||||
if (!runtime_->Initialize(config)) {
|
||||
SetError("OpenXR instance initialization failed: " + runtime_->LastError().message);
|
||||
ResetPreparedObjects();
|
||||
return OpenXRStartupResult::Unavailable;
|
||||
}
|
||||
const auto& extensions = runtime_->EnabledExtensions();
|
||||
if (std::find(extensions.begin(), extensions.end(),
|
||||
"XR_KHR_win32_convert_performance_counter_time") != extensions.end()) {
|
||||
const auto has_extension = [&](const char* name) {
|
||||
return std::find(extensions.begin(), extensions.end(), name) != extensions.end();
|
||||
};
|
||||
#if defined(_WIN32)
|
||||
if (has_extension("XR_KHR_win32_convert_performance_counter_time")) {
|
||||
runtime_->LoadFunction("xrConvertTimeToWin32PerformanceCounterKHR", &convert_display_time_);
|
||||
}
|
||||
if (std::find(extensions.begin(), extensions.end(), "XR_FB_display_refresh_rate") != extensions.end()) {
|
||||
#else
|
||||
if (has_extension("XR_KHR_convert_timespec_time")) {
|
||||
runtime_->LoadFunction("xrConvertTimeToTimespecTimeKHR", &convert_display_time_);
|
||||
}
|
||||
#endif
|
||||
if (has_extension("XR_FB_display_refresh_rate")) {
|
||||
runtime_->LoadFunction("xrGetDisplayRefreshRateFB", &get_display_refresh_rate_);
|
||||
}
|
||||
interpolation_available_.store(convert_display_time_ != nullptr, std::memory_order_release);
|
||||
@@ -289,12 +345,7 @@ public:
|
||||
return OpenXRStartupResult::Unavailable;
|
||||
}
|
||||
|
||||
const auto& requirements = backend_->GraphicsRequirements();
|
||||
aurora_config.desiredBackend = BACKEND_D3D12;
|
||||
aurora_config.xrInterop = true;
|
||||
aurora_config.hasD3D12AdapterLuid = true;
|
||||
aurora_config.d3d12AdapterLuidLow = requirements.adapter_luid_low;
|
||||
aurora_config.d3d12AdapterLuidHigh = requirements.adapter_luid_high;
|
||||
ApplyGraphicsRequirements(aurora_config);
|
||||
prepared_ = true;
|
||||
return OpenXRStartupResult::Prepared;
|
||||
}
|
||||
@@ -303,8 +354,9 @@ public:
|
||||
if (!prepared_ || runtime_ == nullptr || backend_ == nullptr) {
|
||||
return !requested_;
|
||||
}
|
||||
if (active_backend != BACKEND_D3D12) {
|
||||
SetError("Aurora could not create the OpenXR-required D3D12 backend");
|
||||
if (active_backend != kRequiredAuroraBackend) {
|
||||
SetError(std::string("Aurora could not create the OpenXR-required ") +
|
||||
kGraphicsBackendName + " backend");
|
||||
ResetPreparedObjects();
|
||||
return false;
|
||||
}
|
||||
@@ -313,6 +365,12 @@ public:
|
||||
ResetPreparedObjects();
|
||||
return false;
|
||||
}
|
||||
input_ = std::make_unique<OpenXRInput>(logger_);
|
||||
if (!input_->Create(*runtime_)) {
|
||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR controller input unavailable: " << input_->LastError()
|
||||
<< std::endl;
|
||||
input_.reset();
|
||||
}
|
||||
|
||||
stop_.store(false, std::memory_order_release);
|
||||
{
|
||||
@@ -334,7 +392,8 @@ public:
|
||||
ResetPreparedObjects();
|
||||
return false;
|
||||
}
|
||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR asynchronous D3D12 presentation started" << std::endl;
|
||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR asynchronous " << kGraphicsBackendName
|
||||
<< " presentation started" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -426,17 +485,51 @@ private:
|
||||
AuroraStereoFrame frame{};
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
static constexpr AuroraBackend kRequiredAuroraBackend = BACKEND_D3D12;
|
||||
#else
|
||||
static constexpr AuroraBackend kRequiredAuroraBackend = BACKEND_VULKAN;
|
||||
#endif
|
||||
|
||||
bool BackendMatchesConfiguredGraphicsApi(const AuroraConfig& aurora_config) {
|
||||
if (aurora_config.desiredBackend == BACKEND_AUTO ||
|
||||
aurora_config.desiredBackend == kRequiredAuroraBackend) {
|
||||
return true;
|
||||
}
|
||||
SetError(std::string("OpenXR requires the ") + kGraphicsBackendName +
|
||||
" graphics backend on this platform");
|
||||
return false;
|
||||
}
|
||||
|
||||
void ApplyGraphicsRequirements(AuroraConfig& aurora_config) {
|
||||
aurora_config.desiredBackend = kRequiredAuroraBackend;
|
||||
aurora_config.xrInterop = true;
|
||||
#if defined(_WIN32)
|
||||
const auto& requirements = backend_->GraphicsRequirements();
|
||||
aurora_config.hasD3D12AdapterLuid = true;
|
||||
aurora_config.d3d12AdapterLuidLow = requirements.adapter_luid_low;
|
||||
aurora_config.d3d12AdapterLuidHigh = requirements.adapter_luid_high;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ResetPreparedObjects() {
|
||||
ShutdownOrRetainGraphicsObjects();
|
||||
input_.reset();
|
||||
backend_.reset();
|
||||
runtime_.reset();
|
||||
prepared_ = false;
|
||||
}
|
||||
|
||||
bool ShutdownOrRetainGraphicsObjects() noexcept {
|
||||
if (input_ != nullptr) {
|
||||
// Actions belong to the session and must go before it does.
|
||||
input_->Destroy();
|
||||
input_.reset();
|
||||
}
|
||||
if (backend_ != nullptr && !backend_->Shutdown()) {
|
||||
RT_LOG(RT_TAG_RUNTIME)
|
||||
<< "OpenXR D3D12 queue completion is unknown; retaining the backend, "
|
||||
<< "OpenXR " << kGraphicsBackendName
|
||||
<< " queue completion is unknown; retaining the backend, "
|
||||
"runtime, session, and graphics resources until process exit"
|
||||
<< std::endl;
|
||||
(void)backend_.release();
|
||||
@@ -468,6 +561,11 @@ private:
|
||||
}
|
||||
|
||||
void PacingThread() noexcept {
|
||||
#if defined(__ANDROID__)
|
||||
if (runtime_ != nullptr) {
|
||||
OpenXRAndroidRegisterThread(*runtime_, OpenXRAndroidThreadType::RendererMain);
|
||||
}
|
||||
#endif
|
||||
bool fatal = false;
|
||||
bool presentation_logged = false;
|
||||
VRPresentationMode logged_presentation = VRPresentationMode::Desktop;
|
||||
@@ -489,7 +587,7 @@ private:
|
||||
}
|
||||
}
|
||||
if (events == OpenXREventStatus::ExitRequested) {
|
||||
SetError("OpenXR runtime requested session exit; continuing on the desktop mirror");
|
||||
SetError("OpenXR runtime requested session exit; continuing on the mirror output");
|
||||
break;
|
||||
}
|
||||
if (events == OpenXREventStatus::Error) {
|
||||
@@ -525,10 +623,10 @@ private:
|
||||
<< ", bindings=0x" << std::hex << policy.available_bindings
|
||||
<< std::dec << std::endl;
|
||||
}
|
||||
OpenXRD3D12Presentation presentation{};
|
||||
OpenXRPresentation presentation{};
|
||||
const bool immersive = policy.presentation == VRPresentationMode::ImmersiveRace;
|
||||
presentation.mode = immersive ? OpenXRD3D12FrameMode::ImmersiveProjection
|
||||
: OpenXRD3D12FrameMode::VirtualScreen;
|
||||
presentation.mode = immersive ? OpenXRFrameMode::ImmersiveProjection
|
||||
: OpenXRFrameMode::VirtualScreen;
|
||||
presentation.quad_distance_meters = policy.config.hud_distance_meters;
|
||||
presentation.quad_width_meters = policy.config.hud_width_meters;
|
||||
|
||||
@@ -537,23 +635,26 @@ private:
|
||||
const uint32_t interpolation_target = frame_interpolation_fps_.load(std::memory_order_relaxed);
|
||||
SetInterpolationActive(immersive && FrameInterpolationAvailable() && interpolation_target != 0);
|
||||
|
||||
OpenXRD3D12Frame frame{};
|
||||
const OpenXRD3D12BeginStatus begin = backend_->BeginFrame(presentation, frame);
|
||||
if (begin == OpenXRD3D12BeginStatus::SessionNotRunning) {
|
||||
OpenXRBackendFrame frame{};
|
||||
const OpenXRBeginStatus begin = backend_->BeginFrame(presentation, frame);
|
||||
if (begin == OpenXRBeginStatus::SessionNotRunning) {
|
||||
MkwVRPolicySetSessionActive(false);
|
||||
continue;
|
||||
}
|
||||
if (begin == OpenXRD3D12BeginStatus::ExitRequested) {
|
||||
SetError("OpenXR runtime requested session exit; continuing on the desktop mirror");
|
||||
if (begin == OpenXRBeginStatus::ExitRequested) {
|
||||
SetError("OpenXR runtime requested session exit; continuing on the mirror output");
|
||||
break;
|
||||
}
|
||||
if (begin == OpenXRD3D12BeginStatus::Error) {
|
||||
if (begin == OpenXRBeginStatus::Error) {
|
||||
SetError(backend_->LastError());
|
||||
fatal = true;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateFrameTiming(frame.xr_frame);
|
||||
if (input_ != nullptr) {
|
||||
input_->Sync(frame.xr_frame.predicted_display_time);
|
||||
}
|
||||
// Both of these read this frame's located head pose and must run
|
||||
// before FinishFrame submits a layer built from it.
|
||||
ServiceRecenterRequest();
|
||||
@@ -588,16 +689,16 @@ private:
|
||||
}
|
||||
aurora_notify_stereo_frame();
|
||||
|
||||
OpenXRD3D12SubmissionStatus submission = OpenXRD3D12SubmissionStatus::Timeout;
|
||||
OpenXRSubmissionStatus submission = OpenXRSubmissionStatus::Timeout;
|
||||
bool canceled_before_encode = false;
|
||||
const auto cancel_after =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(50);
|
||||
while (!stop_.load(std::memory_order_acquire) &&
|
||||
submission == OpenXRD3D12SubmissionStatus::Timeout) {
|
||||
submission == OpenXRSubmissionStatus::Timeout) {
|
||||
// Fresh rendering wakes us immediately. A 50 ms keep-alive
|
||||
// protects stalls without issuing eager repeats during GPU work.
|
||||
submission = backend_->WaitForSubmission(frame, 50);
|
||||
if (submission == OpenXRD3D12SubmissionStatus::Timeout) {
|
||||
if (submission == OpenXRSubmissionStatus::Timeout) {
|
||||
// A pause, minimized window, or guest stall may leave no GX
|
||||
// frame to consume this packet. Withdraw it, then cancel the
|
||||
// matching bridge target only if Encode has not taken ownership.
|
||||
@@ -632,12 +733,13 @@ private:
|
||||
if (fatal) {
|
||||
break;
|
||||
}
|
||||
const bool submit = submission == OpenXRD3D12SubmissionStatus::Success;
|
||||
const bool submit = submission == OpenXRSubmissionStatus::Success;
|
||||
if (!backend_->FinishFrame(frame, submit)) {
|
||||
SetError(backend_->LastError());
|
||||
fatal = true;
|
||||
} else if (!submit) {
|
||||
SetError("Aurora's D3D12 stereo copy failed; continuing on the desktop mirror");
|
||||
SetError(std::string("Aurora's ") + kGraphicsBackendName +
|
||||
" stereo copy failed; continuing on the mirror output");
|
||||
fatal = true;
|
||||
} else {
|
||||
++timing_submissions_;
|
||||
@@ -665,7 +767,7 @@ private:
|
||||
ShutdownOrRetainGraphicsObjects();
|
||||
}
|
||||
|
||||
void BuildPublishedFrame(const OpenXRD3D12Frame& source, bool immersive,
|
||||
void BuildPublishedFrame(const OpenXRBackendFrame& source, bool immersive,
|
||||
float units_per_meter, uint64_t content_tag) noexcept {
|
||||
ApplyPendingReferenceSpaceChange(source.xr_frame);
|
||||
auto& destination = published_frame_.frame;
|
||||
@@ -722,8 +824,8 @@ private:
|
||||
// Anchors the menu screen in the application space and holds it there. The
|
||||
// pose is captured once, from the first frame whose head pose is good enough
|
||||
// to place it, and released again by a recenter or an origin change.
|
||||
void UpdateVirtualScreenPose(OpenXRD3D12Frame& frame) noexcept {
|
||||
if (frame.presentation.mode != OpenXRD3D12FrameMode::VirtualScreen) {
|
||||
void UpdateVirtualScreenPose(OpenXRBackendFrame& frame) noexcept {
|
||||
if (frame.presentation.mode != OpenXRFrameMode::VirtualScreen) {
|
||||
return;
|
||||
}
|
||||
constexpr XrViewStateFlags kPoseUsable =
|
||||
@@ -775,17 +877,31 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Converts the compositor's predicted display time onto the runtime's
|
||||
// steady clock, which is what Aurora's interpolation deadlines are paced by.
|
||||
uint64_t DisplayTimeNanos(XrTime display_time) noexcept {
|
||||
if (convert_display_time_ == nullptr) return 0;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
|
||||
#if defined(_WIN32)
|
||||
LARGE_INTEGER display_counter{}, counter{}, frequency{};
|
||||
if (XR_FAILED(convert_display_time_(runtime_->Instance(), display_time, &display_counter)) ||
|
||||
!QueryPerformanceFrequency(&frequency) || frequency.QuadPart <= 0 ||
|
||||
!QueryPerformanceCounter(&counter)) return 0;
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
|
||||
const auto delta = static_cast<int64_t>(
|
||||
(static_cast<double>(display_counter.QuadPart) - static_cast<double>(counter.QuadPart)) *
|
||||
1.0e9 / static_cast<double>(frequency.QuadPart));
|
||||
#else
|
||||
// XR_KHR_convert_timespec_time yields CLOCK_MONOTONIC, the clock behind
|
||||
// libc++'s steady_clock, so the delta is measured on that clock too.
|
||||
timespec display_spec{};
|
||||
timespec now_spec{};
|
||||
if (XR_FAILED(convert_display_time_(runtime_->Instance(), display_time, &display_spec)) ||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now_spec) != 0) return 0;
|
||||
const int64_t display_ns = static_cast<int64_t>(display_spec.tv_sec) * 1'000'000'000ll + display_spec.tv_nsec;
|
||||
const int64_t monotonic_ns = static_cast<int64_t>(now_spec.tv_sec) * 1'000'000'000ll + now_spec.tv_nsec;
|
||||
const int64_t delta = display_ns - monotonic_ns;
|
||||
#endif
|
||||
return now_ns + delta > 0 ? static_cast<uint64_t>(now_ns + delta) : 0;
|
||||
}
|
||||
|
||||
@@ -810,7 +926,8 @@ private:
|
||||
|
||||
OpenXRLogCallback logger_;
|
||||
std::unique_ptr<OpenXRRuntime> runtime_;
|
||||
std::unique_ptr<OpenXRD3D12Backend> backend_;
|
||||
std::unique_ptr<GraphicsBackend> backend_;
|
||||
std::unique_ptr<OpenXRInput> input_;
|
||||
std::thread pacing_thread_;
|
||||
std::atomic_bool stop_{false};
|
||||
std::atomic_bool running_{false};
|
||||
@@ -827,7 +944,11 @@ private:
|
||||
std::chrono::steady_clock::time_point timing_start_ = std::chrono::steady_clock::now();
|
||||
uint32_t timing_submissions_ = 0;
|
||||
PFN_xrGetDisplayRefreshRateFB get_display_refresh_rate_ = nullptr;
|
||||
#if defined(_WIN32)
|
||||
using ConvertDisplayTime = XrResult (XRAPI_PTR*)(XrInstance, XrTime, LARGE_INTEGER*);
|
||||
#else
|
||||
using ConvertDisplayTime = XrResult (XRAPI_PTR*)(XrInstance, XrTime, struct timespec*);
|
||||
#endif
|
||||
ConvertDisplayTime convert_display_time_ = nullptr;
|
||||
std::atomic<PublishedFrame*> published_{nullptr};
|
||||
PublishedFrame published_frame_{};
|
||||
@@ -849,7 +970,7 @@ private:
|
||||
bool graphics_retained_ = false;
|
||||
};
|
||||
|
||||
#endif // defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#endif // MKW_OPENXR_GRAPHICS_BACKEND
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -857,32 +978,32 @@ OpenXRStartupResult OpenXRPrepareAurora(AuroraConfig& config) {
|
||||
#if !defined(MKW_ENABLE_OPENXR)
|
||||
(void)config;
|
||||
ConfigurePolicy(false);
|
||||
return RuntimeConfigFile::VrEnabled(false) ? OpenXRStartupResult::Unavailable
|
||||
: OpenXRStartupResult::Disabled;
|
||||
#elif defined(_WIN32)
|
||||
return RuntimeConfigFile::VrEnabled(kVrEnabledDefault) ? OpenXRStartupResult::Unavailable
|
||||
: OpenXRStartupResult::Disabled;
|
||||
#elif MKW_OPENXR_GRAPHICS_BACKEND
|
||||
return OpenXRIntegration::Get().Prepare(config);
|
||||
#else
|
||||
ConfigurePolicy(RuntimeConfigFile::VrEnabled(false));
|
||||
if (!RuntimeConfigFile::VrEnabled(false)) {
|
||||
(void)config;
|
||||
ConfigurePolicy(RuntimeConfigFile::VrEnabled(kVrEnabledDefault));
|
||||
if (!RuntimeConfigFile::VrEnabled(kVrEnabledDefault)) {
|
||||
return OpenXRStartupResult::Disabled;
|
||||
}
|
||||
const OpenXRVulkanCapabilityInfo capability = OpenXRVulkanBackend::DawnInteropCapability();
|
||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR Vulkan unavailable: " << capability.reason << std::endl;
|
||||
RT_LOG(RT_TAG_RUNTIME) << "OpenXR is not wired to a graphics backend on this platform" << std::endl;
|
||||
return OpenXRStartupResult::Unavailable;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OpenXRStartAfterAurora(AuroraBackend active_backend) {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
return OpenXRIntegration::Get().Start(active_backend);
|
||||
#else
|
||||
(void)active_backend;
|
||||
return !RuntimeConfigFile::VrEnabled(false);
|
||||
return !RuntimeConfigFile::VrEnabled(kVrEnabledDefault);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenXRShutdownBeforeAurora() noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
OpenXRIntegration::Get().Shutdown();
|
||||
#else
|
||||
MkwVRPolicySetSessionActive(false);
|
||||
@@ -890,13 +1011,13 @@ void OpenXRShutdownBeforeAurora() noexcept {
|
||||
}
|
||||
|
||||
void OpenXRServiceProducerFrameBoundary() noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
OpenXRIntegration::Get().ServiceProducerFrameBoundary();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OpenXRIsRunning() noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
return OpenXRIntegration::Get().IsRunning();
|
||||
#else
|
||||
return false;
|
||||
@@ -904,13 +1025,13 @@ bool OpenXRIsRunning() noexcept {
|
||||
}
|
||||
|
||||
void OpenXRRequestRecenter() noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
OpenXRIntegration::Get().RequestRecenter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenXRSetLeanBackDegrees(float degrees) noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
OpenXRIntegration::Get().SetLeanBackDegrees(degrees);
|
||||
#else
|
||||
(void)degrees;
|
||||
@@ -918,7 +1039,7 @@ void OpenXRSetLeanBackDegrees(float degrees) noexcept {
|
||||
}
|
||||
|
||||
void OpenXRSetFrameInterpolationFps(uint32_t target) noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
OpenXRIntegration::Get().SetFrameInterpolationFps(target);
|
||||
#else
|
||||
(void)target;
|
||||
@@ -926,7 +1047,7 @@ void OpenXRSetFrameInterpolationFps(uint32_t target) noexcept {
|
||||
}
|
||||
|
||||
OpenXRFrameTiming OpenXRGetFrameTiming() noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
return OpenXRIntegration::Get().FrameTiming();
|
||||
#else
|
||||
return {};
|
||||
@@ -934,7 +1055,7 @@ OpenXRFrameTiming OpenXRGetFrameTiming() noexcept {
|
||||
}
|
||||
|
||||
bool OpenXRFrameInterpolationAvailable() noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
#if MKW_OPENXR_GRAPHICS_BACKEND
|
||||
return OpenXRIntegration::Get().FrameInterpolationAvailable();
|
||||
#else
|
||||
return false;
|
||||
@@ -944,10 +1065,10 @@ bool OpenXRFrameInterpolationAvailable() noexcept {
|
||||
std::string OpenXRLastError() {
|
||||
#if !defined(MKW_ENABLE_OPENXR)
|
||||
return "this build was compiled without OpenXR support";
|
||||
#elif defined(_WIN32)
|
||||
#elif MKW_OPENXR_GRAPHICS_BACKEND
|
||||
return OpenXRIntegration::Get().LastError();
|
||||
#else
|
||||
return OpenXRVulkanBackend::DawnInteropCapability().reason;
|
||||
return "OpenXR is not wired to a graphics backend on this platform";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,7 @@ bool OpenXRRuntime::CreateInstance() {
|
||||
}
|
||||
|
||||
XrInstanceCreateInfo create_info{XR_TYPE_INSTANCE_CREATE_INFO};
|
||||
create_info.next = m_config.instance_create_next;
|
||||
CopyOpenXRName(create_info.applicationInfo.applicationName,
|
||||
XR_MAX_APPLICATION_NAME_SIZE, m_config.application_name);
|
||||
create_info.applicationInfo.applicationVersion = m_config.application_version;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user