From 89da27716279aed04c09884b79c86f15db72427d Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Wed, 2 Sep 2026 02:29:00 +0300 Subject: [PATCH 01/14] freedreno/kgsl: bridge KMS scanout and X11 presentation --- src/freedreno/drm/freedreno_bo.c | 3 +- src/freedreno/drm/freedreno_priv.h | 1 + src/freedreno/drm/kgsl/kgsl_bo.c | 115 +++++++++++++- src/freedreno/drm/kgsl/kgsl_priv.h | 1 + .../frontends/dri/loader_dri3_helper.c | 144 +++++++++++++++++- .../frontends/dri/loader_dri3_helper.h | 11 ++ src/gallium/frontends/dri/meson.build | 2 +- 7 files changed, 272 insertions(+), 5 deletions(-) diff --git a/src/freedreno/drm/freedreno_bo.c b/src/freedreno/drm/freedreno_bo.c index 6eda8101e46c..7835c0db80fd 100644 --- a/src/freedreno/drm/freedreno_bo.c +++ b/src/freedreno/drm/freedreno_bo.c @@ -544,6 +544,8 @@ fd_bo_handle(struct fd_bo *bo) bo->bo_reuse = NO_CACHE; bo->alloc_flags |= FD_BO_SHARED; bo_flush(bo); + if (bo->funcs->kms_handle) + return bo->funcs->kms_handle(bo); return bo->handle; } @@ -844,4 +846,3 @@ fd_bo_state(struct fd_bo *bo) return FD_BO_STATE_BUSY; } - diff --git a/src/freedreno/drm/freedreno_priv.h b/src/freedreno/drm/freedreno_priv.h index 19c9d37f40e6..75f474b3657a 100644 --- a/src/freedreno/drm/freedreno_priv.h +++ b/src/freedreno/drm/freedreno_priv.h @@ -424,6 +424,7 @@ struct fd_bo_funcs { uint64_t (*iova)(struct fd_bo *bo); void (*set_name)(struct fd_bo *bo, const char *fmt, va_list ap); int (*dmabuf)(struct fd_bo *bo); + uint32_t (*kms_handle)(struct fd_bo *bo); /** * Optional hook that is called before ->destroy(). In the case of diff --git a/src/freedreno/drm/kgsl/kgsl_bo.c b/src/freedreno/drm/kgsl/kgsl_bo.c index c676b8197a5f..97739950d531 100644 --- a/src/freedreno/drm/kgsl/kgsl_bo.c +++ b/src/freedreno/drm/kgsl/kgsl_bo.c @@ -2,6 +2,7 @@ #include "util/os_file.h" #include "util/os_mman.h" +#include "drm-uapi/drm_mode.h" #include static uint64_t @@ -45,6 +46,16 @@ void kgsl_bo_close_handle(struct fd_bo *bo) { struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); + if (kgsl_bo->kms_handle) { + struct drm_gem_close close_req = { + .handle = kgsl_bo->kms_handle, + }; + if (drmIoctl(bo->dev->control_fd, DRM_IOCTL_GEM_CLOSE, &close_req)) { + ERROR_MSG("Failed to close KMS handle %u (%s)", + kgsl_bo->kms_handle, strerror(errno)); + } + kgsl_bo->kms_handle = 0; + } if (kgsl_bo->bo_type == KGSL_BO_IMPORT) { close(kgsl_bo->import_fd); } @@ -115,6 +126,29 @@ static int kgsl_bo_dmabuf(struct fd_bo *bo) { return os_dupfd_cloexec(kgsl_bo->import_fd); } +static uint32_t +kgsl_bo_kms_handle(struct fd_bo *bo) +{ + struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); + int dma_buf; + + if (kgsl_bo->kms_handle) + return kgsl_bo->kms_handle; + + dma_buf = kgsl_bo_dmabuf(bo); + if (dma_buf < 0) + return 0; + + if (drmPrimeFDToHandle(bo->dev->control_fd, dma_buf, + &kgsl_bo->kms_handle)) { + ERROR_MSG("Failed to import dma-buf into KMS control fd (%s)", + strerror(errno)); + kgsl_bo->kms_handle = 0; + } + close(dma_buf); + return kgsl_bo->kms_handle; +} + static const struct fd_bo_funcs bo_funcs = { .iova = kgsl_bo_iova, .set_name = kgsl_bo_set_name, @@ -124,6 +158,7 @@ static const struct fd_bo_funcs bo_funcs = { .cpu_prep = kgsl_bo_cpu_prep, .destroy = kgsl_bo_destroy, .dmabuf = kgsl_bo_dmabuf, + .kms_handle = kgsl_bo_kms_handle, }; /* Size is not used by KGSL */ @@ -244,13 +279,80 @@ dma_heap_alloc(uint64_t size) } } +/* + * Qualcomm's SDE KMS can reject generic dma-heap allocations in AddFB even + * though KGSL can render to them. When explicitly requested, allocate the + * shared object as a KMS dumb BO instead, export it as a dma-buf, and then + * import that dma-buf into KGSL. The original GEM handle can be destroyed as + * soon as PRIME export succeeds; the dma-buf keeps the object alive and the X + * server will import its own handle before scanout. + * + * Width/height here only size the allocation. The eventual framebuffer's + * actual dimensions, format and pitch are supplied by GBM/DRI3 when the X + * server imports this dma-buf. + */ +static int +kms_dumb_alloc(struct fd_device *dev, uint32_t size) +{ + struct drm_mode_create_dumb create = { + .width = DIV_ROUND_UP(size, 4), + .height = 1, + .bpp = 32, + }; + struct drm_mode_destroy_dumb destroy = {0}; + const char *kms_path; + int kms_fd; + int owned_fd = -1; + int fd = -1; + + if (!debug_get_bool_option("FD_KGSL_USE_KMS_DUMB", false) || + dev->control_fd < 0) + return -1; + + kms_fd = dev->control_fd; + if (drmIoctl(kms_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create)) { + kms_path = debug_get_option("FD_KGSL_KMS_DEVICE", NULL); + if (!kms_path || + (owned_fd = open(kms_path, O_RDWR | O_CLOEXEC)) < 0 || + drmIoctl(owned_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create)) { + ERROR_MSG("Failed to allocate KMS dumb dma-buf (%s); falling back to dma-heap", + strerror(errno)); + if (owned_fd >= 0) + close(owned_fd); + return -1; + } + kms_fd = owned_fd; + } + + destroy.handle = create.handle; + if (drmPrimeHandleToFD(kms_fd, create.handle, + DRM_CLOEXEC | DRM_RDWR, &fd)) { + ERROR_MSG("Failed to export KMS dumb dma-buf (%s); falling back to dma-heap", + strerror(errno)); + fd = -1; + } + + if (drmIoctl(kms_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy)) { + ERROR_MSG("Failed to destroy temporary KMS dumb handle (%s)", + strerror(errno)); + } + if (owned_fd >= 0) + close(owned_fd); + + return fd; +} + static struct fd_bo * kgsl_bo_new_dmabuf(struct fd_device *dev, uint32_t size) { + bool kms_backed; int fd; struct fd_bo *bo; - fd = dma_heap_alloc(size); + fd = kms_dumb_alloc(dev, size); + kms_backed = fd >= 0; + if (fd < 0) + fd = dma_heap_alloc(size); if (fd < 0) { ERROR_MSG("Failed to allocate dma-buf (%s)", strerror(errno)); return NULL; @@ -258,6 +360,17 @@ kgsl_bo_new_dmabuf(struct fd_device *dev, uint32_t size) bo = kgsl_bo_from_dmabuf(dev, fd); + /* A platform may support dumb allocation/PRIME export but still reject the + * object at KGSL import. Preserve the established dma-heap path as a + * strict fallback rather than failing shared-buffer allocation outright. */ + if (!bo && kms_backed) { + close(fd); + fd = dma_heap_alloc(size); + if (fd < 0) + return NULL; + bo = kgsl_bo_from_dmabuf(dev, fd); + } + close(fd); return bo; } diff --git a/src/freedreno/drm/kgsl/kgsl_priv.h b/src/freedreno/drm/kgsl/kgsl_priv.h index 09e77a1dafb1..d92440211a22 100644 --- a/src/freedreno/drm/kgsl/kgsl_priv.h +++ b/src/freedreno/drm/kgsl/kgsl_priv.h @@ -42,6 +42,7 @@ struct kgsl_bo { const char *name; uint64_t iova; uint32_t queue_id; + uint32_t kms_handle; int import_fd; // fd for imported buffers enum { diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index f214898b4618..6b8722529d45 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -29,10 +29,12 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -42,6 +44,8 @@ #include "pipe/p_screen.h" #include "drm-uapi/dma-buf.h" #include "util/libsync.h" +#include "util/anon_file.h" +#include "util/u_debug.h" #include "util/log.h" #include "util/macros.h" #include "util/u_atomic.h" @@ -66,6 +70,121 @@ static struct loader_dri3_blit_context blit_context = { SIMPLE_MTX_INITIALIZER, NULL }; +static xcb_gcontext_t +dri3_drawable_gc(struct loader_dri3_drawable *draw); + +static void +dri3_shm_bridge_buffer_fini(struct loader_dri3_drawable *draw, + struct loader_dri3_buffer *buffer) +{ + if (!buffer->shm_bridge_map) + return; + + xcb_shm_detach(draw->conn, buffer->shm_bridge_seg); + munmap(buffer->shm_bridge_map, buffer->shm_bridge_size); + buffer->shm_bridge_seg = 0; + buffer->shm_bridge_map = NULL; + buffer->shm_bridge_size = 0; + buffer->shm_bridge_stride = 0; +} + +static bool +dri3_shm_bridge_buffer_init(struct loader_dri3_drawable *draw, + struct loader_dri3_buffer *buffer) +{ + const uint32_t stride = (buffer->width * buffer->cpp + 3u) & ~3u; + const size_t size = (size_t) stride * buffer->height; + xcb_generic_error_t *error; + int fd; + + if (buffer->shm_bridge_map && + buffer->shm_bridge_stride == stride && + buffer->shm_bridge_size == size) + return true; + + dri3_shm_bridge_buffer_fini(draw, buffer); + fd = os_create_anonymous_file(size, "mesa-kgsl-x11-bridge"); + if (fd < 0) + return false; + + buffer->shm_bridge_map = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (buffer->shm_bridge_map == MAP_FAILED) { + buffer->shm_bridge_map = NULL; + close(fd); + return false; + } + + buffer->shm_bridge_seg = xcb_generate_id(draw->conn); + error = xcb_request_check( + draw->conn, + xcb_shm_attach_fd_checked(draw->conn, buffer->shm_bridge_seg, fd, false)); + close(fd); + if (error) { + mesa_loge("DRI3: KGSL MIT-SHM bridge attach failed: X error %u", + error->error_code); + free(error); + munmap(buffer->shm_bridge_map, size); + buffer->shm_bridge_seg = 0; + buffer->shm_bridge_map = NULL; + return false; + } + + buffer->shm_bridge_size = size; + buffer->shm_bridge_stride = stride; + return true; +} + +static bool +dri3_shm_bridge_present(struct loader_dri3_drawable *draw, + struct loader_dri3_buffer *buffer) +{ + struct dri_context *ctx = draw->vtable->get_dri_context(draw); + xcb_generic_error_t *error; + void *transfer = NULL; + int source_stride = 0; + uint8_t *source; + + if (!ctx || !dri3_shm_bridge_buffer_init(draw, buffer)) + return false; + + source = dri2_map_image(ctx, buffer->image, 0, 0, + buffer->width, buffer->height, + __DRI_IMAGE_TRANSFER_READ, + &source_stride, &transfer); + if (!source) + return false; + + for (uint32_t y = 0; y < buffer->height; y++) { + memcpy((uint8_t *) buffer->shm_bridge_map + + (size_t) y * buffer->shm_bridge_stride, + source + (size_t) y * source_stride, + buffer->width * buffer->cpp); + } + dri2_unmap_image(ctx, buffer->image, transfer); + + /* Waiting for the checked request guarantees that Xorg has consumed this + * buffer before the client reuses it. This is one local round trip, but it + * avoids a full-screen ShadowFB loop and copies only accelerated drawables. */ + error = xcb_request_check( + draw->conn, + xcb_shm_put_image_checked(draw->conn, draw->drawable, + dri3_drawable_gc(draw), + buffer->width, buffer->height, + 0, 0, buffer->width, buffer->height, + 0, 0, draw->depth, + XCB_IMAGE_FORMAT_Z_PIXMAP, false, + buffer->shm_bridge_seg, 0)); + if (error) { + mesa_loge("DRI3: KGSL MIT-SHM bridge copy failed: X error %u", + error->error_code); + free(error); + return false; + } + + return true; +} + struct loader_dri3_present_sync { struct util_queue queue; int cancel_fd; @@ -168,7 +287,8 @@ dri3_present_sync_init(struct loader_dri3_drawable *draw, int buffer_fd) draw->present_sync_checked = true; - if (draw->type != LOADER_DRI3_DRAWABLE_WINDOW || + if (draw->shm_bridge || + draw->type != LOADER_DRI3_DRAWABLE_WINDOW || draw->dri_screen_render_gpu != draw->dri_screen_display_gpu || !(dri_fence_get_caps(draw->dri_screen_render_gpu) & __DRI_FENCE_CAP_NATIVE_FD) || @@ -546,6 +666,7 @@ dri3_free_render_buffer(struct loader_dri3_drawable *draw, if (buffer->present_wait_fence) xcb_sync_destroy_fence(draw->conn, buffer->present_wait_fence); + dri3_shm_bridge_buffer_fini(draw, buffer); if (buffer->own_pixmap) xcb_free_pixmap(draw->conn, buffer->pixmap); dri2_destroy_image(buffer->image); @@ -614,6 +735,8 @@ loader_dri3_drawable_init(xcb_connection_t *conn, draw->queries_buffer_age = false; draw->present_sync_checked = false; draw->present_sync = NULL; + draw->shm_bridge = + debug_get_bool_option("MESA_KGSL_X11_SHM_BRIDGE", false); draw->have_back = 0; draw->have_fake_front = 0; @@ -1259,7 +1382,8 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, if (!draw->have_back || draw->type == LOADER_DRI3_DRAWABLE_PIXMAP) return ret; - if (draw->type == LOADER_DRI3_DRAWABLE_WINDOW && + if (!draw->shm_bridge && + draw->type == LOADER_DRI3_DRAWABLE_WINDOW && draw->present_sync && draw->vtable->flush_drawable_with_fence_fd) { render_fence_fd = @@ -1276,6 +1400,22 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, return ret; } + if (draw->shm_bridge && + draw->type == LOADER_DRI3_DRAWABLE_WINDOW && + dri3_shm_bridge_present(draw, back)) { + if (render_fence_fd >= 0) + close(render_fence_fd); + mtx_lock(&draw->mtx); + draw->send_sbc++; + draw->recv_sbc = draw->send_sbc; + back->last_swap = draw->send_sbc; + if (draw->stamp) + ++(*draw->stamp); + mtx_unlock(&draw->mtx); + dri_invalidate_drawable(draw->dri_drawable); + return (int64_t) draw->send_sbc; + } + mtx_lock(&draw->mtx); if (draw->adaptive_sync && !draw->adaptive_sync_active) { diff --git a/src/gallium/frontends/dri/loader_dri3_helper.h b/src/gallium/frontends/dri/loader_dri3_helper.h index 6104050d4ea8..a38046dfb6f9 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.h +++ b/src/gallium/frontends/dri/loader_dri3_helper.h @@ -25,11 +25,13 @@ #define LOADER_DRI3_HEADER_H #include +#include #include #include #include #include +#include #include #include "mesa_interface.h" @@ -80,6 +82,14 @@ struct loader_dri3_buffer { uint32_t flags; uint32_t width, height; uint64_t last_swap; + + /* Optional KGSL/X11 fallback. The render GPU is still used, but the + * completed image is copied through one persistent MIT-SHM allocation + * when the X server cannot consume KGSL dma-bufs correctly. */ + xcb_shm_seg_t shm_bridge_seg; + void *shm_bridge_map; + size_t shm_bridge_size; + uint32_t shm_bridge_stride; }; @@ -174,6 +184,7 @@ struct loader_dri3_drawable { bool block_on_depleted_buffers; bool queries_buffer_age; bool present_sync_checked; + bool shm_bridge; int swap_interval; struct loader_dri3_present_sync *present_sync; diff --git a/src/gallium/frontends/dri/meson.build b/src/gallium/frontends/dri/meson.build index 566118201492..c5c62dfbdbef 100644 --- a/src/gallium/frontends/dri/meson.build +++ b/src/gallium/frontends/dri/meson.build @@ -23,7 +23,7 @@ if with_platform_x11 deps_for_libdri += dep_xcb if with_dri_platform == 'drm' or with_dri_platform == 'pseudo-drm' deps_for_libdri += [dep_xcb_dri3, dep_xcb_present, dep_xcb_sync, - dep_xshmfence, dep_xcb_xfixes] + dep_xshmfence, dep_xcb_xfixes, dep_xcb_shm] files_libdri += files('loader_dri3_helper.c') endif endif From 2788d8df63e43bd4d2c3fdb6bcb1010c863ccff0 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Wed, 2 Sep 2026 14:18:30 +0300 Subject: [PATCH 02/14] dri3: pipeline the KGSL X11 SHM bridge --- .../frontends/dri/loader_dri3_helper.c | 300 ++++++++++++++---- .../frontends/dri/loader_dri3_helper.h | 31 +- 2 files changed, 256 insertions(+), 75 deletions(-) diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index 6b8722529d45..a16588e4b8b3 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include @@ -73,115 +75,264 @@ static struct loader_dri3_blit_context blit_context = { static xcb_gcontext_t dri3_drawable_gc(struct loader_dri3_drawable *draw); +static int64_t +dri3_shm_bridge_now_ns(void) +{ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (int64_t) now.tv_sec * 1000000000LL + now.tv_nsec; +} + static void -dri3_shm_bridge_buffer_fini(struct loader_dri3_drawable *draw, - struct loader_dri3_buffer *buffer) +dri3_shm_bridge_fini(struct loader_dri3_drawable *draw) { - if (!buffer->shm_bridge_map) + if (!draw->shm_bridge_conn) return; - xcb_shm_detach(draw->conn, buffer->shm_bridge_seg); - munmap(buffer->shm_bridge_map, buffer->shm_bridge_size); - buffer->shm_bridge_seg = 0; - buffer->shm_bridge_map = NULL; - buffer->shm_bridge_size = 0; - buffer->shm_bridge_stride = 0; + if (draw->shm_bridge_present_event) { + xcb_present_select_input(draw->shm_bridge_conn, + draw->shm_bridge_present_eid, draw->drawable, + XCB_PRESENT_EVENT_MASK_NO_EVENT); + xcb_unregister_for_special_event(draw->shm_bridge_conn, + draw->shm_bridge_present_event); + } + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + struct loader_dri3_shm_bridge_slot *slot = &draw->shm_bridge_slots[i]; + if (slot->map) { + xcb_shm_detach(draw->shm_bridge_conn, slot->seg); + munmap(slot->map, slot->size); + } + memset(slot, 0, sizeof(*slot)); + } + if (draw->shm_bridge_gc) + xcb_free_gc(draw->shm_bridge_conn, draw->shm_bridge_gc); + xcb_disconnect(draw->shm_bridge_conn); + draw->shm_bridge_conn = NULL; + draw->shm_bridge_gc = 0; + draw->shm_bridge_present_event = NULL; } static bool -dri3_shm_bridge_buffer_init(struct loader_dri3_drawable *draw, - struct loader_dri3_buffer *buffer) +dri3_shm_bridge_init(struct loader_dri3_drawable *draw, + struct loader_dri3_buffer *buffer) { const uint32_t stride = (buffer->width * buffer->cpp + 3u) & ~3u; const size_t size = (size_t) stride * buffer->height; - xcb_generic_error_t *error; - int fd; + xcb_generic_error_t *error = NULL; - if (buffer->shm_bridge_map && - buffer->shm_bridge_stride == stride && - buffer->shm_bridge_size == size) + if (draw->shm_bridge_conn && draw->shm_bridge_slots[0].size == size && + draw->shm_bridge_slots[0].stride == stride) return true; + dri3_shm_bridge_fini(draw); - dri3_shm_bridge_buffer_fini(draw, buffer); - fd = os_create_anonymous_file(size, "mesa-kgsl-x11-bridge"); - if (fd < 0) - return false; - - buffer->shm_bridge_map = mmap(NULL, size, PROT_READ | PROT_WRITE, - MAP_SHARED, fd, 0); - if (buffer->shm_bridge_map == MAP_FAILED) { - buffer->shm_bridge_map = NULL; - close(fd); + draw->shm_bridge_conn = xcb_connect(NULL, NULL); + if (!draw->shm_bridge_conn || xcb_connection_has_error(draw->shm_bridge_conn)) { + mesa_loge("DRI3: KGSL MIT-SHM bridge cannot open its private X connection"); + dri3_shm_bridge_fini(draw); return false; } - - buffer->shm_bridge_seg = xcb_generate_id(draw->conn); - error = xcb_request_check( - draw->conn, - xcb_shm_attach_fd_checked(draw->conn, buffer->shm_bridge_seg, fd, false)); - close(fd); + draw->shm_bridge_gc = xcb_generate_id(draw->shm_bridge_conn); + error = xcb_request_check(draw->shm_bridge_conn, + xcb_create_gc_checked(draw->shm_bridge_conn, draw->shm_bridge_gc, + draw->drawable, 0, NULL)); if (error) { - mesa_loge("DRI3: KGSL MIT-SHM bridge attach failed: X error %u", - error->error_code); + mesa_loge("DRI3: KGSL MIT-SHM bridge GC failed: X error %u", error->error_code); free(error); - munmap(buffer->shm_bridge_map, size); - buffer->shm_bridge_seg = 0; - buffer->shm_bridge_map = NULL; + dri3_shm_bridge_fini(draw); return false; } - buffer->shm_bridge_size = size; - buffer->shm_bridge_stride = stride; + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + struct loader_dri3_shm_bridge_slot *slot = &draw->shm_bridge_slots[i]; + int fd = os_create_anonymous_file(size, "mesa-kgsl-x11-bridge"); + if (fd < 0) { + dri3_shm_bridge_fini(draw); + return false; + } + slot->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (slot->map == MAP_FAILED) { + slot->map = NULL; + close(fd); + dri3_shm_bridge_fini(draw); + return false; + } + slot->seg = xcb_generate_id(draw->shm_bridge_conn); + error = xcb_request_check(draw->shm_bridge_conn, + xcb_shm_attach_fd_checked(draw->shm_bridge_conn, slot->seg, fd, false)); + close(fd); + if (error) { + mesa_loge("DRI3: KGSL MIT-SHM bridge attach failed: X error %u", + error->error_code); + free(error); + slot->size = size; + dri3_shm_bridge_fini(draw); + return false; + } + slot->size = size; + slot->stride = stride; + } + + draw->shm_bridge_present_eid = xcb_generate_id(draw->shm_bridge_conn); + uint32_t stamp = 0; + draw->shm_bridge_present_event = xcb_register_for_special_xge( + draw->shm_bridge_conn, &xcb_present_id, draw->shm_bridge_present_eid, &stamp); + if (draw->shm_bridge_present_event) { + xcb_present_select_input(draw->shm_bridge_conn, + draw->shm_bridge_present_eid, draw->drawable, + XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY); + } + draw->shm_bridge_next_slot = 0; + draw->shm_bridge_msc = 0; + draw->shm_bridge_stats_started_ns = dri3_shm_bridge_now_ns(); + xcb_flush(draw->shm_bridge_conn); return true; } +static void +dri3_shm_bridge_handle_event(struct loader_dri3_drawable *draw, + xcb_generic_event_t *event) +{ + const xcb_query_extension_reply_t *extension = + xcb_get_extension_data(draw->shm_bridge_conn, &xcb_shm_id); + uint8_t type = event->response_type & 0x7f; + if (extension && type == extension->first_event + XCB_SHM_COMPLETION) { + xcb_shm_completion_event_t *completion = (void *) event; + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + if (draw->shm_bridge_slots[i].seg == completion->shmseg) + draw->shm_bridge_slots[i].busy = false; + } + } else if (type == 0) { + xcb_generic_error_t *error = (void *) event; + mesa_loge("DRI3: KGSL MIT-SHM bridge asynchronous X error %u", + error->error_code); + } + free(event); +} + +static struct loader_dri3_shm_bridge_slot * +dri3_shm_bridge_acquire_slot(struct loader_dri3_drawable *draw) +{ + xcb_generic_event_t *event; + while ((event = xcb_poll_for_event(draw->shm_bridge_conn))) + dri3_shm_bridge_handle_event(draw, event); + + for (;;) { + for (unsigned offset = 0; offset < LOADER_DRI3_SHM_BRIDGE_SLOTS; offset++) { + unsigned index = (draw->shm_bridge_next_slot + offset) % + LOADER_DRI3_SHM_BRIDGE_SLOTS; + if (!draw->shm_bridge_slots[index].busy) { + draw->shm_bridge_next_slot = (index + 1) % LOADER_DRI3_SHM_BRIDGE_SLOTS; + return &draw->shm_bridge_slots[index]; + } + } + draw->shm_bridge_waits++; + event = xcb_wait_for_event(draw->shm_bridge_conn); + if (!event) + return NULL; + dri3_shm_bridge_handle_event(draw, event); + } +} + +static bool +dri3_shm_bridge_wait_vblank(struct loader_dri3_drawable *draw) +{ + if (draw->swap_interval == 0 || !draw->shm_bridge_present_event) + return true; + uint32_t serial = ++draw->shm_bridge_present_serial; + uint64_t target = draw->shm_bridge_msc ? + draw->shm_bridge_msc + abs(draw->swap_interval) : 0; + xcb_present_notify_msc(draw->shm_bridge_conn, draw->drawable, serial, + target, 0, 0); + xcb_flush(draw->shm_bridge_conn); + for (;;) { + xcb_generic_event_t *event = xcb_wait_for_special_event( + draw->shm_bridge_conn, draw->shm_bridge_present_event); + if (!event) + return false; + xcb_present_generic_event_t *generic = (void *) event; + if (generic->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY) { + xcb_present_complete_notify_event_t *complete = (void *) event; + if (complete->kind == XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC && + complete->serial == serial) { + draw->shm_bridge_msc = complete->msc; + free(event); + return true; + } + } + free(event); + } +} + static bool dri3_shm_bridge_present(struct loader_dri3_drawable *draw, - struct loader_dri3_buffer *buffer) + struct loader_dri3_buffer *buffer, + const int *rects, int n_rects) { struct dri_context *ctx = draw->vtable->get_dri_context(draw); - xcb_generic_error_t *error; + struct loader_dri3_shm_bridge_slot *slot; void *transfer = NULL; int source_stride = 0; uint8_t *source; + int x0 = 0, y0 = 0, x1 = buffer->width, y1 = buffer->height; - if (!ctx || !dri3_shm_bridge_buffer_init(draw, buffer)) + if (!ctx || !dri3_shm_bridge_init(draw, buffer) || + !dri3_shm_bridge_wait_vblank(draw)) + return false; + slot = dri3_shm_bridge_acquire_slot(draw); + if (!slot) return false; + /* EGL damage is bottom-left based. Coalesce it into one conservative X11 + * rectangle; GLX supplies no damage and therefore keeps a full copy. */ + if (rects && n_rects > 0) { + x0 = buffer->width; + y0 = buffer->height; + x1 = y1 = 0; + for (int i = 0; i < n_rects; i++) { + int rx0 = CLAMP(rects[i * 4], 0, (int) buffer->width); + int rw = CLAMP(rects[i * 4 + 2], 0, (int) buffer->width - rx0); + int rh = CLAMP(rects[i * 4 + 3], 0, (int) buffer->height); + int ry0 = CLAMP((int) buffer->height - rects[i * 4 + 1] - rh, + 0, (int) buffer->height); + x0 = MIN2(x0, rx0); + y0 = MIN2(y0, ry0); + x1 = MAX2(x1, rx0 + rw); + y1 = MAX2(y1, ry0 + rh); + } + if (x1 <= x0 || y1 <= y0) + return true; + } + source = dri2_map_image(ctx, buffer->image, 0, 0, buffer->width, buffer->height, __DRI_IMAGE_TRANSFER_READ, &source_stride, &transfer); if (!source) return false; - - for (uint32_t y = 0; y < buffer->height; y++) { - memcpy((uint8_t *) buffer->shm_bridge_map + - (size_t) y * buffer->shm_bridge_stride, - source + (size_t) y * source_stride, - buffer->width * buffer->cpp); + for (int y = y0; y < y1; y++) { + memcpy((uint8_t *) slot->map + (size_t) y * slot->stride + + (size_t) x0 * buffer->cpp, + source + (size_t) y * source_stride + (size_t) x0 * buffer->cpp, + (size_t) (x1 - x0) * buffer->cpp); } dri2_unmap_image(ctx, buffer->image, transfer); - /* Waiting for the checked request guarantees that Xorg has consumed this - * buffer before the client reuses it. This is one local round trip, but it - * avoids a full-screen ShadowFB loop and copies only accelerated drawables. */ - error = xcb_request_check( - draw->conn, - xcb_shm_put_image_checked(draw->conn, draw->drawable, - dri3_drawable_gc(draw), - buffer->width, buffer->height, - 0, 0, buffer->width, buffer->height, - 0, 0, draw->depth, - XCB_IMAGE_FORMAT_Z_PIXMAP, false, - buffer->shm_bridge_seg, 0)); - if (error) { - mesa_loge("DRI3: KGSL MIT-SHM bridge copy failed: X error %u", - error->error_code); - free(error); - return false; + xcb_shm_put_image(draw->shm_bridge_conn, draw->drawable, + draw->shm_bridge_gc, buffer->width, buffer->height, + x0, y0, x1 - x0, y1 - y0, x0, y0, draw->depth, + XCB_IMAGE_FORMAT_Z_PIXMAP, true, slot->seg, 0); + slot->busy = true; + xcb_flush(draw->shm_bridge_conn); + + draw->shm_bridge_frames++; + draw->shm_bridge_bytes += (uint64_t) (x1 - x0) * (y1 - y0) * buffer->cpp; + if (draw->shm_bridge_stats && draw->shm_bridge_frames % 300 == 0) { + double seconds = (dri3_shm_bridge_now_ns() - draw->shm_bridge_stats_started_ns) / 1e9; + mesa_logi("DRI3: KGSL bridge frames=%" PRIu64 " copied=%.1f MiB rate=%.1f fps waits=%" PRIu64, + draw->shm_bridge_frames, draw->shm_bridge_bytes / (1024.0 * 1024.0), + seconds > 0.0 ? draw->shm_bridge_frames / seconds : 0.0, + draw->shm_bridge_waits); } - return true; } @@ -666,7 +817,6 @@ dri3_free_render_buffer(struct loader_dri3_drawable *draw, if (buffer->present_wait_fence) xcb_sync_destroy_fence(draw->conn, buffer->present_wait_fence); - dri3_shm_bridge_buffer_fini(draw, buffer); if (buffer->own_pixmap) xcb_free_pixmap(draw->conn, buffer->pixmap); dri2_destroy_image(buffer->image); @@ -691,6 +841,8 @@ loader_dri3_drawable_fini(struct loader_dri3_drawable *draw) for (i = 0; i < ARRAY_SIZE(draw->buffers); i++) dri3_free_render_buffer(draw, i); + dri3_shm_bridge_fini(draw); + if (draw->special_event) { xcb_void_cookie_t cookie = xcb_present_select_input_checked(draw->conn, draw->eid, draw->drawable, @@ -737,6 +889,18 @@ loader_dri3_drawable_init(xcb_connection_t *conn, draw->present_sync = NULL; draw->shm_bridge = debug_get_bool_option("MESA_KGSL_X11_SHM_BRIDGE", false); + draw->shm_bridge_stats = + debug_get_bool_option("MESA_KGSL_X11_BRIDGE_STATS", false); + draw->shm_bridge_conn = NULL; + draw->shm_bridge_gc = 0; + draw->shm_bridge_present_event = NULL; + memset(draw->shm_bridge_slots, 0, sizeof(draw->shm_bridge_slots)); + draw->shm_bridge_next_slot = 0; + draw->shm_bridge_present_serial = 0; + draw->shm_bridge_msc = 0; + draw->shm_bridge_frames = 0; + draw->shm_bridge_bytes = 0; + draw->shm_bridge_waits = 0; draw->have_back = 0; draw->have_fake_front = 0; @@ -1402,7 +1566,7 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, if (draw->shm_bridge && draw->type == LOADER_DRI3_DRAWABLE_WINDOW && - dri3_shm_bridge_present(draw, back)) { + dri3_shm_bridge_present(draw, back, rects, n_rects)) { if (render_fence_fd >= 0) close(render_fence_fd); mtx_lock(&draw->mtx); diff --git a/src/gallium/frontends/dri/loader_dri3_helper.h b/src/gallium/frontends/dri/loader_dri3_helper.h index a38046dfb6f9..e5ccc8d52fa2 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.h +++ b/src/gallium/frontends/dri/loader_dri3_helper.h @@ -83,13 +83,6 @@ struct loader_dri3_buffer { uint32_t width, height; uint64_t last_swap; - /* Optional KGSL/X11 fallback. The render GPU is still used, but the - * completed image is copied through one persistent MIT-SHM allocation - * when the X server cannot consume KGSL dma-bufs correctly. */ - xcb_shm_seg_t shm_bridge_seg; - void *shm_bridge_map; - size_t shm_bridge_size; - uint32_t shm_bridge_stride; }; @@ -109,6 +102,16 @@ loader_dri3_pixmap_buf_id(enum loader_dri3_buffer_type buffer_type) struct loader_dri3_drawable; struct loader_dri3_present_sync; +#define LOADER_DRI3_SHM_BRIDGE_SLOTS 3 + +struct loader_dri3_shm_bridge_slot { + xcb_shm_seg_t seg; + void *map; + size_t size; + uint32_t stride; + bool busy; +}; + struct loader_dri3_vtable { void (*set_drawable_size)(struct loader_dri3_drawable *, int, int); bool (*in_current_context)(struct loader_dri3_drawable *); @@ -185,6 +188,20 @@ struct loader_dri3_drawable { bool queries_buffer_age; bool present_sync_checked; bool shm_bridge; + bool shm_bridge_stats; + xcb_connection_t *shm_bridge_conn; + xcb_gcontext_t shm_bridge_gc; + xcb_present_event_t shm_bridge_present_eid; + xcb_special_event_t *shm_bridge_present_event; + struct loader_dri3_shm_bridge_slot + shm_bridge_slots[LOADER_DRI3_SHM_BRIDGE_SLOTS]; + uint32_t shm_bridge_next_slot; + uint32_t shm_bridge_present_serial; + uint64_t shm_bridge_msc; + uint64_t shm_bridge_frames; + uint64_t shm_bridge_bytes; + uint64_t shm_bridge_waits; + int64_t shm_bridge_stats_started_ns; int swap_interval; struct loader_dri3_present_sync *present_sync; From 4c72c7a4b4c33746e1b7b0914e837f4371f16c2d Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 02:32:12 +0300 Subject: [PATCH 03/14] dri3: decouple KGSL rendering from SHM presentation --- src/egl/drivers/dri2/platform_x11_dri3.c | 4 + .../frontends/dri/loader_dri3_helper.c | 720 ++++++++++++++---- .../frontends/dri/loader_dri3_helper.h | 1 + src/glx/dri3_glx.c | 4 + 4 files changed, 588 insertions(+), 141 deletions(-) diff --git a/src/egl/drivers/dri2/platform_x11_dri3.c b/src/egl/drivers/dri2/platform_x11_dri3.c index a79f3a3c8731..817b0dfbaf71 100644 --- a/src/egl/drivers/dri2/platform_x11_dri3.c +++ b/src/egl/drivers/dri2/platform_x11_dri3.c @@ -43,6 +43,10 @@ #include "x11_dri3.h" #include "loader_dri3_helper.h" +/* Keep the private EGL frontend tied to the DRI bridge structure ABI. */ +const char hdmi_los_mesa_egl_bridge_abi[] + __attribute__((used, retain)) = LOADER_DRI3_SHM_BRIDGE_ABI; + static struct dri3_egl_surface * loader_drawable_to_egl_surface(struct loader_dri3_drawable *draw) { diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index a16588e4b8b3..3f003a6332d5 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -75,6 +75,70 @@ static struct loader_dri3_blit_context blit_context = { static xcb_gcontext_t dri3_drawable_gc(struct loader_dri3_drawable *draw); +/* + * Keep this marker in the private DRI target so the HDMI launcher can reject + * an older bridge implementation before it starts Xorg. Bump the value when + * the launcher-visible bridge contract changes. + */ +static const char dri3_shm_bridge_abi[] = LOADER_DRI3_SHM_BRIDGE_ABI; + +/* Keep the large asynchronous state out of loader_dri3_drawable. The smaller + * bridge fields in that structure still make the DRI target, libGLX and + * libEGL a matched ABI set; the HDMI launcher verifies the marker above in + * all three libraries before enabling the bridge. */ +struct dri3_shm_bridge_state { + xcb_special_event_t *present_event; + uint32_t present_stamp; + xcb_pixmap_t pixmaps[LOADER_DRI3_SHM_BRIDGE_SLOTS]; + uint32_t slot_serials[LOADER_DRI3_SHM_BRIDGE_SLOTS]; + uint64_t slot_sequences[LOADER_DRI3_SHM_BRIDGE_SLOTS]; + bool slot_completed[LOADER_DRI3_SHM_BRIDGE_SLOTS]; + bool slot_idle[LOADER_DRI3_SHM_BRIDGE_SLOTS]; + mtx_t mtx; + cnd_t cnd; + thrd_t thread; + int wake_fd; + bool mtx_initialized; + bool cnd_initialized; + bool thread_started; + bool capture_available; + bool capture_in_progress; + bool ready; + bool present_primed; + int stop; + int capture_slot; + int ready_slot; + int ready_swap_interval; + int capture_fence_fd; + struct loader_dri3_buffer *capture_buffer; + uint64_t produced_frames; + uint64_t presented_frames; + uint64_t dropped_frames; + uint64_t server_skipped_frames; + uint64_t submitted_frames; + uint64_t pending_sequence; + uint64_t presented_sequence; + uint64_t next_target_msc; + uint64_t bytes; + uint64_t waits; + int64_t stats_started_ns; +}; + +static struct dri3_shm_bridge_state * +dri3_shm_bridge_state(struct loader_dri3_drawable *draw) +{ + return (void *) draw->shm_bridge_present_event; +} + +static int +dri3_shm_bridge_present_thread(void *data); + +static struct dri_context * +loader_dri3_blit_context_get(struct loader_dri3_drawable *draw); + +static void +loader_dri3_blit_context_put(void); + static int64_t dri3_shm_bridge_now_ns(void) { @@ -84,29 +148,84 @@ dri3_shm_bridge_now_ns(void) } static void -dri3_shm_bridge_fini(struct loader_dri3_drawable *draw) +dri3_shm_bridge_wake(struct dri3_shm_bridge_state *state) +{ + uint64_t value = 1; + + if (state && state->wake_fd >= 0 && + write(state->wake_fd, &value, sizeof(value)) < 0 && errno != EAGAIN) + mesa_logw("DRI3: KGSL bridge wake failed: %s", strerror(errno)); +} + +/* Called with state->mtx held. */ +static void +dri3_shm_bridge_report_stats(struct loader_dri3_drawable *draw, + struct dri3_shm_bridge_state *state) { - if (!draw->shm_bridge_conn) + if (!draw->shm_bridge_stats || state->produced_frames % 300 != 0) return; - if (draw->shm_bridge_present_event) { + double seconds = + (dri3_shm_bridge_now_ns() - state->stats_started_ns) / 1e9; + mesa_logi("DRI3: KGSL bridge produced=%" PRIu64 + " presented=%" PRIu64 " dropped=%" PRIu64 + " server_skipped=%" PRIu64 + " copied=%.1f MiB rate=%.1f fps waits=%" PRIu64, + state->produced_frames, state->presented_frames, + state->dropped_frames, state->server_skipped_frames, + state->bytes / (1024.0 * 1024.0), + seconds > 0.0 ? state->produced_frames / seconds : 0.0, + state->waits); +} + +static void +dri3_shm_bridge_fini(struct loader_dri3_drawable *draw) +{ + struct dri3_shm_bridge_state *state = dri3_shm_bridge_state(draw); + + if (state && state->thread_started) { + p_atomic_set(&state->stop, true); + dri3_shm_bridge_wake(state); + mtx_lock(&state->mtx); + cnd_broadcast(&state->cnd); + mtx_unlock(&state->mtx); + thrd_join(state->thread, NULL); + state->thread_started = false; + } + + if (state && state->present_event && draw->shm_bridge_conn) { xcb_present_select_input(draw->shm_bridge_conn, draw->shm_bridge_present_eid, draw->drawable, XCB_PRESENT_EVENT_MASK_NO_EVENT); xcb_unregister_for_special_event(draw->shm_bridge_conn, - draw->shm_bridge_present_event); + state->present_event); } for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { struct loader_dri3_shm_bridge_slot *slot = &draw->shm_bridge_slots[i]; + if (state && state->pixmaps[i] && draw->shm_bridge_conn) + xcb_free_pixmap(draw->shm_bridge_conn, state->pixmaps[i]); if (slot->map) { - xcb_shm_detach(draw->shm_bridge_conn, slot->seg); + if (draw->shm_bridge_conn) + xcb_shm_detach(draw->shm_bridge_conn, slot->seg); munmap(slot->map, slot->size); } memset(slot, 0, sizeof(*slot)); } - if (draw->shm_bridge_gc) + if (draw->shm_bridge_gc && draw->shm_bridge_conn) xcb_free_gc(draw->shm_bridge_conn, draw->shm_bridge_gc); - xcb_disconnect(draw->shm_bridge_conn); + if (draw->shm_bridge_conn) + xcb_disconnect(draw->shm_bridge_conn); + if (state) { + if (state->capture_fence_fd >= 0) + close(state->capture_fence_fd); + if (state->wake_fd >= 0) + close(state->wake_fd); + if (state->cnd_initialized) + cnd_destroy(&state->cnd); + if (state->mtx_initialized) + mtx_destroy(&state->mtx); + free(state); + } draw->shm_bridge_conn = NULL; draw->shm_bridge_gc = 0; draw->shm_bridge_present_event = NULL; @@ -118,9 +237,11 @@ dri3_shm_bridge_init(struct loader_dri3_drawable *draw, { const uint32_t stride = (buffer->width * buffer->cpp + 3u) & ~3u; const size_t size = (size_t) stride * buffer->height; + struct dri3_shm_bridge_state *state = dri3_shm_bridge_state(draw); xcb_generic_error_t *error = NULL; - if (draw->shm_bridge_conn && draw->shm_bridge_slots[0].size == size && + if (draw->shm_bridge_conn && state && !p_atomic_read(&state->stop) && + draw->shm_bridge_slots[0].size == size && draw->shm_bridge_slots[0].stride == stride) return true; dri3_shm_bridge_fini(draw); @@ -131,13 +252,44 @@ dri3_shm_bridge_init(struct loader_dri3_drawable *draw, dri3_shm_bridge_fini(draw); return false; } - draw->shm_bridge_gc = xcb_generate_id(draw->shm_bridge_conn); - error = xcb_request_check(draw->shm_bridge_conn, - xcb_create_gc_checked(draw->shm_bridge_conn, draw->shm_bridge_gc, - draw->drawable, 0, NULL)); - if (error) { - mesa_loge("DRI3: KGSL MIT-SHM bridge GC failed: X error %u", error->error_code); + + xcb_shm_query_version_reply_t *shm = xcb_shm_query_version_reply( + draw->shm_bridge_conn, + xcb_shm_query_version(draw->shm_bridge_conn), &error); + if (!shm || error || !shm->shared_pixmaps) { + mesa_loge("DRI3: KGSL bridge requires MIT-SHM shared pixmaps%s", + error ? " (query failed)" : ""); free(error); + free(shm); + dri3_shm_bridge_fini(draw); + return false; + } + free(shm); + + state = calloc(1, sizeof(*state)); + if (!state) { + dri3_shm_bridge_fini(draw); + return false; + } + state->wake_fd = -1; + state->capture_fence_fd = -1; + state->capture_slot = -1; + state->ready_slot = -1; + draw->shm_bridge_present_event = (void *) state; + if (mtx_init(&state->mtx, mtx_plain) != thrd_success) { + dri3_shm_bridge_fini(draw); + return false; + } + state->mtx_initialized = true; + if (cnd_init(&state->cnd) != thrd_success) { + dri3_shm_bridge_fini(draw); + return false; + } + state->cnd_initialized = true; + state->wake_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (state->wake_fd < 0) { + mesa_loge("DRI3: KGSL bridge cannot create worker eventfd: %s", + strerror(errno)); dri3_shm_bridge_fini(draw); return false; } @@ -156,6 +308,8 @@ dri3_shm_bridge_init(struct loader_dri3_drawable *draw, dri3_shm_bridge_fini(draw); return false; } + slot->size = size; + slot->stride = stride; slot->seg = xcb_generate_id(draw->shm_bridge_conn); error = xcb_request_check(draw->shm_bridge_conn, xcb_shm_attach_fd_checked(draw->shm_bridge_conn, slot->seg, fd, false)); @@ -164,27 +318,61 @@ dri3_shm_bridge_init(struct loader_dri3_drawable *draw, mesa_loge("DRI3: KGSL MIT-SHM bridge attach failed: X error %u", error->error_code); free(error); - slot->size = size; dri3_shm_bridge_fini(draw); return false; } - slot->size = size; - slot->stride = stride; + state->pixmaps[i] = xcb_generate_id(draw->shm_bridge_conn); + error = xcb_request_check(draw->shm_bridge_conn, + xcb_shm_create_pixmap_checked(draw->shm_bridge_conn, + state->pixmaps[i], draw->drawable, + buffer->width, buffer->height, + draw->depth, slot->seg, 0)); + if (error) { + mesa_loge("DRI3: KGSL MIT-SHM pixmap creation failed: X error %u", + error->error_code); + free(error); + dri3_shm_bridge_fini(draw); + return false; + } } draw->shm_bridge_present_eid = xcb_generate_id(draw->shm_bridge_conn); - uint32_t stamp = 0; - draw->shm_bridge_present_event = xcb_register_for_special_xge( - draw->shm_bridge_conn, &xcb_present_id, draw->shm_bridge_present_eid, &stamp); - if (draw->shm_bridge_present_event) { - xcb_present_select_input(draw->shm_bridge_conn, - draw->shm_bridge_present_eid, draw->drawable, - XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY); + error = xcb_request_check(draw->shm_bridge_conn, + xcb_present_select_input_checked( + draw->shm_bridge_conn, draw->shm_bridge_present_eid, draw->drawable, + XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY | + XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY)); + if (error) { + mesa_loge("DRI3: KGSL bridge PresentSelectInput failed: X error %u", + error->error_code); + free(error); + dri3_shm_bridge_fini(draw); + return false; + } + state->present_event = xcb_register_for_special_xge( + draw->shm_bridge_conn, &xcb_present_id, draw->shm_bridge_present_eid, + &state->present_stamp); + if (!state->present_event) { + mesa_loge("DRI3: KGSL bridge cannot register its Present event queue"); + dri3_shm_bridge_fini(draw); + return false; } + + p_atomic_set(&state->stop, false); draw->shm_bridge_next_slot = 0; draw->shm_bridge_msc = 0; - draw->shm_bridge_stats_started_ns = dri3_shm_bridge_now_ns(); + state->stats_started_ns = dri3_shm_bridge_now_ns(); + if (draw->shm_bridge_stats) + mesa_logi("DRI3: %s", dri3_shm_bridge_abi); xcb_flush(draw->shm_bridge_conn); + + if (thrd_create(&state->thread, + dri3_shm_bridge_present_thread, draw) != thrd_success) { + mesa_loge("DRI3: KGSL MIT-SHM bridge cannot start presentation thread"); + dri3_shm_bridge_fini(draw); + return false; + } + state->thread_started = true; return true; } @@ -192,16 +380,8 @@ static void dri3_shm_bridge_handle_event(struct loader_dri3_drawable *draw, xcb_generic_event_t *event) { - const xcb_query_extension_reply_t *extension = - xcb_get_extension_data(draw->shm_bridge_conn, &xcb_shm_id); uint8_t type = event->response_type & 0x7f; - if (extension && type == extension->first_event + XCB_SHM_COMPLETION) { - xcb_shm_completion_event_t *completion = (void *) event; - for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { - if (draw->shm_bridge_slots[i].seg == completion->shmseg) - draw->shm_bridge_slots[i].busy = false; - } - } else if (type == 0) { + if (type == 0) { xcb_generic_error_t *error = (void *) event; mesa_loge("DRI3: KGSL MIT-SHM bridge asynchronous X error %u", error->error_code); @@ -209,131 +389,365 @@ dri3_shm_bridge_handle_event(struct loader_dri3_drawable *draw, free(event); } -static struct loader_dri3_shm_bridge_slot * -dri3_shm_bridge_acquire_slot(struct loader_dri3_drawable *draw) +/* Called with state->mtx held. PresentOptionCopy makes completion and idle + * close together, but the protocol exposes them independently; require both + * before handing a shared pixmap back to the render thread. */ +static void +dri3_shm_bridge_maybe_release_slot(struct loader_dri3_drawable *draw, + struct dri3_shm_bridge_state *state, + unsigned index) +{ + if (state->slot_completed[index] && state->slot_idle[index]) + draw->shm_bridge_slots[index].busy = false; +} + +static void +dri3_shm_bridge_handle_present_event(struct loader_dri3_drawable *draw, + xcb_present_generic_event_t *generic) +{ + struct dri3_shm_bridge_state *state = dri3_shm_bridge_state(draw); + + if (generic->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY) { + xcb_present_complete_notify_event_t *complete = (void *) generic; + if (complete->kind == XCB_PRESENT_COMPLETE_KIND_PIXMAP) { + mtx_lock(&state->mtx); + draw->shm_bridge_msc = complete->msc; + if (!state->present_primed) { + state->present_primed = true; + state->next_target_msc = complete->msc + 1; + } else if (state->next_target_msc <= complete->msc) { + state->next_target_msc = complete->msc + 1; + } + + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + if (state->slot_serials[i] != complete->serial) + continue; + state->slot_completed[i] = true; + if (complete->mode == XCB_PRESENT_COMPLETE_MODE_SKIP) + state->server_skipped_frames++; + else + state->presented_frames++; + state->presented_sequence = + MAX2(state->presented_sequence, state->slot_sequences[i]); + dri3_shm_bridge_maybe_release_slot(draw, state, i); + break; + } + cnd_broadcast(&state->cnd); + mtx_unlock(&state->mtx); + } + } else if (generic->evtype == XCB_PRESENT_EVENT_IDLE_NOTIFY) { + xcb_present_idle_notify_event_t *idle = (void *) generic; + mtx_lock(&state->mtx); + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + if (state->pixmaps[i] != idle->pixmap || + state->slot_serials[i] != idle->serial) + continue; + state->slot_idle[i] = true; + dri3_shm_bridge_maybe_release_slot(draw, state, i); + break; + } + cnd_broadcast(&state->cnd); + mtx_unlock(&state->mtx); + } + free(generic); +} + +static bool +dri3_shm_bridge_drain_events(struct loader_dri3_drawable *draw) { + struct dri3_shm_bridge_state *state = dri3_shm_bridge_state(draw); xcb_generic_event_t *event; + + while ((event = xcb_poll_for_special_event(draw->shm_bridge_conn, + state->present_event))) + dri3_shm_bridge_handle_present_event(draw, (void *) event); while ((event = xcb_poll_for_event(draw->shm_bridge_conn))) dri3_shm_bridge_handle_event(draw, event); + return !xcb_connection_has_error(draw->shm_bridge_conn); +} + +static bool +dri3_shm_bridge_wait_io(struct loader_dri3_drawable *draw) +{ + struct dri3_shm_bridge_state *state = dri3_shm_bridge_state(draw); + struct pollfd fds[2] = { + { .fd = xcb_get_file_descriptor(draw->shm_bridge_conn), + .events = POLLIN }, + { .fd = state->wake_fd, .events = POLLIN }, + }; + int ret; + + do { + ret = poll(fds, ARRAY_SIZE(fds), 100); + } while (ret < 0 && errno == EINTR); + if (ret < 0) + return false; + + if (fds[1].revents & POLLIN) { + uint64_t value; + while (read(state->wake_fd, &value, sizeof(value)) == sizeof(value)) {} + } + return dri3_shm_bridge_drain_events(draw); +} + +static int +dri3_shm_bridge_acquire_slot(struct loader_dri3_drawable *draw) +{ + struct dri3_shm_bridge_state *state = dri3_shm_bridge_state(draw); + bool counted_wait = false; for (;;) { - for (unsigned offset = 0; offset < LOADER_DRI3_SHM_BRIDGE_SLOTS; offset++) { - unsigned index = (draw->shm_bridge_next_slot + offset) % - LOADER_DRI3_SHM_BRIDGE_SLOTS; - if (!draw->shm_bridge_slots[index].busy) { - draw->shm_bridge_next_slot = (index + 1) % LOADER_DRI3_SHM_BRIDGE_SLOTS; - return &draw->shm_bridge_slots[index]; + if (!dri3_shm_bridge_drain_events(draw)) + return -1; + + mtx_lock(&state->mtx); + bool stopping = p_atomic_read(&state->stop); + bool waiting_for_prime = + !state->present_primed && state->submitted_frames > 0; + if (!stopping && !waiting_for_prime) { + for (unsigned offset = 0; offset < LOADER_DRI3_SHM_BRIDGE_SLOTS; + offset++) { + unsigned index = (draw->shm_bridge_next_slot + offset) % + LOADER_DRI3_SHM_BRIDGE_SLOTS; + if (!draw->shm_bridge_slots[index].busy) { + draw->shm_bridge_slots[index].busy = true; + state->slot_completed[index] = false; + state->slot_idle[index] = false; + draw->shm_bridge_next_slot = + (index + 1) % LOADER_DRI3_SHM_BRIDGE_SLOTS; + mtx_unlock(&state->mtx); + return index; + } } } - draw->shm_bridge_waits++; - event = xcb_wait_for_event(draw->shm_bridge_conn); - if (!event) - return NULL; - dri3_shm_bridge_handle_event(draw, event); + if (!stopping && !counted_wait) { + state->waits++; + counted_wait = true; + } + mtx_unlock(&state->mtx); + if (stopping || !dri3_shm_bridge_wait_io(draw)) + return -1; } } -static bool -dri3_shm_bridge_wait_vblank(struct loader_dri3_drawable *draw) +static int +dri3_shm_bridge_present_thread(void *data) { - if (draw->swap_interval == 0 || !draw->shm_bridge_present_event) - return true; - uint32_t serial = ++draw->shm_bridge_present_serial; - uint64_t target = draw->shm_bridge_msc ? - draw->shm_bridge_msc + abs(draw->swap_interval) : 0; - xcb_present_notify_msc(draw->shm_bridge_conn, draw->drawable, serial, - target, 0, 0); - xcb_flush(draw->shm_bridge_conn); + struct loader_dri3_drawable *draw = data; + struct dri3_shm_bridge_state *state = dri3_shm_bridge_state(draw); + for (;;) { - xcb_generic_event_t *event = xcb_wait_for_special_event( - draw->shm_bridge_conn, draw->shm_bridge_present_event); - if (!event) - return false; - xcb_present_generic_event_t *generic = (void *) event; - if (generic->evtype == XCB_PRESENT_EVENT_COMPLETE_NOTIFY) { - xcb_present_complete_notify_event_t *complete = (void *) event; - if (complete->kind == XCB_PRESENT_COMPLETE_KIND_NOTIFY_MSC && - complete->serial == serial) { - draw->shm_bridge_msc = complete->msc; - free(event); - return true; + int slot_index = dri3_shm_bridge_acquire_slot(draw); + if (slot_index < 0) + break; + + mtx_lock(&state->mtx); + state->capture_slot = slot_index; + state->capture_available = true; + cnd_broadcast(&state->cnd); + mtx_unlock(&state->mtx); + + /* The render thread wakes us through eventfd after queueing a selected + * KGSL buffer and its native render fence. Continue draining Present + * events while waiting so synchronized producers cannot deadlock behind + * completions. */ + for (;;) { + mtx_lock(&state->mtx); + bool stopping = p_atomic_read(&state->stop); + bool ready = state->ready && state->ready_slot == slot_index; + mtx_unlock(&state->mtx); + if (stopping || ready) + break; + if (!dri3_shm_bridge_wait_io(draw)) { + p_atomic_set(&state->stop, true); + break; } } - free(event); - } + + mtx_lock(&state->mtx); + if (p_atomic_read(&state->stop)) { + struct loader_dri3_buffer *buffer = state->capture_buffer; + int fence_fd = state->capture_fence_fd; + draw->shm_bridge_slots[slot_index].busy = false; + state->capture_available = false; + state->capture_in_progress = false; + state->capture_slot = -1; + state->capture_buffer = NULL; + state->capture_fence_fd = -1; + mtx_unlock(&state->mtx); + if (fence_fd >= 0) + close(fence_fd); + if (buffer) { + mtx_lock(&draw->mtx); + buffer->busy = false; + mtx_unlock(&draw->mtx); + } + break; + } + + struct loader_dri3_buffer *buffer = state->capture_buffer; + int fence_fd = state->capture_fence_fd; + int interval = state->ready_swap_interval; + state->ready = false; + state->ready_slot = -1; + state->capture_fence_fd = -1; + mtx_unlock(&state->mtx); + + struct dri_context *ctx = loader_dri3_blit_context_get(draw); + void *transfer = NULL; + int source_stride = 0; + uint8_t *source = NULL; + bool fence_ready = fence_fd < 0 || sync_wait(fence_fd, -1) == 0; + if (fence_fd >= 0) + close(fence_fd); + if (ctx && buffer && fence_ready) { + source = dri2_map_image(ctx, buffer->image, 0, 0, + buffer->width, buffer->height, + __DRI_IMAGE_TRANSFER_READ, + &source_stride, &transfer); + } + if (source) { + struct loader_dri3_shm_bridge_slot *slot = + &draw->shm_bridge_slots[slot_index]; + for (int y = 0; y < buffer->height; y++) { + memcpy((uint8_t *) slot->map + (size_t) y * slot->stride, + source + (size_t) y * source_stride, + (size_t) buffer->width * buffer->cpp); + } + dri2_unmap_image(ctx, buffer->image, transfer); + } + if (ctx) + loader_dri3_blit_context_put(); + + if (buffer) { + mtx_lock(&draw->mtx); + buffer->busy = false; + mtx_unlock(&draw->mtx); + } + + mtx_lock(&state->mtx); + state->capture_in_progress = false; + state->capture_buffer = NULL; + cnd_broadcast(&state->cnd); + if (!source || p_atomic_read(&state->stop)) { + draw->shm_bridge_slots[slot_index].busy = false; + mtx_unlock(&state->mtx); + if (!fence_ready) + mesa_loge("DRI3: KGSL bridge worker could not wait for its render fence"); + else if (!source) + mesa_loge("DRI3: KGSL bridge worker could not map its selected frame"); + p_atomic_set(&state->stop, true); + break; + } + + uint32_t serial = ++draw->shm_bridge_present_serial; + uint64_t target_msc = 0; + if (state->present_primed) { + target_msc = state->next_target_msc; + state->next_target_msc += MAX2(abs(interval), 1); + } + state->slot_serials[slot_index] = serial; + state->slot_completed[slot_index] = false; + state->slot_idle[slot_index] = false; + state->submitted_frames++; + state->bytes += + (uint64_t) buffer->width * buffer->height * buffer->cpp; + mtx_unlock(&state->mtx); + + xcb_present_pixmap(draw->shm_bridge_conn, draw->drawable, + state->pixmaps[slot_index], serial, + 0, 0, 0, 0, XCB_NONE, XCB_NONE, XCB_NONE, + XCB_PRESENT_OPTION_COPY, target_msc, + 0, 0, 0, NULL); + if (xcb_flush(draw->shm_bridge_conn) <= 0) + break; + } + + p_atomic_set(&state->stop, true); + mtx_lock(&state->mtx); + state->capture_available = false; + state->capture_slot = -1; + cnd_broadcast(&state->cnd); + mtx_unlock(&state->mtx); + dri3_shm_bridge_wake(state); + return -1; } static bool dri3_shm_bridge_present(struct loader_dri3_drawable *draw, struct loader_dri3_buffer *buffer, - const int *rects, int n_rects) + const int *rects, int n_rects, + int render_fence_fd) { - struct dri_context *ctx = draw->vtable->get_dri_context(draw); - struct loader_dri3_shm_bridge_slot *slot; - void *transfer = NULL; - int source_stride = 0; - uint8_t *source; - int x0 = 0, y0 = 0, x1 = buffer->width, y1 = buffer->height; + struct dri3_shm_bridge_state *state; + int slot_index; + int interval = draw->swap_interval; + uint64_t sequence; + bool ok; - if (!ctx || !dri3_shm_bridge_init(draw, buffer) || - !dri3_shm_bridge_wait_vblank(draw)) + if (!draw->vtable->get_dri_context(draw) || buffer->cpp != 4 || + !dri3_shm_bridge_init(draw, buffer)) { + if (render_fence_fd >= 0) + close(render_fence_fd); return false; - slot = dri3_shm_bridge_acquire_slot(draw); - if (!slot) + } + state = dri3_shm_bridge_state(draw); + (void) rects; + (void) n_rects; + + mtx_lock(&state->mtx); + state->produced_frames++; + bool first_frame = state->produced_frames == 1; + while ((interval != 0 || first_frame) && + (!state->capture_available || state->capture_in_progress) && + !p_atomic_read(&state->stop)) + cnd_wait(&state->cnd, &state->mtx); + if (p_atomic_read(&state->stop)) { + mtx_unlock(&state->mtx); + if (render_fence_fd >= 0) + close(render_fence_fd); return false; + } - /* EGL damage is bottom-left based. Coalesce it into one conservative X11 - * rectangle; GLX supplies no damage and therefore keeps a full copy. */ - if (rects && n_rects > 0) { - x0 = buffer->width; - y0 = buffer->height; - x1 = y1 = 0; - for (int i = 0; i < n_rects; i++) { - int rx0 = CLAMP(rects[i * 4], 0, (int) buffer->width); - int rw = CLAMP(rects[i * 4 + 2], 0, (int) buffer->width - rx0); - int rh = CLAMP(rects[i * 4 + 3], 0, (int) buffer->height); - int ry0 = CLAMP((int) buffer->height - rects[i * 4 + 1] - rh, - 0, (int) buffer->height); - x0 = MIN2(x0, rx0); - y0 = MIN2(y0, ry0); - x1 = MAX2(x1, rx0 + rw); - y1 = MAX2(y1, ry0 + rh); - } - if (x1 <= x0 || y1 <= y0) - return true; + /* Present completion opens exactly one capture slot. An uncapped client + * skips all other swaps before mapping the KGSL image, so rendering remains + * uncapped without flooding Xorg or doing invisible readbacks. */ + if (!state->capture_available || state->capture_in_progress) { + state->dropped_frames++; + dri3_shm_bridge_report_stats(draw, state); + mtx_unlock(&state->mtx); + if (render_fence_fd >= 0) + close(render_fence_fd); + return true; } - source = dri2_map_image(ctx, buffer->image, 0, 0, - buffer->width, buffer->height, - __DRI_IMAGE_TRANSFER_READ, - &source_stride, &transfer); - if (!source) - return false; - for (int y = y0; y < y1; y++) { - memcpy((uint8_t *) slot->map + (size_t) y * slot->stride + - (size_t) x0 * buffer->cpp, - source + (size_t) y * source_stride + (size_t) x0 * buffer->cpp, - (size_t) (x1 - x0) * buffer->cpp); - } - dri2_unmap_image(ctx, buffer->image, transfer); - - xcb_shm_put_image(draw->shm_bridge_conn, draw->drawable, - draw->shm_bridge_gc, buffer->width, buffer->height, - x0, y0, x1 - x0, y1 - y0, x0, y0, draw->depth, - XCB_IMAGE_FORMAT_Z_PIXMAP, true, slot->seg, 0); - slot->busy = true; - xcb_flush(draw->shm_bridge_conn); + slot_index = state->capture_slot; + state->capture_available = false; + state->capture_slot = -1; + state->capture_in_progress = true; + sequence = ++state->pending_sequence; + state->slot_sequences[slot_index] = sequence; + state->capture_buffer = buffer; + state->capture_fence_fd = render_fence_fd; + state->ready_slot = slot_index; + state->ready_swap_interval = interval; + state->ready = true; + mtx_lock(&draw->mtx); + buffer->busy = true; + mtx_unlock(&draw->mtx); + dri3_shm_bridge_report_stats(draw, state); + cnd_broadcast(&state->cnd); + mtx_unlock(&state->mtx); + dri3_shm_bridge_wake(state); - draw->shm_bridge_frames++; - draw->shm_bridge_bytes += (uint64_t) (x1 - x0) * (y1 - y0) * buffer->cpp; - if (draw->shm_bridge_stats && draw->shm_bridge_frames % 300 == 0) { - double seconds = (dri3_shm_bridge_now_ns() - draw->shm_bridge_stats_started_ns) / 1e9; - mesa_logi("DRI3: KGSL bridge frames=%" PRIu64 " copied=%.1f MiB rate=%.1f fps waits=%" PRIu64, - draw->shm_bridge_frames, draw->shm_bridge_bytes / (1024.0 * 1024.0), - seconds > 0.0 ? draw->shm_bridge_frames / seconds : 0.0, - draw->shm_bridge_waits); - } - return true; + mtx_lock(&state->mtx); + while (interval != 0 && + state->presented_sequence < sequence && + !p_atomic_read(&state->stop)) + cnd_wait(&state->cnd, &state->mtx); + ok = !p_atomic_read(&state->stop); + mtx_unlock(&state->mtx); + return ok; } struct loader_dri3_present_sync { @@ -749,6 +1163,20 @@ dri3_fence_await(xcb_connection_t *c, struct loader_dri3_drawable *draw, static void dri3_update_max_num_back(struct loader_dri3_drawable *draw) { + /* + * Normal DRI3 Present keeps submitted pixmaps busy until IdleNotify, which + * naturally rotates an interval-zero producer through four render buffers. + * The SHM bridge presents a separate server-owned pixmap, so its KGSL back + * buffers are never owned by Xorg. Retain the same amount of producer + * buffering explicitly; otherwise every swap renders into the same BO and + * serializes behind the preceding KGSL work. + */ + if (draw->shm_bridge) { + draw->max_num_back = draw->swap_interval == 0 ? 4 : 3; + assert(draw->max_num_back <= LOADER_DRI3_MAX_BACK); + return; + } + switch (draw->last_present_mode) { case XCB_PRESENT_COMPLETE_MODE_FLIP: { if (draw->swap_interval == 0) @@ -835,14 +1263,13 @@ loader_dri3_drawable_fini(struct loader_dri3_drawable *draw) { int i; + dri3_shm_bridge_fini(draw); dri3_present_sync_fini(draw); driDestroyDrawable(draw->dri_drawable); for (i = 0; i < ARRAY_SIZE(draw->buffers); i++) dri3_free_render_buffer(draw, i); - dri3_shm_bridge_fini(draw); - if (draw->special_event) { xcb_void_cookie_t cookie = xcb_present_select_input_checked(draw->conn, draw->eid, draw->drawable, @@ -1546,9 +1973,8 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, if (!draw->have_back || draw->type == LOADER_DRI3_DRAWABLE_PIXMAP) return ret; - if (!draw->shm_bridge && - draw->type == LOADER_DRI3_DRAWABLE_WINDOW && - draw->present_sync && + if (draw->type == LOADER_DRI3_DRAWABLE_WINDOW && + (draw->shm_bridge || draw->present_sync) && draw->vtable->flush_drawable_with_fence_fd) { render_fence_fd = draw->vtable->flush_drawable_with_fence_fd(draw, flush_flags); @@ -1564,17 +1990,29 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, return ret; } - if (draw->shm_bridge && - draw->type == LOADER_DRI3_DRAWABLE_WINDOW && - dri3_shm_bridge_present(draw, back, rects, n_rects)) { - if (render_fence_fd >= 0) - close(render_fence_fd); + bool bridge_presented = false; + if (draw->shm_bridge && draw->type == LOADER_DRI3_DRAWABLE_WINDOW) { + bridge_presented = + dri3_shm_bridge_present(draw, back, rects, n_rects, + render_fence_fd); + /* The bridge consumes the fence whether it presents or drops this + * producer frame. Xorg never sees the native fence: the worker waits + * for it before reading the selected KGSL buffer. */ + render_fence_fd = -1; + + } + if (bridge_presented) { mtx_lock(&draw->mtx); draw->send_sbc++; draw->recv_sbc = draw->send_sbc; back->last_swap = draw->send_sbc; if (draw->stamp) ++(*draw->stamp); + /* Xorg never owns this KGSL pixmap: the bridge copied the selected + * frame into an independent MIT-SHM pixmap before returning. Advance + * the producer ring just as normal asynchronous DRI3 Present would do, + * without inventing a time-based release policy. */ + draw->cur_back = (draw->cur_back + 1) % draw->max_num_back; mtx_unlock(&draw->mtx); dri_invalidate_drawable(draw->dri_drawable); return (int64_t) draw->send_sbc; diff --git a/src/gallium/frontends/dri/loader_dri3_helper.h b/src/gallium/frontends/dri/loader_dri3_helper.h index e5ccc8d52fa2..2405d4c05d93 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.h +++ b/src/gallium/frontends/dri/loader_dri3_helper.h @@ -103,6 +103,7 @@ struct loader_dri3_drawable; struct loader_dri3_present_sync; #define LOADER_DRI3_SHM_BRIDGE_SLOTS 3 +#define LOADER_DRI3_SHM_BRIDGE_ABI "HDMI_LOS_MESA_BRIDGE_ABI=3" struct loader_dri3_shm_bridge_slot { xcb_shm_seg_t seg; diff --git a/src/glx/dri3_glx.c b/src/glx/dri3_glx.c index 0a2ef15d36ea..5386cc888a23 100644 --- a/src/glx/dri3_glx.c +++ b/src/glx/dri3_glx.c @@ -78,6 +78,10 @@ #include "x11_dri3.h" #include "loader_dri_helper.h" #include "util/u_debug.h" + +/* Keep the private GLX frontend tied to the DRI bridge structure ABI. */ +const char hdmi_los_mesa_glx_bridge_abi[] + __attribute__((used, retain)) = LOADER_DRI3_SHM_BRIDGE_ABI; #include "dri_util.h" static struct dri3_drawable * From 3ce48e027e1a84c3b1ad527dda35fbc5c11d87ae Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 02:40:40 +0300 Subject: [PATCH 04/14] dri3: wait on KGSL bridge events without polling --- src/gallium/frontends/dri/loader_dri3_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index 3f003a6332d5..90e841e37509 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -478,7 +478,7 @@ dri3_shm_bridge_wait_io(struct loader_dri3_drawable *draw) int ret; do { - ret = poll(fds, ARRAY_SIZE(fds), 100); + ret = poll(fds, ARRAY_SIZE(fds), -1); } while (ret < 0 && errno == EINTR); if (ret < 0) return false; From 6c30ef8c98091bcdd044fd9798ca83f4ec511ef4 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 03:58:10 +0300 Subject: [PATCH 05/14] freedreno/kgsl: scope translated handles to KMS scanout --- src/freedreno/drm/kgsl/kgsl_bo.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/freedreno/drm/kgsl/kgsl_bo.c b/src/freedreno/drm/kgsl/kgsl_bo.c index 97739950d531..e73a09d39d5f 100644 --- a/src/freedreno/drm/kgsl/kgsl_bo.c +++ b/src/freedreno/drm/kgsl/kgsl_bo.c @@ -132,6 +132,17 @@ kgsl_bo_kms_handle(struct fd_bo *bo) struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); int dma_buf; + /* + * fd_bo_handle() predates the separate KMS control fd and normally + * returns KGSL's allocation id. Translating it into a GEM handle is + * needed only by the opt-in Xorg scanout allocator. Keeping this gated + * also preserves the established handle contract for ordinary KGSL BOs, + * which cannot be exported by kgsl_bo_dmabuf() and would otherwise turn + * into handle 0 merely because this callback is installed. + */ + if (!debug_get_bool_option("FD_KGSL_USE_KMS_DUMB", false)) + return bo->handle; + if (kgsl_bo->kms_handle) return kgsl_bo->kms_handle; From 1200a2450b079cd2804c176018b65d66582bf079 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 05:24:12 +0300 Subject: [PATCH 06/14] dri3: add adaptive GPU bridge for UHD X11 --- src/gallium/frontends/dri/dri2.c | 2 +- .../frontends/dri/loader_dri3_helper.c | 165 +++++++++++++----- 2 files changed, 125 insertions(+), 42 deletions(-) diff --git a/src/gallium/frontends/dri/dri2.c b/src/gallium/frontends/dri/dri2.c index 292757a2ab6f..5f50620cd4dc 100644 --- a/src/gallium/frontends/dri/dri2.c +++ b/src/gallium/frontends/dri/dri2.c @@ -1548,7 +1548,7 @@ dri2_blit_image(struct dri_context *ctx, struct dri_image *dst, struct dri_image { struct pipe_context *pipe = ctx->st->pipe; struct pipe_screen *screen; - struct pipe_fence_handle *fence; + struct pipe_fence_handle *fence = NULL; struct pipe_blit_info blit; if (!dst || !src) diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index 90e841e37509..ed9d5bc75e6a 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -90,6 +90,7 @@ struct dri3_shm_bridge_state { xcb_special_event_t *present_event; uint32_t present_stamp; xcb_pixmap_t pixmaps[LOADER_DRI3_SHM_BRIDGE_SLOTS]; + struct dri_image *slot_images[LOADER_DRI3_SHM_BRIDGE_SLOTS]; uint32_t slot_serials[LOADER_DRI3_SHM_BRIDGE_SLOTS]; uint64_t slot_sequences[LOADER_DRI3_SHM_BRIDGE_SLOTS]; bool slot_completed[LOADER_DRI3_SHM_BRIDGE_SLOTS]; @@ -105,6 +106,7 @@ struct dri3_shm_bridge_state { bool capture_in_progress; bool ready; bool present_primed; + bool gpu_blit; int stop; int capture_slot; int ready_slot; @@ -209,6 +211,8 @@ dri3_shm_bridge_fini(struct loader_dri3_drawable *draw) xcb_shm_detach(draw->shm_bridge_conn, slot->seg); munmap(slot->map, slot->size); } + if (state && state->slot_images[i]) + dri2_destroy_image(state->slot_images[i]); memset(slot, 0, sizeof(*slot)); } if (draw->shm_bridge_gc && draw->shm_bridge_conn) @@ -275,6 +279,13 @@ dri3_shm_bridge_init(struct loader_dri3_drawable *draw, state->capture_fence_fd = -1; state->capture_slot = -1; state->ready_slot = -1; + int64_t gpu_bridge_min_pixels = debug_get_num_option( + "MESA_KGSL_X11_GPU_BRIDGE_MIN_PIXELS", 8000000); + uint64_t drawable_pixels = (uint64_t) buffer->width * buffer->height; + state->gpu_blit = + debug_get_bool_option("MESA_KGSL_X11_GPU_BRIDGE", false) && + (gpu_bridge_min_pixels <= 0 || + drawable_pixels >= (uint64_t) gpu_bridge_min_pixels); draw->shm_bridge_present_event = (void *) state; if (mtx_init(&state->mtx, mtx_plain) != thrd_success) { dri3_shm_bridge_fini(draw); @@ -294,46 +305,110 @@ dri3_shm_bridge_init(struct loader_dri3_drawable *draw, return false; } - for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { - struct loader_dri3_shm_bridge_slot *slot = &draw->shm_bridge_slots[i]; - int fd = os_create_anonymous_file(size, "mesa-kgsl-x11-bridge"); - if (fd < 0) { - dri3_shm_bridge_fini(draw); - return false; - } - slot->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (slot->map == MAP_FAILED) { - slot->map = NULL; - close(fd); - dri3_shm_bridge_fini(draw); - return false; + if (state->gpu_blit) { + int fourcc = DRM_FORMAT_XRGB8888; + + if (!dri2_query_image(buffer->image, __DRI_IMAGE_ATTRIB_FOURCC, + &fourcc)) + fourcc = draw->depth == 32 ? DRM_FORMAT_ARGB8888 : + DRM_FORMAT_XRGB8888; + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + struct loader_dri3_shm_bridge_slot *slot = + &draw->shm_bridge_slots[i]; + int server_width = 0; + int server_height = 0; + + state->pixmaps[i] = xcb_generate_id(draw->shm_bridge_conn); + error = xcb_request_check( + draw->shm_bridge_conn, + xcb_create_pixmap_checked(draw->shm_bridge_conn, draw->depth, + state->pixmaps[i], draw->drawable, + buffer->width, buffer->height)); + if (error) { + mesa_logw("DRI3: KGSL bridge could not create an Xorg-owned " + "GPU slot: X error %u", error->error_code); + free(error); + state->gpu_blit = false; + break; + } + + state->slot_images[i] = loader_dri3_get_pixmap_buffer( + draw->shm_bridge_conn, state->pixmaps[i], + draw->dri_screen_render_gpu, fourcc, + draw->multiplanes_available, &server_width, &server_height, slot); + if (!state->slot_images[i] || server_width != buffer->width || + server_height != buffer->height) { + mesa_logw("DRI3: KGSL bridge could not import an Xorg-owned " + "GPU slot; using CPU readback"); + state->gpu_blit = false; + break; + } + slot->size = size; + slot->stride = stride; } - slot->size = size; - slot->stride = stride; - slot->seg = xcb_generate_id(draw->shm_bridge_conn); - error = xcb_request_check(draw->shm_bridge_conn, - xcb_shm_attach_fd_checked(draw->shm_bridge_conn, slot->seg, fd, false)); - close(fd); - if (error) { - mesa_loge("DRI3: KGSL MIT-SHM bridge attach failed: X error %u", - error->error_code); - free(error); - dri3_shm_bridge_fini(draw); - return false; + } + + if (!state->gpu_blit) { + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + if (state->slot_images[i]) { + dri2_destroy_image(state->slot_images[i]); + state->slot_images[i] = NULL; + } + if (state->pixmaps[i]) { + xcb_free_pixmap(draw->shm_bridge_conn, state->pixmaps[i]); + state->pixmaps[i] = 0; + } + memset(&draw->shm_bridge_slots[i], 0, + sizeof(draw->shm_bridge_slots[i])); } - state->pixmaps[i] = xcb_generate_id(draw->shm_bridge_conn); - error = xcb_request_check(draw->shm_bridge_conn, - xcb_shm_create_pixmap_checked(draw->shm_bridge_conn, - state->pixmaps[i], draw->drawable, - buffer->width, buffer->height, - draw->depth, slot->seg, 0)); - if (error) { - mesa_loge("DRI3: KGSL MIT-SHM pixmap creation failed: X error %u", - error->error_code); - free(error); - dri3_shm_bridge_fini(draw); - return false; + + for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { + struct loader_dri3_shm_bridge_slot *slot = + &draw->shm_bridge_slots[i]; + int fd = os_create_anonymous_file(size, "mesa-kgsl-x11-bridge"); + if (fd < 0) { + dri3_shm_bridge_fini(draw); + return false; + } + slot->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, + fd, 0); + if (slot->map == MAP_FAILED) { + slot->map = NULL; + close(fd); + dri3_shm_bridge_fini(draw); + return false; + } + slot->size = size; + slot->stride = stride; + slot->seg = xcb_generate_id(draw->shm_bridge_conn); + error = xcb_request_check( + draw->shm_bridge_conn, + xcb_shm_attach_fd_checked(draw->shm_bridge_conn, slot->seg, fd, + false)); + close(fd); + if (error) { + mesa_loge("DRI3: KGSL MIT-SHM bridge attach failed: X error %u", + error->error_code); + free(error); + dri3_shm_bridge_fini(draw); + return false; + } + state->pixmaps[i] = xcb_generate_id(draw->shm_bridge_conn); + error = xcb_request_check( + draw->shm_bridge_conn, + xcb_shm_create_pixmap_checked( + draw->shm_bridge_conn, state->pixmaps[i], draw->drawable, + buffer->width, buffer->height, draw->depth, slot->seg, 0)); + if (error) { + mesa_loge("DRI3: KGSL MIT-SHM pixmap creation failed: X error %u", + error->error_code); + free(error); + dri3_shm_bridge_fini(draw); + return false; + } } + } else if (draw->shm_bridge_stats) { + mesa_logi("DRI3: KGSL bridge using GPU blits into Xorg-owned pixmaps"); } draw->shm_bridge_present_eid = xcb_generate_id(draw->shm_bridge_conn); @@ -597,10 +672,17 @@ dri3_shm_bridge_present_thread(void *data) void *transfer = NULL; int source_stride = 0; uint8_t *source = NULL; + bool copied = false; bool fence_ready = fence_fd < 0 || sync_wait(fence_fd, -1) == 0; if (fence_fd >= 0) close(fence_fd); - if (ctx && buffer && fence_ready) { + if (ctx && buffer && fence_ready && state->gpu_blit) { + dri2_blit_image(ctx, state->slot_images[slot_index], buffer->image, + 0, 0, buffer->width, buffer->height, + 0, 0, buffer->width, buffer->height, + __BLIT_FLAG_FINISH); + copied = true; + } else if (ctx && buffer && fence_ready) { source = dri2_map_image(ctx, buffer->image, 0, 0, buffer->width, buffer->height, __DRI_IMAGE_TRANSFER_READ, @@ -615,6 +697,7 @@ dri3_shm_bridge_present_thread(void *data) (size_t) buffer->width * buffer->cpp); } dri2_unmap_image(ctx, buffer->image, transfer); + copied = true; } if (ctx) loader_dri3_blit_context_put(); @@ -629,13 +712,13 @@ dri3_shm_bridge_present_thread(void *data) state->capture_in_progress = false; state->capture_buffer = NULL; cnd_broadcast(&state->cnd); - if (!source || p_atomic_read(&state->stop)) { + if (!copied || p_atomic_read(&state->stop)) { draw->shm_bridge_slots[slot_index].busy = false; mtx_unlock(&state->mtx); if (!fence_ready) mesa_loge("DRI3: KGSL bridge worker could not wait for its render fence"); - else if (!source) - mesa_loge("DRI3: KGSL bridge worker could not map its selected frame"); + else if (!copied) + mesa_loge("DRI3: KGSL bridge worker could not copy its selected frame"); p_atomic_set(&state->stop, true); break; } From f897e810d23c7909c42b0417d1dde85db86a2dd3 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 06:31:00 +0300 Subject: [PATCH 07/14] dri3: use renderonly PRIME for leased KGSL displays --- src/freedreno/drm/freedreno_bo.c | 2 - src/freedreno/drm/freedreno_priv.h | 2 - src/freedreno/drm/kgsl/kgsl_bo.c | 126 +----------------- src/freedreno/drm/kgsl/kgsl_priv.h | 1 - .../drivers/freedreno/freedreno_screen.c | 67 ++++++++++ .../frontends/dri/loader_dri3_helper.c | 36 ++++- .../frontends/dri/loader_dri3_helper.h | 2 +- src/loader/loader.c | 15 +++ src/x11/x11_dri3.c | 5 - 9 files changed, 117 insertions(+), 139 deletions(-) diff --git a/src/freedreno/drm/freedreno_bo.c b/src/freedreno/drm/freedreno_bo.c index 7835c0db80fd..40bcea6cc3a8 100644 --- a/src/freedreno/drm/freedreno_bo.c +++ b/src/freedreno/drm/freedreno_bo.c @@ -544,8 +544,6 @@ fd_bo_handle(struct fd_bo *bo) bo->bo_reuse = NO_CACHE; bo->alloc_flags |= FD_BO_SHARED; bo_flush(bo); - if (bo->funcs->kms_handle) - return bo->funcs->kms_handle(bo); return bo->handle; } diff --git a/src/freedreno/drm/freedreno_priv.h b/src/freedreno/drm/freedreno_priv.h index 75f474b3657a..98037778f30e 100644 --- a/src/freedreno/drm/freedreno_priv.h +++ b/src/freedreno/drm/freedreno_priv.h @@ -424,8 +424,6 @@ struct fd_bo_funcs { uint64_t (*iova)(struct fd_bo *bo); void (*set_name)(struct fd_bo *bo, const char *fmt, va_list ap); int (*dmabuf)(struct fd_bo *bo); - uint32_t (*kms_handle)(struct fd_bo *bo); - /** * Optional hook that is called before ->destroy(). In the case of * batch deletes (such as BO cache cleanup or cleaning up a submit) diff --git a/src/freedreno/drm/kgsl/kgsl_bo.c b/src/freedreno/drm/kgsl/kgsl_bo.c index e73a09d39d5f..c676b8197a5f 100644 --- a/src/freedreno/drm/kgsl/kgsl_bo.c +++ b/src/freedreno/drm/kgsl/kgsl_bo.c @@ -2,7 +2,6 @@ #include "util/os_file.h" #include "util/os_mman.h" -#include "drm-uapi/drm_mode.h" #include static uint64_t @@ -46,16 +45,6 @@ void kgsl_bo_close_handle(struct fd_bo *bo) { struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); - if (kgsl_bo->kms_handle) { - struct drm_gem_close close_req = { - .handle = kgsl_bo->kms_handle, - }; - if (drmIoctl(bo->dev->control_fd, DRM_IOCTL_GEM_CLOSE, &close_req)) { - ERROR_MSG("Failed to close KMS handle %u (%s)", - kgsl_bo->kms_handle, strerror(errno)); - } - kgsl_bo->kms_handle = 0; - } if (kgsl_bo->bo_type == KGSL_BO_IMPORT) { close(kgsl_bo->import_fd); } @@ -126,40 +115,6 @@ static int kgsl_bo_dmabuf(struct fd_bo *bo) { return os_dupfd_cloexec(kgsl_bo->import_fd); } -static uint32_t -kgsl_bo_kms_handle(struct fd_bo *bo) -{ - struct kgsl_bo *kgsl_bo = to_kgsl_bo(bo); - int dma_buf; - - /* - * fd_bo_handle() predates the separate KMS control fd and normally - * returns KGSL's allocation id. Translating it into a GEM handle is - * needed only by the opt-in Xorg scanout allocator. Keeping this gated - * also preserves the established handle contract for ordinary KGSL BOs, - * which cannot be exported by kgsl_bo_dmabuf() and would otherwise turn - * into handle 0 merely because this callback is installed. - */ - if (!debug_get_bool_option("FD_KGSL_USE_KMS_DUMB", false)) - return bo->handle; - - if (kgsl_bo->kms_handle) - return kgsl_bo->kms_handle; - - dma_buf = kgsl_bo_dmabuf(bo); - if (dma_buf < 0) - return 0; - - if (drmPrimeFDToHandle(bo->dev->control_fd, dma_buf, - &kgsl_bo->kms_handle)) { - ERROR_MSG("Failed to import dma-buf into KMS control fd (%s)", - strerror(errno)); - kgsl_bo->kms_handle = 0; - } - close(dma_buf); - return kgsl_bo->kms_handle; -} - static const struct fd_bo_funcs bo_funcs = { .iova = kgsl_bo_iova, .set_name = kgsl_bo_set_name, @@ -169,7 +124,6 @@ static const struct fd_bo_funcs bo_funcs = { .cpu_prep = kgsl_bo_cpu_prep, .destroy = kgsl_bo_destroy, .dmabuf = kgsl_bo_dmabuf, - .kms_handle = kgsl_bo_kms_handle, }; /* Size is not used by KGSL */ @@ -290,80 +244,13 @@ dma_heap_alloc(uint64_t size) } } -/* - * Qualcomm's SDE KMS can reject generic dma-heap allocations in AddFB even - * though KGSL can render to them. When explicitly requested, allocate the - * shared object as a KMS dumb BO instead, export it as a dma-buf, and then - * import that dma-buf into KGSL. The original GEM handle can be destroyed as - * soon as PRIME export succeeds; the dma-buf keeps the object alive and the X - * server will import its own handle before scanout. - * - * Width/height here only size the allocation. The eventual framebuffer's - * actual dimensions, format and pitch are supplied by GBM/DRI3 when the X - * server imports this dma-buf. - */ -static int -kms_dumb_alloc(struct fd_device *dev, uint32_t size) -{ - struct drm_mode_create_dumb create = { - .width = DIV_ROUND_UP(size, 4), - .height = 1, - .bpp = 32, - }; - struct drm_mode_destroy_dumb destroy = {0}; - const char *kms_path; - int kms_fd; - int owned_fd = -1; - int fd = -1; - - if (!debug_get_bool_option("FD_KGSL_USE_KMS_DUMB", false) || - dev->control_fd < 0) - return -1; - - kms_fd = dev->control_fd; - if (drmIoctl(kms_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create)) { - kms_path = debug_get_option("FD_KGSL_KMS_DEVICE", NULL); - if (!kms_path || - (owned_fd = open(kms_path, O_RDWR | O_CLOEXEC)) < 0 || - drmIoctl(owned_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create)) { - ERROR_MSG("Failed to allocate KMS dumb dma-buf (%s); falling back to dma-heap", - strerror(errno)); - if (owned_fd >= 0) - close(owned_fd); - return -1; - } - kms_fd = owned_fd; - } - - destroy.handle = create.handle; - if (drmPrimeHandleToFD(kms_fd, create.handle, - DRM_CLOEXEC | DRM_RDWR, &fd)) { - ERROR_MSG("Failed to export KMS dumb dma-buf (%s); falling back to dma-heap", - strerror(errno)); - fd = -1; - } - - if (drmIoctl(kms_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy)) { - ERROR_MSG("Failed to destroy temporary KMS dumb handle (%s)", - strerror(errno)); - } - if (owned_fd >= 0) - close(owned_fd); - - return fd; -} - static struct fd_bo * kgsl_bo_new_dmabuf(struct fd_device *dev, uint32_t size) { - bool kms_backed; int fd; struct fd_bo *bo; - fd = kms_dumb_alloc(dev, size); - kms_backed = fd >= 0; - if (fd < 0) - fd = dma_heap_alloc(size); + fd = dma_heap_alloc(size); if (fd < 0) { ERROR_MSG("Failed to allocate dma-buf (%s)", strerror(errno)); return NULL; @@ -371,17 +258,6 @@ kgsl_bo_new_dmabuf(struct fd_device *dev, uint32_t size) bo = kgsl_bo_from_dmabuf(dev, fd); - /* A platform may support dumb allocation/PRIME export but still reject the - * object at KGSL import. Preserve the established dma-heap path as a - * strict fallback rather than failing shared-buffer allocation outright. */ - if (!bo && kms_backed) { - close(fd); - fd = dma_heap_alloc(size); - if (fd < 0) - return NULL; - bo = kgsl_bo_from_dmabuf(dev, fd); - } - close(fd); return bo; } diff --git a/src/freedreno/drm/kgsl/kgsl_priv.h b/src/freedreno/drm/kgsl/kgsl_priv.h index d92440211a22..09e77a1dafb1 100644 --- a/src/freedreno/drm/kgsl/kgsl_priv.h +++ b/src/freedreno/drm/kgsl/kgsl_priv.h @@ -42,7 +42,6 @@ struct kgsl_bo { const char *name; uint64_t iova; uint32_t queue_id; - uint32_t kms_handle; int import_fd; // fd for imported buffers enum { diff --git a/src/gallium/drivers/freedreno/freedreno_screen.c b/src/gallium/drivers/freedreno/freedreno_screen.c index 1d854b0b6e5e..c24d35d386a4 100644 --- a/src/gallium/drivers/freedreno/freedreno_screen.c +++ b/src/gallium/drivers/freedreno/freedreno_screen.c @@ -12,6 +12,8 @@ #include "util/format/u_format.h" #include "util/format/u_format_s3tc.h" +#include "util/libdrm.h" +#include "util/os_file.h" #include "util/os_misc.h" #include "util/u_debug.h" #include "util/u_inlines.h" @@ -25,6 +27,7 @@ #include #include #include +#include #include "drm-uapi/drm_fourcc.h" #include "freedreno_fence.h" @@ -86,6 +89,59 @@ static const struct debug_named_value fd_debug_options[] = { }; /* clang-format on */ +static void +fd_kgsl_renderonly_destroy(struct renderonly *ro) +{ + if (ro->kms_fd >= 0) + close(ro->kms_fd); + + util_sparse_array_finish(&ro->bo_map); + FREE(ro); +} + +/* + * Android KGSL submits GPU work through /dev/kgsl-3d0 while scanout is owned + * by a separate DRM/KMS device. Model that split with Mesa's established + * renderonly contract instead of teaching the byte-oriented KGSL BO allocator + * how to manufacture display buffers. The fd supplied to this screen is the + * DRI3/KMS control fd; duplicating it preserves the GEM handle namespace used + * by the X server while render submissions continue through screen->dev->fd. + */ +static struct renderonly * +fd_kgsl_renderonly_create(int kms_fd) +{ + struct renderonly *ro = CALLOC_STRUCT(renderonly); + + if (!ro) + return NULL; + + ro->kms_fd = os_dupfd_cloexec(kms_fd); + ro->gpu_fd = -1; + if (ro->kms_fd < 0) { + FREE(ro); + return NULL; + } + + ro->create_for_resource = + renderonly_create_kms_dumb_buffer_for_resource; + ro->destroy = fd_kgsl_renderonly_destroy; + util_sparse_array_init(&ro->bo_map, + sizeof(struct renderonly_scanout), 64); + simple_mtx_init(&ro->bo_map_lock, mtx_plain); + return ro; +} + +static bool +fd_kgsl_has_kms_control_fd(struct fd_device *dev) +{ + drmVersionPtr version = drmGetVersion(fd_device_fd(dev)); + bool has_kms = version && !strcmp(version->name, "msm"); + + if (version) + drmFreeVersion(version); + return has_kms; +} + DEBUG_GET_ONCE_FLAGS_OPTION(fd_mesa_debug, "FD_MESA_DEBUG", fd_debug_options, 0) int fd_mesa_debug = 0; @@ -985,6 +1041,17 @@ fd_screen_create(int fd, screen->dev = dev; screen->is_kgsl = fd_get_features(dev) & FD_FEATURE_KGSL; screen->kgsl_dmabuf = screen->is_kgsl && fd_kgsl_dmabuf_enabled(); + if (!ro && screen->is_kgsl && fd_kgsl_has_kms_control_fd(dev) && + debug_get_bool_option("FD_KGSL_RENDERONLY", false)) { + ro = fd_kgsl_renderonly_create(fd_device_fd(dev)); + if (!ro) { + mesa_loge("freedreno/kgsl: failed to create KMS renderonly screen"); + fd_device_del(dev); + FREE(screen); + return NULL; + } + mesa_logi("freedreno/kgsl: using Mesa renderonly for KMS scanout"); + } screen->ro = ro; // maybe this should be in context? diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index ed9d5bc75e6a..53fdbaf0eb39 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -54,6 +54,7 @@ #include "util/u_queue.h" #include "util/simple_mtx.h" #include "drm-uapi/drm_fourcc.h" +#include "dri_context.h" #include "dri_screen.h" #include "dri_util.h" @@ -673,7 +674,22 @@ dri3_shm_bridge_present_thread(void *data) int source_stride = 0; uint8_t *source = NULL; bool copied = false; - bool fence_ready = fence_fd < 0 || sync_wait(fence_fd, -1) == 0; + void *gpu_wait_fence = NULL; + bool fence_ready = true; + if (ctx && state->gpu_blit && fence_fd >= 0) { + /* Queue the producer's native sync-file as an in-fence for this + * context. This is Gallium's normal cross-context synchronization + * path: KGSL waits before executing the blit without blocking this + * worker on the producer first. */ + gpu_wait_fence = dri_create_fence_fd(ctx, fence_fd); + if (gpu_wait_fence) + dri_server_wait_sync(ctx, gpu_wait_fence, 0); + else + fence_ready = false; + } else if (fence_fd >= 0) { + /* CPU readback cannot start until the producer is complete. */ + fence_ready = sync_wait(fence_fd, -1) == 0; + } if (fence_fd >= 0) close(fence_fd); if (ctx && buffer && fence_ready && state->gpu_blit) { @@ -699,6 +715,8 @@ dri3_shm_bridge_present_thread(void *data) dri2_unmap_image(ctx, buffer->image, transfer); copied = true; } + if (gpu_wait_fence) + dri_destroy_fence(ctx->screen, gpu_wait_fence); if (ctx) loader_dri3_blit_context_put(); @@ -937,7 +955,6 @@ dri3_present_sync_init(struct loader_dri3_drawable *draw, int buffer_fd) if (draw->shm_bridge || draw->type != LOADER_DRI3_DRAWABLE_WINDOW || - draw->dri_screen_render_gpu != draw->dri_screen_display_gpu || !(dri_fence_get_caps(draw->dri_screen_render_gpu) & __DRI_FENCE_CAP_NATIVE_FD) || !dri3_dmabuf_sync_file_unavailable(buffer_fd)) @@ -2056,8 +2073,12 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, if (!draw->have_back || draw->type == LOADER_DRI3_DRAWABLE_PIXMAP) return ret; + bool prime_explicit_sync = + draw->present_sync && + draw->dri_screen_render_gpu != draw->dri_screen_display_gpu; + if (draw->type == LOADER_DRI3_DRAWABLE_WINDOW && - (draw->shm_bridge || draw->present_sync) && + (draw->shm_bridge || (draw->present_sync && !prime_explicit_sync)) && draw->vtable->flush_drawable_with_fence_fd) { render_fence_fd = draw->vtable->flush_drawable_with_fence_fd(draw, flush_flags); @@ -2115,6 +2136,15 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, back->image, 0, 0, back->width, back->height, 0, 0, __BLIT_FLAG_FLUSH); + + /* The normal PRIME path relies on dma-buf implicit fencing. KGSL + * dma-bufs on Android do not expose that contract, so export a native + * fence after the render-to-display blit and pass it to Present through + * the existing X Sync wait-fence path. */ + if (prime_explicit_sync && + draw->vtable->flush_drawable_with_fence_fd) + render_fence_fd = + draw->vtable->flush_drawable_with_fence_fd(draw, flush_flags); } /* If we need to preload the new back buffer, remember the source. diff --git a/src/gallium/frontends/dri/loader_dri3_helper.h b/src/gallium/frontends/dri/loader_dri3_helper.h index 2405d4c05d93..de85902416b6 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.h +++ b/src/gallium/frontends/dri/loader_dri3_helper.h @@ -103,7 +103,7 @@ struct loader_dri3_drawable; struct loader_dri3_present_sync; #define LOADER_DRI3_SHM_BRIDGE_SLOTS 3 -#define LOADER_DRI3_SHM_BRIDGE_ABI "HDMI_LOS_MESA_BRIDGE_ABI=3" +#define LOADER_DRI3_SHM_BRIDGE_ABI "HDMI_LOS_MESA_BRIDGE_ABI=4" struct loader_dri3_shm_bridge_slot { xcb_shm_seg_t seg; diff --git a/src/loader/loader.c b/src/loader/loader.c index 939ae54965a2..0c6446947bbf 100644 --- a/src/loader/loader.c +++ b/src/loader/loader.c @@ -488,6 +488,21 @@ bool loader_get_user_preferred_fd(int *fd_render_gpu, int *original_fd) } prime = {}; prime.str = NULL; + /* KGSL is a render device without a DRM node, so libdrm cannot discover + * it as a DRI_PRIME candidate. Preserve the DRI3-provided DRM fd as the + * display GPU and use KGSL as the render GPU. This feeds the established + * PRIME render/display path instead of bypassing DRI3 in x11_dri3_open(). + */ + if (original_fd && debug_get_bool_option("FD_FORCE_KGSL", false)) { + int kgsl_fd = loader_open_device("/dev/kgsl-3d0"); + + if (kgsl_fd >= 0) { + *original_fd = *fd_render_gpu; + *fd_render_gpu = kgsl_fd; + return true; + } + } + if (dri_prime) prime.str = strdup(dri_prime); #ifdef USE_DRICONF diff --git a/src/x11/x11_dri3.c b/src/x11/x11_dri3.c index b995372be661..3587d24d6ad6 100644 --- a/src/x11/x11_dri3.c +++ b/src/x11/x11_dri3.c @@ -23,7 +23,6 @@ #include "x11_dri3.h" -#include #include #include #include @@ -49,10 +48,6 @@ x11_dri3_open(xcb_connection_t *conn, int fd; const xcb_query_extension_reply_t *extension; - const char *env = getenv("MESA_LOADER_DRIVER_OVERRIDE"); - if (env && !strcmp(env, "kgsl")) - return open("/dev/kgsl-3d0", O_RDWR); - xcb_prefetch_extension_data(conn, &xcb_dri3_id); extension = xcb_get_extension_data(conn, &xcb_dri3_id); if (!(extension && extension->present)) From f4cb22e8082e9874dd8d6854c953ebe49691e73a Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 06:52:21 +0300 Subject: [PATCH 08/14] loader: retain the DRI3 fd for KGSL renderonly --- src/loader/loader.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/loader/loader.c b/src/loader/loader.c index 0c6446947bbf..939ae54965a2 100644 --- a/src/loader/loader.c +++ b/src/loader/loader.c @@ -488,21 +488,6 @@ bool loader_get_user_preferred_fd(int *fd_render_gpu, int *original_fd) } prime = {}; prime.str = NULL; - /* KGSL is a render device without a DRM node, so libdrm cannot discover - * it as a DRI_PRIME candidate. Preserve the DRI3-provided DRM fd as the - * display GPU and use KGSL as the render GPU. This feeds the established - * PRIME render/display path instead of bypassing DRI3 in x11_dri3_open(). - */ - if (original_fd && debug_get_bool_option("FD_FORCE_KGSL", false)) { - int kgsl_fd = loader_open_device("/dev/kgsl-3d0"); - - if (kgsl_fd >= 0) { - *original_fd = *fd_render_gpu; - *fd_render_gpu = kgsl_fd; - return true; - } - } - if (dri_prime) prime.str = strdup(dri_prime); #ifdef USE_DRICONF From d2c5ddbd9f3a9806906a63490c8d1f849fed4254 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 07:56:37 +0300 Subject: [PATCH 09/14] freedreno/kgsl: recognize downstream msm_drm KMS --- src/gallium/drivers/freedreno/freedreno_screen.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gallium/drivers/freedreno/freedreno_screen.c b/src/gallium/drivers/freedreno/freedreno_screen.c index c24d35d386a4..9bf03e141096 100644 --- a/src/gallium/drivers/freedreno/freedreno_screen.c +++ b/src/gallium/drivers/freedreno/freedreno_screen.c @@ -135,7 +135,14 @@ static bool fd_kgsl_has_kms_control_fd(struct fd_device *dev) { drmVersionPtr version = drmGetVersion(fd_device_fd(dev)); - bool has_kms = version && !strcmp(version->name, "msm"); + /* + * Mainline names this DRM driver "msm". Qualcomm's downstream display + * stack, including the Sony pdx234 kernel, reports "msm_drm" instead. + * Both expose the KMS/dumb-buffer interface needed by renderonly. + */ + bool has_kms = version && + (!strcmp(version->name, "msm") || + !strcmp(version->name, "msm_drm")); if (version) drmFreeVersion(version); From 295495c75777ad53c5dda4495d1efc448b8ab40b Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 08:34:17 +0300 Subject: [PATCH 10/14] dri3: select KGSL fd for bridge clients --- src/x11/x11_dri3.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/x11/x11_dri3.c b/src/x11/x11_dri3.c index 3587d24d6ad6..c952b672ad61 100644 --- a/src/x11/x11_dri3.c +++ b/src/x11/x11_dri3.c @@ -23,6 +23,7 @@ #include "x11_dri3.h" +#include #include #include #include @@ -48,6 +49,14 @@ x11_dri3_open(xcb_connection_t *conn, int fd; const xcb_query_extension_reply_t *extension; + /* The leased Xorg must use the DRI3 render-node fd so renderonly can + * allocate KMS-compatible scanout buffers. Opt-in bridge clients instead + * render into native KGSL buffers and explicitly copy completed frames to + * Xorg-owned pixmaps, so their render screen must be opened on KGSL. */ + const char *bridge = getenv("MESA_KGSL_X11_SHM_BRIDGE"); + if (bridge && !strcmp(bridge, "1")) + return open("/dev/kgsl-3d0", O_RDWR | O_CLOEXEC); + xcb_prefetch_extension_data(conn, &xcb_dri3_id); extension = xcb_get_extension_data(conn, &xcb_dri3_id); if (!(extension && extension->present)) From 7d248aaf33f35d95fa28e9031887936bb2178d97 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 12:27:26 +0300 Subject: [PATCH 11/14] freedreno/kgsl: separate render and KMS fds --- src/freedreno/drm/freedreno_device.c | 20 +++++++++++-------- src/freedreno/drm/freedreno_drmif.h | 1 + src/freedreno/drm/freedreno_priv.h | 2 +- .../drivers/freedreno/freedreno_screen.c | 6 +++--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/freedreno/drm/freedreno_device.c b/src/freedreno/drm/freedreno_device.c index 141716bbfc71..8dec709978ec 100644 --- a/src/freedreno/drm/freedreno_device.c +++ b/src/freedreno/drm/freedreno_device.c @@ -53,12 +53,11 @@ fd_device_new(int fd) * on the first capability query. When kgsl is forced (the whole kgsl stack * exports FD_FORCE_KGSL=1), ignore the fd we were handed and open the kgsl * GPU node directly for rendering. The fd we were handed is kept as the - * device control/identity fd (dev->control_fd, returned by fd_device_fd): + * device control/identity fd (dev->control_fd): * it still identifies the screen for u_pipe_screen_lookup_or_create()'s - * fd-keyed cache (which compares file descriptions, so dev->fd MUST stay a - * dup of the cache key - a freshly opened kgsl fd is a different file - * description and would break cache eviction -> use-after-free) and is the - * fd handed to DRI3 clients. Only GPU submission uses the kgsl fd. */ + * fd-keyed cache (which compares file descriptions, so the screen's + * get_screen_fd callback must return this control fd) and is the fd handed + * to DRI3 clients. */ if (debug_get_bool_option("FD_FORCE_KGSL", false)) { int kgsl_fd = open("/dev/kgsl-3d0", O_RDWR | O_CLOEXEC); if (kgsl_fd >= 0) { @@ -296,9 +295,14 @@ fd_device_del(struct fd_device *dev) int fd_device_fd(struct fd_device *dev) { - /* Return the control/identity fd, not the GPU submission fd. On the kgsl - * stack these differ: the screen cache and DRI3 must see the fd we were - * handed (a dup of the cache key), while GPU submission uses dev->fd. */ + return dev->fd; +} + +int +fd_device_control_fd(struct fd_device *dev) +{ + /* The KGSL backend submits through dev->fd while the DRI/KMS winsys and + * fd-keyed screen cache must retain the DRM fd supplied by the loader. */ return dev->control_fd; } diff --git a/src/freedreno/drm/freedreno_drmif.h b/src/freedreno/drm/freedreno_drmif.h index 930a476ab039..f63255a2ff77 100644 --- a/src/freedreno/drm/freedreno_drmif.h +++ b/src/freedreno/drm/freedreno_drmif.h @@ -170,6 +170,7 @@ struct fd_device *fd_device_ref(struct fd_device *dev); void fd_device_purge(struct fd_device *dev); void fd_device_del(struct fd_device *dev); int fd_device_fd(struct fd_device *dev); +int fd_device_control_fd(struct fd_device *dev); enum fd_version { FD_VERSION_MADVISE = 1, /* kernel supports madvise */ diff --git a/src/freedreno/drm/freedreno_priv.h b/src/freedreno/drm/freedreno_priv.h index 98037778f30e..4773768c7fb7 100644 --- a/src/freedreno/drm/freedreno_priv.h +++ b/src/freedreno/drm/freedreno_priv.h @@ -189,7 +189,7 @@ submit_offset(struct fd_bo *bo, uint32_t offset) struct fd_device { int fd; /* GPU submission fd (kgsl GPU node on the kgsl stack) */ - int control_fd; /* screen-cache / DRI3 identity fd; == fd unless redirected */ + int control_fd; /* screen-cache / DRI/KMS fd; == fd unless redirected */ enum fd_version version; int32_t refcnt; uint32_t features; diff --git a/src/gallium/drivers/freedreno/freedreno_screen.c b/src/gallium/drivers/freedreno/freedreno_screen.c index 9bf03e141096..3e263e8acf9e 100644 --- a/src/gallium/drivers/freedreno/freedreno_screen.c +++ b/src/gallium/drivers/freedreno/freedreno_screen.c @@ -134,7 +134,7 @@ fd_kgsl_renderonly_create(int kms_fd) static bool fd_kgsl_has_kms_control_fd(struct fd_device *dev) { - drmVersionPtr version = drmGetVersion(fd_device_fd(dev)); + drmVersionPtr version = drmGetVersion(fd_device_control_fd(dev)); /* * Mainline names this DRM driver "msm". Qualcomm's downstream display * stack, including the Sony pdx234 kernel, reports "msm_drm" instead. @@ -1000,7 +1000,7 @@ static int fd_screen_get_fd(struct pipe_screen *pscreen) { struct fd_screen *screen = fd_screen(pscreen); - return fd_device_fd(screen->dev); + return fd_device_control_fd(screen->dev); } static void @@ -1050,7 +1050,7 @@ fd_screen_create(int fd, screen->kgsl_dmabuf = screen->is_kgsl && fd_kgsl_dmabuf_enabled(); if (!ro && screen->is_kgsl && fd_kgsl_has_kms_control_fd(dev) && debug_get_bool_option("FD_KGSL_RENDERONLY", false)) { - ro = fd_kgsl_renderonly_create(fd_device_fd(dev)); + ro = fd_kgsl_renderonly_create(fd_device_control_fd(dev)); if (!ro) { mesa_loge("freedreno/kgsl: failed to create KMS renderonly screen"); fd_device_del(dev); From 64b6af2fcce710e0e7d82dbe6e78ee7d903cb0be Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 12:30:41 +0300 Subject: [PATCH 12/14] dri3: add KGSL shadow-present buffers --- .../frontends/dri/loader_dri3_helper.c | 129 +++++++++++++++--- .../frontends/dri/loader_dri3_helper.h | 4 +- 2 files changed, 116 insertions(+), 17 deletions(-) diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index 53fdbaf0eb39..de963ebc5883 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -944,11 +944,13 @@ dri3_dmabuf_sync_file_unavailable(int fd) } static bool -dri3_present_sync_init(struct loader_dri3_drawable *draw, int buffer_fd) +dri3_present_sync_init(struct loader_dri3_drawable *draw, int buffer_fd, + bool explicit_sync_required) { struct loader_dri3_present_sync *sync; - if (draw->present_sync_checked) + if (draw->present_sync_checked && + (draw->present_sync || !explicit_sync_required)) return draw->present_sync != NULL; draw->present_sync_checked = true; @@ -957,7 +959,8 @@ dri3_present_sync_init(struct loader_dri3_drawable *draw, int buffer_fd) draw->type != LOADER_DRI3_DRAWABLE_WINDOW || !(dri_fence_get_caps(draw->dri_screen_render_gpu) & __DRI_FENCE_CAP_NATIVE_FD) || - !dri3_dmabuf_sync_file_unavailable(buffer_fd)) + (!explicit_sync_required && + !dri3_dmabuf_sync_file_unavailable(buffer_fd))) return false; sync = calloc(1, sizeof(*sync)); @@ -1416,6 +1419,12 @@ loader_dri3_drawable_init(xcb_connection_t *conn, draw->present_sync = NULL; draw->shm_bridge = debug_get_bool_option("MESA_KGSL_X11_SHM_BRIDGE", false); + draw->shadow_present = + debug_get_bool_option("MESA_KGSL_X11_SHADOW", false); + if (draw->shm_bridge && draw->shadow_present) { + mesa_loge("DRI3: SHM bridge and shadow presentation are mutually exclusive"); + return 1; + } draw->shm_bridge_stats = debug_get_bool_option("MESA_KGSL_X11_BRIDGE_STATS", false); draw->shm_bridge_conn = NULL; @@ -2073,17 +2082,21 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, if (!draw->have_back || draw->type == LOADER_DRI3_DRAWABLE_PIXMAP) return ret; + bool shadow_present = + draw->shadow_present && draw->type == LOADER_DRI3_DRAWABLE_WINDOW; bool prime_explicit_sync = draw->present_sync && draw->dri_screen_render_gpu != draw->dri_screen_display_gpu; - if (draw->type == LOADER_DRI3_DRAWABLE_WINDOW && - (draw->shm_bridge || (draw->present_sync && !prime_explicit_sync)) && - draw->vtable->flush_drawable_with_fence_fd) { - render_fence_fd = - draw->vtable->flush_drawable_with_fence_fd(draw, flush_flags); - } else { - draw->vtable->flush_drawable(draw, flush_flags); + if (!shadow_present) { + if (draw->type == LOADER_DRI3_DRAWABLE_WINDOW && + (draw->shm_bridge || (draw->present_sync && !prime_explicit_sync)) && + draw->vtable->flush_drawable_with_fence_fd) { + render_fence_fd = + draw->vtable->flush_drawable_with_fence_fd(draw, flush_flags); + } else { + draw->vtable->flush_drawable(draw, flush_flags); + } } back = dri3_find_back_alloc(draw); @@ -2094,6 +2107,36 @@ loader_dri3_swap_buffers_msc(struct loader_dri3_drawable *draw, return ret; } + if (shadow_present) { + if (!back->needs_present_blit || !back->linear_buffer) { + mesa_loge("DRI3: shadow presentation buffer is incomplete"); + return ret; + } + + /* Resolve in the application's current context. This orders the copy + * after all rendering to the private image. The following native-fence + * flush therefore covers both rendering and the tiled/UBWC-to-linear + * resolve exported to Xorg. */ + if (!loader_dri3_blit_image(draw, back->linear_buffer, back->image, + 0, 0, back->width, back->height, + 0, 0, __BLIT_FLAG_FLUSH)) { + mesa_loge("DRI3: failed to resolve KGSL shadow buffer"); + return ret; + } + if (!draw->present_sync || + !draw->vtable->flush_drawable_with_fence_fd) { + mesa_loge("DRI3: explicit presentation sync is unavailable for " + "KGSL shadow"); + return ret; + } + render_fence_fd = + draw->vtable->flush_drawable_with_fence_fd(draw, flush_flags); + if (render_fence_fd < 0) { + mesa_loge("DRI3: failed to export KGSL shadow completion fence"); + return ret; + } + } + bool bridge_presented = false; if (draw->shm_bridge && draw->type == LOADER_DRI3_DRAWABLE_WINDOW) { bridge_presented = @@ -2484,7 +2527,8 @@ has_supported_modifier(struct loader_dri3_drawable *draw, unsigned int format, */ static struct loader_dri3_buffer * dri3_alloc_render_buffer(struct loader_dri3_drawable *draw, unsigned int fourcc, - int width, int height, int depth) + int width, int height, int depth, + enum loader_dri3_buffer_type buffer_type) { struct loader_dri3_buffer *buffer; struct dri_image *pixmap_buffer = NULL, *linear_buffer_display_gpu = NULL; @@ -2507,7 +2551,47 @@ dri3_alloc_render_buffer(struct loader_dri3_drawable *draw, unsigned int fourcc, if (!buffer->cpp) goto no_image; - if (draw->dri_screen_render_gpu == draw->dri_screen_display_gpu) { + bool shadow_present = + draw->shadow_present && draw->type == LOADER_DRI3_DRAWABLE_WINDOW && + buffer_type == loader_dri3_buffer_back; + + if (shadow_present) { + /* Keep the application's render target private so Freedreno can select + * its native tiled/UBWC layout. Only the persistent linear renderonly + * image is exported to Xorg and participates in Present/KMS lifetime. */ + buffer->image = + dri_create_image(draw->dri_screen_render_gpu, + width, height, format, NULL, 0, + __DRI_IMAGE_USE_BACKBUFFER | + (draw->is_protected_content ? + __DRI_IMAGE_USE_PROTECTED : 0), + buffer); + if (!buffer->image) { + mesa_loge("DRI3: failed to allocate private KGSL shadow image"); + goto no_image; + } + + buffer->linear_buffer = + dri_create_image(draw->dri_screen_render_gpu, + width, height, + dri3_linear_format_for_format(draw, format), + NULL, 0, + __DRI_IMAGE_USE_SHARE | + __DRI_IMAGE_USE_LINEAR | + __DRI_IMAGE_USE_BACKBUFFER | + __DRI_IMAGE_USE_SCANOUT | + __DRI_IMAGE_USE_PRIME_BUFFER | + (draw->is_protected_content ? + __DRI_IMAGE_USE_PROTECTED : 0), + buffer); + if (!buffer->linear_buffer) { + mesa_loge("DRI3: failed to allocate KMS shadow-present image"); + goto no_linear_buffer; + } + + buffer->needs_present_blit = true; + pixmap_buffer = buffer->linear_buffer; + } else if (draw->dri_screen_render_gpu == draw->dri_screen_display_gpu) { if (draw->multiplanes_available && draw->dri_screen_render_gpu->base.screen->resource_create_with_modifiers) { xcb_dri3_get_supported_modifiers_cookie_t mod_cookie; xcb_dri3_get_supported_modifiers_reply_t *mod_reply; @@ -2660,7 +2744,12 @@ dri3_alloc_render_buffer(struct loader_dri3_drawable *draw, unsigned int fourcc, * though DRI3 buffer sharing itself works. In that case Present needs an * explicit render-completion fence. */ - dri3_present_sync_init(draw, buffer_fds[0]); + if (!dri3_present_sync_init(draw, buffer_fds[0], + buffer->needs_present_blit) && + buffer->needs_present_blit) { + mesa_loge("DRI3: KGSL shadow requires native explicit Present fences"); + goto no_buffer_attrib; + } if (draw->dri_screen_render_gpu != draw->dri_screen_display_gpu && draw->dri_screen_display_gpu && linear_buffer_display_gpu) { @@ -2720,6 +2809,12 @@ dri3_alloc_render_buffer(struct loader_dri3_drawable *draw, unsigned int fourcc, buffer->width = width; buffer->height = height; dri3_setup_present_wait_fence(draw, buffer); + if (buffer->needs_present_blit && !buffer->present_wait_fence) { + mesa_loge("DRI3: failed to create KGSL shadow Present wait fence"); + xcb_free_pixmap(draw->conn, buffer->pixmap); + buffer->pixmap = 0; + goto no_buffer_attrib; + } /* Mark the buffer as idle */ @@ -2734,7 +2829,7 @@ dri3_alloc_render_buffer(struct loader_dri3_drawable *draw, unsigned int fourcc, } while (--i >= 0); dri2_destroy_image(pixmap_buffer); no_linear_buffer: - if (draw->dri_screen_render_gpu != draw->dri_screen_display_gpu) + if (buffer->image && buffer->image != pixmap_buffer) dri2_destroy_image(buffer->image); no_image: free(buffer); @@ -3087,7 +3182,8 @@ dri3_get_buffer(struct dri_drawable *driDrawable, fourcc, draw->width, draw->height, - draw->depth); + draw->depth, + buffer_type); if (!new_buffer) return NULL; @@ -3399,7 +3495,8 @@ dri3_find_back_alloc(struct loader_dri3_drawable *draw) if (!back && draw->back_format != DRM_FORMAT_INVALID && dri3_update_drawable(draw)) back = dri3_alloc_render_buffer(draw, draw->back_format, - draw->width, draw->height, draw->depth); + draw->width, draw->height, draw->depth, + loader_dri3_buffer_back); if (!back) return NULL; diff --git a/src/gallium/frontends/dri/loader_dri3_helper.h b/src/gallium/frontends/dri/loader_dri3_helper.h index de85902416b6..e8f56e36115d 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.h +++ b/src/gallium/frontends/dri/loader_dri3_helper.h @@ -53,6 +53,7 @@ struct loader_dri3_buffer { * while creating screen in dri3_create_screen() function. */ struct dri_image *linear_buffer; + bool needs_present_blit; /* Synchronization between the client and X server is done using an * xshmfence that is mapped into an X server SyncFence. This lets the @@ -103,7 +104,7 @@ struct loader_dri3_drawable; struct loader_dri3_present_sync; #define LOADER_DRI3_SHM_BRIDGE_SLOTS 3 -#define LOADER_DRI3_SHM_BRIDGE_ABI "HDMI_LOS_MESA_BRIDGE_ABI=4" +#define LOADER_DRI3_SHM_BRIDGE_ABI "HDMI_LOS_MESA_BRIDGE_ABI=5" struct loader_dri3_shm_bridge_slot { xcb_shm_seg_t seg; @@ -189,6 +190,7 @@ struct loader_dri3_drawable { bool queries_buffer_age; bool present_sync_checked; bool shm_bridge; + bool shadow_present; bool shm_bridge_stats; xcb_connection_t *shm_bridge_conn; xcb_gcontext_t shm_bridge_gc; From 99c6bd35433a08d39e2044daecad448eb181f394 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Thu, 3 Sep 2026 20:24:12 +0300 Subject: [PATCH 13/14] dri3: pipeline flip-eligible KGSL bridge presents --- .../frontends/dri/loader_dri3_helper.c | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/gallium/frontends/dri/loader_dri3_helper.c b/src/gallium/frontends/dri/loader_dri3_helper.c index de963ebc5883..9913548f2332 100644 --- a/src/gallium/frontends/dri/loader_dri3_helper.c +++ b/src/gallium/frontends/dri/loader_dri3_helper.c @@ -93,7 +93,6 @@ struct dri3_shm_bridge_state { xcb_pixmap_t pixmaps[LOADER_DRI3_SHM_BRIDGE_SLOTS]; struct dri_image *slot_images[LOADER_DRI3_SHM_BRIDGE_SLOTS]; uint32_t slot_serials[LOADER_DRI3_SHM_BRIDGE_SLOTS]; - uint64_t slot_sequences[LOADER_DRI3_SHM_BRIDGE_SLOTS]; bool slot_completed[LOADER_DRI3_SHM_BRIDGE_SLOTS]; bool slot_idle[LOADER_DRI3_SHM_BRIDGE_SLOTS]; mtx_t mtx; @@ -118,9 +117,10 @@ struct dri3_shm_bridge_state { uint64_t presented_frames; uint64_t dropped_frames; uint64_t server_skipped_frames; + uint64_t flip_frames; + uint64_t copy_frames; + uint64_t suboptimal_copy_frames; uint64_t submitted_frames; - uint64_t pending_sequence; - uint64_t presented_sequence; uint64_t next_target_msc; uint64_t bytes; uint64_t waits; @@ -173,9 +173,12 @@ dri3_shm_bridge_report_stats(struct loader_dri3_drawable *draw, mesa_logi("DRI3: KGSL bridge produced=%" PRIu64 " presented=%" PRIu64 " dropped=%" PRIu64 " server_skipped=%" PRIu64 + " modes=%" PRIu64 "/%" PRIu64 "/%" PRIu64 " copied=%.1f MiB rate=%.1f fps waits=%" PRIu64, state->produced_frames, state->presented_frames, state->dropped_frames, state->server_skipped_frames, + state->flip_frames, state->copy_frames, + state->suboptimal_copy_frames, state->bytes / (1024.0 * 1024.0), seconds > 0.0 ? state->produced_frames / seconds : 0.0, state->waits); @@ -281,7 +284,7 @@ dri3_shm_bridge_init(struct loader_dri3_drawable *draw, state->capture_slot = -1; state->ready_slot = -1; int64_t gpu_bridge_min_pixels = debug_get_num_option( - "MESA_KGSL_X11_GPU_BRIDGE_MIN_PIXELS", 8000000); + "MESA_KGSL_X11_GPU_BRIDGE_MIN_PIXELS", 0); uint64_t drawable_pixels = (uint64_t) buffer->width * buffer->height; state->gpu_blit = debug_get_bool_option("MESA_KGSL_X11_GPU_BRIDGE", false) && @@ -490,21 +493,32 @@ dri3_shm_bridge_handle_present_event(struct loader_dri3_drawable *draw, draw->shm_bridge_msc = complete->msc; if (!state->present_primed) { state->present_primed = true; - state->next_target_msc = complete->msc + 1; + /* Leave one complete refresh between the priming Present and the + * first scheduled frame. The bridge can fill the following + * slots during that lead instead of racing a full-surface copy + * against the immediately following vblank. */ + state->next_target_msc = complete->msc + 2; } else if (state->next_target_msc <= complete->msc) { - state->next_target_msc = complete->msc + 1; + /* Re-prime a starved queue with the same one-refresh lead. */ + state->next_target_msc = complete->msc + 2; } for (unsigned i = 0; i < LOADER_DRI3_SHM_BRIDGE_SLOTS; i++) { if (state->slot_serials[i] != complete->serial) continue; state->slot_completed[i] = true; - if (complete->mode == XCB_PRESENT_COMPLETE_MODE_SKIP) + if (complete->mode == XCB_PRESENT_COMPLETE_MODE_SKIP) { state->server_skipped_frames++; - else + } else { state->presented_frames++; - state->presented_sequence = - MAX2(state->presented_sequence, state->slot_sequences[i]); + if (complete->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) + state->flip_frames++; + else if (complete->mode == XCB_PRESENT_COMPLETE_MODE_COPY) + state->copy_frames++; + else if (complete->mode == + XCB_PRESENT_COMPLETE_MODE_SUBOPTIMAL_COPY) + state->suboptimal_copy_frames++; + } dri3_shm_bridge_maybe_release_slot(draw, state, i); break; } @@ -755,10 +769,14 @@ dri3_shm_bridge_present_thread(void *data) (uint64_t) buffer->width * buffer->height * buffer->cpp; mtx_unlock(&state->mtx); + uint32_t options = state->gpu_blit ? XCB_PRESENT_OPTION_NONE : + XCB_PRESENT_OPTION_COPY; + if (state->gpu_blit && interval <= 0) + options |= XCB_PRESENT_OPTION_ASYNC; xcb_present_pixmap(draw->shm_bridge_conn, draw->drawable, state->pixmaps[slot_index], serial, 0, 0, 0, 0, XCB_NONE, XCB_NONE, XCB_NONE, - XCB_PRESENT_OPTION_COPY, target_msc, + options, target_msc, 0, 0, 0, NULL); if (xcb_flush(draw->shm_bridge_conn) <= 0) break; @@ -783,8 +801,6 @@ dri3_shm_bridge_present(struct loader_dri3_drawable *draw, struct dri3_shm_bridge_state *state; int slot_index; int interval = draw->swap_interval; - uint64_t sequence; - bool ok; if (!draw->vtable->get_dri_context(draw) || buffer->cpp != 4 || !dri3_shm_bridge_init(draw, buffer)) { @@ -799,6 +815,12 @@ dri3_shm_bridge_present(struct loader_dri3_drawable *draw, mtx_lock(&state->mtx); state->produced_frames++; bool first_frame = state->produced_frames == 1; + + /* Synchronized clients are backpressured by the three Present slots. The + * worker copies and submits distinct future-MSC requests until every slot + * is busy; CompleteNotify plus IdleNotify then releases one slot per + * refresh. Waiting for each completion in this producer path would defer + * the copy until its target vblank and reduce a 60 Hz display to 30 Hz. */ while ((interval != 0 || first_frame) && (!state->capture_available || state->capture_in_progress) && !p_atomic_read(&state->stop)) @@ -826,8 +848,6 @@ dri3_shm_bridge_present(struct loader_dri3_drawable *draw, state->capture_available = false; state->capture_slot = -1; state->capture_in_progress = true; - sequence = ++state->pending_sequence; - state->slot_sequences[slot_index] = sequence; state->capture_buffer = buffer; state->capture_fence_fd = render_fence_fd; state->ready_slot = slot_index; @@ -840,15 +860,7 @@ dri3_shm_bridge_present(struct loader_dri3_drawable *draw, cnd_broadcast(&state->cnd); mtx_unlock(&state->mtx); dri3_shm_bridge_wake(state); - - mtx_lock(&state->mtx); - while (interval != 0 && - state->presented_sequence < sequence && - !p_atomic_read(&state->stop)) - cnd_wait(&state->cnd, &state->mtx); - ok = !p_atomic_read(&state->stop); - mtx_unlock(&state->mtx); - return ok; + return true; } struct loader_dri3_present_sync { From acbc90e2066b2f8cbd49f7fcecf6e8819c27a7b0 Mon Sep 17 00:00:00 2001 From: KiralyCraft Date: Sat, 5 Sep 2026 20:04:39 +0300 Subject: [PATCH 14/14] x11: keep leased-screen device selection opt-in for KGSL clients The HDMI loader stopped honoring the ordinary KGSL device override and always requested a server DRI3 fd. Termux:X11 supports imported DRI3 pixmaps but has no open-device callback, so the otherwise clean PR96/HDMI merge could not create a GLX context. Preserve direct KGSL selection unless renderonly is explicitly enabled. Bridge clients also retain native KGSL rendering. Parse the bridge option consistently with the DRI loader, including true/false values. Do not add a fallback that could discard a required KMS fd. Add a hardware device-selection regression test covering eight ordinary, bridge, renderonly and other-driver configurations without GPU submission or display changes. The merged AArch64 GLX/EGL/Gallium/GBM stack passed Termux:X11 pixel, MSAA, resize, context-switch, imported-fence, burst and cadence checks, plus native BO-handle and real KMS framebuffer allocation tests. Full leased HDMI scanout remains a separate acceptance gate. (cherry picked from commit a522d4e1a1fd1ff610d8d13dfefeb6ec6171a44d) --- src/x11/meson.build | 17 +++++ src/x11/tests/kgsl_device_selection_test.c | 87 ++++++++++++++++++++++ src/x11/x11_dri3.c | 17 +++-- 3 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 src/x11/tests/kgsl_device_selection_test.c diff --git a/src/x11/meson.build b/src/x11/meson.build index 5955a293dde4..9b9b9fd91833 100644 --- a/src/x11/meson.build +++ b/src/x11/meson.build @@ -23,3 +23,20 @@ libloader_x11 = static_library( ], build_by_default : false, ) + +if with_tests and freedreno_kmds.contains('kgsl') and ( + with_dri_platform == 'drm' or with_dri_platform == 'pseudo-drm') + test( + 'kgsl_device_selection', + executable( + 'kgsl_device_selection_test', + 'tests/kgsl_device_selection_test.c', + include_directories : [inc_loader_x11], + dependencies : [ + idep_mesautil, dep_xcb, dep_xcb_dri3, dep_xcb_present, dep_xcb_xfixes, + ], + link_with : libloader_x11, + ), + suite : ['x11'], + ) +endif diff --git a/src/x11/tests/kgsl_device_selection_test.c b/src/x11/tests/kgsl_device_selection_test.c new file mode 100644 index 000000000000..1b5d62db7749 --- /dev/null +++ b/src/x11/tests/kgsl_device_selection_test.c @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include "x11_dri3.h" + +/* Hardware selection test only: no X server, allocation, GPU submission or + * display changes. A deliberately invalid display provides an errored XCB + * connection, so server-fd paths must fail instead of silently opening KGSL. + */ +static const struct { + const char *name; + const char *driver; + const char *bridge; + const char *renderonly; + bool native; +} cases[] = { + {"ordinary KGSL", "kgsl", NULL, NULL, true}, + {"disabled HDMI options", "kgsl", "0", "0", true}, + {"HDMI renderonly", "kgsl", NULL, "1", false}, + {"HDMI renderonly boolean", "kgsl", NULL, "true", false}, + {"HDMI bridge", "kgsl", "1", NULL, true}, + {"HDMI bridge boolean", "kgsl", "true", NULL, true}, + {"other driver", "llvmpipe", NULL, NULL, false}, + {"no override", NULL, NULL, NULL, false}, +}; + +static int +set_option(const char *name, const char *value) +{ + return value ? setenv(name, value, 1) : unsetenv(name); +} + +int +main(void) +{ + struct stat kgsl_stat; + int reference = open("/dev/kgsl-3d0", O_RDWR | O_CLOEXEC); + if (reference < 0) { + fprintf(stderr, "SKIP: /dev/kgsl-3d0 is not accessible\n"); + return 77; + } + int ret = fstat(reference, &kgsl_stat); + close(reference); + if (ret < 0 || !S_ISCHR(kgsl_stat.st_mode)) + return 1; + + xcb_connection_t *conn = xcb_connect(":", NULL); + if (xcb_connection_has_error(conn) != XCB_CONN_CLOSED_PARSE_ERR) { + fprintf(stderr, "Expected an invalid-display XCB connection\n"); + xcb_disconnect(conn); + return 1; + } + + int result = 0; + for (unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + if (set_option("MESA_LOADER_DRIVER_OVERRIDE", cases[i].driver) || + set_option("MESA_KGSL_X11_SHM_BRIDGE", cases[i].bridge) || + set_option("FD_KGSL_RENDERONLY", cases[i].renderonly)) { + perror("set device-selection options"); + result = 1; + break; + } + + int fd = x11_dri3_open(conn, 0, 0); + bool passed = fd < 0 && !cases[i].native; + if (fd >= 0) { + struct stat selected; + int flags = fcntl(fd, F_GETFD); + passed = cases[i].native && fstat(fd, &selected) == 0 && + S_ISCHR(selected.st_mode) && + selected.st_rdev == kgsl_stat.st_rdev && + flags >= 0 && (flags & FD_CLOEXEC); + close(fd); + } + printf("%s: %s\n", passed ? "PASS" : "FAIL", cases[i].name); + if (!passed) + result = 1; + } + + xcb_disconnect(conn); + return result; +} diff --git a/src/x11/x11_dri3.c b/src/x11/x11_dri3.c index c952b672ad61..2feb958c20fa 100644 --- a/src/x11/x11_dri3.c +++ b/src/x11/x11_dri3.c @@ -33,6 +33,8 @@ #include #include +#include "util/u_debug.h" + /** x11_dri3_open * * Wrapper around xcb_dri3_open @@ -49,12 +51,15 @@ x11_dri3_open(xcb_connection_t *conn, int fd; const xcb_query_extension_reply_t *extension; - /* The leased Xorg must use the DRI3 render-node fd so renderonly can - * allocate KMS-compatible scanout buffers. Opt-in bridge clients instead - * render into native KGSL buffers and explicitly copy completed frames to - * Xorg-owned pixmaps, so their render screen must be opened on KGSL. */ - const char *bridge = getenv("MESA_KGSL_X11_SHM_BRIDGE"); - if (bridge && !strcmp(bridge, "1")) + /* Preserve the KGSL override for Android X servers such as Termux:X11, + * which accept DRI3 pixmaps but do not provide a DRI3 device fd. Only + * explicitly enabled renderonly clients need the server's KMS fd for + * scanout allocation. Bridge clients still render on native KGSL and copy + * completed frames into separately allocated Xorg-owned pixmaps. */ + const char *driver = getenv("MESA_LOADER_DRIVER_OVERRIDE"); + if (debug_get_bool_option("MESA_KGSL_X11_SHM_BRIDGE", false) || + (driver && !strcmp(driver, "kgsl") && + !debug_get_bool_option("FD_KGSL_RENDERONLY", false))) return open("/dev/kgsl-3d0", O_RDWR | O_CLOEXEC); xcb_prefetch_extension_data(conn, &xcb_dri3_id);