mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
guest GL: a GLX context is what glXCreateContextAttribsARB asked for
SDL2 on X11 makes a throwaway legacy GLX context, destroys it, then makes the one it wants with glXCreateContextAttribsARB. Three things made every such context the host's first, compatibility one: - glXMakeCurrent made the host context through eglCreateContext with no attributes, resetting the version and profile and rebinding desktop GL; the attributes now stay with the GLX context until it is made current; - eglMakeCurrent counted a context per call, so the last eglDestroyContext never reached the host and the throwaway lived on; - the native host latched its first desktop context and never went back to ES; it now makes a new context when the profile or API asked for changes. GL_CONTEXT_PROFILE_MASK now says compatibility for a compatibility context (it said core for every desktop one). Test: tests/games/glx-context.sh (3.2 core, 2.1 compatibility and ES 2.0 after SDL's throwaway, on Xwayland inside the guest). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_461fe3ff06374ecba693831148697ba6
This commit is contained in:
1 parent
988e269480
commit
6230d6f4f1
5 files changed
+225
-12
No files matched your search
@@ -258,6 +258,7 @@ const EGL_CONTEXT_MAJOR_VERSION: EGLint = 0x3098;
|
||||
const EGL_CONTEXT_MINOR_VERSION: EGLint = 0x30FB;
|
||||
const EGL_CONTEXT_OPENGL_PROFILE_MASK: EGLint = 0x30FD;
|
||||
const EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT: EGLint = 0x2;
|
||||
const EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT: EGLint = 0x1;
|
||||
|
||||
/// Desktop GL's fixed-function entry points, which libGLESv2 does not
|
||||
/// export: looked up through EGL once a desktop context exists.
|
||||
@@ -447,6 +448,9 @@ pub struct Gl {
|
||||
current_on: Option<std::thread::ThreadId>,
|
||||
/// Fixed function, once the context is a desktop one.
|
||||
compat: Option<Compat>,
|
||||
/// The desktop context's request (major, minor, compatibility), so a
|
||||
/// later CONTEXT_CREATE asking for something else gets a new context.
|
||||
desktop: Option<(u32, u32, bool)>,
|
||||
/// Read when the context is made; the answer never changes after.
|
||||
pub vendor: String,
|
||||
pub renderer: String,
|
||||
@@ -588,6 +592,7 @@ impl Gl {
|
||||
width,
|
||||
height,
|
||||
compat: None,
|
||||
desktop: None,
|
||||
vendor: gl_string(0x1F00),
|
||||
renderer: gl_string(0x1F01),
|
||||
version: gl_string(0x1F02),
|
||||
@@ -630,7 +635,51 @@ impl Gl {
|
||||
/// `major` 0 asks for the highest version the host offers; a
|
||||
/// compatibility context of an older version is a valid answer to any
|
||||
/// request for 2.x or 3.0, so those are not passed on.
|
||||
fn become_desktop(&mut self, major: u32, minor: u32) -> Result<(), String> {
|
||||
/// Back to an OpenGL ES context (a guest that made a desktop one, threw
|
||||
/// it away and asked for ES: SDL's GLX path).
|
||||
fn become_es(&mut self, major: u32) -> Result<(), String> {
|
||||
unsafe {
|
||||
eglBindAPI(EGL_OPENGL_ES_API);
|
||||
let attrs = [
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8,
|
||||
EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, EGL_NONE,
|
||||
];
|
||||
let mut config: EGLConfig = std::ptr::null_mut();
|
||||
let mut num = 0;
|
||||
if eglChooseConfig(self.display, attrs.as_ptr(), &mut config, 1, &mut num) == 0 || num < 1 {
|
||||
return Err("no OpenGL ES EGL config".into());
|
||||
}
|
||||
let ctx_attrs = [EGL_CONTEXT_CLIENT_VERSION, major.max(2) as EGLint, EGL_NONE];
|
||||
let context = eglCreateContext(self.display, config, std::ptr::null_mut(), ctx_attrs.as_ptr());
|
||||
if context.is_null() {
|
||||
return Err(format!("no OpenGL ES context: {:#x}", eglGetError()));
|
||||
}
|
||||
let pb = [EGL_WIDTH, self.width, EGL_HEIGHT, self.height, EGL_NONE];
|
||||
let surface = eglCreatePbufferSurface(self.display, config, pb.as_ptr());
|
||||
if surface.is_null() || eglMakeCurrent(self.display, surface, surface, context) == 0 {
|
||||
eglDestroyContext(self.display, context);
|
||||
return Err(format!("no OpenGL ES pbuffer: {:#x}", eglGetError()));
|
||||
}
|
||||
eglDestroySurface(self.display, self.surface);
|
||||
eglDestroyContext(self.display, self.context);
|
||||
self.surface = surface;
|
||||
self.context = context;
|
||||
self.config = config;
|
||||
self.names = Names::default();
|
||||
self.pack_store = PixelStore::default();
|
||||
self.unpack_store = PixelStore::default();
|
||||
self.current_on = Some(std::thread::current().id());
|
||||
self.compat = None;
|
||||
self.desktop = None;
|
||||
self.vendor = gl_string(0x1F00);
|
||||
self.renderer = gl_string(0x1F01);
|
||||
self.version = gl_string(0x1F02);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn become_desktop(&mut self, major: u32, minor: u32, compat: bool) -> Result<(), String> {
|
||||
unsafe {
|
||||
if eglBindAPI(EGL_OPENGL_API) == 0 {
|
||||
return Err(format!("no desktop OpenGL in EGL: {:#x}", eglGetError()));
|
||||
@@ -660,9 +709,11 @@ impl Gl {
|
||||
eglBindAPI(EGL_OPENGL_ES_API);
|
||||
return Err("no desktop OpenGL EGL config".into());
|
||||
}
|
||||
// Core only when asked for, from 3.2 (where profiles begin).
|
||||
let core = !compat && (major > 3 || (major == 3 && minor >= 2));
|
||||
let mut ctx_attrs = vec![
|
||||
EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
||||
EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
|
||||
if core { EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT } else { EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT },
|
||||
];
|
||||
if major > 3 || (major == 3 && minor > 0) {
|
||||
ctx_attrs.extend_from_slice(&[EGL_CONTEXT_MAJOR_VERSION, major as EGLint]);
|
||||
@@ -694,6 +745,7 @@ impl Gl {
|
||||
self.unpack_store = PixelStore::default();
|
||||
self.current_on = Some(std::thread::current().id());
|
||||
self.compat = Compat::load();
|
||||
self.desktop = Some((major, minor, !core));
|
||||
self.vendor = gl_string(0x1F00);
|
||||
self.renderer = gl_string(0x1F01);
|
||||
self.version = gl_string(0x1F02);
|
||||
@@ -799,10 +851,20 @@ impl Gl {
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
if api == crate::proto::client_api::DESKTOP && self.compat.is_none() {
|
||||
if let Err(e) = self.become_desktop(major, minor) {
|
||||
// { api, major, minor, profile }: profile 1 is
|
||||
// compatibility (older guests send three words: the
|
||||
// compatibility profile, as before).
|
||||
let compat = a.u32().is_none_or(|p| p != 0);
|
||||
let core = !compat && (major > 3 || (major == 3 && minor >= 2));
|
||||
let changed = self.desktop.is_some_and(|(_, _, c)| c == core);
|
||||
if api == crate::proto::client_api::DESKTOP && (self.compat.is_none() || changed) {
|
||||
if let Err(e) = self.become_desktop(major, minor, compat) {
|
||||
eprintln!("gex-gl: {e}; staying on OpenGL ES");
|
||||
}
|
||||
} else if api != crate::proto::client_api::DESKTOP && self.compat.is_some() {
|
||||
if let Err(e) = self.become_es(major) {
|
||||
eprintln!("gex-gl: {e}; staying on desktop OpenGL");
|
||||
}
|
||||
}
|
||||
reply.blob(self.vendor.as_bytes());
|
||||
reply.blob(self.renderer.as_bytes());
|
||||
|
||||
+56
-6
@@ -59,6 +59,19 @@ static const int gexgl_glx_context_token = 0;
|
||||
|
||||
static void *glx_current_display;
|
||||
static unsigned long glx_current_drawable;
|
||||
static void *glx_current_context;
|
||||
|
||||
/* A context made by glXCreateContextAttribsARB: what it asked for, kept until
|
||||
* glXMakeCurrent makes the host context. The host context is made there, not
|
||||
* at creation, and making it went through eglCreateContext with no
|
||||
* attributes, which put the version and profile back to EGL's defaults and
|
||||
* eglBindAPI back to desktop GL: a 3.2 core or ES 2 request came up as the
|
||||
* host's compatibility context. */
|
||||
#define GEXGL_GLX_ATTRIBS_MAGIC 0x67786174u
|
||||
struct gexgl_glx_attribs_context {
|
||||
unsigned magic;
|
||||
int es, major, minor, profile;
|
||||
};
|
||||
|
||||
static void gexgl_glx_ensure_egl(void *dpy);
|
||||
|
||||
@@ -276,6 +289,13 @@ void *glXCreateContextAttribsARB(void *dpy, void *config, void *share,
|
||||
what it actually got through GL_VERSION -- and an explicit core request
|
||||
overrides the compatibility default above. */
|
||||
int wants_es = 0;
|
||||
/* What this context asks for, from GLX's defaults (1.0, compatibility),
|
||||
whatever a previous context left in the globals. */
|
||||
if (!host_context_made) {
|
||||
egl_context_major = 1;
|
||||
egl_context_minor = 0;
|
||||
egl_context_profile = 1;
|
||||
}
|
||||
for (const int *a = attribs; a && *a != 0 && !host_context_made; a += 2) {
|
||||
switch (a[0]) {
|
||||
case 0x2091: egl_context_major = a[1]; break; /* MAJOR_VERSION_ARB */
|
||||
@@ -302,7 +322,19 @@ void *glXCreateContextAttribsARB(void *dpy, void *config, void *share,
|
||||
GL because that is what reaching for GLX usually means, and only the
|
||||
profile mask can say otherwise. */
|
||||
if (wants_es) eglBindAPI(0x30A0u /* EGL_OPENGL_ES_API */);
|
||||
return GEXGL_GLX_CONTEXT;
|
||||
struct gexgl_glx_attribs_context *c = malloc(sizeof(*c));
|
||||
if (!c) return GEXGL_GLX_CONTEXT;
|
||||
c->magic = GEXGL_GLX_ATTRIBS_MAGIC;
|
||||
c->es = wants_es;
|
||||
c->major = egl_context_major;
|
||||
c->minor = egl_context_minor;
|
||||
c->profile = egl_context_profile;
|
||||
return c;
|
||||
}
|
||||
|
||||
static struct gexgl_glx_attribs_context *gexgl_glx_attribs_of(void *ctx) {
|
||||
struct gexgl_glx_attribs_context *c = ctx;
|
||||
return ctx && ctx != GEXGL_GLX_CONTEXT && c->magic == GEXGL_GLX_ATTRIBS_MAGIC ? c : NULL;
|
||||
}
|
||||
|
||||
/* Destroying the context really destroys it, which is what makes an odd
|
||||
@@ -315,8 +347,14 @@ void *glXCreateContextAttribsARB(void *dpy, void *config, void *share,
|
||||
* application ran on whatever the throwaway had asked for. Letting the host
|
||||
* drop it means the next creation is a real creation. */
|
||||
void glXDestroyContext(void *dpy, void *ctx) {
|
||||
(void)dpy; (void)ctx;
|
||||
(void)dpy;
|
||||
struct gexgl_glx_attribs_context *c = gexgl_glx_attribs_of(ctx);
|
||||
eglDestroyContext(NULL, ctx);
|
||||
if (ctx == glx_current_context) glx_current_context = NULL;
|
||||
if (c) {
|
||||
c->magic = 0;
|
||||
free(c);
|
||||
}
|
||||
}
|
||||
|
||||
int glXMakeCurrent(void *dpy, unsigned long drawable, void *ctx) {
|
||||
@@ -325,6 +363,7 @@ int glXMakeCurrent(void *dpy, unsigned long drawable, void *ctx) {
|
||||
eglMakeCurrent(dpy, NULL, NULL, NULL);
|
||||
glx_current_display = NULL;
|
||||
glx_current_drawable = 0;
|
||||
glx_current_context = NULL;
|
||||
return 1;
|
||||
}
|
||||
/* The drawable is an X window id, which is exactly what
|
||||
@@ -333,10 +372,21 @@ int glXMakeCurrent(void *dpy, unsigned long drawable, void *ctx) {
|
||||
void *config = NULL;
|
||||
void *surface = eglCreateWindowSurface(dpy, config,
|
||||
(void *)(uintptr_t)drawable, NULL);
|
||||
void *context = egl_current_context ? egl_current_context
|
||||
: eglCreateContext(dpy, config, NULL,
|
||||
NULL);
|
||||
void *context = egl_current_context;
|
||||
struct gexgl_glx_attribs_context *c = gexgl_glx_attribs_of(ctx);
|
||||
if (!context && c) {
|
||||
/* The host context as glXCreateContextAttribsARB asked for it. */
|
||||
eglBindAPI(c->es ? 0x30A0u /* EGL_OPENGL_ES_API */ : 0x30A2u /* EGL_OPENGL_API */);
|
||||
egl_context_major = c->major;
|
||||
egl_context_minor = c->minor;
|
||||
egl_context_profile = c->profile;
|
||||
context_create();
|
||||
context = (void *)3;
|
||||
} else if (!context) {
|
||||
context = eglCreateContext(dpy, config, NULL, NULL);
|
||||
}
|
||||
if (!eglMakeCurrent(dpy, surface, surface, context)) return 0;
|
||||
glx_current_context = ctx;
|
||||
glx_current_display = dpy;
|
||||
glx_current_drawable = drawable;
|
||||
return 1;
|
||||
@@ -359,7 +409,7 @@ void glXSwapBuffers(void *dpy, unsigned long drawable) {
|
||||
}
|
||||
|
||||
void *glXGetCurrentContext(void) {
|
||||
return egl_current_context ? GEXGL_GLX_CONTEXT : NULL;
|
||||
return egl_current_context ? (glx_current_context ? glx_current_context : GEXGL_GLX_CONTEXT) : NULL;
|
||||
}
|
||||
void *glXGetCurrentDisplay(void) { return glx_current_display; }
|
||||
unsigned long glXGetCurrentDrawable(void) { return glx_current_drawable; }
|
||||
|
||||
+18
-2
@@ -1972,7 +1972,19 @@ void glGetIntegerv(unsigned pname, int *out) {
|
||||
return;
|
||||
}
|
||||
if (pname == 0x9126u) { /* GL_CONTEXT_PROFILE_MASK */
|
||||
if (out) out[0] = egl_api == 0x30A2u ? 1 : 0; /* GL_CONTEXT_CORE_PROFILE_BIT */
|
||||
/* What the host's context is: its version string says compatibility
|
||||
when it is one (GL_CONTEXT_COMPATIBILITY_PROFILE_BIT, 2), and a
|
||||
desktop context of 3.2 or later otherwise is core (1). ES has no
|
||||
profiles. This answered core for every desktop context. */
|
||||
int mask = 0;
|
||||
if (egl_api == 0x30A2u) {
|
||||
const char *v = (const char *)glGetString(0x1F02u);
|
||||
int major = 0, minor = 0;
|
||||
host_gl_version(&major, &minor);
|
||||
if (v && strstr(v, "Compatibility")) mask = 2;
|
||||
else if (major > 3 || (major == 3 && minor >= 2)) mask = 1;
|
||||
}
|
||||
if (out) out[0] = mask;
|
||||
return;
|
||||
}
|
||||
/* Sized from the pname before the request goes out, for two reasons.
|
||||
@@ -3001,7 +3013,11 @@ unsigned eglMakeCurrent(void *dpy, void *draw, void *read, void *ctx) {
|
||||
/* A safety net for callers that reach a current context without going
|
||||
through this library's eglCreateContext. The latch inside makes it a
|
||||
no-op once the host context exists. */
|
||||
if (ctx) context_create();
|
||||
/* Only when there is none: the latch counts a context per call, and
|
||||
counting every make-current as a new context kept the host context
|
||||
alive after its last eglDestroyContext (SDL's throwaway GLX context
|
||||
then stood in for the one it really wanted). */
|
||||
if (ctx && !host_context_made) context_create();
|
||||
egl_current_display = ctx ? dpy : NULL;
|
||||
egl_current_context = ctx;
|
||||
egl_current_draw = ctx ? draw : NULL;
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# GLX contexts under gex (tests/games/src/glx-context.c): a program on
|
||||
# Xwayland inside the guest, with gex's guest GL for x86-64.
|
||||
#
|
||||
# tests/games/glx-context.sh [GEXC]
|
||||
#
|
||||
# Env: GEX_GL_X64 (guest/gl/build.sh with GEX_CC=gcc; built here if unset).
|
||||
set -eu
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(cd "$here/../.." && pwd)
|
||||
gexc=${1:-$root/target/x86/release/gexc}
|
||||
work=${TMPDIR:-/tmp}/gex-glx-context-$(id -u)
|
||||
rm -rf "$work"; mkdir -p "$work"
|
||||
gl=${GEX_GL_X64:-}
|
||||
if [ -z "$gl" ]; then gl=$work/gl; GEX_CC=gcc "$root/guest/gl/build.sh" "$gl" > /dev/null; fi
|
||||
gcc -O1 -o "$work/glx-context" "$here/src/glx-context.c" -lX11 -lGL
|
||||
n=$(( 50 + $(id -u) % 40 ))
|
||||
rm -f "/tmp/.X$n-lock" "/tmp/.X11-unix/X$n"
|
||||
cd "$work"
|
||||
set +e
|
||||
timeout 1800 "$gexc" run --no-cgen --gl --wayland "$work/wl" --wayland-size 320x240 --wayland-out null \
|
||||
--env LD_LIBRARY_PATH="$gl" --env XDG_RUNTIME_DIR="$work" --env HOME="$work" \
|
||||
/bin/sh "$here/xwayland-inguest.sh" "$n" "$work/xwayland.log" ./glx-context > "$work/out" 2>&1
|
||||
rc=$?
|
||||
rm -f "/tmp/.X$n-lock" "/tmp/.X11-unix/X$n"
|
||||
grep -E '^(ok|FAIL| |[0-9]+ failures)' "$work/out"
|
||||
[ $rc = 0 ] && grep -q '^0 failures' "$work/out"
|
||||
@@ -0,0 +1,58 @@
|
||||
/* GLX contexts as SDL2 makes them: a throwaway legacy context, then the one
|
||||
* wanted, from glXCreateContextAttribsARB. The second must be what it asked
|
||||
* for (gex's guest GL used to make every context the host's compatibility
|
||||
* one at glXMakeCurrent). Run by tests/games/glx-context.sh. */
|
||||
#include <X11/Xlib.h>
|
||||
#include <GL/glx.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef GLXContext (*cca_t)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
|
||||
static int failures;
|
||||
#define CHECK(name, cond) do { int ok_ = (cond); printf("%s %s\n", ok_ ? "ok" : "FAIL", name); if (!ok_) failures++; } while (0)
|
||||
|
||||
int main(void) {
|
||||
Display *d = XOpenDisplay(NULL);
|
||||
if (!d) { printf("FAIL no display\n"); return 1; }
|
||||
int att[] = {GLX_RGBA, GLX_DOUBLEBUFFER, None};
|
||||
XVisualInfo *vi = glXChooseVisual(d, DefaultScreen(d), att);
|
||||
XSetWindowAttributes a = {0};
|
||||
a.colormap = XCreateColormap(d, RootWindow(d, vi->screen), vi->visual, AllocNone);
|
||||
Window w = XCreateWindow(d, RootWindow(d, vi->screen), 0, 0, 64, 64, 0, vi->depth, InputOutput, vi->visual, CWColormap, &a);
|
||||
XMapWindow(d, w);
|
||||
XFlush(d);
|
||||
int n = 0;
|
||||
GLXFBConfig *cfg = glXChooseFBConfig(d, DefaultScreen(d), NULL, &n);
|
||||
cca_t cca = (cca_t)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
|
||||
CHECK("glXCreateContextAttribsARB", cca && n > 0);
|
||||
if (!cca || !n) return 1;
|
||||
|
||||
struct { const char *name; int major, minor, mask; } want[] = {
|
||||
{"3.2 core", 3, 2, 1}, {"2.1 compatibility", 2, 1, 2}, {"ES 2.0", 2, 0, 4},
|
||||
};
|
||||
for (unsigned i = 0; i < sizeof want / sizeof want[0]; i++) {
|
||||
/* SDL's throwaway context first. */
|
||||
GLXContext t = glXCreateContext(d, vi, NULL, True);
|
||||
glXMakeCurrent(d, w, t);
|
||||
glXMakeCurrent(d, None, NULL);
|
||||
glXDestroyContext(d, t);
|
||||
int ca[] = {0x2091, want[i].major, 0x2092, want[i].minor, 0x9126, want[i].mask, 0};
|
||||
GLXContext c = cca(d, cfg[0], NULL, True, ca);
|
||||
int made = c && glXMakeCurrent(d, w, c);
|
||||
const char *v = (const char *)glGetString(GL_VERSION);
|
||||
GLint mask = 0;
|
||||
glGetIntegerv(0x9126 /* GL_CONTEXT_PROFILE_MASK */, &mask);
|
||||
printf(" %s: %s, profile mask %d\n", want[i].name, v ? v : "(null)", mask);
|
||||
char what[64];
|
||||
snprintf(what, sizeof what, "%s: made current", want[i].name);
|
||||
CHECK(what, made && glXGetCurrentContext() == c);
|
||||
snprintf(what, sizeof what, "%s: what it asked for", want[i].name);
|
||||
if (want[i].mask == 4) CHECK(what, v && !strncmp(v, "OpenGL ES", 9));
|
||||
else if (want[i].mask == 1) CHECK(what, v && v[0] >= '3' && (mask & 1));
|
||||
else CHECK(what, v && v[0] >= '2' && strncmp(v, "OpenGL ES", 9) && !(mask & 1));
|
||||
glXMakeCurrent(d, None, NULL);
|
||||
glXDestroyContext(d, c);
|
||||
}
|
||||
printf("%d failures\n", failures);
|
||||
return failures;
|
||||
}
|
||||
Reference in new issue
Block a user