Pace host presents and add cross-context sync barrier
Gate host presents with the surface's wl_callback so at most one buffer is outstanding at a time (coalescing bursts via back_dirty), add a glFinish barrier between the wlr GLES2 renderer context and the host EGL context used by the blit, and switch the scene clear colour to black after removing the import-failure present. Keep temporary F8/F9 frame dumps (scene vs. host back buffer) for grey-band diagnosis.
This commit is contained in:
parent
3d2c627552
commit
570465c6e6
1 changed files with 176 additions and 3 deletions
179
tabbed-wlroots.c
179
tabbed-wlroots.c
|
|
@ -47,6 +47,8 @@
|
|||
#include <wlr/interfaces/wlr_keyboard.h>
|
||||
#include <wlr/render/allocator.h>
|
||||
#include <wlr/render/drm_format_set.h>
|
||||
#include <wlr/render/egl.h>
|
||||
#include <wlr/render/gles2.h>
|
||||
#include <wlr/render/swapchain.h>
|
||||
#include <wlr/render/wlr_renderer.h>
|
||||
#include <wlr/types/wlr_buffer.h>
|
||||
|
|
@ -144,6 +146,11 @@ struct tabbed_host {
|
|||
GLint gl_pos_loc, gl_uv_loc, gl_tex_loc;
|
||||
GLuint import_tex;
|
||||
GLuint popup_tex;
|
||||
/* Present gating: at most one buffer aimed at the host compositor at a
|
||||
time, paced by the host surface's wl_callback. */
|
||||
struct wl_callback *frame_cb;
|
||||
bool present_pending;
|
||||
bool back_dirty;
|
||||
};
|
||||
|
||||
static struct tabbed_server server;
|
||||
|
|
@ -153,6 +160,12 @@ static bool needs_redraw;
|
|||
static bool frame_pending;
|
||||
static bool shutting_down;
|
||||
|
||||
/* Temporary frame-dump keys (F8/F9) to attribute the lxterm grey band: F8
|
||||
saves the scene buffer (client content as wlroots drew it), F9 saves our
|
||||
composited host back buffer. To be removed after diagnosis. */
|
||||
static bool dump_scene_req, dump_host_req;
|
||||
static int dump_seq;
|
||||
|
||||
static void set_active_surface(struct wlr_xdg_toplevel *toplevel);
|
||||
static void render_and_present(void);
|
||||
|
||||
|
|
@ -913,10 +926,85 @@ static void host_kbd_leave(void *data, struct wl_keyboard *kbd, uint32_t serial,
|
|||
wlr_seat_keyboard_notify_clear_focus(server.seat);
|
||||
}
|
||||
|
||||
/* Temporary grey-band diagnosis: dump the scene buffer (client content as
|
||||
wlroots rendered it) to /tmp/scene_N.ppm. The buffer memory is top-down,
|
||||
XRGB8888. */
|
||||
static void dump_scene_buffer(struct wlr_buffer *buf) {
|
||||
if (!buf) return;
|
||||
void *data;
|
||||
uint32_t format;
|
||||
size_t stride;
|
||||
if (!wlr_buffer_begin_data_ptr_access(buf, WLR_BUFFER_DATA_PTR_ACCESS_READ,
|
||||
&data, &format, &stride))
|
||||
return;
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/tmp/scene_%d.ppm", dump_seq);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) {
|
||||
int w = buf->width, h = buf->height;
|
||||
fprintf(f, "P6\n%d %d\n255\n", w, h);
|
||||
unsigned char *p = data;
|
||||
for (int y = 0; y < h; y++) {
|
||||
unsigned char *row = p + (size_t)y * stride;
|
||||
for (int x = 0; x < w; x++) {
|
||||
fputc(row[x*4+0], f);
|
||||
fputc(row[x*4+1], f);
|
||||
fputc(row[x*4+2], f);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
fprintf(stderr, "[dump] scene buffer -> %s (%dx%d, fmt=0x%x)\n",
|
||||
path, w, h, format);
|
||||
}
|
||||
wlr_buffer_end_data_ptr_access(buf);
|
||||
dump_seq++;
|
||||
}
|
||||
|
||||
/* Dump what we are about to present to the host (our composited back buffer).
|
||||
Must be called with the host EGL context current and back buffer freshly
|
||||
drawn. GL readback is bottom-up, so flip vertically. */
|
||||
static void dump_host_back(void) {
|
||||
int w = host.width, h = host.height;
|
||||
unsigned char *px = malloc((size_t)w * h * 4);
|
||||
if (!px) return;
|
||||
glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, px);
|
||||
char path[64];
|
||||
snprintf(path, sizeof(path), "/tmp/host_%d.ppm", dump_seq);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f) {
|
||||
fprintf(f, "P6\n%d %d\n255\n", w, h);
|
||||
for (int y = h - 1; y >= 0; y--) {
|
||||
unsigned char *row = px + (size_t)y * w * 4;
|
||||
for (int x = 0; x < w; x++) {
|
||||
fputc(row[x*4+0], f);
|
||||
fputc(row[x*4+1], f);
|
||||
fputc(row[x*4+2], f);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
fprintf(stderr, "[dump] host back buffer -> %s (%dx%d)\n", path, w, h);
|
||||
}
|
||||
free(px);
|
||||
dump_seq++;
|
||||
}
|
||||
|
||||
static void host_kbd_key(void *data, struct wl_keyboard *kbd, uint32_t serial,
|
||||
uint32_t time, uint32_t key, uint32_t state)
|
||||
{
|
||||
(void)data; (void)kbd; (void)serial;
|
||||
/* Temporary: F8/F9 frame dumps for the grey-band diagnosis. */
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
|
||||
if (key == 67) /* F8 */ {
|
||||
dump_scene_req = true;
|
||||
needs_redraw = true;
|
||||
return;
|
||||
}
|
||||
if (key == 68) /* F9 */ {
|
||||
dump_host_req = true;
|
||||
needs_redraw = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Ctrl+PageUp / Ctrl+PageDown cycle tabs */
|
||||
bool ctrl = (host.host_mods_depressed & (1u << 2)) != 0;
|
||||
if (state == WL_KEYBOARD_KEY_STATE_PRESSED && ctrl &&
|
||||
|
|
@ -1337,13 +1425,71 @@ static void composite_popups(void) {
|
|||
popup_draw_cb, &ctx);
|
||||
}
|
||||
|
||||
/* Pace presents to the host compositor: this fires after the host has shown
|
||||
the buffer we queued, so we know we may queue the next one. Any frame drawn
|
||||
while a present is still pending is coalesced into the back buffer and
|
||||
presented here, keeping exactly one outstanding buffer (no burst, no
|
||||
tearing on busy repainters like terminals). */
|
||||
static const struct wl_callback_listener host_frame_listener;
|
||||
/* Request a host frame callback and present the drawing in the back buffer.
|
||||
The callback must be requested *before* the swap: frame requests are part
|
||||
of the state committed by eglSwapBuffers, so requesting it after would wait
|
||||
on a commit that never comes. */
|
||||
static void present_to_host(void) {
|
||||
host.frame_cb = wl_surface_frame(host.surface);
|
||||
wl_callback_add_listener(host.frame_cb, &host_frame_listener, NULL);
|
||||
eglSwapBuffers(host.egl_display, host.egl_surface);
|
||||
host.present_pending = true;
|
||||
}
|
||||
|
||||
static void host_frame_done(void *data, struct wl_callback *callback,
|
||||
uint32_t callback_data)
|
||||
{
|
||||
(void)data; (void)callback; (void)callback_data;
|
||||
if (host.frame_cb) {
|
||||
wl_callback_destroy(host.frame_cb);
|
||||
host.frame_cb = NULL;
|
||||
}
|
||||
host.present_pending = false;
|
||||
|
||||
if (host.back_dirty) {
|
||||
host.back_dirty = false;
|
||||
present_to_host();
|
||||
}
|
||||
}
|
||||
|
||||
static const struct wl_callback_listener host_frame_listener = {
|
||||
.done = host_frame_done,
|
||||
};
|
||||
|
||||
/* The scene is rendered by the wlroots GLES2 renderer, which owns its own
|
||||
EGL display/context, while we sample that buffer from a *different* EGL
|
||||
context (host.egl_display). Without an explicit barrier the GPU may still
|
||||
be executing the renderer's draw commands when we import+read the dmabuf,
|
||||
which shows as flicker/tearing on content that repaints continuously.
|
||||
Make the renderer's context current on this thread and glFinish() so the
|
||||
buffer is guaranteed fully rendered before we blit it. */
|
||||
static void sync_renderer_context(void) {
|
||||
struct wlr_egl *src = wlr_gles2_renderer_get_egl(server.renderer);
|
||||
if (!src) return;
|
||||
EGLDisplay d = wlr_egl_get_display(src);
|
||||
EGLContext c = wlr_egl_get_context(src);
|
||||
if (d == EGL_NO_DISPLAY || c == EGL_NO_CONTEXT) return;
|
||||
if (d == host.egl_display && c == host.egl_context) return;
|
||||
eglMakeCurrent(d, EGL_NO_SURFACE, EGL_NO_SURFACE, c);
|
||||
glFinish();
|
||||
eglMakeCurrent(host.egl_display, host.egl_surface, host.egl_surface,
|
||||
host.egl_context);
|
||||
}
|
||||
|
||||
static void blit_scene_buffer(struct wlr_buffer *buf) {
|
||||
int w = host.width, h = host.height;
|
||||
glViewport(0, 0, w, h);
|
||||
glClearColor(0.13f, 0.13f, 0.13f, 1.0f);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (buf && import_scene_buffer(buf, host.import_tex)) {
|
||||
bool imported = buf && import_scene_buffer(buf, host.import_tex);
|
||||
if (imported) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
draw_texture_rect(host.import_tex, buf->width, buf->height,
|
||||
|
|
@ -1351,7 +1497,26 @@ static void blit_scene_buffer(struct wlr_buffer *buf) {
|
|||
glDisable(GL_BLEND);
|
||||
}
|
||||
composite_popups();
|
||||
eglSwapBuffers(host.egl_display, host.egl_surface);
|
||||
|
||||
if (dump_host_req) {
|
||||
dump_host_req = false;
|
||||
dump_host_back();
|
||||
}
|
||||
|
||||
if (!imported) {
|
||||
/* Never present a frame that is only the clear colour: keep the last
|
||||
good frame on the host. A later frame with a good import presents
|
||||
normally. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (host.present_pending) {
|
||||
/* Host hasn't shown the previous buffer yet: redraw into the back
|
||||
buffer and coalesce the swap until the frame callback arrives. */
|
||||
host.back_dirty = true;
|
||||
} else {
|
||||
present_to_host();
|
||||
}
|
||||
}
|
||||
|
||||
static void render_and_present(void) {
|
||||
|
|
@ -1371,6 +1536,14 @@ static void render_and_present(void) {
|
|||
goto frame_done;
|
||||
}
|
||||
|
||||
/* Barrier between the wlr renderer's context and our host blit. */
|
||||
sync_renderer_context();
|
||||
|
||||
if (dump_scene_req) {
|
||||
dump_scene_req = false;
|
||||
dump_scene_buffer(state.buffer);
|
||||
}
|
||||
|
||||
blit_scene_buffer(state.buffer);
|
||||
|
||||
if (!wlr_output_commit_state(server.output, &state))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue