From ce8b7f8d6d5586c32f9a5b417aebfb22e2d42cc3 Mon Sep 17 00:00:00 2001 From: Madison Date: Sun, 4 Oct 2026 19:06:15 +0900 Subject: [PATCH] Frame: set the swapchain attachments with the driver's GL The first run on the Frame had both eyes' framebuffers come back as GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, and the eyes stayed black. The plain gl* names in libxash.so are gl4es's (it's linked first), and gl4es swaps a texture name it hasn't seen for a new empty texture of its own, so the swapchain image never made it into the attachment. The EXT path doesn't have the problem since it gets its functions from eglGetProcAddress. The plain GLES 3 fallback I added does the same now. The framebuffer is still made and bound through gl4es, because gl4es refuses to bind a framebuffer it didn't make and the engine draws through it. Also on the Frame build: - the engine's messages show up in logcat (tag Xash). They always went there, but the default developer level is 0 and the commandline.txt from the assets has no -dev, so nothing was printed. The default is 3 there now, -dev on the commandline still wins. - vr_refresh comes from the frame period instead of the list of rates the runtime offers. SteamVR picks the rate per app, it said 90 on offer while the display ran at 72, and the positional movement scaling uses vr_refresh. Builds. Haven't had it in the headset yet. --- .../jni/src/Lambda1VR/L1VR_SurfaceView.c | 23 ++++---- .../Android/jni/src/Lambda1VR/TBXR_Common.c | 53 +++++++++++++++---- .../jni/src/Xash3D/xash3d/engine/Android.mk | 3 +- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c b/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c index 7f91f1e..77dd6c1 100644 --- a/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c +++ b/Projects/Android/jni/src/Lambda1VR/L1VR_SurfaceView.c @@ -544,15 +544,6 @@ void * AppThreadFunction( void * parm ) TBXR_EnterVR(); TBXR_InitRenderer(); -#ifdef L1VR_STEAM_FRAME - //The positional movement scaling goes by vr_refresh, so make it the real rate - if (TBXR_GetRefresh() > 0) - { - char rate[16]; - Q_snprintf(rate, sizeof(rate), "%d", TBXR_GetRefresh()); - Cvar_Set2("vr_refresh", rate, true); - } -#endif TBXR_InitActions(); TBXR_WaitForSessionActive(); @@ -572,6 +563,20 @@ void * AppThreadFunction( void * parm ) TBXR_FrameSetup(); #ifdef L1VR_STEAM_FRAME + { + //The positional movement scaling goes by vr_refresh. SteamVR picks the rate for + //the app, so take it from the frame period rather than from what's on offer. + static bool refreshSet = false; + const XrDuration period = gAppState.FrameState.predictedDisplayPeriod; + if (!refreshSet && period > 0) + { + char rate[16]; + Q_snprintf(rate, sizeof(rate), "%d", (int)(1e9 / (double)period + 0.5)); + Cvar_Set2("vr_refresh", rate, true); + ALOGI("[openxr] frame period %.2f ms, vr_refresh %s", period / 1e6, rate); + refreshSet = true; + } + } if (!TBXR_ShouldRender()) { //Standby: keep the frame loop going, but the game waits too diff --git a/Projects/Android/jni/src/Lambda1VR/TBXR_Common.c b/Projects/Android/jni/src/Lambda1VR/TBXR_Common.c index 6aedfc3..f1226d5 100644 --- a/Projects/Android/jni/src/Lambda1VR/TBXR_Common.c +++ b/Projects/Android/jni/src/Lambda1VR/TBXR_Common.c @@ -504,6 +504,30 @@ static void ovrFramebuffer_Clear(ovrFramebuffer* frameBuffer) { frameBuffer->FrameBuffers = NULL; } +#ifdef L1VR_STEAM_FRAME +// The GL functions the swapchain attachments are set with, from the driver +typedef struct { + void (GL_APIENTRYP GenRenderbuffers)(GLsizei n, GLuint* renderbuffers); + void (GL_APIENTRYP BindRenderbuffer)(GLenum target, GLuint renderbuffer); + void (GL_APIENTRYP RenderbufferStorage)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); + void (GL_APIENTRYP FramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + void (GL_APIENTRYP FramebufferRenderbuffer)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); + GLenum (GL_APIENTRYP CheckFramebufferStatus)(GLenum target); +} TBXR_DriverGL; + +static TBXR_DriverGL driverGL; + +static void TBXR_LoadDriverGL() +{ + driverGL.GenRenderbuffers = (void*)eglGetProcAddress("glGenRenderbuffers"); + driverGL.BindRenderbuffer = (void*)eglGetProcAddress("glBindRenderbuffer"); + driverGL.RenderbufferStorage = (void*)eglGetProcAddress("glRenderbufferStorage"); + driverGL.FramebufferTexture2D = (void*)eglGetProcAddress("glFramebufferTexture2D"); + driverGL.FramebufferRenderbuffer = (void*)eglGetProcAddress("glFramebufferRenderbuffer"); + driverGL.CheckFramebufferStatus = (void*)eglGetProcAddress("glCheckFramebufferStatus"); +} +#endif + typedef void (GL_APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); typedef void (GL_APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLsizei samples); @@ -525,6 +549,9 @@ static bool ovrFramebuffer_Create( frameBuffer->Width = width; frameBuffer->Height = height; frameBuffer->Multisamples = multisamples; +#ifdef L1VR_STEAM_FRAME + TBXR_LoadDriverGL(); +#endif XrSwapchainCreateInfo swapChainCreateInfo; memset(&swapChainCreateInfo, 0, sizeof(swapChainCreateInfo)); @@ -583,22 +610,28 @@ static bool ovrFramebuffer_Create( #ifdef L1VR_STEAM_FRAME if (multisamples <= 1) { // No multisampling, so plain GLES 3 does it and the runtime's GL doesn't - // have to offer EXT_multisampled_render_to_texture. - GL(glGenRenderbuffers(1, &frameBuffer->DepthBuffers[i])); - GL(glBindRenderbuffer(GL_RENDERBUFFER, frameBuffer->DepthBuffers[i])); - GL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height)); - GL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); + // have to offer EXT_multisampled_render_to_texture. The attachments have to + // be set with the driver's own GL, the same way the EXT path above gets its + // functions: the plain names in this library are gl4es's, which would swap + // the swapchain's texture for one of its own and leave the attachment empty. + // The framebuffer itself is made and bound through gl4es though, it only + // binds framebuffers it has made (and the engine draws through it). + TBXR_DriverGL* gles = &driverGL; + gles->GenRenderbuffers(1, &frameBuffer->DepthBuffers[i]); + gles->BindRenderbuffer(GL_RENDERBUFFER, frameBuffer->DepthBuffers[i]); + gles->RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); + gles->BindRenderbuffer(GL_RENDERBUFFER, 0); GL(glGenFramebuffers(1, &frameBuffer->FrameBuffers[i])); GL(glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer->FrameBuffers[i])); - GL(glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0)); - GL(glFramebufferRenderbuffer( + gles->FramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0); + gles->FramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, - frameBuffer->DepthBuffers[i])); - GL(GLenum renderFramebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER)); + frameBuffer->DepthBuffers[i]); + GLenum renderFramebufferStatus = gles->CheckFramebufferStatus(GL_FRAMEBUFFER); GL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); if (renderFramebufferStatus != GL_FRAMEBUFFER_COMPLETE) { ALOGE( diff --git a/Projects/Android/jni/src/Xash3D/xash3d/engine/Android.mk b/Projects/Android/jni/src/Xash3D/xash3d/engine/Android.mk index 8d6e2ae..4416098 100644 --- a/Projects/Android/jni/src/Xash3D/xash3d/engine/Android.mk +++ b/Projects/Android/jni/src/Xash3D/xash3d/engine/Android.mk @@ -16,7 +16,8 @@ LOCAL_CFLAGS += -DVR # Valve Steam Frame build (gradle assembleFrame, docs/STEAM_FRAME_VR.md) ifeq ($(L1VR_STEAM_FRAME),1) -LOCAL_CFLAGS += -DL1VR_STEAM_FRAME +# the engine's messages go to logcat (tag Xash), but only with a developer level +LOCAL_CFLAGS += -DL1VR_STEAM_FRAME -DDEFAULT_DEV=3 endif LOCAL_CONLYFLAGS += -std=c99