Files
ppsspp/Common/VR/PPSSPPVR.cpp
Henrik Rydgård 80a99a67d9 Control: Change internal interfaces to batch-process input axis updates
These naturally come in bunches on many platforms like Android, so lay
some groundwork to also handle them in bunches to minimize locking in
the future.

Linux buildfix
2023-08-31 11:55:53 +02:00

964 lines
30 KiB
C++

#include "Common/VR/PPSSPPVR.h"
#include "Common/VR/VRBase.h"
#if XR_USE_GRAPHICS_API_OPENGL || XR_USE_GRAPHICS_API_OPENGL_ES
#include "Common/GPU/OpenGL/GLRenderManager.h"
#endif
#include "Common/VR/VRInput.h"
#include "Common/VR/VRMath.h"
#include "Common/VR/VRRenderer.h"
#include "Common/Input/InputState.h"
#include "Common/Input/KeyCodes.h"
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/Math/lin/matrix4x4.h"
#include "Common/Input/InputState.h"
#include "Common/Input/KeyCodes.h"
#include "Core/HLE/sceDisplay.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/Config.h"
#include "Core/KeyMap.h"
#include "Core/System.h"
enum VRMatrix {
VR_PROJECTION_MATRIX,
VR_VIEW_MATRIX_LEFT_EYE,
VR_VIEW_MATRIX_RIGHT_EYE,
VR_MATRIX_COUNT
};
enum VRMirroring {
VR_MIRRORING_AXIS_X,
VR_MIRRORING_AXIS_Y,
VR_MIRRORING_AXIS_Z,
VR_MIRRORING_PITCH,
VR_MIRRORING_YAW,
VR_MIRRORING_ROLL,
VR_MIRRORING_COUNT
};
static VRAppMode appMode = VR_MENU_MODE;
static std::map<int, std::map<int, float> > pspAxis;
static std::map<int, bool> pspKeys; // key can be virtual, so not using the enum.
static int vr3DGeometryCount = 0;
static long vrCompat[VR_COMPAT_MAX];
static bool vrFlatForced = false;
static bool vrFlatGame = false;
static float vrMatrix[VR_MATRIX_COUNT][16];
static bool vrMirroring[VR_MIRRORING_COUNT];
static int vrMirroringVariant = 0;
static XrView vrView[2];
static void (*cbNativeAxis)(const AxisInput *axis, size_t count);
static bool (*cbNativeKey)(const KeyInput &key);
static void (*cbNativeTouch)(const TouchInput &touch);
/*
================================================================================
VR button mapping
================================================================================
*/
struct ButtonMapping {
ovrButton ovr;
InputKeyCode keycode;
bool pressed;
int repeat;
ButtonMapping(InputKeyCode keycode, ovrButton ovr) {
this->keycode = keycode;
this->ovr = ovr;
pressed = false;
repeat = 0;
}
};
static std::vector<ButtonMapping> leftControllerMapping = {
ButtonMapping(NKCODE_BUTTON_X, ovrButton_X),
ButtonMapping(NKCODE_BUTTON_Y, ovrButton_Y),
ButtonMapping(NKCODE_ALT_LEFT, ovrButton_GripTrigger),
ButtonMapping(NKCODE_DPAD_UP, ovrButton_Up),
ButtonMapping(NKCODE_DPAD_DOWN, ovrButton_Down),
ButtonMapping(NKCODE_DPAD_LEFT, ovrButton_Left),
ButtonMapping(NKCODE_DPAD_RIGHT, ovrButton_Right),
ButtonMapping(NKCODE_BUTTON_THUMBL, ovrButton_LThumb),
ButtonMapping(NKCODE_ENTER, ovrButton_Trigger),
ButtonMapping(NKCODE_BACK, ovrButton_Enter),
};
static std::vector<ButtonMapping> rightControllerMapping = {
ButtonMapping(NKCODE_BUTTON_A, ovrButton_A),
ButtonMapping(NKCODE_BUTTON_B, ovrButton_B),
ButtonMapping(NKCODE_ALT_RIGHT, ovrButton_GripTrigger),
ButtonMapping(NKCODE_DPAD_UP, ovrButton_Up),
ButtonMapping(NKCODE_DPAD_DOWN, ovrButton_Down),
ButtonMapping(NKCODE_DPAD_LEFT, ovrButton_Left),
ButtonMapping(NKCODE_DPAD_RIGHT, ovrButton_Right),
ButtonMapping(NKCODE_BUTTON_THUMBR, ovrButton_RThumb),
ButtonMapping(NKCODE_ENTER, ovrButton_Trigger),
};
static const InputDeviceID controllerIds[] = {DEVICE_ID_XR_CONTROLLER_LEFT, DEVICE_ID_XR_CONTROLLER_RIGHT};
static std::vector<ButtonMapping> controllerMapping[2] = {
leftControllerMapping,
rightControllerMapping
};
static bool controllerMotion[2][5] = {};
static bool hmdMotion[4] = {};
static float hmdMotionLast[2] = {};
static float hmdMotionDiff[2] = {};
static float hmdMotionDiffLast[2] = {};
static int mouseController = 1;
static bool mousePressed = false;
inline float clampFloat(float x, float minValue, float maxValue) {
if (x < minValue) return minValue;
if (x > maxValue) return maxValue;
return x;
}
/*
================================================================================
VR app flow integration
================================================================================
*/
bool IsVREnabled() {
// For now, let the OPENXR build flag control enablement.
// This will change.
#ifdef OPENXR
return true;
#else
return false;
#endif
}
#if PPSSPP_PLATFORM(ANDROID)
void InitVROnAndroid(void* vm, void* activity, const char* system, int version, const char* name) {
//Get device vendor (uppercase)
char vendor[64];
sscanf(system, "%[^:]", vendor);
for (unsigned int i = 0; i < strlen(vendor); i++) {
if ((vendor[i] >= 'a') && (vendor[i] <= 'z')) {
vendor[i] = vendor[i] - 'a' + 'A';
}
}
//Set platform flags
if (strcmp(vendor, "PICO") == 0) {
VR_SetPlatformFLag(VR_PLATFORM_CONTROLLER_PICO, true);
VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_INSTANCE, true);
} else if ((strcmp(vendor, "META") == 0) || (strcmp(vendor, "OCULUS") == 0)) {
VR_SetPlatformFLag(VR_PLATFORM_CONTROLLER_QUEST, true);
VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_FOVEATION, true);
VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_PASSTHROUGH, true);
VR_SetPlatformFLag(VR_PLATFORM_EXTENSION_PERFORMANCE, true);
}
VR_SetPlatformFLag(VR_PLATFORM_RENDERER_VULKAN, (GPUBackend)g_Config.iGPUBackend == GPUBackend::VULKAN);
//Init VR
ovrJava java;
java.Vm = (JavaVM*)vm;
java.ActivityObject = (jobject)activity;
VR_Init(&java, name, version);
}
#endif
void EnterVR(bool firstStart, void* vulkanContext) {
if (firstStart) {
engine_t* engine = VR_GetEngine();
bool useVulkan = (GPUBackend)g_Config.iGPUBackend == GPUBackend::VULKAN;
if (useVulkan) {
auto* context = (VulkanContext*)vulkanContext;
engine->graphicsBindingVulkan = {};
engine->graphicsBindingVulkan.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR;
engine->graphicsBindingVulkan.next = NULL;
engine->graphicsBindingVulkan.device = context->GetDevice();
engine->graphicsBindingVulkan.instance = context->GetInstance();
engine->graphicsBindingVulkan.physicalDevice = context->GetCurrentPhysicalDevice();
engine->graphicsBindingVulkan.queueFamilyIndex = context->GetGraphicsQueueFamilyIndex();
engine->graphicsBindingVulkan.queueIndex = 0;
VR_EnterVR(engine, &engine->graphicsBindingVulkan);
} else {
VR_EnterVR(engine, nullptr);
}
IN_VRInit(engine);
}
VR_SetConfig(VR_CONFIG_VIEWPORT_VALID, false);
}
void GetVRResolutionPerEye(int* width, int* height) {
if (VR_GetEngine()->appState.Instance) {
VR_GetResolution(VR_GetEngine(), width, height);
}
}
void SetVRCallbacks(void (*axis)(const AxisInput *axis, size_t count), bool(*key)(const KeyInput &key), void (*touch)(const TouchInput &touch)) {
cbNativeAxis = axis;
cbNativeKey = key;
cbNativeTouch = touch;
}
/*
================================================================================
VR input integration
================================================================================
*/
void SetVRAppMode(VRAppMode mode) {
appMode = mode;
}
void UpdateVRInput(bool haptics, float dp_xscale, float dp_yscale) {
//axis
if (pspKeys[(int)VIRTKEY_VR_CAMERA_ADJUST]) {
AxisInput axis[2] = {};
axis[0].deviceId = DEVICE_ID_DEFAULT;
axis[1].deviceId = DEVICE_ID_DEFAULT;
for (int j = 0; j < 2; j++) {
XrVector2f joystick = IN_VRGetJoystickState(j);
//horizontal
axis[0].axisId = j == 0 ? JOYSTICK_AXIS_X : JOYSTICK_AXIS_Z;
axis[0].value = joystick.x;
//vertical
axis[1].axisId = j == 0 ? JOYSTICK_AXIS_Y : JOYSTICK_AXIS_RZ;
axis[1].value = -joystick.y;
cbNativeAxis(axis, 2);
}
}
//buttons
KeyInput keyInput = {};
for (int j = 0; j < 2; j++) {
int status = IN_VRGetButtonState(j);
for (ButtonMapping& m : controllerMapping[j]) {
//fill KeyInput structure
bool pressed = status & m.ovr;
keyInput.flags = pressed ? KEY_DOWN : KEY_UP;
keyInput.keyCode = m.keycode;
keyInput.deviceId = controllerIds[j];
//process the key action
if (m.pressed != pressed) {
if (pressed && haptics) {
INVR_Vibrate(100, j, 1000);
}
cbNativeKey(keyInput);
m.pressed = pressed;
m.repeat = 0;
} else if (pressed && (m.repeat > 30)) {
keyInput.flags |= KEY_IS_REPEAT;
cbNativeKey(keyInput);
m.repeat = 0;
} else {
m.repeat++;
}
}
}
//motion control
if (g_Config.bEnableMotions) {
for (int j = 0; j < 2; j++) {
bool activate;
float limit = g_Config.fMotionLength; //length of needed movement in meters
XrVector3f axis = {0, 1, 0};
float center = ToRadians(VR_GetConfigFloat(VR_CONFIG_MENU_YAW));
XrQuaternionf orientation = XrQuaternionf_CreateFromVectorAngle(axis, center);
XrVector3f position = XrQuaternionf_Rotate(orientation, IN_VRGetPose(j).position);
//up
activate = position.y > limit;
keyInput.flags = activate ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_MOTION_UP;
keyInput.deviceId = controllerIds[j];
if (controllerMotion[j][0] != activate) cbNativeKey(keyInput);
controllerMotion[j][0] = activate;
//down
activate = position.y < -limit * 1.5f;
keyInput.flags = activate ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_MOTION_DOWN;
keyInput.deviceId = controllerIds[j];
if (controllerMotion[j][1] != activate) cbNativeKey(keyInput);
controllerMotion[j][1] = activate;
//left
activate = position.x < -limit * (j == 0 ? 1.0f : 0.25f);
keyInput.flags = activate ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_MOTION_LEFT;
keyInput.deviceId = controllerIds[j];
if (controllerMotion[j][2] != activate) cbNativeKey(keyInput);
controllerMotion[j][2] = activate;
//right
activate = position.x > limit * (j == 1 ? 1.0f : 0.25f);
keyInput.flags = activate ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_MOTION_RIGHT;
keyInput.deviceId = controllerIds[j];
if (controllerMotion[j][3] != activate) cbNativeKey(keyInput);
controllerMotion[j][3] = activate;
//forward
activate = position.z < -limit;
keyInput.flags = activate ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_MOTION_FORWARD;
keyInput.deviceId = controllerIds[j];
if (controllerMotion[j][4] != activate) cbNativeKey(keyInput);
controllerMotion[j][4] = activate;
}
}
// Head control
if (g_Config.bHeadRotationEnabled) {
float pitch = -VR_GetHMDAngles().x;
float yaw = -VR_GetHMDAngles().y;
bool disable = vrFlatForced || appMode == VR_MENU_MODE;
bool isVR = !IsFlatVRScene();
// calculate delta angles of the rotation
if (isVR) {
float f = g_Config.bHeadRotationSmoothing ? 0.5f : 1.0f;
float deltaPitch = pitch - hmdMotionLast[0];
float deltaYaw = yaw - hmdMotionLast[1];
while (deltaYaw >= 180) deltaYaw -= 360;
while (deltaYaw < -180) deltaYaw += 360;
hmdMotionLast[0] = pitch;
hmdMotionLast[1] = yaw;
hmdMotionDiffLast[0] = hmdMotionDiffLast[0] * (1-f) + hmdMotionDiff[0] * f;
hmdMotionDiffLast[1] = hmdMotionDiffLast[1] * (1-f) + hmdMotionDiff[1] * f;
hmdMotionDiff[0] += deltaPitch;
hmdMotionDiff[1] += deltaYaw;
pitch = hmdMotionDiff[0];
yaw = hmdMotionDiff[1];
}
bool activate;
float limit = isVR ? g_Config.fHeadRotationScale : 20;
keyInput.deviceId = DEVICE_ID_XR_HMD;
//left
activate = !disable && yaw < -limit;
keyInput.flags = activate ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_ROTATION_LEFT;
if (hmdMotion[2] != activate) cbNativeKey(keyInput);
if (isVR && activate) hmdMotionDiff[1] += limit;
hmdMotion[2] = activate;
//right
activate = !disable && yaw > limit;
keyInput.flags = activate ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_ROTATION_RIGHT;
if (hmdMotion[3] != activate) cbNativeKey(keyInput);
if (isVR && activate) hmdMotionDiff[1] -= limit;
hmdMotion[3] = activate;
}
// Camera adjust
if (pspKeys[VIRTKEY_VR_CAMERA_ADJUST]) {
for (auto& device : pspAxis) {
for (auto& axis : device.second) {
switch(axis.first) {
case JOYSTICK_AXIS_X:
if (axis.second < -0.75f) g_Config.fCameraSide -= 0.1f;
if (axis.second > 0.75f) g_Config.fCameraSide += 0.1f;
g_Config.fCameraSide = clampFloat(g_Config.fCameraSide, -150.0f, 150.0f);
break;
case JOYSTICK_AXIS_Y:
if (axis.second > 0.75f) g_Config.fCameraHeight -= 0.1f;
if (axis.second < -0.75f) g_Config.fCameraHeight += 0.1f;
g_Config.fCameraHeight = clampFloat(g_Config.fCameraHeight, -150.0f, 150.0f);
break;
case JOYSTICK_AXIS_Z:
if (g_Config.bEnableVR) {
if (axis.second < -0.75f) g_Config.fHeadUpDisplayScale -= 0.01f;
if (axis.second > 0.75f) g_Config.fHeadUpDisplayScale += 0.01f;
g_Config.fHeadUpDisplayScale = clampFloat(g_Config.fHeadUpDisplayScale, 0.0f, 1.5f);
} else {
if (axis.second < -0.75f) g_Config.fCanvas3DDistance += 0.1f;
if (axis.second > 0.75f) g_Config.fCanvas3DDistance -= 0.1f;
g_Config.fCanvas3DDistance = clampFloat(g_Config.fCanvas3DDistance, 1.0f, 15.0f);
}
break;
case JOYSTICK_AXIS_RZ:
if (axis.second > 0.75f) g_Config.fCameraDistance -= 0.1f;
if (axis.second < -0.75f) g_Config.fCameraDistance += 0.1f;
g_Config.fCameraDistance = clampFloat(g_Config.fCameraDistance, -150.0f, 150.0f);
break;
}
}
}
}
//enable or disable mouse
for (int j = 0; j < 2; j++) {
bool pressed = IN_VRGetButtonState(j) & ovrButton_Trigger;
if (pressed) {
int lastController = mouseController;
mouseController = j;
//prevent misclicks when changing the left/right controller
if (lastController != mouseController) {
mousePressed = true;
}
}
}
//mouse cursor
if ((mouseController >= 0) && ((appMode == VR_DIALOG_MODE) || (appMode == VR_MENU_MODE))) {
//get position on screen
XrPosef pose = IN_VRGetPose(mouseController);
XrVector3f angles = XrQuaternionf_ToEulerAngles(pose.orientation);
float width = (float)VR_GetConfig(VR_CONFIG_VIEWPORT_WIDTH);
float height = (float)VR_GetConfig(VR_CONFIG_VIEWPORT_HEIGHT);
float cx = width / 2;
float cy = height / 2;
float speed = (cx + cy) / 2;
float x = cx - tan(ToRadians(angles.y - VR_GetConfigFloat(VR_CONFIG_MENU_YAW))) * speed;
float y = cy - tan(ToRadians(angles.x)) * speed * VR_GetConfigFloat(VR_CONFIG_CANVAS_ASPECT);
//set renderer
VR_SetConfig(VR_CONFIG_MOUSE_X, (int)x);
VR_SetConfig(VR_CONFIG_MOUSE_Y, (int)y);
VR_SetConfig(VR_CONFIG_MOUSE_SIZE, 6 * (int)pow(VR_GetConfigFloat(VR_CONFIG_CANVAS_DISTANCE), 0.25f));
VR_SetConfig(VR_CONFIG_CANVAS_6DOF, g_Config.bEnable6DoF);
//inform engine about the status
TouchInput touch;
touch.id = mouseController;
touch.x = x * dp_xscale;
touch.y = (height - y - 1) * dp_yscale / VR_GetConfigFloat(VR_CONFIG_CANVAS_ASPECT);
bool pressed = IN_VRGetButtonState(mouseController) & ovrButton_Trigger;
if (mousePressed != pressed) {
if (pressed) {
touch.flags = TOUCH_DOWN;
cbNativeTouch(touch);
touch.flags = TOUCH_UP;
cbNativeTouch(touch);
}
mousePressed = pressed;
}
// mouse wheel emulation
// TODO: Spams key-up events if nothing changed!
for (int j = 0; j < 2; j++) {
keyInput.deviceId = controllerIds[j];
float scroll = -IN_VRGetJoystickState(j).y;
keyInput.flags = scroll < -0.5f ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
cbNativeKey(keyInput);
keyInput.flags = scroll > 0.5f ? KEY_DOWN : KEY_UP;
keyInput.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
cbNativeKey(keyInput);
}
} else {
VR_SetConfig(VR_CONFIG_MOUSE_SIZE, 0);
}
}
bool UpdateVRAxis(const AxisInput &axis) {
if (pspAxis.find(axis.deviceId) == pspAxis.end()) {
pspAxis[axis.deviceId] = std::map<int, float>();
}
pspAxis[axis.deviceId][axis.axisId] = axis.value;
return !pspKeys[VIRTKEY_VR_CAMERA_ADJUST];
}
bool UpdateVRKeys(const KeyInput &key) {
//store key value
std::vector<int> nativeKeys;
bool wasScreenKeyOn = pspKeys[CTRL_SCREEN];
bool wasCameraAdjustOn = pspKeys[VIRTKEY_VR_CAMERA_ADJUST];
if (KeyMap::InputMappingToPspButton(InputMapping(key.deviceId, key.keyCode), &nativeKeys)) {
for (int& nativeKey : nativeKeys) {
pspKeys[nativeKey] = key.flags & KEY_DOWN;
}
}
//block VR controller keys in the UI mode
if ((key.deviceId == controllerIds[0]) || (key.deviceId == controllerIds[1])) {
if ((appMode == VR_DIALOG_MODE) || (appMode == VR_MENU_MODE)) {
switch (key.keyCode) {
case NKCODE_BACK:
case NKCODE_EXT_MOUSEWHEEL_UP:
case NKCODE_EXT_MOUSEWHEEL_DOWN:
return true;
default:
return false;
}
}
}
// Update force flat 2D mode
if (g_Config.bManualForceVR) {
if (!wasScreenKeyOn && pspKeys[CTRL_SCREEN]) {
vrFlatForced = !vrFlatForced;
}
} else {
vrFlatForced = pspKeys[CTRL_SCREEN];
}
// Release keys on enabling camera adjust
if (!wasCameraAdjustOn && pspKeys[VIRTKEY_VR_CAMERA_ADJUST]) {
KeyInput keyUp;
keyUp.deviceId = key.deviceId;
keyUp.flags = KEY_UP;
pspKeys[VIRTKEY_VR_CAMERA_ADJUST] = false;
for (auto& pspKey : pspKeys) {
if (pspKey.second) {
keyUp.keyCode = (InputKeyCode)pspKey.first;
cbNativeKey(keyUp);
}
}
pspKeys[VIRTKEY_VR_CAMERA_ADJUST] = true;
}
// Reset camera adjust
if (pspKeys[VIRTKEY_VR_CAMERA_ADJUST] && pspKeys[VIRTKEY_VR_CAMERA_RESET]) {
g_Config.fCanvas3DDistance = 3.0f;
g_Config.fCameraHeight = 0;
g_Config.fCameraSide = 0;
g_Config.fCameraDistance = 0;
g_Config.fHeadUpDisplayScale = 0.3f;
}
//block keys by camera adjust
return !pspKeys[VIRTKEY_VR_CAMERA_ADJUST];
}
/*
================================================================================
// VR games compatibility
================================================================================
*/
#if XR_USE_GRAPHICS_API_OPENGL || XR_USE_GRAPHICS_API_OPENGL_ES
void PreprocessSkyplane(GLRStep* step) {
// Do not do anything if the scene is not in VR.
if (IsFlatVRScene()) {
return;
}
// Check if it is the step we need to modify.
for (auto& cmd : step->commands) {
if (cmd.cmd == GLRRenderCommand::BIND_FB_TEXTURE) {
return;
}
}
// Clear sky with the fog color.
if (!vrCompat[VR_COMPAT_FBO_CLEAR]) {
GLRRenderData &skyClear = step->commands.insert(step->commands.begin());
skyClear.cmd = GLRRenderCommand::CLEAR;
skyClear.clear.colorMask = 0xF;
skyClear.clear.clearMask = GL_COLOR_BUFFER_BIT; // don't need to initialize clearZ, clearStencil
skyClear.clear.clearColor = vrCompat[VR_COMPAT_FOG_COLOR];
skyClear.clear.scissorX = 0;
skyClear.clear.scissorY = 0;
skyClear.clear.scissorW = 0; // signal no scissor
skyClear.clear.scissorH = 0;
vrCompat[VR_COMPAT_FBO_CLEAR] = true;
}
// Remove original sky plane.
bool depthEnabled = false;
for (auto& command : step->commands) {
if (command.cmd == GLRRenderCommand::DEPTH) {
depthEnabled = command.depth.enabled;
} else if ((command.cmd == GLRRenderCommand::DRAW && command.draw.indexBuffer != nullptr) && !depthEnabled) {
command.draw.count = 0;
}
}
}
void PreprocessStepVR(void* step) {
auto* glrStep = (GLRStep*)step;
if (vrCompat[VR_COMPAT_SKYPLANE]) PreprocessSkyplane(glrStep);
}
#else
void PreprocessStepVR(void* step) {}
#endif
void SetVRCompat(VRCompatFlag flag, long value) {
vrCompat[flag] = value;
}
/*
================================================================================
VR rendering integration
================================================================================
*/
void* BindVRFramebuffer() {
return VR_BindFramebuffer(VR_GetEngine());
}
bool StartVRRender() {
if (!VR_GetConfig(VR_CONFIG_VIEWPORT_VALID)) {
VR_InitRenderer(VR_GetEngine(), IsMultiviewSupported());
VR_SetConfig(VR_CONFIG_VIEWPORT_VALID, true);
}
if (VR_InitFrame(VR_GetEngine())) {
// VR flags
bool vrIncompatibleGame = PSP_CoreParameter().compat.vrCompat().ForceFlatScreen;
bool vrScene = !vrFlatForced && (g_Config.bManualForceVR || (vr3DGeometryCount > 15));
bool vrStereo = !PSP_CoreParameter().compat.vrCompat().ForceMono && g_Config.bEnableStereo;
// Get VR status
for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
vrView[eye] = VR_GetView(eye);
}
UpdateVRViewMatrices();
// Update projection matrix
XrFovf fov = {};
for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
fov.angleLeft += vrView[eye].fov.angleLeft / 2.0f;
fov.angleRight += vrView[eye].fov.angleRight / 2.0f;
fov.angleUp += vrView[eye].fov.angleUp / 2.0f;
fov.angleDown += vrView[eye].fov.angleDown / 2.0f;
}
float nearZ = g_Config.fFieldOfViewPercentage / 200.0f;
float tanAngleLeft = tanf(fov.angleLeft);
float tanAngleRight = tanf(fov.angleRight);
float tanAngleDown = tanf(fov.angleDown);
float tanAngleUp = tanf(fov.angleUp);
float M[16] = {};
M[0] = 2 / (tanAngleRight - tanAngleLeft);
M[2] = (tanAngleRight + tanAngleLeft) / (tanAngleRight - tanAngleLeft);
M[5] = 2 / (tanAngleUp - tanAngleDown);
M[6] = (tanAngleUp + tanAngleDown) / (tanAngleUp - tanAngleDown);
M[10] = -1;
M[11] = -(nearZ + nearZ);
M[14] = -1;
memcpy(vrMatrix[VR_PROJECTION_MATRIX], M, sizeof(float) * 16);
// Decide if the scene is 3D or not
VR_SetConfigFloat(VR_CONFIG_CANVAS_ASPECT, 480.0f / 272.0f);
if (g_Config.bEnableVR && !vrIncompatibleGame && (appMode == VR_GAME_MODE) && vrScene) {
VR_SetConfig(VR_CONFIG_MODE, vrStereo ? VR_MODE_STEREO_6DOF : VR_MODE_MONO_6DOF);
vrFlatGame = false;
} else {
VR_SetConfig(VR_CONFIG_MODE, vrStereo ? VR_MODE_STEREO_SCREEN : VR_MODE_MONO_SCREEN);
if (IsGameVRScene()) {
vrFlatGame = true;
}
}
vr3DGeometryCount /= 2;
// Set compatibility
vrCompat[VR_COMPAT_SKYPLANE] = PSP_CoreParameter().compat.vrCompat().Skyplane;
// Set customizations
__DisplaySetFramerate(g_Config.bForce72Hz ? 72 : 60);
VR_SetConfigFloat(VR_CONFIG_CANVAS_DISTANCE, vrScene && (appMode == VR_GAME_MODE) ? g_Config.fCanvas3DDistance : g_Config.fCanvasDistance);
VR_SetConfig(VR_CONFIG_PASSTHROUGH, g_Config.bPassthrough);
return true;
}
return false;
}
void FinishVRRender() {
VR_FinishFrame(VR_GetEngine());
}
void PreVRFrameRender(int fboIndex) {
VR_BeginFrame(VR_GetEngine(), fboIndex);
vrCompat[VR_COMPAT_FBO_CLEAR] = false;
}
void PostVRFrameRender() {
VR_EndFrame(VR_GetEngine());
}
int GetVRFBOIndex() {
return VR_GetConfig(VR_CONFIG_CURRENT_FBO);
}
int GetVRPassesCount() {
bool vrStereo = !PSP_CoreParameter().compat.vrCompat().ForceMono && g_Config.bEnableStereo;
if (!IsMultiviewSupported() && vrStereo) {
return 2;
} else {
return 1;
}
}
bool IsMultiviewSupported() {
return false;
}
bool IsPassthroughSupported() {
return VR_GetPlatformFlag(VR_PLATFORM_EXTENSION_PASSTHROUGH);
}
bool IsFlatVRGame() {
return vrFlatGame;
}
bool IsFlatVRScene() {
int vrMode = VR_GetConfig(VR_CONFIG_MODE);
return (vrMode == VR_MODE_MONO_SCREEN) || (vrMode == VR_MODE_STEREO_SCREEN);
}
bool IsGameVRScene() {
return (appMode == VR_GAME_MODE) || (appMode == VR_DIALOG_MODE);
}
bool Is2DVRObject(float* projMatrix, bool ortho) {
// Quick analyze if the object is in 2D
if ((fabs(fabs(projMatrix[12]) - 1.0f) < EPSILON) && (fabs(fabs(projMatrix[13]) - 1.0f) < EPSILON) && (fabs(fabs(projMatrix[14]) - 1.0f) < EPSILON)) {
return true;
} else if ((fabs(projMatrix[0]) > 10.0f) && (fabs(projMatrix[5]) > 10.0f)) {
return true;
} else if (fabs(projMatrix[15] - 1) < EPSILON) {
return true;
}
// Update 3D geometry count
bool identity = IsMatrixIdentity(projMatrix);
if (!identity && !ortho) {
vr3DGeometryCount++;
}
return identity || ortho;
}
void UpdateVRParams(float* projMatrix) {
// Set mirroring of axes
vrMirroring[VR_MIRRORING_AXIS_X] = projMatrix[0] < 0;
vrMirroring[VR_MIRRORING_AXIS_Y] = projMatrix[5] < 0;
vrMirroring[VR_MIRRORING_AXIS_Z] = projMatrix[10] > 0;
int variant = 1;
variant += projMatrix[0] < 0;
variant += (projMatrix[5] < 0) << 1;
variant += (projMatrix[10] < 0) << 2;
if (PSP_CoreParameter().compat.vrCompat().MirroringVariant > 0) {
variant = PSP_CoreParameter().compat.vrCompat().MirroringVariant;
}
switch (variant) {
case 1: //e.g. ATV
vrMirroring[VR_MIRRORING_PITCH] = false;
vrMirroring[VR_MIRRORING_YAW] = true;
vrMirroring[VR_MIRRORING_ROLL] = true;
break;
case 2: //e.g. Tales of the World
vrMirroring[VR_MIRRORING_PITCH] = false;
vrMirroring[VR_MIRRORING_YAW] = false;
vrMirroring[VR_MIRRORING_ROLL] = false;
break;
case 3: //e.g.PES 2014
case 4: //untested
case 6: //e.g Dante's Inferno
case 8: //untested
vrMirroring[VR_MIRRORING_PITCH] = true;
vrMirroring[VR_MIRRORING_YAW] = true;
vrMirroring[VR_MIRRORING_ROLL] = false;
break;
case 5: //e.g. Assassins Creed
case 7: //e.g. Ghost in the shell
vrMirroring[VR_MIRRORING_PITCH] = true;
vrMirroring[VR_MIRRORING_YAW] = false;
vrMirroring[VR_MIRRORING_ROLL] = true;
break;
default:
assert(false);
std::exit(1);
}
if (vrMirroringVariant != variant) {
vrMirroringVariant = variant;
UpdateVRViewMatrices();
}
}
void UpdateVRProjection(float* projMatrix, float* leftEye, float* rightEye) {
float* hmdProjection = vrMatrix[VR_PROJECTION_MATRIX];
for (int i = 0; i < 16; i++) {
if ((hmdProjection[i] > 0) != (projMatrix[i] > 0)) {
hmdProjection[i] *= -1.0f;
}
}
memcpy(leftEye, hmdProjection, 16 * sizeof(float));
memcpy(rightEye, hmdProjection, 16 * sizeof(float));
}
void UpdateVRView(float* leftEye, float* rightEye) {
float* dst[] = {leftEye, rightEye};
float* matrix[] = {vrMatrix[VR_VIEW_MATRIX_LEFT_EYE], vrMatrix[VR_VIEW_MATRIX_RIGHT_EYE]};
for (int index = 0; index < 2; index++) {
// Validate the view matrix
if (PSP_CoreParameter().compat.vrCompat().IdentityViewHack && IsMatrixIdentity(dst[index])) {
return;
}
// Get view matrix from the game
Lin::Matrix4x4 gameView = {};
memcpy(gameView.m, dst[index], 16 * sizeof(float));
// Get view matrix from the headset
Lin::Matrix4x4 hmdView = {};
memcpy(hmdView.m, matrix[index], 16 * sizeof(float));
// Combine the matrices
Lin::Matrix4x4 renderView = hmdView * gameView;
memcpy(dst[index], renderView.m, 16 * sizeof(float));
}
}
void UpdateVRViewMatrices() {
// Get 6DoF scale
float scale = 1.0f;
if (PSP_CoreParameter().compat.vrCompat().UnitsPerMeter > 0) {
scale = PSP_CoreParameter().compat.vrCompat().UnitsPerMeter;
}
// Get input
bool flatScreen = false;
XrPosef invView = vrView[0].pose;
int vrMode = VR_GetConfig(VR_CONFIG_MODE);
if ((vrMode == VR_MODE_MONO_SCREEN) || (vrMode == VR_MODE_STEREO_SCREEN)) {
invView = XrPosef_Identity();
flatScreen = true;
}
// get axis mirroring configuration
float mx = vrMirroring[VR_MIRRORING_PITCH] ? -1.0f : 1.0f;
float my = vrMirroring[VR_MIRRORING_YAW] ? -1.0f : 1.0f;
float mz = vrMirroring[VR_MIRRORING_ROLL] ? -1.0f : 1.0f;
// ensure there is maximally one axis to mirror rotation
if (mx + my + mz < 0) {
mx *= -1.0f;
my *= -1.0f;
mz *= -1.0f;
} else {
invView = XrPosef_Inverse(invView);
}
// apply camera pitch offset
XrVector3f positionOffset = {g_Config.fCameraSide, g_Config.fCameraHeight, g_Config.fCameraDistance};
if (!flatScreen) {
float pitchOffset = 0;
switch (g_Config.iCameraPitch) {
case 1: //Top view -> First person
pitchOffset = 90;
positionOffset = {positionOffset.x, positionOffset.z, -positionOffset.y};
break;
case 2: //First person -> Top view
pitchOffset = -90;
positionOffset = {positionOffset.x, -positionOffset.z + 20, positionOffset.y};
break;
}
XrQuaternionf rotationOffset = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, ToRadians(pitchOffset));
invView.orientation = XrQuaternionf_Multiply(rotationOffset, invView.orientation);
}
// decompose rotation
XrVector3f rotation = XrQuaternionf_ToEulerAngles(invView.orientation);
float mPitch = mx * ToRadians(rotation.x);
float mYaw = my * ToRadians(rotation.y);
float mRoll = mz * ToRadians(rotation.z);
// use in-game camera interpolated rotation
if (g_Config.bHeadRotationEnabled) mYaw = -my * ToRadians(hmdMotionDiffLast[1]); // horizontal
// create updated quaternion
XrQuaternionf pitch = XrQuaternionf_CreateFromVectorAngle({1, 0, 0}, mPitch);
XrQuaternionf yaw = XrQuaternionf_CreateFromVectorAngle({0, 1, 0}, mYaw);
XrQuaternionf roll = XrQuaternionf_CreateFromVectorAngle({0, 0, 1}, mRoll);
invView.orientation = XrQuaternionf_Multiply(roll, XrQuaternionf_Multiply(pitch, yaw));
float M[16];
XrQuaternionf_ToMatrix4f(&invView.orientation, M);
// Apply 6Dof head movement
if (g_Config.bEnable6DoF && !g_Config.bHeadRotationEnabled && (g_Config.iCameraPitch == 0)) {
M[3] -= vrView[0].pose.position.x * (vrMirroring[VR_MIRRORING_AXIS_X] ? -1.0f : 1.0f) * scale;
M[7] -= vrView[0].pose.position.y * (vrMirroring[VR_MIRRORING_AXIS_Y] ? -1.0f : 1.0f) * scale;
M[11] -= vrView[0].pose.position.z * (vrMirroring[VR_MIRRORING_AXIS_Z] ? -1.0f : 1.0f) * scale;
}
// Camera adjust - distance
if (fabsf(positionOffset.z) > 0.0f) {
XrVector3f forward = {0.0f, 0.0f, positionOffset.z * scale};
forward = XrQuaternionf_Rotate(invView.orientation, forward);
forward = XrVector3f_ScalarMultiply(forward, vrMirroring[VR_MIRRORING_AXIS_Z] ? -1.0f : 1.0f);
M[3] += forward.x;
M[7] += forward.y;
M[11] += forward.z;
}
// Camera adjust - height
if (fabsf(positionOffset.y) > 0.0f) {
XrVector3f up = {0.0f, -positionOffset.y * scale, 0.0f};
up = XrQuaternionf_Rotate(invView.orientation, up);
up = XrVector3f_ScalarMultiply(up, vrMirroring[VR_MIRRORING_AXIS_Y] ? -1.0f : 1.0f);
M[3] += up.x;
M[7] += up.y;
M[11] += up.z;
}
// Camera adjust - side
if (fabsf(positionOffset.x) > 0.0f) {
XrVector3f side = {-positionOffset.x * scale, 0.0f, 0.0f};
side = XrQuaternionf_Rotate(invView.orientation, side);
side = XrVector3f_ScalarMultiply(side, vrMirroring[VR_MIRRORING_AXIS_X] ? -1.0f : 1.0f);
M[3] += side.x;
M[7] += side.y;
M[11] += side.z;
}
for (int matrix = VR_VIEW_MATRIX_LEFT_EYE; matrix <= VR_VIEW_MATRIX_RIGHT_EYE; matrix++) {
// Stereoscopy
bool vrStereo = !PSP_CoreParameter().compat.vrCompat().ForceMono && g_Config.bEnableStereo;
if (vrStereo) {
bool mirrored = vrMirroring[VR_MIRRORING_AXIS_Z] ^ (matrix == VR_VIEW_MATRIX_RIGHT_EYE);
float dx = fabs(vrView[1].pose.position.x - vrView[0].pose.position.x);
float dy = fabs(vrView[1].pose.position.y - vrView[0].pose.position.y);
float dz = fabs(vrView[1].pose.position.z - vrView[0].pose.position.z);
float ipd = sqrt(dx * dx + dy * dy + dz * dz);
XrVector3f separation = {ipd * scale * 0.5f, 0.0f, 0.0f};
separation = XrQuaternionf_Rotate(invView.orientation, separation);
separation = XrVector3f_ScalarMultiply(separation, mirrored ? -1.0f : 2.0f);
M[3] += separation.x;
M[7] += separation.y;
M[11] += separation.z;
}
memcpy(vrMatrix[matrix], M, sizeof(float) * 16);
}
}