fix: isolated EGL context for mpv rendering

This commit is contained in:
edde746
2026-02-23 18:20:08 +01:00
parent e23f472d78
commit 3d9c6b203f
4 changed files with 276 additions and 115 deletions
+106 -49
View File
@@ -3,7 +3,6 @@
#include <flutter_linux/flutter_linux.h> #include <flutter_linux/flutter_linux.h>
#include <epoxy/gl.h> #include <epoxy/gl.h>
#include <epoxy/egl.h> #include <epoxy/egl.h>
#include <epoxy/glx.h>
#include <gdk/gdk.h> #include <gdk/gdk.h>
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
@@ -15,32 +14,9 @@
#include <cstring> #include <cstring>
#include <string> #include <string>
// Static helper to get proc address - must be defined outside the namespace // Flutter on Linux uses EGL (OpenGL ES) for both X11 and Wayland.
// to have the correct function signature.
static void* get_opengl_proc_address(void* ctx, const char* name) { static void* get_opengl_proc_address(void* ctx, const char* name) {
(void)ctx; (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<void*>(eglGetProcAddress(name));
}
#endif
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY(display)) {
return reinterpret_cast<void*>(glXGetProcAddressARB(
reinterpret_cast<const GLubyte*>(name)));
}
#endif
// Fallback for non-X11/non-Wayland (rare)
return reinterpret_cast<void*>(eglGetProcAddress(name)); return reinterpret_cast<void*>(eglGetProcAddress(name));
} }
@@ -69,7 +45,6 @@ bool MpvPlayer::Initialize() {
// Configure mpv for embedded playback. // Configure mpv for embedded playback.
mpv_set_option_string(mpv_, "vo", "libmpv"); 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_, "hwdec", "auto");
mpv_set_option_string(mpv_, "keep-open", "yes"); mpv_set_option_string(mpv_, "keep-open", "yes");
@@ -112,6 +87,54 @@ bool MpvPlayer::InitRenderContext() {
return false; 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. // Set up OpenGL parameters for mpv.
mpv_opengl_init_params gl_init_params{ mpv_opengl_init_params gl_init_params{
.get_proc_address = get_opengl_proc_address, .get_proc_address = get_opengl_proc_address,
@@ -122,20 +145,42 @@ bool MpvPlayer::InitRenderContext() {
{MPV_RENDER_PARAM_API_TYPE, {MPV_RENDER_PARAM_API_TYPE,
const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL)}, const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL)},
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params}, {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params},
{MPV_RENDER_PARAM_INVALID, nullptr}, // slot for X11/Wayland display
{MPV_RENDER_PARAM_INVALID, nullptr}, {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); 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) { if (err < 0) {
g_warning("MPV: mpv_render_context_create() failed: %s", g_warning("MPV: mpv_render_context_create() failed: %s",
mpv_error_string(err)); mpv_error_string(err));
eglDestroyContext(egl_display_, egl_context_);
egl_context_ = EGL_NO_CONTEXT;
return false; return false;
} }
// Set up render update callback. // Set up render update callback.
mpv_render_context_set_update_callback(mpv_gl_, OnMpvRenderUpdate, this); 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; return true;
} }
@@ -181,21 +226,54 @@ void MpvPlayer::Dispose() {
// Running these off the main thread prevents stalling the GLib main loop. // Running these off the main thread prevents stalling the GLib main loop.
auto* gl = mpv_gl_; auto* gl = mpv_gl_;
auto* handle = mpv_; auto* handle = mpv_;
auto egl_display = egl_display_;
auto egl_context = egl_context_;
mpv_gl_ = nullptr; mpv_gl_ = nullptr;
mpv_ = 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) { 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); mpv_render_context_free(gl);
} }
if (handle) { if (handle) {
mpv_terminate_destroy(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(); }).detach();
observed_properties_.clear(); 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<std::string>& args) { void MpvPlayer::Command(const std::vector<std::string>& args) {
if (disposed_ || !mpv_) return; 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); 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) { void MpvPlayer::ReportMouseMove(int x, int y) {
if (disposed_ || !mpv_) return; if (disposed_ || !mpv_) return;
std::string x_str = std::to_string(x); std::string x_str = std::to_string(x);
+11
View File
@@ -6,6 +6,7 @@
#include <mpv/render_gl.h> #include <mpv/render_gl.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <epoxy/gl.h> #include <epoxy/gl.h>
#include <epoxy/egl.h>
#include <atomic> #include <atomic>
#include <functional> #include <functional>
@@ -49,6 +50,12 @@ class MpvPlayer {
/// Returns true if the render context has been created. /// Returns true if the render context has been created.
bool HasRenderContext() const { return mpv_gl_ != nullptr; } 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. /// Disposes mpv and releases resources.
void Dispose(); void Dispose();
@@ -126,6 +133,10 @@ class MpvPlayer {
mpv_handle* mpv_ = nullptr; mpv_handle* mpv_ = nullptr;
mpv_render_context* mpv_gl_ = 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<bool> needs_redraw_{false}; std::atomic<bool> needs_redraw_{false};
std::atomic<bool> disposed_{false}; std::atomic<bool> disposed_{false};
EventCallback event_callback_; EventCallback event_callback_;
+7 -5
View File
@@ -38,11 +38,8 @@ static void send_event(MpvPlugin* self, FlValue* event) {
static void mpv_plugin_dispose(GObject* object) { static void mpv_plugin_dispose(GObject* object) {
MpvPlugin* self = MPV_PLUGIN(object); MpvPlugin* self = MPV_PLUGIN(object);
if (self->player) { // Texture must be disposed BEFORE player — mpv_texture_dispose needs
self->player->Dispose(); // the player's EGL context to clean up GL resources.
self->player.reset();
}
if (self->texture) { if (self->texture) {
mpv_texture_dispose(self->texture); mpv_texture_dispose(self->texture);
if (self->texture_registrar) { if (self->texture_registrar) {
@@ -53,6 +50,11 @@ static void mpv_plugin_dispose(GObject* object) {
self->texture = nullptr; self->texture = nullptr;
} }
if (self->player) {
self->player->Dispose();
self->player.reset();
}
g_clear_object(&self->method_channel); g_clear_object(&self->method_channel);
g_clear_object(&self->event_channel); g_clear_object(&self->event_channel);
g_clear_object(&self->registrar); g_clear_object(&self->registrar);
+152 -61
View File
@@ -1,6 +1,26 @@
#include "mpv_texture.h" #include "mpv_texture.h"
#include <epoxy/gl.h> #include <epoxy/gl.h>
#include <epoxy/egl.h>
// 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 { struct _MpvTexture {
FlTextureGL parent_instance; FlTextureGL parent_instance;
@@ -9,44 +29,96 @@ struct _MpvTexture {
FlTextureRegistrar* registrar; // not owned FlTextureRegistrar* registrar; // not owned
FlView* view; // not owned, for querying allocation size FlView* view; // not owned, for querying allocation size
GLuint fbo; // mpv's FBO and texture (owned by mpv's isolated EGL context)
GLuint texture; 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 width;
int32_t height; int32_t height;
}; };
G_DEFINE_TYPE(MpvTexture, mpv_texture, fl_texture_gl_get_type()) G_DEFINE_TYPE(MpvTexture, mpv_texture, fl_texture_gl_get_type())
static void ensure_fbo(MpvTexture* self, int32_t w, int32_t h) { // Create/resize the FBO in mpv's context and the shared EGLImage + Flutter texture.
if (self->fbo != 0 && self->width == w && self->height == h) { static void ensure_textures(MpvTexture* self, int32_t w, int32_t h) {
if (self->mpv_fbo != 0 && self->width == w && self->height == h) {
return; return;
} }
// Delete old resources. EGLDisplay egl_display = self->player->GetEglDisplay();
if (self->fbo != 0) { EGLContext egl_context = self->player->GetEglContext();
glDeleteFramebuffers(1, &self->fbo);
self->fbo = 0; // 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) { if (self->mpv_fbo != 0) {
glDeleteTextures(1, &self->texture); glDeleteFramebuffers(1, &self->mpv_fbo);
self->texture = 0; }
if (self->egl_image != EGL_NO_IMAGE_KHR) {
_eglDestroyImageKHR(egl_display, self->egl_image);
} }
self->width = w; self->width = w;
self->height = h; self->height = h;
glGenTextures(1, &self->texture); // Create mpv's texture and FBO
glBindTexture(GL_TEXTURE_2D, self->texture); glGenTextures(1, &self->mpv_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, glBindTexture(GL_TEXTURE_2D, self->mpv_texture);
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 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_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); 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, static gboolean mpv_texture_populate(FlTextureGL* gl_texture,
@@ -82,47 +154,31 @@ static gboolean mpv_texture_populate(FlTextureGL* gl_texture,
return FALSE; return FALSE;
} }
ensure_fbo(self, w, h); ensure_textures(self, w, h);
// Save GL state that mpv may clobber. // Save Flutter's current EGL state
GLint prev_viewport[4]; EGLDisplay flutter_display = eglGetCurrentDisplay();
GLint prev_scissor_box[4]; EGLContext flutter_context = eglGetCurrentContext();
GLboolean prev_blend, prev_scissor_test; EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW);
GLint prev_blend_src, prev_blend_dst; EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ);
GLint prev_fbo;
glGetIntegerv(GL_VIEWPORT, prev_viewport); // Switch to mpv's isolated context for rendering
glGetIntegerv(GL_SCISSOR_BOX, prev_scissor_box); EGLDisplay egl_display = self->player->GetEglDisplay();
glGetBooleanv(GL_BLEND, &prev_blend); EGLContext egl_context = self->player->GetEglContext();
glGetBooleanv(GL_SCISSOR_TEST, &prev_scissor_test); eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
glGetIntegerv(GL_BLEND_SRC_ALPHA, &prev_blend_src);
glGetIntegerv(GL_BLEND_DST_ALPHA, &prev_blend_dst);
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
// Render mpv into our FBO. // Render mpv into its FBO
glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); glBindFramebuffer(GL_FRAMEBUFFER, self->mpv_fbo);
glViewport(0, 0, w, h);
self->player->ClearRedrawFlag(); self->player->ClearRedrawFlag();
self->player->Render(w, h, static_cast<int>(self->fbo)); self->player->Render(w, h, static_cast<int>(self->mpv_fbo));
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glFlush();
// Restore GL state. // Restore Flutter's context
glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], eglMakeCurrent(flutter_display, flutter_draw, flutter_read, flutter_context);
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);
*target = GL_TEXTURE_2D; *target = GL_TEXTURE_2D;
*name = self->texture; *name = self->flutter_texture;
*width = static_cast<uint32_t>(w); *width = static_cast<uint32_t>(w);
*height = static_cast<uint32_t>(h); *height = static_cast<uint32_t>(h);
@@ -137,8 +193,10 @@ static void mpv_texture_init(MpvTexture* self) {
self->player = nullptr; self->player = nullptr;
self->registrar = nullptr; self->registrar = nullptr;
self->view = nullptr; self->view = nullptr;
self->fbo = 0; self->mpv_fbo = 0;
self->texture = 0; self->mpv_texture = 0;
self->flutter_texture = 0;
self->egl_image = EGL_NO_IMAGE_KHR;
self->width = 0; self->width = 0;
self->height = 0; self->height = 0;
} }
@@ -146,6 +204,7 @@ static void mpv_texture_init(MpvTexture* self) {
MpvTexture* mpv_texture_new(mpv::MpvPlayer* player, MpvTexture* mpv_texture_new(mpv::MpvPlayer* player,
FlTextureRegistrar* registrar, FlTextureRegistrar* registrar,
FlView* view) { FlView* view) {
init_egl_image_extensions();
MpvTexture* self = MPV_TEXTURE(g_object_new(MPV_TEXTURE_TYPE, nullptr)); MpvTexture* self = MPV_TEXTURE(g_object_new(MPV_TEXTURE_TYPE, nullptr));
self->player = player; self->player = player;
self->registrar = registrar; self->registrar = registrar;
@@ -163,13 +222,45 @@ void mpv_texture_mark_frame_available(MpvTexture* self) {
void mpv_texture_dispose(MpvTexture* self) { void mpv_texture_dispose(MpvTexture* self) {
if (!self) return; if (!self) return;
if (self->fbo != 0) { EGLDisplay egl_display = EGL_NO_DISPLAY;
glDeleteFramebuffers(1, &self->fbo); EGLContext egl_context = EGL_NO_CONTEXT;
self->fbo = 0;
if (self->player) {
egl_display = self->player->GetEglDisplay();
egl_context = self->player->GetEglContext();
} }
if (self->texture != 0) {
glDeleteTextures(1, &self->texture); // Clean up Flutter's texture (in Flutter's current context)
self->texture = 0; 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; self->player = nullptr;