vr: add OpenXR headset output, VR tracker and VR controller input behind --vr (#1726)

This commit is contained in:
Leandro Bras
2026-10-20 02:17:17 +02:00
committed by nmzik
parent 0cad4672e9
commit 742fe17173
17 changed files with 1637 additions and 28 deletions
+24 -9
View File
@@ -25,12 +25,8 @@ namespace Libs::Controller {
LIB_NAME("Pad", "Pad");
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
constexpr int PAD_ERROR_ALREADY_OPENED = -2137915388; /* 0x80920004 */
constexpr int PAD_ERROR_NOT_INITIALIZED = -2137915387; /* 0x80920005 */
constexpr int PAD_ERROR_NO_HANDLE = -2137915384; /* 0x80920008 */
constexpr int PAD_HANDLE = 1;
constexpr int PAD_HANDLE = 1;
static_assert(PAD_HANDLE + Config::MAX_PLAYERS <= PAD_HANDLE_VR_LEFT);
constexpr uint32_t RUMBLE_DURATION_MS = 0xffff;
constexpr uint32_t RELEASE_FLUSH_MS = 50;
@@ -215,9 +211,11 @@ static GameController* PlayerForDevice(int id) {
}
static GameController* PlayerForHandle(int handle) {
return handle >= PAD_HANDLE && handle < PAD_HANDLE + Config::MAX_PLAYERS
? g_players[handle - PAD_HANDLE].get()
: nullptr;
if (handle >= PAD_HANDLE && handle < PAD_HANDLE + Config::MAX_PLAYERS) {
return g_players[handle - PAD_HANDLE].get();
}
// The Sense controller ports belong to the initial user.
return PadVrHand(handle) >= 0 ? g_players[0].get() : nullptr;
}
static void pad_fill_data(PadData* data, const ControllerState& state, bool connected,
@@ -1204,10 +1202,18 @@ int PadCheckHandle(int handle) {
if (g_players[0] == nullptr) {
return PAD_ERROR_NOT_INITIALIZED;
}
if (PadVrHand(handle) >= 0) {
return PadVrCheckHandle(handle);
}
auto* player = PlayerForHandle(handle);
return player != nullptr ? player->CheckPortHandle(handle) : PAD_ERROR_INVALID_HANDLE;
}
// Handle 0 is never valid, so this only reports whether scePadInit has run.
bool PadInitialized() {
return g_players[0] != nullptr && g_players[0]->CheckPortHandle(0) != PAD_ERROR_NOT_INITIALIZED;
}
int KYTY_SYSV_ABI PadInit() {
PRINT_NAME();
for (const auto& player: g_players) {
@@ -1225,6 +1231,9 @@ int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
"\t param = 0x%016" PRIx64 "\n",
user_id, type, index, reinterpret_cast<uint64_t>(param));
if (const int hand = PadVrPortHand(type); hand >= 0) {
return PadVrPort(user_id, hand, index, true);
}
const int player = Config::GetPlayerFromUserId(user_id);
return player >= 0 ? g_players[player]->OpenPort(user_id, type, index) : PAD_ERROR_INVALID_ARG;
}
@@ -1237,6 +1246,9 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
"\t index = %d\n",
user_id, type, index);
if (const int hand = PadVrPortHand(type); hand >= 0) {
return PadVrPort(user_id, hand, index, false);
}
const int player = Config::GetPlayerFromUserId(user_id);
return player >= 0 ? g_players[player]->GetPortHandle(user_id, type, index)
: PAD_ERROR_NO_HANDLE;
@@ -1245,6 +1257,9 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
int KYTY_SYSV_ABI PadClose(int handle) {
PRINT_NAME();
LOGF("\t handle = %d\n", handle);
if (PadVrHand(handle) >= 0) {
return PadVrClose(handle);
}
auto* player = PlayerForHandle(handle);
if (player == nullptr) {
return PAD_ERROR_INVALID_HANDLE;
+32 -1
View File
@@ -18,6 +18,12 @@ struct Lifecycle {
constexpr int HOST_INPUT_CONTROLLER_ID = -1000;
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
constexpr int PAD_ERROR_ALREADY_OPENED = -2137915388; /* 0x80920004 */
constexpr int PAD_ERROR_NOT_INITIALIZED = -2137915387; /* 0x80920005 */
constexpr int PAD_ERROR_NO_HANDLE = -2137915384; /* 0x80920008 */
constexpr uint32_t PAD_BUTTON_L3 = 0x00000002;
constexpr uint32_t PAD_BUTTON_R3 = 0x00000004;
constexpr uint32_t PAD_BUTTON_OPTIONS = 0x00000008;
@@ -86,7 +92,32 @@ enum class Setting { SpeakerVolume, VibrationIntensity, TriggerEffectIntensity }
void CycleSetting(Setting setting);
float GetSettingScale(Setting setting);
int PadCheckHandle(int handle);
int PadCheckHandle(int handle);
bool PadInitialized();
// Virtual headset only (padVr.cpp): the Sense controller ports of the initial user. Their handles
// stay outside the per-player range and otherwise act on the first player.
constexpr int PAD_HANDLE_VR_LEFT = 0x10; // The right controller follows it.
// 0 for the left and 1 for the right Sense controller handle, -1 for any other handle.
constexpr int PadVrHand(int handle) {
return handle == PAD_HANDLE_VR_LEFT || handle == PAD_HANDLE_VR_LEFT + 1
? handle - PAD_HANDLE_VR_LEFT
: -1;
}
// Hand of a Sense controller port type, -1 for other types or without the virtual headset.
int PadVrPortHand(int type);
// scePadOpen (open) or scePadGetHandle for a Sense controller port.
int PadVrPort(int user_id, int hand, int index, bool open);
int PadVrCheckHandle(int handle);
int PadVrClose(int handle);
struct PadVrControllerData;
int KYTY_SYSV_ABI PadVrControllerReadState(int handle, PadVrControllerData* data);
int KYTY_SYSV_ABI PadVrControllerSetTriggerEffects(int left_handle, int right_handle,
const PadTriggerEffectParam* param);
int KYTY_SYSV_ABI PadInit();
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
+29
View File
@@ -12,6 +12,17 @@ inline constexpr uint64_t OUTPUT_MODE_89_91HZ = 0x2000c;
inline constexpr uint64_t OUTPUT_MODE_119_88HZ = 0x2000f;
inline constexpr int32_t ERROR_REPROJECTION_NOT_INITIALIZED = -1972240373; // 0x8a72000b
// Virtual ER15 field of view per eye: 55/42.5/51.2/51.2 degrees. "Out" is the temporal side and
// "in" the nasal side. These tangents describe that geometry, not bit-exact firmware output.
inline constexpr float FOV_TAN_OUT = 1.4281480312f;
inline constexpr float FOV_TAN_IN = 0.9163311720f;
inline constexpr float FOV_TAN_TOP = 1.2437491417f;
inline constexpr float FOV_TAN_BOTTOM = 1.2437491417f;
// Each eye sits half the lens separation to the side of the head.
inline constexpr uint8_t LENS_SEPARATION_MM = 63;
inline constexpr float EYE_OFFSET_M = LENS_SEPARATION_MM / 2000.0f;
struct ReprojectionState {
std::atomic<uint64_t> output_mode {0};
std::atomic<uint32_t> timing_us {3000};
@@ -19,6 +30,24 @@ struct ReprojectionState {
[[nodiscard]] ReprojectionState* GetReprojectionState();
struct SceFVector3 {
float x;
float y;
float z;
};
// The headset video-out port registers SceHmd2ReprojectionBuffer objects. The eye texture of their
// first layer is the surface the game flips; both eyes sit side by side in it.
struct ReprojectionSurface {
uint64_t address;
uint32_t width;
uint32_t height;
uint64_t pixel_format; // SceVideoOutPixelFormat
};
[[nodiscard]] bool GetReprojectionSurface(const void* reprojection_buffer,
ReprojectionSurface& out);
} // namespace Libs::Hmd2
#endif // EMULATOR_SRC_LIBS_HMD2_H_
+215 -4
View File
@@ -1,15 +1,20 @@
#include "common/assert.h"
#include "common/emulatorConfig.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h"
#include "graphics/shader/shaderBindings.h"
#include "kernel/pthread.h"
#include "libs/agc.h"
#include "libs/errno.h"
#include "libs/hmd2.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <new>
@@ -21,10 +26,19 @@ namespace Hmd2 {
constexpr int32_t ERROR_ALREADY_INITIALIZED = -1972240383; // 0x8a720001
constexpr int32_t ERROR_NOT_INITIALIZED = -1972240382; // 0x8a720002
constexpr int32_t ERROR_HANDLE_INVALID = -1972240381; // 0x8a720003
constexpr int32_t ERROR_PARAMETER_NULL = -1972240376; // 0x8a720008
constexpr int32_t ERROR_PARAMETER_INVALID = -1972240375; // 0x8a720009
constexpr int32_t ERROR_REPROJECTION_ALREADY_INITIALIZED = -1972240372; // 0x8a72000c
constexpr int32_t ERROR_REPROJECTION_IN_VR_MODE = -1972240356; // 0x8a72001c
// Any non-negative value is a valid headset handle.
constexpr int32_t HMD_HANDLE = 0x0f000001;
constexpr uint32_t REPROJECTION_LAYER_TYPE_VR3D = 0x10;
constexpr uint32_t REPROJECTION_LAYER_TYPE_VR2D = 0x12;
constexpr uint32_t REPROJECTION_MAX_SOURCE_COUNT = 5;
constexpr int32_t GAZE_TRACKING_STATUS_DISABLE = -1;
struct SceHmd2InitializeParam {
void* reserved0;
uint8_t reserved[8];
@@ -69,6 +83,58 @@ struct SceHmd2ReprojectionInitializeParam {
uint32_t reserved[5];
};
struct SceHmd2ReprojectionSource {
uint32_t layerType;
uint32_t data[73]; // VR3D: srcL texture, srcR texture, ...; VR2D: src texture, ...
const void* option;
};
struct SceHmd2ReprojectionBuffer {
const SceHmd2ReprojectionSource* src;
uint32_t srcCount;
};
struct SceHmd2ReprojectionRenderConfig {
uint32_t syncMode;
uint32_t phase;
uint32_t reserved[2];
};
struct SceHmd2ReprojectionParam {
const void* settings;
int32_t settingCount;
const void* option;
};
struct SceHmd2GazeGetResultParam {
uint32_t sizeOfThis;
int32_t handle;
int64_t flipArg;
};
struct SceHmd2GazeEyeResult {
int32_t isPupilDiameterValid;
float pupilDiameter;
int32_t isBlink;
};
struct SceHmd2GazeResult {
uint32_t sizeOfThis;
int32_t status;
uint64_t timestamp;
int32_t isGazeValid;
SceFVector3 gazeDirection;
SceHmd2GazeEyeResult left;
SceHmd2GazeEyeResult right;
};
struct SceHmd2GazeResultForFoveatedRendering {
uint32_t sizeOfThis;
int32_t status;
SceFVector3 gazeDirection;
uint32_t reserved;
};
static_assert(sizeof(SceHmd2InitializeParam) == 16);
static_assert(offsetof(SceHmd2InitializeParam, reserved) == 8);
static_assert(sizeof(SceHmd2DeviceInformation) == 28);
@@ -84,6 +150,19 @@ static_assert(offsetof(SceHmd2ReprojectionInitializeParam, type) == 32);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, pSeeThroughBuff) == 40);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, reprojectionTiming) == 48);
static_assert(offsetof(SceHmd2ReprojectionInitializeParam, reserved) == 52);
static_assert(sizeof(SceHmd2ReprojectionSource) == 304);
static_assert(offsetof(SceHmd2ReprojectionSource, option) == 296);
static_assert(sizeof(SceHmd2ReprojectionBuffer) == 16);
static_assert(sizeof(SceHmd2ReprojectionRenderConfig) == 16);
static_assert(sizeof(SceHmd2ReprojectionParam) == 24);
static_assert(offsetof(SceHmd2ReprojectionParam, option) == 16);
static_assert(sizeof(SceHmd2GazeGetResultParam) == 16);
static_assert(offsetof(SceHmd2GazeGetResultParam, flipArg) == 8);
static_assert(sizeof(SceHmd2GazeResult) == 56);
static_assert(offsetof(SceHmd2GazeResult, gazeDirection) == 20);
static_assert(offsetof(SceHmd2GazeResult, right) == 44);
static_assert(sizeof(SceHmd2GazeResultForFoveatedRendering) == 24);
static_assert(offsetof(SceHmd2GazeResultForFoveatedRendering, gazeDirection) == 8);
static std::atomic<bool> g_initialized = false;
static std::mutex g_reprojection_mutex;
@@ -127,7 +206,7 @@ static int32_t KYTY_SYSV_ABI Hmd2GetDeviceInformation(SceHmd2DeviceInformation*
if (Config::VrEnabled()) {
// Virtual headset defaults: READY, MOUNT, 63 mm lens separation.
info->hmuMount = 1;
info->lensSeparationDistance = 63;
info->lensSeparationDistance = LENS_SEPARATION_MM;
// Headset characteristics: 4000x2040 panel and 2 m virtual image distance.
info->deviceInfo.panelResolution = {PANEL_WIDTH, PANEL_HEIGHT};
info->virtualImageDistance = 2.0f;
@@ -149,9 +228,7 @@ static int32_t KYTY_SYSV_ABI Hmd2GetFieldOfViewWithoutHandle(SceHmd2FieldOfView*
if (fov == nullptr) {
return ERROR_PARAMETER_NULL;
}
// Virtual ER15 field of view: 55/42.5/51.2/51.2 degrees.
// These tangents describe that geometry, not bit-exact firmware output.
*fov = {1.4281480312f, 0.9163311720f, 1.2437491417f, 1.2437491417f};
*fov = {FOV_TAN_OUT, FOV_TAN_IN, FOV_TAN_TOP, FOV_TAN_BOTTOM};
LOGF("Hmd2: FOV tangents out=%f in=%f top=%f bottom=%f\n", fov->tanOut, fov->tanIn,
fov->tanTop, fov->tanBottom);
return OK;
@@ -212,6 +289,133 @@ static int32_t KYTY_SYSV_ABI Hmd2ReprojectionEnableVrMode(uint64_t output_mode)
return OK;
}
static int32_t KYTY_SYSV_ABI Hmd2Open(int32_t user_id, int32_t type, int32_t index,
const void* param) {
PRINT_NAME();
if (!g_initialized.load()) {
return ERROR_NOT_INITIALIZED;
}
if (user_id != Config::GetUserId() || type != 0 || index != 0 || param != nullptr) {
return ERROR_PARAMETER_INVALID;
}
return HMD_HANDLE;
}
static int32_t KYTY_SYSV_ABI Hmd2ReprojectionBeginFrame(int64_t flip_arg) {
PRINT_NAME();
LOGF("\t flip_arg = %" PRId64 "\n", flip_arg);
return OK;
}
// Microseconds of process time, the clock VrTracker2 predicts poses for. The headset shows a frame
// one refresh after it begins.
static int32_t KYTY_SYSV_ABI Hmd2ReprojectionGetPredictedDisplayTime(int64_t flip_arg,
uint64_t* timestamp) {
PRINT_NAME();
if (timestamp == nullptr) {
return ERROR_PARAMETER_NULL;
}
const auto* state = GetReprojectionState();
const bool fast = state != nullptr && state->output_mode.load() == OUTPUT_MODE_119_88HZ;
*timestamp = LibKernel::KernelGetProcessTime() + (fast ? 8342 : 11122);
LOGF("\t flip_arg = %" PRId64 ", time = %" PRIu64 "\n", flip_arg, *timestamp);
return OK;
}
static int32_t KYTY_SYSV_ABI Hmd2ReprojectionSetRenderConfig(SceHmd2ReprojectionRenderConfig config,
const void* option) {
PRINT_NAME();
LOGF("\t sync_mode = %u, phase = %u\n", config.syncMode, config.phase);
if (GetReprojectionState() == nullptr) {
return ERROR_REPROJECTION_NOT_INITIALIZED;
}
return option != nullptr ? ERROR_PARAMETER_INVALID : OK;
}
static int32_t KYTY_SYSV_ABI Hmd2ReprojectionSetParam(int32_t index, int64_t flip_arg,
const SceHmd2ReprojectionParam* param,
const void* option) {
PRINT_NAME();
if (GetReprojectionState() == nullptr) {
return ERROR_REPROJECTION_NOT_INITIALIZED;
}
if (param == nullptr || param->settings == nullptr) {
return ERROR_PARAMETER_NULL;
}
LOGF("\t index = %d, flip_arg = %" PRId64 ", layers = %d\n", index, flip_arg,
param->settingCount);
return option != nullptr ? ERROR_PARAMETER_INVALID : OK;
}
// No eye tracker: report DISABLE, which a PS5 also returns when the user turned eye tracking off.
template <typename Result>
static int32_t GazeResult(const SceHmd2GazeGetResultParam* param, Result* result) {
if (param == nullptr || result == nullptr) {
return ERROR_PARAMETER_NULL;
}
if (param->sizeOfThis != sizeof(*param) || result->sizeOfThis != sizeof(*result)) {
return ERROR_PARAMETER_INVALID;
}
if (param->handle != HMD_HANDLE) {
return ERROR_HANDLE_INVALID;
}
*result = {};
result->sizeOfThis = sizeof(*result);
result->status = GAZE_TRACKING_STATUS_DISABLE;
result->gazeDirection = {0.0f, 0.0f,
-1.0f}; // The display center; +z points away from the lens.
return OK;
}
static int32_t KYTY_SYSV_ABI Hmd2GazeGetResult(const SceHmd2GazeGetResultParam* param,
SceHmd2GazeResult* result) {
PRINT_NAME();
const auto error = GazeResult(param, result);
if (error == OK) {
result->timestamp = LibKernel::KernelGetProcessTime();
}
return error;
}
static int32_t KYTY_SYSV_ABI Hmd2GazeGetResultForFoveatedRendering(
const SceHmd2GazeGetResultParam* param, SceHmd2GazeResultForFoveatedRendering* result) {
PRINT_NAME();
return GazeResult(param, result);
}
bool GetReprojectionSurface(const void* reprojection_buffer, ReprojectionSurface& out) {
const auto* buffer = static_cast<const SceHmd2ReprojectionBuffer*>(reprojection_buffer);
if (buffer == nullptr || buffer->src == nullptr || buffer->srcCount == 0 ||
buffer->srcCount > REPROJECTION_MAX_SOURCE_COUNT ||
(buffer->src->layerType != REPROJECTION_LAYER_TYPE_VR3D &&
buffer->src->layerType != REPROJECTION_LAYER_TYPE_VR2D)) {
return false;
}
Graphics::ShaderTextureResource texture;
std::memcpy(texture.fields, buffer->src->data, sizeof(texture.fields));
// Eye textures are RGBA ordered render targets; flip them through the matching SDR format.
const auto& policies = Graphics::VIDEO_OUT_FORMAT_POLICIES;
const auto policy = std::find_if(policies.begin(), policies.end(), [&](const auto& p) {
return p.info.guest_format == texture.Format() &&
(p.pixel_format & 0xff000000u) == 0x22000000u &&
!Graphics::IsHdrVideoOutPixelFormat(p.pixel_format);
});
if (texture.IsNull() || texture.TileMode() != Graphics::Prospero::TileMode::kRenderTarget ||
policy == policies.end()) {
LOGF("Hmd2: unsupported eye texture format=%u tile=%u\n",
static_cast<uint32_t>(texture.Format()), static_cast<uint32_t>(texture.TileMode()));
return false;
}
if (buffer->src->layerType == REPROJECTION_LAYER_TYPE_VR3D &&
std::memcmp(buffer->src->data, buffer->src->data + std::size(texture.fields),
sizeof(texture.fields)) != 0) {
Log::WriteToConsoleAndLog(
"Hmd2: separate eye textures are not supported; showing the left one\n");
}
out = {texture.Base40(), texture.Width5() + 1u, texture.Height5() + 1u, policy->pixel_format};
return true;
}
static int32_t KYTY_SYSV_ABI Hmd2ReprojectionSetTiming(int32_t timing) {
auto* state = GetReprojectionState();
if (state == nullptr) {
@@ -235,6 +439,13 @@ LIB_DEFINE(InitHmd2_1) {
LIB_FUNC("C0rPwER-yxg", Hmd2::Hmd2ReprojectionInitialize);
LIB_FUNC("VVvFh51o20s", Hmd2::Hmd2ReprojectionEnableVrMode);
LIB_FUNC("FkQX7rjFomk", Hmd2::Hmd2ReprojectionSetTiming);
LIB_FUNC("f3kPeoTZnIE", Hmd2::Hmd2Open);
LIB_FUNC("Ocf081WpBpA", Hmd2::Hmd2ReprojectionBeginFrame);
LIB_FUNC("SVEG+1D7qHA", Hmd2::Hmd2ReprojectionGetPredictedDisplayTime);
LIB_FUNC("hA9LshbSkzw", Hmd2::Hmd2ReprojectionSetRenderConfig);
LIB_FUNC("xMo9ENEu2E0", Hmd2::Hmd2ReprojectionSetParam);
LIB_FUNC("lAoFUedcfqA", Hmd2::Hmd2GazeGetResult);
LIB_FUNC("retc+-uRMhk", Hmd2::Hmd2GazeGetResultForFoveatedRendering);
}
} // namespace Libs
+2
View File
@@ -189,6 +189,8 @@ LIB_DEFINE(InitPad_1) {
LIB_FUNC("RR4novUEENY", Controller::PadSetLightBar);
LIB_FUNC("6ncge5+l5Qs", Controller::PadClose);
LIB_FUNC("n3kSX62fgNo", PadUnknownN3kSX62fgNo);
LIB_FUNC("iA-DdobUen8", Controller::PadVrControllerReadState);
LIB_FUNC("v8P+9PRqg10", Controller::PadVrControllerSetTriggerEffects);
}
namespace LibMouse {
+228
View File
@@ -0,0 +1,228 @@
#include "common/abi.h"
#include "common/logging/log.h"
#include "graphics/presentation/vrInput.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "libs/hmd2.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstring>
namespace Libs {
LIB_VERSION("VrTracker2", 1, "VrTracker2", 1, 1);
namespace VrTracker2 {
constexpr int32_t ERROR_NOT_INIT = -1972109311; // 0x8a740001
constexpr int32_t ERROR_ALREADY_INITIALIZED = -1972109310; // 0x8a740002
constexpr int32_t ERROR_ARGUMENT_INVALID = -1972109306; // 0x8a740006
constexpr uint32_t MEMORY_SIZE = 32u << 20u;
constexpr uint32_t MEMORY_ALIGNMENT = 0x10000;
// Device types: the headset, then the right and the left controller.
constexpr int32_t DEVICE_TYPE_HMD = 0;
constexpr int32_t DEVICE_TYPE_VR_CONTROLLER_LEFT = 2;
constexpr uint32_t STATUS_TRACKING = 1;
constexpr uint32_t QUALITY_FULL = 9;
constexpr uint8_t MOTION_SENSOR_DATA_VALID = 3; // Velocity and acceleration.
struct SceFQuaternion {
float x;
float y;
float z;
float w;
};
struct SceVrTracker2QueryMemoryResult {
uint32_t sizeOfThis;
uint32_t memorySize;
uint32_t memoryAlignment;
uint32_t reserved[5];
};
struct SceVrTracker2InitializeParam {
uint32_t sizeOfThis;
uint32_t profile;
int32_t highThreadPriority;
int32_t lowThreadPriority;
uint64_t highThreadAffinity;
uint64_t lowThreadAffinity;
void* memoryPointer;
uint8_t options[32]; // Memory size and tracking options; nothing here depends on them.
};
struct SceVrTracker2GetResultParam {
uint32_t sizeOfThis;
int32_t handle;
uint32_t reserved00;
uint64_t predictionTime;
uint32_t reserved01;
int64_t flipArg;
uint32_t reserved02[2];
};
struct SceVrTracker2PoseData {
Hmd2::SceFVector3 position;
uint32_t reserved00;
SceFQuaternion orientation;
uint32_t reserved01[8];
};
struct SceVrTracker2ResultData {
int32_t handle;
uint32_t connected;
uint32_t playAreaUpdateCounter;
uint32_t playAreaMode;
uint64_t timestamp;
uint32_t status;
uint32_t positionQuality;
uint32_t orientationQuality;
Hmd2::SceFVector3 velocity;
Hmd2::SceFVector3 acceleration;
Hmd2::SceFVector3 angularVelocity;
uint32_t reserved01[3];
uint8_t motionSensorDataValidFlags;
uint8_t reserved010[3];
int64_t flipArg;
union {
struct {
SceVrTracker2PoseData devicePose;
SceVrTracker2PoseData leftEyePose;
SceVrTracker2PoseData rightEyePose;
SceVrTracker2PoseData headPose;
SceVrTracker2PoseData gazeOriginPose;
} hmdInfo;
struct {
SceVrTracker2PoseData devicePose;
uint64_t lastValidVisionTimestamp;
uint64_t lastValidSensorTimestamp;
uint32_t reserved[60];
} vrControllerInfo;
};
uint32_t reserved02;
};
static_assert(sizeof(SceVrTracker2QueryMemoryResult) == 0x20);
static_assert(sizeof(SceVrTracker2InitializeParam) == 0x48);
static_assert(offsetof(SceVrTracker2InitializeParam, memoryPointer) == 0x20);
static_assert(sizeof(SceVrTracker2GetResultParam) == 0x30);
static_assert(offsetof(SceVrTracker2GetResultParam, flipArg) == 0x20);
static_assert(sizeof(SceVrTracker2PoseData) == 0x40);
static_assert(sizeof(SceVrTracker2ResultData) == 0x1a8);
static_assert(offsetof(SceVrTracker2ResultData, status) == 0x18);
static_assert(offsetof(SceVrTracker2ResultData, motionSensorDataValidFlags) == 0x54);
static_assert(offsetof(SceVrTracker2ResultData, flipArg) == 0x58);
static_assert(offsetof(SceVrTracker2ResultData, hmdInfo.devicePose) == 0x60);
static_assert(offsetof(SceVrTracker2ResultData, hmdInfo.gazeOriginPose) == 0x160);
static_assert(offsetof(SceVrTracker2ResultData, vrControllerInfo.lastValidVisionTimestamp) == 0xa0);
static std::atomic<bool> g_initialized = false;
// Handle registered for each device type.
static std::atomic<int32_t> g_devices[DEVICE_TYPE_VR_CONTROLLER_LEFT + 1] = {-1, -1, -1};
// Writes a tracking-space pose moved along its own x axis by shift metres. w stays non-negative so
// that a rotation always has one representation, whatever sign the runtime reports.
static void SetPose(SceVrTracker2PoseData& out, const Vr::VrPose& pose, float shift = 0.0f) {
const auto [x, y, z, w] = pose.orientation;
const float s = w < 0.0f ? -1.0f : 1.0f;
out.position = {pose.position[0] + shift * (1.0f - 2.0f * (y * y + z * z)),
pose.position[1] + shift * 2.0f * (x * y + w * z),
pose.position[2] + shift * 2.0f * (x * z - w * y)};
out.orientation = {s * x, s * y, s * z, s * w};
}
static int32_t KYTY_SYSV_ABI QueryMemory(const void* param,
SceVrTracker2QueryMemoryResult* result) {
PRINT_NAME();
if (param == nullptr || result == nullptr) {
return ERROR_ARGUMENT_INVALID;
}
result->memorySize = MEMORY_SIZE;
result->memoryAlignment = MEMORY_ALIGNMENT;
return OK;
}
static int32_t KYTY_SYSV_ABI Initialize(const SceVrTracker2InitializeParam* param) {
PRINT_NAME();
if (param == nullptr || param->memoryPointer == nullptr) {
return ERROR_ARGUMENT_INVALID;
}
return g_initialized.exchange(true) ? ERROR_ALREADY_INITIALIZED : OK;
}
static int32_t KYTY_SYSV_ABI RegisterDevice(int32_t device_type, int32_t handle) {
PRINT_NAME();
LOGF("\t device_type = %d\n"
"\t handle = %d\n",
device_type, handle);
if (!g_initialized.load()) {
return ERROR_NOT_INIT;
}
if (device_type < DEVICE_TYPE_HMD || device_type > DEVICE_TYPE_VR_CONTROLLER_LEFT ||
handle < 0) {
return ERROR_ARGUMENT_INVALID;
}
g_devices[device_type].store(handle);
return OK;
}
// Without a headset runtime every device reports the identity pose at the tracking origin.
static int32_t KYTY_SYSV_ABI GetResult(const SceVrTracker2GetResultParam* param,
SceVrTracker2ResultData* result) {
PRINT_NAME();
if (param == nullptr || result == nullptr) {
return ERROR_ARGUMENT_INVALID;
}
if (!g_initialized.load()) {
return ERROR_NOT_INIT;
}
std::memset(result, 0, sizeof(*result));
result->handle = param->handle;
result->flipArg = param->flipArg;
result->timestamp =
param->predictionTime != 0 ? param->predictionTime : LibKernel::KernelGetProcessTime();
int32_t type = DEVICE_TYPE_HMD;
while (type <= DEVICE_TYPE_VR_CONTROLLER_LEFT && g_devices[type].load() != param->handle) {
type++;
}
if (param->handle < 0 || type > DEVICE_TYPE_VR_CONTROLLER_LEFT) {
return OK; // Not registered: not connected, tracking not started.
}
result->connected = 1;
result->status = STATUS_TRACKING;
result->positionQuality = QUALITY_FULL;
result->orientationQuality = QUALITY_FULL;
result->motionSensorDataValidFlags = MOTION_SENSOR_DATA_VALID;
if (type == DEVICE_TYPE_HMD) {
const auto pose = Vr::OpenXrGetHeadPose();
auto& hmd = result->hmdInfo;
SetPose(hmd.devicePose, pose);
SetPose(hmd.leftEyePose, pose, -Hmd2::EYE_OFFSET_M);
SetPose(hmd.rightEyePose, pose, Hmd2::EYE_OFFSET_M);
hmd.headPose = hmd.devicePose;
hmd.gazeOriginPose = hmd.devicePose;
return OK;
}
auto& controller = result->vrControllerInfo;
SetPose(controller.devicePose,
Vr::OpenXrGetController(type == DEVICE_TYPE_VR_CONTROLLER_LEFT ? 0 : 1).pose);
controller.lastValidVisionTimestamp = result->timestamp;
controller.lastValidSensorTimestamp = result->timestamp;
return OK;
}
} // namespace VrTracker2
LIB_DEFINE(InitVrTracker2_1) {
LIB_FUNC("TwqZnaIjWv4", VrTracker2::QueryMemory);
LIB_FUNC("6Jy73SRfG-o", VrTracker2::Initialize);
LIB_FUNC("Dog+g25QYjw", VrTracker2::RegisterDevice);
LIB_FUNC("J4Vh3VVX0iU", VrTracker2::GetResult);
}
} // namespace Libs
+2
View File
@@ -92,6 +92,7 @@ LIB_DEFINE(InitFont_1);
LIB_DEFINE(InitFontFt_1);
LIB_DEFINE(InitAgcDriver_1);
LIB_DEFINE(InitHmd2_1);
LIB_DEFINE(InitVrTracker2_1);
LIB_DEFINE(InitLibKernel_1);
LIB_DEFINE(InitNet_1);
LIB_DEFINE(InitPad_1);
@@ -131,6 +132,7 @@ void InitAll(Loader::SymbolDatabase* s) {
InitDeviceService_1(s);
InitUsbd_1(s);
InitHmd2_1(s);
InitVrTracker2_1(s);
InitLibKernel_1(s);
LibMouse::InitMouse_1(s);
LibKeyboard::InitKeyboard_1(s);
+158
View File
@@ -0,0 +1,158 @@
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "graphics/presentation/vrInput.h"
#include "kernel/pthread.h"
#include "libs/controller.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "libs/padData.h"
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstring>
// Virtual headset only: the PS VR2 Sense controller ports. A headset runtime drives them when one
// is running; otherwise each hand reads its half of the first player's keyboard or gamepad.
namespace Libs::Controller {
LIB_NAME("Pad", "Pad");
constexpr int PAD_PORT_TYPE_VR_CONTROLLER_LEFT = 3;
// The buttons of each Sense controller. The left Create button is reserved for the system.
constexpr uint32_t HAND_BUTTONS[2] = {
PAD_BUTTON_L1 | PAD_BUTTON_L2 | PAD_BUTTON_L3 | PAD_BUTTON_TRIANGLE | PAD_BUTTON_SQUARE,
PAD_BUTTON_R1 | PAD_BUTTON_R2 | PAD_BUTTON_R3 | PAD_BUTTON_CIRCLE | PAD_BUTTON_CROSS |
PAD_BUTTON_OPTIONS};
struct PadVrControllerData {
uint32_t buttons;
uint8_t stick_x;
uint8_t stick_y;
uint8_t stick_padding[2];
uint8_t analog_l2;
uint8_t analog_r2;
uint8_t analog_padding[2];
uint32_t finger_touch;
uint8_t analog_finger_touch[4];
float acceleration[3];
float angular_velocity[3];
float orientation[4];
uint8_t reserve1[4];
bool connected;
uint8_t connected_count;
uint64_t timestamp;
uint8_t reserve2[24];
};
static_assert(sizeof(PadVrControllerData) == 104);
static_assert(offsetof(PadVrControllerData, finger_touch) == 12);
static_assert(offsetof(PadVrControllerData, acceleration) == 20);
static_assert(offsetof(PadVrControllerData, orientation) == 44);
static_assert(offsetof(PadVrControllerData, connected) == 64);
static_assert(offsetof(PadVrControllerData, timestamp) == 72);
static std::atomic<bool> g_open[2];
int PadVrPortHand(int type) {
const bool vr =
type == PAD_PORT_TYPE_VR_CONTROLLER_LEFT || type == PAD_PORT_TYPE_VR_CONTROLLER_LEFT + 1;
return vr && Config::VrEnabled() ? type - PAD_PORT_TYPE_VR_CONTROLLER_LEFT : -1;
}
int PadVrPort(int user_id, int hand, int index, bool open) {
if (!PadInitialized()) {
return PAD_ERROR_NOT_INITIALIZED;
}
const int handle = PAD_HANDLE_VR_LEFT + hand;
if (user_id != Config::GetUserId() || index != 0) {
return open ? PAD_ERROR_INVALID_ARG : PAD_ERROR_NO_HANDLE;
}
if (!open) {
return g_open[hand].load() ? handle : PAD_ERROR_NO_HANDLE;
}
return g_open[hand].exchange(true) ? PAD_ERROR_ALREADY_OPENED : handle;
}
int PadVrCheckHandle(int handle) {
const int hand = PadVrHand(handle);
return hand >= 0 && g_open[hand].load() ? OK : PAD_ERROR_INVALID_HANDLE;
}
int PadVrClose(int handle) {
const int hand = PadVrHand(handle);
return hand >= 0 && g_open[hand].exchange(false) ? OK : PAD_ERROR_INVALID_HANDLE;
}
static uint8_t ToByte(float value) {
return static_cast<uint8_t>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
}
int KYTY_SYSV_ABI PadVrControllerReadState(int handle, PadVrControllerData* data) {
PRINT_NAME();
const int hand = PadVrHand(handle);
if (hand < 0) {
return PAD_ERROR_INVALID_HANDLE;
}
if (data == nullptr) {
return PAD_ERROR_INVALID_ARG;
}
PadData pad {};
if (const auto error = PadReadState(handle, &pad); error != OK) {
return error;
}
*data = {};
auto& trigger = hand == 0 ? data->analog_l2 : data->analog_r2;
data->buttons = pad.buttons & HAND_BUTTONS[hand];
data->stick_x = hand == 0 ? pad.left_stick_x : pad.right_stick_x;
data->stick_y = hand == 0 ? pad.left_stick_y : pad.right_stick_y;
trigger = hand == 0 ? pad.analog_buttons_l2 : pad.analog_buttons_r2;
std::memcpy(data->acceleration, &pad.acceleration_x, sizeof(data->acceleration));
std::memcpy(data->angular_velocity, &pad.angular_velocity_x, sizeof(data->angular_velocity));
std::memcpy(data->orientation, &pad.orientation_x, sizeof(data->orientation));
data->connected = pad.connected;
data->connected_count = pad.connected_count;
data->timestamp = pad.timestamp;
if (const auto xr = Vr::OpenXrGetController(hand); xr.connected) {
// Touch controllers have no button left for the right Options, so the left Menu stands in.
const bool options = hand == 1 && Vr::OpenXrGetController(0).menu;
// Each mapping names both hands; HAND_BUTTONS keeps this hand's half.
const uint32_t buttons = (xr.trigger > 0.5f ? PAD_BUTTON_L2 | PAD_BUTTON_R2 : 0u) |
(xr.squeeze > 0.5f ? PAD_BUTTON_L1 | PAD_BUTTON_R1 : 0u) |
(xr.stick_click ? PAD_BUTTON_L3 | PAD_BUTTON_R3 : 0u) |
(xr.primary ? PAD_BUTTON_SQUARE | PAD_BUTTON_CROSS : 0u) |
(xr.secondary ? PAD_BUTTON_TRIANGLE | PAD_BUTTON_CIRCLE : 0u) |
(options ? PAD_BUTTON_OPTIONS : 0u);
data->buttons = buttons & HAND_BUTTONS[hand];
data->stick_x = ToByte(xr.stick_x * 0.5f + 0.5f);
data->stick_y = ToByte(0.5f - xr.stick_y * 0.5f);
trigger = ToByte(xr.trigger);
if (xr.pose.valid) {
std::memcpy(data->orientation, xr.pose.orientation, sizeof(data->orientation));
}
data->connected = true;
data->connected_count = std::max<uint8_t>(data->connected_count, 1);
data->timestamp = LibKernel::KernelGetProcessTime();
}
return OK;
}
int KYTY_SYSV_ABI PadVrControllerSetTriggerEffects(int left_handle, int right_handle,
const PadTriggerEffectParam* param) {
PRINT_NAME();
LOGF("\t left_handle = %d\n"
"\t right_handle = %d\n",
left_handle, right_handle);
if (PadVrHand(left_handle) != 0 || PadVrCheckHandle(left_handle) != OK ||
PadVrHand(right_handle) != 1 || PadVrCheckHandle(right_handle) != OK) {
return PAD_ERROR_INVALID_HANDLE;
}
return param != nullptr ? OK : PAD_ERROR_INVALID_ARG;
}
} // namespace Libs::Controller