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 <epoxy/gl.h>
|
||||
#include <epoxy/egl.h>
|
||||
#include <epoxy/glx.h>
|
||||
#include <gdk/gdk.h>
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h>
|
||||
@@ -15,32 +14,9 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
// 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<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));
|
||||
}
|
||||
|
||||
@@ -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<char*>(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<std::string>& 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);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mpv/render_gl.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <epoxy/gl.h>
|
||||
#include <epoxy/egl.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
@@ -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<bool> needs_redraw_{false};
|
||||
std::atomic<bool> disposed_{false};
|
||||
EventCallback event_callback_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+152
-61
@@ -1,6 +1,26 @@
|
||||
#include "mpv_texture.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 {
|
||||
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<int>(self->fbo));
|
||||
self->player->Render(w, h, static_cast<int>(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<uint32_t>(w);
|
||||
*height = static_cast<uint32_t>(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;
|
||||
|
||||
Reference in New Issue
Block a user