Files
plezy/linux/runner/mpv/mpv_texture.cc
T
edde746 9cdfe7591e feat(linux): restore the Flutter-texture path as the SDR fallback
2.13.0 deleted the display-agnostic EGL/Flutter-texture renderer and made
the native Wayland plane the only path, hard-rejecting every session that
cannot host one - X11, XWayland (SteamOS Gaming Mode runs native apps
through Gamescope's XWayland), or a plane whose EGL bootstrap failed. This
restores the 2.11.0 texture path from git history as the fallback:

- Re-add mpv_texture.cc/.h and mpv_gpu_bootstrap.cc/.h (2.11.0 verbatim):
  an FlTextureGL whose populate renders mpv into an offscreen FBO sampled
  by Flutter via an EGL image.
- MpvPlayer gains the texture-mode render-context API (InitRenderContext,
  HasRenderContext, GetEglDisplay/Context, Render(w,h,fbo)) beside the
  plane's InitRenderContextForSurface/RenderToSurface. The isolated ES 2.0
  context on Flutter's display and the X11 display param are exactly the
  2.11.0 configuration hardware decode demonstrably worked in.
- initialize now tries the plane first and falls back to the texture path
  when it cannot be brought up, returning the texture id (Dart's 2.11.0
  'result is int' contract) with waitForVideoReady gating playback until
  the GPU bootstrap settles. Both paths share one mpv core, so the
  plane/texture decision precedes render-context creation.
- hdr-enabled/hdr-tone-mapping are intercepted in texture mode (no plane,
  no HDR); the HDR toggle hides itself via isHDRSupported.
- New Linux setting 'Video rendering mode' (Automatic / Texture) forces
  the fallback - the user-visible workaround for plane-only trouble and
  for the hwdec interop regression, plus translations in all locales.

SDR only on the fallback, matching 2.11.0; the plane path is unchanged.
2026-08-14 18:16:32 +02:00

541 lines
22 KiB
C++

#include "mpv_texture.h"
#include <epoxy/egl.h>
#include <epoxy/gl.h>
#include <algorithm>
#include <cstdint>
#include <string>
#include <vector>
#include "mpv_gpu_bootstrap.h"
namespace {
GQuark TextureErrorDomain() { return g_quark_from_static_string("plezy-mpv-texture"); }
struct TextureResources {
GLuint mpv_fbo = 0;
GLuint mpv_texture = 0;
GLuint flutter_texture = 0;
EGLImageKHR egl_image = EGL_NO_IMAGE_KHR;
int32_t width = 0;
int32_t height = 0;
bool complete() const {
return mpv_fbo != 0 && mpv_texture != 0 && flutter_texture != 0 && egl_image != EGL_NO_IMAGE_KHR;
}
};
bool SetError(GError** error, const char* message) {
g_set_error_literal(error, TextureErrorDomain(), 1, message);
return false;
}
void ClearGlErrors() {
while (glGetError() != GL_NO_ERROR) {
}
}
} // namespace
struct _MpvTexture {
FlTextureGL parent_instance;
mpv::MpvPlayer* player;
FlTextureRegistrar* registrar;
FlView* view;
GMutex mutex;
bool disposed;
TextureResources* active;
std::vector<TextureResources>* retired;
mpv::GpuImageDispatch* image_dispatch;
EGLDisplay flutter_display;
EGLContext flutter_share_context;
EGLContext flutter_cleanup_context;
GMutex bootstrap_mutex;
gint bootstrap_state;
gchar* bootstrap_error;
MpvTextureReadyCallback ready_callback;
gpointer ready_user_data;
GDestroyNotify ready_destroy_notify;
};
G_DEFINE_TYPE(MpvTexture, mpv_texture, fl_texture_gl_get_type())
namespace {
void SignalBootstrap(MpvTexture* self, gboolean success, const char* message) {
MpvTextureReadyCallback callback = nullptr;
gpointer user_data = nullptr;
g_mutex_lock(&self->bootstrap_mutex);
if (self->bootstrap_state == 0) {
self->bootstrap_state = success ? 1 : 2;
if (!success) self->bootstrap_error = g_strdup(message ? message : "Video initialization failed");
callback = self->ready_callback;
user_data = self->ready_user_data;
}
g_mutex_unlock(&self->bootstrap_mutex);
if (callback) callback(success, message, user_data);
}
bool RestoreContext(
EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context, EGLenum api, GError** error) {
if (api != EGL_NONE && !eglBindAPI(api)) {
g_warning("MPV texture: failed to restore Flutter EGL API: 0x%x", eglGetError());
return SetError(error, "Failed to restore Flutter EGL API");
}
if (eglMakeCurrent(display, draw, read, context)) return true;
g_warning("MPV texture: failed to restore EGL context: 0x%x", eglGetError());
return SetError(error, "Failed to restore Flutter EGL context");
}
void RestoreOrReleaseContext(
EGLDisplay flutter_display, EGLSurface flutter_draw, EGLSurface flutter_read, EGLContext flutter_context,
EGLenum flutter_api, EGLDisplay mpv_display) {
if (flutter_display != EGL_NO_DISPLAY && flutter_context != EGL_NO_CONTEXT) {
const bool api_restored = flutter_api == EGL_NONE || eglBindAPI(flutter_api);
if (api_restored && eglMakeCurrent(flutter_display, flutter_draw, flutter_read, flutter_context)) return;
g_warning("MPV texture: failed to restore EGL state during cleanup: 0x%x", eglGetError());
}
if (mpv_display != EGL_NO_DISPLAY) {
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
g_warning("MPV texture: failed to bind OpenGL while releasing cleanup context: 0x%x", eglGetError());
} else if (!eglMakeCurrent(mpv_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
g_warning("MPV texture: failed to release EGL context during cleanup: 0x%x", eglGetError());
}
}
if (flutter_api != EGL_NONE && !eglBindAPI(flutter_api)) {
g_warning("MPV texture: failed to restore Flutter EGL API after cleanup: 0x%x", eglGetError());
}
}
bool ResourceSetEmpty(const TextureResources& resources) {
return resources.mpv_fbo == 0 && resources.mpv_texture == 0 && resources.flutter_texture == 0 &&
resources.egl_image == EGL_NO_IMAGE_KHR;
}
bool EnsureFlutterCleanupContext(
MpvTexture* self, EGLDisplay flutter_display, EGLContext flutter_context, EGLenum flutter_api, GError** error) {
if (self->flutter_cleanup_context != EGL_NO_CONTEXT) {
if (self->flutter_display == flutter_display && self->flutter_share_context == flutter_context) return true;
return SetError(error, "Flutter EGL context changed while video textures were active");
}
if (flutter_api != EGL_OPENGL_ES_API) {
return SetError(error, "Flutter is not using an OpenGL ES context");
}
EGLint config_id = 0;
EGLint client_version = 0;
if (!eglQueryContext(flutter_display, flutter_context, EGL_CONFIG_ID, &config_id) ||
!eglQueryContext(flutter_display, flutter_context, EGL_CONTEXT_CLIENT_VERSION, &client_version)) {
g_warning("MPV texture: failed to query Flutter EGL context: 0x%x", eglGetError());
return SetError(error, "Failed to query Flutter EGL context");
}
EGLConfig config = nullptr;
EGLint num_configs = 0;
const EGLint config_attribs[] = {EGL_CONFIG_ID, config_id, EGL_NONE};
if (!eglChooseConfig(flutter_display, config_attribs, &config, 1, &num_configs) || num_configs != 1) {
g_warning("MPV texture: failed to select Flutter EGL config: 0x%x", eglGetError());
return SetError(error, "Failed to select Flutter EGL config");
}
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
g_warning("MPV texture: failed to bind OpenGL ES for cleanup context creation: 0x%x", eglGetError());
return SetError(error, "Failed to bind OpenGL ES for video cleanup");
}
const EGLint context_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, client_version, EGL_NONE};
const EGLContext cleanup_context = eglCreateContext(flutter_display, config, flutter_context, context_attribs);
const bool api_restored = eglBindAPI(flutter_api) == EGL_TRUE;
if (cleanup_context == EGL_NO_CONTEXT || !api_restored) {
if (cleanup_context != EGL_NO_CONTEXT && !eglDestroyContext(flutter_display, cleanup_context)) {
g_warning("MPV texture: failed to destroy rejected cleanup context: 0x%x", eglGetError());
}
if (!api_restored) {
g_warning("MPV texture: failed to restore Flutter EGL API after cleanup context creation: 0x%x", eglGetError());
}
return SetError(error, "Failed to create video cleanup context");
}
self->flutter_display = flutter_display;
self->flutter_share_context = flutter_context;
self->flutter_cleanup_context = cleanup_context;
return true;
}
void DestroyFlutterCleanupContext(MpvTexture* self) {
if (self->flutter_cleanup_context == EGL_NO_CONTEXT || self->flutter_display == EGL_NO_DISPLAY) return;
const EGLenum previous_api = eglQueryAPI();
if (eglGetCurrentContext() == self->flutter_cleanup_context) {
if (!eglBindAPI(EGL_OPENGL_ES_API) ||
!eglMakeCurrent(self->flutter_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
g_warning("MPV texture: failed to release Flutter cleanup context: 0x%x", eglGetError());
}
}
if (!eglDestroyContext(self->flutter_display, self->flutter_cleanup_context)) {
g_warning("MPV texture: failed to destroy Flutter cleanup context: 0x%x", eglGetError());
}
if (previous_api != EGL_NONE && !eglBindAPI(previous_api)) {
g_warning("MPV texture: failed to restore EGL API after cleanup context destruction: 0x%x", eglGetError());
}
self->flutter_cleanup_context = EGL_NO_CONTEXT;
self->flutter_share_context = EGL_NO_CONTEXT;
self->flutter_display = EGL_NO_DISPLAY;
}
void RetireIncompleteCandidate(MpvTexture* self, const TextureResources& candidate) {
if (!ResourceSetEmpty(candidate)) self->retired->push_back(candidate);
}
void CleanupResourceSet(
MpvTexture* self, TextureResources* resources, EGLDisplay flutter_display, EGLSurface flutter_draw,
EGLSurface flutter_read, EGLContext flutter_context, EGLenum flutter_api) {
const EGLDisplay mpv_display = self->player ? self->player->GetEglDisplay() : EGL_NO_DISPLAY;
const EGLContext mpv_context = self->player ? self->player->GetEglContext() : EGL_NO_CONTEXT;
if (resources->egl_image != EGL_NO_IMAGE_KHR && mpv_display != EGL_NO_DISPLAY && self->image_dispatch &&
*self->image_dispatch) {
if (self->image_dispatch->Destroy(mpv_display, resources->egl_image)) {
resources->egl_image = EGL_NO_IMAGE_KHR;
} else {
g_warning("MPV texture: failed to destroy EGL image: 0x%x", eglGetError());
}
}
if (resources->flutter_texture) {
const bool flutter_current = flutter_display == self->flutter_display &&
flutter_context == self->flutter_share_context && flutter_context != EGL_NO_CONTEXT &&
eglGetCurrentContext() == self->flutter_share_context;
const bool cleanup_current =
!flutter_current && self->flutter_display != EGL_NO_DISPLAY &&
self->flutter_cleanup_context != EGL_NO_CONTEXT && eglBindAPI(EGL_OPENGL_ES_API) &&
eglMakeCurrent(self->flutter_display, EGL_NO_SURFACE, EGL_NO_SURFACE, self->flutter_cleanup_context);
if (flutter_current || cleanup_current) {
glDeleteTextures(1, &resources->flutter_texture);
resources->flutter_texture = 0;
} else {
g_warning("MPV texture: failed to activate Flutter cleanup context: 0x%x", eglGetError());
}
if (cleanup_current) {
RestoreOrReleaseContext(
flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api, self->flutter_display);
}
}
if (resources->mpv_fbo || resources->mpv_texture) {
const bool mpv_current = mpv_display != EGL_NO_DISPLAY && mpv_context != EGL_NO_CONTEXT &&
eglBindAPI(EGL_OPENGL_ES_API) &&
eglMakeCurrent(mpv_display, EGL_NO_SURFACE, EGL_NO_SURFACE, mpv_context);
if (mpv_current) {
if (resources->mpv_fbo) glDeleteFramebuffers(1, &resources->mpv_fbo);
if (resources->mpv_texture) glDeleteTextures(1, &resources->mpv_texture);
resources->mpv_fbo = 0;
resources->mpv_texture = 0;
} else {
g_warning("MPV texture: failed to activate EGL context during cleanup: 0x%x", eglGetError());
}
RestoreOrReleaseContext(flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api, mpv_display);
}
}
void CleanupRetired(MpvTexture* self) {
if (!self->retired || self->retired->empty() || !self->player) return;
const EGLDisplay flutter_display = eglGetCurrentDisplay();
const EGLContext flutter_context = eglGetCurrentContext();
const EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW);
const EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ);
const EGLenum flutter_api = eglQueryAPI();
for (auto& resources : *self->retired) {
CleanupResourceSet(self, &resources, flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api);
}
auto& retired = *self->retired;
retired.erase(
std::remove_if(
retired.begin(), retired.end(),
[](const TextureResources& resources) { return ResourceSetEmpty(resources); }),
retired.end());
}
bool EnsureTextures(MpvTexture* self, int32_t width, int32_t height, GError** error) {
if (self->active->complete() && self->active->width == width && self->active->height == height) return true;
const EGLDisplay flutter_display = eglGetCurrentDisplay();
const EGLContext flutter_context = eglGetCurrentContext();
const EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW);
const EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ);
const EGLenum flutter_api = eglQueryAPI();
const EGLDisplay mpv_display = self->player->GetEglDisplay();
const EGLContext mpv_context = self->player->GetEglContext();
if (flutter_display == EGL_NO_DISPLAY || flutter_context == EGL_NO_CONTEXT || mpv_display == EGL_NO_DISPLAY ||
mpv_context == EGL_NO_CONTEXT) {
return SetError(error, "Video EGL contexts are unavailable");
}
if (!EnsureFlutterCleanupContext(self, flutter_display, flutter_context, flutter_api, error)) return false;
if (!*self->image_dispatch) {
std::string dispatch_error;
if (!mpv::ResolveGpuImageDispatch(flutter_display, self->image_dispatch, &dispatch_error)) {
g_warning("MPV texture: GPU bootstrap rejected: %s", dispatch_error.c_str());
return SetError(error, dispatch_error.c_str());
}
}
TextureResources candidate;
candidate.width = width;
candidate.height = height;
if (!eglBindAPI(EGL_OPENGL_ES_API) || !eglMakeCurrent(mpv_display, EGL_NO_SURFACE, EGL_NO_SURFACE, mpv_context)) {
RestoreOrReleaseContext(flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api, mpv_display);
return SetError(error, "Failed to activate video EGL context");
}
ClearGlErrors();
glGenTextures(1, &candidate.mpv_texture);
glBindTexture(GL_TEXTURE_2D, candidate.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, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glGenFramebuffers(1, &candidate.mpv_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, candidate.mpv_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, candidate.mpv_texture, 0);
bool framebuffer_complete = candidate.mpv_texture != 0 && candidate.mpv_fbo != 0 &&
glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE &&
glGetError() == GL_NO_ERROR;
if (framebuffer_complete) {
candidate.egl_image = self->image_dispatch->Create(
mpv_display, mpv_context, reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(candidate.mpv_texture)));
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glFlush();
framebuffer_complete = framebuffer_complete && glGetError() == GL_NO_ERROR;
if (!RestoreContext(flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api, error)) {
CleanupResourceSet(self, &candidate, flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api);
RetireIncompleteCandidate(self, candidate);
return false;
}
if (!framebuffer_complete || candidate.egl_image == EGL_NO_IMAGE_KHR) {
CleanupResourceSet(self, &candidate, flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api);
RetireIncompleteCandidate(self, candidate);
return SetError(error, "Failed to create a complete video framebuffer");
}
ClearGlErrors();
glGenTextures(1, &candidate.flutter_texture);
glBindTexture(GL_TEXTURE_2D, candidate.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);
self->image_dispatch->image_target_texture(GL_TEXTURE_2D, reinterpret_cast<GLeglImageOES>(candidate.egl_image));
const bool flutter_texture_complete = candidate.flutter_texture != 0 && glGetError() == GL_NO_ERROR;
glBindTexture(GL_TEXTURE_2D, 0);
if (!flutter_texture_complete) {
CleanupResourceSet(self, &candidate, flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api);
RetireIncompleteCandidate(self, candidate);
return SetError(error, "Failed to bind the shared video image");
}
if (self->active->complete()) self->retired->push_back(*self->active);
*self->active = candidate;
CleanupRetired(self);
return true;
}
static gboolean MpvTexturePopulate(
FlTextureGL* texture, uint32_t* target, uint32_t* name, uint32_t* width, uint32_t* height, GError** error) {
MpvTexture* self = MPV_TEXTURE(texture);
g_mutex_lock(&self->mutex);
if (self->disposed || !self->player) {
g_object_ref(self);
g_mutex_unlock(&self->mutex);
SignalBootstrap(self, FALSE, "Video texture was disposed");
g_object_unref(self);
return SetError(error, "Video texture was disposed");
}
if (!self->player->HasRenderContext() && !self->player->InitRenderContext()) {
g_mutex_unlock(&self->mutex);
return SetError(error, "Failed to create video render context");
}
GtkAllocation allocation;
gtk_widget_get_allocation(GTK_WIDGET(self->view), &allocation);
const int scale = gtk_widget_get_scale_factor(GTK_WIDGET(self->view));
const int32_t requested_width = allocation.width * scale;
const int32_t requested_height = allocation.height * scale;
if (requested_width <= 0 || requested_height <= 0) {
g_mutex_unlock(&self->mutex);
return SetError(error, "Video surface has no drawable size");
}
// GL/EGL failures during the first populate are not terminal. Flutter may
// call populate again while waitForVideoReady owns the bounded deadline.
if (!EnsureTextures(self, requested_width, requested_height, error)) {
g_mutex_unlock(&self->mutex);
return FALSE;
}
const EGLDisplay flutter_display = eglGetCurrentDisplay();
const EGLContext flutter_context = eglGetCurrentContext();
const EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW);
const EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ);
const EGLenum flutter_api = eglQueryAPI();
const EGLDisplay mpv_display = self->player->GetEglDisplay();
const EGLContext mpv_context = self->player->GetEglContext();
if (!eglBindAPI(EGL_OPENGL_ES_API) || !eglMakeCurrent(mpv_display, EGL_NO_SURFACE, EGL_NO_SURFACE, mpv_context)) {
RestoreOrReleaseContext(flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api, mpv_display);
g_mutex_unlock(&self->mutex);
return SetError(error, "Failed to activate video EGL context");
}
ClearGlErrors();
glBindFramebuffer(GL_FRAMEBUFFER, self->active->mpv_fbo);
self->player->ClearRedrawFlag();
self->player->Render(requested_width, requested_height, static_cast<int>(self->active->mpv_fbo));
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glFlush();
const bool render_succeeded = glGetError() == GL_NO_ERROR;
if (!RestoreContext(flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api, error)) {
RestoreOrReleaseContext(flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api, mpv_display);
g_mutex_unlock(&self->mutex);
return FALSE;
}
if (!render_succeeded) {
g_mutex_unlock(&self->mutex);
return SetError(error, "Video render operation failed");
}
*target = GL_TEXTURE_2D;
*name = self->active->flutter_texture;
*width = static_cast<uint32_t>(requested_width);
*height = static_cast<uint32_t>(requested_height);
g_object_ref(self);
g_mutex_unlock(&self->mutex);
SignalBootstrap(self, TRUE, nullptr);
g_object_unref(self);
return TRUE;
}
static void MpvTextureFinalize(GObject* object) {
MpvTexture* self = MPV_TEXTURE(object);
if (self->ready_destroy_notify && self->ready_user_data) {
self->ready_destroy_notify(self->ready_user_data);
}
g_free(self->bootstrap_error);
delete self->active;
delete self->retired;
delete self->image_dispatch;
g_mutex_clear(&self->bootstrap_mutex);
g_mutex_clear(&self->mutex);
G_OBJECT_CLASS(mpv_texture_parent_class)->finalize(object);
}
} // namespace
static void mpv_texture_class_init(MpvTextureClass* klass) {
FL_TEXTURE_GL_CLASS(klass)->populate = MpvTexturePopulate;
G_OBJECT_CLASS(klass)->finalize = MpvTextureFinalize;
}
static void mpv_texture_init(MpvTexture* self) {
self->player = nullptr;
self->registrar = nullptr;
self->view = nullptr;
g_mutex_init(&self->mutex);
self->disposed = false;
self->active = new TextureResources();
self->retired = new std::vector<TextureResources>();
self->image_dispatch = new mpv::GpuImageDispatch();
self->flutter_display = EGL_NO_DISPLAY;
self->flutter_share_context = EGL_NO_CONTEXT;
self->flutter_cleanup_context = EGL_NO_CONTEXT;
g_mutex_init(&self->bootstrap_mutex);
self->bootstrap_state = 0;
self->bootstrap_error = nullptr;
self->ready_callback = nullptr;
self->ready_user_data = nullptr;
self->ready_destroy_notify = nullptr;
}
MpvTexture* mpv_texture_new(mpv::MpvPlayer* player, FlTextureRegistrar* registrar, FlView* view) {
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_set_ready_callback(
MpvTexture* self, MpvTextureReadyCallback callback, gpointer user_data, GDestroyNotify destroy_notify) {
gboolean success = FALSE;
const gchar* message = nullptr;
bool complete = false;
g_mutex_lock(&self->bootstrap_mutex);
self->ready_callback = callback;
self->ready_user_data = user_data;
self->ready_destroy_notify = destroy_notify;
if (self->bootstrap_state != 0) {
complete = true;
success = self->bootstrap_state == 1;
message = self->bootstrap_error;
}
g_mutex_unlock(&self->bootstrap_mutex);
if (complete && callback) callback(success, message, user_data);
}
void mpv_texture_mark_frame_available(MpvTexture* self) {
if (!self) return;
g_mutex_lock(&self->mutex);
FlTextureRegistrar* registrar = self->disposed ? nullptr : self->registrar;
g_mutex_unlock(&self->mutex);
if (registrar) {
fl_texture_registrar_mark_texture_frame_available(registrar, FL_TEXTURE(self));
}
}
void mpv_texture_dispose(MpvTexture* self) {
if (!self) return;
g_mutex_lock(&self->mutex);
if (self->disposed) {
g_mutex_unlock(&self->mutex);
return;
}
self->disposed = true;
SignalBootstrap(self, FALSE, "Video initialization was cancelled");
if (self->player) {
const EGLDisplay flutter_display = eglGetCurrentDisplay();
const EGLContext flutter_context = eglGetCurrentContext();
const EGLSurface flutter_draw = eglGetCurrentSurface(EGL_DRAW);
const EGLSurface flutter_read = eglGetCurrentSurface(EGL_READ);
const EGLenum flutter_api = eglQueryAPI();
CleanupResourceSet(self, self->active, flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api);
for (auto& resources : *self->retired) {
CleanupResourceSet(self, &resources, flutter_display, flutter_draw, flutter_read, flutter_context, flutter_api);
}
DestroyFlutterCleanupContext(self);
const auto leaked_sets =
static_cast<size_t>(!ResourceSetEmpty(*self->active)) +
static_cast<size_t>(std::count_if(self->retired->begin(), self->retired->end(), [](const auto& resources) {
return !ResourceSetEmpty(resources);
}));
if (leaked_sets != 0) {
g_warning("MPV texture: %zu resource set(s) could not be released before disposal", leaked_sets);
}
}
*self->active = TextureResources{};
self->retired->clear();
self->player = nullptr;
self->registrar = nullptr;
self->view = nullptr;
g_mutex_unlock(&self->mutex);
}
int64_t mpv_texture_get_id(MpvTexture* self) { return fl_texture_get_id(FL_TEXTURE(self)); }