mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
guest/gl: a swap at interval 0 submits its frame without waiting
eglSwapBuffers (and glXSwapBuffers through it) waited for the frame's batch to run so it could pace on the reply's count of frames shown. At swap interval 0 nothing is paced (Unity with vsync off), so the batch now goes with GEX_GL_SUBMIT_ASYNC, which both hosts already serve for the NT layer's frames: the host copies it and the guest builds the next frame while it runs; the next submit waits for it. In Muck's page the render thread spent 48% of its time in synchronous submits. A host that refuses the ioctl gets every batch the waiting way; GEXGL_SYNC_PRESENT=1 never tries. The state-query probe adds 50 swaps at interval 0 with reads between (tests/gl-state-query-cache.sh: 10 of 10 natively and under gexc --gl). 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
707e84b817
commit
7605de5ce4
2 files changed
+69
-1
No files matched your search
@@ -317,6 +317,49 @@ static int fbq_invalidates(uint32_t opcode) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Hand everything recorded so far to the host without waiting for it to
|
||||
run: GEX_GL_SUBMIT_ASYNC (shims/native/src/gl.rs, shims/browser/src/gl.rs)
|
||||
copies the batch and returns, and the next submit of either kind waits for
|
||||
it. For a batch with nothing to answer -- a frame's last, when nothing is
|
||||
paced on its reply. In the page a synchronous submit is a round trip to
|
||||
the compositor's Worker, which is most of what Unity's render thread spent
|
||||
its time on. A host without it refuses once, and every batch then goes
|
||||
the waiting way. GEXGL_SYNC_PRESENT=1 never tries. */
|
||||
#define GEXGL_SUBMIT_ASYNC 0xc0086704u
|
||||
static int async_refused = -1;
|
||||
|
||||
static void flush_async(void) {
|
||||
if (!ring || ring->tail == 0) {
|
||||
reply_at = 0;
|
||||
return;
|
||||
}
|
||||
if (async_refused < 0) async_refused = getenv("GEXGL_SYNC_PRESENT") ? 1 : 0;
|
||||
if (async_refused) {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
gexgl_note_submit();
|
||||
if (ioctl(gl_fd, GEXGL_SUBMIT_ASYNC, ring) != 0) {
|
||||
if (errno == ENOTTY || errno == EINVAL || errno == ENOSYS) {
|
||||
/* Refused before anything ran (an older host): the batch is still
|
||||
in the ring, so submit it the waiting way and stop trying. */
|
||||
async_refused = 1;
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
/* EIO: the batch was taken, and the one before it failed; that is
|
||||
this call's report, as flush() makes one. */
|
||||
static int said;
|
||||
if (!said) {
|
||||
said = 1;
|
||||
fprintf(stderr, "gexgl: an asynchronous batch failed on the host\n");
|
||||
}
|
||||
}
|
||||
ring->tail = 0;
|
||||
ring->reply_len = 0;
|
||||
reply_at = 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. */
|
||||
@@ -3235,6 +3278,13 @@ unsigned eglSwapBuffers(void *dpy, void *surface) {
|
||||
gexgl_x11_follow_size(s);
|
||||
gexgl_wl_commit(s);
|
||||
CMD0(GEXGL_PRESENT);
|
||||
/* Nothing is paced on the reply at swap interval 0 (Unity with vsync
|
||||
off), so the frame goes without waiting for it. */
|
||||
if (egl_swap_interval == 0) {
|
||||
flush_async();
|
||||
++egl_swaps_issued;
|
||||
return EGL_TRUE;
|
||||
}
|
||||
flush();
|
||||
/* How many frames the page has actually shown. Zero from a decoder that
|
||||
predates this reply, which is why nothing below acts on zero. */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* 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;
|
||||
* framebuffer whose name comes back; and swaps at interval 0, which the
|
||||
* library submits without waiting. 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)
|
||||
@@ -83,6 +84,23 @@ int main(void) {
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb[0]);
|
||||
expect("a new framebuffer: read buffer", GL_READ_BUFFER, GL_COLOR_ATTACHMENT0);
|
||||
|
||||
/* Swaps at interval 0, which the guest library submits without waiting
|
||||
* (flush_async), interleaved with reads that must see each frame. */
|
||||
eglSwapInterval(display, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
int frames_ok = 1;
|
||||
for (int i = 0; i < 50; i++) {
|
||||
glClearColor((float)(i & 1), 0.0f, 1.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
unsigned char px[4] = {7, 7, 7, 7};
|
||||
glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
||||
if (px[0] != ((i & 1) ? 255 : 0) || px[2] != 255) frames_ok = 0;
|
||||
eglSwapBuffers(display, surface);
|
||||
}
|
||||
cases++;
|
||||
passed += frames_ok;
|
||||
printf("CASE swaps at interval 0 %s\n", frames_ok ? "PASS" : "FAIL");
|
||||
|
||||
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