Vendor media_kit_video 2.0.1 with the X11/EGL fix (hardware rendering on X11 with Flutter >= 3.38)
ci / core (push) Successful in 28s
ci / server (push) Successful in 51s
ci / updater-windows (push) Failing after 5m59s

Flutter >= 3.38 keeps its EGL contexts on the raster thread, so
media_kit's video output found none on the platform thread and fell
back to software rendering (no shaders, ~5 fps) on X11. The patched
output opens Flutter's EGL display itself, steps GDK's GLX context aside
on X11 and releases its own context. Patch from a friend's agent, kept
in third_party/media_kit_video/starsync/. Wayland/NVIDIA still renders
on the GPU (checked); the X11 path was verified on the friend's machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
foxyzandClaude Opus 5.5 committed 2026-10-16 01:13:14 +03:00
1 parent 25b19f2d6a
commit a87ee98faa
131 files changed
+15532 -6

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
# Starsync's copy of media_kit_video
Upstream: `media_kit_video` 2.0.1 from pub.dev (media-kit/media-kit, MIT,
see ../LICENSE), vendored because upstream has not released since
December 2025. The `example/` folder is left out.
Patches (apply with `patch -p7` from the package root, as recorded):
- `egl-platform-display.patch`: Flutter >= 3.38 keeps its EGL contexts on
the raster thread, so nothing is current on the platform thread where
the video output is created and media_kit fell back to software
rendering (no shaders, slow). The output now opens Flutter's EGL display
itself (eglGetPlatformDisplayEXT on the GDK X11/Wayland display) with
Flutter's config attributes; on X11 it steps GDK's GLX context aside
while its EGL context is in use (libglvnd refuses EGL on a thread
holding GLX), and releases its context instead of leaving it current.
Written by a friend's agent from a real X11 failure.
To update: copy the new upstream version over this folder (minus
`example/`), re-apply the patches, run the app under X11
(`GDK_BACKEND=x11`) and Wayland.
@@ -0,0 +1,237 @@
diff -ru /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv_orig/linux/include/media_kit_video/video_output.h /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv/linux/include/media_kit_video/video_output.h
--- /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv_orig/linux/include/media_kit_video/video_output.h 2026-10-15 22:51:53.800899923 +0200
+++ /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv/linux/include/media_kit_video/video_output.h 2026-10-15 22:54:01.866997836 +0200
@@ -11,6 +11,7 @@
#include <flutter_linux/flutter_linux.h>
#include <epoxy/egl.h>
+#include <epoxy/glx.h>
#include "mpv/client.h"
#include "mpv/render.h"
@@ -94,6 +95,27 @@
EGLDisplay video_output_get_egl_display(VideoOutput* self);
+// GDK's GLX context of the calling thread, set aside while an EGL context
+// is used there.
+typedef struct {
+ Display* display;
+ GLXDrawable draw;
+ GLXDrawable read;
+ GLXContext context;
+} VideoOutputGlxState;
+
+void video_output_glx_release(VideoOutputGlxState* state);
+
+void video_output_glx_restore(VideoOutputGlxState* state);
+
+// Makes |context| current again, or, when it is EGL_NO_CONTEXT, releases
+// whatever context of |own_display| this thread holds.
+void video_output_restore_egl_context(EGLDisplay own_display,
+ EGLDisplay display,
+ EGLSurface draw,
+ EGLSurface read,
+ EGLContext context);
+
EGLContext video_output_get_egl_context(VideoOutput* self);
EGLSurface video_output_get_egl_surface(VideoOutput* self);
diff -ru /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv_orig/linux/texture_gl.cc /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv/linux/texture_gl.cc
--- /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv_orig/linux/texture_gl.cc 2026-10-15 22:51:53.800854599 +0200
+++ /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv/linux/texture_gl.cc 2026-10-15 22:54:01.866903241 +0200
@@ -64,6 +64,9 @@
TextureGL* self = TEXTURE_GL(object);
VideoOutput* video_output = self->video_output;
+ VideoOutputGlxState glx_state;
+ video_output_glx_release(&glx_state);
+
// Save current context
EGLDisplay current_display = eglGetCurrentDisplay();
EGLContext current_context = eglGetCurrentContext();
@@ -101,10 +104,14 @@
}
// Restore previous context
- eglMakeCurrent(current_display, current_draw, current_read, current_context);
+ video_output_restore_egl_context(egl_display, current_display,
+ current_draw, current_read,
+ current_context);
}
}
+ video_output_glx_restore(&glx_state);
+
self->current_width = 1;
self->current_height = 1;
self->video_output = NULL;
diff -ru /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv_orig/linux/video_output.cc /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv/linux/video_output.cc
--- /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv_orig/linux/video_output.cc 2026-10-15 22:51:53.800808124 +0200
+++ /tmp/claude-1000/-home-funmaker-Desktop-kek-starsync-debug-sync-replay-project/d3b87c25-a0fb-470b-98fb-f17446c5db6d/scratchpad/mkv/linux/video_output.cc 2026-10-15 22:53:45.478604032 +0200
@@ -15,6 +15,74 @@
#include <gdk/gdkwayland.h>
#include <gdk/gdkx.h>
+// Flutter >= 3.38 keeps its EGL contexts on the raster thread, so nothing is
+// current on the platform thread (where |video_output_new| runs). Open the
+// display the way Flutter's FlOpenGLManager does: EGL hands back the same
+// EGLDisplay for the same platform and native display, which the EGLImage
+// shared in |texture_gl_populate_texture| needs. eglGetPlatformDisplayEXT is
+// looked up by hand: epoxy's dispatch for it needs a current display.
+static EGLDisplay flutter_egl_display() {
+ typedef EGLDisplay (*GetPlatformDisplay)(EGLenum, void*, const EGLint*);
+ GetPlatformDisplay get_platform_display =
+ (GetPlatformDisplay)eglGetProcAddress("eglGetPlatformDisplayEXT");
+ if (get_platform_display == NULL) {
+ return EGL_NO_DISPLAY;
+ }
+ EGLDisplay egl_display = EGL_NO_DISPLAY;
+ GdkDisplay* display = gdk_display_get_default();
+ if (GDK_IS_WAYLAND_DISPLAY(display)) {
+ egl_display = get_platform_display(
+ EGL_PLATFORM_WAYLAND_EXT, gdk_wayland_display_get_wl_display(display),
+ NULL);
+ } else if (GDK_IS_X11_DISPLAY(display)) {
+ egl_display = get_platform_display(
+ EGL_PLATFORM_X11_EXT, gdk_x11_display_get_xdisplay(display), NULL);
+ }
+ if (egl_display != EGL_NO_DISPLAY &&
+ !eglInitialize(egl_display, NULL, NULL)) {
+ egl_display = EGL_NO_DISPLAY;
+ }
+ return egl_display;
+}
+
+void video_output_glx_release(VideoOutputGlxState* state) {
+ *state = VideoOutputGlxState{};
+ // On X11, GDK draws Flutter's view with GLX on the platform thread, and
+ // libglvnd refuses an EGL context (EGL_BAD_ACCESS) on a thread holding a
+ // GLX one: step it aside while ours is in use.
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()) ||
+ glXGetCurrentContext() == NULL) {
+ return;
+ }
+ state->display = glXGetCurrentDisplay();
+ state->draw = glXGetCurrentDrawable();
+ state->read = glXGetCurrentReadDrawable();
+ state->context = glXGetCurrentContext();
+ glXMakeContextCurrent(state->display, None, None, NULL);
+}
+
+void video_output_glx_restore(VideoOutputGlxState* state) {
+ if (state->context != NULL) {
+ glXMakeContextCurrent(state->display, state->draw, state->read,
+ state->context);
+ }
+}
+
+void video_output_restore_egl_context(EGLDisplay own_display,
+ EGLDisplay display,
+ EGLSurface draw,
+ EGLSurface read,
+ EGLContext context) {
+ // Nothing was current (the platform thread): release ours, or it stays
+ // current here and the raster thread cannot make it current (BadAccess).
+ if (context == EGL_NO_CONTEXT) {
+ eglMakeCurrent(own_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ } else {
+ eglMakeCurrent(display, draw, read, context);
+ }
+}
+
struct _VideoOutput {
GObject parent_instance;
TextureGL* texture_gl;
@@ -59,6 +127,8 @@
// Free mpv_render_context with our own isolated EGL context
if (self->render_context != NULL) {
+ VideoOutputGlxState glx_state;
+ video_output_glx_release(&glx_state);
if (self->egl_context != EGL_NO_CONTEXT) {
eglMakeCurrent(self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, self->egl_context);
}
@@ -66,9 +136,10 @@
self->render_context = NULL;
// Restore Flutter's context
- if (flutter_context != EGL_NO_CONTEXT) {
- eglMakeCurrent(current_display, flutter_draw_surface, flutter_read_surface, flutter_context);
- }
+ video_output_restore_egl_context(self->egl_display, current_display,
+ flutter_draw_surface,
+ flutter_read_surface, flutter_context);
+ video_output_glx_restore(&glx_state);
}
// Clean up EGL resources
@@ -140,14 +211,20 @@
// mpv_set_option_string(self->handle, "video-timing-offset", "0");
gboolean hardware_acceleration_supported = FALSE;
if (self->configuration.enable_hardware_acceleration) {
+ VideoOutputGlxState glx_state;
+ video_output_glx_release(&glx_state);
// Get Flutter's current EGL display (DO NOT share context)
EGLDisplay flutter_display = eglGetCurrentDisplay();
EGLContext flutter_context = eglGetCurrentContext();
EGLSurface flutter_draw_surface = eglGetCurrentSurface(EGL_DRAW);
EGLSurface flutter_read_surface = eglGetCurrentSurface(EGL_READ);
- if (flutter_display != EGL_NO_DISPLAY && flutter_context != EGL_NO_CONTEXT) {
- self->egl_display = flutter_display;
+ EGLDisplay egl_display = flutter_context != EGL_NO_CONTEXT
+ ? flutter_display
+ : flutter_egl_display();
+
+ if (egl_display != EGL_NO_DISPLAY) {
+ self->egl_display = egl_display;
// Bind OpenGL ES API (Flutter uses OpenGL ES on Linux)
eglBindAPI(EGL_OPENGL_ES_API);
@@ -156,7 +233,21 @@
EGLConfig config = NULL;
EGLint config_id = 0;
- if (eglQueryContext(self->egl_display, flutter_context, EGL_CONFIG_ID, &config_id)) {
+ if (flutter_context == EGL_NO_CONTEXT) {
+ // The attributes FlOpenGLManager chooses Flutter's config with.
+ EGLint num_configs = 0;
+ const EGLint config_attribs[] = {
+ EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8,
+ EGL_NONE,
+ };
+ if (!eglChooseConfig(self->egl_display, config_attribs, &config, 1,
+ &num_configs) ||
+ num_configs == 0) {
+ g_printerr("media_kit: VideoOutput: Failed to choose an EGL config.\n");
+ config = NULL;
+ }
+ } else if (eglQueryContext(self->egl_display, flutter_context, EGL_CONFIG_ID, &config_id)) {
g_print("media_kit: VideoOutput: Flutter's EGL config ID: %d\n", config_id);
// Get Flutter's exact config
@@ -239,7 +330,9 @@
}
// Restore Flutter's context
- eglMakeCurrent(flutter_display, flutter_draw_surface, flutter_read_surface, flutter_context);
+ video_output_restore_egl_context(
+ self->egl_display, flutter_display, flutter_draw_surface,
+ flutter_read_surface, flutter_context);
} else {
g_printerr("media_kit: VideoOutput: Failed to make isolated EGL context current. Error: 0x%x\n", eglGetError());
}
@@ -250,8 +343,9 @@
g_printerr("media_kit: VideoOutput: Could not obtain Flutter's EGL config.\n");
}
} else {
- g_printerr("media_kit: VideoOutput: EGL display or context is invalid.\n");
+ g_printerr("media_kit: VideoOutput: EGL display is invalid.\n");
}
+ video_output_glx_restore(&glx_state);
}
#ifdef MPV_RENDER_API_TYPE_SW
if (!hardware_acceleration_supported) {