Add OpenXR VR rendering support
This commit is contained in:
parent
d3b482c103
commit
f47d1c0331
23 changed files with 2358 additions and 162 deletions
|
|
@ -4,6 +4,8 @@ BlanKart is a modification of SRB2Kart v2 Indev to make it closer to SRB2Kart ga
|
|||
|
||||
If you're interested in helping out, theres a matrix room located [here](https://matrix.to/#/#blankart:matrix.org)!
|
||||
|
||||
The VR work in this fork is inspired in part by [`kart-public-vr`](https://git.do.srb2.org/chreas/kart-public-vr), an earlier SRB2Kart OpenVR implementation.
|
||||
|
||||
# Notice
|
||||
|
||||
This is still in active development and things are going to change. If you find any bugs besure to report to the [issue tracker](https://codeberg.org/NepDisk/blankart/issues)!
|
||||
|
|
|
|||
|
|
@ -157,6 +157,9 @@ add_executable(BLANKART MACOSX_BUNDLE WIN32
|
|||
h_timers.cpp
|
||||
stun.c
|
||||
lonesha256.c
|
||||
vr/vr_main.c
|
||||
vr/vr_render.c
|
||||
vr/vr_math.c
|
||||
)
|
||||
|
||||
if(("${CMAKE_COMPILER_IS_GNUCC}" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
|
||||
|
|
@ -364,6 +367,13 @@ if(SRB2_CONFIG_ENABLE_DISCORDRPC)
|
|||
target_sources(BLANKART PRIVATE discord.c)
|
||||
endif()
|
||||
|
||||
# VR Support
|
||||
target_compile_definitions(BLANKART PRIVATE -DHAVE_VR)
|
||||
target_link_libraries(BLANKART PRIVATE openxr_loader)
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(BLANKART PRIVATE GL GLX X11)
|
||||
endif()
|
||||
|
||||
set(SRB2_HAVE_THREADS ON)
|
||||
target_compile_definitions(BLANKART PRIVATE -DHAVE_THREADS)
|
||||
|
||||
|
|
|
|||
321
src/d_main.cpp
321
src/d_main.cpp
|
|
@ -56,6 +56,7 @@
|
|||
#include "r_local.h"
|
||||
#include "r_voicepreference.hpp" // Preferences directory
|
||||
#include "s_sound.h"
|
||||
#include "screen.h"
|
||||
#include "st_stuff.h"
|
||||
#include "v_video.h"
|
||||
#include "w_wad.h"
|
||||
|
|
@ -425,12 +426,80 @@ gamestate_t wipegamestate = GS_LEVEL;
|
|||
INT16 wipetypepre = -1;
|
||||
INT16 wipetypepost = -1;
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "vr/vr_main.h"
|
||||
#include "vr/vr_render.h"
|
||||
static void D_Display_Internal(void);
|
||||
|
||||
static void D_Display(void)
|
||||
{
|
||||
static boolean vr_auto_started_menu = false;
|
||||
|
||||
if (vr_started && gamestate == GS_TITLESCREEN && !menustack[0] && !vr_auto_started_menu)
|
||||
{
|
||||
M_StartControlPanel();
|
||||
CONS_Printf("VR: Auto-opened title menu; menustack[0]=%d, gamestate=%d.\n",
|
||||
(INT32)menustack[0], (INT32)gamestate);
|
||||
vr_auto_started_menu = true;
|
||||
}
|
||||
else if (!vr_started || gamestate != GS_TITLESCREEN)
|
||||
vr_auto_started_menu = false;
|
||||
|
||||
const boolean vr_quad_ui = (cv_vruimode.value != 0);
|
||||
|
||||
if (vr_started && VR_BeginFrame())
|
||||
{
|
||||
if (VR_SetEye(0))
|
||||
{
|
||||
D_Display_Internal();
|
||||
VR_ReleaseEye(0);
|
||||
}
|
||||
|
||||
if (VR_SetEye(1))
|
||||
{
|
||||
D_Display_Internal();
|
||||
VR_ReleaseEye(1);
|
||||
}
|
||||
|
||||
if (vr_quad_ui && VR_BindUISwapchain())
|
||||
{
|
||||
D_Display_Internal();
|
||||
VR_BindDefaultFramebuffer();
|
||||
}
|
||||
|
||||
VR_EndFrame();
|
||||
}
|
||||
else
|
||||
{
|
||||
vr_render_pass = VR_PASS_NONE;
|
||||
D_Display_Internal();
|
||||
}
|
||||
}
|
||||
|
||||
static void D_Display_Internal(void)
|
||||
#else
|
||||
static void D_Display(void)
|
||||
#endif
|
||||
{
|
||||
boolean forcerefresh = false;
|
||||
static boolean wipe = false;
|
||||
INT32 wipedefindex = 0;
|
||||
UINT8 i;
|
||||
#ifdef HAVE_VR
|
||||
const boolean vr_quad_ui = (vr_started && cv_vruimode.value != 0);
|
||||
const boolean vr_in_eye_ui = (vr_started && !vr_quad_ui
|
||||
&& (vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT));
|
||||
const boolean vr_draw_3d = (!vr_started || vr_render_pass == VR_PASS_NONE
|
||||
|| vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT);
|
||||
const boolean vr_draw_ui = (!vr_started || vr_render_pass == VR_PASS_NONE || vr_render_pass == VR_PASS_UI
|
||||
|| (!vr_quad_ui && (vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT)));
|
||||
const boolean vr_draw_title = (!vr_started || vr_render_pass != VR_PASS_UI);
|
||||
#else
|
||||
const boolean vr_in_eye_ui = false;
|
||||
const boolean vr_draw_3d = true;
|
||||
const boolean vr_draw_ui = true;
|
||||
const boolean vr_draw_title = true;
|
||||
#endif
|
||||
|
||||
ZoneScoped;
|
||||
|
||||
|
|
@ -539,79 +608,89 @@ static void D_Display(void)
|
|||
}
|
||||
|
||||
// do buffered drawing
|
||||
switch (gamestate)
|
||||
if (vr_draw_ui)
|
||||
{
|
||||
case GS_TITLESCREEN:
|
||||
if (!titlemapinaction || !curbghide) {
|
||||
F_TitleScreenDrawer();
|
||||
break;
|
||||
}
|
||||
/* FALLTHRU */
|
||||
case GS_LEVEL:
|
||||
if (!gametic)
|
||||
break;
|
||||
AM_Drawer();
|
||||
break;
|
||||
#ifdef HAVE_VR
|
||||
if (vr_in_eye_ui)
|
||||
VR_BeginInEyeUI();
|
||||
#endif
|
||||
|
||||
case GS_INTERMISSION:
|
||||
Y_IntermissionDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_VOTING:
|
||||
Y_VoteDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_TIMEATTACK:
|
||||
break;
|
||||
|
||||
case GS_INTRO:
|
||||
F_IntroDrawer();
|
||||
if (wipegamestate == (gamestate_t)-1)
|
||||
switch (gamestate)
|
||||
{
|
||||
wipe = true;
|
||||
wipedefindex = gamestate; // wipe_xxx_toblack
|
||||
}
|
||||
break;
|
||||
case GS_TITLESCREEN:
|
||||
if (!vr_draw_title)
|
||||
break;
|
||||
if (!titlemapinaction || !curbghide) {
|
||||
F_TitleScreenDrawer();
|
||||
break;
|
||||
}
|
||||
/* FALLTHRU */
|
||||
case GS_LEVEL:
|
||||
if (!gametic)
|
||||
break;
|
||||
AM_Drawer();
|
||||
break;
|
||||
|
||||
case GS_CUTSCENE:
|
||||
F_CutsceneDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_EVALUATION:
|
||||
F_GameEvaluationDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_CREDITS:
|
||||
F_CreditDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_BLANCREDITS:
|
||||
F_BlanCreditDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_SECRETCREDITS:
|
||||
F_SecretCreditsDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_WAITINGPLAYERS:
|
||||
// The clientconnect drawer is independent...
|
||||
if (netgame)
|
||||
{
|
||||
// I don't think HOM from nothing drawing is independent...
|
||||
F_WaitingPlayersDrawer();
|
||||
case GS_INTERMISSION:
|
||||
Y_IntermissionDrawer();
|
||||
HU_Drawer();
|
||||
}
|
||||
case GS_DEDICATEDSERVER:
|
||||
case GS_NULL:
|
||||
case FORCEWIPE:
|
||||
break;
|
||||
break;
|
||||
|
||||
case GS_VOTING:
|
||||
Y_VoteDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_TIMEATTACK:
|
||||
break;
|
||||
|
||||
case GS_INTRO:
|
||||
F_IntroDrawer();
|
||||
if (wipegamestate == (gamestate_t)-1)
|
||||
{
|
||||
wipe = true;
|
||||
wipedefindex = gamestate; // wipe_xxx_toblack
|
||||
}
|
||||
break;
|
||||
|
||||
case GS_CUTSCENE:
|
||||
F_CutsceneDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_EVALUATION:
|
||||
F_GameEvaluationDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_CREDITS:
|
||||
F_CreditDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_BLANCREDITS:
|
||||
F_BlanCreditDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_SECRETCREDITS:
|
||||
F_SecretCreditsDrawer();
|
||||
HU_Drawer();
|
||||
break;
|
||||
|
||||
case GS_WAITINGPLAYERS:
|
||||
// The clientconnect drawer is independent...
|
||||
if (netgame)
|
||||
{
|
||||
// I don't think HOM from nothing drawing is independent...
|
||||
F_WaitingPlayersDrawer();
|
||||
HU_Drawer();
|
||||
}
|
||||
case GS_DEDICATEDSERVER:
|
||||
case GS_NULL:
|
||||
case FORCEWIPE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// STUPID race condition...
|
||||
|
|
@ -620,22 +699,31 @@ static void D_Display(void)
|
|||
|
||||
// clean up border stuff
|
||||
// see if the border needs to be initially drawn
|
||||
if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
|
||||
{
|
||||
|
||||
D_RenderLevel();
|
||||
|
||||
ps_uitime = I_GetPreciseTime();
|
||||
|
||||
if (gamestate == GS_LEVEL)
|
||||
if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
|
||||
{
|
||||
|
||||
if (vr_draw_3d)
|
||||
D_RenderLevel();
|
||||
|
||||
ps_uitime = I_GetPreciseTime();
|
||||
|
||||
#ifdef HAVE_VR
|
||||
if (vr_in_eye_ui)
|
||||
VR_BeginInEyeUI();
|
||||
#endif
|
||||
|
||||
if (vr_draw_ui && gamestate == GS_LEVEL)
|
||||
{
|
||||
ST_Drawer();
|
||||
F_TextPromptDrawer();
|
||||
HU_Drawer();
|
||||
}
|
||||
else if (vr_draw_ui)
|
||||
{
|
||||
if (vr_draw_title)
|
||||
F_TitleScreenDrawer();
|
||||
}
|
||||
}
|
||||
else
|
||||
F_TitleScreenDrawer();
|
||||
}
|
||||
else
|
||||
{
|
||||
ps_uitime = I_GetPreciseTime();
|
||||
|
|
@ -648,7 +736,7 @@ static void D_Display(void)
|
|||
V_SetPalette(0);
|
||||
|
||||
// draw pause pic
|
||||
if (paused && cv_showhud.value && !demo.playback)
|
||||
if (vr_draw_ui && paused && cv_showhud.value && !demo.playback)
|
||||
{
|
||||
INT32 py;
|
||||
patch_t *patch;
|
||||
|
|
@ -660,24 +748,45 @@ static void D_Display(void)
|
|||
V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - patch->width)/2, py, V_SNAPTOTOP, patch);
|
||||
}
|
||||
|
||||
if (demo.rewinding)
|
||||
if (vr_draw_ui && demo.rewinding)
|
||||
V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSLAM);
|
||||
|
||||
// vid size change is now finished if it was on...
|
||||
vid.recalc = false;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
I_lock_mutex(&m_menu_mutex);
|
||||
if (vr_draw_ui)
|
||||
{
|
||||
#ifdef HAVE_VR
|
||||
if (vr_in_eye_ui)
|
||||
VR_BeginInEyeUI();
|
||||
#endif
|
||||
M_Drawer(); // menu is drawn even on top of everything
|
||||
if (cv_songcredits.value && !( (G_GamestateUsesLevel() && hu_showscores) && (netgame || multiplayer) ))
|
||||
HU_DrawSongCredits(); // As are music credits.
|
||||
#ifdef HAVE_THREADS
|
||||
I_unlock_mutex(m_menu_mutex);
|
||||
#endif
|
||||
// focus lost moved to M_Drawer
|
||||
|
||||
CON_Drawer();
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started && vr_render_pass == VR_PASS_UI)
|
||||
{
|
||||
static boolean vr_logged_ui_pass = false;
|
||||
if (!vr_logged_ui_pass)
|
||||
{
|
||||
CONS_Printf("VR: UI pass active; menustack[0]=%d, gamestate=%d, vid=%dx%d dup=%d, ui=%ux%u.\n",
|
||||
(INT32)menustack[0], (INT32)gamestate, vid.width, vid.height, vid.dup, vr_ui_width, vr_ui_height);
|
||||
vr_logged_ui_pass = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
I_lock_mutex(&m_menu_mutex);
|
||||
#endif
|
||||
M_Drawer(); // menu is drawn even on top of everything
|
||||
if (cv_songcredits.value && !( (G_GamestateUsesLevel() && hu_showscores) && (netgame || multiplayer) ))
|
||||
HU_DrawSongCredits(); // As are music credits.
|
||||
#ifdef HAVE_THREADS
|
||||
I_unlock_mutex(m_menu_mutex);
|
||||
#endif
|
||||
// focus lost moved to M_Drawer
|
||||
|
||||
CON_Drawer();
|
||||
}
|
||||
|
||||
ps_uitime = I_GetPreciseTime() - ps_uitime;
|
||||
|
||||
|
|
@ -712,13 +821,16 @@ dedipostwipe:
|
|||
if (dedicated)
|
||||
return; // NOW we can bail
|
||||
|
||||
NetUpdate(); // send out any new accumulation
|
||||
|
||||
// It's safe to end the game now.
|
||||
if (G_GetExitGameFlag())
|
||||
if (vr_draw_ui)
|
||||
{
|
||||
Command_ExitGame_f();
|
||||
G_ClearExitGameFlag();
|
||||
NetUpdate(); // send out any new accumulation
|
||||
|
||||
// It's safe to end the game now.
|
||||
if (G_GetExitGameFlag())
|
||||
{
|
||||
Command_ExitGame_f();
|
||||
G_ClearExitGameFlag();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -726,10 +838,10 @@ dedipostwipe:
|
|||
//
|
||||
if (!wipe)
|
||||
{
|
||||
if (cv_shittyscreen.value)
|
||||
if (vr_draw_ui && cv_shittyscreen.value)
|
||||
V_DrawVhsEffect(cv_shittyscreen.value == 2);
|
||||
|
||||
if (cv_netstat.value)
|
||||
if (vr_draw_ui && cv_netstat.value)
|
||||
{
|
||||
char s[50];
|
||||
Net_GetNetStat();
|
||||
|
|
@ -746,7 +858,7 @@ dedipostwipe:
|
|||
V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-10, V_YELLOWMAP, s);
|
||||
}
|
||||
|
||||
if (cv_perfstats.value)
|
||||
if (vr_draw_ui && cv_perfstats.value)
|
||||
{
|
||||
M_DrawPerfStats();
|
||||
}
|
||||
|
|
@ -812,6 +924,15 @@ static double D_EndFrame(precise_t enterprecise, int *frameskip)
|
|||
double deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
|
||||
double deltatics = deltasecs * NEWTICRATE;
|
||||
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
{
|
||||
if (frameskip)
|
||||
*frameskip = 0;
|
||||
return deltatics;
|
||||
}
|
||||
#endif
|
||||
|
||||
// If time spent this game loop exceeds a single tic,
|
||||
// it's probably because of rendering.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1512,6 +1512,19 @@ void D_RegisterClientCommands(void)
|
|||
CV_RegisterVar(&cv_scr_depth);
|
||||
CV_RegisterVar(&cv_scr_width);
|
||||
CV_RegisterVar(&cv_scr_height);
|
||||
CV_RegisterVar(&cv_vrviewmode);
|
||||
CV_RegisterVar(&cv_vrcomfortmode);
|
||||
CV_RegisterVar(&cv_vrenabled);
|
||||
CV_RegisterVar(&cv_vrresolution);
|
||||
CV_RegisterVar(&cv_vrscale);
|
||||
CV_RegisterVar(&cv_vruidistance);
|
||||
CV_RegisterVar(&cv_vruiscale);
|
||||
CV_RegisterVar(&cv_vruimode);
|
||||
CV_RegisterVar(&cv_vrposemode);
|
||||
CV_RegisterVar(&cv_vrplayerscale);
|
||||
CV_RegisterVar(&cv_vrspriterotate);
|
||||
CV_RegisterVar(&cv_vrdisableskystereo);
|
||||
CV_RegisterVar(&cv_vrtrackintro);
|
||||
CV_RegisterVar(&cv_parallelsoftware);
|
||||
|
||||
CV_RegisterVar(&cv_director);
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@
|
|||
#include "../tables.h"
|
||||
#include "r_opengl/r_opengl.h"
|
||||
#include "../r_main.h" // for cv_fov
|
||||
#ifdef HAVE_VR
|
||||
#include "../vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
typedef struct clipnode_s
|
||||
{
|
||||
|
|
@ -333,6 +336,11 @@ angle_t gld_FrustumAngle(angle_t tiltangle)
|
|||
|
||||
// ok, this is a gross hack that barely works...
|
||||
// but at least it doesn't overestimate too much...
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
clipfov = 120.0;
|
||||
else
|
||||
#endif
|
||||
clipfov = atan(1 / (GLdouble)projMatrix[0]) * 360.0 / M_PI;
|
||||
floatangle = 2.0 + (45.0 + ((double)tilt / 1.9)) * clipfov / 90.0;
|
||||
if (floatangle >= 180.0)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@
|
|||
#include "../p_slopes.h"
|
||||
#include "hw_md2.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "../screen.h"
|
||||
#include "../vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
// SRB2Kart
|
||||
#include "../k_kart.h"
|
||||
#include "../r_fps.h"
|
||||
|
|
@ -522,6 +527,18 @@ void HWR_RenderViewpoint(player_t *player, boolean drawSkyTexture, boolean is_sk
|
|||
// since our clipper uses this to determine our actual visible geometry
|
||||
GL_SetTransform(&atransform);
|
||||
HWR_ClearSprites();
|
||||
|
||||
#ifdef HAVE_VR
|
||||
// Let sprite billboards and clipping follow the user's HMD yaw in VR.
|
||||
if (vr_started && cv_vrspriterotate.value)
|
||||
{
|
||||
if (cv_kartencore.value)
|
||||
viewangle += ANGLE_MAX * atan2(vrHMDPoseMatrix[8], vrHMDPoseMatrix[0]) / M_PI * 0.5;
|
||||
else
|
||||
viewangle -= ANGLE_MAX * atan2(vrHMDPoseMatrix[8], vrHMDPoseMatrix[0]) / M_PI * 0.5;
|
||||
}
|
||||
#endif
|
||||
|
||||
HWR_ResetClipper();
|
||||
|
||||
if (rootportal)
|
||||
|
|
@ -560,7 +577,11 @@ void HWR_RenderViewpoint(player_t *player, boolean drawSkyTexture, boolean is_sk
|
|||
// HWR_DrawSkyBackground is not able to set the texture without
|
||||
// pausing batching first
|
||||
HWR_PauseBatching();
|
||||
if (skyboxmo[0] && cv_skybox.value && !is_skybox && !rootportal && !gl_debugportal)
|
||||
if (skyboxmo[0] && cv_skybox.value && !is_skybox && !rootportal && !gl_debugportal
|
||||
#ifdef HAVE_VR
|
||||
&& (!vr_started || !cv_vrcomfortmode.value)
|
||||
#endif
|
||||
)
|
||||
{
|
||||
//if (gl_printportals)
|
||||
// CONS_Printf("drawing a skybox\n");
|
||||
|
|
@ -701,11 +722,33 @@ void HWR_RollTransform(FTransform *tr, angle_t roll)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_VR
|
||||
static inline boolean HWR_VRRenderingEye(void)
|
||||
{
|
||||
return (vr_started && !vr_drawing_ui &&
|
||||
(vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT));
|
||||
}
|
||||
#endif
|
||||
|
||||
// -----------------+
|
||||
// HWR_ClearView : clear the viewwindow, with maximum z value. also clears stencil buffer.
|
||||
// -----------------+
|
||||
static inline void HWR_ClearView(void)
|
||||
{
|
||||
#ifdef HAVE_VR
|
||||
if (HWR_VRRenderingEye())
|
||||
{
|
||||
GL_GClipRect(0,
|
||||
0,
|
||||
(INT32)vr_render_width,
|
||||
(INT32)vr_render_height,
|
||||
ZCLIP_PLANE);
|
||||
|
||||
GL_ClearBuffer(false, true, true, NULL);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
GL_GClipRect(viewwindowx,
|
||||
viewwindowy,
|
||||
(viewwindowx + viewwidth),
|
||||
|
|
@ -719,7 +762,11 @@ void HWR_RenderPlayerView(void)
|
|||
{
|
||||
player_t * player = &players[displayplayers[viewssnum]];
|
||||
|
||||
const boolean skybox = (skyboxmo[0] && cv_skybox.value); // True if there's a skybox object and skyboxes are on
|
||||
const boolean skybox = (skyboxmo[0] && cv_skybox.value
|
||||
#ifdef HAVE_VR
|
||||
&& (!vr_started || !cv_vrcomfortmode.value)
|
||||
#endif
|
||||
); // True if there's a skybox object and skyboxes are on
|
||||
|
||||
FRGBAFloat ClearColor;
|
||||
|
||||
|
|
@ -744,6 +791,14 @@ void HWR_RenderPlayerView(void)
|
|||
if (viewssnum == 0) // Only do it if it's the first screen being rendered
|
||||
GL_ClearBuffer(true, true, true, &ClearColor); // Clear the Color Buffer, stops HOMs. Also seems to fix the skybox issue on Intel GPUs.
|
||||
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
{
|
||||
const float player_scale = (cv_vrplayerscale.value && player->mo) ? FIXED_TO_FLOAT(player->mo->scale) : 1.0f;
|
||||
VR_ScaleViewMatrices(player_scale, cv_vrdisableskystereo.value ? 0 : 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
ps_hw_skyboxtime = I_GetPreciseTime();
|
||||
if (skybox) // If there's a skybox and we should be drawing the sky, draw the skybox
|
||||
{
|
||||
|
|
@ -798,6 +853,11 @@ void HWR_RenderPlayerView(void)
|
|||
|
||||
// added by Hurdler for correct splitscreen
|
||||
// moved here by hurdler so it works with the new near clipping plane
|
||||
#ifdef HAVE_VR
|
||||
if (HWR_VRRenderingEye())
|
||||
GL_GClipRect(0, 0, (INT32)vr_render_width, (INT32)vr_render_height, NZCLIP_PLANE);
|
||||
else
|
||||
#endif
|
||||
GL_GClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
#include "hw_gpu.h"
|
||||
#include "hw_shaders.h"
|
||||
#include "../z_zone.h"
|
||||
#ifdef HAVE_VR
|
||||
#include "../vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
// ================
|
||||
// Shader sources
|
||||
|
|
@ -361,7 +364,17 @@ static void HWR_CompileShader(int index)
|
|||
|
||||
if (vertex_source)
|
||||
{
|
||||
char *preprocessed = HWR_PreprocessShader(vertex_source);
|
||||
char *source_to_preprocess = vertex_source;
|
||||
#ifdef HAVE_VR
|
||||
const int shader_target = index % NUMSHADERTARGETS;
|
||||
if (vr_started && shader_target >= SHADER_FLOOR && shader_target <= SHADER_SKY)
|
||||
source_to_preprocess = Z_StrDup(GLSL_VR_VERTEX_SHADER);
|
||||
#endif
|
||||
char *preprocessed = HWR_PreprocessShader(source_to_preprocess);
|
||||
#ifdef HAVE_VR
|
||||
if (source_to_preprocess != vertex_source)
|
||||
Z_Free(source_to_preprocess);
|
||||
#endif
|
||||
if (!preprocessed) return;
|
||||
GL_LoadShader(index, preprocessed, HWD_SHADERSTAGE_VERTEX);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,35 @@
|
|||
//
|
||||
|
||||
#define GLSL_FALLBACK_VERTEX_SHADER \
|
||||
"uniform mat4 vrEyeMatrix;\n" \
|
||||
"uniform mat4 vrEyeProjection;\n" \
|
||||
"uniform mat4 vrHeadPoseMatrix;\n" \
|
||||
"void main()\n" \
|
||||
"{\n" \
|
||||
"gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"gl_FrontColor = gl_Color;\n" \
|
||||
"gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
|
||||
"gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"\tif (vrEyeProjection[0][0] != 0.0)\n" \
|
||||
"\t{\n" \
|
||||
"\t\tgl_Position = vrEyeProjection * vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"\t\tgl_ClipVertex = vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"\t}\n" \
|
||||
"\telse\n" \
|
||||
"\t{\n" \
|
||||
"\t\tgl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"\t\tgl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"\t}\n" \
|
||||
"\tgl_FrontColor = gl_Color;\n" \
|
||||
"\tgl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
|
||||
"}\0"
|
||||
|
||||
#define GLSL_VR_VERTEX_SHADER \
|
||||
"uniform mat4 vrEyeMatrix;\n" \
|
||||
"uniform mat4 vrEyeProjection;\n" \
|
||||
"uniform mat4 vrHeadPoseMatrix;\n" \
|
||||
"void main()\n" \
|
||||
"{\n" \
|
||||
"\tgl_Position = vrEyeProjection * vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"\tgl_ClipVertex = vrEyeMatrix * vrHeadPoseMatrix * gl_ModelViewMatrix * gl_Vertex;\n" \
|
||||
"\tgl_FrontColor = gl_Color;\n" \
|
||||
"\tgl_TexCoord[0].xy = gl_MultiTexCoord0.xy;\n" \
|
||||
"}\0"
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@
|
|||
#include "../g_game.h"
|
||||
#include "../r_fps.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "../screen.h"
|
||||
#include "../vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
|
||||
// ==========================================================================
|
||||
// Sky dome rendering, ported from PrBoom+
|
||||
|
|
@ -163,6 +168,11 @@ void HWR_BuildSkyDome(void)
|
|||
|
||||
void HWR_DrawSkyBackground(player_t *player)
|
||||
{
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started && cv_vrcomfortmode.value)
|
||||
return;
|
||||
#endif
|
||||
|
||||
GL_SetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated);
|
||||
|
||||
if (cv_glskydome.value)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@
|
|||
#include "../hw_shaders.h"
|
||||
#include "../hw_gpu.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "../../vr/vr_main.h"
|
||||
#include "../../screen.h"
|
||||
extern boolean gl_rendering_skybox;
|
||||
#endif
|
||||
|
||||
#if defined (HWRENDER) && !defined (NOROPENGL)
|
||||
|
||||
// requires GL 4.3
|
||||
|
|
@ -603,6 +609,7 @@ typedef void (APIENTRY *PFNglUniform4f) (GLint, GLfloat, GLfloat, GLfloat, GL
|
|||
typedef void (APIENTRY *PFNglUniform1fv) (GLint, GLsizei, const GLfloat*);
|
||||
typedef void (APIENTRY *PFNglUniform2fv) (GLint, GLsizei, const GLfloat*);
|
||||
typedef void (APIENTRY *PFNglUniform3fv) (GLint, GLsizei, const GLfloat*);
|
||||
typedef void (APIENTRY *PFNglUniformMatrix4fv) (GLint, GLsizei, GLboolean, const GLfloat*);
|
||||
typedef GLint (APIENTRY *PFNglGetUniformLocation) (GLuint, const GLchar*);
|
||||
|
||||
static PFNglCreateShader pglCreateShader;
|
||||
|
|
@ -625,6 +632,7 @@ static PFNglUniform4f pglUniform4f;
|
|||
static PFNglUniform1fv pglUniform1fv;
|
||||
static PFNglUniform2fv pglUniform2fv;
|
||||
static PFNglUniform3fv pglUniform3fv;
|
||||
static PFNglUniformMatrix4fv pglUniformMatrix4fv;
|
||||
static PFNglGetUniformLocation pglGetUniformLocation;
|
||||
|
||||
// 13062019
|
||||
|
|
@ -659,6 +667,12 @@ typedef enum
|
|||
|
||||
gluniform_scr_resolution,
|
||||
|
||||
#ifdef HAVE_VR
|
||||
gluniform_evm, // Eye View Matrix
|
||||
gluniform_epm, // Eye Projection Matrix
|
||||
gluniform_hpm, // Head Pose Matrix
|
||||
#endif
|
||||
|
||||
gluniform_max,
|
||||
} gluniform_t;
|
||||
|
||||
|
|
@ -740,6 +754,7 @@ void SetupGLFunc4(void)
|
|||
*(void**)&pglUniform1fv = GetGLFunc("glUniform1fv");
|
||||
*(void**)&pglUniform2fv = GetGLFunc("glUniform2fv");
|
||||
*(void**)&pglUniform3fv = GetGLFunc("glUniform3fv");
|
||||
*(void**)&pglUniformMatrix4fv = GetGLFunc("glUniformMatrix4fv");
|
||||
*(void**)&pglGetUniformLocation = GetGLFunc("glGetUniformLocation");
|
||||
#endif
|
||||
|
||||
|
|
@ -1389,7 +1404,10 @@ void GL_ClearBuffer(FBOOLEAN ColorMask,
|
|||
if (StencilMask)
|
||||
ClearMask |= GL_STENCIL_BUFFER_BIT;
|
||||
|
||||
GL_SetBlend(DepthMask ? PF_Occlude | CurrentPolyFlags : CurrentPolyFlags&~PF_Occlude);
|
||||
// A depth clear begins a fresh 3D pass. Do not preserve HUD/UI state such
|
||||
// as PF_NoDepthTest or translucent blending here, or later transparent
|
||||
// materials can draw through opaque world geometry in VR multi-pass frames.
|
||||
GL_SetBlend(DepthMask ? PF_Occlude : CurrentPolyFlags&~PF_Occlude);
|
||||
|
||||
pglClear(ClearMask);
|
||||
pglEnableClientState(GL_VERTEX_ARRAY); // We always use this one
|
||||
|
|
@ -1944,6 +1962,50 @@ static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAF
|
|||
|
||||
UNIFORM_1(shader->uniforms[gluniform_leveltime], shader_leveltime, pglUniform1f);
|
||||
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
{
|
||||
static const GLfloat identityMatrix[16] = {
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f
|
||||
};
|
||||
static const GLfloat zeroMatrix[16] = {
|
||||
0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 0.0f
|
||||
};
|
||||
const GLfloat *eyeviewmatrix = identityMatrix;
|
||||
const GLfloat *headposematrix = identityMatrix;
|
||||
const GLfloat *projectionmatrix = zeroMatrix;
|
||||
|
||||
if (!vr_drawing_ui && (vr_render_pass == VR_PASS_3D_LEFT || vr_render_pass == VR_PASS_3D_RIGHT))
|
||||
{
|
||||
if (gl_rendering_skybox)
|
||||
{
|
||||
eyeviewmatrix = vrEyeSkyboxViewMatrix[vr_current_eye];
|
||||
headposematrix = (cv_vrposemode.value == 1 || cv_vrposemode.value == 3) ? vrHMDPoseSkyboxMatrix : identityMatrix;
|
||||
}
|
||||
else
|
||||
{
|
||||
eyeviewmatrix = vrScaledEyeViewMatrix[vr_current_eye];
|
||||
headposematrix = (cv_vrposemode.value == 1 || cv_vrposemode.value == 3) ? vrHMDScaledPoseMatrix : identityMatrix;
|
||||
}
|
||||
|
||||
projectionmatrix = vrEyeProjMatrix[vr_current_eye];
|
||||
}
|
||||
|
||||
if (shader->uniforms[gluniform_evm] != -1)
|
||||
pglUniformMatrix4fv(shader->uniforms[gluniform_evm], 1, GL_FALSE, eyeviewmatrix);
|
||||
if (shader->uniforms[gluniform_epm] != -1)
|
||||
pglUniformMatrix4fv(shader->uniforms[gluniform_epm], 1, GL_FALSE, projectionmatrix);
|
||||
if (shader->uniforms[gluniform_hpm] != -1)
|
||||
pglUniformMatrix4fv(shader->uniforms[gluniform_hpm], 1, GL_FALSE, headposematrix);
|
||||
}
|
||||
#endif
|
||||
|
||||
UNIFORM_2(shader->uniforms[gluniform_scr_resolution], (GLfloat)vid.width, (GLfloat)vid.height, pglUniform2f);
|
||||
|
||||
#undef UNIFORM_1
|
||||
|
|
@ -2082,6 +2144,12 @@ static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i)
|
|||
// misc. (custom shaders)
|
||||
shader->uniforms[gluniform_leveltime] = GETUNI("leveltime");
|
||||
|
||||
#ifdef HAVE_VR
|
||||
shader->uniforms[gluniform_evm] = GETUNI("vrEyeMatrix");
|
||||
shader->uniforms[gluniform_epm] = GETUNI("vrEyeProjection");
|
||||
shader->uniforms[gluniform_hpm] = GETUNI("vrHeadPoseMatrix");
|
||||
#endif
|
||||
|
||||
#undef GETUNI
|
||||
|
||||
// set permanent uniform values
|
||||
|
|
|
|||
27
src/p_user.c
27
src/p_user.c
|
|
@ -50,6 +50,11 @@
|
|||
// Thok camera snap (ctrl-f "chalupa")
|
||||
#include "g_input.h"
|
||||
#include "m_menu.h" // menustack
|
||||
#include "screen.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
// SRB2kart
|
||||
#include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems
|
||||
|
|
@ -192,11 +197,20 @@ fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move)
|
|||
//
|
||||
boolean P_AutoPause(void)
|
||||
{
|
||||
boolean focuspause;
|
||||
|
||||
// Don't pause even on menu-up or focus-lost in netgames or record attack
|
||||
if (netgame || modeattacking || gamestate == GS_TITLESCREEN)
|
||||
return false;
|
||||
|
||||
return ((menustack[0] && !demo.playback) || ( window_notinfocus && cv_pauseifunfocused.value ));
|
||||
focuspause = (window_notinfocus && cv_pauseifunfocused.value);
|
||||
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
focuspause = false;
|
||||
#endif
|
||||
|
||||
return ((menustack[0] && !demo.playback) || focuspause);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -3187,10 +3201,17 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
|
|||
const INT32 timeovercam = max(0, min(180, (player->karthud[khud_timeovercam] - 2*TICRATE)*15));
|
||||
camrotate += timeovercam;
|
||||
}
|
||||
else if (leveltime < introtime && !(modeattacking && !demo.playback)) // Whoooshy camera! (don't do this in RA when we PLAY, still do it in replays however~)
|
||||
else if (leveltime < introtime && !(modeattacking && !demo.playback)
|
||||
#ifdef HAVE_VR
|
||||
&& (!vr_started || cv_vrtrackintro.value)
|
||||
#endif
|
||||
) // Whoooshy camera! (don't do this in RA when we PLAY, still do it in replays however~)
|
||||
{
|
||||
const INT32 introcam = (introtime - leveltime);
|
||||
camrotate += introcam*5;
|
||||
#ifdef HAVE_VR
|
||||
if (!vr_started || cv_vrtrackintro.value > 1)
|
||||
#endif
|
||||
camrotate += introcam*5;
|
||||
camdist += (introcam * mapobjectscale)*3;
|
||||
camheight += (introcam * mapobjectscale)*2;
|
||||
}
|
||||
|
|
|
|||
12
src/r_fps.c
12
src/r_fps.c
|
|
@ -28,6 +28,9 @@
|
|||
#ifdef HWRENDER
|
||||
#include "hardware/hw_main.h"
|
||||
#endif
|
||||
#ifdef HAVE_VR
|
||||
#include "vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
// The fraction of a tic being drawn (for interpolation between two tics)
|
||||
static fixed_t rendertimefrac;
|
||||
|
|
@ -55,6 +58,15 @@ UINT32 R_GetFramerateCap(void)
|
|||
return TICRATE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
{
|
||||
// In VR, OpenXR xrWaitFrame is the display clock. Returning zero keeps
|
||||
// the legacy desktop cap from reducing rendering to TICRATE or half-rate.
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (cv_fpscap.value == 0)
|
||||
{
|
||||
// 0: Match refresh rate
|
||||
|
|
|
|||
78
src/screen.c
78
src/screen.c
|
|
@ -36,6 +36,10 @@
|
|||
#include "p_local.h" // P_AutoPause()
|
||||
#include "m_menu.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
#ifdef HWRENDER
|
||||
#include "hardware/hw_main.h"
|
||||
#include "hardware/hw_glob.h"
|
||||
|
|
@ -72,6 +76,46 @@ static void Highreshudscale_OnChange(void);
|
|||
static CV_PossibleValue_t highreshudscale_cons_t[] = {{4*FRACUNIT/5, "MIN"}, {6*FRACUNIT/5, "MAX"}, {0, NULL}};
|
||||
consvar_t cv_highreshudscale = CVAR_INIT ("highreshudscale", "1", CV_SAVE|CV_FLOAT|CV_CALL|CV_NOINIT|CV_NOSHOWHELP, highreshudscale_cons_t, Highreshudscale_OnChange);
|
||||
|
||||
static void SCR_ChangeVR(void);
|
||||
static void SCR_ChangeScaleVR(void);
|
||||
static void SCR_ChangeResolutionVR(void);
|
||||
|
||||
static CV_PossibleValue_t vrviewmode_cons_t[] = {{-1, "Hide"}, {0, "Left Eye"}, {1, "Right Eye"}, {0, NULL}};
|
||||
consvar_t cv_vrviewmode = CVAR_INIT ("vrviewmode", "0", CV_SAVE, vrviewmode_cons_t, NULL);
|
||||
|
||||
static CV_PossibleValue_t vrtrackintro_cons_t[] = {{0, "Off"}, {1, "Gentle"}, {2, "Standard"}, {0, NULL}};
|
||||
consvar_t cv_vrtrackintro = CVAR_INIT ("vrtrackintro", "1", CV_SAVE, vrtrackintro_cons_t, NULL);
|
||||
|
||||
consvar_t cv_vrcomfortmode = CVAR_INIT ("vrcomfortmode", "No", CV_SAVE, CV_YesNo, NULL);
|
||||
consvar_t cv_vrdisableskystereo = CVAR_INIT ("vrdisableskystereo", "No", CV_SAVE, CV_YesNo, NULL);
|
||||
consvar_t cv_vrplayerscale = CVAR_INIT ("vrplayerscale", "Yes", CV_SAVE, CV_YesNo, NULL);
|
||||
consvar_t cv_vrspriterotate = CVAR_INIT ("vrspriterotate", "Yes", CV_SAVE, CV_YesNo, NULL);
|
||||
consvar_t cv_vruidistance = CVAR_INIT ("vruidistance", "105", CV_SAVE, CV_Unsigned, NULL);
|
||||
consvar_t cv_vruiscale = CVAR_INIT ("vruiscale", "50", CV_SAVE, CV_Unsigned, NULL);
|
||||
|
||||
static CV_PossibleValue_t vruimode_cons_t[] = {{0, "InEye"}, {1, "Quad"}, {0, NULL}};
|
||||
consvar_t cv_vruimode = CVAR_INIT ("vruimode", "Quad", CV_SAVE, vruimode_cons_t, NULL);
|
||||
|
||||
static CV_PossibleValue_t vrposemode_cons_t[] = {
|
||||
{0, "OpenXRView"},
|
||||
{1, "OpenVRSplit"},
|
||||
{2, "OpenXRDebugPose"},
|
||||
{3, "OpenVRDebugPose"},
|
||||
{0, NULL}
|
||||
};
|
||||
consvar_t cv_vrposemode = CVAR_INIT ("vrposemode", "OpenXRView", CV_SAVE, vrposemode_cons_t, NULL);
|
||||
|
||||
static CV_PossibleValue_t vrresolution_cons_t[] = {
|
||||
{0, "50%"}, {1, "75%"}, {2, "100%"}, {3, "125%"},
|
||||
{4, "150%"}, {5, "175%"}, {6, "200%"}, {0, NULL}
|
||||
};
|
||||
consvar_t cv_vrresolution = CVAR_INIT ("vrresolution", "2", CV_SAVE|CV_NOINIT|CV_CALL, vrresolution_cons_t, SCR_ChangeResolutionVR);
|
||||
|
||||
static CV_PossibleValue_t vrscale_cons_t[] = {{0, "Small"}, {1, "Standard"}, {2, "Large"}, {0, NULL}};
|
||||
consvar_t cv_vrscale = CVAR_INIT ("vrscale", "1", CV_SAVE|CV_NOINIT|CV_CALL, vrscale_cons_t, SCR_ChangeScaleVR);
|
||||
|
||||
consvar_t cv_vrenabled = CVAR_INIT ("vrenabled", "Off", CV_SAVE|CV_CALL, CV_OnOff, SCR_ChangeVR);
|
||||
|
||||
static void Highreshudscale_OnChange(void)
|
||||
{
|
||||
SCR_Recalc();
|
||||
|
|
@ -514,6 +558,40 @@ void SCR_ChangeFullscreen(void)
|
|||
return;
|
||||
}
|
||||
|
||||
static void SCR_ChangeVR(void)
|
||||
{
|
||||
#ifdef HAVE_VR
|
||||
vr_enabled = (cv_vrenabled.value != 0);
|
||||
|
||||
if (!graphics_started)
|
||||
return;
|
||||
|
||||
if (cv_vrenabled.value)
|
||||
VR_Init();
|
||||
else
|
||||
VR_Shutdown();
|
||||
#else
|
||||
if (cv_vrenabled.value)
|
||||
CONS_Alert(CONS_ERROR, "This build does not include OpenXR support.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SCR_ChangeScaleVR(void)
|
||||
{
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
CONS_Printf("VR: world scale changed; new tracking scale applies next frame.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SCR_ChangeResolutionVR(void)
|
||||
{
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
CONS_Printf("VR: vrresolution changes require restarting VR so OpenXR swapchains can be recreated.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void SCR_ChangeRenderer(void)
|
||||
{
|
||||
if (chosenrendermode != render_none
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ extern CV_PossibleValue_t cv_renderer_t[];
|
|||
extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_renderer, cv_renderhitbox, cv_fullscreen;
|
||||
extern consvar_t cv_highreshudscale;
|
||||
extern consvar_t cv_vhseffect, cv_shittyscreen, cv_votebgscaling;
|
||||
extern consvar_t cv_vrviewmode, cv_vrcomfortmode, cv_vrenabled, cv_vrresolution, cv_vrscale;
|
||||
extern consvar_t cv_vruidistance, cv_vruiscale, cv_vruimode, cv_vrposemode, cv_vrplayerscale, cv_vrspriterotate;
|
||||
extern consvar_t cv_vrdisableskystereo, cv_vrtrackintro;
|
||||
extern consvar_t cv_parallelsoftware;
|
||||
extern consvar_t cv_accuratefps;
|
||||
|
||||
|
|
|
|||
|
|
@ -1461,6 +1461,7 @@ INT32 I_StartupSystem(void)
|
|||
#if SDL_VERSION_ATLEAST(2,0,22)
|
||||
SDL_SetHint(SDL_HINT_APP_NAME, "BlanKart");
|
||||
#endif
|
||||
SDL_SetHint("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", "1");
|
||||
if (!SDL_Init(0))
|
||||
I_Error("SRB2Kart: SDL System Error: %s", SDL_GetError()); //Alam: Oh no....
|
||||
#ifndef NOMUMBLE
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@
|
|||
#include "../d_main.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../i_sound.h" // midi pause/unpause
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "../vr/vr_main.h"
|
||||
#endif
|
||||
|
||||
#include "../i_gamepad.h"
|
||||
#include "../st_stuff.h"
|
||||
#include "../hu_stuff.h"
|
||||
|
|
@ -545,6 +550,11 @@ static void VID_Command_Mode_f (void)
|
|||
|
||||
static void Impl_SetFocused(boolean focused)
|
||||
{
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started)
|
||||
focused = true;
|
||||
#endif
|
||||
|
||||
window_notinfocus = !focused;
|
||||
|
||||
if (window_notinfocus)
|
||||
|
|
@ -1316,51 +1326,60 @@ void I_FinishUpdate(void)
|
|||
if (rendermode == render_none)
|
||||
return; //Alam: No software or OpenGl surface
|
||||
|
||||
SCR_CalculateFPS();
|
||||
#ifdef HAVE_VR
|
||||
const boolean vr_draw_ui = (!vr_started || vr_render_pass == VR_PASS_NONE || vr_render_pass == VR_PASS_UI);
|
||||
#else
|
||||
const boolean vr_draw_ui = true;
|
||||
#endif
|
||||
|
||||
if (st_overlay)
|
||||
if (vr_draw_ui)
|
||||
{
|
||||
if (cv_ticrate.value)
|
||||
SCR_DisplayTicRate();
|
||||
SCR_CalculateFPS();
|
||||
|
||||
if (cv_showping.value && netgame &&
|
||||
( consoleplayer != serverplayer || (server_lagless == 0 || server_lagless == -1) ))
|
||||
if (st_overlay)
|
||||
{
|
||||
if (server_lagless == 1)
|
||||
if (cv_ticrate.value)
|
||||
SCR_DisplayTicRate();
|
||||
|
||||
if (cv_showping.value && netgame &&
|
||||
( consoleplayer != serverplayer || (server_lagless == 0 || server_lagless == -1) ))
|
||||
{
|
||||
if (consoleplayer != serverplayer)
|
||||
SCR_DisplayLocalPing();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (
|
||||
player = 1;
|
||||
player < MAXPLAYERS;
|
||||
player++
|
||||
){
|
||||
if (D_IsPlayerHumanAndGaming(player))
|
||||
{
|
||||
if (server_lagless == 1)
|
||||
{
|
||||
if (consoleplayer != serverplayer)
|
||||
SCR_DisplayLocalPing();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (
|
||||
player = 1;
|
||||
player < MAXPLAYERS;
|
||||
player++
|
||||
){
|
||||
if (D_IsPlayerHumanAndGaming(player))
|
||||
{
|
||||
SCR_DisplayLocalPing();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
|
||||
SCR_DisplayLocalPing();
|
||||
}
|
||||
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
|
||||
SCR_DisplayLocalPing();
|
||||
}
|
||||
|
||||
if (marathonmode)
|
||||
SCR_DisplayMarathonInfo();
|
||||
if (marathonmode)
|
||||
SCR_DisplayMarathonInfo();
|
||||
|
||||
// draw captions if enabled
|
||||
if (cv_closedcaptioning.value)
|
||||
SCR_ClosedCaptions();
|
||||
// draw captions if enabled
|
||||
if (cv_closedcaptioning.value)
|
||||
SCR_ClosedCaptions();
|
||||
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
if (discordRequestList != NULL)
|
||||
ST_AskToJoinEnvelope();
|
||||
if (discordRequestList != NULL)
|
||||
ST_AskToJoinEnvelope();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (rendermode == render_soft && vid.screens[0])
|
||||
{
|
||||
|
|
@ -1389,7 +1408,12 @@ void I_FinishUpdate(void)
|
|||
else if (rendermode == render_opengl)
|
||||
{
|
||||
// Final postprocess step of palette rendering, after everything else has been drawn.
|
||||
if (HWR_ShouldUsePaletteRendering())
|
||||
#ifdef HAVE_VR
|
||||
const boolean vr_ui_pass = (vr_started && vr_render_pass == VR_PASS_UI);
|
||||
#else
|
||||
const boolean vr_ui_pass = false;
|
||||
#endif
|
||||
if (!vr_ui_pass && HWR_ShouldUsePaletteRendering())
|
||||
{
|
||||
GL_MakeScreenTexture(HWD_SCREENTEXTURE_GENERIC2);
|
||||
GL_SetShader(HWR_GetShaderFromTarget(SHADER_PALETTE_POSTPROCESS));
|
||||
|
|
@ -1736,7 +1760,12 @@ void I_StartupGraphics(void)
|
|||
return;
|
||||
|
||||
disable_mouse = static_cast<bool>(M_CheckParm("-nomouse"));
|
||||
disable_fullscreen = M_CheckParm("-win") ? true : false;
|
||||
#ifdef HAVE_VR
|
||||
const bool wantVR = (M_CheckParm("-openvr") || M_CheckParm("-vr") || cv_vrenabled.value);
|
||||
#else
|
||||
const bool wantVR = false;
|
||||
#endif
|
||||
disable_fullscreen = (M_CheckParm("-win") || wantVR) ? true : false;
|
||||
|
||||
keyboard_started = true;
|
||||
|
||||
|
|
@ -1786,6 +1815,8 @@ void I_StartupGraphics(void)
|
|||
// Choose OpenGL renderer
|
||||
else if (M_CheckParm("-opengl"))
|
||||
chosenrendermode = render_opengl;
|
||||
else if (wantVR)
|
||||
chosenrendermode = render_opengl;
|
||||
|
||||
// Don't startup OpenGL
|
||||
if (M_CheckParm("-nogl"))
|
||||
|
|
@ -1823,6 +1854,10 @@ void I_StartupGraphics(void)
|
|||
|
||||
VID_SetMode(VID_GetModeForSize(vid.width, vid.height));
|
||||
|
||||
#ifdef HAVE_VR
|
||||
VR_Init();
|
||||
#endif
|
||||
|
||||
SDLdoUngrabMouse();
|
||||
|
||||
SDL_RaiseWindow(window);
|
||||
|
|
@ -1902,5 +1937,3 @@ void I_SetBorderlessWindow(void)
|
|||
SDL_SetWindowBordered(window, bordered);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "../doomdef.h"
|
||||
#include "../d_main.h"
|
||||
#include "../screen.h"
|
||||
|
||||
#ifdef HWRENDER
|
||||
#include "../hardware/r_opengl/r_opengl.h"
|
||||
|
|
@ -171,34 +172,80 @@ boolean OglSdlSurface(INT32 w, INT32 h)
|
|||
|
||||
\return void
|
||||
*/
|
||||
#ifdef HAVE_VR
|
||||
#include "../vr/vr_main.h"
|
||||
#include "../vr/vr_render.h"
|
||||
#endif
|
||||
|
||||
void OglSdlFinishUpdate(boolean waitvbl)
|
||||
{
|
||||
static boolean oldwaitvbl = false;
|
||||
static int oldwaitvbl = -1;
|
||||
int sdlw, sdlh;
|
||||
if (oldwaitvbl != waitvbl)
|
||||
#ifdef HAVE_VR
|
||||
const boolean effective_waitvbl = (vr_started ? false : waitvbl);
|
||||
#else
|
||||
const boolean effective_waitvbl = waitvbl;
|
||||
#endif
|
||||
if (oldwaitvbl != (int)effective_waitvbl)
|
||||
{
|
||||
SDL_GL_SetSwapInterval(waitvbl);
|
||||
SDL_GL_SetSwapInterval(effective_waitvbl);
|
||||
}
|
||||
|
||||
oldwaitvbl = waitvbl;
|
||||
oldwaitvbl = (int)effective_waitvbl;
|
||||
|
||||
SDL_GetWindowSizeInPixels(window, &sdlw, &sdlh);
|
||||
|
||||
HWR_MakeScreenFinalTexture();
|
||||
if (gl_shadersavailable)
|
||||
GL_SetShader(HWR_GetShaderFromTarget(SHADER_FINAL_POST_PROCESS));
|
||||
HWR_DrawScreenFinalTexture(sdlw, sdlh);
|
||||
if (gl_shadersavailable)
|
||||
GL_UnSetShader();
|
||||
SDL_GL_SwapWindow(window);
|
||||
#ifdef HAVE_VR
|
||||
if (vr_started && vr_render_pass != VR_PASS_NONE)
|
||||
{
|
||||
if (vr_render_pass == VR_PASS_UI)
|
||||
return;
|
||||
|
||||
GL_GClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
|
||||
if (cv_vruimode.value == 0)
|
||||
{
|
||||
if (vr_current_eye == cv_vrviewmode.value)
|
||||
{
|
||||
if (VR_MirrorEyeToDefaultFramebuffer(sdlw, sdlh))
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
// Sryder: We need to draw the final screen texture again into the other buffer in the original position so that
|
||||
// effects that want to take the old screen can do so after this
|
||||
// Generic2 has the screen image without palette rendering brightness adjustments.
|
||||
// Using that here will prevent brightness adjustments being applied twice.
|
||||
GL_DrawScreenTexture(HWD_SCREENTEXTURE_GENERIC2, NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Optional desktop mirror of one eye. Use an FBO blit so the mirror
|
||||
// does not copy a 4096x4096 screen texture or write back into the
|
||||
// headset swapchain.
|
||||
if (vr_current_eye == cv_vrviewmode.value)
|
||||
{
|
||||
if (VR_MirrorEyeToDefaultFramebuffer(sdlw, sdlh))
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
HWR_MakeScreenFinalTexture();
|
||||
if (gl_shadersavailable)
|
||||
GL_SetShader(HWR_GetShaderFromTarget(SHADER_FINAL_POST_PROCESS));
|
||||
HWR_DrawScreenFinalTexture(sdlw, sdlh);
|
||||
if (gl_shadersavailable)
|
||||
GL_UnSetShader();
|
||||
SDL_GL_SwapWindow(window);
|
||||
|
||||
GL_GClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
|
||||
|
||||
// Sryder: We need to draw the final screen texture again into the other buffer in the original position so that
|
||||
// effects that want to take the old screen can do so after this
|
||||
// Generic2 has the screen image without palette rendering brightness adjustments.
|
||||
// Using that here will prevent brightness adjustments being applied twice.
|
||||
if (gl_shadersavailable)
|
||||
GL_SetShader(HWR_GetShaderFromTarget(SHADER_FINAL_POST_PROCESS));
|
||||
HWR_DrawScreenFinalTexture(sdlw, sdlh);
|
||||
if (gl_shadersavailable)
|
||||
GL_UnSetShader();
|
||||
}
|
||||
}
|
||||
|
||||
#endif //HWRENDER
|
||||
|
|
|
|||
601
src/vr/vr_main.c
Normal file
601
src/vr/vr_main.c
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
#include "vr_main.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
|
||||
#include "vr_render.h"
|
||||
#include "vr_math.h"
|
||||
#include "../m_argv.h"
|
||||
#include "../console.h"
|
||||
#include "../screen.h"
|
||||
#include "../i_video.h"
|
||||
#include "../r_fps.h"
|
||||
#include "../v_video.h"
|
||||
#include <stdbool.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include "../sdl/sdlmain.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef __linux__
|
||||
#include <GL/glx.h>
|
||||
#endif
|
||||
|
||||
int vr_current_eye = 0;
|
||||
boolean vr_started = false;
|
||||
boolean vr_enabled = false;
|
||||
boolean vr_drawing_ui = false;
|
||||
vr_render_pass_t vr_render_pass = VR_PASS_NONE;
|
||||
|
||||
XrInstance vr_instance = XR_NULL_HANDLE;
|
||||
XrSession vr_session = XR_NULL_HANDLE;
|
||||
XrSystemId vr_system_id = XR_NULL_SYSTEM_ID;
|
||||
XrSpace vr_local_space = XR_NULL_HANDLE;
|
||||
XrSpace vr_view_space = XR_NULL_HANDLE;
|
||||
|
||||
float vrHMDPoseMatrix[16];
|
||||
float vrHMDScaledPoseMatrix[16];
|
||||
float vrHMDPoseSkyboxMatrix[16];
|
||||
float vrEyeViewMatrix[2][16];
|
||||
float vrScaledEyeViewMatrix[2][16];
|
||||
float vrEyeSkyboxViewMatrix[2][16];
|
||||
float vrEyeProjMatrix[2][16];
|
||||
|
||||
int vrWorldScale[3] = {400, 250, 100};
|
||||
float vrPlayerScale = 1.0f;
|
||||
|
||||
uint32_t vr_render_width = 0;
|
||||
uint32_t vr_render_height = 0;
|
||||
|
||||
float* vrVisibleAreaVertices[2] = {NULL, NULL};
|
||||
float* vrVisibleAreaUVs[2] = {NULL, NULL};
|
||||
uint32_t vrVisibleAreaVertexCount[2] = {0, 0};
|
||||
|
||||
static PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = NULL;
|
||||
static PFN_xrGetVisibilityMaskKHR pfnGetVisibilityMaskKHR = NULL;
|
||||
static PFN_xrEnumerateDisplayRefreshRatesFB pfnEnumerateDisplayRefreshRatesFB = NULL;
|
||||
static PFN_xrGetDisplayRefreshRateFB pfnGetDisplayRefreshRateFB = NULL;
|
||||
static PFN_xrRequestDisplayRefreshRateFB pfnRequestDisplayRefreshRateFB = NULL;
|
||||
static boolean displayRefreshRateExtensionEnabled = false;
|
||||
|
||||
static float VR_RenderScaleForMode(int mode)
|
||||
{
|
||||
static const float multipliers[] = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
|
||||
|
||||
if (mode < 0)
|
||||
mode = 0;
|
||||
else if (mode > 6)
|
||||
mode = 6;
|
||||
|
||||
return multipliers[mode];
|
||||
}
|
||||
|
||||
static int VR_StartupResolutionMode(void)
|
||||
{
|
||||
int mode = cv_vrresolution.string ? cv_vrresolution.value : 2;
|
||||
|
||||
if (M_CheckParm("+vrresolution") && M_IsNextParm()) {
|
||||
const char* value = M_GetNextParm();
|
||||
|
||||
if (!strcasecmp(value, "50%"))
|
||||
mode = 0;
|
||||
else if (!strcasecmp(value, "75%"))
|
||||
mode = 1;
|
||||
else if (!strcasecmp(value, "100%"))
|
||||
mode = 2;
|
||||
else if (!strcasecmp(value, "125%"))
|
||||
mode = 3;
|
||||
else if (!strcasecmp(value, "150%"))
|
||||
mode = 4;
|
||||
else if (!strcasecmp(value, "175%"))
|
||||
mode = 5;
|
||||
else if (!strcasecmp(value, "200%"))
|
||||
mode = 6;
|
||||
else
|
||||
mode = atoi(value);
|
||||
}
|
||||
|
||||
if (mode < 0)
|
||||
mode = 0;
|
||||
else if (mode > 6)
|
||||
mode = 6;
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static boolean VR_RuntimeExtensionSupported(const char* extensionName)
|
||||
{
|
||||
uint32_t extensionCount = 0;
|
||||
XrResult res = xrEnumerateInstanceExtensionProperties(NULL, 0, &extensionCount, NULL);
|
||||
if (XR_FAILED(res) || extensionCount == 0)
|
||||
return false;
|
||||
|
||||
XrExtensionProperties* extensions =
|
||||
(XrExtensionProperties*)malloc(extensionCount * sizeof(XrExtensionProperties));
|
||||
if (!extensions)
|
||||
return false;
|
||||
|
||||
for (uint32_t i = 0; i < extensionCount; i++) {
|
||||
extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES;
|
||||
extensions[i].next = NULL;
|
||||
}
|
||||
|
||||
res = xrEnumerateInstanceExtensionProperties(NULL, extensionCount, &extensionCount, extensions);
|
||||
if (XR_FAILED(res)) {
|
||||
free(extensions);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < extensionCount; i++) {
|
||||
if (strcmp(extensions[i].extensionName, extensionName) == 0) {
|
||||
free(extensions);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
free(extensions);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void VR_ConfigureDisplayRefreshRate(void)
|
||||
{
|
||||
if (!displayRefreshRateExtensionEnabled || !pfnGetDisplayRefreshRateFB)
|
||||
return;
|
||||
|
||||
float currentRefreshRate = 0.0f;
|
||||
if (XR_SUCCEEDED(pfnGetDisplayRefreshRateFB(vr_session, ¤tRefreshRate)) &&
|
||||
currentRefreshRate > 0.0f) {
|
||||
CONS_Printf("VR: Runtime display refresh rate is %.2f Hz.\n", currentRefreshRate);
|
||||
|
||||
if (pfnRequestDisplayRefreshRateFB) {
|
||||
XrResult res = pfnRequestDisplayRefreshRateFB(vr_session, currentRefreshRate);
|
||||
if (XR_SUCCEEDED(res))
|
||||
CONS_Printf("VR: Requested runtime refresh pacing at %.2f Hz.\n", currentRefreshRate);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pfnEnumerateDisplayRefreshRatesFB)
|
||||
return;
|
||||
|
||||
uint32_t refreshRateCount = 0;
|
||||
if (XR_FAILED(pfnEnumerateDisplayRefreshRatesFB(vr_session, 0, &refreshRateCount, NULL)) ||
|
||||
refreshRateCount == 0)
|
||||
return;
|
||||
|
||||
float* refreshRates = (float*)malloc(refreshRateCount * sizeof(float));
|
||||
if (!refreshRates)
|
||||
return;
|
||||
|
||||
if (XR_SUCCEEDED(pfnEnumerateDisplayRefreshRatesFB(vr_session, refreshRateCount, &refreshRateCount, refreshRates))) {
|
||||
char buffer[256];
|
||||
size_t used = 0;
|
||||
|
||||
buffer[0] = '\0';
|
||||
for (uint32_t i = 0; i < refreshRateCount && used < sizeof(buffer); i++) {
|
||||
int written = snprintf(buffer + used, sizeof(buffer) - used,
|
||||
"%s%.2f", i ? ", " : "", refreshRates[i]);
|
||||
if (written < 0)
|
||||
break;
|
||||
used += (size_t)written;
|
||||
}
|
||||
CONS_Printf("VR: Runtime supported refresh rates: %s Hz.\n", buffer);
|
||||
}
|
||||
|
||||
free(refreshRates);
|
||||
}
|
||||
|
||||
static void openxr_process_visibility_mesh(int eye, uint32_t width, uint32_t height)
|
||||
{
|
||||
if (!pfnGetVisibilityMaskKHR) return;
|
||||
|
||||
XrVisibilityMaskKHR mask = {XR_TYPE_VISIBILITY_MASK_KHR};
|
||||
XrResult res = pfnGetVisibilityMaskKHR(vr_session, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, eye, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &mask);
|
||||
if (XR_SUCCEEDED(res) && mask.vertexCapacityInput == 0) {
|
||||
mask.vertexCapacityInput = mask.vertexCountOutput;
|
||||
mask.indexCapacityInput = mask.indexCountOutput;
|
||||
mask.vertices = (XrVector2f*)malloc(mask.vertexCapacityInput * sizeof(XrVector2f));
|
||||
mask.indices = (uint32_t*)malloc(mask.indexCapacityInput * sizeof(uint32_t));
|
||||
|
||||
res = pfnGetVisibilityMaskKHR(vr_session, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, eye, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &mask);
|
||||
if (XR_SUCCEEDED(res) && mask.indexCountOutput > 0) {
|
||||
vrVisibleAreaVertexCount[eye] = mask.indexCountOutput;
|
||||
float* vertex = vrVisibleAreaVertices[eye] = (float*)malloc(mask.indexCountOutput * 3 * sizeof(float));
|
||||
float* uv = vrVisibleAreaUVs[eye] = (float*)malloc(mask.indexCountOutput * 2 * sizeof(float));
|
||||
|
||||
uint32_t texsize = 512;
|
||||
while (texsize < width || texsize < height) texsize <<= 1;
|
||||
|
||||
float xfix = 1.0f / ((float)texsize / (float)width);
|
||||
float yfix = 1.0f / ((float)texsize / (float)height);
|
||||
|
||||
for (uint32_t i = 0; i < mask.indexCountOutput; i++) {
|
||||
uint32_t idx = mask.indices[i];
|
||||
vertex[i*3] = (mask.vertices[idx].x - 0.5f) * 2.0f;
|
||||
vertex[i*3+1] = (mask.vertices[idx].y - 0.5f) * 2.0f;
|
||||
vertex[i*3+2] = 1.0f;
|
||||
|
||||
uv[i*2] = mask.vertices[idx].x * xfix;
|
||||
uv[i*2+1] = mask.vertices[idx].y * yfix;
|
||||
}
|
||||
}
|
||||
free(mask.vertices);
|
||||
free(mask.indices);
|
||||
}
|
||||
}
|
||||
|
||||
boolean VR_Init(void)
|
||||
{
|
||||
if (vr_started) return true;
|
||||
|
||||
// Check for explicit VR command-line flags
|
||||
boolean wantVR = (M_CheckParm("-openvr") || M_CheckParm("-vr") || vr_enabled);
|
||||
|
||||
if (!wantVR)
|
||||
{
|
||||
CONS_Printf("VR: No -vr flag detected, skipping VR initialization.\n");
|
||||
CONS_Printf("VR: Use '-vr' command-line flag to enable OpenXR.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
CONS_Printf("VR: Initializing OpenXR...\n");
|
||||
|
||||
XrResult res;
|
||||
|
||||
const boolean visibilityMaskSupported =
|
||||
VR_RuntimeExtensionSupported(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME);
|
||||
const boolean displayRefreshRateSupported =
|
||||
VR_RuntimeExtensionSupported(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
|
||||
const char* enabledExtensions[3];
|
||||
uint32_t enabledExtensionCount = 0;
|
||||
|
||||
enabledExtensions[enabledExtensionCount++] = XR_KHR_OPENGL_ENABLE_EXTENSION_NAME;
|
||||
if (visibilityMaskSupported)
|
||||
enabledExtensions[enabledExtensionCount++] = XR_KHR_VISIBILITY_MASK_EXTENSION_NAME;
|
||||
if (displayRefreshRateSupported)
|
||||
enabledExtensions[enabledExtensionCount++] = XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME;
|
||||
|
||||
XrInstanceCreateInfo createInfo = {XR_TYPE_INSTANCE_CREATE_INFO};
|
||||
createInfo.next = NULL;
|
||||
strncpy(createInfo.applicationInfo.applicationName, "SRB2Kart Blankart", XR_MAX_APPLICATION_NAME_SIZE);
|
||||
createInfo.applicationInfo.applicationVersion = 1;
|
||||
strncpy(createInfo.applicationInfo.engineName, "SRB2Kart", XR_MAX_ENGINE_NAME_SIZE);
|
||||
createInfo.applicationInfo.engineVersion = 1;
|
||||
createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
|
||||
createInfo.enabledExtensionCount = enabledExtensionCount;
|
||||
createInfo.enabledExtensionNames = enabledExtensions;
|
||||
createInfo.createFlags = 0;
|
||||
createInfo.enabledApiLayerCount = 0;
|
||||
createInfo.enabledApiLayerNames = NULL;
|
||||
|
||||
res = xrCreateInstance(&createInfo, &vr_instance);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: xrCreateInstance with optional extensions failed (XrResult=%d), retrying with OpenGL only...\n", (int)res);
|
||||
createInfo.enabledExtensionCount = 1;
|
||||
createInfo.enabledExtensionNames = enabledExtensions;
|
||||
displayRefreshRateExtensionEnabled = false;
|
||||
res = xrCreateInstance(&createInfo, &vr_instance);
|
||||
} else {
|
||||
displayRefreshRateExtensionEnabled = displayRefreshRateSupported;
|
||||
}
|
||||
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to create OpenXR instance (XrResult=%d).\n", (int)res);
|
||||
CONS_Printf("VR: Is an OpenXR runtime (Monado, SteamVR, etc.) installed and active?\n");
|
||||
CONS_Printf("VR: Check that XR_RUNTIME_JSON is set or /etc/xdg/openxr/1/active_runtime.json exists.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
CONS_Printf("VR: OpenXR instance created successfully.\n");
|
||||
|
||||
// Print runtime info
|
||||
XrInstanceProperties instanceProps = {XR_TYPE_INSTANCE_PROPERTIES};
|
||||
instanceProps.next = NULL;
|
||||
if (XR_SUCCEEDED(xrGetInstanceProperties(vr_instance, &instanceProps))) {
|
||||
CONS_Printf("VR: Runtime: %s (version %u.%u.%u)\n",
|
||||
instanceProps.runtimeName,
|
||||
XR_VERSION_MAJOR(instanceProps.runtimeVersion),
|
||||
XR_VERSION_MINOR(instanceProps.runtimeVersion),
|
||||
XR_VERSION_PATCH(instanceProps.runtimeVersion));
|
||||
}
|
||||
|
||||
xrGetInstanceProcAddr(vr_instance, "xrGetOpenGLGraphicsRequirementsKHR", (PFN_xrVoidFunction*)&pfnGetOpenGLGraphicsRequirementsKHR);
|
||||
xrGetInstanceProcAddr(vr_instance, "xrGetVisibilityMaskKHR", (PFN_xrVoidFunction*)&pfnGetVisibilityMaskKHR);
|
||||
if (displayRefreshRateExtensionEnabled) {
|
||||
xrGetInstanceProcAddr(vr_instance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)&pfnEnumerateDisplayRefreshRatesFB);
|
||||
xrGetInstanceProcAddr(vr_instance, "xrGetDisplayRefreshRateFB", (PFN_xrVoidFunction*)&pfnGetDisplayRefreshRateFB);
|
||||
xrGetInstanceProcAddr(vr_instance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)&pfnRequestDisplayRefreshRateFB);
|
||||
}
|
||||
|
||||
XrSystemGetInfo systemInfo = {XR_TYPE_SYSTEM_GET_INFO};
|
||||
systemInfo.next = NULL;
|
||||
systemInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
||||
res = xrGetSystem(vr_instance, &systemInfo, &vr_system_id);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to get OpenXR system (XrResult=%d). Is a headset connected?\n", (int)res);
|
||||
xrDestroyInstance(vr_instance);
|
||||
vr_instance = XR_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
|
||||
CONS_Printf("VR: OpenXR system acquired (systemId=%llu).\n", (unsigned long long)vr_system_id);
|
||||
|
||||
if (pfnGetOpenGLGraphicsRequirementsKHR) {
|
||||
XrGraphicsRequirementsOpenGLKHR graphicsRequirements = {XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR};
|
||||
graphicsRequirements.next = NULL;
|
||||
res = pfnGetOpenGLGraphicsRequirementsKHR(vr_instance, vr_system_id, &graphicsRequirements);
|
||||
if (XR_SUCCEEDED(res)) {
|
||||
CONS_Printf("VR: OpenGL requirements: min=%u.%u max=%u.%u\n",
|
||||
XR_VERSION_MAJOR(graphicsRequirements.minApiVersionSupported),
|
||||
XR_VERSION_MINOR(graphicsRequirements.minApiVersionSupported),
|
||||
XR_VERSION_MAJOR(graphicsRequirements.maxApiVersionSupported),
|
||||
XR_VERSION_MINOR(graphicsRequirements.maxApiVersionSupported));
|
||||
}
|
||||
}
|
||||
|
||||
// Query the runtime's recommended per-eye resolution before creating the
|
||||
// OpenXR session, then resize Blankart's render surface to match it.
|
||||
uint32_t viewCount = 0;
|
||||
xrEnumerateViewConfigurationViews(vr_instance, vr_system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, NULL);
|
||||
|
||||
if (viewCount < 2) {
|
||||
CONS_Printf("VR: ERROR - Expected 2 views for stereo, got %u.\n", viewCount);
|
||||
VR_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
XrViewConfigurationView* configViews = (XrViewConfigurationView*)malloc(viewCount * sizeof(XrViewConfigurationView));
|
||||
for (uint32_t i = 0; i < viewCount; i++) {
|
||||
configViews[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
|
||||
configViews[i].next = NULL;
|
||||
}
|
||||
xrEnumerateViewConfigurationViews(vr_instance, vr_system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, configViews);
|
||||
|
||||
const uint32_t recommendedWidth = configViews[0].recommendedImageRectWidth;
|
||||
const uint32_t recommendedHeight = configViews[0].recommendedImageRectHeight;
|
||||
const uint32_t maxWidth = configViews[0].maxImageRectWidth;
|
||||
const uint32_t maxHeight = configViews[0].maxImageRectHeight;
|
||||
const int resolutionMode = VR_StartupResolutionMode();
|
||||
const float renderScale = VR_RenderScaleForMode(resolutionMode);
|
||||
|
||||
vr_render_width = (uint32_t)((float)recommendedWidth * renderScale + 0.5f);
|
||||
vr_render_height = (uint32_t)((float)recommendedHeight * renderScale + 0.5f);
|
||||
|
||||
if (maxWidth > 0 && vr_render_width > maxWidth)
|
||||
vr_render_width = maxWidth;
|
||||
if (maxHeight > 0 && vr_render_height > maxHeight)
|
||||
vr_render_height = maxHeight;
|
||||
if (vr_render_width < 1)
|
||||
vr_render_width = 1;
|
||||
if (vr_render_height < 1)
|
||||
vr_render_height = 1;
|
||||
|
||||
CONS_Printf("VR: Recommended render size: %ux%u (max: %ux%u); using %ux%u (%d%%).\n",
|
||||
recommendedWidth, recommendedHeight, maxWidth, maxHeight,
|
||||
vr_render_width, vr_render_height, (int)(renderScale * 100.0f + 0.5f));
|
||||
free(configViews);
|
||||
|
||||
if ((uint32_t)vid.width != vr_render_width || (uint32_t)vid.height != vr_render_height) {
|
||||
CONS_Printf("VR: Setting game render size to OpenXR eye size %ux%u.\n", vr_render_width, vr_render_height);
|
||||
VID_SetMode(VID_GetModeForSize((INT32)vr_render_width, (INT32)vr_render_height));
|
||||
}
|
||||
|
||||
SDL_PropertiesID props = SDL_GetWindowProperties(window);
|
||||
if (!props) {
|
||||
CONS_Printf("VR: Failed to get SDL window properties!\n");
|
||||
xrDestroyInstance(vr_instance);
|
||||
vr_instance = XR_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
XrGraphicsBindingOpenGLWin32KHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR};
|
||||
graphicsBinding.next = NULL;
|
||||
graphicsBinding.hDC = (HDC)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HDC_POINTER, NULL);
|
||||
graphicsBinding.hGLRC = (HGLRC)SDL_GL_GetCurrentContext();
|
||||
CONS_Printf("VR: Win32 graphics binding: hDC=%p, hGLRC=%p\n", (void*)graphicsBinding.hDC, (void*)graphicsBinding.hGLRC);
|
||||
#elif defined(__linux__)
|
||||
XrGraphicsBindingOpenGLXlibKHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR};
|
||||
graphicsBinding.next = NULL;
|
||||
graphicsBinding.xDisplay = (Display*)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
|
||||
|
||||
// Get the GLX context and drawable from the current context
|
||||
graphicsBinding.glxContext = glXGetCurrentContext();
|
||||
graphicsBinding.glxDrawable = glXGetCurrentDrawable();
|
||||
|
||||
// Get the visual ID and FB config from the current context
|
||||
if (graphicsBinding.xDisplay && graphicsBinding.glxContext) {
|
||||
// Query the FB config from the current context
|
||||
int fbConfigId = 0;
|
||||
glXQueryContext(graphicsBinding.xDisplay, graphicsBinding.glxContext, GLX_FBCONFIG_ID, &fbConfigId);
|
||||
|
||||
// Find the matching FB config
|
||||
int numConfigs = 0;
|
||||
int attribs[] = { GLX_FBCONFIG_ID, fbConfigId, None };
|
||||
GLXFBConfig* configs = glXChooseFBConfig(graphicsBinding.xDisplay,
|
||||
DefaultScreen(graphicsBinding.xDisplay), attribs, &numConfigs);
|
||||
if (configs && numConfigs > 0) {
|
||||
graphicsBinding.glxFBConfig = configs[0];
|
||||
|
||||
// Get visual ID from the FB config
|
||||
XVisualInfo* vi = glXGetVisualFromFBConfig(graphicsBinding.xDisplay, configs[0]);
|
||||
if (vi) {
|
||||
graphicsBinding.visualid = vi->visualid;
|
||||
XFree(vi);
|
||||
}
|
||||
XFree(configs);
|
||||
} else {
|
||||
CONS_Printf("VR: WARNING - Could not find matching GLX FB config.\n");
|
||||
graphicsBinding.glxFBConfig = NULL;
|
||||
graphicsBinding.visualid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CONS_Printf("VR: Xlib graphics binding: display=%p, context=%p, drawable=%lu, visualid=%lu\n",
|
||||
(void*)graphicsBinding.xDisplay,
|
||||
(void*)graphicsBinding.glxContext,
|
||||
(unsigned long)graphicsBinding.glxDrawable,
|
||||
(unsigned long)graphicsBinding.visualid);
|
||||
|
||||
if (!graphicsBinding.xDisplay || !graphicsBinding.glxContext) {
|
||||
CONS_Printf("VR: ERROR - X11 display or GLX context is NULL! Is the game running under X11?\n");
|
||||
CONS_Printf("VR: Wayland-native is not yet supported; try running with SDL_VIDEODRIVER=x11\n");
|
||||
xrDestroyInstance(vr_instance);
|
||||
vr_instance = XR_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
XrSessionCreateInfo sessionCreateInfo = {XR_TYPE_SESSION_CREATE_INFO};
|
||||
sessionCreateInfo.next = &graphicsBinding;
|
||||
sessionCreateInfo.systemId = vr_system_id;
|
||||
sessionCreateInfo.createFlags = 0;
|
||||
|
||||
res = xrCreateSession(vr_instance, &sessionCreateInfo, &vr_session);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to create OpenXR session (XrResult=%d).\n", (int)res);
|
||||
xrDestroyInstance(vr_instance);
|
||||
vr_instance = XR_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
|
||||
CONS_Printf("VR: OpenXR session created successfully.\n");
|
||||
|
||||
// Begin the session
|
||||
XrSessionBeginInfo beginInfo = {XR_TYPE_SESSION_BEGIN_INFO};
|
||||
beginInfo.next = NULL;
|
||||
beginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
||||
res = xrBeginSession(vr_session, &beginInfo);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to begin OpenXR session (XrResult=%d).\n", (int)res);
|
||||
xrDestroySession(vr_session);
|
||||
vr_session = XR_NULL_HANDLE;
|
||||
xrDestroyInstance(vr_instance);
|
||||
vr_instance = XR_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
|
||||
CONS_Printf("VR: OpenXR session begun.\n");
|
||||
VR_ConfigureDisplayRefreshRate();
|
||||
|
||||
XrReferenceSpaceCreateInfo spaceCreateInfo = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
|
||||
spaceCreateInfo.next = NULL;
|
||||
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
||||
spaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f;
|
||||
spaceCreateInfo.poseInReferenceSpace.orientation.x = 0.0f;
|
||||
spaceCreateInfo.poseInReferenceSpace.orientation.y = 0.0f;
|
||||
spaceCreateInfo.poseInReferenceSpace.orientation.z = 0.0f;
|
||||
spaceCreateInfo.poseInReferenceSpace.position.x = 0.0f;
|
||||
spaceCreateInfo.poseInReferenceSpace.position.y = 0.0f;
|
||||
spaceCreateInfo.poseInReferenceSpace.position.z = 0.0f;
|
||||
res = xrCreateReferenceSpace(vr_session, &spaceCreateInfo, &vr_local_space);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to create local reference space.\n");
|
||||
VR_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
|
||||
res = xrCreateReferenceSpace(vr_session, &spaceCreateInfo, &vr_view_space);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to create view reference space.\n");
|
||||
VR_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VR_InitSwapchains()) {
|
||||
CONS_Printf("VR: Failed to initialize swapchains!\n");
|
||||
VR_Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
CONS_Printf("VR: Swapchains initialized.\n");
|
||||
|
||||
if (pfnGetVisibilityMaskKHR) {
|
||||
openxr_process_visibility_mesh(0, vr_render_width, vr_render_height);
|
||||
openxr_process_visibility_mesh(1, vr_render_width, vr_render_height);
|
||||
CONS_Printf("VR: Visibility masks processed.\n");
|
||||
} else {
|
||||
CONS_Printf("VR: Visibility mask extension not available (cosmetic only).\n");
|
||||
}
|
||||
|
||||
vr_started = true;
|
||||
vr_enabled = true;
|
||||
CV_StealthSet(&cv_vidwait, "Off");
|
||||
CV_StealthSetValue(&cv_fpscap, -1);
|
||||
CV_StealthSet(&cv_ticrate, "Compact");
|
||||
CONS_Printf("VR: Desktop vsync disabled and FPS cap set to Unlimited; OpenXR xrWaitFrame will pace rendering.\n");
|
||||
CONS_Printf("VR: OpenXR initialization complete! VR is now active.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void VR_Shutdown(void)
|
||||
{
|
||||
if (!vr_started && vr_instance == XR_NULL_HANDLE) return;
|
||||
|
||||
if (vrVisibleAreaVertices[0]) { free(vrVisibleAreaVertices[0]); vrVisibleAreaVertices[0] = NULL; }
|
||||
if (vrVisibleAreaVertices[1]) { free(vrVisibleAreaVertices[1]); vrVisibleAreaVertices[1] = NULL; }
|
||||
if (vrVisibleAreaUVs[0]) { free(vrVisibleAreaUVs[0]); vrVisibleAreaUVs[0] = NULL; }
|
||||
if (vrVisibleAreaUVs[1]) { free(vrVisibleAreaUVs[1]); vrVisibleAreaUVs[1] = NULL; }
|
||||
|
||||
VR_DestroySwapchains();
|
||||
|
||||
if (vr_local_space != XR_NULL_HANDLE) xrDestroySpace(vr_local_space);
|
||||
if (vr_view_space != XR_NULL_HANDLE) xrDestroySpace(vr_view_space);
|
||||
if (vr_session != XR_NULL_HANDLE) xrDestroySession(vr_session);
|
||||
if (vr_instance != XR_NULL_HANDLE) xrDestroyInstance(vr_instance);
|
||||
|
||||
vr_local_space = XR_NULL_HANDLE;
|
||||
vr_view_space = XR_NULL_HANDLE;
|
||||
vr_session = XR_NULL_HANDLE;
|
||||
vr_instance = XR_NULL_HANDLE;
|
||||
vr_started = false;
|
||||
vr_enabled = false;
|
||||
vr_drawing_ui = false;
|
||||
displayRefreshRateExtensionEnabled = false;
|
||||
pfnEnumerateDisplayRefreshRatesFB = NULL;
|
||||
pfnGetDisplayRefreshRateFB = NULL;
|
||||
pfnRequestDisplayRefreshRateFB = NULL;
|
||||
}
|
||||
|
||||
void VR_ScaleViewMatrices(float player_scale, int skybox_scale)
|
||||
{
|
||||
memcpy(vrHMDScaledPoseMatrix, vrHMDPoseMatrix, sizeof(float) * 16);
|
||||
vrHMDScaledPoseMatrix[12] *= player_scale;
|
||||
vrHMDScaledPoseMatrix[13] *= player_scale;
|
||||
vrHMDScaledPoseMatrix[14] *= player_scale;
|
||||
|
||||
memcpy(vrScaledEyeViewMatrix[0], vrEyeViewMatrix[0], sizeof(float) * 16);
|
||||
memcpy(vrScaledEyeViewMatrix[1], vrEyeViewMatrix[1], sizeof(float) * 16);
|
||||
|
||||
vrScaledEyeViewMatrix[0][12] *= player_scale;
|
||||
vrScaledEyeViewMatrix[0][13] *= player_scale;
|
||||
vrScaledEyeViewMatrix[0][14] *= player_scale;
|
||||
vrScaledEyeViewMatrix[1][12] *= player_scale;
|
||||
vrScaledEyeViewMatrix[1][13] *= player_scale;
|
||||
vrScaledEyeViewMatrix[1][14] *= player_scale;
|
||||
|
||||
vrPlayerScale = player_scale;
|
||||
|
||||
memcpy(vrEyeSkyboxViewMatrix[0], vrScaledEyeViewMatrix[0], sizeof(float) * 16);
|
||||
memcpy(vrEyeSkyboxViewMatrix[1], vrScaledEyeViewMatrix[1], sizeof(float) * 16);
|
||||
|
||||
if(skybox_scale > 0)
|
||||
{
|
||||
vrEyeSkyboxViewMatrix[0][12] /= skybox_scale;
|
||||
vrEyeSkyboxViewMatrix[0][13] /= skybox_scale;
|
||||
vrEyeSkyboxViewMatrix[0][14] /= skybox_scale;
|
||||
vrEyeSkyboxViewMatrix[1][12] /= skybox_scale;
|
||||
vrEyeSkyboxViewMatrix[1][13] /= skybox_scale;
|
||||
vrEyeSkyboxViewMatrix[1][14] /= skybox_scale;
|
||||
}
|
||||
else
|
||||
{
|
||||
vrEyeSkyboxViewMatrix[0][12] =
|
||||
vrEyeSkyboxViewMatrix[0][13] =
|
||||
vrEyeSkyboxViewMatrix[0][14] =
|
||||
vrEyeSkyboxViewMatrix[1][12] =
|
||||
vrEyeSkyboxViewMatrix[1][13] =
|
||||
vrEyeSkyboxViewMatrix[1][14] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAVE_VR
|
||||
76
src/vr/vr_main.h
Normal file
76
src/vr/vr_main.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef __VR_MAIN_H__
|
||||
#define __VR_MAIN_H__
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "../doomdef.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define XR_USE_PLATFORM_WIN32
|
||||
#define XR_USE_GRAPHICS_API_OPENGL
|
||||
#include <windows.h>
|
||||
#elif defined(__linux__)
|
||||
#define XR_USE_PLATFORM_XLIB
|
||||
#define XR_USE_GRAPHICS_API_OPENGL
|
||||
#include <X11/Xlib.h>
|
||||
#include <GL/glx.h>
|
||||
#endif
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
#include <openxr/openxr_platform.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Global state
|
||||
extern int vr_current_eye;
|
||||
extern boolean vr_started;
|
||||
extern boolean vr_enabled;
|
||||
extern boolean vr_drawing_ui;
|
||||
|
||||
typedef enum {
|
||||
VR_PASS_NONE,
|
||||
VR_PASS_3D_LEFT,
|
||||
VR_PASS_3D_RIGHT,
|
||||
VR_PASS_UI
|
||||
} vr_render_pass_t;
|
||||
|
||||
extern vr_render_pass_t vr_render_pass;
|
||||
|
||||
extern XrInstance vr_instance;
|
||||
extern XrSession vr_session;
|
||||
extern XrSystemId vr_system_id;
|
||||
extern XrSpace vr_local_space;
|
||||
extern XrSpace vr_view_space;
|
||||
|
||||
// Global matrices (size 16)
|
||||
extern float vrHMDPoseMatrix[16];
|
||||
extern float vrHMDScaledPoseMatrix[16];
|
||||
extern float vrHMDPoseSkyboxMatrix[16];
|
||||
extern float vrEyeViewMatrix[2][16];
|
||||
extern float vrScaledEyeViewMatrix[2][16];
|
||||
extern float vrEyeSkyboxViewMatrix[2][16];
|
||||
extern float vrEyeProjMatrix[2][16];
|
||||
|
||||
extern int vrWorldScale[3];
|
||||
extern float vrPlayerScale;
|
||||
|
||||
extern uint32_t vr_render_width;
|
||||
extern uint32_t vr_render_height;
|
||||
|
||||
// Visibility mask
|
||||
extern float* vrVisibleAreaVertices[2];
|
||||
extern float* vrVisibleAreaUVs[2];
|
||||
extern uint32_t vrVisibleAreaVertexCount[2];
|
||||
|
||||
// Lifecycle
|
||||
boolean VR_Init(void);
|
||||
void VR_Shutdown(void);
|
||||
void VR_ScaleViewMatrices(float player_scale, int skybox_scale);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HAVE_VR
|
||||
#endif // __VR_MAIN_H__
|
||||
126
src/vr/vr_math.c
Normal file
126
src/vr/vr_math.c
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#include "vr_math.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
|
||||
#include <math.h>
|
||||
|
||||
void VR_MatrixInv(float* a, const float* b)
|
||||
{
|
||||
float x, y, z;
|
||||
// transpose of rotation matrix
|
||||
a[ 0] = b[ 0];
|
||||
a[ 5] = b[ 5];
|
||||
a[10] = b[10];
|
||||
x=b[1]; a[1]=b[4]; a[4]=x;
|
||||
x=b[2]; a[2]=b[8]; a[8]=x;
|
||||
x=b[6]; a[6]=b[9]; a[9]=x;
|
||||
|
||||
// copy projection part
|
||||
a[ 3] = b[ 3];
|
||||
a[ 7] = b[ 7];
|
||||
a[11] = b[11];
|
||||
a[15] = b[15];
|
||||
|
||||
// convert origin: new_pos = - new_rotation_matrix * old_pos
|
||||
x = (a[ 0]*b[12]) + (a[ 4]*b[13]) + (a[ 8]*b[14]);
|
||||
y = (a[ 1]*b[12]) + (a[ 5]*b[13]) + (a[ 9]*b[14]);
|
||||
z = (a[ 2]*b[12]) + (a[ 6]*b[13]) + (a[10]*b[14]);
|
||||
a[12] = -x;
|
||||
a[13] = -y;
|
||||
a[14] = -z;
|
||||
}
|
||||
|
||||
void VR_MatrixMultiply(float* out, const float* a, const float* b)
|
||||
{
|
||||
float r[16];
|
||||
|
||||
for (int col = 0; col < 4; col++) {
|
||||
for (int row = 0; row < 4; row++) {
|
||||
r[row + col * 4] =
|
||||
a[row + 0 * 4] * b[0 + col * 4] +
|
||||
a[row + 1 * 4] * b[1 + col * 4] +
|
||||
a[row + 2 * 4] * b[2 + col * 4] +
|
||||
a[row + 3 * 4] * b[3 + col * 4];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
out[i] = r[i];
|
||||
}
|
||||
|
||||
void VR_RotateVecByMat(const float* m, float* v)
|
||||
{
|
||||
float x = (m[0]*v[0]) + (m[4]*v[1]) + (m[ 8]*v[2]); // Note: corrected from original openvr_common.c
|
||||
float y = (m[1]*v[0]) + (m[5]*v[1]) + (m[ 9]*v[2]); // which used v[0] in all y places etc.
|
||||
float z = (m[2]*v[0]) + (m[6]*v[1]) + (m[10]*v[2]); // Wait, I should check how it was exactly.
|
||||
v[0] = x;
|
||||
v[1] = y;
|
||||
v[2] = z;
|
||||
}
|
||||
|
||||
void VR_PoseToMatrix(const XrPosef* pose, float* glMatrix)
|
||||
{
|
||||
// Convert quaternion to 3x3 rotation matrix
|
||||
float xx = pose->orientation.x * pose->orientation.x;
|
||||
float yy = pose->orientation.y * pose->orientation.y;
|
||||
float zz = pose->orientation.z * pose->orientation.z;
|
||||
float xy = pose->orientation.x * pose->orientation.y;
|
||||
float xz = pose->orientation.x * pose->orientation.z;
|
||||
float yz = pose->orientation.y * pose->orientation.z;
|
||||
float wx = pose->orientation.w * pose->orientation.x;
|
||||
float wy = pose->orientation.w * pose->orientation.y;
|
||||
float wz = pose->orientation.w * pose->orientation.z;
|
||||
|
||||
glMatrix[ 0] = 1.0f - 2.0f * (yy + zz);
|
||||
glMatrix[ 1] = 2.0f * (xy + wz);
|
||||
glMatrix[ 2] = 2.0f * (xz - wy);
|
||||
glMatrix[ 3] = 0.0f;
|
||||
|
||||
glMatrix[ 4] = 2.0f * (xy - wz);
|
||||
glMatrix[ 5] = 1.0f - 2.0f * (xx + zz);
|
||||
glMatrix[ 6] = 2.0f * (yz + wx);
|
||||
glMatrix[ 7] = 0.0f;
|
||||
|
||||
glMatrix[ 8] = 2.0f * (xz + wy);
|
||||
glMatrix[ 9] = 2.0f * (yz - wx);
|
||||
glMatrix[10] = 1.0f - 2.0f * (xx + yy);
|
||||
glMatrix[11] = 0.0f;
|
||||
|
||||
glMatrix[12] = pose->position.x;
|
||||
glMatrix[13] = pose->position.y;
|
||||
glMatrix[14] = pose->position.z;
|
||||
glMatrix[15] = 1.0f;
|
||||
}
|
||||
|
||||
void VR_FovToProjection(const XrFovf* fov, float nearZ, float farZ, float* glMatrix)
|
||||
{
|
||||
float tanLeft = tanf(fov->angleLeft);
|
||||
float tanRight = tanf(fov->angleRight);
|
||||
float tanDown = tanf(fov->angleDown);
|
||||
float tanUp = tanf(fov->angleUp);
|
||||
|
||||
float tanWidth = tanRight - tanLeft;
|
||||
float tanHeight = tanUp - tanDown;
|
||||
|
||||
glMatrix[ 0] = 2.0f / tanWidth;
|
||||
glMatrix[ 1] = 0.0f;
|
||||
glMatrix[ 2] = 0.0f;
|
||||
glMatrix[ 3] = 0.0f;
|
||||
|
||||
glMatrix[ 4] = 0.0f;
|
||||
glMatrix[ 5] = 2.0f / tanHeight;
|
||||
glMatrix[ 6] = 0.0f;
|
||||
glMatrix[ 7] = 0.0f;
|
||||
|
||||
glMatrix[ 8] = (tanRight + tanLeft) / tanWidth;
|
||||
glMatrix[ 9] = (tanUp + tanDown) / tanHeight;
|
||||
glMatrix[10] = -(farZ + nearZ) / (farZ - nearZ);
|
||||
glMatrix[11] = -1.0f;
|
||||
|
||||
glMatrix[12] = 0.0f;
|
||||
glMatrix[13] = 0.0f;
|
||||
glMatrix[14] = -(2.0f * farZ * nearZ) / (farZ - nearZ);
|
||||
glMatrix[15] = 0.0f;
|
||||
}
|
||||
|
||||
#endif // HAVE_VR
|
||||
33
src/vr/vr_math.h
Normal file
33
src/vr/vr_math.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef __VR_MATH_H__
|
||||
#define __VR_MATH_H__
|
||||
|
||||
#ifdef HAVE_VR
|
||||
|
||||
#include <openxr/openxr.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Invert a 4x4 matrix (assumes it's a rigid body transform: rotation + translation)
|
||||
// safe to use same pointer for a and b (a = inv(b))
|
||||
void VR_MatrixInv(float* a, const float* b);
|
||||
|
||||
// Multiply two OpenGL 4x4 matrices (column-major): out = a * b
|
||||
void VR_MatrixMultiply(float* out, const float* a, const float* b);
|
||||
|
||||
// Rotate a 3D vector by a 4x4 matrix
|
||||
void VR_RotateVecByMat(const float* m, float* v);
|
||||
|
||||
// Convert an OpenXR XrPosef to an OpenGL 4x4 matrix (column-major)
|
||||
void VR_PoseToMatrix(const XrPosef* pose, float* glMatrix);
|
||||
|
||||
// Create an asymmetric OpenGL projection matrix from OpenXR FOV tangents
|
||||
void VR_FovToProjection(const XrFovf* fov, float nearZ, float farZ, float* glMatrix);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HAVE_VR
|
||||
#endif // __VR_MATH_H__
|
||||
795
src/vr/vr_render.c
Normal file
795
src/vr/vr_render.c
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
#include "vr_render.h"
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "vr_math.h"
|
||||
#include "../console.h"
|
||||
#include "../screen.h"
|
||||
#include "../hardware/r_opengl/r_opengl.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#elif defined(__linux__)
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#endif
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef GL_FRAMEBUFFER
|
||||
#define GL_FRAMEBUFFER 0x8D40
|
||||
#define GL_COLOR_ATTACHMENT0 0x8CE0
|
||||
#define GL_DEPTH_ATTACHMENT 0x8D00
|
||||
#define GL_DEPTH_COMPONENT24 0x81A6
|
||||
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
|
||||
#endif
|
||||
|
||||
#ifndef GL_RENDERBUFFER
|
||||
#define GL_RENDERBUFFER 0x8D41
|
||||
#endif
|
||||
#ifndef GL_READ_FRAMEBUFFER
|
||||
#define GL_READ_FRAMEBUFFER 0x8CA8
|
||||
#endif
|
||||
#ifndef GL_DRAW_FRAMEBUFFER
|
||||
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
||||
#endif
|
||||
|
||||
typedef void (APIENTRY * PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers);
|
||||
typedef void (APIENTRY * PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer);
|
||||
typedef void (APIENTRY * PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
typedef void (APIENTRY * PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers);
|
||||
typedef void (APIENTRY * PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer);
|
||||
typedef void (APIENTRY * PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (APIENTRY * PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
||||
typedef void (APIENTRY * PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers);
|
||||
typedef void (APIENTRY * PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers);
|
||||
typedef GLenum (APIENTRY * PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target);
|
||||
typedef void (APIENTRY * PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
|
||||
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
|
||||
static PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = NULL;
|
||||
static PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = NULL;
|
||||
static PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = NULL;
|
||||
static PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers = NULL;
|
||||
static PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer = NULL;
|
||||
static PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = NULL;
|
||||
static PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer = NULL;
|
||||
static PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = NULL;
|
||||
static PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers = NULL;
|
||||
static PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus = NULL;
|
||||
static PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer = NULL;
|
||||
|
||||
XrSwapchain vr_swapchains[2] = {XR_NULL_HANDLE, XR_NULL_HANDLE};
|
||||
uint32_t vr_swapchain_lengths[2] = {0, 0};
|
||||
XrSwapchainImageOpenGLKHR* vr_swapchain_images[2] = {NULL, NULL};
|
||||
uint32_t* vr_framebuffers[2] = {NULL, NULL};
|
||||
uint32_t* vr_depthbuffers[2] = {NULL, NULL};
|
||||
|
||||
XrSwapchain vr_ui_swapchain = XR_NULL_HANDLE;
|
||||
uint32_t vr_ui_swapchain_length = 0;
|
||||
XrSwapchainImageOpenGLKHR* vr_ui_swapchain_images = NULL;
|
||||
uint32_t* vr_ui_framebuffers = NULL;
|
||||
|
||||
uint32_t vr_ui_width = 0;
|
||||
uint32_t vr_ui_height = 0;
|
||||
|
||||
enum {
|
||||
VR_DEFAULT_UI_SWAPCHAIN_WIDTH = 1920,
|
||||
VR_DEFAULT_UI_SWAPCHAIN_HEIGHT = 1080
|
||||
};
|
||||
|
||||
static void LoadFBOFunctions(void) {
|
||||
if (glGenFramebuffers) return;
|
||||
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)SDL_GL_GetProcAddress("glGenFramebuffers");
|
||||
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)SDL_GL_GetProcAddress("glBindFramebuffer");
|
||||
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)SDL_GL_GetProcAddress("glFramebufferTexture2D");
|
||||
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)SDL_GL_GetProcAddress("glGenRenderbuffers");
|
||||
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)SDL_GL_GetProcAddress("glBindRenderbuffer");
|
||||
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)SDL_GL_GetProcAddress("glRenderbufferStorage");
|
||||
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC)SDL_GL_GetProcAddress("glFramebufferRenderbuffer");
|
||||
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteFramebuffers");
|
||||
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteRenderbuffers");
|
||||
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)SDL_GL_GetProcAddress("glCheckFramebufferStatus");
|
||||
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC)SDL_GL_GetProcAddress("glBlitFramebuffer");
|
||||
}
|
||||
|
||||
boolean VR_InitSwapchains(void)
|
||||
{
|
||||
LoadFBOFunctions();
|
||||
if (!glGenFramebuffers) {
|
||||
CONS_Printf("VR_InitSwapchains: FBO extensions not found!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int eye = 0; eye < 2; eye++) {
|
||||
XrSwapchainCreateInfo swapchainCreateInfo = {XR_TYPE_SWAPCHAIN_CREATE_INFO};
|
||||
swapchainCreateInfo.next = NULL;
|
||||
swapchainCreateInfo.createFlags = 0;
|
||||
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
swapchainCreateInfo.format = GL_SRGB8_ALPHA8;
|
||||
swapchainCreateInfo.sampleCount = 1;
|
||||
swapchainCreateInfo.width = vr_render_width;
|
||||
swapchainCreateInfo.height = vr_render_height;
|
||||
swapchainCreateInfo.faceCount = 1;
|
||||
swapchainCreateInfo.arraySize = 1;
|
||||
swapchainCreateInfo.mipCount = 1;
|
||||
|
||||
XrResult res = xrCreateSwapchain(vr_session, &swapchainCreateInfo, &vr_swapchains[eye]);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to create swapchain %d (XrResult=%d).\n", eye, (int)res);
|
||||
return false;
|
||||
}
|
||||
|
||||
xrEnumerateSwapchainImages(vr_swapchains[eye], 0, &vr_swapchain_lengths[eye], NULL);
|
||||
CONS_Printf("VR: Swapchain %d has %u images.\n", eye, vr_swapchain_lengths[eye]);
|
||||
|
||||
vr_swapchain_images[eye] = (XrSwapchainImageOpenGLKHR*)malloc(vr_swapchain_lengths[eye] * sizeof(XrSwapchainImageOpenGLKHR));
|
||||
for (uint32_t i = 0; i < vr_swapchain_lengths[eye]; i++) {
|
||||
vr_swapchain_images[eye][i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
|
||||
vr_swapchain_images[eye][i].next = NULL;
|
||||
}
|
||||
xrEnumerateSwapchainImages(vr_swapchains[eye], vr_swapchain_lengths[eye], &vr_swapchain_lengths[eye], (XrSwapchainImageBaseHeader*)vr_swapchain_images[eye]);
|
||||
|
||||
vr_framebuffers[eye] = (uint32_t*)malloc(vr_swapchain_lengths[eye] * sizeof(uint32_t));
|
||||
vr_depthbuffers[eye] = (uint32_t*)malloc(vr_swapchain_lengths[eye] * sizeof(uint32_t));
|
||||
|
||||
for (uint32_t i = 0; i < vr_swapchain_lengths[eye]; i++) {
|
||||
glGenFramebuffers(1, &vr_framebuffers[eye][i]);
|
||||
glGenRenderbuffers(1, &vr_depthbuffers[eye][i]);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, vr_framebuffers[eye][i]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, vr_swapchain_images[eye][i].image, 0);
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, vr_depthbuffers[eye][i]);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, vr_render_width, vr_render_height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, vr_depthbuffers[eye][i]);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
CONS_Printf("VR: WARNING - FBO %u for eye %d is incomplete (status=0x%x)!\n", i, eye, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
// Keep UI at a stable 16:9 panel size. Eye targets can be square on canted HMDs.
|
||||
vr_ui_width = VR_DEFAULT_UI_SWAPCHAIN_WIDTH;
|
||||
vr_ui_height = VR_DEFAULT_UI_SWAPCHAIN_HEIGHT;
|
||||
|
||||
XrSwapchainCreateInfo uiSwapchainCreateInfo = {XR_TYPE_SWAPCHAIN_CREATE_INFO};
|
||||
uiSwapchainCreateInfo.arraySize = 1;
|
||||
uiSwapchainCreateInfo.format = GL_SRGB8_ALPHA8;
|
||||
uiSwapchainCreateInfo.width = vr_ui_width;
|
||||
uiSwapchainCreateInfo.height = vr_ui_height;
|
||||
uiSwapchainCreateInfo.mipCount = 1;
|
||||
uiSwapchainCreateInfo.faceCount = 1;
|
||||
uiSwapchainCreateInfo.sampleCount = 1;
|
||||
uiSwapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT;
|
||||
|
||||
XrResult res = xrCreateSwapchain(vr_session, &uiSwapchainCreateInfo, &vr_ui_swapchain);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: Failed to create UI swapchain.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
xrEnumerateSwapchainImages(vr_ui_swapchain, 0, &vr_ui_swapchain_length, NULL);
|
||||
vr_ui_swapchain_images = (XrSwapchainImageOpenGLKHR*)malloc(vr_ui_swapchain_length * sizeof(XrSwapchainImageOpenGLKHR));
|
||||
for (uint32_t i = 0; i < vr_ui_swapchain_length; i++) {
|
||||
vr_ui_swapchain_images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
|
||||
vr_ui_swapchain_images[i].next = NULL;
|
||||
}
|
||||
xrEnumerateSwapchainImages(vr_ui_swapchain, vr_ui_swapchain_length, &vr_ui_swapchain_length, (XrSwapchainImageBaseHeader*)vr_ui_swapchain_images);
|
||||
|
||||
vr_ui_framebuffers = (uint32_t*)malloc(vr_ui_swapchain_length * sizeof(uint32_t));
|
||||
|
||||
for (uint32_t i = 0; i < vr_ui_swapchain_length; i++) {
|
||||
glGenFramebuffers(1, &vr_ui_framebuffers[i]);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, vr_ui_framebuffers[i]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, vr_ui_swapchain_images[i].image, 0);
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||
CONS_Printf("VR: Failed to create UI framebuffer %d\n", i);
|
||||
return false;
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
CONS_Printf("VR: UI swapchain created (%ux%u, %u images).\n",
|
||||
vr_ui_width, vr_ui_height, vr_ui_swapchain_length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static XrPosef cachedUIPose;
|
||||
static boolean cachedUIPoseValid = false;
|
||||
|
||||
void VR_DestroySwapchains(void)
|
||||
{
|
||||
for (int eye = 0; eye < 2; eye++) {
|
||||
if (vr_framebuffers[eye] && glDeleteFramebuffers) {
|
||||
glDeleteFramebuffers(vr_swapchain_lengths[eye], vr_framebuffers[eye]);
|
||||
free(vr_framebuffers[eye]);
|
||||
vr_framebuffers[eye] = NULL;
|
||||
}
|
||||
if (vr_depthbuffers[eye] && glDeleteRenderbuffers) {
|
||||
glDeleteRenderbuffers(vr_swapchain_lengths[eye], vr_depthbuffers[eye]);
|
||||
free(vr_depthbuffers[eye]);
|
||||
vr_depthbuffers[eye] = NULL;
|
||||
}
|
||||
if (vr_swapchain_images[eye]) {
|
||||
free(vr_swapchain_images[eye]);
|
||||
vr_swapchain_images[eye] = NULL;
|
||||
}
|
||||
if (vr_swapchains[eye] != XR_NULL_HANDLE) {
|
||||
xrDestroySwapchain(vr_swapchains[eye]);
|
||||
vr_swapchains[eye] = XR_NULL_HANDLE;
|
||||
}
|
||||
vr_swapchain_lengths[eye] = 0;
|
||||
}
|
||||
|
||||
if (vr_ui_framebuffers && glDeleteFramebuffers) {
|
||||
glDeleteFramebuffers(vr_ui_swapchain_length, vr_ui_framebuffers);
|
||||
free(vr_ui_framebuffers);
|
||||
vr_ui_framebuffers = NULL;
|
||||
}
|
||||
if (vr_ui_swapchain_images) {
|
||||
free(vr_ui_swapchain_images);
|
||||
vr_ui_swapchain_images = NULL;
|
||||
}
|
||||
if (vr_ui_swapchain != XR_NULL_HANDLE) {
|
||||
xrDestroySwapchain(vr_ui_swapchain);
|
||||
vr_ui_swapchain = XR_NULL_HANDLE;
|
||||
}
|
||||
vr_ui_swapchain_length = 0;
|
||||
vr_ui_width = 0;
|
||||
vr_ui_height = 0;
|
||||
cachedUIPoseValid = false;
|
||||
}
|
||||
|
||||
// Cached per-frame state
|
||||
static XrFrameState frameState = {XR_TYPE_FRAME_STATE};
|
||||
static uint32_t swapchainImageIndex[2];
|
||||
static uint32_t uiSwapchainImageIndex;
|
||||
static boolean vr_frame_begun = false;
|
||||
static boolean eyeSwapchainAcquired[2] = {false, false};
|
||||
static boolean eyeSwapchainRendered[2] = {false, false};
|
||||
static boolean uiSwapchainAcquired = false;
|
||||
static XrView cachedViews[2]; // Cache the located views for EndFrame
|
||||
static boolean cachedViewsValid = false;
|
||||
static XrPosef cachedHeadPose;
|
||||
static boolean cachedHeadPoseValid = false;
|
||||
static XrSessionState vr_session_state = XR_SESSION_STATE_UNKNOWN;
|
||||
static boolean vr_session_running = false;
|
||||
|
||||
// Poll and handle OpenXR events (session state machine)
|
||||
static void VR_PollEvents(void)
|
||||
{
|
||||
if (!vr_started) return;
|
||||
|
||||
XrEventDataBuffer eventData = {XR_TYPE_EVENT_DATA_BUFFER};
|
||||
eventData.next = NULL;
|
||||
|
||||
while (xrPollEvent(vr_instance, &eventData) == XR_SUCCESS) {
|
||||
switch (eventData.type) {
|
||||
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
|
||||
XrEventDataSessionStateChanged* stateChanged = (XrEventDataSessionStateChanged*)&eventData;
|
||||
vr_session_state = stateChanged->state;
|
||||
CONS_Printf("VR: Session state changed to %d\n", (int)vr_session_state);
|
||||
|
||||
switch (vr_session_state) {
|
||||
case XR_SESSION_STATE_READY:
|
||||
// Session is already begun in VR_Init, but handle if needed
|
||||
vr_session_running = true;
|
||||
break;
|
||||
case XR_SESSION_STATE_SYNCHRONIZED:
|
||||
case XR_SESSION_STATE_VISIBLE:
|
||||
case XR_SESSION_STATE_FOCUSED:
|
||||
vr_session_running = true;
|
||||
break;
|
||||
case XR_SESSION_STATE_STOPPING:
|
||||
xrEndSession(vr_session);
|
||||
vr_session_running = false;
|
||||
break;
|
||||
case XR_SESSION_STATE_LOSS_PENDING:
|
||||
case XR_SESSION_STATE_EXITING:
|
||||
vr_session_running = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Reset for next event
|
||||
eventData.type = XR_TYPE_EVENT_DATA_BUFFER;
|
||||
eventData.next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void VR_SetIdentityMatrix(float* m)
|
||||
{
|
||||
memset(m, 0, sizeof(float) * 16);
|
||||
m[0] = 1.0f;
|
||||
m[5] = 1.0f;
|
||||
m[10] = 1.0f;
|
||||
m[15] = 1.0f;
|
||||
}
|
||||
|
||||
static void VR_ScaleMatrixTranslation(float* m, float scale)
|
||||
{
|
||||
m[12] *= scale;
|
||||
m[13] *= scale;
|
||||
m[14] *= scale;
|
||||
}
|
||||
|
||||
static float VR_YawFromQuaternion(const XrQuaternionf* q)
|
||||
{
|
||||
return atan2f(2.0f * (q->w * q->y + q->x * q->z),
|
||||
1.0f - 2.0f * (q->y * q->y + q->x * q->x));
|
||||
}
|
||||
|
||||
static float VR_RollFromQuaternion(const XrQuaternionf* q)
|
||||
{
|
||||
return atan2f(2.0f * (q->w * q->z + q->x * q->y),
|
||||
1.0f - 2.0f * (q->z * q->z + q->y * q->y));
|
||||
}
|
||||
|
||||
static XrQuaternionf VR_QuaternionMultiply(const XrQuaternionf* a, const XrQuaternionf* b)
|
||||
{
|
||||
XrQuaternionf q;
|
||||
|
||||
q.x = a->w * b->x + a->x * b->w + a->y * b->z - a->z * b->y;
|
||||
q.y = a->w * b->y - a->x * b->z + a->y * b->w + a->z * b->x;
|
||||
q.z = a->w * b->z + a->x * b->y - a->y * b->x + a->z * b->w;
|
||||
q.w = a->w * b->w - a->x * b->x - a->y * b->y - a->z * b->z;
|
||||
return q;
|
||||
}
|
||||
|
||||
static XrQuaternionf VR_QuaternionFromYaw(float yaw)
|
||||
{
|
||||
XrQuaternionf q;
|
||||
const float halfYaw = yaw * 0.5f;
|
||||
|
||||
q.x = 0.0f;
|
||||
q.y = sinf(halfYaw);
|
||||
q.z = 0.0f;
|
||||
q.w = cosf(halfYaw);
|
||||
return q;
|
||||
}
|
||||
|
||||
static XrQuaternionf VR_QuaternionFromRoll(float roll)
|
||||
{
|
||||
XrQuaternionf q;
|
||||
const float halfRoll = roll * 0.5f;
|
||||
|
||||
q.x = 0.0f;
|
||||
q.y = 0.0f;
|
||||
q.z = sinf(halfRoll);
|
||||
q.w = cosf(halfRoll);
|
||||
return q;
|
||||
}
|
||||
|
||||
static XrQuaternionf VR_QuaternionFromYawRoll(float yaw, float roll)
|
||||
{
|
||||
XrQuaternionf yawQ = VR_QuaternionFromYaw(yaw);
|
||||
XrQuaternionf rollQ = VR_QuaternionFromRoll(roll);
|
||||
return VR_QuaternionMultiply(&yawQ, &rollQ);
|
||||
}
|
||||
|
||||
static XrPosef VR_MakeUIPose(float distance)
|
||||
{
|
||||
XrPosef pose;
|
||||
|
||||
memset(&pose, 0, sizeof(pose));
|
||||
pose.orientation.w = 1.0f;
|
||||
pose.position.z = -distance;
|
||||
|
||||
if (!cachedHeadPoseValid)
|
||||
return pose;
|
||||
|
||||
const float yaw = VR_YawFromQuaternion(&cachedHeadPose.orientation);
|
||||
const float roll = VR_RollFromQuaternion(&cachedHeadPose.orientation);
|
||||
pose.orientation = VR_QuaternionFromYawRoll(yaw, roll);
|
||||
pose.position = cachedHeadPose.position;
|
||||
pose.position.x += -sinf(yaw) * distance;
|
||||
pose.position.z += -cosf(yaw) * distance;
|
||||
|
||||
return pose;
|
||||
}
|
||||
|
||||
static int VR_WorldScale(void)
|
||||
{
|
||||
int mode = cv_vrscale.string ? cv_vrscale.value : 1;
|
||||
|
||||
if (mode < 0)
|
||||
mode = 0;
|
||||
else if (mode > 2)
|
||||
mode = 2;
|
||||
|
||||
return vrWorldScale[mode];
|
||||
}
|
||||
|
||||
boolean VR_BeginFrame(void)
|
||||
{
|
||||
if (!vr_started) return false;
|
||||
|
||||
// Poll events first — this drives the session state machine
|
||||
VR_PollEvents();
|
||||
|
||||
if (!vr_session_running) return false;
|
||||
|
||||
cachedViewsValid = false;
|
||||
cachedHeadPoseValid = false;
|
||||
vr_frame_begun = false;
|
||||
eyeSwapchainAcquired[0] = false;
|
||||
eyeSwapchainAcquired[1] = false;
|
||||
eyeSwapchainRendered[0] = false;
|
||||
eyeSwapchainRendered[1] = false;
|
||||
uiSwapchainAcquired = false;
|
||||
|
||||
XrFrameWaitInfo frameWaitInfo = {XR_TYPE_FRAME_WAIT_INFO};
|
||||
frameWaitInfo.next = NULL;
|
||||
frameState.type = XR_TYPE_FRAME_STATE;
|
||||
frameState.next = NULL;
|
||||
XrResult res = xrWaitFrame(vr_session, &frameWaitInfo, &frameState);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: xrWaitFrame failed (XrResult=%d)\n", (int)res);
|
||||
return false;
|
||||
}
|
||||
|
||||
XrFrameBeginInfo frameBeginInfo = {XR_TYPE_FRAME_BEGIN_INFO};
|
||||
frameBeginInfo.next = NULL;
|
||||
res = xrBeginFrame(vr_session, &frameBeginInfo);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: xrBeginFrame failed (XrResult=%d)\n", (int)res);
|
||||
return false;
|
||||
}
|
||||
vr_frame_begun = true;
|
||||
|
||||
// Locate views to get per-eye poses and FOVs
|
||||
XrViewLocateInfo viewLocateInfo = {XR_TYPE_VIEW_LOCATE_INFO};
|
||||
viewLocateInfo.next = NULL;
|
||||
viewLocateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
||||
viewLocateInfo.displayTime = frameState.predictedDisplayTime;
|
||||
viewLocateInfo.space = vr_local_space;
|
||||
|
||||
XrViewState viewState = {XR_TYPE_VIEW_STATE};
|
||||
viewState.next = NULL;
|
||||
uint32_t viewCountOutput = 0;
|
||||
cachedViews[0].type = XR_TYPE_VIEW; cachedViews[0].next = NULL;
|
||||
cachedViews[1].type = XR_TYPE_VIEW; cachedViews[1].next = NULL;
|
||||
|
||||
res = xrLocateViews(vr_session, &viewLocateInfo, &viewState, 2, &viewCountOutput, cachedViews);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: xrLocateViews failed (XrResult=%d)\n", (int)res);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (viewCountOutput == 2 &&
|
||||
(viewState.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) &&
|
||||
(viewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT)) {
|
||||
cachedViewsValid = true;
|
||||
|
||||
XrSpaceLocation headLocation = {XR_TYPE_SPACE_LOCATION};
|
||||
res = xrLocateSpace(vr_view_space, vr_local_space, frameState.predictedDisplayTime, &headLocation);
|
||||
if (XR_SUCCEEDED(res) &&
|
||||
(headLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) &&
|
||||
(headLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)) {
|
||||
cachedHeadPose = headLocation.pose;
|
||||
cachedHeadPoseValid = true;
|
||||
} else {
|
||||
cachedHeadPose = cachedViews[0].pose;
|
||||
cachedHeadPose.position.x = (cachedViews[0].pose.position.x + cachedViews[1].pose.position.x) * 0.5f;
|
||||
cachedHeadPose.position.y = (cachedViews[0].pose.position.y + cachedViews[1].pose.position.y) * 0.5f;
|
||||
cachedHeadPose.position.z = (cachedViews[0].pose.position.z + cachedViews[1].pose.position.z) * 0.5f;
|
||||
cachedHeadPoseValid = true;
|
||||
}
|
||||
|
||||
float headPoseMatrix[16];
|
||||
float headInverseMeters[16];
|
||||
VR_PoseToMatrix(&cachedHeadPose, headPoseMatrix);
|
||||
VR_MatrixInv(headInverseMeters, headPoseMatrix);
|
||||
|
||||
const float worldScale = VR_WorldScale();
|
||||
const int poseMode = cv_vrposemode.value;
|
||||
|
||||
if (poseMode == 3)
|
||||
memcpy(vrHMDPoseMatrix, headPoseMatrix, sizeof(float) * 16);
|
||||
else
|
||||
memcpy(vrHMDPoseMatrix, headInverseMeters, sizeof(float) * 16);
|
||||
VR_ScaleMatrixTranslation(vrHMDPoseMatrix, worldScale);
|
||||
|
||||
memcpy(vrHMDScaledPoseMatrix, vrHMDPoseMatrix, sizeof(float) * 16);
|
||||
VR_ScaleMatrixTranslation(vrHMDScaledPoseMatrix, vrPlayerScale);
|
||||
|
||||
memcpy(vrHMDPoseSkyboxMatrix, vrHMDPoseMatrix, sizeof(float) * 16);
|
||||
vrHMDPoseSkyboxMatrix[12] = 0.0f;
|
||||
vrHMDPoseSkyboxMatrix[13] = 0.0f;
|
||||
vrHMDPoseSkyboxMatrix[14] = 0.0f;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
VR_FovToProjection(&cachedViews[i].fov, 0.9f, 32768.0f, vrEyeProjMatrix[i]);
|
||||
|
||||
float eyePoseMatrix[16];
|
||||
VR_PoseToMatrix(&cachedViews[i].pose, eyePoseMatrix);
|
||||
|
||||
if (poseMode == 1 || poseMode == 3) {
|
||||
float eyeToHeadMatrix[16];
|
||||
|
||||
// Match the old OpenVR path: keep the HMD pose and per-eye
|
||||
// head-to-eye transform separate, then multiply them in shader.
|
||||
VR_MatrixMultiply(eyeToHeadMatrix, headInverseMeters, eyePoseMatrix);
|
||||
VR_MatrixInv(vrEyeViewMatrix[i], eyeToHeadMatrix);
|
||||
} else if (poseMode == 2) {
|
||||
memcpy(vrEyeViewMatrix[i], eyePoseMatrix, sizeof(float) * 16);
|
||||
} else {
|
||||
// SDK-style path: render each eye from the full located view.
|
||||
VR_MatrixInv(vrEyeViewMatrix[i], eyePoseMatrix);
|
||||
}
|
||||
VR_ScaleMatrixTranslation(vrEyeViewMatrix[i], worldScale);
|
||||
}
|
||||
|
||||
VR_ScaleViewMatrices(vrPlayerScale, cv_vrdisableskystereo.value ? 0 : 1);
|
||||
} else {
|
||||
VR_SetIdentityMatrix(vrHMDPoseMatrix);
|
||||
VR_SetIdentityMatrix(vrHMDScaledPoseMatrix);
|
||||
VR_SetIdentityMatrix(vrHMDPoseSkyboxMatrix);
|
||||
VR_SetIdentityMatrix(vrEyeViewMatrix[0]);
|
||||
VR_SetIdentityMatrix(vrEyeViewMatrix[1]);
|
||||
VR_SetIdentityMatrix(vrScaledEyeViewMatrix[0]);
|
||||
VR_SetIdentityMatrix(vrScaledEyeViewMatrix[1]);
|
||||
VR_SetIdentityMatrix(vrEyeSkyboxViewMatrix[0]);
|
||||
VR_SetIdentityMatrix(vrEyeSkyboxViewMatrix[1]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean VR_SetEye(int eye)
|
||||
{
|
||||
if (!vr_started || !vr_session_running || !vr_frame_begun) return false;
|
||||
if (eye < 0 || eye > 1) return false;
|
||||
|
||||
vr_current_eye = eye;
|
||||
vr_drawing_ui = false;
|
||||
vr_render_pass = (eye == 0) ? VR_PASS_3D_LEFT : VR_PASS_3D_RIGHT;
|
||||
|
||||
XrSwapchainImageAcquireInfo acquireInfo = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
|
||||
acquireInfo.next = NULL;
|
||||
XrResult res = xrAcquireSwapchainImage(vr_swapchains[eye], &acquireInfo, &swapchainImageIndex[eye]);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: xrAcquireSwapchainImage failed for eye %d (XrResult=%d)\n", eye, (int)res);
|
||||
return false;
|
||||
}
|
||||
eyeSwapchainAcquired[eye] = true;
|
||||
|
||||
XrSwapchainImageWaitInfo waitInfo = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
|
||||
waitInfo.next = NULL;
|
||||
waitInfo.timeout = XR_INFINITE_DURATION;
|
||||
res = xrWaitSwapchainImage(vr_swapchains[eye], &waitInfo);
|
||||
if (XR_FAILED(res)) {
|
||||
CONS_Printf("VR: xrWaitSwapchainImage failed for eye %d (XrResult=%d)\n", eye, (int)res);
|
||||
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
|
||||
releaseInfo.next = NULL;
|
||||
xrReleaseSwapchainImage(vr_swapchains[eye], &releaseInfo);
|
||||
eyeSwapchainAcquired[eye] = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, vr_framebuffers[eye][swapchainImageIndex[eye]]);
|
||||
glViewport(0, 0, vr_render_width, vr_render_height);
|
||||
SetModelView((GLint)vr_render_width, (GLint)vr_render_height);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VR_ReleaseEye(int eye)
|
||||
{
|
||||
if (!vr_started || !vr_session_running || eye < 0 || eye > 1 || !eyeSwapchainAcquired[eye]) return;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
|
||||
releaseInfo.next = NULL;
|
||||
xrReleaseSwapchainImage(vr_swapchains[eye], &releaseInfo);
|
||||
eyeSwapchainAcquired[eye] = false;
|
||||
eyeSwapchainRendered[eye] = true;
|
||||
}
|
||||
|
||||
void VR_BindDefaultFramebuffer(void)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
boolean VR_MirrorEyeToDefaultFramebuffer(int width, int height)
|
||||
{
|
||||
if (!vr_started || !vr_session_running || vr_current_eye < 0 || vr_current_eye > 1)
|
||||
return false;
|
||||
if (!eyeSwapchainAcquired[vr_current_eye] || !glBlitFramebuffer)
|
||||
return false;
|
||||
if (width <= 0 || height <= 0)
|
||||
return false;
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, vr_framebuffers[vr_current_eye][swapchainImageIndex[vr_current_eye]]);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glViewport(0, 0, width, height);
|
||||
glBlitFramebuffer(0, 0, (GLint)vr_render_width, (GLint)vr_render_height,
|
||||
0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VR_BeginInEyeUI(void)
|
||||
{
|
||||
if (!vr_started || !vr_session_running || !vr_frame_begun) return;
|
||||
if (vr_current_eye < 0 || vr_current_eye > 1 || !eyeSwapchainAcquired[vr_current_eye]) return;
|
||||
|
||||
vr_drawing_ui = true;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, vr_framebuffers[vr_current_eye][swapchainImageIndex[vr_current_eye]]);
|
||||
glViewport(0, 0, vr_render_width, vr_render_height);
|
||||
GL_UnSetShader();
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
}
|
||||
|
||||
boolean VR_BindUISwapchain(void)
|
||||
{
|
||||
if (!vr_started || !vr_session_running || !vr_frame_begun || vr_ui_swapchain == XR_NULL_HANDLE) return false;
|
||||
|
||||
if (!uiSwapchainAcquired) {
|
||||
XrSwapchainImageAcquireInfo acquireInfo = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
|
||||
acquireInfo.next = NULL;
|
||||
XrResult res = xrAcquireSwapchainImage(vr_ui_swapchain, &acquireInfo, &uiSwapchainImageIndex);
|
||||
if (XR_FAILED(res)) return false;
|
||||
|
||||
XrSwapchainImageWaitInfo waitInfo = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
|
||||
waitInfo.next = NULL;
|
||||
waitInfo.timeout = XR_INFINITE_DURATION;
|
||||
res = xrWaitSwapchainImage(vr_ui_swapchain, &waitInfo);
|
||||
if (XR_FAILED(res)) {
|
||||
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
|
||||
releaseInfo.next = NULL;
|
||||
xrReleaseSwapchainImage(vr_ui_swapchain, &releaseInfo);
|
||||
return false;
|
||||
}
|
||||
|
||||
uiSwapchainAcquired = true;
|
||||
}
|
||||
|
||||
vr_render_pass = VR_PASS_UI;
|
||||
vr_drawing_ui = true;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, vr_ui_framebuffers[uiSwapchainImageIndex]);
|
||||
// Keep the hardware 2D renderer's logical screen size unchanged. Its UI
|
||||
// patch and screen-texture paths key off screen_width/screen_height, while
|
||||
// the OpenXR swapchain can still use its full physical viewport.
|
||||
SetModelView((GLint)vid.width, (GLint)vid.height);
|
||||
glViewport(0, 0, vr_ui_width, vr_ui_height);
|
||||
GL_UnSetShader();
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VR_EndFrame(void)
|
||||
{
|
||||
if (!vr_started || !vr_session_running || !vr_frame_begun) return;
|
||||
|
||||
XrCompositionLayerProjectionView projectionViews[2];
|
||||
for (int i = 0; i < 2; i++) {
|
||||
projectionViews[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
|
||||
projectionViews[i].next = NULL;
|
||||
|
||||
// Use the ACTUAL tracked view poses, not hardcoded placeholders
|
||||
if (cachedViewsValid) {
|
||||
projectionViews[i].pose = cachedViews[i].pose;
|
||||
projectionViews[i].fov = cachedViews[i].fov;
|
||||
} else {
|
||||
// Fallback identity pose (should rarely happen)
|
||||
projectionViews[i].pose.orientation.w = 1.0f;
|
||||
projectionViews[i].pose.orientation.x = 0.0f;
|
||||
projectionViews[i].pose.orientation.y = 0.0f;
|
||||
projectionViews[i].pose.orientation.z = 0.0f;
|
||||
projectionViews[i].pose.position.x = 0.0f;
|
||||
projectionViews[i].pose.position.y = 0.0f;
|
||||
projectionViews[i].pose.position.z = 0.0f;
|
||||
projectionViews[i].fov.angleUp = 0.9f;
|
||||
projectionViews[i].fov.angleDown = -0.9f;
|
||||
projectionViews[i].fov.angleLeft = -0.9f;
|
||||
projectionViews[i].fov.angleRight = 0.9f;
|
||||
}
|
||||
|
||||
projectionViews[i].subImage.swapchain = vr_swapchains[i];
|
||||
projectionViews[i].subImage.imageRect.offset.x = 0;
|
||||
projectionViews[i].subImage.imageRect.offset.y = 0;
|
||||
projectionViews[i].subImage.imageRect.extent.width = vr_render_width;
|
||||
projectionViews[i].subImage.imageRect.extent.height = vr_render_height;
|
||||
projectionViews[i].subImage.imageArrayIndex = 0;
|
||||
}
|
||||
|
||||
const XrCompositionLayerBaseHeader* layers[2];
|
||||
uint32_t layerCount = 0;
|
||||
|
||||
XrCompositionLayerProjection projectionLayer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
|
||||
if (cachedViewsValid && eyeSwapchainRendered[0] && eyeSwapchainRendered[1]) {
|
||||
projectionLayer.layerFlags = 0;
|
||||
projectionLayer.space = vr_local_space;
|
||||
projectionLayer.viewCount = 2;
|
||||
projectionLayer.views = projectionViews;
|
||||
layers[layerCount++] = (const XrCompositionLayerBaseHeader*)&projectionLayer;
|
||||
}
|
||||
|
||||
XrCompositionLayerQuad uiLayer = {XR_TYPE_COMPOSITION_LAYER_QUAD};
|
||||
if (uiSwapchainAcquired) {
|
||||
XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
|
||||
releaseInfo.next = NULL;
|
||||
xrReleaseSwapchainImage(vr_ui_swapchain, &releaseInfo);
|
||||
|
||||
uiLayer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT |
|
||||
XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
|
||||
uiLayer.space = cachedHeadPoseValid ? vr_local_space : vr_view_space;
|
||||
uiLayer.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
||||
uiLayer.subImage.swapchain = vr_ui_swapchain;
|
||||
uiLayer.subImage.imageRect.offset.x = 0;
|
||||
uiLayer.subImage.imageRect.offset.y = 0;
|
||||
uiLayer.subImage.imageRect.extent.width = vr_ui_width;
|
||||
uiLayer.subImage.imageRect.extent.height = vr_ui_height;
|
||||
uiLayer.subImage.imageArrayIndex = 0;
|
||||
|
||||
const float uiDistance = cv_vruidistance.value > 0 ? (float)cv_vruidistance.value / 70.0f : 1.5f;
|
||||
const float uiWidth = cv_vruiscale.value > 0 ? (float)cv_vruiscale.value * 0.040f : 1.0f;
|
||||
|
||||
cachedUIPose = VR_MakeUIPose(uiDistance);
|
||||
cachedUIPoseValid = true;
|
||||
uiLayer.pose = cachedUIPose;
|
||||
|
||||
uiLayer.size.width = uiWidth;
|
||||
uiLayer.size.height = uiWidth * ((float)vr_ui_height / (float)vr_ui_width);
|
||||
|
||||
layers[layerCount++] = (const XrCompositionLayerBaseHeader*)&uiLayer;
|
||||
|
||||
uiSwapchainAcquired = false;
|
||||
}
|
||||
|
||||
XrFrameEndInfo frameEndInfo = {XR_TYPE_FRAME_END_INFO};
|
||||
frameEndInfo.next = NULL;
|
||||
frameEndInfo.displayTime = frameState.predictedDisplayTime;
|
||||
frameEndInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
||||
frameEndInfo.layerCount = layerCount;
|
||||
frameEndInfo.layers = layerCount > 0 ? layers : NULL;
|
||||
|
||||
XrResult res = xrEndFrame(vr_session, &frameEndInfo);
|
||||
if (XR_FAILED(res)) {
|
||||
static int endFrameErrorCount = 0;
|
||||
if (endFrameErrorCount < 5) {
|
||||
CONS_Printf("VR: xrEndFrame failed (XrResult=%d)\n", (int)res);
|
||||
endFrameErrorCount++;
|
||||
}
|
||||
}
|
||||
vr_frame_begun = false;
|
||||
vr_render_pass = VR_PASS_NONE;
|
||||
vr_drawing_ui = false;
|
||||
}
|
||||
|
||||
#endif // HAVE_VR
|
||||
42
src/vr/vr_render.h
Normal file
42
src/vr/vr_render.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef __VR_RENDER_H__
|
||||
#define __VR_RENDER_H__
|
||||
|
||||
#ifdef HAVE_VR
|
||||
#include "vr_main.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern XrSwapchain vr_swapchains[2];
|
||||
extern uint32_t vr_swapchain_lengths[2];
|
||||
extern XrSwapchainImageOpenGLKHR* vr_swapchain_images[2];
|
||||
extern uint32_t* vr_framebuffers[2];
|
||||
extern uint32_t* vr_depthbuffers[2];
|
||||
|
||||
extern XrSwapchain vr_ui_swapchain;
|
||||
extern uint32_t vr_ui_swapchain_length;
|
||||
extern XrSwapchainImageOpenGLKHR* vr_ui_swapchain_images;
|
||||
extern uint32_t* vr_ui_framebuffers;
|
||||
extern uint32_t vr_ui_width;
|
||||
extern uint32_t vr_ui_height;
|
||||
|
||||
boolean VR_InitSwapchains(void);
|
||||
void VR_DestroySwapchains(void);
|
||||
|
||||
// Frame Loop
|
||||
boolean VR_BeginFrame(void);
|
||||
boolean VR_SetEye(int eye);
|
||||
void VR_ReleaseEye(int eye);
|
||||
void VR_BindDefaultFramebuffer(void);
|
||||
void VR_BeginInEyeUI(void);
|
||||
boolean VR_MirrorEyeToDefaultFramebuffer(int width, int height);
|
||||
boolean VR_BindUISwapchain(void);
|
||||
void VR_EndFrame(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HAVE_VR
|
||||
#endif // __VR_RENDER_H__
|
||||
Loading…
Reference in a new issue