mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
libretro: add VR mode
This commit is contained in:
1 parent
d277222a81
commit
be7f2f5973
19 files changed
+685
-39
No files matched your search
@@ -32,6 +32,7 @@
|
||||
#include "DolphinLibretro/Common/Globals.h"
|
||||
#include "DolphinLibretro/Common/Options.h"
|
||||
#include "DolphinLibretro/Video.h"
|
||||
#include "DolphinLibretro/VR.h"
|
||||
#include "DolphinLibretro/VideoContexts/ContextStatus.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h"
|
||||
@@ -612,6 +613,9 @@ bool retro_load_game(const struct retro_game_info* game)
|
||||
Config::SetBase(Config::GFX_PERF_QUERIES_ENABLE,
|
||||
Libretro::GetOption<bool>(gfx_gamespecific::GFX_PERF_QUERIES_ENABLE, /*def=*/false));
|
||||
|
||||
if (Libretro::VR::IsEnabled())
|
||||
Libretro::VR::ApplyConfig();
|
||||
|
||||
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
|
||||
// wiimote - alternative input server
|
||||
{
|
||||
@@ -839,6 +843,7 @@ void retro_unload_game(void)
|
||||
// Rest of shutdown
|
||||
g_context_status.MarkUnitialized();
|
||||
Libretro::Input::Shutdown();
|
||||
Libretro::VR::Shutdown();
|
||||
Libretro::Log::Shutdown();
|
||||
UICommon::ShutdownControllers();
|
||||
UICommon::Shutdown();
|
||||
|
||||
@@ -7,6 +7,7 @@ add_library(dolphin_libretro SHARED
|
||||
Log.cpp
|
||||
Main.cpp
|
||||
Video.cpp
|
||||
VR.cpp
|
||||
)
|
||||
|
||||
if(ENABLE_VULKAN)
|
||||
|
||||
@@ -1070,6 +1070,21 @@ static struct retro_core_option_v2_definition option_defs[] = {
|
||||
},
|
||||
"disabled"
|
||||
},
|
||||
{
|
||||
Libretro::Options::gfx_settings::VR_MODE,
|
||||
"Graphics > Settings > VR Mode",
|
||||
"VR Mode",
|
||||
"'Stereoscopic' shows the game on a virtual screen in 3D. 'Immersive' also turns the game camera with your head. Expect odd things in immersive mode. Requires a frontend with VR support.",
|
||||
nullptr,
|
||||
CATEGORY_GFX_SETTINGS,
|
||||
{
|
||||
{ "disabled", nullptr },
|
||||
{ "stereo", "Stereoscopic" },
|
||||
{ "immersive", "Immersive (Free Look)" },
|
||||
{ nullptr, nullptr }
|
||||
},
|
||||
"disabled"
|
||||
},
|
||||
|
||||
// ========== Graphics.Enhancements ==========
|
||||
{
|
||||
|
||||
@@ -264,6 +264,7 @@ namespace gfx_settings {
|
||||
constexpr const char FAST_DEPTH_CALCULATION[] = "dolphin_fast_depth_calculation";
|
||||
constexpr const char DISABLE_FOG[] = "dolphin_disable_fog";
|
||||
constexpr const char MODS_ENABLE[] = "dolphin_mods_enabled";
|
||||
constexpr const char VR_MODE[] = "dolphin_vr_mode";
|
||||
} // namespace gfx_settings
|
||||
|
||||
// ======================================================
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "DolphinLibretro/Input.h"
|
||||
#include "DolphinLibretro/Common/Globals.h"
|
||||
#include "DolphinLibretro/Common/Options.h"
|
||||
#include "DolphinLibretro/VR.h"
|
||||
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
|
||||
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h"
|
||||
#endif
|
||||
@@ -115,6 +116,9 @@ double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z
|
||||
double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z
|
||||
double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z
|
||||
double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][3] = {}; // x, y, z
|
||||
double g_point[NUM_CONTROLLERS_FOR_SENSORS][4] = {}; // right, left, down, up
|
||||
// Player 1's Wiimote is driven by the right VR controller (VR Mode on at boot).
|
||||
static bool s_vr_motion = false;
|
||||
|
||||
static struct retro_input_descriptor descGC[] = {
|
||||
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"},
|
||||
@@ -660,23 +664,34 @@ void InitSensors()
|
||||
port_max = (Core::System::GetInstance().IsWii() &&
|
||||
Libretro::Options::GetCached<int>(Libretro::Options::sysconf::ALT_GC_PORTS_ON_WII)) ? 8 : 4;
|
||||
|
||||
// In VR the right controller moves player 1's Wiimote.
|
||||
s_vr_motion = Libretro::VR::IsEnabled();
|
||||
|
||||
for (int i = 0; i < port_max; i++)
|
||||
{
|
||||
if (sensor_interface.set_sensor_state)
|
||||
if (s_vr_motion && i == 0)
|
||||
{
|
||||
sensor_enabled[i][SENSOR_ACCELEROMETER] = true;
|
||||
sensor_enabled[i][SENSOR_GYRO] = true;
|
||||
// At rest until the controller is seen: gravity reads as up.
|
||||
g_accel_pos[i][2] = 9.80665;
|
||||
INFO_LOG_FMT(BOOT, "Sensor interface: Port: {} driven by the right VR controller", i);
|
||||
}
|
||||
else if (sensor_interface.set_sensor_state)
|
||||
{
|
||||
sensor_enabled[i][SENSOR_ACCELEROMETER] = sensor_interface.set_sensor_state(i, RETRO_SENSOR_ACCELEROMETER_ENABLE, 60);
|
||||
sensor_enabled[i][SENSOR_GYRO] = sensor_interface.set_sensor_state(i, RETRO_SENSOR_GYROSCOPE_ENABLE, 60);
|
||||
|
||||
if (sensor_enabled[i][SENSOR_ACCELEROMETER] || sensor_enabled[i][SENSOR_GYRO])
|
||||
{
|
||||
auto sensor = std::make_shared<SensorDevice>(i);
|
||||
sensor->RegisterAll();
|
||||
g_controller_interface.AddDevice(sensor);
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(BOOT, "Sensor interface: Port: {} ACCELEROMETER: {} GYROSCOPE: {}", i,
|
||||
sensor_enabled[i][SENSOR_ACCELEROMETER], sensor_enabled[i][SENSOR_GYRO]);
|
||||
}
|
||||
|
||||
if (sensor_enabled[i][SENSOR_ACCELEROMETER] || sensor_enabled[i][SENSOR_GYRO])
|
||||
{
|
||||
auto sensor = std::make_shared<SensorDevice>(i);
|
||||
sensor->RegisterAll();
|
||||
g_controller_interface.AddDevice(sensor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,15 +717,20 @@ void Shutdown()
|
||||
Pad::ResetRumble(i);
|
||||
|
||||
// Was crossed over and hardcoded to port 0, leaving sensors running.
|
||||
if (sensor_enabled[i][SENSOR_ACCELEROMETER])
|
||||
sensor_interface.set_sensor_state(i, RETRO_SENSOR_ACCELEROMETER_DISABLE, 0);
|
||||
// A port driven by a VR controller never turned the frontend's sensors on.
|
||||
if (!UsesVRMotion(i))
|
||||
{
|
||||
if (sensor_enabled[i][SENSOR_ACCELEROMETER])
|
||||
sensor_interface.set_sensor_state(i, RETRO_SENSOR_ACCELEROMETER_DISABLE, 0);
|
||||
|
||||
if (sensor_enabled[i][SENSOR_GYRO])
|
||||
sensor_interface.set_sensor_state(i, RETRO_SENSOR_GYROSCOPE_DISABLE, 0);
|
||||
if (sensor_enabled[i][SENSOR_GYRO])
|
||||
sensor_interface.set_sensor_state(i, RETRO_SENSOR_GYROSCOPE_DISABLE, 0);
|
||||
}
|
||||
|
||||
sensor_enabled[i][SENSOR_ACCELEROMETER] = false;
|
||||
sensor_enabled[i][SENSOR_GYRO] = false;
|
||||
}
|
||||
s_vr_motion = false;
|
||||
|
||||
Keyboard::Shutdown();
|
||||
Pad::Shutdown();
|
||||
@@ -719,9 +739,30 @@ void Shutdown()
|
||||
rumble.set_rumble_state = nullptr;
|
||||
}
|
||||
|
||||
bool UsesVRMotion(unsigned port)
|
||||
{
|
||||
return s_vr_motion && port == 0;
|
||||
}
|
||||
|
||||
void SetVRMotion(unsigned port, const float accel[3], const float gyro[3], const float point[2])
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
g_accel_pos[port][i] = std::max(0.0f, accel[i]);
|
||||
g_accel_neg[port][i] = std::max(0.0f, -accel[i]);
|
||||
g_gyro_pos[port][i] = std::max(0.0f, gyro[i]);
|
||||
g_gyro_neg[port][i] = std::max(0.0f, -gyro[i]);
|
||||
}
|
||||
g_point[port][0] = std::max(0.0f, point[0]);
|
||||
g_point[port][1] = std::max(0.0f, -point[0]);
|
||||
g_point[port][2] = std::max(0.0f, -point[1]);
|
||||
g_point[port][3] = std::max(0.0f, point[1]);
|
||||
}
|
||||
|
||||
void UpdateAccelerometer(unsigned port)
|
||||
{
|
||||
if (!sensor_enabled[port][SENSOR_ACCELEROMETER] || !sensor_interface.get_sensor_input)
|
||||
if (!sensor_enabled[port][SENSOR_ACCELEROMETER] || !sensor_interface.get_sensor_input ||
|
||||
UsesVRMotion(port))
|
||||
return;
|
||||
|
||||
static const float G = 9.80665f;
|
||||
@@ -753,7 +794,8 @@ void UpdateAccelerometer(unsigned port)
|
||||
|
||||
void UpdateGyro(unsigned port)
|
||||
{
|
||||
if (!sensor_enabled[port][SENSOR_GYRO] || !sensor_interface.get_sensor_input)
|
||||
if (!sensor_enabled[port][SENSOR_GYRO] || !sensor_interface.get_sensor_input ||
|
||||
UsesVRMotion(port))
|
||||
return;
|
||||
|
||||
// rad/s about the remote's own axes: +X left, +Y back, +Z up.
|
||||
@@ -951,19 +993,26 @@ void UpdateWiimoteMappings(const WiimoteUpdateFlags& f, unsigned port, unsigned
|
||||
std::string devAnalog = Libretro::Input::GetQualifiedName(port, RETRO_DEVICE_ANALOG);
|
||||
std::string devPointer = Libretro::Input::GetQualifiedName(port, RETRO_DEVICE_POINTER);
|
||||
|
||||
// In VR the right controller aims the cursor too, alongside the thumbstick or mouse.
|
||||
const bool vr_aim = Libretro::Input::UsesVRMotion(port) && irMode != 0;
|
||||
const std::string devSensor = GetQualifiedNameSensor(port);
|
||||
auto aim = [&](const std::string& expr, const char* direction) {
|
||||
return vr_aim ? "(" + expr + ") | `" + devSensor + ":" + direction + "`" : expr;
|
||||
};
|
||||
|
||||
if (irMode == 0 || irMode == 1)
|
||||
{
|
||||
// Set right stick to control the IR
|
||||
std::string irMod = Libretro::Options::GetCached<std::string>(Libretro::Options::wiimote::IR_MODIFIER);
|
||||
wmIR->SetControlExpression(0, wrap(irMod, "`" + devAnalog + ":Y1-`")); // Up
|
||||
wmIR->SetControlExpression(1, wrap(irMod, "`" + devAnalog + ":Y1+`")); // Down
|
||||
wmIR->SetControlExpression(2, wrap(irMod, "`" + devAnalog + ":X1-`")); // Left
|
||||
wmIR->SetControlExpression(3, wrap(irMod, "`" + devAnalog + ":X1+`")); // Right
|
||||
wmIR->SetControlExpression(0, aim(wrap(irMod, "`" + devAnalog + ":Y1-`"), "PointUp")); // Up
|
||||
wmIR->SetControlExpression(1, aim(wrap(irMod, "`" + devAnalog + ":Y1+`"), "PointDown")); // Down
|
||||
wmIR->SetControlExpression(2, aim(wrap(irMod, "`" + devAnalog + ":X1-`"), "PointLeft")); // Left
|
||||
wmIR->SetControlExpression(3, aim(wrap(irMod, "`" + devAnalog + ":X1+`"), "PointRight")); // Right
|
||||
|
||||
static_cast<ControllerEmu::NumericSetting<bool>*>(wmIR->numeric_settings[4].get())
|
||||
->SetValue(irMode == 0); // Relative input
|
||||
static_cast<ControllerEmu::NumericSetting<bool>*>(wmIR->numeric_settings[5].get())
|
||||
->SetValue(true); // Auto hide
|
||||
->SetValue(!vr_aim); // Auto hide
|
||||
|
||||
// Swing‑related updates
|
||||
ControllerEmu::ControlGroup* wmSwing = wm->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Swing);
|
||||
@@ -993,10 +1042,10 @@ void UpdateWiimoteMappings(const WiimoteUpdateFlags& f, unsigned port, unsigned
|
||||
else
|
||||
{
|
||||
// Mouse controls IR
|
||||
wmIR->SetControlExpression(0, "`" + devPointer + ":Y0-`"); // Up
|
||||
wmIR->SetControlExpression(1, "`" + devPointer + ":Y0+`"); // Down
|
||||
wmIR->SetControlExpression(2, "`" + devPointer + ":X0-`"); // Left
|
||||
wmIR->SetControlExpression(3, "`" + devPointer + ":X0+`"); // Right
|
||||
wmIR->SetControlExpression(0, aim("`" + devPointer + ":Y0-`", "PointUp")); // Up
|
||||
wmIR->SetControlExpression(1, aim("`" + devPointer + ":Y0+`", "PointDown")); // Down
|
||||
wmIR->SetControlExpression(2, aim("`" + devPointer + ":X0-`", "PointLeft")); // Left
|
||||
wmIR->SetControlExpression(3, aim("`" + devPointer + ":X0+`", "PointRight")); // Right
|
||||
static_cast<ControllerEmu::NumericSetting<bool>*>(wmIR->numeric_settings[4].get())
|
||||
->SetValue(false); // Relative input
|
||||
static_cast<ControllerEmu::NumericSetting<bool>*>(wmIR->numeric_settings[5].get())
|
||||
|
||||
@@ -42,12 +42,19 @@ extern double g_accel_pos[NUM_CONTROLLERS_FOR_SENSORS][3];
|
||||
extern double g_accel_neg[NUM_CONTROLLERS_FOR_SENSORS][3];
|
||||
extern double g_gyro_pos[NUM_CONTROLLERS_FOR_SENSORS][3];
|
||||
extern double g_gyro_neg[NUM_CONTROLLERS_FOR_SENSORS][3];
|
||||
// Where the VR controller points, as one-sided -1..1 pairs: right, left, down, up.
|
||||
extern double g_point[NUM_CONTROLLERS_FOR_SENSORS][4];
|
||||
|
||||
static retro_sensor_interface sensor_interface = {0};
|
||||
|
||||
void Init(const WindowSystemInfo& wsi);
|
||||
void InitStage2();
|
||||
void InitSensors();
|
||||
// VR: player 1's Wiimote takes its motion and pointing from the right VR controller.
|
||||
bool UsesVRMotion(unsigned port);
|
||||
// Accelerometer in m/s^2 and gyroscope in rad/s about the remote's axes (+X left, +Y back,
|
||||
// +Z up), and where it points (-1..1, +X right, +Y up).
|
||||
void SetVRMotion(unsigned port, const float accel[3], const float gyro[3], const float point[2]);
|
||||
void UpdateAccelerometer(unsigned port);
|
||||
void UpdateGyro(unsigned port);
|
||||
void Update();
|
||||
@@ -99,6 +106,10 @@ public:
|
||||
AddInput(new ScalarInput("AccelY-", &Libretro::Input::g_accel_neg[m_port][1]));
|
||||
AddInput(new ScalarInput("AccelZ+", &Libretro::Input::g_accel_pos[m_port][2]));
|
||||
AddInput(new ScalarInput("AccelZ-", &Libretro::Input::g_accel_neg[m_port][2]));
|
||||
AddInput(new ScalarInput("PointRight", &Libretro::Input::g_point[m_port][0]));
|
||||
AddInput(new ScalarInput("PointLeft", &Libretro::Input::g_point[m_port][1]));
|
||||
AddInput(new ScalarInput("PointDown", &Libretro::Input::g_point[m_port][2]));
|
||||
AddInput(new ScalarInput("PointUp", &Libretro::Input::g_point[m_port][3]));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "DolphinLibretro/Input.h"
|
||||
#include "DolphinLibretro/Common/Options.h"
|
||||
#include "DolphinLibretro/Video.h"
|
||||
#include "DolphinLibretro/VR.h"
|
||||
#include "VideoBackends/OGL/OGLTexture.h"
|
||||
#include "VideoBackends/OGL/OGLGfx.h"
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
@@ -74,6 +75,8 @@ namespace Libretro
|
||||
{
|
||||
extern retro_environment_t environ_cb;
|
||||
static bool widescreen;
|
||||
// Whether the geometry last reported to the frontend was sized for VR
|
||||
static bool vr_frame;
|
||||
double g_core_refresh_rate{0};
|
||||
// Reset per game (see retro_run): the first refresh-rate settle after boot must not
|
||||
// trigger a mode-family reinit, only a real in-game 50<->60 switch should.
|
||||
@@ -183,10 +186,10 @@ void retro_get_system_av_info(retro_system_av_info* info)
|
||||
// Fixed max (>= any runtime height) so base can grow via SET_GEOMETRY alone.
|
||||
int max_height = base_height > 576 ? base_height : 576;
|
||||
|
||||
info->geometry.base_width = EFB_WIDTH * efbScale;
|
||||
info->geometry.base_width = Libretro::Video::GetAdjustedBaseWidth() * efbScale;
|
||||
info->geometry.base_height = base_height * efbScale;
|
||||
|
||||
info->geometry.max_width = EFB_WIDTH * efbScale;
|
||||
info->geometry.max_width = Libretro::Video::GetAdjustedBaseWidth() * efbScale;
|
||||
info->geometry.max_height = max_height * efbScale;
|
||||
|
||||
if (g_widescreen)
|
||||
@@ -196,6 +199,11 @@ void retro_get_system_av_info(retro_system_av_info* info)
|
||||
|
||||
info->geometry.aspect_ratio = Libretro::widescreen ? 16.0 / 9.0 : 4.0 / 3.0;
|
||||
|
||||
// The packed frame holds both eyes side by side
|
||||
Libretro::vr_frame = Libretro::VR::IsEnabled();
|
||||
if (Libretro::vr_frame)
|
||||
info->geometry.aspect_ratio *= 2.0;
|
||||
|
||||
Core::System& system = Core::System::GetInstance();
|
||||
double fps = system.GetVideoInterface().GetTargetRefreshRate();
|
||||
if (fps <= 0.0)
|
||||
@@ -324,7 +332,17 @@ void retro_run(void)
|
||||
->SetSystemFrameBuffer((GLuint)Libretro::Video::hw_render.get_current_framebuffer());
|
||||
}
|
||||
|
||||
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_settings::EFB_SCALE))
|
||||
// Turning VR on or off doubles or halves the frame width, so resize it as for EFB scale.
|
||||
bool vr_resized = false;
|
||||
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_settings::VR_MODE))
|
||||
{
|
||||
const bool vr_enabled = Libretro::VR::IsEnabled();
|
||||
if (vr_enabled || Libretro::vr_frame)
|
||||
Libretro::VR::ApplyConfig();
|
||||
vr_resized = vr_enabled != Libretro::vr_frame;
|
||||
}
|
||||
|
||||
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_settings::EFB_SCALE) || vr_resized)
|
||||
{
|
||||
g_Config.iEFBScale = Libretro::Options::GetCached<int>(
|
||||
Libretro::Options::gfx_settings::EFB_SCALE);
|
||||
@@ -410,13 +428,19 @@ void retro_run(void)
|
||||
Libretro::Input::ResetControllers(flags);
|
||||
|
||||
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_settings::ASPECT_RATIO))
|
||||
Config::SetCurrent(
|
||||
Config::GFX_ASPECT_RATIO, static_cast<AspectMode>(
|
||||
Libretro::GetOption<int>(
|
||||
Libretro::Options::gfx_settings::ASPECT_RATIO, static_cast<int>(AspectMode::Stretch)
|
||||
{
|
||||
// VR sets the aspect ratio together with its stereoscopy settings
|
||||
if (Libretro::VR::IsEnabled())
|
||||
Libretro::VR::ApplyConfig();
|
||||
else
|
||||
Config::SetCurrent(
|
||||
Config::GFX_ASPECT_RATIO, static_cast<AspectMode>(
|
||||
Libretro::GetOption<int>(
|
||||
Libretro::Options::gfx_settings::ASPECT_RATIO, static_cast<int>(AspectMode::Stretch)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if (Libretro::Options::IsUpdated(Libretro::Options::gfx_hacks::EFB_TO_TEXTURE))
|
||||
g_Config.bSkipEFBCopyToRam = Libretro::Options::GetCached<bool>(Libretro::Options::gfx_hacks::EFB_TO_TEXTURE, true);
|
||||
@@ -485,6 +509,8 @@ void retro_run(void)
|
||||
if (Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED))
|
||||
Libretro::Input::BluetoothPassthroughBind();
|
||||
|
||||
Libretro::VR::Update(Libretro::widescreen ? 16.0f / 9.0f : 4.0f / 3.0f);
|
||||
|
||||
RETRO_PERFORMANCE_INIT(dolphin_main_func);
|
||||
RETRO_PERFORMANCE_START(dolphin_main_func);
|
||||
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
#include "DolphinLibretro/VR.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Matrix.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "DolphinLibretro/Common/Globals.h"
|
||||
#include "DolphinLibretro/Common/Options.h"
|
||||
#include "DolphinLibretro/Input.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
#include "VideoCommon/VertexShaderManager.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
namespace Libretro
|
||||
{
|
||||
namespace VR
|
||||
{
|
||||
static bool s_views_sent = false;
|
||||
// heading that counts as straight ahead for the game camera
|
||||
static bool s_have_forward = false;
|
||||
static Common::Quaternion s_forward = Common::Quaternion::Identity();
|
||||
// The frontend showed both eyes this session, then stopped):
|
||||
// stereoscopy is off until it shows both again, so the GPU doesn't draw a second eye.
|
||||
static bool s_frontend_stereo_seen = false;
|
||||
static bool s_frontend_flat = false;
|
||||
// The right controller as player 1's Wiimote: its last velocity, for acceleration, and the
|
||||
// heading the cursor is measured from.
|
||||
static bool s_have_controller_velocity = false;
|
||||
static Common::Vec3 s_controller_velocity;
|
||||
static std::chrono::steady_clock::time_point s_controller_time;
|
||||
static bool s_have_point_forward = false;
|
||||
static Common::Quaternion s_point_forward = Common::Quaternion::Identity();
|
||||
|
||||
static Common::Quaternion ToQuaternion(const float* xyzw)
|
||||
{
|
||||
return Common::Quaternion(xyzw[3], xyzw[0], xyzw[1], xyzw[2]).Normalized();
|
||||
}
|
||||
|
||||
// A rotation about +Y by the heading of q's forward (-Z) axis, leaving out pitch and roll.
|
||||
static Common::Quaternion Heading(const Common::Quaternion& q)
|
||||
{
|
||||
const Common::Vec4& v = q.data; // x, y, z, w
|
||||
const float yaw =
|
||||
std::atan2(2.0f * (v.x * v.z + v.w * v.y), 1.0f - 2.0f * (v.x * v.x + v.y * v.y));
|
||||
return Common::Quaternion(std::cos(yaw * 0.5f), 0.0f, std::sin(yaw * 0.5f), 0.0f);
|
||||
}
|
||||
|
||||
Mode GetMode()
|
||||
{
|
||||
const std::string mode = Options::GetCached<std::string>(Options::gfx_settings::VR_MODE);
|
||||
if (mode == "stereo")
|
||||
return Mode::Stereo;
|
||||
if (mode == "immersive")
|
||||
return Mode::Immersive;
|
||||
return Mode::Off;
|
||||
}
|
||||
|
||||
bool IsEnabled()
|
||||
{
|
||||
return GetMode() != Mode::Off;
|
||||
}
|
||||
|
||||
void ApplyConfig()
|
||||
{
|
||||
const Mode mode = GetMode();
|
||||
const bool enabled = mode != Mode::Off;
|
||||
|
||||
// The headset's field of view must cover each eye's whole half of the frame.
|
||||
const AspectMode aspect = mode == Mode::Immersive ?
|
||||
AspectMode::Stretch :
|
||||
static_cast<AspectMode>(Libretro::GetOption<int>(
|
||||
Options::gfx_settings::ASPECT_RATIO, static_cast<int>(AspectMode::Stretch)));
|
||||
Config::SetCurrent(Config::GFX_ASPECT_RATIO, aspect);
|
||||
Config::SetCurrent(Config::GFX_STEREO_MODE,
|
||||
enabled && !s_frontend_flat ? StereoMode::SideBySide : StereoMode::Off);
|
||||
Config::SetCurrent(Config::GFX_STEREO_PER_EYE_RESOLUTION_FULL,
|
||||
enabled && aspect != AspectMode::Stretch);
|
||||
}
|
||||
|
||||
static void DisableCamera()
|
||||
{
|
||||
// Take a new forward heading when the camera is next used
|
||||
s_have_forward = false;
|
||||
if (g_libretro_vr_camera.enabled)
|
||||
{
|
||||
g_libretro_vr_camera.enabled = false;
|
||||
g_libretro_vr_camera.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void ClearViews()
|
||||
{
|
||||
if (!s_views_sent)
|
||||
return;
|
||||
|
||||
retro_video_views views{};
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_VIDEO_VIEWS, &views);
|
||||
s_views_sent = false;
|
||||
}
|
||||
|
||||
static bool UpdateCamera(const retro_vr_frame_state& state)
|
||||
{
|
||||
const float* left = state.eyes[RETRO_VR_EYE_LEFT].fov_tan;
|
||||
const float* right = state.eyes[RETRO_VR_EYE_RIGHT].fov_tan;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (!(left[i] > 0.0f) || !(right[i] > 0.0f))
|
||||
return false;
|
||||
}
|
||||
|
||||
// fov_tan is left, right, up, down. Dolphin's projection maps view-space x / -z in [-l, r] to
|
||||
// [-1, 1] with scale 2 / (l + r) and offset (r - l) / (l + r); likewise y with up and down.
|
||||
const auto offset = [](float neg, float pos) { return (pos - neg) / (neg + pos); };
|
||||
const float left_offset_x = offset(left[0], left[1]);
|
||||
const float right_offset_x = offset(right[0], right[1]);
|
||||
|
||||
LibretroVRCamera& camera = g_libretro_vr_camera;
|
||||
camera.scale_x = 4.0f / (left[0] + left[1] + right[0] + right[1]);
|
||||
camera.offset_x = (left_offset_x + right_offset_x) * 0.5f;
|
||||
camera.eye_offset_x = (right_offset_x - left_offset_x) * 0.5f;
|
||||
camera.scale_y = 4.0f / (left[2] + left[3] + right[2] + right[3]);
|
||||
camera.offset_y = (offset(left[3], left[2]) + offset(right[3], right[2])) * 0.5f;
|
||||
|
||||
const Common::Quaternion head = ToQuaternion(state.eyes[RETRO_VR_EYE_LEFT].orientation);
|
||||
|
||||
if (!s_have_forward || (state.flags & RETRO_VR_FRAME_RECENTERED))
|
||||
{
|
||||
s_forward = Heading(head);
|
||||
s_have_forward = true;
|
||||
}
|
||||
const Common::Quaternion relative = s_forward.Conjugate() * head;
|
||||
camera.view = Common::Matrix44::FromQuaternion(relative.Conjugate());
|
||||
|
||||
camera.enabled = true;
|
||||
camera.dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void UpdateVRMotion()
|
||||
{
|
||||
if (!Input::UsesVRMotion(0))
|
||||
return;
|
||||
|
||||
constexpr float G = 9.80665f;
|
||||
float accel[3] = {0.0f, 0.0f, G}; // at rest, level
|
||||
float gyro[3] = {};
|
||||
float point[2] = {};
|
||||
|
||||
retro_vr_controller_poses poses{};
|
||||
const retro_vr_hand_state& hand = poses.hands[RETRO_VR_HAND_RIGHT];
|
||||
const retro_vr_pose& aim = hand.aim;
|
||||
if (!environ_cb(RETRO_ENVIRONMENT_GET_VR_CONTROLLER_POSES, &poses) ||
|
||||
!(hand.flags & RETRO_VR_HAND_ACTIVE) || !(aim.flags & RETRO_VR_POSE_ORIENTATION_VALID))
|
||||
{
|
||||
s_have_controller_velocity = false;
|
||||
Input::SetVRMotion(0, accel, gyro, point);
|
||||
return;
|
||||
}
|
||||
|
||||
const Common::Quaternion remote = ToQuaternion(aim.orientation);
|
||||
const Common::Quaternion to_remote = remote.Conjugate();
|
||||
|
||||
Common::Vec3 acceleration(0.0f, G, 0.0f);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (aim.flags & RETRO_VR_POSE_LINEAR_VELOCITY_VALID)
|
||||
{
|
||||
const Common::Vec3 velocity(aim.linear_velocity[0], aim.linear_velocity[1],
|
||||
aim.linear_velocity[2]);
|
||||
const float dt = std::chrono::duration<float>(now - s_controller_time).count();
|
||||
if (s_have_controller_velocity && dt > 1.0f / 500.0f && dt < 0.1f)
|
||||
acceleration += (velocity - s_controller_velocity) / dt;
|
||||
s_controller_velocity = velocity;
|
||||
s_have_controller_velocity = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_have_controller_velocity = false;
|
||||
}
|
||||
s_controller_time = now;
|
||||
|
||||
Common::Vec3 rotation;
|
||||
if (aim.flags & RETRO_VR_POSE_ANGULAR_VELOCITY_VALID)
|
||||
rotation = Common::Vec3(aim.angular_velocity[0], aim.angular_velocity[1],
|
||||
aim.angular_velocity[2]);
|
||||
|
||||
// Into the remote's axes (+X right, +Y up, +Z back), then the Wiimote's (+X left, +Y back,
|
||||
// +Z up): a rotation, so rates map the same way as accelerations.
|
||||
const Common::Vec3 a = to_remote * acceleration;
|
||||
const Common::Vec3 w = to_remote * rotation;
|
||||
accel[0] = -a.x;
|
||||
accel[1] = a.z;
|
||||
accel[2] = a.y;
|
||||
gyro[0] = -w.x;
|
||||
gyro[1] = w.z;
|
||||
gyro[2] = w.y;
|
||||
|
||||
// The cursor is where the remoting is pointing.
|
||||
retro_vr_frame_state frame_state{};
|
||||
if (environ_cb(RETRO_ENVIRONMENT_GET_VR_FRAME_STATE, &frame_state) &&
|
||||
(frame_state.flags & RETRO_VR_FRAME_RECENTERED))
|
||||
{
|
||||
s_have_point_forward = false;
|
||||
}
|
||||
retro_vr_head_pose head{};
|
||||
if (!s_have_point_forward &&
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_VR_HEAD_POSE, &head) &&
|
||||
(head.flags & RETRO_VR_HEAD_POSE_ORIENTATION_VALID))
|
||||
{
|
||||
s_point_forward = Heading(ToQuaternion(head.orientation));
|
||||
s_have_point_forward = true;
|
||||
}
|
||||
const Common::Vec3 direction = s_point_forward.Conjugate() * (remote * Common::Vec3(0, 0, -1));
|
||||
const float yaw = std::atan2(direction.x, -direction.z);
|
||||
const float pitch =
|
||||
std::atan2(direction.y, std::sqrt(direction.x * direction.x + direction.z * direction.z));
|
||||
constexpr float DEGREES = 180.0f / 3.14159265f;
|
||||
const float half_yaw =
|
||||
std::max(1, Options::GetCached<int>(Options::wiimote::IR_YAW, 25)) * 0.5f;
|
||||
const float half_pitch =
|
||||
std::max(1, Options::GetCached<int>(Options::wiimote::IR_PITCH, 25)) * 0.5f;
|
||||
point[0] = std::clamp(yaw * DEGREES / half_yaw, -1.0f, 1.0f);
|
||||
point[1] = std::clamp(pitch * DEGREES / half_pitch, -1.0f, 1.0f);
|
||||
|
||||
Input::SetVRMotion(0, accel, gyro, point);
|
||||
}
|
||||
|
||||
void Update(float eye_aspect_ratio)
|
||||
{
|
||||
UpdateVRMotion();
|
||||
|
||||
const Mode mode = GetMode();
|
||||
if (mode == Mode::Off)
|
||||
{
|
||||
DisableCamera();
|
||||
ClearViews();
|
||||
s_frontend_stereo_seen = false;
|
||||
s_frontend_flat = false;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned status = 0;
|
||||
if (!environ_cb(RETRO_ENVIRONMENT_GET_VIDEO_VIEWS_STATUS, &status))
|
||||
status = 0;
|
||||
|
||||
const bool frontend_stereo = (status & RETRO_VIDEO_VIEWS_STATUS_STEREO) != 0;
|
||||
s_frontend_stereo_seen |= frontend_stereo;
|
||||
const bool frontend_flat = s_frontend_stereo_seen && !frontend_stereo;
|
||||
if (frontend_flat != s_frontend_flat)
|
||||
{
|
||||
s_frontend_flat = frontend_flat;
|
||||
INFO_LOG_FMT(VIDEO, "VR: frontend {} both eyes, stereoscopy {}",
|
||||
frontend_flat ? "stopped showing" : "shows", frontend_flat ? "off" : "on");
|
||||
Config::SetCurrent(Config::GFX_STEREO_MODE,
|
||||
frontend_flat ? StereoMode::Off : StereoMode::SideBySide);
|
||||
}
|
||||
|
||||
const unsigned width = g_presenter ? g_presenter->GetBackbufferWidth() : 0;
|
||||
const unsigned height = g_presenter ? g_presenter->GetBackbufferHeight() : 0;
|
||||
|
||||
if (!(status & RETRO_VIDEO_VIEWS_STATUS_PRESENTS) || width < 2 || height == 0)
|
||||
{
|
||||
DisableCamera();
|
||||
ClearViews();
|
||||
return;
|
||||
}
|
||||
|
||||
const bool stereo = g_ActiveConfig.stereo_mode == StereoMode::SideBySide;
|
||||
const unsigned eye_width = stereo ? width / 2 : width;
|
||||
|
||||
retro_video_view views[2] = {};
|
||||
retro_video_views map{};
|
||||
map.views = views;
|
||||
map.reference_space = RETRO_VR_REFERENCE_SPACE_LOCAL;
|
||||
|
||||
if (stereo && (status & RETRO_VIDEO_VIEWS_STATUS_STEREO))
|
||||
{
|
||||
views[0] = {0, 0, eye_width, height, 0, RETRO_VIDEO_VIEW_EYE_LEFT, eye_aspect_ratio};
|
||||
views[1] = {eye_width, 0, eye_width, height, 0, RETRO_VIDEO_VIEW_EYE_RIGHT, eye_aspect_ratio};
|
||||
map.num_views = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
views[0] = {0, 0, eye_width, height, 0, RETRO_VIDEO_VIEW_EYE_NONE, eye_aspect_ratio};
|
||||
map.num_views = 1;
|
||||
}
|
||||
|
||||
g_libretro_vr_camera.display_aspect = eye_aspect_ratio;
|
||||
|
||||
retro_vr_frame_state frame_state{};
|
||||
if (mode == Mode::Immersive && map.num_views == 2 && (status & RETRO_VIDEO_VIEWS_STATUS_HMD) &&
|
||||
environ_cb(RETRO_ENVIRONMENT_GET_VR_FRAME_STATE, &frame_state) && UpdateCamera(frame_state))
|
||||
{
|
||||
map.flags = RETRO_VIDEO_VIEWS_FLAG_TRACKED;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisableCamera();
|
||||
}
|
||||
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_VIDEO_VIEWS, &map);
|
||||
s_views_sent = true;
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
{
|
||||
DisableCamera();
|
||||
g_libretro_vr_camera.applied = false;
|
||||
g_libretro_vr_camera.game_tan_x = 0.0f;
|
||||
g_libretro_vr_camera.game_tan_y = 0.0f;
|
||||
s_views_sent = false;
|
||||
s_frontend_stereo_seen = false;
|
||||
s_frontend_flat = false;
|
||||
s_have_controller_velocity = false;
|
||||
s_have_point_forward = false;
|
||||
}
|
||||
} // namespace VR
|
||||
} // namespace Libretro
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace Libretro
|
||||
{
|
||||
namespace VR
|
||||
{
|
||||
enum class Mode
|
||||
{
|
||||
Off,
|
||||
// Both eyes side by side, shown by the frontend on a virtual screen.
|
||||
Stereo,
|
||||
// As Stereo, with the game camera turned by the headset and drawn at its field of view.
|
||||
Immersive,
|
||||
};
|
||||
|
||||
Mode GetMode();
|
||||
bool IsEnabled();
|
||||
|
||||
// Sets Dolphin's stereoscopy (and, when immersive, aspect ratio) settings for the current mode.
|
||||
void ApplyConfig();
|
||||
|
||||
// Called every retro_run() before the frame is emulated: tells the frontend where each eye is in
|
||||
// the frame and, in immersive mode, points the game camera along the headset's pose.
|
||||
void Update(float eye_aspect_ratio);
|
||||
|
||||
void Shutdown();
|
||||
} // namespace VR
|
||||
} // namespace Libretro
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "Core/Host.h"
|
||||
#include "Core/System.h"
|
||||
#include "DolphinLibretro/Common/Options.h"
|
||||
#include "DolphinLibretro/VR.h"
|
||||
#include "DolphinLibretro/VideoContexts/ContextStatus.h"
|
||||
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
@@ -71,6 +72,12 @@ static Common::DynamicLibrary d3d11_library;
|
||||
static Common::DynamicLibrary d3d12_library;
|
||||
#endif
|
||||
|
||||
int GetAdjustedBaseWidth()
|
||||
{
|
||||
// VR packs both eyes side by side, each at the full width.
|
||||
return VR::IsEnabled() ? EFB_WIDTH * 2 : EFB_WIDTH;
|
||||
}
|
||||
|
||||
int GetAdjustedBaseHeight()
|
||||
{
|
||||
const bool crop_overscan = Libretro::Options::GetCached<bool>(
|
||||
@@ -288,7 +295,7 @@ void ContextReset(void)
|
||||
|
||||
int efbScale = Libretro::Options::GetCached<int>(
|
||||
Libretro::Options::gfx_settings::EFB_SCALE, 1);
|
||||
Vk::SetSurfaceSize(EFB_WIDTH * efbScale,
|
||||
Vk::SetSurfaceSize(GetAdjustedBaseWidth() * efbScale,
|
||||
GetAdjustedBaseHeight() * efbScale);
|
||||
}
|
||||
#endif
|
||||
@@ -360,7 +367,7 @@ void ContextReset(void)
|
||||
UpdateActiveConfig();
|
||||
|
||||
std::unique_ptr<DX11SwapChain> swap_chain = std::make_unique<DX11SwapChain>(
|
||||
wsi, EFB_WIDTH * efbScale, GetAdjustedBaseHeight() * efbScale,
|
||||
wsi, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale,
|
||||
nullptr, nullptr);
|
||||
|
||||
auto gfx = std::make_unique<DX11::Gfx>(std::move(swap_chain), wsi.render_surface_scale);
|
||||
@@ -437,7 +444,7 @@ void ContextReset(void)
|
||||
UpdateActiveConfig();
|
||||
|
||||
auto swap_chain = std::make_unique<DX12SwapChain>(
|
||||
wsi, EFB_WIDTH * efbScale, GetAdjustedBaseHeight() * efbScale, d3d12);
|
||||
wsi, GetAdjustedBaseWidth() * efbScale, GetAdjustedBaseHeight() * efbScale, d3d12);
|
||||
|
||||
if (!swap_chain->Initialize())
|
||||
{
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Libretro
|
||||
{
|
||||
namespace Video
|
||||
{
|
||||
int GetAdjustedBaseWidth();
|
||||
int GetAdjustedBaseHeight();
|
||||
void Init(void);
|
||||
bool Video_InitializeBackend();
|
||||
|
||||
@@ -318,7 +318,7 @@ void* GLContextLR::GetFuncAddress(const std::string& name)
|
||||
bool GLContextLR::Initialize(const WindowSystemInfo& wsi, bool stereo, bool core)
|
||||
{
|
||||
const int efbScale = Libretro::Options::GetCached<int>(Libretro::Options::gfx_settings::EFB_SCALE, 1);
|
||||
m_backbuffer_width = EFB_WIDTH * efbScale;
|
||||
m_backbuffer_width = Libretro::Video::GetAdjustedBaseWidth() * efbScale;
|
||||
m_backbuffer_height = Libretro::Video::GetAdjustedBaseHeight() * efbScale;
|
||||
|
||||
switch (Libretro::Video::hw_render.context_type)
|
||||
|
||||
@@ -418,7 +418,9 @@ static VKAPI_ATTR VkResult VKAPI_CALL vkQueuePresentKHR(VkQueue queue,
|
||||
{
|
||||
if (AbstractTexture* efb_tex = g_framebuffer_manager->GetEFBColorTexture())
|
||||
{
|
||||
out_w = efb_tex->GetWidth();
|
||||
// VR packs both eyes side by side, so the frame is wider than the EFB by the same
|
||||
// factor as the swapchain.
|
||||
out_w = efb_tex->GetWidth() * Libretro::Video::GetAdjustedBaseWidth() / EFB_WIDTH;
|
||||
out_h = efb_tex->GetHeight();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +272,11 @@ std::string GeneratePassthroughGeometryShader(u32 num_tex, u32 num_colors)
|
||||
// We have to explicitly unroll this loop otherwise the GL compiler gets cranky.
|
||||
for (u32 v = 0; v < 3; v++)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
// Outputs are undefined after EmitVertex()
|
||||
if (v > 0)
|
||||
code.Write(" gl_Layer = j;\n");
|
||||
#endif
|
||||
code.Write(" gl_Position = gl_in[{}].gl_Position;\n", v);
|
||||
for (u32 i = 0; i < num_tex; i++)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
#include "VideoCommon/RenderState.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
#ifdef __LIBRETRO__
|
||||
#include <cmath>
|
||||
|
||||
#include "VideoCommon/VertexShaderManager.h"
|
||||
#endif
|
||||
|
||||
static constexpr int LINE_PT_TEX_OFFSETS[8] = {0, 16, 8, 4, 2, 1, 1, 1};
|
||||
|
||||
@@ -63,6 +68,31 @@ void GeometryShaderManager::SetConstants(PrimitiveType prim)
|
||||
|
||||
constants.stereoparams[2] = g_ActiveConfig.stereo_convergence;
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (g_libretro_vr_camera.applied)
|
||||
{
|
||||
// Move each eye onto its own headset frustum as well as separating it. The shader adds
|
||||
// hoffset * (w - convergence), so scaling the convergence keeps the separation's constant
|
||||
// term while hoffset gains the frustum offset.
|
||||
const float offset = g_ActiveConfig.stereo_depth;
|
||||
const float eye_offset = g_libretro_vr_camera.eye_offset_x;
|
||||
const float left = eye_offset - offset;
|
||||
constants.stereoparams[0] = left;
|
||||
constants.stereoparams[1] = -left;
|
||||
constants.stereoparams[2] =
|
||||
std::abs(left) > 1e-6f ? g_ActiveConfig.stereo_convergence * -offset / left : 0.0f;
|
||||
}
|
||||
else if (g_libretro_vr_camera.screen_applied)
|
||||
{
|
||||
// 2D has w = 1, so this moves each eye's copy of the screen onto its own frustum. Both
|
||||
// eyes then see it in the same direction.
|
||||
const float eye_offset = g_libretro_vr_camera.eye_offset_x;
|
||||
constants.stereoparams[0] = eye_offset;
|
||||
constants.stereoparams[1] = -eye_offset;
|
||||
constants.stereoparams[2] = 0.0f;
|
||||
}
|
||||
#endif
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -559,6 +559,10 @@ void VertexManagerBase::Flush()
|
||||
CalculateNormals(VertexLoaderManager::GetCurrentVertexFormat());
|
||||
// Calculate ZSlope for zfreeze
|
||||
const auto used_textures = UsedTextures();
|
||||
#ifdef __LIBRETRO__
|
||||
bool vr_textured = false;
|
||||
bool vr_samples_copy = false;
|
||||
#endif
|
||||
std::vector<std::string> texture_names;
|
||||
Common::SmallVector<u32, 8> texture_units;
|
||||
std::array<SamplerState, 8> samplers;
|
||||
@@ -571,6 +575,10 @@ void VertexManagerBase::Flush()
|
||||
const auto cache_entry = g_texture_cache->Load(i);
|
||||
if (!cache_entry)
|
||||
continue;
|
||||
#ifdef __LIBRETRO__
|
||||
vr_textured = true;
|
||||
vr_samples_copy |= cache_entry->IsCopy();
|
||||
#endif
|
||||
const float custom_tex_scale = cache_entry->GetWidth() / float(cache_entry->native_width);
|
||||
samplers[i] = TextureCacheBase::GetSamplerState(
|
||||
i, custom_tex_scale, cache_entry->is_custom_tex, cache_entry->has_arbitrary_mips);
|
||||
@@ -583,6 +591,10 @@ void VertexManagerBase::Flush()
|
||||
const auto cache_entry = g_texture_cache->Load(i);
|
||||
if (cache_entry)
|
||||
{
|
||||
#ifdef __LIBRETRO__
|
||||
vr_textured = true;
|
||||
vr_samples_copy |= cache_entry->IsCopy();
|
||||
#endif
|
||||
if (!Common::Contains(texture_names, cache_entry->texture_info_name))
|
||||
{
|
||||
texture_names.push_back(cache_entry->texture_info_name);
|
||||
@@ -596,6 +608,10 @@ void VertexManagerBase::Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef __LIBRETRO__
|
||||
g_libretro_vr_camera.draw_textured = vr_textured;
|
||||
g_libretro_vr_camera.draw_samples_copy = vr_samples_copy;
|
||||
#endif
|
||||
vertex_shader_manager.SetConstants(texture_names, xf_state_manager);
|
||||
if (!bpmem.genMode.zfreeze)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,44 @@
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
#include "VideoCommon/XFStateManager.h"
|
||||
#ifdef __LIBRETRO__
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
|
||||
LibretroVRCamera g_libretro_vr_camera;
|
||||
|
||||
// The headset only replaces what is drawn to the screen. Render-to-texture passes such as shadows
|
||||
// and reflections draw into smaller viewports and keep the game's projection.
|
||||
static bool IsLibretroVRScreenViewport()
|
||||
{
|
||||
return g_libretro_vr_camera.enabled &&
|
||||
std::abs(2.0f * xfmem.viewport.wd) >= EFB_WIDTH * 0.75f &&
|
||||
std::abs(2.0f * xfmem.viewport.ht) >= EFB_HEIGHT * 0.5f;
|
||||
}
|
||||
|
||||
static bool UseLibretroVRCamera()
|
||||
{
|
||||
return IsLibretroVRScreenViewport() && xfmem.projection.type == ProjectionType::Perspective;
|
||||
}
|
||||
|
||||
// Textured 2D such as menus and HUD. Untextured 2D (fades) and 2D drawn from a copy of the frame
|
||||
// (bloom, blur) cover the scene, which fills the headset's view, so they stay full size.
|
||||
static bool UseLibretroVRScreen()
|
||||
{
|
||||
return IsLibretroVRScreenViewport() &&
|
||||
xfmem.projection.type == ProjectionType::Orthographic &&
|
||||
g_libretro_vr_camera.draw_textured && !g_libretro_vr_camera.draw_samples_copy;
|
||||
}
|
||||
|
||||
static bool LibretroVRCameraChanged()
|
||||
{
|
||||
const bool camera = UseLibretroVRCamera();
|
||||
const bool screen = UseLibretroVRScreen();
|
||||
return camera != g_libretro_vr_camera.applied ||
|
||||
screen != g_libretro_vr_camera.screen_applied ||
|
||||
((camera || screen) && g_libretro_vr_camera.dirty);
|
||||
}
|
||||
#endif
|
||||
|
||||
void VertexShaderManager::Init()
|
||||
{
|
||||
@@ -109,8 +147,52 @@ Common::Matrix44 VertexShaderManager::LoadProjectionMatrix()
|
||||
PRIM_LOG("Projection: {} {} {} {} {} {}", rawProjection[0], rawProjection[1], rawProjection[2],
|
||||
rawProjection[3], rawProjection[4], rawProjection[5]);
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
const bool use_vr_camera = UseLibretroVRCamera();
|
||||
const bool use_vr_screen = UseLibretroVRScreen();
|
||||
LibretroVRCamera& vr = g_libretro_vr_camera;
|
||||
if (use_vr_camera)
|
||||
{
|
||||
// Remember how wide the game's camera sees, for the 2D screen.
|
||||
vr.game_tan_x = 1.0f / std::abs(m_projection_matrix[0]);
|
||||
vr.game_tan_y = 1.0f / std::abs(m_projection_matrix[5]);
|
||||
|
||||
// Draw with the headset's field of view, keeping the sign of a mirrored projection.
|
||||
const float sign_x = rawProjection[0] < 0.0f ? -1.0f : 1.0f;
|
||||
const float sign_y = rawProjection[2] < 0.0f ? -1.0f : 1.0f;
|
||||
m_projection_matrix[0] = vr.scale_x * sign_x;
|
||||
m_projection_matrix[2] = vr.offset_x * sign_x;
|
||||
m_projection_matrix[5] = vr.scale_y * sign_y;
|
||||
m_projection_matrix[6] = vr.offset_y * sign_y;
|
||||
vr.dirty = false;
|
||||
}
|
||||
else if (use_vr_screen)
|
||||
{
|
||||
// Full-screen 2D
|
||||
const float tan_x = vr.game_tan_x > 0.0f ? vr.game_tan_x : 0.58f;
|
||||
const float tan_y = vr.game_tan_y > 0.0f ? vr.game_tan_y : 0.58f / vr.display_aspect;
|
||||
const float scale_x = tan_x * vr.scale_x;
|
||||
const float scale_y = tan_y * vr.scale_y;
|
||||
m_projection_matrix[0] *= scale_x;
|
||||
m_projection_matrix[3] = m_projection_matrix[3] * scale_x - vr.offset_x;
|
||||
m_projection_matrix[5] *= scale_y;
|
||||
m_projection_matrix[7] = m_projection_matrix[7] * scale_y - vr.offset_y;
|
||||
vr.dirty = false;
|
||||
}
|
||||
// The per-eye stereo offsets depend on whether this projection uses the headset.
|
||||
if (use_vr_camera || use_vr_screen || vr.applied || vr.screen_applied)
|
||||
Core::System::GetInstance().GetGeometryShaderManager().SetProjectionChanged();
|
||||
vr.applied = use_vr_camera;
|
||||
vr.screen_applied = use_vr_screen;
|
||||
#endif
|
||||
|
||||
auto corrected_matrix = Common::Matrix44::FromArray(m_projection_matrix);
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
if (use_vr_camera)
|
||||
corrected_matrix *= g_libretro_vr_camera.view;
|
||||
#endif
|
||||
|
||||
if (g_freelook_camera.IsActive() && xfmem.projection.type == ProjectionType::Perspective)
|
||||
corrected_matrix *= g_freelook_camera.GetView();
|
||||
|
||||
@@ -121,7 +203,11 @@ Common::Matrix44 VertexShaderManager::LoadProjectionMatrix()
|
||||
|
||||
void VertexShaderManager::SetProjectionMatrix(XFStateManager& xf_state_manager)
|
||||
{
|
||||
if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty())
|
||||
if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty()
|
||||
#ifdef __LIBRETRO__
|
||||
|| LibretroVRCameraChanged()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
xf_state_manager.ResetProjection();
|
||||
auto corrected_matrix = LoadProjectionMatrix();
|
||||
@@ -412,7 +498,11 @@ void VertexShaderManager::SetConstants(std::span<const std::string> textures,
|
||||
}
|
||||
|
||||
if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty() ||
|
||||
!projection_actions.empty() || m_projection_graphics_mod_change)
|
||||
!projection_actions.empty() || m_projection_graphics_mod_change
|
||||
#ifdef __LIBRETRO__
|
||||
|| LibretroVRCameraChanged()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
xf_state_manager.ResetProjection();
|
||||
m_projection_graphics_mod_change = !projection_actions.empty();
|
||||
|
||||
@@ -16,6 +16,39 @@ class PointerWrap;
|
||||
struct PortableVertexDeclaration;
|
||||
class XFStateManager;
|
||||
|
||||
#ifdef __LIBRETRO__
|
||||
// Head-tracked camera for the libretro core's immersive VR mode.
|
||||
struct LibretroVRCamera
|
||||
{
|
||||
bool enabled = false;
|
||||
// Set when the values below change, so the projection is reloaded.
|
||||
bool dirty = false;
|
||||
// Whether the projection currently loaded uses the headset camera, or the 2D screen.
|
||||
bool applied = false;
|
||||
bool screen_applied = false;
|
||||
// Head rotation, applied after the game's view like the Free Look camera.
|
||||
Common::Matrix44 view = Common::Matrix44::Identity();
|
||||
// The headset's eye frustums averaged into one centred projection, in Dolphin's layout.
|
||||
float scale_x = 1.0f;
|
||||
float offset_x = 0.0f;
|
||||
float scale_y = 1.0f;
|
||||
float offset_y = 0.0f;
|
||||
// How far the left eye's frustum sits from the centred one, in clip-space x per unit of w.
|
||||
// The right eye's is the negation.
|
||||
float eye_offset_x = 0.0f;
|
||||
// Tangents of the game camera's half field of view, from its last projection; 0 until seen.
|
||||
float game_tan_x = 0.0f;
|
||||
float game_tan_y = 0.0f;
|
||||
// The display's shape, for the 2D screen before the game camera is seen.
|
||||
float display_aspect = 4.0f / 3.0f;
|
||||
// What the draw being flushed samples. 2D drawn from a copy of the frame (bloom, blur, fades
|
||||
// of the scene) must cover the scene exactly, so it is left at full size.
|
||||
bool draw_textured = false;
|
||||
bool draw_samples_copy = false;
|
||||
};
|
||||
extern LibretroVRCamera g_libretro_vr_camera;
|
||||
#endif
|
||||
|
||||
// The non-API dependent parts.
|
||||
class alignas(16) VertexShaderManager
|
||||
{
|
||||
|
||||
@@ -268,6 +268,10 @@ void VideoBackendBase::PopulateBackendInfo(const WindowSystemInfo& wsi)
|
||||
ActivateBackend(Config::Get(Config::MAIN_GFX_BACKEND));
|
||||
g_backend_info.DisplayName = g_video_backend->GetDisplayName();
|
||||
g_video_backend->InitBackendInfo(wsi);
|
||||
#ifdef __LIBRETRO__
|
||||
if (g_backend_info.api_type == APIType::Nothing)
|
||||
return;
|
||||
#endif
|
||||
// We validate the config after initializing the backend info, as system-specific settings
|
||||
// such as anti-aliasing, or the selected adapter may be invalid, and should be checked.
|
||||
g_Config.VerifyValidity();
|
||||
|
||||
Reference in new issue
Block a user