mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
guest/gl: framebuffer state queries answered from a cache
Unity asks glGetIntegerv(GL_SAMPLES) and (GL_READ_BUFFER) four times each a frame (Muck's menu: 8 of its 10 submits a frame natively), and every query is a submit and a wait; in the page each is a round trip to the compositor's Worker. GL_SAMPLES, GL_SAMPLE_BUFFERS, GL_READ_BUFFER and GL_DRAW_BUFFER are now kept per (draw framebuffer, read framebuffer) once the host has answered, until a command that can change them goes into the ring (attachments, renderbuffer and multisample storage, read/draw buffers, deletions, a binding not made through glBindFramebuffer) or the context changes. GEXGL_NO_QUERY_CACHE=1 asks every time. tests/gl-state-query-cache.sh: the probe's nine cases against native Mesa, then under gexc --gl with the cache on (12 submits) and off (21). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_d2453757b8a84fdbb4ee7c9e093141df
This commit is contained in:
1 parent
d6c8b5196a
commit
85c3bb78ba
4 files changed
+215
-2
No files matched your search
+105
-1
@@ -228,6 +228,95 @@ static void flush(void) {
|
||||
reply_at = 0;
|
||||
}
|
||||
|
||||
/* Framebuffer state queries answered here.
|
||||
*
|
||||
* Unity asks glGetIntegerv(GL_SAMPLES) and glGetIntegerv(GL_READ_BUFFER)
|
||||
* four times each a frame (Muck's menu), and every query is a submit: the
|
||||
* batch so far runs and the guest waits for it. In the page that is a round
|
||||
* trip to the compositor's Worker each, and Muck's render thread spent
|
||||
* nearly half its time in them. The answers depend only on which
|
||||
* framebuffers are bound and on their attachments and read/draw buffers, so
|
||||
* the first answer for a binding is kept until a command that could change
|
||||
* it goes into the ring (fbq_invalidates) or the context changes. Which
|
||||
* framebuffers are bound is tracked from glBindFramebuffer; a binding made
|
||||
* any other way stops the caching until GL_FRAMEBUFFER is bound again.
|
||||
* GEXGL_NO_QUERY_CACHE=1 asks the host every time. */
|
||||
struct fbq_entry {
|
||||
unsigned pname, draw, read;
|
||||
int value;
|
||||
};
|
||||
static struct fbq_entry fbq_cache[16];
|
||||
static unsigned fbq_count;
|
||||
static unsigned fbq_draw, fbq_read;
|
||||
static int fbq_known;
|
||||
|
||||
static void fbq_forget(void) { fbq_count = 0; }
|
||||
|
||||
static int fbq_cacheable(unsigned pname) {
|
||||
static int off = -1;
|
||||
if (off < 0) off = getenv("GEXGL_NO_QUERY_CACHE") ? 1 : 0;
|
||||
if (off) return 0;
|
||||
switch (pname) {
|
||||
case 0x80A9u: /* GL_SAMPLES */
|
||||
case 0x80A8u: /* GL_SAMPLE_BUFFERS */
|
||||
case 0x0C02u: /* GL_READ_BUFFER */
|
||||
case 0x0C01u: /* GL_DRAW_BUFFER */
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int fbq_lookup(unsigned pname, int *out) {
|
||||
if (!fbq_known) return 0;
|
||||
for (unsigned i = 0; i < fbq_count; ++i)
|
||||
if (fbq_cache[i].pname == pname && fbq_cache[i].draw == fbq_draw &&
|
||||
fbq_cache[i].read == fbq_read) {
|
||||
*out = fbq_cache[i].value;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fbq_store(unsigned pname, int value) {
|
||||
if (!fbq_known) return;
|
||||
if (fbq_count == sizeof(fbq_cache) / sizeof(fbq_cache[0])) fbq_count = 0;
|
||||
fbq_cache[fbq_count++] = (struct fbq_entry){pname, fbq_draw, fbq_read, value};
|
||||
}
|
||||
|
||||
/* Commands after which a cached answer may be wrong: attachments, buffer
|
||||
* storage that can change a sample count, read and draw buffer selection,
|
||||
* deletions (a name can come back as another object), and bindings made
|
||||
* other than through glBindFramebuffer below. The numbers are the wire
|
||||
* opcodes (crates/gex-gl/src/proto.rs, and the generated GL 4.4 and
|
||||
* compatibility transports). */
|
||||
static int fbq_invalidates(uint32_t opcode) {
|
||||
switch (opcode) {
|
||||
case 97: case 104: case 107: case 109: case 111: case 114: case 119: case 120:
|
||||
case 1132: case 1158: case 1160: case 1161: case 1163: case 1374: case 1378:
|
||||
case 1405: case 1407: case 1416: case 1418: case 1422:
|
||||
case 8211: case 8751: case 8752: case 8753: case 8754: case 8953: case 8954:
|
||||
case 8959: case 8960: case 8962: case 8963: case 8964: case 8969: case 8970:
|
||||
case 8971: case 8972: case 8973: case 8975: case 8979: case 8980: case 8982:
|
||||
case 8983: case 8984: case 8985: case 8986: case 8987: case 8989: case 8991:
|
||||
case 9960: case 9991: case 9992: case 9993: case 9994: case 9995: case 9996:
|
||||
case 9997: case 10001: case 10002: case 10003: case 10004: case 10005:
|
||||
case 10006: case 10007: case 10008: case 10009: case 10022: case 10023:
|
||||
case 10024: case 10025: case 10026: case 10027: case 10412: case 10437:
|
||||
case 10438: case 10439: case 10442: case 10443: case 10444: case 10763:
|
||||
case 10764: case 10767: case 10768: case 10788: case 10793: case 10796:
|
||||
case 10802: case 10804: case 10825: case 10826: case 10828: case 10829:
|
||||
case 10848: case 10853: case 10854: case 10857: case 10858: case 10861:
|
||||
case 10863: case 10871:
|
||||
return 1;
|
||||
case 8260: case 8261: /* glBindFramebuffer(EXT) through the generated transport */
|
||||
fbq_known = 0;
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Start a command. Returns where to write its arguments, having reserved
|
||||
room for them, or NULL if the batch is full -- in which case it is flushed
|
||||
and the caller tries again. */
|
||||
@@ -248,6 +337,7 @@ static uint32_t *begin(uint32_t opcode, size_t arg_words, size_t tail_bytes) {
|
||||
return NULL; /* one command larger than the whole buffer */
|
||||
}
|
||||
}
|
||||
if ((fbq_count || fbq_known) && fbq_invalidates(opcode)) fbq_forget();
|
||||
uint32_t *at = (uint32_t *)(cmds + ring->tail);
|
||||
at[0] = opcode;
|
||||
at[1] = (uint32_t)len;
|
||||
@@ -1501,6 +1591,14 @@ GEN_OBJECTS(glGenRenderbuffers, GEXGL_GEN_RENDERBUFFERS)
|
||||
DELETE_OBJECTS(glDeleteRenderbuffers, GEXGL_DELETE_RENDERBUFFERS)
|
||||
void glBindFramebuffer(unsigned target, unsigned name) {
|
||||
CMD2(GEXGL_BIND_FRAMEBUFFER, target, name);
|
||||
if (target == 0x8D40u) { /* GL_FRAMEBUFFER: both */
|
||||
fbq_draw = fbq_read = name;
|
||||
fbq_known = 1;
|
||||
} else if (target == 0x8CA9u) { /* GL_DRAW_FRAMEBUFFER */
|
||||
fbq_draw = name;
|
||||
} else if (target == 0x8CA8u) { /* GL_READ_FRAMEBUFFER */
|
||||
fbq_read = name;
|
||||
}
|
||||
}
|
||||
void glBindRenderbuffer(unsigned target, unsigned name) { CMD2(GEXGL_BIND_RENDERBUFFER, target, name); }
|
||||
unsigned glCheckFramebufferStatus(unsigned target) {
|
||||
@@ -2015,13 +2113,17 @@ void glGetIntegerv(unsigned pname, int *out) {
|
||||
* its own reply back as this one's; and copying whatever the host sent
|
||||
* writes past the caller's array whenever the two disagree, which is an
|
||||
* out-of-bounds write into guest memory rather than a wrong value. */
|
||||
const int cacheable = fbq_cacheable(pname);
|
||||
if (cacheable && out && fbq_lookup(pname, out)) return;
|
||||
const size_t expected = gexgl_state_query_components(pname) * sizeof(*out);
|
||||
CMD1(GEXGL_GET_INTEGERV, pname);
|
||||
flush();
|
||||
uint32_t bytes = 0;
|
||||
const uint8_t *values = reply_blob(&bytes);
|
||||
if (out && values && expected && bytes == expected)
|
||||
if (out && values && expected && bytes == expected) {
|
||||
memcpy(out, values, expected);
|
||||
if (cacheable && expected == sizeof(*out)) fbq_store(pname, out[0]);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t gexgl_state_query_components(unsigned pname) {
|
||||
@@ -3088,6 +3190,8 @@ unsigned eglDestroyContext(void *dpy, void *ctx) {
|
||||
}
|
||||
|
||||
unsigned eglMakeCurrent(void *dpy, void *draw, void *read, void *ctx) {
|
||||
fbq_forget();
|
||||
fbq_known = 0;
|
||||
if (!egl_ready) return EGL_FALSE;
|
||||
/* A safety net for callers that reach a current context without going
|
||||
through this library's eglCreateContext. The latch inside makes it a
|
||||
|
||||
+1
-1
@@ -198,7 +198,7 @@
|
||||
#define GEXGL_ALPHA_FUNC 197
|
||||
#define GEXGL_PUSH_ATTRIB 198
|
||||
#define GEXGL_POP_ATTRIB 199
|
||||
#define GEXGL_CLIENT_ARRAY 200 /* the NT layer's; see proto.rs */
|
||||
#define GEXGL_CLIENT_ARRAY 200
|
||||
|
||||
#define GEXGL_SURFACE_WAYLAND 0
|
||||
#define GEXGL_SURFACE_X 1
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# The guest GL library's cached framebuffer state queries against native Mesa's
|
||||
# answers (tests/integration/programs/gl-state-query-probe.c), then under gexc
|
||||
# --gl with the cache on and off.
|
||||
#
|
||||
# tests/gl-state-query-cache.sh [gexc] (default target/x86/release/gexc)
|
||||
set -eu
|
||||
ROOT=$(CDPATH='' cd -- "$(dirname "$0")/.." && pwd)
|
||||
GEXC=${1:-$ROOT/target/x86/release/gexc}
|
||||
SOURCE="$ROOT/tests/integration/programs/gl-state-query-probe.c"
|
||||
WORK=${WORK:-$(mktemp -d)}
|
||||
mkdir -p "$WORK"
|
||||
cc -O2 -Wall -Wextra -o "$WORK/probe" "$SOURCE" -lEGL -lGL
|
||||
EGL_PLATFORM=surfaceless LIBGL_ALWAYS_SOFTWARE=1 "$WORK/probe" | tee "$WORK/native.log"
|
||||
grep -q 'failed=0' "$WORK/native.log"
|
||||
GEX_CC=${GEX_CC:-cc} "$ROOT/guest/gl/build.sh" "$WORK/guest-gl" >/dev/null
|
||||
"$GEXC" run --gl --env LD_LIBRARY_PATH="$WORK/guest-gl" "$WORK/probe" | tee "$WORK/gex.log"
|
||||
grep -q 'failed=0' "$WORK/gex.log"
|
||||
"$GEXC" run --gl --env LD_LIBRARY_PATH="$WORK/guest-gl" --env GEXGL_NO_QUERY_CACHE=1 "$WORK/probe" | tee "$WORK/gex-nocache.log"
|
||||
grep -q 'failed=0' "$WORK/gex-nocache.log"
|
||||
echo "ok gl-state-query-cache"
|
||||
@@ -0,0 +1,88 @@
|
||||
/* Framebuffer state queries the guest library answers from its cache
|
||||
* (guest/gl/gexgl.c fbq_*): GL_SAMPLES and GL_READ_BUFFER across binding
|
||||
* changes, glReadBuffer, renderbuffer storage with samples, and a deleted
|
||||
* framebuffer whose name comes back. Prints SUMMARY cases=N passed=N failed=0;
|
||||
* runs on native Mesa too.
|
||||
*
|
||||
* cc -o probe gl-state-query-probe.c -lEGL -lGL (native reference)
|
||||
* gexc run --gl --env LD_LIBRARY_PATH=GUEST_GL probe (tests/gl-state-query-cache.sh) */
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include <EGL/egl.h>
|
||||
#include <GL/glcorearb.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static int cases, passed;
|
||||
static void expect(const char *name, GLenum pname, GLint want) {
|
||||
GLint got = -1;
|
||||
glGetIntegerv(pname, &got);
|
||||
/* Twice: the second answer is the cached one. */
|
||||
GLint again = -2;
|
||||
glGetIntegerv(pname, &again);
|
||||
int ok = got == want && again == want;
|
||||
cases++;
|
||||
passed += ok;
|
||||
printf("CASE %s %s (got %d then %d, want %d)\n", name, ok ? "PASS" : "FAIL", got, again, want);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (display == EGL_NO_DISPLAY || !eglInitialize(display, NULL, NULL) || !eglBindAPI(EGL_OPENGL_API)) return 2;
|
||||
const EGLint config_attributes[] = {
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
|
||||
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE,
|
||||
};
|
||||
EGLConfig config = NULL;
|
||||
EGLint config_count = 0;
|
||||
if (!eglChooseConfig(display, config_attributes, &config, 1, &config_count) || config_count < 1) return 2;
|
||||
const EGLint context_attributes[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE};
|
||||
const EGLint surface_attributes[] = {EGL_WIDTH, 16, EGL_HEIGHT, 8, EGL_NONE};
|
||||
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes);
|
||||
EGLSurface surface = eglCreatePbufferSurface(display, config, surface_attributes);
|
||||
if (context == EGL_NO_CONTEXT || surface == EGL_NO_SURFACE || !eglMakeCurrent(display, surface, surface, context))
|
||||
return 2;
|
||||
|
||||
GLint default_read = -1;
|
||||
glGetIntegerv(GL_READ_BUFFER, &default_read);
|
||||
GLuint fbo[2], rb[2];
|
||||
glGenFramebuffers(2, fbo);
|
||||
glGenRenderbuffers(2, rb);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rb[0]);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 16, 8);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rb[1]);
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 16, 8);
|
||||
GLint ms = 0;
|
||||
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &ms);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb[0]);
|
||||
expect("fbo0 samples", GL_SAMPLES, 0);
|
||||
expect("fbo0 read buffer", GL_READ_BUFFER, GL_COLOR_ATTACHMENT0);
|
||||
glReadBuffer(GL_NONE);
|
||||
expect("fbo0 read buffer after glReadBuffer", GL_READ_BUFFER, GL_NONE);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo[1]);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb[1]);
|
||||
expect("fbo1 samples", GL_SAMPLES, ms);
|
||||
/* Re-attach the single-sampled one to the same framebuffer. */
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb[0]);
|
||||
expect("fbo1 samples after re-attaching", GL_SAMPLES, 0);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
expect("default read buffer", GL_READ_BUFFER, default_read);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[0]);
|
||||
expect("read default, draw fbo0: read buffer", GL_READ_BUFFER, default_read);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
|
||||
expect("read fbo0: read buffer", GL_READ_BUFFER, GL_NONE);
|
||||
|
||||
/* Deleted and made again: the name may come back, with fresh state. */
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &fbo[0]);
|
||||
GLuint again = 0;
|
||||
glGenFramebuffers(1, &again);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, again);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb[0]);
|
||||
expect("a new framebuffer: read buffer", GL_READ_BUFFER, GL_COLOR_ATTACHMENT0);
|
||||
|
||||
printf("SUMMARY cases=%d passed=%d failed=%d\n", cases, passed, cases - passed);
|
||||
return cases == passed ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user