From 1eac3db3cef9fc3220fa9d610d588a4e23dbad78 Mon Sep 17 00:00:00 2001 From: Madison Date: Sun, 4 Oct 2026 21:06:56 +0900 Subject: [PATCH] Frame: put the HUD at vr_hud_distance The client DLL pushes every HUD item sideways by width / 36 per eye (it works that out from vr_stereo_side). On the Frame's lenses that's about 4.5 degrees an eye, so the HUD sat around 0.4 m away and was hard on the eyes. The DLL is a submodule, so while it draws the HUD the eyes are given as the mono ones (which is zero for it), and the whole HUD pass is shifted here instead, by the pixels that put it vr_hud_distance metres ahead given that eye's real position and focal length. That also puts the centre print and the engine's FPS counter at the same place. Scope mode and the screen layer are left alone. vr_hud_distance is archived, in metres, and defaults to 1.0. The shift for each eye is logged once. Frame build only. Builds, haven't had it in the headset yet. --- .../jni/src/Lambda1VR/L1VR_SurfaceView.c | 32 +++++++++++ Projects/Android/jni/src/Lambda1VR/VrCvars.h | 6 ++- .../src/Xash3D/xash3d/engine/client/cl_game.c | 54 +++++++++++++++++++ .../src/Xash3D/xash3d/engine/client/cl_scrn.c | 9 ++++ .../src/Xash3D/xash3d/engine/client/gl_draw.c | 13 +++++ 5 files changed, 113 insertions(+), 1 deletion(-) diff --git a/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c b/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c index 955f428..ef3ab2a 100644 --- a/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c +++ b/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c @@ -356,6 +356,32 @@ bool VR_GetEyeTransform(int eye, float worldScale, vec3_t offset, float* cantDeg } return true; } + +/* +How far to shift the 2D content (the HUD) in an eye's image, in pixels, positive to the right, +so that it sits vr_hud_distance metres ahead of the head. 0 for the mono eyes and the screen layer. +*/ +float VR_GetHudShift(int eye) +{ + if (eye < 0 || eye >= ovrMaxNumEyes || !gAppState.SessionActive || gAppState.Projections == NULL || + VR_UseScreenLayer() || vr_hud_distance == NULL) + return 0.0f; + + const XrView* view = &gAppState.Projections[eye]; + const int width = gAppState.Renderer.FrameBuffer[eye].Width; + const float shift = VrEye_HudShiftPixels(view->pose.position.x, view->fov.angleLeft, view->fov.angleRight, + width, vr_hud_distance->value); + + static bool logged[ovrMaxNumEyes]; + if (!logged[eye]) + { + logged[eye] = true; + ALOGI("[openxr] hud: eye %d at x %.1f mm, focal length %.0f px, vr_hud_distance %.2f m, shift %.1f px", + eye, view->pose.position.x * 1000.0f, VrEye_FocalPixels(view->fov.angleLeft, view->fov.angleRight, width), + vr_hud_distance->value, shift); + } + return shift; +} #endif void R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ); @@ -437,6 +463,9 @@ convar_t *vr_quick_crouchjump; convar_t *vr_stereo_side; convar_t *vr_gesture_triggered_use; convar_t *vr_use_gesture_boundary; +#ifdef L1VR_STEAM_FRAME +convar_t *vr_hud_distance; +#endif void initialize_gl4es(); @@ -496,6 +525,9 @@ void VR_Init() vr_quick_crouchjump = Cvar_Get( "vr_quick_crouchjump", "1", CVAR_ARCHIVE, "Set to 0 to disable quick crouch-jump mode (double clicking jump button triggers duck)" ); vr_gesture_triggered_use = Cvar_Get( "vr_gesture_triggered_use", "1", CVAR_ARCHIVE, "Set to 0 to disable use gesture, 1 to enable" ); vr_use_gesture_boundary = Cvar_Get( "vr_use_gesture_boundary", "0.35", CVAR_ARCHIVE, "Use gesture boundary" ); +#ifdef L1VR_STEAM_FRAME + vr_hud_distance = Cvar_Get( "vr_hud_distance", "1.0", CVAR_ARCHIVE, "How far ahead the HUD sits, in metres" ); +#endif //Not to be changed by users, as it will be overwritten anyway vr_stereo_side = Cvar_Get( "vr_stereo_side", "0", CVAR_READ_ONLY, "Eye being drawn" ); diff --git a/Projects/Android/jni/src/Lambda1VR/VrCvars.h b/Projects/Android/jni/src/Lambda1VR/VrCvars.h index 20859ac..b2756c5 100644 --- a/Projects/Android/jni/src/Lambda1VR/VrCvars.h +++ b/Projects/Android/jni/src/Lambda1VR/VrCvars.h @@ -33,4 +33,8 @@ extern convar_t *vr_gesture_triggered_use; extern convar_t *vr_use_gesture_boundary; //Used and updated continuously during rendering -extern convar_t *vr_stereo_side; \ No newline at end of file +extern convar_t *vr_stereo_side; + +#ifdef L1VR_STEAM_FRAME +extern convar_t *vr_hud_distance; +#endif diff --git a/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_game.c b/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_game.c index 23f5364..9a9ae37 100644 --- a/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_game.c +++ b/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_game.c @@ -1148,6 +1148,52 @@ static void CL_DrawPause( void ) R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.pauseIcon ); } +#ifdef L1VR_STEAM_FRAME +extern convar_t *vr_stereo_side; +float VR_GetHudShift( int eye ); +void R_SetHudShift( float pixels ); +bool isScopeEngaged( void ); + +/* +=============== +CL_RedrawHUD + +The client DLL puts its HUD in front of the screen with a fixed sideways shift of width / 36 +per eye, which it works out from vr_stereo_side. That's about 0.4 m on the Frame's lenses, and +tiring. While it draws, the eyes are given as the mono ones (that's 0 for it, and the 2D +offset onto the optical axis still works for those), and the whole HUD is shifted here +instead, to where vr_hud_distance says. A scope is mono already, so that is left alone. +=============== +*/ +static void CL_RedrawHUD( void ) +{ + const int eye = vr_stereo_side->integer; + float shift; + + if( eye < VR_EYE_LEFT || eye > VR_EYE_RIGHT || isScopeEngaged( )) + { + CL_DrawCenterPrint (); + clgame.dllFuncs.pfnRedraw( cl.time, cl.refdef.intermission ); + return; + } + + shift = VR_GetHudShift( eye ); + + vr_stereo_side->value = eye + 2; + vr_stereo_side->integer = eye + 2; + R_SetHudShift( shift ); + R_Set2DMode( true ); + + CL_DrawCenterPrint (); + clgame.dllFuncs.pfnRedraw( cl.time, cl.refdef.intermission ); + + vr_stereo_side->value = eye; + vr_stereo_side->integer = eye; + R_SetHudShift( 0.0f ); + R_Set2DMode( true ); +} +#endif + void CL_DrawHUD( int state ) { if( state == CL_ACTIVE && !cl.video_prepped ) @@ -1161,14 +1207,22 @@ void CL_DrawHUD( int state ) case CL_ACTIVE: CL_DrawScreenFade (); CL_DrawCrosshair (); +#ifdef L1VR_STEAM_FRAME + CL_RedrawHUD (); +#else CL_DrawCenterPrint (); clgame.dllFuncs.pfnRedraw( cl.time, cl.refdef.intermission ); +#endif break; case CL_PAUSED: CL_DrawScreenFade (); CL_DrawCrosshair (); +#ifdef L1VR_STEAM_FRAME + CL_RedrawHUD (); +#else CL_DrawCenterPrint (); clgame.dllFuncs.pfnRedraw( cl.time, cl.refdef.intermission ); +#endif CL_DrawPause(); break; case CL_LOADING: diff --git a/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_scrn.c b/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_scrn.c index 61ff4a1..d32ba41 100644 --- a/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_scrn.c +++ b/Projects/Android/jni/src/Xash3D/xash3d/engine/client/cl_scrn.c @@ -43,11 +43,20 @@ typedef struct static dirty_t scr_dirty, scr_old_dirty[2]; static qboolean scr_init = false; +#ifdef L1VR_STEAM_FRAME +float VR_GetHudShift( int eye ); +#endif + int GetStereoDepthOffset() { if (vr_stereo_side->value >= VR_EYE_LEFT_MONO) return 0; +#ifdef L1VR_STEAM_FRAME + // the same place the HUD is put + return (int)VR_GetHudShift( vr_stereo_side->integer ); +#endif + return (int)( ( vr_stereo_side->value * -2.0f ) + 1.0f) * (scr_width->integer / 36.0f); } diff --git a/Projects/Android/jni/src/Xash3D/xash3d/engine/client/gl_draw.c b/Projects/Android/jni/src/Xash3D/xash3d/engine/client/gl_draw.c index 5d51b96..877f5b8 100644 --- a/Projects/Android/jni/src/Xash3D/xash3d/engine/client/gl_draw.c +++ b/Projects/Android/jni/src/Xash3D/xash3d/engine/client/gl_draw.c @@ -283,6 +283,16 @@ two are not the same point, so 2D content anchored to the buffer centre would no the eyes. Shifting the ortho window here moves every 2D item at once. =============== */ +#ifdef L1VR_STEAM_FRAME +// extra shift for the HUD pass (see CL_RedrawHUD), in pixels, positive to the right +static float r_hudShift = 0.0f; + +void R_SetHudShift( float pixels ) +{ + r_hudShift = pixels; +} +#endif + void R_Get2DOffset( float *dx, float *dy ) { *dx = *dy = 0.0f; @@ -292,6 +302,9 @@ void R_Get2DOffset( float *dx, float *dy ) return; VR_Get2DOffset( vr_stereo_side->integer, glState.width, glState.height, dx, dy ); +#ifdef L1VR_STEAM_FRAME + *dx += r_hudShift; +#endif } /*