fix: isolated EGL context for mpv rendering
This commit is contained in:
+106
-49
@@ -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);
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user