From 3d9c6b203f1090bc6f61b92fd746c909cdcaf9f7 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:20:08 +0100 Subject: [PATCH] fix: isolated EGL context for mpv rendering --- linux/runner/mpv/mpv_player.cc | 155 +++++++++++++++-------- linux/runner/mpv/mpv_player.h | 11 ++ linux/runner/mpv/mpv_plugin.cc | 12 +- linux/runner/mpv/mpv_texture.cc | 213 +++++++++++++++++++++++--------- 4 files changed, 276 insertions(+), 115 deletions(-) diff --git a/linux/runner/mpv/mpv_player.cc b/linux/runner/mpv/mpv_player.cc index 1103a3e9..4e6a1f05 100644 --- a/linux/runner/mpv/mpv_player.cc +++ b/linux/runner/mpv/mpv_player.cc @@ -3,7 +3,6 @@ #include #include #include -#include #include #ifdef GDK_WINDOWING_X11 #include @@ -15,32 +14,9 @@ #include #include -// Static helper to get proc address - must be defined outside the namespace -// to have the correct function signature. +// Flutter on Linux uses EGL (OpenGL ES) for both X11 and Wayland. static void* get_opengl_proc_address(void* ctx, const char* name) { (void)ctx; - - // Detect the actual display backend at runtime. On most Linux distros both - // GDK_WINDOWING_WAYLAND and GDK_WINDOWING_X11 are defined at compile time, - // but only one is active at runtime. The previous check (epoxy_has_egl()) - // was wrong because it returns true whenever the EGL library is loadable, - // even on X11 sessions with NVIDIA drivers — causing mpv to get EGL function - // pointers for a GLX context and producing corrupted/blank video. - GdkDisplay* display = gdk_display_get_default(); - -#ifdef GDK_WINDOWING_WAYLAND - if (GDK_IS_WAYLAND_DISPLAY(display)) { - return reinterpret_cast(eglGetProcAddress(name)); - } -#endif -#ifdef GDK_WINDOWING_X11 - if (GDK_IS_X11_DISPLAY(display)) { - return reinterpret_cast(glXGetProcAddressARB( - reinterpret_cast(name))); - } -#endif - - // Fallback for non-X11/non-Wayland (rare) return reinterpret_cast(eglGetProcAddress(name)); } @@ -69,7 +45,6 @@ bool MpvPlayer::Initialize() { // Configure mpv for embedded playback. mpv_set_option_string(mpv_, "vo", "libmpv"); - mpv_set_option_string(mpv_, "gpu-api", "opengl"); mpv_set_option_string(mpv_, "hwdec", "auto"); mpv_set_option_string(mpv_, "keep-open", "yes"); @@ -112,6 +87,54 @@ bool MpvPlayer::InitRenderContext() { return false; } + // Capture Flutter's EGL display and create an isolated EGL context. + // Flutter on Linux uses EGL for both X11 and Wayland. Running mpv in + // an isolated context prevents OpenGL state pollution between mpv and + // Flutter, which caused corrupted/blank video on some drivers. + EGLDisplay flutter_display = eglGetCurrentDisplay(); + EGLContext flutter_context = eglGetCurrentContext(); + + if (flutter_display == EGL_NO_DISPLAY || flutter_context == EGL_NO_CONTEXT) { + g_warning("MPV: No EGL context available"); + return false; + } + + egl_display_ = flutter_display; + + // Query Flutter's EGL config and reuse it for compatibility + EGLConfig config = nullptr; + EGLint config_id = 0; + + if (!eglQueryContext(egl_display_, flutter_context, EGL_CONFIG_ID, &config_id)) { + g_warning("MPV: Failed to query Flutter's EGL config ID"); + return false; + } + + EGLint num_configs = 0; + EGLint config_attribs[] = { EGL_CONFIG_ID, config_id, EGL_NONE }; + if (!eglChooseConfig(egl_display_, config_attribs, &config, 1, &num_configs) || num_configs == 0) { + g_warning("MPV: Failed to get Flutter's EGL config"); + return false; + } + + // Create isolated EGL context (NOT shared with Flutter) to prevent + // GL state pollution + eglBindAPI(EGL_OPENGL_ES_API); + EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE, + }; + egl_context_ = eglCreateContext(egl_display_, config, EGL_NO_CONTEXT, context_attribs); + if (egl_context_ == EGL_NO_CONTEXT) { + g_warning("MPV: Failed to create isolated EGL context: 0x%x", eglGetError()); + return false; + } + + // Make the isolated context current for mpv render context creation + EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW); + EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ); + eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context_); + // Set up OpenGL parameters for mpv. mpv_opengl_init_params gl_init_params{ .get_proc_address = get_opengl_proc_address, @@ -122,20 +145,42 @@ bool MpvPlayer::InitRenderContext() { {MPV_RENDER_PARAM_API_TYPE, const_cast(MPV_RENDER_API_TYPE_OPENGL)}, {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params}, + {MPV_RENDER_PARAM_INVALID, nullptr}, // slot for X11/Wayland display {MPV_RENDER_PARAM_INVALID, nullptr}, }; + // Pass X11/Wayland display for VAAPI hardware acceleration + GdkDisplay* gdk_display = gdk_display_get_default(); +#ifdef GDK_WINDOWING_WAYLAND + if (GDK_IS_WAYLAND_DISPLAY(gdk_display)) { + params[2].type = MPV_RENDER_PARAM_WL_DISPLAY; + params[2].data = gdk_wayland_display_get_wl_display(gdk_display); + } +#endif +#ifdef GDK_WINDOWING_X11 + if (GDK_IS_X11_DISPLAY(gdk_display)) { + params[2].type = MPV_RENDER_PARAM_X11_DISPLAY; + params[2].data = gdk_x11_display_get_xdisplay(gdk_display); + } +#endif + int err = mpv_render_context_create(&mpv_gl_, mpv_, params); + + // Restore Flutter's context + eglMakeCurrent(egl_display_, flutter_draw, flutter_read, flutter_context); + if (err < 0) { g_warning("MPV: mpv_render_context_create() failed: %s", mpv_error_string(err)); + eglDestroyContext(egl_display_, egl_context_); + egl_context_ = EGL_NO_CONTEXT; return false; } // Set up render update callback. mpv_render_context_set_update_callback(mpv_gl_, OnMpvRenderUpdate, this); - g_message("MPV: Render context created successfully"); + g_message("MPV: Render context created with isolated EGL context"); return true; } @@ -181,21 +226,54 @@ void MpvPlayer::Dispose() { // Running these off the main thread prevents stalling the GLib main loop. auto* gl = mpv_gl_; auto* handle = mpv_; + auto egl_display = egl_display_; + auto egl_context = egl_context_; mpv_gl_ = nullptr; mpv_ = nullptr; + egl_display_ = EGL_NO_DISPLAY; + egl_context_ = EGL_NO_CONTEXT; - std::thread([gl, handle]() { + std::thread([gl, handle, egl_display, egl_context]() { if (gl) { + // mpv render context must be freed with its EGL context current + if (egl_context != EGL_NO_CONTEXT) { + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context); + } mpv_render_context_free(gl); } if (handle) { mpv_terminate_destroy(handle); } + if (egl_context != EGL_NO_CONTEXT) { + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(egl_display, egl_context); + } }).detach(); observed_properties_.clear(); } +void MpvPlayer::Render(int width, int height, int fbo) { + if (disposed_ || !mpv_gl_) return; + + mpv_opengl_fbo mpv_fbo{ + .fbo = fbo, + .w = width, + .h = height, + .internal_format = 0, + }; + + int flip_y = 0; + + mpv_render_param params[] = { + {MPV_RENDER_PARAM_OPENGL_FBO, &mpv_fbo}, + {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, + {MPV_RENDER_PARAM_INVALID, nullptr}, + }; + + mpv_render_context_render(mpv_gl_, params); +} + void MpvPlayer::Command(const std::vector& args) { if (disposed_ || !mpv_) return; @@ -287,27 +365,6 @@ void MpvPlayer::ObserveProperty(const std::string& name, mpv_observe_property(mpv_, userdata, name.c_str(), mpv_fmt); } -void MpvPlayer::Render(int width, int height, int fbo) { - if (disposed_ || !mpv_gl_) return; - - mpv_opengl_fbo mpv_fbo{ - .fbo = fbo, - .w = width, - .h = height, - .internal_format = GL_RGBA8, - }; - - int flip_y = 0; - - mpv_render_param params[] = { - {MPV_RENDER_PARAM_OPENGL_FBO, &mpv_fbo}, - {MPV_RENDER_PARAM_FLIP_Y, &flip_y}, - {MPV_RENDER_PARAM_INVALID, nullptr}, - }; - - mpv_render_context_render(mpv_gl_, params); -} - void MpvPlayer::ReportMouseMove(int x, int y) { if (disposed_ || !mpv_) return; std::string x_str = std::to_string(x); diff --git a/linux/runner/mpv/mpv_player.h b/linux/runner/mpv/mpv_player.h index 2c3f988a..d63747d1 100644 --- a/linux/runner/mpv/mpv_player.h +++ b/linux/runner/mpv/mpv_player.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,12 @@ class MpvPlayer { /// Returns true if the render context has been created. bool HasRenderContext() const { return mpv_gl_ != nullptr; } + /// Returns the isolated EGL display used for mpv rendering. + EGLDisplay GetEglDisplay() const { return egl_display_; } + + /// Returns the isolated EGL context used for mpv rendering. + EGLContext GetEglContext() const { return egl_context_; } + /// Disposes mpv and releases resources. void Dispose(); @@ -126,6 +133,10 @@ class MpvPlayer { mpv_handle* mpv_ = nullptr; mpv_render_context* mpv_gl_ = nullptr; + // Isolated EGL context for mpv rendering (not shared with Flutter) + EGLDisplay egl_display_ = EGL_NO_DISPLAY; + EGLContext egl_context_ = EGL_NO_CONTEXT; + std::atomic needs_redraw_{false}; std::atomic disposed_{false}; EventCallback event_callback_; diff --git a/linux/runner/mpv/mpv_plugin.cc b/linux/runner/mpv/mpv_plugin.cc index df0fadd5..71683dc6 100644 --- a/linux/runner/mpv/mpv_plugin.cc +++ b/linux/runner/mpv/mpv_plugin.cc @@ -38,11 +38,8 @@ static void send_event(MpvPlugin* self, FlValue* event) { static void mpv_plugin_dispose(GObject* object) { MpvPlugin* self = MPV_PLUGIN(object); - if (self->player) { - self->player->Dispose(); - self->player.reset(); - } - + // Texture must be disposed BEFORE player — mpv_texture_dispose needs + // the player's EGL context to clean up GL resources. if (self->texture) { mpv_texture_dispose(self->texture); if (self->texture_registrar) { @@ -53,6 +50,11 @@ static void mpv_plugin_dispose(GObject* object) { self->texture = nullptr; } + if (self->player) { + self->player->Dispose(); + self->player.reset(); + } + g_clear_object(&self->method_channel); g_clear_object(&self->event_channel); g_clear_object(&self->registrar); diff --git a/linux/runner/mpv/mpv_texture.cc b/linux/runner/mpv/mpv_texture.cc index 6ec8b717..35fead56 100644 --- a/linux/runner/mpv/mpv_texture.cc +++ b/linux/runner/mpv/mpv_texture.cc @@ -1,6 +1,26 @@ #include "mpv_texture.h" #include +#include + +// EGLImage extension function pointers +typedef EGLImageKHR (*PFNEGLCREATEIMAGEKHRPROC)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint*); +typedef EGLBoolean (*PFNEGLDESTROYIMAGEKHRPROC)(EGLDisplay, EGLImageKHR); +typedef void (*PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)(GLenum, GLeglImageOES); + +static PFNEGLCREATEIMAGEKHRPROC _eglCreateImageKHR = nullptr; +static PFNEGLDESTROYIMAGEKHRPROC _eglDestroyImageKHR = nullptr; +static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC _glEGLImageTargetTexture2DOES = nullptr; + +static void init_egl_image_extensions() { + static bool initialized = false; + if (!initialized) { + _eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); + _eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); + _glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); + initialized = true; + } +} struct _MpvTexture { FlTextureGL parent_instance; @@ -9,44 +29,96 @@ struct _MpvTexture { FlTextureRegistrar* registrar; // not owned FlView* view; // not owned, for querying allocation size - GLuint fbo; - GLuint texture; + // mpv's FBO and texture (owned by mpv's isolated EGL context) + GLuint mpv_fbo; + GLuint mpv_texture; + + // Flutter's texture (owned by Flutter's EGL context) + GLuint flutter_texture; + + // EGLImage bridging the two contexts + EGLImageKHR egl_image; + int32_t width; int32_t height; }; G_DEFINE_TYPE(MpvTexture, mpv_texture, fl_texture_gl_get_type()) -static void ensure_fbo(MpvTexture* self, int32_t w, int32_t h) { - if (self->fbo != 0 && self->width == w && self->height == h) { +// Create/resize the FBO in mpv's context and the shared EGLImage + Flutter texture. +static void ensure_textures(MpvTexture* self, int32_t w, int32_t h) { + if (self->mpv_fbo != 0 && self->width == w && self->height == h) { return; } - // Delete old resources. - if (self->fbo != 0) { - glDeleteFramebuffers(1, &self->fbo); - self->fbo = 0; + EGLDisplay egl_display = self->player->GetEglDisplay(); + EGLContext egl_context = self->player->GetEglContext(); + + // Save Flutter's current EGL state + EGLDisplay flutter_display = eglGetCurrentDisplay(); + EGLContext flutter_context = eglGetCurrentContext(); + EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW); + EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ); + + // --- Switch to mpv's isolated context --- + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context); + + // Clean up previous mpv resources + if (self->mpv_texture != 0) { + glDeleteTextures(1, &self->mpv_texture); } - if (self->texture != 0) { - glDeleteTextures(1, &self->texture); - self->texture = 0; + if (self->mpv_fbo != 0) { + glDeleteFramebuffers(1, &self->mpv_fbo); + } + if (self->egl_image != EGL_NO_IMAGE_KHR) { + _eglDestroyImageKHR(egl_display, self->egl_image); } self->width = w; self->height = h; - glGenTextures(1, &self->texture); - glBindTexture(GL_TEXTURE_2D, self->texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, - GL_UNSIGNED_BYTE, nullptr); + // Create mpv's texture and FBO + glGenTextures(1, &self->mpv_texture); + glBindTexture(GL_TEXTURE_2D, self->mpv_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + + glGenFramebuffers(1, &self->mpv_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, self->mpv_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, self->mpv_texture, 0); + + // Create EGLImage from mpv's texture for cross-context sharing + EGLint image_attribs[] = { EGL_NONE }; + self->egl_image = _eglCreateImageKHR( + egl_display, egl_context, EGL_GL_TEXTURE_2D_KHR, + (EGLClientBuffer)(uintptr_t)self->mpv_texture, image_attribs); - glGenFramebuffers(1, &self->fbo); - glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - self->texture, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glFlush(); + + // --- Switch back to Flutter's context --- + eglMakeCurrent(flutter_display, flutter_draw, flutter_read, flutter_context); + + // Clean up previous Flutter texture + if (self->flutter_texture != 0) { + glDeleteTextures(1, &self->flutter_texture); + } + + // Create Flutter's texture backed by the EGLImage + glGenTextures(1, &self->flutter_texture); + glBindTexture(GL_TEXTURE_2D, self->flutter_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + _glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, self->egl_image); + glBindTexture(GL_TEXTURE_2D, 0); } static gboolean mpv_texture_populate(FlTextureGL* gl_texture, @@ -82,47 +154,31 @@ static gboolean mpv_texture_populate(FlTextureGL* gl_texture, return FALSE; } - ensure_fbo(self, w, h); + ensure_textures(self, w, h); - // Save GL state that mpv may clobber. - GLint prev_viewport[4]; - GLint prev_scissor_box[4]; - GLboolean prev_blend, prev_scissor_test; - GLint prev_blend_src, prev_blend_dst; - GLint prev_fbo; + // Save Flutter's current EGL state + EGLDisplay flutter_display = eglGetCurrentDisplay(); + EGLContext flutter_context = eglGetCurrentContext(); + EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW); + EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ); - glGetIntegerv(GL_VIEWPORT, prev_viewport); - glGetIntegerv(GL_SCISSOR_BOX, prev_scissor_box); - glGetBooleanv(GL_BLEND, &prev_blend); - glGetBooleanv(GL_SCISSOR_TEST, &prev_scissor_test); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &prev_blend_src); - glGetIntegerv(GL_BLEND_DST_ALPHA, &prev_blend_dst); - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo); + // Switch to mpv's isolated context for rendering + EGLDisplay egl_display = self->player->GetEglDisplay(); + EGLContext egl_context = self->player->GetEglContext(); + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context); - // Render mpv into our FBO. - glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); - glViewport(0, 0, w, h); + // Render mpv into its FBO + glBindFramebuffer(GL_FRAMEBUFFER, self->mpv_fbo); self->player->ClearRedrawFlag(); - self->player->Render(w, h, static_cast(self->fbo)); + self->player->Render(w, h, static_cast(self->mpv_fbo)); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glFlush(); - // Restore GL state. - glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], - prev_viewport[3]); - glScissor(prev_scissor_box[0], prev_scissor_box[1], prev_scissor_box[2], - prev_scissor_box[3]); - if (prev_blend) - glEnable(GL_BLEND); - else - glDisable(GL_BLEND); - if (prev_scissor_test) - glEnable(GL_SCISSOR_TEST); - else - glDisable(GL_SCISSOR_TEST); - glBlendFunc(prev_blend_src, prev_blend_dst); - glBindFramebuffer(GL_FRAMEBUFFER, prev_fbo); + // Restore Flutter's context + eglMakeCurrent(flutter_display, flutter_draw, flutter_read, flutter_context); *target = GL_TEXTURE_2D; - *name = self->texture; + *name = self->flutter_texture; *width = static_cast(w); *height = static_cast(h); @@ -137,8 +193,10 @@ static void mpv_texture_init(MpvTexture* self) { self->player = nullptr; self->registrar = nullptr; self->view = nullptr; - self->fbo = 0; - self->texture = 0; + self->mpv_fbo = 0; + self->mpv_texture = 0; + self->flutter_texture = 0; + self->egl_image = EGL_NO_IMAGE_KHR; self->width = 0; self->height = 0; } @@ -146,6 +204,7 @@ static void mpv_texture_init(MpvTexture* self) { MpvTexture* mpv_texture_new(mpv::MpvPlayer* player, FlTextureRegistrar* registrar, FlView* view) { + init_egl_image_extensions(); MpvTexture* self = MPV_TEXTURE(g_object_new(MPV_TEXTURE_TYPE, nullptr)); self->player = player; self->registrar = registrar; @@ -163,13 +222,45 @@ void mpv_texture_mark_frame_available(MpvTexture* self) { void mpv_texture_dispose(MpvTexture* self) { if (!self) return; - if (self->fbo != 0) { - glDeleteFramebuffers(1, &self->fbo); - self->fbo = 0; + EGLDisplay egl_display = EGL_NO_DISPLAY; + EGLContext egl_context = EGL_NO_CONTEXT; + + if (self->player) { + egl_display = self->player->GetEglDisplay(); + egl_context = self->player->GetEglContext(); } - if (self->texture != 0) { - glDeleteTextures(1, &self->texture); - self->texture = 0; + + // Clean up Flutter's texture (in Flutter's current context) + if (self->flutter_texture != 0) { + glDeleteTextures(1, &self->flutter_texture); + self->flutter_texture = 0; + } + + // Clean up EGLImage + if (self->egl_image != EGL_NO_IMAGE_KHR && egl_display != EGL_NO_DISPLAY) { + _eglDestroyImageKHR(egl_display, self->egl_image); + self->egl_image = EGL_NO_IMAGE_KHR; + } + + // Clean up mpv's GL resources in mpv's context + if (egl_context != EGL_NO_CONTEXT) { + EGLDisplay cur_display = eglGetCurrentDisplay(); + EGLContext cur_context = eglGetCurrentContext(); + EGLSurface cur_draw = eglGetCurrentSurface(EGL_DRAW); + EGLSurface cur_read = eglGetCurrentSurface(EGL_READ); + + eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context); + + if (self->mpv_texture != 0) { + glDeleteTextures(1, &self->mpv_texture); + self->mpv_texture = 0; + } + if (self->mpv_fbo != 0) { + glDeleteFramebuffers(1, &self->mpv_fbo); + self->mpv_fbo = 0; + } + + eglMakeCurrent(cur_display, cur_draw, cur_read, cur_context); } self->player = nullptr;