diff --git a/Common/VR/VRFramebuffer.cpp b/Common/VR/VRFramebuffer.cpp index d94d24f675..8d6b6766da 100644 --- a/Common/VR/VRFramebuffer.cpp +++ b/Common/VR/VRFramebuffer.cpp @@ -3,6 +3,29 @@ #if XR_USE_GRAPHICS_API_OPENGL || XR_USE_GRAPHICS_API_OPENGL_ES #include "Common/GPU/OpenGL/GLCommon.h" +#include + +#ifndef GL_FRAMEBUFFER_SRGB_EXT +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#endif + +// GLFeatures.h clashes with the EGL macros the OpenXR headers bring in, so this asks GL itself. +static bool HasSRGBWriteControl() { + static int has = -1; + if (has < 0) { + GLint count = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &count); + has = 0; + for (GLint i = 0; i < count; i++) { + const char *ext = (const char *)glGetStringi(GL_EXTENSIONS, i); + if (ext && !strcmp(ext, "GL_EXT_sRGB_write_control")) { + has = 1; + break; + } + } + } + return has == 1; +} #endif @@ -267,6 +290,11 @@ void* ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer) { #if XR_USE_GRAPHICS_API_OPENGL_ES || XR_USE_GRAPHICS_API_OPENGL if (UseGL()) { GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->GLFrameBuffers[frameBuffer->TextureSwapChainIndex])); + // The swapchain is sRGB and our colors are sRGB-encoded already: GLES would encode them again on + // write (too bright). Context state, but the other framebuffers aren't sRGB, so it only affects this. + if (HasSRGBWriteControl()) { + GL(glDisable(GL_FRAMEBUFFER_SRGB_EXT)); + } } #endif return nullptr; diff --git a/Common/VR/VRRenderer.cpp b/Common/VR/VRRenderer.cpp index ba18bb26aa..4d16c89917 100644 --- a/Common/VR/VRRenderer.cpp +++ b/Common/VR/VRRenderer.cpp @@ -206,8 +206,9 @@ void VR_Recenter(engine_t* engine) { } } - // Update menu orientation - VR_SetConfigFloat(VR_CONFIG_MENU_PITCH, hmdorientation.x); + // Update menu orientation. The screen stays upright: tilting it to the head's pitch at recenter + // time (often looking down while putting the headset on) left it at an angle for the session. + VR_SetConfigFloat(VR_CONFIG_MENU_PITCH, 0.0f); VR_SetConfigFloat(VR_CONFIG_MENU_YAW, 0.0f); } diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 122d7240ba..097af96a19 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -544,11 +544,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) { WRITE(p, " vec4 outPos = mul(u_proj_lens, viewPos);\n"); WRITE(p, " vec4 orgPos = mul(u_proj, viewPos);\n"); -#ifdef __LIBRETRO__ // Preserve game depth before clipping and viewport conversion. The VR lens - // changes XY, but depth must still agree with through-mode draws and clears. + // changes XY, but depth must still agree with through-mode draws and clears, + // and go through the viewport's Z scale (often negative on the PSP). WRITE(p, " outPos.z = orgPos.z * (outPos.w / orgPos.w);\n"); -#endif } else { WRITE(p, " vec4 outPos = mul(u_proj, viewPos);\n"); } @@ -1020,13 +1019,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag WRITE(p, " %sgl_Position = outPos;\n", compat.vsOutPrefix); if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) { -#ifndef __LIBRETRO__ - // Z correction for the depth buffer - if (useHWTransform) { - WRITE(p, " %sgl_Position.z = orgPos.z / abs(orgPos.w) * abs(outPos.w);\n", compat.vsOutPrefix); - } -#endif - // HUD scaling WRITE(p, " %sgl_Position.x *= u_scaleX;\n", compat.vsOutPrefix); WRITE(p, " %sgl_Position.y *= u_scaleY;\n", compat.vsOutPrefix); diff --git a/docs/vr-openxr-vulkan.md b/docs/vr-openxr-vulkan.md index 22e4a29dda..1bc953c2b2 100644 --- a/docs/vr-openxr-vulkan.md +++ b/docs/vr-openxr-vulkan.md @@ -74,7 +74,7 @@ only on dirty split GTA's scene into two layers moving at different speeds. ### The OpenGL-only parts, and their Vulkan versions -OpenGL VR goes once these are confirmed on a headset: +OpenGL VR stays an option for a while. What it does its own way, and the Vulkan equivalent: - **Skyplane** (`compatvr.ini`, the GTA Stories games): `PreprocessSkyplane` edits GL render steps at replay, `PreprocessSkyplaneVulkan` does the same to Vulkan steps in `PreprocessSteps`, before @@ -95,6 +95,17 @@ The other VR compat flags (`ForceMono`, `ForceFlatScreen`, `IdentityViewHack`, ` `ProjectionHack`, `UnitsPerMeter`) are read in shared code (`PPSSPPVR.cpp`, the shader generator), so they don't depend on the backend. +### Fixes found on the headset + +- **Depth** was inverted, on GL too: the shader replaced the lens projection's Z with the game's + (`orgPos`) at the very end, skipping the viewport's Z scale, which broke when the viewport transform + moved into the shader. It's replaced before the transform now (libretro already did this). +- **Gamma on GL:** the GL eye swapchains are `GL_SRGB8_ALPHA8`, and GLES encodes writes to an sRGB + framebuffer, so our already sRGB-encoded colors came out too bright. Writes skip the encoding now + (`GL_EXT_sRGB_write_control`). +- **The flat screen** stays upright. It used to take the head's pitch at recenter time, often looking + down while putting the headset on. Recentering is the system gesture (hold the Meta button). + ## Phase 2: PC VR The desktop window (Windows, then Linux) stays the primary swapchain and shows a mirror. The @@ -105,5 +116,6 @@ becomes the non-pacing one. The OpenXR loader on desktop is our dynamic one (`Op ## Status -- [ ] Phase 1 +- [x] Phase 1: runs on a Quest 2 (menus, Burnout Legends, GTA LCS in flat mode, stereo and 6DoF on) + at 72 fps. Skyplane not checked specifically yet. - [ ] Phase 2