diff --git a/thermion_dart/native/include/opengl/linux/LinuxOpenGLContext.h b/thermion_dart/native/include/opengl/linux/LinuxOpenGLContext.h index 7fc0c7171..c42c57760 100644 --- a/thermion_dart/native/include/opengl/linux/LinuxOpenGLContext.h +++ b/thermion_dart/native/include/opengl/linux/LinuxOpenGLContext.h @@ -8,8 +8,8 @@ namespace thermion::opengl::linux_platform { /** - * Manages an EGL display/context, GBM device, and rendering surfaces for the - * OpenGL backend on Linux. Analogous to LinuxVulkanContext. + * Manages an EGL display/context and rendering surfaces for the OpenGL backend + * on Linux. Analogous to LinuxVulkanContext. * * The EGL context created here is returned via GetSharedContext() so Filament's * PlatformEGL can create its own context in the same share group — GL texture @@ -30,10 +30,19 @@ class LinuxOpenGLContext { bool IsValid() const; const char* GetLastError() const; + // Select the preferred cross-context texture transport. A disposable 1x1 + // desktop-GL texture is imported through consumerContext. If that succeeds, + // subsequent surfaces use direct same-display EGLImages; otherwise they + // retain the GBM/DMA-BUF compatibility transport. + bool ConfigureTextureTransport( + void* consumerContext, uint32_t consumerApi); + bool UsesEglImageTextureTransport() const; + int64_t CreateRenderingSurface(uint32_t width, uint32_t height); void DestroyRenderingSurface(int64_t surfaceId); uint32_t GetGLTextureId(int64_t surfaceId); + void* GetEGLImage(int64_t surfaceId); SurfaceExportInfo GetSurfaceExportInfo(int64_t surfaceId); void* GetSharedContext(); // Returns our EGLContext for Filament sharing diff --git a/thermion_dart/native/include/opengl/linux/LinuxOpenGLTexture.h b/thermion_dart/native/include/opengl/linux/LinuxOpenGLTexture.h index eca685127..d1ea2c10f 100644 --- a/thermion_dart/native/include/opengl/linux/LinuxOpenGLTexture.h +++ b/thermion_dart/native/include/opengl/linux/LinuxOpenGLTexture.h @@ -14,26 +14,28 @@ struct gbm_bo; namespace thermion::opengl::linux_platform { /** - * GL texture backed by a GBM buffer object with DMA-BUF export. + * GL texture owned by the isolated desktop-OpenGL producer context. * - * Creation flow: - * 1. gbm_bo_create(gbm, w, h, GBM_FORMAT_ABGR8888, GBM_BO_USE_RENDERING) - * 2. Export DMA-BUF fd, stride, offset from the GBM buffer - * 3. Build EGLImage from DMA-BUF params - * 4. glGenTextures -> glBindTexture(GL_TEXTURE_2D) -> glEGLImageTargetTexture2DOES + * The preferred transport exposes a regular GL_TEXTURE_2D as an EGLImage. + * Flutter imports that image into its GLES/GL share group. The compatibility + * transport backs the texture with a GBM buffer and exports DMA-BUF metadata. * - * The resulting GL texture ID is valid in any EGL context that shares with the - * one used during creation (same share group). + * In both cases the GL texture ID is valid in Filament's producer share group. */ class LinuxOpenGLTexture { public: ~LinuxOpenGLTexture(); - static std::unique_ptr create( + static std::unique_ptr createEglImage( + EGLDisplay display, EGLContext context, EGLSurface surface, + uint32_t width, uint32_t height); + + static std::unique_ptr createDmaBuf( EGLDisplay display, EGLContext context, EGLSurface surface, struct gbm_device* gbm, uint32_t width, uint32_t height); GLuint GetGLTextureId() const { return _glTextureId; } + EGLImage GetEGLImage() const { return _eglImage; } int GetDmaBufFd() const { return _dmaBufFd; } uint32_t GetStride() const { return _stride; } uint32_t GetOffset() const { return _offset; } diff --git a/thermion_dart/native/src/opengl/linux/LinuxOpenGLContext.cpp b/thermion_dart/native/src/opengl/linux/LinuxOpenGLContext.cpp index 718df0802..f87fa54d1 100644 --- a/thermion_dart/native/src/opengl/linux/LinuxOpenGLContext.cpp +++ b/thermion_dart/native/src/opengl/linux/LinuxOpenGLContext.cpp @@ -27,6 +27,11 @@ namespace thermion::opengl::linux_platform { +enum class TextureTransport { + DMA_BUF, + EGL_IMAGE, +}; + class ScopedEglThreadState { public: ScopedEglThreadState() @@ -116,29 +121,9 @@ class LinuxOpenGLContext::Impl { } explicit Impl(void* borrowedDisplay) { - std::cerr << "[ThermionGL:Context] Initializing EGL/GBM..." << std::endl; - - // Step 1: Open DRM render node - _drmFd = open("/dev/dri/renderD128", O_RDWR); - if (_drmFd < 0) { - _lastError = "Failed to open /dev/dri/renderD128"; - LOG_ERROR("Failed to open /dev/dri/renderD128"); - return; - } - std::cerr << "[ThermionGL:Context] DRM fd=" << _drmFd << std::endl; + std::cerr << "[ThermionGL:Context] Initializing EGL..." << std::endl; - // Step 2: Create GBM device - _gbmDevice = gbm_create_device(_drmFd); - if (!_gbmDevice) { - _lastError = "Failed to create GBM device"; - LOG_ERROR("Failed to create GBM device"); - close(_drmFd); - _drmFd = -1; - return; - } - std::cerr << "[ThermionGL:Context] GBM device created OK" << std::endl; - - // Step 3: Reuse Flutter's initialized display when available. NVIDIA's + // Step 1: Reuse Flutter's initialized display when available. NVIDIA's // EGL implementation can corrupt the concurrently rendering Flutter // context if another platform display is initialized in-process. _display = static_cast(borrowedDisplay); @@ -162,6 +147,9 @@ class LinuxOpenGLContext::Impl { << std::endl; } else { // Non-Flutter fallback: obtain a display tied to the GBM device. + if (!EnsureGbmDevice()) { + return; + } PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress( "eglGetPlatformDisplayEXT"); @@ -198,7 +186,7 @@ class LinuxOpenGLContext::Impl { // EGL_BAD_ACCESS when a second client API is activated there. StartEglThread(); RunOnEglThread([this]() { - // Step 4: Choose EGL config + // Step 2: Choose EGL config // Must bind EGL_OPENGL_API (not ES) to match Filament's // PlatformEGLHeadless which uses full OpenGL 4.1 on Linux desktop. ScopedEglThreadState eglThreadState; @@ -228,7 +216,7 @@ class LinuxOpenGLContext::Impl { return; } - // Step 5: Create EGL context (OpenGL 4.1 to match Filament's + // Step 3: Create EGL context (OpenGL 4.1 to match Filament's // PlatformEGLHeadless). EGLint contextAttribs[] = { EGL_CONTEXT_MAJOR_VERSION, 4, @@ -301,20 +289,139 @@ class LinuxOpenGLContext::Impl { bool IsValid() const { return _display != EGL_NO_DISPLAY && - _context != EGL_NO_CONTEXT && - _gbmDevice != nullptr; + _context != EGL_NO_CONTEXT; } const char* GetLastError() const { return _lastError.c_str(); } + bool ConfigureTextureTransport( + void* consumerContext, uint32_t consumerApi) { + if (!IsValid() || consumerContext == nullptr) { + _lastError = + "Cannot configure texture transport without valid producer " + "and consumer EGL contexts"; + return false; + } + + // First verify that the desktop producer can export a regular + // GL_TEXTURE_2D as an EGLImage. + std::unique_ptr probeTexture; + RunOnEglThread([&]() { + probeTexture = LinuxOpenGLTexture::createEglImage( + _display, _context, _producerSurface, 1, 1); + }); + if (!probeTexture) { + std::cerr + << "[ThermionGL:Context] Same-display EGLImage export probe " + "failed; using DMA-BUF" + << std::endl; + if (!EnsureGbmDevice()) { + return false; + } + _textureTransport = TextureTransport::DMA_BUF; + return true; + } + + // Import through the utility context that shares with Flutter's real + // raster context. This runs only after bootstrap populate() has + // returned, so it never changes EGL state inside Flutter's callback. + // Keep the probe on the isolated EGL worker: NVIDIA can return + // EGL_BAD_ACCESS when a second client API is made current on Flutter's + // GTK platform thread. + bool imported = false; + RunOnEglThread([&]() { + auto imageTargetTexture = + reinterpret_cast( + eglGetProcAddress("glEGLImageTargetTexture2DOES")); + if (imageTargetTexture) { + ScopedEglThreadState eglThreadState; + if (!eglBindAPI(static_cast(consumerApi))) { + std::cerr + << "[ThermionGL:Context] EGLImage import probe could " + "not bind Flutter's EGL API: 0x" + << std::hex << eglGetError() << std::dec << std::endl; + } else if (!eglMakeCurrent( + _display, EGL_NO_SURFACE, EGL_NO_SURFACE, + static_cast(consumerContext))) { + std::cerr + << "[ThermionGL:Context] EGLImage import probe could " + "not make the Flutter utility context current: 0x" + << std::hex << eglGetError() << std::dec << std::endl; + } else { + while (glGetError() != GL_NO_ERROR) {} + GLuint consumerTexture = 0; + glGenTextures(1, &consumerTexture); + glBindTexture(GL_TEXTURE_2D, consumerTexture); + 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); + imageTargetTexture( + GL_TEXTURE_2D, probeTexture->GetEGLImage()); + GLenum importError = glGetError(); + imported = + consumerTexture != 0 && importError == GL_NO_ERROR; + if (!imported) { + std::cerr + << "[ThermionGL:Context] EGLImage import probe " + "failed, GL error: 0x" + << std::hex << importError << std::dec + << std::endl; + } + glBindTexture(GL_TEXTURE_2D, 0); + if (consumerTexture != 0) { + glDeleteTextures(1, &consumerTexture); + } + } + } else { + std::cerr + << "[ThermionGL:Context] " + "glEGLImageTargetTexture2DOES is unavailable" + << std::endl; + } + }); + + // The source texture and its EGLImage belong to the producer context. + RunOnEglThread([&]() { + probeTexture.reset(); + }); + + if (!imported && !EnsureGbmDevice()) { + return false; + } + _textureTransport = imported ? TextureTransport::EGL_IMAGE + : TextureTransport::DMA_BUF; + std::cerr + << "[ThermionGL:Context] Selected " + << (imported ? "same-display EGLImage" : "GBM/DMA-BUF") + << " texture transport" << std::endl; + return true; + } + + bool UsesEglImageTextureTransport() const { + return _textureTransport == TextureTransport::EGL_IMAGE; + } + int64_t CreateRenderingSurface(uint32_t width, uint32_t height) { std::unique_ptr texture; RunOnEglThread([&]() { - texture = LinuxOpenGLTexture::create( - _display, _context, _producerSurface, _gbmDevice, width, - height); + if (_textureTransport == TextureTransport::EGL_IMAGE) { + texture = LinuxOpenGLTexture::createEglImage( + _display, _context, _producerSurface, width, height); + } else { + if (!EnsureGbmDevice()) { + return; + } + texture = LinuxOpenGLTexture::createDmaBuf( + _display, _context, _producerSurface, _gbmDevice, width, + height); + } }); if (!texture) { LOG_ERROR("Failed to create OpenGL rendering surface"); @@ -342,6 +449,14 @@ class LinuxOpenGLContext::Impl { return it != _surfaces.end() ? it->second->GetGLTextureId() : 0; } + void* GetEGLImage(int64_t surfaceId) { + auto it = _surfaces.find(surfaceId); + if (it == _surfaces.end()) { + return nullptr; + } + return static_cast(it->second->GetEGLImage()); + } + SurfaceExportInfo GetSurfaceExportInfo(int64_t surfaceId) { auto it = _surfaces.find(surfaceId); if (it == _surfaces.end()) { @@ -371,6 +486,34 @@ class LinuxOpenGLContext::Impl { } private: + bool EnsureGbmDevice() { + if (_gbmDevice) { + return true; + } + if (_drmFd < 0) { + _drmFd = open("/dev/dri/renderD128", O_RDWR); + if (_drmFd < 0) { + _lastError = "Failed to open /dev/dri/renderD128 for DMA-BUF"; + LOG_ERROR("Failed to open /dev/dri/renderD128"); + return false; + } + std::cerr << "[ThermionGL:Context] DMA-BUF DRM fd=" << _drmFd + << std::endl; + } + + _gbmDevice = gbm_create_device(_drmFd); + if (!_gbmDevice) { + _lastError = "Failed to create GBM device for DMA-BUF"; + LOG_ERROR("Failed to create GBM device"); + close(_drmFd); + _drmFd = -1; + return false; + } + std::cerr << "[ThermionGL:Context] DMA-BUF GBM device created" + << std::endl; + return true; + } + void StartEglThread() { _eglThread = std::thread([this]() { while (true) { @@ -433,6 +576,7 @@ class LinuxOpenGLContext::Impl { std::condition_variable _taskReady; std::deque> _tasks; bool _stopEglThread = false; + TextureTransport _textureTransport = TextureTransport::DMA_BUF; std::unordered_map> _surfaces; int64_t _nextSurfaceId = 1; @@ -453,6 +597,15 @@ const char* LinuxOpenGLContext::GetLastError() const { return pImpl->GetLastError(); } +bool LinuxOpenGLContext::ConfigureTextureTransport( + void* consumerContext, uint32_t consumerApi) { + return pImpl->ConfigureTextureTransport(consumerContext, consumerApi); +} + +bool LinuxOpenGLContext::UsesEglImageTextureTransport() const { + return pImpl->UsesEglImageTextureTransport(); +} + int64_t LinuxOpenGLContext::CreateRenderingSurface(uint32_t width, uint32_t height) { return pImpl->CreateRenderingSurface(width, height); } @@ -465,6 +618,10 @@ uint32_t LinuxOpenGLContext::GetGLTextureId(int64_t surfaceId) { return pImpl->GetGLTextureId(surfaceId); } +void* LinuxOpenGLContext::GetEGLImage(int64_t surfaceId) { + return pImpl->GetEGLImage(surfaceId); +} + SurfaceExportInfo LinuxOpenGLContext::GetSurfaceExportInfo(int64_t surfaceId) { return pImpl->GetSurfaceExportInfo(surfaceId); } diff --git a/thermion_dart/native/src/opengl/linux/LinuxOpenGLTexture.cpp b/thermion_dart/native/src/opengl/linux/LinuxOpenGLTexture.cpp index d629db0d6..62243ef69 100644 --- a/thermion_dart/native/src/opengl/linux/LinuxOpenGLTexture.cpp +++ b/thermion_dart/native/src/opengl/linux/LinuxOpenGLTexture.cpp @@ -77,6 +77,12 @@ class ScopedEglContext { }; LinuxOpenGLTexture::~LinuxOpenGLTexture() { + if (_eglImage != EGL_NO_IMAGE && s_eglDestroyImageKHR && + _display != EGL_NO_DISPLAY) { + s_eglDestroyImageKHR(_display, _eglImage); + _eglImage = EGL_NO_IMAGE; + } + if (_glTextureId != 0) { ScopedEglContext context(_display, _context, _surface); if (context.current()) { @@ -91,11 +97,6 @@ LinuxOpenGLTexture::~LinuxOpenGLTexture() { _glTextureId = 0; } - if (_eglImage != EGL_NO_IMAGE && s_eglDestroyImageKHR && _display != EGL_NO_DISPLAY) { - s_eglDestroyImageKHR(_display, _eglImage); - _eglImage = EGL_NO_IMAGE; - } - if (_dmaBufFd >= 0) { close(_dmaBufFd); _dmaBufFd = -1; @@ -107,7 +108,92 @@ LinuxOpenGLTexture::~LinuxOpenGLTexture() { } } -std::unique_ptr LinuxOpenGLTexture::create( +std::unique_ptr LinuxOpenGLTexture::createEglImage( + EGLDisplay display, EGLContext context, EGLSurface surface, + uint32_t width, uint32_t height) +{ + ensureExtensionFunctions(); + if (!s_eglCreateImageKHR || !s_eglDestroyImageKHR) { + std::cerr + << "[ThermionGL:Texture] EGLImage texture functions unavailable" + << std::endl; + return nullptr; + } + + ScopedEglContext scopedContext(display, context, surface); + if (!scopedContext.current()) { + std::cerr + << "[ThermionGL:Texture] Failed to make producer context current " + "for EGLImage texture, EGL error: 0x" + << std::hex << eglGetError() << std::dec << std::endl; + return nullptr; + } + + while (glGetError() != GL_NO_ERROR) {} + + GLuint glTextureId = 0; + glGenTextures(1, &glTextureId); + glBindTexture(GL_TEXTURE_2D, glTextureId); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + 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); + glBindTexture(GL_TEXTURE_2D, 0); + + GLenum textureError = glGetError(); + if (glTextureId == 0 || textureError != GL_NO_ERROR) { + std::cerr + << "[ThermionGL:Texture] Failed to allocate EGLImage source " + "texture, GL error: 0x" + << std::hex << textureError << std::dec << std::endl; + if (glTextureId != 0) { + glDeleteTextures(1, &glTextureId); + } + return nullptr; + } + + EGLint imageAttribs[] = { + EGL_GL_TEXTURE_LEVEL_KHR, 0, + EGL_NONE + }; + EGLImage eglImage = s_eglCreateImageKHR( + display, context, EGL_GL_TEXTURE_2D_KHR, + reinterpret_cast( + static_cast(glTextureId)), + imageAttribs); + if (eglImage == EGL_NO_IMAGE) { + EGLint imageError = eglGetError(); + std::cerr + << "[ThermionGL:Texture] Failed to export GL texture as " + "EGLImage, EGL error: 0x" + << std::hex << imageError << std::dec << std::endl; + glDeleteTextures(1, &glTextureId); + return nullptr; + } + + // Publish producer commands before another client API imports the image. + glFlush(); + + auto texture = + std::unique_ptr(new LinuxOpenGLTexture()); + texture->_glTextureId = glTextureId; + texture->_eglImage = eglImage; + texture->_width = width; + texture->_height = height; + texture->_display = display; + texture->_context = context; + texture->_surface = surface; + + std::cerr << "[ThermionGL:Texture] EGLImage texture created: id=" + << glTextureId << " image=" << eglImage << " " << width << "x" + << height << std::endl; + return texture; +} + +std::unique_ptr LinuxOpenGLTexture::createDmaBuf( EGLDisplay display, EGLContext context, EGLSurface surface, struct gbm_device* gbm, uint32_t width, uint32_t height) { diff --git a/thermion_flutter/thermion_flutter/linux/egl_texture.cc b/thermion_flutter/thermion_flutter/linux/egl_texture.cc index c0c5db7a6..dcd0382d4 100644 --- a/thermion_flutter/thermion_flutter/linux/egl_texture.cc +++ b/thermion_flutter/thermion_flutter/linux/egl_texture.cc @@ -177,24 +177,33 @@ thermion_texture_populate(FlTextureGL *texture, } // Create a new texture on Flutter's context and bind the EGLImage + while (glGetError() != GL_NO_ERROR) {} glGenTextures(1, &self->flutter_gl_texture_id); glBindTexture(GL_TEXTURE_2D, self->flutter_gl_texture_id); 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); - - while (glGetError() != GL_NO_ERROR) {} - s_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, self->egl_image); GLenum glErr = glGetError(); - if (glErr != GL_NO_ERROR) { + glBindTexture(GL_TEXTURE_2D, 0); + if (self->flutter_gl_texture_id == 0 || + glErr != GL_NO_ERROR) { std::cerr << "[ThermionEGL] GL error after EGLImage import: 0x" << std::hex << glErr << std::dec << std::endl; + if (self->flutter_gl_texture_id != 0) { + glDeleteTextures(1, &self->flutter_gl_texture_id); + self->flutter_gl_texture_id = 0; + } + g_set_error( + error, g_quark_from_static_string("thermion"), 3, + "Failed to import EGLImage into Flutter's context " + "(GL error 0x%x)", + glErr); + return FALSE; } - glBindTexture(GL_TEXTURE_2D, 0); self->initialized = TRUE; } @@ -350,6 +359,7 @@ void thermion_texture_gl_init(ThermionTextureGL* self) { self->initialized = FALSE; self->surface_id = -1; self->use_egl_image = FALSE; + self->producer_owned_by_context = FALSE; self->use_direct_sharing = FALSE; self->is_context_bootstrap = FALSE; self->pending_ready_call = nullptr; diff --git a/thermion_flutter/thermion_flutter/linux/egl_texture.h b/thermion_flutter/thermion_flutter/linux/egl_texture.h index b9a282121..564d02a03 100644 --- a/thermion_flutter/thermion_flutter/linux/egl_texture.h +++ b/thermion_flutter/thermion_flutter/linux/egl_texture.h @@ -43,6 +43,9 @@ struct _ThermionTextureGL { // EGLImage bridge path: texture bridged from Filament's context to // Flutter's render context via EGLImage. gboolean use_egl_image; + // The isolated LinuxOpenGLContext owns the source texture and EGLImage. + // Flutter still owns flutter_gl_texture_id. + gboolean producer_owned_by_context; // Flutter-side GL texture (created on Flutter's context, backed by egl_image) GLuint flutter_gl_texture_id; // Direct sharing path: same EGL share group as Flutter, no EGLImage needed diff --git a/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc b/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc index 88fee72c4..b779c5adc 100644 --- a/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc +++ b/thermion_flutter/thermion_flutter/linux/thermion_flutter_plugin.cc @@ -121,15 +121,16 @@ struct _ThermionFlutterPlugin EGLenum flutter_egl_api; // Used for plugin GL operations when Flutter exposes desktop OpenGL. EGLContext utility_egl_context; - // Used to release Flutter-owned texture names when Flutter exposes GLES and - // Filament must run through the cross-API DMA-BUF pathway. + // Used to import and release Flutter-owned texture names when Filament runs + // in an independent desktop-GL share group. EGLContext flutter_utility_egl_context; EGLDisplay egl_display; // Flutter's EGL display EGLConfig egl_config; // config matching Flutter's context gboolean use_direct_opengl; // TRUE only for compatible desktop GL void* thermion_platform; // standalone OpenGLPlatform (EGLHeadless) - // OpenGL path — fallback (LinuxOpenGLContext with GBM/DMA-BUF) + // OpenGL path — isolated desktop producer with same-display EGLImage or + // GBM/DMA-BUF texture transport. thermion::opengl::linux_platform::LinuxOpenGLContext *opengl_context; std::string opengl_initialization_error; @@ -296,11 +297,10 @@ static bool ensure_opengl_context(ThermionFlutterPlugin *self) if (api == EGL_OPENGL_ES_API) { - // Filament's Linux backend is compiled for desktop OpenGL. EGL object - // sharing cannot cross the GLES / desktop-GL API boundary. This is the - // case handled by PR #136's DMA-BUF bridge. Keep both APIs on Flutter's - // captured EGLDisplay; a second GBM EGLDisplay can corrupt NVIDIA's - // concurrently rendering Flutter context. + // Filament's Linux backend is compiled for desktop OpenGL, so its context + // cannot share GL object names directly with Flutter's GLES context. Keep + // both APIs on Flutter's captured EGLDisplay and prefer an EGLImage bridge; + // retain PR #136's DMA-BUF bridge when cross-API image import is unavailable. self->opengl_context = new thermion::opengl::linux_platform::LinuxOpenGLContext( reinterpret_cast(flutterDpy)); @@ -321,6 +321,18 @@ static bool ensure_opengl_context(ThermionFlutterPlugin *self) self->backend_type = 0; return false; } + if (!self->opengl_context->ConfigureTextureTransport( + reinterpret_cast(flutterUtilityCtx), + static_cast(api))) + { + self->opengl_initialization_error = + self->opengl_context->GetLastError(); + eglDestroyContext(flutterDpy, flutterUtilityCtx); + delete self->opengl_context; + self->opengl_context = nullptr; + self->backend_type = 0; + return false; + } self->flutter_egl_context = flutterCtx; self->flutter_egl_api = api; self->flutter_utility_egl_context = flutterUtilityCtx; @@ -328,8 +340,11 @@ static bool ensure_opengl_context(ThermionFlutterPlugin *self) self->egl_config = flutterConfig; self->backend_type = BACKEND_OPENGL; std::cerr - << "[ThermionGL] Flutter uses GLES; selected same-display " - "GBM/DMA-BUF OpenGL fallback" + << "[ThermionGL] Flutter uses GLES; selected " + << (self->opengl_context->UsesEglImageTextureTransport() + ? "same-display EGLImage" + : "GBM/DMA-BUF") + << " OpenGL transport" << std::endl; return true; } @@ -338,7 +353,7 @@ static bool ensure_opengl_context(ThermionFlutterPlugin *self) { std::cerr << "[ThermionGL] Flutter desktop OpenGL " << glMajor << "." << glMinor << " is below Filament's 4.1 requirement; " - "selected same-display GBM/DMA-BUF fallback" + "using an isolated desktop-GL producer" << std::endl; self->opengl_context = new thermion::opengl::linux_platform::LinuxOpenGLContext( @@ -360,12 +375,30 @@ static bool ensure_opengl_context(ThermionFlutterPlugin *self) self->backend_type = 0; return false; } + if (!self->opengl_context->ConfigureTextureTransport( + reinterpret_cast(flutterUtilityCtx), + static_cast(api))) + { + self->opengl_initialization_error = + self->opengl_context->GetLastError(); + eglDestroyContext(flutterDpy, flutterUtilityCtx); + delete self->opengl_context; + self->opengl_context = nullptr; + self->backend_type = 0; + return false; + } self->flutter_egl_context = flutterCtx; self->flutter_egl_api = api; self->flutter_utility_egl_context = flutterUtilityCtx; self->egl_display = flutterDpy; self->egl_config = flutterConfig; self->backend_type = BACKEND_OPENGL; + std::cerr + << "[ThermionGL] Selected " + << (self->opengl_context->UsesEglImageTextureTransport() + ? "same-display EGLImage" + : "GBM/DMA-BUF") + << " OpenGL transport" << std::endl; return true; } @@ -658,7 +691,67 @@ static FlMethodResponse *handle_create_texture_opengl_direct(ThermionFlutterPlug return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); } -// DMA-BUF fallback path for OpenGL +// Same-display EGLImage path for an isolated desktop-GL producer. Filament +// imports the source texture ID from LinuxOpenGLContext's share group; Flutter +// imports the EGLImage into its own GLES/GL share group during populate(). +static FlMethodResponse *handle_create_texture_opengl_egl_image( + ThermionFlutterPlugin *self, int width, int height) +{ + int64_t surfaceId = self->opengl_context->CreateRenderingSurface( + static_cast(width), static_cast(height)); + if (surfaceId < 0) + { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "CREATE_FAILED", "Failed to create EGLImage rendering surface", + nullptr)); + } + + uint32_t glTextureId = + self->opengl_context->GetGLTextureId(surfaceId); + EGLImage eglImage = static_cast( + self->opengl_context->GetEGLImage(surfaceId)); + if (glTextureId == 0 || eglImage == EGL_NO_IMAGE_KHR) + { + self->opengl_context->DestroyRenderingSurface(surfaceId); + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "CREATE_FAILED", "Producer did not export a valid EGLImage texture", + nullptr)); + } + + ThermionTextureGL *textureGL = thermion_texture_gl_create_shared( + static_cast(width), static_cast(height), + glTextureId, eglImage, surfaceId, self->texture_registrar); + textureGL->producer_owned_by_context = TRUE; + FlTexture *flTexture = FL_TEXTURE(textureGL); + if (!fl_texture_registrar_register_texture( + self->texture_registrar, flTexture)) + { + // LinuxOpenGLContext owns the producer texture and EGLImage. + textureGL->gl_texture_id = 0; + textureGL->egl_image = EGL_NO_IMAGE_KHR; + g_object_unref(textureGL); + self->opengl_context->DestroyRenderingSurface(surfaceId); + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "REGISTER_FAILED", "Failed to register EGLImage texture with Flutter", + nullptr)); + } + + self->textures->push_back(textureGL); + int64_t flutterTextureId = fl_texture_get_id(flTexture); + std::cerr << "[ThermionGL] Same-display EGLImage surface=" << surfaceId + << " GL=" << glTextureId << " image=" << eglImage + << " flutterId=" << flutterTextureId << " (" << width << "x" + << height << ")" << std::endl; + + g_autoptr(FlValue) result = fl_value_new_list(); + fl_value_append_take(result, fl_value_new_int(flutterTextureId)); + fl_value_append_take( + result, fl_value_new_int(static_cast(glTextureId))); + fl_value_append_take(result, fl_value_new_int(0)); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +// DMA-BUF compatibility path for OpenGL. static FlMethodResponse *handle_create_texture_opengl_dmabuf(ThermionFlutterPlugin *self, int width, int height) { int64_t surfaceId = self->opengl_context->CreateRenderingSurface( @@ -717,6 +810,10 @@ static FlMethodResponse *handle_create_texture_opengl(ThermionFlutterPlugin *sel { return handle_create_texture_opengl_direct(self, width, height); } + if (self->opengl_context->UsesEglImageTextureTransport()) + { + return handle_create_texture_opengl_egl_image(self, width, height); + } return handle_create_texture_opengl_dmabuf(self, width, height); } @@ -797,6 +894,8 @@ static FlMethodResponse *handle_destroy_texture(ThermionFlutterPlugin *self, FlM int64_t surfaceId = tex->surface_id; gboolean useDirectSharing = tex->use_direct_sharing; gboolean useEglImage = tex->use_egl_image; + gboolean producerOwnedByContext = + tex->producer_owned_by_context; gboolean isContextBootstrap = tex->is_context_bootstrap; GLuint glTextureId = tex->gl_texture_id; GLuint flutterGlTextureId = tex->flutter_gl_texture_id; @@ -820,14 +919,12 @@ static FlMethodResponse *handle_destroy_texture(ThermionFlutterPlugin *self, FlM gboolean initializedOnlyForBootstrapCleanup = FALSE; if (self->backend_type == BACKEND_OPENGL) { - if (useDirectSharing || useEglImage) + if (useDirectSharing) { - // EGLImage bridge: source names belong to Filament's utility group. - // Bootstrap names belong to Flutter's group. + // Bootstrap textures are created in Flutter's raster share group. if (glTextureId != 0 && self->utility_egl_context == EGL_NO_CONTEXT && - self->flutter_utility_egl_context == EGL_NO_CONTEXT && - useDirectSharing) + self->flutter_utility_egl_context == EGL_NO_CONTEXT) { // A bootstrap may be cancelled after populate() but before Dart // requests the driver platform. Import the captured context now so @@ -835,40 +932,71 @@ static FlMethodResponse *handle_destroy_texture(ThermionFlutterPlugin *self, FlM initializedOnlyForBootstrapCleanup = isContextBootstrap && ensure_opengl_context(self); } - EGLContext sourceContext = useDirectSharing && - self->flutter_utility_egl_context != EGL_NO_CONTEXT + EGLContext sourceContext = + self->flutter_utility_egl_context != EGL_NO_CONTEXT ? self->flutter_utility_egl_context : self->utility_egl_context; if (glTextureId != 0 && sourceContext != EGL_NO_CONTEXT) { EglContextGuard guard(self->egl_display); - eglBindAPI(useDirectSharing - ? self->flutter_egl_api - : EGL_OPENGL_API); - eglMakeCurrent(self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - sourceContext); - glDeleteTextures(1, &glTextureId); + eglBindAPI(self->flutter_egl_api); + if (eglMakeCurrent( + self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + sourceContext)) + { + glDeleteTextures(1, &glTextureId); + } } - - if (useEglImage) + } + else if (useEglImage) + { + // Delete Flutter's imported texture before its backing EGLImage. + EGLContext flutterContext = + self->flutter_utility_egl_context != EGL_NO_CONTEXT + ? self->flutter_utility_egl_context + : self->utility_egl_context; + if (flutterGlTextureId != 0 && + flutterContext != EGL_NO_CONTEXT) { - EGLContext flutterContext = - self->flutter_utility_egl_context != EGL_NO_CONTEXT - ? self->flutter_utility_egl_context - : self->utility_egl_context; - if (flutterGlTextureId != 0 && - flutterContext != EGL_NO_CONTEXT) + EglContextGuard guard(self->egl_display); + eglBindAPI(self->flutter_egl_api); + if (eglMakeCurrent( + self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + flutterContext)) { - EglContextGuard guard(self->egl_display); - eglBindAPI(self->flutter_egl_api); - eglMakeCurrent(self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - flutterContext); glDeleteTextures(1, &flutterGlTextureId); } + } + + if (producerOwnedByContext) + { + // LinuxOpenGLContext destroys the EGLImage followed by the source + // texture on its persistent desktop-GL worker. + if (self->opengl_context) + { + self->opengl_context->DestroyRenderingSurface(surfaceId); + } + } + else + { + // Direct desktop-GL mode creates both objects in the plugin's + // utility context. if (eglImage != EGL_NO_IMAGE_KHR) { destroy_egl_image(self->egl_display, eglImage); } + if (glTextureId != 0 && + self->utility_egl_context != EGL_NO_CONTEXT) + { + EglContextGuard guard(self->egl_display); + eglBindAPI(EGL_OPENGL_API); + if (eglMakeCurrent( + self->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + self->utility_egl_context)) + { + glDeleteTextures(1, &glTextureId); + } + } } } else if (self->opengl_context)