Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions thermion_dart/native/include/opengl/linux/LinuxOpenGLContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
20 changes: 11 additions & 9 deletions thermion_dart/native/include/opengl/linux/LinuxOpenGLTexture.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<LinuxOpenGLTexture> create(
static std::unique_ptr<LinuxOpenGLTexture> createEglImage(
EGLDisplay display, EGLContext context, EGLSurface surface,
uint32_t width, uint32_t height);

static std::unique_ptr<LinuxOpenGLTexture> 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; }
Expand Down
215 changes: 186 additions & 29 deletions thermion_dart/native/src/opengl/linux/LinuxOpenGLContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@

namespace thermion::opengl::linux_platform {

enum class TextureTransport {
DMA_BUF,
EGL_IMAGE,
};

class ScopedEglThreadState {
public:
ScopedEglThreadState()
Expand Down Expand Up @@ -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<EGLDisplay>(borrowedDisplay);
Expand All @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<LinuxOpenGLTexture> 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<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(
eglGetProcAddress("glEGLImageTargetTexture2DOES"));
if (imageTargetTexture) {
ScopedEglThreadState eglThreadState;
if (!eglBindAPI(static_cast<EGLenum>(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<EGLContext>(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<LinuxOpenGLTexture> 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");
Expand Down Expand Up @@ -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<void*>(it->second->GetEGLImage());
}

SurfaceExportInfo GetSurfaceExportInfo(int64_t surfaceId) {
auto it = _surfaces.find(surfaceId);
if (it == _surfaces.end()) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -433,6 +576,7 @@ class LinuxOpenGLContext::Impl {
std::condition_variable _taskReady;
std::deque<std::function<void()>> _tasks;
bool _stopEglThread = false;
TextureTransport _textureTransport = TextureTransport::DMA_BUF;

std::unordered_map<int64_t, std::unique_ptr<LinuxOpenGLTexture>> _surfaces;
int64_t _nextSurfaceId = 1;
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
Loading