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.
This commit is contained in:
Madison committed 2026-10-04 19:10:33 +09:00
1 parent 5c2769c0ea
commit ce8b7f8d6d
3 files changed
+59 -20

No files matched your search

@@ -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
@@ -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(
@@ -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