Added Android Meta Quest Support

This commit is contained in:
iChris4 committed 2026-09-16 22:06:34 +02:00
1 parent 49b8602543
commit e52227baf5
67 files changed
+5662 -176

No files matched your search

+12
View File
@@ -1,4 +1,7 @@
#include "fiber_manager.h"
#if defined(__ANDROID__)
#include "aurora_events.h"
#endif
#include "memory.h"
#include "abi_bridge.h"
#include "hle_stubs.h"
@@ -219,6 +222,10 @@ void GuestFiberManager::Shutdown() {
s_initialized = false;
}
bool GuestFiberManager::IsOnSchedulerFiber() {
return !s_initialized || s_schedulerFiber == nullptr || HostContext::IsCurrent(s_schedulerFiber);
}
bool GuestFiberManager::IsInitialized() {
return s_initialized;
}
@@ -414,6 +421,11 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu
// Switch to the target fiber (the target fiber will load its own context)
HostContext::Switch(fiberHandle);
#if defined(__ANDROID__)
// A guest thread running on its own stack may have deferred an SDL event
// poll to the scheduler stack; service it now if that is where we are.
ServicePendingAuroraEventsOnScheduler();
#endif
// When we return here, the fiber that issued SwitchToThread has resumed.
// That does not automatically mean the previous guest thread became runnable
+9
View File
@@ -33,6 +33,15 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#if defined(__ANDROID__) && __ANDROID_API__ < 30
// Bionic only declares memfd_create() from API 30, but every Android kernel
// this build can run on (4.x on the Quest line) has had the syscall since 3.17.
#include <linux/memfd.h>
#include <sys/syscall.h>
static int memfd_create(const char* name, unsigned int flags) {
return static_cast<int>(syscall(__NR_memfd_create, name, flags));
}
#endif
#endif
namespace GuestFlat {
+2 -1
View File
@@ -509,7 +509,8 @@ int64_t ConsumeAudioPollDeltaMicros()
// kMaxBlocksPerTick per pass. Never return early on a zero delta: two scheduler passes
// can land in the same microsecond and the backlog still needs servicing.
constexpr int64_t kMaxPollDeltaMicros = 100'000;
return std::min(elapsed, kMaxPollDeltaMicros);
// int64_t is `long` on LP64 Android while the clock rep is `long long`; pick one type.
return std::min<int64_t>(static_cast<int64_t>(elapsed), static_cast<int64_t>(kMaxPollDeltaMicros));
}
} // namespace
+37
View File
@@ -70,6 +70,13 @@
#include <aurora/aurora.h>
#include <aurora/gfx.h>
#include <dolphin/gx/GXAurora.h>
#if defined(__ANDROID__)
#include <android/log.h>
// Renames main() to SDL_main(), which SDLActivity's nativeRunMain resolves
// from libmain.so with dlsym once the Java side has set up the surface.
#include <SDL3/SDL_main.h>
#endif
#include <dolphin/vi.h>
// Defined in `runtime/src/hle/vi.cpp` (used by GX/VI HLE).
@@ -245,6 +252,11 @@ void WriteProcessTranscriptChunk(ProcessTranscriptState& state, const char* data
void PumpTranscriptPipe(ProcessTranscriptState& state, int readFd, int mirrorFd) {
std::array<char, 4096> buffer{};
#if defined(__ANDROID__)
// Android sends stdout/stderr to /dev/null, so the mirror fd shows nothing;
// forward complete lines to logcat as well (tag WiiCompiled).
std::string pendingLine;
#endif
for (;;) {
#if defined(_WIN32)
const int bytesRead = _read(readFd, buffer.data(), static_cast<unsigned int>(buffer.size()));
@@ -275,6 +287,21 @@ void PumpTranscriptPipe(ProcessTranscriptState& state, int readFd, int mirrorFd)
}
WriteProcessTranscriptChunk(state, buffer.data(), static_cast<size_t>(bytesRead));
#if defined(__ANDROID__)
pendingLine.append(buffer.data(), static_cast<size_t>(bytesRead));
size_t lineStart = 0;
for (size_t newline = pendingLine.find('\n'); newline != std::string::npos;
newline = pendingLine.find('\n', lineStart)) {
const std::string line = pendingLine.substr(lineStart, newline - lineStart);
__android_log_write(ANDROID_LOG_INFO, "WiiCompiled", line.c_str());
lineStart = newline + 1;
}
pendingLine.erase(0, lineStart);
if (pendingLine.size() > 3500) {
__android_log_write(ANDROID_LOG_INFO, "WiiCompiled", pendingLine.c_str());
pendingLine.clear();
}
#endif
}
}
@@ -1352,6 +1379,16 @@ int RuntimeMain(int argc, char** argv) {
const std::string auroraCachePath = RuntimeConfigFile::PathToUtf8(rendererCacheDirectory);
auroraConfig.userPath = auroraUserPath.c_str();
auroraConfig.cachePath = auroraCachePath.c_str();
#if defined(__ANDROID__)
// Aurora defaults resourcesPath to SDL_GetBasePath(), which is empty on
// Android; the transferable pipeline cache lives with the other bundled
// resources the activity unpacked.
std::string auroraResourcesPath;
if (const auto resources = RuntimeConfigFile::ExecutableDirectory()) {
auroraResourcesPath = RuntimeConfigFile::PathToUtf8(*resources);
auroraConfig.resourcesPath = auroraResourcesPath.c_str();
}
#endif
auroraConfig.logCallback = &RuntimeAuroraLogCallback;
auroraConfig.logLevel = LOG_DEBUG;
const bool configWidescreen = RuntimeConfigFile::WidescreenEnabled(true);
+3 -3
View File
@@ -16,7 +16,7 @@
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Media.Control.h>
#elif defined(__linux__)
#elif defined(__linux__) && !defined(__ANDROID__)
#include <dlfcn.h>
#include <string>
@@ -196,7 +196,7 @@ void MonitorWindowsMediaSessions() noexcept {
<< error.message().c_str() << std::endl;
}
}
#elif defined(__linux__)
#elif defined(__linux__) && !defined(__ANDROID__)
// For linux - watches MPRIS players on the D-Bus session bus and drives the
// same g_externalMediaPlaying / g_mediaControl* atomics.
@@ -453,7 +453,7 @@ void StartMonitor() noexcept {
// The process owns this monitor for its remaining lifetime. Keeping it
// detached avoids shutdown ordering between WinRT and static audio state.
std::thread(MonitorWindowsMediaSessions).detach();
#elif defined(__linux__)
#elif defined(__linux__) && !defined(__ANDROID__)
// Detached so there is no shutdown ordering to manage against static audio state.
std::thread(MonitorLinuxMprisSessions).detach();
#else
+15
View File
@@ -46,6 +46,14 @@ std::optional<std::filesystem::path> ExecutableDirectory() noexcept {
std::error_code ec;
const auto resolved = std::filesystem::weakly_canonical(path, ec);
return (ec ? std::filesystem::path(path) : resolved).parent_path();
#elif defined(__ANDROID__)
// Bundled read-only runtime resources (wii_bootstrap/, dsp_coef.bin,
// initial_pipeline_cache.db) unpacked from the APK by the activity. Stands
// in for the executable directory the desktop builds look next to.
if (const char* resources = std::getenv("MKW_ANDROID_RESOURCES_DIR"); resources && *resources) {
return std::filesystem::path(resources);
}
return std::nullopt;
#else
return std::nullopt;
#endif
@@ -66,6 +74,13 @@ std::filesystem::path ApplicationDataDirectory(std::string_view applicationName)
if (const passwd* user = getpwuid(getuid()); user && user->pw_dir && *user->pw_dir) {
return std::filesystem::path(user->pw_dir) / "Library" / "Application Support" / applicationName;
}
#elif defined(__ANDROID__)
// The app's external files directory (Android/data/<package>/files) so a
// user can push game data and Config.toml with adb or a file browser; the
// activity falls back to the private files directory when there is none.
if (const char* data = std::getenv("MKW_ANDROID_DATA_DIR"); data && *data) {
return std::filesystem::path(data) / applicationName;
}
#endif
return std::filesystem::current_path() / applicationName;
}
+171
View File
@@ -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__)
+450
View File
@@ -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)
+184 -63
View File
@@ -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
}
+1
View File
@@ -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