262 lines
8.7 KiB
C++
262 lines
8.7 KiB
C++
#include "mpv_texture.h"
|
|
|
|
#include <epoxy/egl.h>
|
|
#include <epoxy/gl.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;
|
|
|
|
mpv::MpvPlayer* player; // not owned
|
|
FlTextureRegistrar* registrar; // not owned
|
|
FlView* view; // not owned, for querying allocation size
|
|
|
|
// 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())
|
|
|
|
// 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;
|
|
}
|
|
|
|
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->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;
|
|
|
|
// 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);
|
|
|
|
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, uint32_t* target, uint32_t* name, uint32_t* width, uint32_t* height, GError** error) {
|
|
MpvTexture* self = MPV_TEXTURE(gl_texture);
|
|
|
|
if (!self->player) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Lazily create the mpv render context on first populate() call,
|
|
// since Flutter's GL context is current here.
|
|
if (!self->player->HasRenderContext()) {
|
|
if (!self->player->InitRenderContext()) {
|
|
g_set_error(error, g_quark_from_static_string("mpv"), 0, "Failed to create mpv render context");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Determine target size from the FlView widget allocation.
|
|
GtkAllocation alloc;
|
|
gtk_widget_get_allocation(GTK_WIDGET(self->view), &alloc);
|
|
int scale = gtk_widget_get_scale_factor(GTK_WIDGET(self->view));
|
|
int32_t w = alloc.width * scale;
|
|
int32_t h = alloc.height * scale;
|
|
|
|
if (w <= 0 || h <= 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
ensure_textures(self, w, h);
|
|
|
|
// 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 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 its FBO
|
|
glBindFramebuffer(GL_FRAMEBUFFER, self->mpv_fbo);
|
|
self->player->ClearRedrawFlag();
|
|
self->player->Render(w, h, static_cast<int>(self->mpv_fbo));
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glFlush();
|
|
|
|
// Restore Flutter's context
|
|
eglMakeCurrent(flutter_display, flutter_draw, flutter_read, flutter_context);
|
|
|
|
*target = GL_TEXTURE_2D;
|
|
*name = self->flutter_texture;
|
|
*width = static_cast<uint32_t>(w);
|
|
*height = static_cast<uint32_t>(h);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void mpv_texture_class_init(MpvTextureClass* klass) {
|
|
FL_TEXTURE_GL_CLASS(klass)->populate = mpv_texture_populate;
|
|
}
|
|
|
|
static void mpv_texture_init(MpvTexture* self) {
|
|
self->player = nullptr;
|
|
self->registrar = nullptr;
|
|
self->view = nullptr;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
self->view = view;
|
|
return self;
|
|
}
|
|
|
|
void mpv_texture_mark_frame_available(MpvTexture* self) {
|
|
if (self && self->registrar) {
|
|
fl_texture_registrar_mark_texture_frame_available(self->registrar, FL_TEXTURE(self));
|
|
}
|
|
}
|
|
|
|
void mpv_texture_dispose(MpvTexture* self) {
|
|
if (!self) return;
|
|
|
|
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();
|
|
}
|
|
|
|
// 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;
|
|
self->registrar = nullptr;
|
|
self->view = nullptr;
|
|
}
|
|
|
|
int64_t mpv_texture_get_id(MpvTexture* self) { return fl_texture_get_id(FL_TEXTURE(self)); }
|