Files
MobileGL/MobileGL/MG_State/EGLState/Core.cpp
T
BZLZHH 7fd7f71d6c [Feature] (EGL): Wayland window surfaces, presented through wl_shm
A Wayland client's window is its own wl_surface, and what the compositor shows is the wl_buffer
attached to it - but the pixels of a MobileGL frame are drawn where the backend is, on a split
build in another process on another OS.  So far a wl_egl_window was taken for an X11 window and
its client-side address sent to the Android server (EGL_BAD_NATIVE_WINDOW at best), and under
glvnd a Wayland display never reached this vendor at all.

A window on a Wayland display - asked for as EGL_PLATFORM_WAYLAND, or a wl_display recognised by
its first word being wl_display_interface - is now a window surface in the EGL state at the
wl_egl_window's size with a pbuffer of that size behind it in the backend, whatever
MOBILEGL_IPC_SURFACE says.  At every swap the frame is read back out of the default framebuffer
through this library's own glReadPixels (the application's read framebuffer, pack buffer and pack
state put back as they were), its rows flipped and swizzled into a wl_shm buffer, and attached,
damaged and committed on the application's wl_surface.  Three buffers rotate on their release
events, dispatched on an event queue of the surface's own.  libwayland-client is loaded at run
time, so neither the build nor the library depends on it.  EGL_KHR_platform_wayland and
EGL_EXT_platform_wayland are announced on Linux.

Measured: glmark2-wayland runs its build scene at 13 FPS (400x300) and the window shows the frame
under kwin.  A window resize is not followed yet; the surface keeps its creation size.
2026-10-01 10:59:07 -04:00

1737 lines
82 KiB
C++

// MobileGL - MobileGL/MG_State/EGLState/Core.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "Core.h"
#include <EGL/eglext.h>
// An imported buffer is held by descriptors, and the copy an image takes of one is the platform's
// own dup()/close(): unistd.h's where the import exists, io.h's on the Windows build that compiles
// this file but never sees EGL_LINUX_DMA_BUF_EXT.
#if defined(_WIN32)
#include <io.h>
#else
#include <unistd.h>
#endif
#include <cerrno>
namespace MobileGL {
namespace MG_State {
namespace EGLState {
namespace {
constexpr EGLint EGL_DISPLAY_MAJOR_VERSION = 1;
constexpr EGLint EGL_DISPLAY_MINOR_VERSION = 5;
constexpr EGLint DEFAULT_MAX_PBUFFER_SIZE = 16384;
template <typename AttrType>
Optional<AttrType> ParseAttribValue(const AttrType* attribList, EGLint attrib) {
if (!attribList) {
return Nullopt;
}
for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) {
if (attribList[i] == static_cast<AttrType>(attrib)) {
return attribList[i + 1];
}
}
return Nullopt;
}
// Taking and closing the descriptor an image owns, per platform. Named rather
// than spelled inline so the ownership rule reads the same on both.
#if defined(_WIN32)
Int DuplicateImageDescriptor(Int fd) { return _dup(fd); }
void CloseImageDescriptor(Int fd) { _close(fd); }
#else
Int DuplicateImageDescriptor(Int fd) { return ::dup(fd); }
void CloseImageDescriptor(Int fd) { ::close(fd); }
#endif
// ONE PLANE'S ATTRIBUTE SPELLINGS, in plane order. Plane 0's fd/offset/pitch triple
// is EGL_EXT_image_dma_buf_import's; plane 3's triple and every MODIFIER_LO/HI pair
// belong to EGL_EXT_image_dma_buf_import_modifiers.
struct DmaBufPlaneAttributes {
EGLint Fd;
EGLint Offset;
EGLint Pitch;
EGLint ModifierLo;
EGLint ModifierHi;
};
constexpr DmaBufPlaneAttributes kDmaBufPlaneAttributes[EGLContext::kMaxImagePlanes] = {
{EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT,
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT},
{EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT,
EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT},
{EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT,
EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT},
{EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT,
EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT},
};
// The EGL spelling of one plane attribute, for the single line a refused image gets:
// the attribute list is gone by the time the caller reads eglGetError.
String DmaBufPlaneAttributeName(Int plane, const char* role) {
return std::format("EGL_DMA_BUF_PLANE{}_{}", plane, role);
}
// Whether the caller mentioned this plane at all, so that a description with a hole
// in it can be refused by name instead of silently losing the planes past the hole.
Bool HasAnyPlaneAttribute(const EGLAttrib* attribList, Int plane) {
const DmaBufPlaneAttributes& attributes = kDmaBufPlaneAttributes[plane];
return ParseAttribValue(attribList, attributes.Fd) || ParseAttribValue(attribList, attributes.Offset) ||
ParseAttribValue(attribList, attributes.Pitch) ||
ParseAttribValue(attribList, attributes.ModifierLo) ||
ParseAttribValue(attribList, attributes.ModifierHi);
}
EGLint QueryDefaultX11VisualId() {
#if defined(__linux__) && !defined(__ANDROID__)
const char* displayName = std::getenv("DISPLAY");
if (!displayName) {
return 0;
}
void* x11Lib = dlopen("libX11.so.6", RTLD_LOCAL | RTLD_NOW);
if (!x11Lib) {
x11Lib = dlopen("libX11.so", RTLD_LOCAL | RTLD_NOW);
}
if (!x11Lib) {
return 0;
}
using XOpenDisplayFn = void* (*)(const char*);
using XDefaultScreenFn = int (*)(void*);
using XDefaultVisualFn = void* (*)(void*, int);
using XVisualIDFromVisualFn = unsigned long (*)(void*);
using XCloseDisplayFn = int (*)(void*);
auto* xOpenDisplay = reinterpret_cast<XOpenDisplayFn>(dlsym(x11Lib, "XOpenDisplay"));
auto* xDefaultScreen = reinterpret_cast<XDefaultScreenFn>(dlsym(x11Lib, "XDefaultScreen"));
auto* xDefaultVisual = reinterpret_cast<XDefaultVisualFn>(dlsym(x11Lib, "XDefaultVisual"));
auto* xVisualIDFromVisual =
reinterpret_cast<XVisualIDFromVisualFn>(dlsym(x11Lib, "XVisualIDFromVisual"));
auto* xCloseDisplay = reinterpret_cast<XCloseDisplayFn>(dlsym(x11Lib, "XCloseDisplay"));
if (!xOpenDisplay || !xDefaultScreen || !xDefaultVisual || !xVisualIDFromVisual || !xCloseDisplay) {
dlclose(x11Lib);
return 0;
}
void* display = xOpenDisplay(displayName);
if (!display) {
dlclose(x11Lib);
return 0;
}
const int screen = xDefaultScreen(display);
void* visual = xDefaultVisual(display, screen);
const auto visualId = visual ? static_cast<EGLint>(xVisualIDFromVisual(visual)) : 0;
xCloseDisplay(display);
dlclose(x11Lib);
return visualId;
#else
return 0;
#endif
}
} // namespace
Bool EGLContext::DisplayLookupKey::operator==(const DisplayLookupKey& rhs) const {
return this->NativeDisplayKey == rhs.NativeDisplayKey && this->Platform == rhs.Platform;
}
SizeT EGLContext::DisplayLookupHasher::operator()(const DisplayLookupKey& key) const {
const auto nativeHash = std::hash<Uint64>{}(key.NativeDisplayKey);
const auto platformHash = std::hash<Uint64>{}(static_cast<Uint64>(key.Platform));
return nativeHash ^ (platformHash + 0x9e3779b97f4a7c15ull + (nativeHash << 6) + (nativeHash >> 2));
}
std::thread::id EGLContext::CurrentThreadKey() {
return std::this_thread::get_id();
}
void EGLContext::SetError(EGLint errorCode) {
if (errorCode == EGL_SUCCESS) {
return;
}
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
m_threadErrors[CurrentThreadKey()] = errorCode;
}
EGLint EGLContext::ConsumeError() {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto threadKey = CurrentThreadKey();
auto errorIt = m_threadErrors.find(threadKey);
if (errorIt == m_threadErrors.end()) {
return EGL_SUCCESS;
}
const EGLint error = errorIt->second;
m_threadErrors.erase(errorIt);
return error;
}
void EGLContext::SetBoundAPI(EGLenum api) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
m_threadBoundAPI[CurrentThreadKey()] = api;
}
EGLenum EGLContext::GetBoundAPI() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto apiIt = m_threadBoundAPI.find(CurrentThreadKey());
if (apiIt == m_threadBoundAPI.end()) {
return EGL_OPENGL_API;
}
return apiIt->second;
}
EGLContext::EGLDisplayHandle EGLContext::GetOrCreateDisplay(Uint64 nativeDisplayKey, EGLenum platform) {
const DisplayLookupKey key = {
.NativeDisplayKey = nativeDisplayKey,
.Platform = platform,
};
auto lookupIt = m_displayLookup.find(key);
if (lookupIt != m_displayLookup.end()) {
return lookupIt->second;
}
const auto display = EncodeHandle<EGLDisplayHandle>(m_nextDisplayHandle++);
DisplayObject displayObject = {
.NativeDisplayKey = nativeDisplayKey,
.Platform = platform,
.Initialized = false,
.MajorVersion = EGL_DISPLAY_MAJOR_VERSION,
.MinorVersion = EGL_DISPLAY_MINOR_VERSION,
.SwapInterval = 1,
};
displayObject.Configs.push_back(CreateDefaultConfig(display, 1, 0));
displayObject.Configs.push_back(CreateDefaultConfig(display, 2, 8));
m_displays[display] = displayObject;
m_displayLookup[key] = display;
return display;
}
EGLContext::EGLConfigHandle EGLContext::CreateDefaultConfig(EGLDisplayHandle display, EGLint configId,
EGLint stencilSize) {
const auto config = EncodeHandle<EGLConfigHandle>(m_nextConfigHandle++);
ConfigObject cfg = {
.Display = display,
.ConfigId = configId,
.RedSize = 8,
.GreenSize = 8,
.BlueSize = 8,
.AlphaSize = 8,
.DepthSize = 24,
.StencilSize = stencilSize,
.SurfaceType = EGL_WINDOW_BIT | EGL_PBUFFER_BIT | EGL_PIXMAP_BIT,
.RenderableType = EGL_OPENGL_BIT | EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT,
.MinSwapInterval = 0,
.MaxSwapInterval = 4,
.NativeVisualId = QueryDefaultX11VisualId(),
};
#if defined(ANDROID) || defined(__ANDROID__)
cfg.NativeVisualId = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
#endif
m_configs[config] = cfg;
return config;
}
EGLContext::DisplayObject* EGLContext::TryGetDisplay(EGLDisplayHandle display) {
auto it = m_displays.find(display);
return it == m_displays.end() ? nullptr : &it->second;
}
const EGLContext::DisplayObject* EGLContext::TryGetDisplay(EGLDisplayHandle display) const {
auto it = m_displays.find(display);
return it == m_displays.end() ? nullptr : &it->second;
}
const EGLContext::ConfigObject* EGLContext::TryGetConfig(EGLConfigHandle config) const {
auto it = m_configs.find(config);
return it == m_configs.end() ? nullptr : &it->second;
}
const EGLContext::ContextObject* EGLContext::TryGetContext(EGLContextHandle context) const {
auto it = m_contexts.find(context);
return it == m_contexts.end() ? nullptr : &it->second;
}
const EGLContext::SurfaceObject* EGLContext::TryGetSurface(EGLSurfaceHandle surface) const {
auto it = m_surfaces.find(surface);
return it == m_surfaces.end() ? nullptr : &it->second;
}
const EGLContext::SyncObject* EGLContext::TryGetSync(EGLSyncHandle sync) const {
auto it = m_syncs.find(sync);
return it == m_syncs.end() ? nullptr : &it->second;
}
const EGLContext::ImageObject* EGLContext::TryGetImage(EGLImageHandle image) const {
auto it = m_images.find(image);
return it == m_images.end() ? nullptr : &it->second;
}
void EGLContext::ReleaseThreadUnlocked(const std::thread::id& threadKey) {
auto currentIt = m_threadCurrents.find(threadKey);
if (currentIt == m_threadCurrents.end()) {
return;
}
const EGLSurfaceHandle drawSurface = currentIt->second.DrawSurface;
const EGLSurfaceHandle readSurface = currentIt->second.ReadSurface;
const EGLContextHandle context = currentIt->second.Context;
if (context != nullptr) {
auto ownerIt = m_contextOwners.find(context);
if (ownerIt != m_contextOwners.end() && ownerIt->second == threadKey) {
m_contextOwners.erase(ownerIt);
}
}
m_threadCurrents.erase(currentIt);
DestroyPendingSurfaceIfUnused(drawSurface);
DestroyPendingSurfaceIfUnused(readSurface);
}
Bool EGLContext::IsSurfaceCurrentUnlocked(EGLSurfaceHandle surface) const {
if (surface == EGL_NO_SURFACE) {
return false;
}
for (const auto& current : m_threadCurrents) {
if (current.second.DrawSurface == surface || current.second.ReadSurface == surface) {
return true;
}
}
return false;
}
void EGLContext::DestroyPendingSurfaceIfUnused(EGLSurfaceHandle surface) {
auto surfaceIt = m_surfaces.find(surface);
if (surfaceIt == m_surfaces.end() || !surfaceIt->second.DestroyPending ||
IsSurfaceCurrentUnlocked(surface)) {
return;
}
m_surfaces.erase(surfaceIt);
}
void EGLContext::ReleaseDisplayObjects(EGLDisplayHandle display) {
Vector<EGLContextHandle> removedContexts;
for (auto contextIt = m_contexts.begin(); contextIt != m_contexts.end();) {
if (contextIt->second.Display == display) {
removedContexts.push_back(contextIt->first);
contextIt = m_contexts.erase(contextIt);
} else {
++contextIt;
}
}
for (const auto context : removedContexts) {
m_contextOwners.erase(context);
}
for (auto surfaceIt = m_surfaces.begin(); surfaceIt != m_surfaces.end();) {
if (surfaceIt->second.Display == display) {
surfaceIt = m_surfaces.erase(surfaceIt);
} else {
++surfaceIt;
}
}
for (auto syncIt = m_syncs.begin(); syncIt != m_syncs.end();) {
if (syncIt->second.Display == display) {
syncIt = m_syncs.erase(syncIt);
} else {
++syncIt;
}
}
for (auto imageIt = m_images.begin(); imageIt != m_images.end();) {
if (imageIt->second.Display == display) {
// The display is going away and takes its images with it, so the descriptors
// they own go too: an image erased without this leaks an fd per plane.
ReleaseImageDescriptors(imageIt->second);
imageIt = m_images.erase(imageIt);
} else {
++imageIt;
}
}
for (auto threadIt = m_threadCurrents.begin(); threadIt != m_threadCurrents.end();) {
if (threadIt->second.Display == display) {
if (threadIt->second.Context != nullptr) {
auto ownerIt = m_contextOwners.find(threadIt->second.Context);
if (ownerIt != m_contextOwners.end() && ownerIt->second == threadIt->first) {
m_contextOwners.erase(ownerIt);
}
}
threadIt = m_threadCurrents.erase(threadIt);
} else {
++threadIt;
}
}
}
EGLContext::EGLDisplayHandle EGLContext::GetDisplay(NativeDisplayType nativeDisplay) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
return GetOrCreateDisplay(ToNativeKey(nativeDisplay), EGL_NONE);
}
EGLContext::EGLDisplayHandle EGLContext::GetPlatformDisplay(EGLenum platform, void* nativeDisplay) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
return GetOrCreateDisplay(ToNativeKey(nativeDisplay), platform);
}
Bool EGLContext::ValidateDisplay(EGLDisplayHandle display) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
return m_displays.find(display) != m_displays.end();
}
Bool EGLContext::IsDisplayInitialized(EGLDisplayHandle display) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* displayObject = TryGetDisplay(display);
return displayObject && displayObject->Initialized;
}
Bool EGLContext::GetDisplayNative(EGLDisplayHandle display, Uint64* nativeDisplayKey,
EGLenum* platform) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* displayObject = TryGetDisplay(display);
if (!displayObject) return false;
*nativeDisplayKey = displayObject->NativeDisplayKey;
*platform = displayObject->Platform;
return true;
}
Bool EGLContext::InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto* displayObject = TryGetDisplay(display);
if (!displayObject) {
SetError(EGL_BAD_DISPLAY);
return false;
}
displayObject->Initialized = true;
displayObject->MajorVersion = EGL_DISPLAY_MAJOR_VERSION;
displayObject->MinorVersion = EGL_DISPLAY_MINOR_VERSION;
if (major) {
*major = displayObject->MajorVersion;
}
if (minor) {
*minor = displayObject->MinorVersion;
}
return true;
}
Bool EGLContext::TerminateDisplay(EGLDisplayHandle display) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto* displayObject = TryGetDisplay(display);
if (!displayObject) {
SetError(EGL_BAD_DISPLAY);
return false;
}
ReleaseDisplayObjects(display);
displayObject->Initialized = false;
return true;
}
Bool EGLContext::HasAnyInitializedDisplay() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
for (const auto& [handle, displayObject] : m_displays) {
if (displayObject.Initialized) {
return true;
}
}
return false;
}
Bool EGLContext::HasAnyCurrentContext() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
for (const auto& [threadId, current] : m_threadCurrents) {
if (current.Context != nullptr) {
return true;
}
}
return false;
}
Bool EGLContext::ChooseConfig(EGLDisplayHandle display, const EGLint* attribList, EGLConfigHandle* configs,
EGLint configSize, EGLint* numConfig) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto* displayObject = TryGetDisplay(display);
if (!displayObject) {
SetError(EGL_BAD_DISPLAY);
return false;
}
if (!displayObject->Initialized) {
SetError(EGL_NOT_INITIALIZED);
return false;
}
if (!numConfig) {
SetError(EGL_BAD_PARAMETER);
return false;
}
Vector<EGLConfigHandle> matchedConfigs;
for (const auto config : displayObject->Configs) {
const auto* cfg = TryGetConfig(config);
if (!cfg) {
continue;
}
Bool matches = true;
if (attribList) {
for (SizeT i = 0; attribList[i] != EGL_NONE; i += 2) {
const EGLint attr = attribList[i];
const EGLint requested = attribList[i + 1];
// WHAT DIES HERE, exactly: with an attribute list present this loop is where
// eglChooseConfig faults (a NULL list survives it and answers two configs),
// and the fault is one instruction inside the attribute switch. These two
// lines name the attribute and the config before either is dereferenced.
MGLOG_I("chooseConfig: attr=0x%04x requested=%d config=%p", attr, requested,
static_cast<const void*>(cfg));
if (requested == EGL_DONT_CARE) {
continue;
}
switch (attr) {
case EGL_CONFIG_ID:
matches = cfg->ConfigId == requested;
break;
case EGL_RED_SIZE:
matches = cfg->RedSize >= requested;
break;
case EGL_GREEN_SIZE:
matches = cfg->GreenSize >= requested;
break;
case EGL_BLUE_SIZE:
matches = cfg->BlueSize >= requested;
break;
case EGL_ALPHA_SIZE:
matches = cfg->AlphaSize >= requested;
break;
case EGL_BUFFER_SIZE:
matches = (cfg->RedSize + cfg->GreenSize + cfg->BlueSize + cfg->AlphaSize) >= requested;
break;
case EGL_DEPTH_SIZE:
matches = cfg->DepthSize >= requested;
break;
case EGL_STENCIL_SIZE:
matches = cfg->StencilSize >= requested;
break;
case EGL_SURFACE_TYPE:
matches = (cfg->SurfaceType & requested) == requested;
break;
case EGL_RENDERABLE_TYPE:
case EGL_CONFORMANT:
matches = (cfg->RenderableType & requested) == requested;
break;
case EGL_SAMPLE_BUFFERS:
case EGL_SAMPLES:
matches = requested == 0;
break;
case EGL_NATIVE_VISUAL_ID:
matches = cfg->NativeVisualId == requested;
break;
default:
break;
}
if (!matches) {
break;
}
}
}
if (matches) {
matchedConfigs.push_back(config);
}
}
*numConfig = static_cast<EGLint>(matchedConfigs.size());
if (!configs || configSize <= 0) {
return true;
}
const SizeT copyCount = std::min(static_cast<SizeT>(configSize), matchedConfigs.size());
for (SizeT i = 0; i < copyCount; ++i) {
configs[i] = matchedConfigs[i];
}
return true;
}
Bool EGLContext::GetConfigs(EGLDisplayHandle display, EGLConfigHandle* configs, EGLint configSize,
EGLint* numConfig) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
return ChooseConfig(display, nullptr, configs, configSize, numConfig);
}
Bool EGLContext::ValidateConfig(EGLConfigHandle config) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
return m_configs.find(config) != m_configs.end();
}
Bool EGLContext::ValidateConfigOnDisplay(EGLDisplayHandle display, EGLConfigHandle config) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* cfg = TryGetConfig(config);
return cfg && cfg->Display == display;
}
Bool EGLContext::GetConfigAttrib(EGLDisplayHandle display, EGLConfigHandle config, EGLint attribute,
EGLint* value) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!value) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_PARAMETER);
return false;
}
const auto* cfg = TryGetConfig(config);
if (!cfg) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_CONFIG);
return false;
}
if (cfg->Display != display) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_MATCH);
return false;
}
switch (attribute) {
case EGL_BUFFER_SIZE:
*value = cfg->RedSize + cfg->GreenSize + cfg->BlueSize + cfg->AlphaSize;
return true;
case EGL_ALPHA_MASK_SIZE:
*value = 0;
return true;
case EGL_BIND_TO_TEXTURE_RGB:
case EGL_BIND_TO_TEXTURE_RGBA:
*value = EGL_FALSE;
return true;
case EGL_COLOR_BUFFER_TYPE:
*value = EGL_RGB_BUFFER;
return true;
case EGL_CONFIG_CAVEAT:
*value = EGL_NONE;
return true;
case EGL_CONFIG_ID:
*value = cfg->ConfigId;
return true;
case EGL_LEVEL:
*value = 0;
return true;
case EGL_LUMINANCE_SIZE:
*value = 0;
return true;
case EGL_MAX_PBUFFER_WIDTH:
case EGL_MAX_PBUFFER_HEIGHT:
*value = DEFAULT_MAX_PBUFFER_SIZE;
return true;
case EGL_MAX_PBUFFER_PIXELS:
*value = DEFAULT_MAX_PBUFFER_SIZE * DEFAULT_MAX_PBUFFER_SIZE;
return true;
case EGL_RED_SIZE:
*value = cfg->RedSize;
return true;
case EGL_GREEN_SIZE:
*value = cfg->GreenSize;
return true;
case EGL_BLUE_SIZE:
*value = cfg->BlueSize;
return true;
case EGL_ALPHA_SIZE:
*value = cfg->AlphaSize;
return true;
case EGL_DEPTH_SIZE:
*value = cfg->DepthSize;
return true;
case EGL_STENCIL_SIZE:
*value = cfg->StencilSize;
return true;
case EGL_SURFACE_TYPE:
*value = cfg->SurfaceType;
return true;
case EGL_RENDERABLE_TYPE:
case EGL_CONFORMANT:
*value = cfg->RenderableType;
return true;
case EGL_MIN_SWAP_INTERVAL:
*value = cfg->MinSwapInterval;
return true;
case EGL_MAX_SWAP_INTERVAL:
*value = cfg->MaxSwapInterval;
return true;
case EGL_NATIVE_RENDERABLE:
*value = EGL_TRUE;
return true;
case EGL_NATIVE_VISUAL_ID:
*value = cfg->NativeVisualId;
return true;
case EGL_NATIVE_VISUAL_TYPE:
*value = cfg->NativeVisualId;
return true;
case EGL_SAMPLE_BUFFERS:
case EGL_SAMPLES:
*value = 0;
return true;
case EGL_TRANSPARENT_TYPE:
*value = EGL_NONE;
return true;
case EGL_TRANSPARENT_RED_VALUE:
case EGL_TRANSPARENT_GREEN_VALUE:
case EGL_TRANSPARENT_BLUE_VALUE:
*value = 0;
return true;
default:
const_cast<EGLContext*>(this)->SetError(EGL_BAD_ATTRIBUTE);
return false;
}
}
EGLContext::EGLContextHandle EGLContext::CreateContext(EGLDisplayHandle display, EGLConfigHandle config,
EGLContextHandle shareCtx,
const EGLint* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* displayObject = TryGetDisplay(display);
if (!displayObject) {
SetError(EGL_BAD_DISPLAY);
return nullptr;
}
if (!displayObject->Initialized) {
SetError(EGL_NOT_INITIALIZED);
return nullptr;
}
// EGL_KHR_no_config_context: a context with NO config of its own. It is the shape a
// compositor asks for - its contexts are used with whatever framebuffer the frame
// needs, so pinning one config to the context would be a claim about the future the
// caller does not have - and EGL spells the request as this null handle. A context
// that does name a config is validated exactly as before.
const Bool noConfigContext = (config == nullptr);
if (!noConfigContext && !ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return nullptr;
}
if (shareCtx != nullptr && !ValidateContextOnDisplay(display, shareCtx)) {
SetError(EGL_BAD_CONTEXT);
return nullptr;
}
ContextObject contextObject = {
.Display = display,
.Config = config,
.SharedContext = shareCtx,
.ClientAPI = GetBoundAPI(),
.ClientVersion = 1,
.MajorVersion = 1,
.MinorVersion = 0,
};
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_CLIENT_VERSION); value) {
contextObject.ClientVersion = *value;
contextObject.MajorVersion = *value;
}
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_MAJOR_VERSION); value) {
contextObject.MajorVersion = *value;
}
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_MINOR_VERSION); value) {
contextObject.MinorVersion = *value;
}
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_PROFILE_MASK); value) {
contextObject.OpenGLProfileMask = *value;
}
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_FLAGS_KHR); value) {
contextObject.EGLContextFlags = *value;
if (*value & EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR) {
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT;
}
if (*value & EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR) {
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_DEBUG_BIT;
}
if (*value & EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR) {
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT;
}
}
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE);
value && *value == EGL_TRUE) {
contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT;
}
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_DEBUG); value && *value == EGL_TRUE) {
contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_DEBUG_BIT;
}
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_ROBUST_ACCESS);
value && *value == EGL_TRUE) {
contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT;
}
const auto context = EncodeHandle<EGLContextHandle>(m_nextContextHandle++);
m_contexts[context] = contextObject;
return context;
}
Bool EGLContext::DestroyContext(EGLDisplayHandle display, EGLContextHandle context) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto contextIt = m_contexts.find(context);
if (contextIt == m_contexts.end()) {
SetError(EGL_BAD_CONTEXT);
return false;
}
if (contextIt->second.Display != display) {
SetError(EGL_BAD_MATCH);
return false;
}
auto ownerIt = m_contextOwners.find(context);
if (ownerIt != m_contextOwners.end()) {
SetError(EGL_BAD_ACCESS);
return false;
}
m_contexts.erase(contextIt);
return true;
}
Bool EGLContext::QueryContext(EGLDisplayHandle display, EGLContextHandle context, EGLint attribute,
EGLint* value) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!value) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_PARAMETER);
return false;
}
const auto* contextObject = TryGetContext(context);
if (!contextObject) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_CONTEXT);
return false;
}
if (contextObject->Display != display) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_MATCH);
return false;
}
switch (attribute) {
case EGL_CONTEXT_CLIENT_TYPE:
*value = static_cast<EGLint>(contextObject->ClientAPI);
return true;
case EGL_CONTEXT_CLIENT_VERSION:
#if EGL_CONTEXT_MAJOR_VERSION != EGL_CONTEXT_CLIENT_VERSION
case EGL_CONTEXT_MAJOR_VERSION:
#endif
*value = contextObject->MajorVersion;
return true;
case EGL_CONTEXT_MINOR_VERSION:
*value = contextObject->MinorVersion;
return true;
case EGL_CONTEXT_FLAGS_KHR:
*value = contextObject->EGLContextFlags;
return true;
case EGL_CONFIG_ID: {
const auto* cfg = TryGetConfig(contextObject->Config);
if (!cfg) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_CONFIG);
return false;
}
*value = cfg->ConfigId;
return true;
}
default:
const_cast<EGLContext*>(this)->SetError(EGL_BAD_ATTRIBUTE);
return false;
}
}
Bool EGLContext::ValidateContext(EGLContextHandle context) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
return m_contexts.find(context) != m_contexts.end();
}
Bool EGLContext::ValidateContextOnDisplay(EGLDisplayHandle display, EGLContextHandle context) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* ctx = TryGetContext(context);
return ctx && ctx->Display == display;
}
Bool EGLContext::IsCurrentContextOpenGLCoreProfile() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return false;
}
const auto* ctx = TryGetContext(currentIt->second.Context);
if (!ctx || ctx->ClientAPI != EGL_OPENGL_API ||
(ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT)) {
return false;
}
return (ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT) ||
ctx->MajorVersion > 3 || (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1);
}
Bool EGLContext::IsCurrentContextOpenGLCompatibilityProfile() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return false;
}
const auto* ctx = TryGetContext(currentIt->second.Context);
// Affirmative check: true only when the host explicitly requested the
// compatibility bit. Attrib-less contexts report false and thus read as core
// for GL_CONTEXT_PROFILE_MASK (EGL defaults 3.x contexts to the core profile).
return ctx && ctx->ClientAPI == EGL_OPENGL_API &&
(ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT);
}
EGLint EGLContext::GetCurrentContextFlags() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return 0;
}
const auto* ctx = TryGetContext(currentIt->second.Context);
return ctx ? ctx->OpenGLContextFlags : 0;
}
EGLContext::EGLSurfaceHandle EGLContext::CreateWindowSurface(EGLDisplayHandle display,
EGLConfigHandle config,
NativeWindowType window,
const EGLint* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
(void)attribList;
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE;
}
if (!ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return EGL_NO_SURFACE;
}
if (ToNativeKey(window) == 0) {
SetError(EGL_BAD_NATIVE_WINDOW);
return EGL_NO_SURFACE;
}
const auto surface = EncodeHandle<EGLSurfaceHandle>(m_nextSurfaceHandle++);
m_surfaces[surface] = SurfaceObject{
.Display = display,
.Config = config,
.Type = SurfaceType::Window,
.NativeHandleKey = ToNativeKey(window),
.ClientBuffer = nullptr,
.BufferType = EGL_NONE,
};
return surface;
}
EGLContext::EGLSurfaceHandle EGLContext::CreatePbufferSurface(EGLDisplayHandle display,
EGLConfigHandle config,
const EGLint* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE;
}
if (!ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return EGL_NO_SURFACE;
}
EGLint width = 1;
EGLint height = 1;
if (auto value = ParseAttribValue(attribList, EGL_WIDTH); value) {
width = *value;
}
if (auto value = ParseAttribValue(attribList, EGL_HEIGHT); value) {
height = *value;
}
if (width < 0 || height < 0) {
SetError(EGL_BAD_ATTRIBUTE);
return EGL_NO_SURFACE;
}
const auto surface = EncodeHandle<EGLSurfaceHandle>(m_nextSurfaceHandle++);
m_surfaces[surface] = SurfaceObject{
.Display = display,
.Config = config,
.Type = SurfaceType::Pbuffer,
.NativeHandleKey = 0,
.ClientBuffer = nullptr,
.BufferType = EGL_NONE,
.Width = width,
.Height = height,
.TextureFormat = ParseAttribValue(attribList, EGL_TEXTURE_FORMAT).value_or(EGL_NO_TEXTURE),
.TextureTarget = ParseAttribValue(attribList, EGL_TEXTURE_TARGET).value_or(EGL_NO_TEXTURE),
.MipmapLevel = ParseAttribValue(attribList, EGL_MIPMAP_LEVEL).value_or(0),
.MipmapTexture = ParseAttribValue(attribList, EGL_MIPMAP_TEXTURE).value_or(EGL_FALSE),
};
return surface;
}
EGLContext::EGLSurfaceHandle EGLContext::CreatePixmapSurface(EGLDisplayHandle display,
EGLConfigHandle config,
EGLNativePixmapType pixmap,
const EGLint* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
(void)attribList;
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE;
}
if (!ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return EGL_NO_SURFACE;
}
if (ToNativeKey(pixmap) == 0) {
SetError(EGL_BAD_NATIVE_PIXMAP);
return EGL_NO_SURFACE;
}
const auto surface = EncodeHandle<EGLSurfaceHandle>(m_nextSurfaceHandle++);
m_surfaces[surface] = SurfaceObject{
.Display = display,
.Config = config,
.Type = SurfaceType::Pixmap,
.NativeHandleKey = ToNativeKey(pixmap),
.ClientBuffer = nullptr,
.BufferType = EGL_NONE,
};
return surface;
}
EGLContext::EGLSurfaceHandle EGLContext::CreatePbufferFromClientBuffer(EGLDisplayHandle display,
EGLenum bufferType,
EGLClientBuffer buffer,
EGLConfigHandle config,
const EGLint* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE;
}
if (!ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return EGL_NO_SURFACE;
}
if (!buffer) {
SetError(EGL_BAD_PARAMETER);
return EGL_NO_SURFACE;
}
EGLint width = 1;
EGLint height = 1;
if (auto value = ParseAttribValue(attribList, EGL_WIDTH); value) {
width = *value;
}
if (auto value = ParseAttribValue(attribList, EGL_HEIGHT); value) {
height = *value;
}
const auto surface = EncodeHandle<EGLSurfaceHandle>(m_nextSurfaceHandle++);
m_surfaces[surface] = SurfaceObject{
.Display = display,
.Config = config,
.Type = SurfaceType::PbufferFromClientBuffer,
.NativeHandleKey = 0,
.ClientBuffer = buffer,
.BufferType = bufferType,
.Width = width,
.Height = height,
};
return surface;
}
EGLContext::EGLSurfaceHandle EGLContext::CreatePlatformWindowSurface(EGLDisplayHandle display,
EGLConfigHandle config,
void* nativeWindow,
const EGLAttrib* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE;
}
if (!ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return EGL_NO_SURFACE;
}
if (ToNativeKey(nativeWindow) == 0) {
SetError(EGL_BAD_NATIVE_WINDOW);
return EGL_NO_SURFACE;
}
const auto surface = EncodeHandle<EGLSurfaceHandle>(m_nextSurfaceHandle++);
m_surfaces[surface] = SurfaceObject{
.Display = display,
.Config = config,
.Type = SurfaceType::PlatformWindow,
.NativeHandleKey = ToNativeKey(nativeWindow),
.ClientBuffer = nullptr,
.BufferType = EGL_NONE,
.Width = static_cast<EGLint>(ParseAttribValue(attribList, EGL_WIDTH).value_or(0)),
.Height = static_cast<EGLint>(ParseAttribValue(attribList, EGL_HEIGHT).value_or(0)),
};
return surface;
}
EGLContext::EGLSurfaceHandle EGLContext::CreatePlatformPixmapSurface(EGLDisplayHandle display,
EGLConfigHandle config,
void* nativePixmap,
const EGLAttrib* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
(void)attribList;
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE;
}
if (!ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return EGL_NO_SURFACE;
}
if (ToNativeKey(nativePixmap) == 0) {
SetError(EGL_BAD_NATIVE_PIXMAP);
return EGL_NO_SURFACE;
}
const auto surface = EncodeHandle<EGLSurfaceHandle>(m_nextSurfaceHandle++);
m_surfaces[surface] = SurfaceObject{
.Display = display,
.Config = config,
.Type = SurfaceType::PlatformPixmap,
.NativeHandleKey = ToNativeKey(nativePixmap),
.ClientBuffer = nullptr,
.BufferType = EGL_NONE,
};
return surface;
}
Bool EGLContext::DestroySurface(EGLDisplayHandle display, EGLSurfaceHandle surface) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto surfaceIt = m_surfaces.find(surface);
if (surfaceIt == m_surfaces.end()) {
SetError(EGL_BAD_SURFACE);
return false;
}
if (surfaceIt->second.Display != display) {
SetError(EGL_BAD_MATCH);
return false;
}
if (IsSurfaceCurrentUnlocked(surface)) {
surfaceIt->second.DestroyPending = true;
return true;
}
m_surfaces.erase(surfaceIt);
return true;
}
Bool EGLContext::ResizeSurface(EGLDisplayHandle display, EGLSurfaceHandle surface,
EGLint width, EGLint height) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto surfaceIt = m_surfaces.find(surface);
if (surfaceIt == m_surfaces.end()) {
SetError(EGL_BAD_SURFACE);
return false;
}
if (surfaceIt->second.Display != display) {
SetError(EGL_BAD_MATCH);
return false;
}
surfaceIt->second.Width = std::max<EGLint>(width, 1);
surfaceIt->second.Height = std::max<EGLint>(height, 1);
return true;
}
#if MOBILEGL_BUILD_DISAGGREGATED
// P12 (on-screen server window). See Core.h. Split builds only (G1).
EGLContext::EGLSurfaceHandle EGLContext::CreateServerOwnedWindowSurface(EGLDisplayHandle display,
EGLConfigHandle config,
const void* nativeWindow,
EGLint width, EGLint height,
Bool platformWindow) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SURFACE;
}
if (!ValidateConfigOnDisplay(display, config)) {
SetError(EGL_BAD_CONFIG);
return EGL_NO_SURFACE;
}
if (width < 0 || height < 0) {
SetError(EGL_BAD_ATTRIBUTE);
return EGL_NO_SURFACE;
}
// No EGL_BAD_NATIVE_WINDOW for a null window: the window is the server's.
const auto surface = EncodeHandle<EGLSurfaceHandle>(m_nextSurfaceHandle++);
m_surfaces[surface] = SurfaceObject{
.Display = display,
.Config = config,
.Type = platformWindow ? SurfaceType::PlatformWindow : SurfaceType::Window,
.NativeHandleKey = ToNativeKey(nativeWindow),
.ClientBuffer = nullptr,
.BufferType = EGL_NONE,
.Width = width,
.Height = height,
};
return surface;
}
// MOBILEGL_IPC_SURFACE=host: the registration is the server-owned one verbatim, because
// what makes a window surface host-framed is not its EGL state but where its pixels go -
// and that is decided one layer down, by the backend the creation calls next.
EGLContext::EGLSurfaceHandle EGLContext::CreateHostFrameWindowSurface(EGLDisplayHandle display,
EGLConfigHandle config,
const void* nativeWindow,
EGLint width, EGLint height,
Bool platformWindow) {
return CreateServerOwnedWindowSurface(display, config, nativeWindow, width, height,
platformWindow);
}
Bool EGLContext::SetSurfaceExtent(EGLSurfaceHandle surface, EGLint width, EGLint height) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto surfaceIt = m_surfaces.find(surface);
if (surfaceIt == m_surfaces.end() || width < 0 || height < 0) {
return false;
}
surfaceIt->second.Width = width;
surfaceIt->second.Height = height;
return true;
}
#endif
Bool EGLContext::QuerySurface(EGLDisplayHandle display, EGLSurfaceHandle surface, EGLint attribute,
EGLint* value) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!value) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_PARAMETER);
return false;
}
const auto* surfaceObject = TryGetSurface(surface);
if (!surfaceObject) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_SURFACE);
return false;
}
if (surfaceObject->Display != display) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_MATCH);
return false;
}
switch (attribute) {
case EGL_WIDTH:
*value = surfaceObject->Width;
return true;
case EGL_HEIGHT:
*value = surfaceObject->Height;
return true;
case EGL_TEXTURE_FORMAT:
*value = surfaceObject->TextureFormat;
return true;
case EGL_TEXTURE_TARGET:
*value = surfaceObject->TextureTarget;
return true;
case EGL_MIPMAP_LEVEL:
*value = surfaceObject->MipmapLevel;
return true;
case EGL_MIPMAP_TEXTURE:
*value = surfaceObject->MipmapTexture;
return true;
case EGL_RENDER_BUFFER:
*value = surfaceObject->RenderBuffer;
return true;
case EGL_SWAP_BEHAVIOR:
*value = surfaceObject->SwapBehavior;
return true;
case EGL_CONFIG_ID: {
const auto* cfg = TryGetConfig(surfaceObject->Config);
if (!cfg) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_CONFIG);
return false;
}
*value = cfg->ConfigId;
return true;
}
default:
const_cast<EGLContext*>(this)->SetError(EGL_BAD_ATTRIBUTE);
return false;
}
}
Bool EGLContext::ValidateSurface(EGLSurfaceHandle surface) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
return m_surfaces.find(surface) != m_surfaces.end();
}
Bool EGLContext::ValidateSurfaceOnDisplay(EGLDisplayHandle display, EGLSurfaceHandle surface) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* surf = TryGetSurface(surface);
return surf && surf->Display == display;
}
Bool EGLContext::SwapInterval(EGLDisplayHandle display, EGLint interval) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto* displayObject = TryGetDisplay(display);
if (!displayObject) {
SetError(EGL_BAD_DISPLAY);
return false;
}
if (!displayObject->Initialized) {
SetError(EGL_NOT_INITIALIZED);
return false;
}
if (displayObject->Configs.empty()) {
SetError(EGL_BAD_CONFIG);
return false;
}
const auto* cfg = TryGetConfig(displayObject->Configs.front());
if (!cfg) {
SetError(EGL_BAD_CONFIG);
return false;
}
if (interval < cfg->MinSwapInterval || interval > cfg->MaxSwapInterval) {
SetError(EGL_BAD_PARAMETER);
return false;
}
displayObject->SwapInterval = interval;
return true;
}
Bool EGLContext::MakeCurrent(EGLDisplayHandle display, EGLSurfaceHandle draw, EGLSurfaceHandle read,
EGLContextHandle context) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const Bool releaseCurrentRequest =
draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && context == nullptr;
const auto threadKey = CurrentThreadKey();
if (releaseCurrentRequest) {
if (display != EGL_NO_DISPLAY) {
auto* displayObject = TryGetDisplay(display);
if (!displayObject) {
SetError(EGL_BAD_DISPLAY);
return false;
}
if (!displayObject->Initialized) {
SetError(EGL_NOT_INITIALIZED);
return false;
}
}
ReleaseThreadUnlocked(threadKey);
return true;
}
auto* displayObject = TryGetDisplay(display);
if (!displayObject) {
SetError(EGL_BAD_DISPLAY);
return false;
}
if (!displayObject->Initialized) {
SetError(EGL_NOT_INITIALIZED);
return false;
}
if (context != nullptr) {
if (!ValidateContextOnDisplay(display, context)) {
SetError(EGL_BAD_CONTEXT);
return false;
}
// EGL_KHR_surfaceless_context: BOTH surfaces absent is a context that draws into
// framebuffers it binds itself, which is how a compositor draws the frames the
// display host handed it. One absent and one present stays refused - that is a
// pair EGL gives no meaning to, and accepting it would record a draw target that
// does not exist.
const Bool surfaceless = (draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE);
if (!surfaceless && (draw == EGL_NO_SURFACE || read == EGL_NO_SURFACE)) {
SetError(EGL_BAD_MATCH);
return false;
}
} else {
if (draw != EGL_NO_SURFACE || read != EGL_NO_SURFACE) {
SetError(EGL_BAD_MATCH);
return false;
}
}
if (draw != EGL_NO_SURFACE && !ValidateSurfaceOnDisplay(display, draw)) {
SetError(EGL_BAD_SURFACE);
return false;
}
if (read != EGL_NO_SURFACE && !ValidateSurfaceOnDisplay(display, read)) {
SetError(EGL_BAD_SURFACE);
return false;
}
if (context != nullptr) {
auto ownerIt = m_contextOwners.find(context);
if (ownerIt != m_contextOwners.end() && ownerIt->second != threadKey) {
SetError(EGL_BAD_ACCESS);
return false;
}
}
ReleaseThreadUnlocked(threadKey);
m_threadCurrents[threadKey] = ThreadCurrentState{
.Display = display,
.DrawSurface = draw,
.ReadSurface = read,
.Context = context,
};
if (context != nullptr) {
m_contextOwners[context] = threadKey;
}
return true;
}
void EGLContext::ReleaseThread() {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
ReleaseThreadUnlocked(CurrentThreadKey());
}
EGLContext::EGLContextHandle EGLContext::GetCurrentContext() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return nullptr;
}
return currentIt->second.Context;
}
EGLContext::EGLDisplayHandle EGLContext::GetCurrentDisplay() const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return EGL_NO_DISPLAY;
}
return currentIt->second.Display;
}
EGLContext::EGLSurfaceHandle EGLContext::GetCurrentSurface(EGLint readdraw) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
if (currentIt == m_threadCurrents.end()) {
return EGL_NO_SURFACE;
}
if (readdraw == EGL_DRAW) {
return currentIt->second.DrawSurface;
}
if (readdraw == EGL_READ) {
return currentIt->second.ReadSurface;
}
return EGL_NO_SURFACE;
}
Bool EGLContext::IsDoubleBufferedSurface(EGLSurfaceHandle surface) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
const auto* surfaceObject = TryGetSurface(surface);
if (!surfaceObject) {
return false;
}
if (surfaceObject->RenderBuffer != EGL_BACK_BUFFER) {
return false;
}
return surfaceObject->Type == SurfaceType::Window ||
surfaceObject->Type == SurfaceType::PlatformWindow;
}
EGLContext::EGLSyncHandle EGLContext::CreateSync(EGLDisplayHandle display, EGLenum type,
const EGLAttrib* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_SYNC;
}
if (type != EGL_SYNC_FENCE) {
SetError(EGL_BAD_ATTRIBUTE);
return EGL_NO_SYNC;
}
SyncObject sync = {
.Display = display,
.Type = type,
.Condition = EGL_SYNC_PRIOR_COMMANDS_COMPLETE,
.Status = EGL_SIGNALED,
};
if (auto value = ParseAttribValue(attribList, EGL_SYNC_CONDITION); value) {
sync.Condition = static_cast<EGLenum>(*value);
}
const auto syncHandle = EncodeHandle<EGLSyncHandle>(m_nextSyncHandle++);
m_syncs[syncHandle] = sync;
return syncHandle;
}
Bool EGLContext::DestroySync(EGLDisplayHandle display, EGLSyncHandle sync) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto syncIt = m_syncs.find(sync);
if (syncIt == m_syncs.end()) {
SetError(EGL_BAD_PARAMETER);
return false;
}
if (syncIt->second.Display != display) {
SetError(EGL_BAD_MATCH);
return false;
}
m_syncs.erase(syncIt);
return true;
}
EGLint EGLContext::ClientWaitSync(EGLDisplayHandle display, EGLSyncHandle sync, EGLint flags,
EGLTime timeout) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
(void)flags;
(void)timeout;
const auto* syncObject = TryGetSync(sync);
if (!syncObject) {
SetError(EGL_BAD_PARAMETER);
return EGL_FALSE;
}
if (syncObject->Display != display) {
SetError(EGL_BAD_MATCH);
return EGL_FALSE;
}
return EGL_CONDITION_SATISFIED;
}
Bool EGLContext::GetSyncAttrib(EGLDisplayHandle display, EGLSyncHandle sync, EGLint attribute,
EGLAttrib* value) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!value) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_PARAMETER);
return false;
}
const auto* syncObject = TryGetSync(sync);
if (!syncObject) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_PARAMETER);
return false;
}
if (syncObject->Display != display) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_MATCH);
return false;
}
switch (attribute) {
case EGL_SYNC_TYPE:
*value = static_cast<EGLAttrib>(syncObject->Type);
return true;
case EGL_SYNC_STATUS:
*value = static_cast<EGLAttrib>(syncObject->Status);
return true;
case EGL_SYNC_CONDITION:
*value = static_cast<EGLAttrib>(syncObject->Condition);
return true;
default:
const_cast<EGLContext*>(this)->SetError(EGL_BAD_ATTRIBUTE);
return false;
}
}
Bool EGLContext::WaitSync(EGLDisplayHandle display, EGLSyncHandle sync, EGLint flags) const {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
(void)flags;
const auto* syncObject = TryGetSync(sync);
if (!syncObject) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_PARAMETER);
return false;
}
if (syncObject->Display != display) {
const_cast<EGLContext*>(this)->SetError(EGL_BAD_MATCH);
return false;
}
return true;
}
void EGLContext::ReleaseImageDescriptors(ImageObject& imageObject) {
for (Int plane = 0; plane < imageObject.PlaneCount; ++plane) {
if (imageObject.Planes[plane].Fd >= 0) {
CloseImageDescriptor(imageObject.Planes[plane].Fd);
imageObject.Planes[plane].Fd = -1;
}
}
imageObject.PlaneCount = 0;
}
EGLContext::EGLImageHandle EGLContext::CreateImage(EGLDisplayHandle display, EGLContextHandle context,
EGLenum target, EGLClientBuffer buffer,
const EGLAttrib* attribList) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (!IsDisplayInitialized(display)) {
SetError(EGL_NOT_INITIALIZED);
return EGL_NO_IMAGE;
}
if (context != nullptr && !ValidateContextOnDisplay(display, context)) {
SetError(EGL_BAD_CONTEXT);
return EGL_NO_IMAGE;
}
ImageObject imageObject = {
.Display = display,
.Context = context,
.Target = target,
.Buffer = buffer,
};
// EGL_LINUX_DMA_BUF_EXT: the caller handed over a DESCRIPTOR and described the
// memory behind it, and that description is the whole image - there is no handle to
// import later. Every other target still names a buffer this state cannot see, so
// it stays what it was: the record of the target it was made for.
if (target == EGL_LINUX_DMA_BUF_EXT) {
// ONE line per refusal, naming what was missing: a caller that gets
// EGL_BAD_PARAMETER for a buffer it believes it described completely has
// nowhere else to look - the attribute list is gone by the time eglGetError is
// read, and the frame it wanted to draw into is the frame it cannot.
const auto refuse = [&](const String& missing) {
SetError(EGL_BAD_PARAMETER);
MGLOG_E("eglCreateImage(EGL_LINUX_DMA_BUF_EXT): %s is missing; refusing the image "
"(EGL_BAD_PARAMETER)",
missing.c_str());
};
const auto width = ParseAttribValue(attribList, EGL_WIDTH);
const auto height = ParseAttribValue(attribList, EGL_HEIGHT);
const auto fourCC = ParseAttribValue(attribList, EGL_LINUX_DRM_FOURCC_EXT);
if (!width) {
refuse("EGL_WIDTH");
return EGL_NO_IMAGE;
}
if (!height) {
refuse("EGL_HEIGHT");
return EGL_NO_IMAGE;
}
if (!fourCC) {
refuse("EGL_LINUX_DRM_FOURCC_EXT");
return EGL_NO_IMAGE;
}
// The planes, read and checked BEFORE one descriptor is duplicated, so that a
// refusal leaves nothing behind to close.
EGLint planeFd[EGLContext::kMaxImagePlanes] = {};
EGLint planeOffset[EGLContext::kMaxImagePlanes] = {};
EGLint planePitch[EGLContext::kMaxImagePlanes] = {};
Uint64 planeModifier[EGLContext::kMaxImagePlanes] = {};
Bool planeHasModifier[EGLContext::kMaxImagePlanes] = {};
EGLint planeCount = 0;
for (Int plane = 0; plane < EGLContext::kMaxImagePlanes; ++plane) {
const DmaBufPlaneAttributes& attributes = kDmaBufPlaneAttributes[plane];
const auto fd = ParseAttribValue(attribList, attributes.Fd);
const auto offset = ParseAttribValue(attribList, attributes.Offset);
const auto pitch = ParseAttribValue(attribList, attributes.Pitch);
const auto modifierLo = ParseAttribValue(attribList, attributes.ModifierLo);
const auto modifierHi = ParseAttribValue(attribList, attributes.ModifierHi);
if (!fd && !offset && !pitch && !modifierLo && !modifierHi) {
// The caller never mentioned this plane. Planes are positional - the
// description carries no plane index - so this ends the list, and a
// plane past the hole is refused by name rather than dropped.
for (Int higher = plane + 1; higher < EGLContext::kMaxImagePlanes; ++higher) {
if (HasAnyPlaneAttribute(attribList, higher)) {
refuse(DmaBufPlaneAttributeName(plane, "FD_EXT"));
return EGL_NO_IMAGE;
}
}
break;
}
// All three of fd/offset/pitch or none, and both halves of the modifier or
// neither: a partial triple describes memory no importer could read.
if (!fd) {
refuse(DmaBufPlaneAttributeName(plane, "FD_EXT"));
return EGL_NO_IMAGE;
}
if (!offset) {
refuse(DmaBufPlaneAttributeName(plane, "OFFSET_EXT"));
return EGL_NO_IMAGE;
}
if (!pitch) {
refuse(DmaBufPlaneAttributeName(plane, "PITCH_EXT"));
return EGL_NO_IMAGE;
}
if (modifierLo.has_value() != modifierHi.has_value()) {
refuse(DmaBufPlaneAttributeName(plane, modifierLo ? "MODIFIER_HI_EXT" : "MODIFIER_LO_EXT"));
return EGL_NO_IMAGE;
}
planeFd[plane] = static_cast<EGLint>(*fd);
planeOffset[plane] = static_cast<EGLint>(*offset);
planePitch[plane] = static_cast<EGLint>(*pitch);
if (modifierLo) {
// The modifier arrives in two halves and is one number: DRM cuts a
// DRM_FORMAT_MOD_* where EGL takes an EGLint.
planeModifier[plane] = (static_cast<Uint64>(static_cast<Uint32>(*modifierHi)) << 32) |
static_cast<Uint32>(*modifierLo);
planeHasModifier[plane] = true;
}
planeCount = plane + 1;
}
if (planeCount == 0) {
// A dma-buf image with no plane at all: the description starts here, so
// this is the attribute that is missing.
refuse(DmaBufPlaneAttributeName(0, "FD_EXT"));
return EGL_NO_IMAGE;
}
// THE IMAGE TAKES ITS OWN DESCRIPTORS. The caller's fds stay the caller's -
// kwin hands over a dup() of its DmaBufAttributes and closes it as soon as
// eglCreateImage returns, and an image that had borrowed it would be holding a
// number some other part of the process now owns.
for (Int plane = 0; plane < planeCount; ++plane) {
const Int ownedFd = DuplicateImageDescriptor(planeFd[plane]);
if (ownedFd < 0) {
SetError(EGL_BAD_ALLOC);
MGLOG_E("eglCreateImage(EGL_LINUX_DMA_BUF_EXT): cannot take a copy of %s=%d (%s); "
"refusing the image (EGL_BAD_ALLOC)",
DmaBufPlaneAttributeName(plane, "FD_EXT").c_str(), planeFd[plane],
std::strerror(errno));
ReleaseImageDescriptors(imageObject);
return EGL_NO_IMAGE;
}
imageObject.Planes[plane] = ImagePlane{
.Fd = ownedFd,
.Offset = planeOffset[plane],
.Pitch = planePitch[plane],
.Modifier = planeModifier[plane],
.HasModifier = planeHasModifier[plane],
};
// Counted as they are stored, so that a failure below releases exactly what
// has been taken.
imageObject.PlaneCount = plane + 1;
}
imageObject.Width = static_cast<EGLint>(*width);
imageObject.Height = static_cast<EGLint>(*height);
imageObject.FourCC = static_cast<EGLint>(*fourCC);
MGLOG_D("eglCreateImage(EGL_LINUX_DMA_BUF_EXT): %dx%d fourcc=0x%08x planes=%d plane0(fd=%d "
"offset=%d pitch=%d %s0x%016llx)",
imageObject.Width, imageObject.Height, imageObject.FourCC, imageObject.PlaneCount,
imageObject.Planes[0].Fd, imageObject.Planes[0].Offset, imageObject.Planes[0].Pitch,
imageObject.Planes[0].HasModifier ? "modifier=" : "no-modifier=",
static_cast<unsigned long long>(imageObject.Planes[0].Modifier));
}
const auto image = EncodeHandle<EGLImageHandle>(m_nextImageHandle++);
m_images[image] = imageObject;
return image;
}
Bool EGLContext::DestroyImage(EGLDisplayHandle display, EGLImageHandle image) {
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto imageIt = m_images.find(image);
if (imageIt == m_images.end()) {
SetError(EGL_BAD_PARAMETER);
return false;
}
if (imageIt->second.Display != display) {
SetError(EGL_BAD_MATCH);
return false;
}
// The descriptors this image owns die with it. Nothing else in this process holds
// them: they are copies, and the caller's originals were never touched.
ReleaseImageDescriptors(imageIt->second);
m_images.erase(imageIt);
return true;
}
} // namespace EGLState
// Leak-at-exit storage; see GlobalObjects.cpp.
UniquePtr<EGLState::EGLContext>& pEGLContext = *new UniquePtr<EGLState::EGLContext>();
} // namespace MG_State
} // namespace MobileGL