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.
130 lines
5.5 KiB
C++
130 lines
5.5 KiB
C++
#include "mpv_gpu_bootstrap.h"
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
namespace mpv {
|
|
namespace {
|
|
|
|
bool HasExtension(const char* extensions, const char* requested) {
|
|
if (!extensions || !requested || requested[0] == '\0' || std::strchr(requested, ' ')) return false;
|
|
const size_t requested_length = std::strlen(requested);
|
|
const char* current = extensions;
|
|
while ((current = std::strstr(current, requested)) != nullptr) {
|
|
const bool starts_token = current == extensions || current[-1] == ' ';
|
|
const char following = current[requested_length];
|
|
if (starts_token && (following == '\0' || following == ' ')) return true;
|
|
current += requested_length;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ParseEglVersion(const char* version, int* major, int* minor) {
|
|
if (!version || !major || !minor) return false;
|
|
char* end = nullptr;
|
|
const long parsed_major = std::strtol(version, &end, 10);
|
|
if (end == version || *end != '.') return false;
|
|
const char* minor_start = end + 1;
|
|
const long parsed_minor = std::strtol(minor_start, &end, 10);
|
|
if (end == minor_start || parsed_major < 0 || parsed_minor < 0) return false;
|
|
*major = static_cast<int>(parsed_major);
|
|
*minor = static_cast<int>(parsed_minor);
|
|
return true;
|
|
}
|
|
|
|
bool AtLeastEgl15(const GpuBootstrapProbe& probe) {
|
|
return probe.egl_major > 1 || (probe.egl_major == 1 && probe.egl_minor >= 5);
|
|
}
|
|
|
|
bool Fail(std::string* error, const char* message) {
|
|
if (error) *error = message;
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
EGLImageKHR GpuImageDispatch::Create(EGLDisplay display, EGLContext context, EGLClientBuffer buffer) const {
|
|
if (uses_core) {
|
|
if (!create_image_core) return EGL_NO_IMAGE_KHR;
|
|
const EGLAttrib attributes[] = {EGL_NONE};
|
|
return reinterpret_cast<EGLImageKHR>(
|
|
create_image_core(display, context, EGL_GL_TEXTURE_2D_KHR, buffer, attributes));
|
|
}
|
|
if (!create_image_khr) return EGL_NO_IMAGE_KHR;
|
|
const EGLint attributes[] = {EGL_NONE};
|
|
return create_image_khr(display, context, EGL_GL_TEXTURE_2D_KHR, buffer, attributes);
|
|
}
|
|
|
|
bool GpuImageDispatch::Destroy(EGLDisplay display, EGLImageKHR image) const {
|
|
if (image == EGL_NO_IMAGE_KHR) return true;
|
|
if (uses_core) {
|
|
return destroy_image_core && destroy_image_core(display, reinterpret_cast<EGLImage>(image)) == EGL_TRUE;
|
|
}
|
|
return destroy_image_khr && destroy_image_khr(display, image) == EGL_TRUE;
|
|
}
|
|
|
|
GpuImageDispatch::operator bool() const {
|
|
const bool image_functions =
|
|
uses_core ? create_image_core && destroy_image_core : create_image_khr && destroy_image_khr;
|
|
return image_functions && image_target_texture;
|
|
}
|
|
|
|
bool ValidateGpuBootstrapProbe(const GpuBootstrapProbe& probe, std::string* error) {
|
|
const bool egl15 = AtLeastEgl15(probe);
|
|
if (!egl15 && !HasExtension(probe.egl_extensions, "EGL_KHR_surfaceless_context")) {
|
|
return Fail(error, "EGL surfaceless contexts are unavailable");
|
|
}
|
|
|
|
const bool core_images = egl15 && probe.create_image_core && probe.destroy_image_core;
|
|
const bool has_khr_image_extension =
|
|
HasExtension(probe.egl_extensions, "EGL_KHR_image") || HasExtension(probe.egl_extensions, "EGL_KHR_image_base");
|
|
const bool khr_images = has_khr_image_extension && probe.create_image_khr && probe.destroy_image_khr;
|
|
if (!core_images && !khr_images) {
|
|
return Fail(error, "EGL image creation is unavailable");
|
|
}
|
|
if (!HasExtension(probe.gl_extensions, "GL_OES_EGL_image")) {
|
|
return Fail(error, "OpenGL EGL image binding is unavailable");
|
|
}
|
|
if (!probe.image_target_texture) {
|
|
return Fail(error, "OpenGL EGL image entry point is unavailable");
|
|
}
|
|
if (error) error->clear();
|
|
return true;
|
|
}
|
|
|
|
bool ResolveGpuImageDispatch(EGLDisplay display, GpuImageDispatch* dispatch, std::string* error) {
|
|
if (!dispatch || display == EGL_NO_DISPLAY || eglGetCurrentContext() == EGL_NO_CONTEXT) {
|
|
return Fail(error, "No current EGL context is available");
|
|
}
|
|
|
|
GpuBootstrapProbe probe;
|
|
if (!ParseEglVersion(eglQueryString(display, EGL_VERSION), &probe.egl_major, &probe.egl_minor)) {
|
|
return Fail(error, "EGL version is unavailable");
|
|
}
|
|
probe.egl_extensions = eglQueryString(display, EGL_EXTENSIONS);
|
|
probe.gl_extensions = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
|
|
probe.create_image_core = reinterpret_cast<void*>(eglGetProcAddress("eglCreateImage"));
|
|
probe.destroy_image_core = reinterpret_cast<void*>(eglGetProcAddress("eglDestroyImage"));
|
|
probe.create_image_khr = reinterpret_cast<void*>(eglGetProcAddress("eglCreateImageKHR"));
|
|
probe.destroy_image_khr = reinterpret_cast<void*>(eglGetProcAddress("eglDestroyImageKHR"));
|
|
probe.image_target_texture = reinterpret_cast<void*>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
|
|
if (!ValidateGpuBootstrapProbe(probe, error)) return false;
|
|
|
|
GpuImageDispatch resolved;
|
|
const bool egl15 = AtLeastEgl15(probe);
|
|
if (egl15 && probe.create_image_core && probe.destroy_image_core) {
|
|
resolved.uses_core = true;
|
|
resolved.create_image_core = reinterpret_cast<EglCreateImageCoreProc>(probe.create_image_core);
|
|
resolved.destroy_image_core = reinterpret_cast<EglDestroyImageCoreProc>(probe.destroy_image_core);
|
|
} else {
|
|
resolved.create_image_khr = reinterpret_cast<EglCreateImageKhrProc>(probe.create_image_khr);
|
|
resolved.destroy_image_khr = reinterpret_cast<EglDestroyImageKhrProc>(probe.destroy_image_khr);
|
|
}
|
|
resolved.image_target_texture = reinterpret_cast<GlImageTargetTextureProc>(probe.image_target_texture);
|
|
if (!resolved) return Fail(error, "GPU image dispatch is incomplete");
|
|
*dispatch = resolved;
|
|
return true;
|
|
}
|
|
|
|
} // namespace mpv
|