diff --git a/packages/termux-wsi-layer/10_termux.json b/packages/termux-wsi-layer/10_termux.json new file mode 100644 index 000000000000000..0a436fce3258556 --- /dev/null +++ b/packages/termux-wsi-layer/10_termux.json @@ -0,0 +1,6 @@ +{ + "file_format_version": "1.0.0", + "ICD": { + "library_path": "termux-wsi-layer.so" + } +} diff --git a/packages/termux-wsi-layer/CMakeLists.txt b/packages/termux-wsi-layer/CMakeLists.txt new file mode 100644 index 000000000000000..ae17723ec98a3e7 --- /dev/null +++ b/packages/termux-wsi-layer/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.29) +project(termux-wsi-layer C) + +set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_C_STANDARD 11) +find_package(PkgConfig REQUIRED) +pkg_check_modules(X11 REQUIRED x11 x11-xcb xcb xcb-randr xcb-dri3 xcb-present) + +add_library(termux-wsi-layer SHARED egl.c window.c sync.c) +set_target_properties(termux-wsi-layer PROPERTIES PREFIX "") +target_include_directories(termux-wsi-layer PRIVATE include) +target_compile_options(termux-wsi-layer PRIVATE ${X11_CFLAGS}) +target_link_options(termux-wsi-layer PRIVATE -Wl,--no-as-needed -landroid -Wl,--as-needed -ggdb ${X11_LDFLAGS}) + +add_executable(test test.c) +target_link_options(test PRIVATE -lEGL -lGLESv2 -lX11 -lm) + +install(TARGETS termux-wsi-layer LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) +install(FILES 10_termux.json DESTINATION ${CMAKE_INSTALL_PREFIX}/share/glvnd/egl_vendor.d) +add_custom_target(uninstall) +add_custom_command(TARGET uninstall POST_BUILD COMMAND rm ARGS -v -f + ${CMAKE_INSTALL_PREFIX}/share/glvnd/egl_vendor.d/10_termux.json + ${CMAKE_INSTALL_PREFIX}/lib/termux-wsi-layer.so +) diff --git a/packages/termux-wsi-layer/build.sh b/packages/termux-wsi-layer/build.sh new file mode 100644 index 000000000000000..75671ad4eafa837 --- /dev/null +++ b/packages/termux-wsi-layer/build.sh @@ -0,0 +1,15 @@ +TERMUX_PKG_HOMEPAGE=https://termux.dev +TERMUX_PKG_DESCRIPTION="Termux's ICD/WSI wrapper for using with proprietary Android drivers" +TERMUX_PKG_LICENSE="GPL-3.0" +TERMUX_PKG_MAINTAINER="@termux" +TERMUX_PKG_VERSION="0.0.1" +TERMUX_PKG_SKIP_SRC_EXTRACT=true +TERMUX_PKG_BUILD_DEPENDS="libglvnd, libxcb, libx11" +TERMUX_PKG_NO_STRIP=true + +TERMUX_PKG_SRCDIR="$TERMUX_PREFIX/opt/termux-wsi-layer/src" + +termux_step_pre_configure() { + mkdir -p "$TERMUX_PREFIX/opt/termux-wsi-layer/src" + cp -r "${TERMUX_PKG_BUILDER_DIR}/"{include,*.c,*.h,*.json,CMakeLists.txt} "$TERMUX_PREFIX/opt/termux-wsi-layer/src" +} diff --git a/packages/termux-wsi-layer/common.h b/packages/termux-wsi-layer/common.h new file mode 100644 index 000000000000000..d54d601446624c4 --- /dev/null +++ b/packages/termux-wsi-layer/common.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 Twaik Yont + * + * Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +// Workaround for `error: 'AHardwareBuffer_describe' is unavailable: introduced in Android 26` +// We will explicitly mark these symbols as weak to avoid using `ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK` +// These symbols will equal to NULL in the case if they were not linked and we can check this in __eglMain +#define AHardwareBuffer_allocate _AHardwareBuffer_allocate +#define AHardwareBuffer_release _AHardwareBuffer_release +#define AHardwareBuffer_describe _AHardwareBuffer_describe +#define AHardwareBuffer_sendHandleToUnixSocket _AHardwareBuffer_sendHandleToUnixSocket +#include +#undef AHardwareBuffer_allocate +#undef AHardwareBuffer_release +#undef AHardwareBuffer_describe +#undef AHardwareBuffer_sendHandleToUnixSocket + +#define WEAK __attribute__((weak)) +int AHardwareBuffer_allocate(const AHardwareBuffer_Desc* _Nonnull desc, AHardwareBuffer* _Nullable* _Nonnull outBuffer) WEAK; +void AHardwareBuffer_release(AHardwareBuffer* _Nonnull buffer) WEAK; +void AHardwareBuffer_describe(const AHardwareBuffer* _Nonnull buffer, AHardwareBuffer_Desc* _Nonnull outDesc) WEAK; +int AHardwareBuffer_sendHandleToUnixSocket(const AHardwareBuffer* _Nonnull buffer, int socketFd) WEAK; + +// We can not link to these functions directly for two reasons +// 1. This API is not exposed to NDK. +// 2. The `libui.so` is explicitly blocklisted for linking. +// So our solution is to declare these symbols as weak and let linker override them. +// We use GraphicBuffer_getNativeBuffer only as fallback in the case if AHardwareBuffer_to_ANativeWindowBuffer is unavailable. +// Both AHardwareBuffer_to_ANativeWindowBuffer and GraphicBuffer_getNativeBuffer are aliases to make code easier to read. + +#define AHardwareBuffer_to_ANativeWindowBuffer _ZN7android38AHardwareBuffer_to_ANativeWindowBufferEP15AHardwareBuffer +#define GraphicBuffer_getNativeBuffer _ZNK7android13GraphicBuffer15getNativeBufferEv +size_t AHardwareBuffer_to_ANativeWindowBuffer(void* _Nullable) WEAK; +size_t GraphicBuffer_getNativeBuffer(void* _Nullable) WEAK; \ No newline at end of file diff --git a/packages/termux-wsi-layer/egl.c b/packages/termux-wsi-layer/egl.c new file mode 100644 index 000000000000000..8679e4d9a2d64e5 --- /dev/null +++ b/packages/termux-wsi-layer/egl.c @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +typedef struct { + EGLDisplay egldpy; + Display *x11dpy; +} EGLDisplay_t; + +struct ANativeWindow* TermuxNativeWindow_create(Display* dpy, Window win); +void TermuxNativeWindow_init(void); + +static const char* eglStrError(EGLint err) { + switch(err) { +#define ERR(e) case e: return #e + ERR(EGL_SUCCESS); + ERR(EGL_NOT_INITIALIZED); + ERR(EGL_BAD_ACCESS); + ERR(EGL_BAD_ALLOC); + ERR(EGL_BAD_ATTRIBUTE); + ERR(EGL_BAD_CONTEXT); + ERR(EGL_BAD_CONFIG); + ERR(EGL_BAD_CURRENT_SURFACE); + ERR(EGL_BAD_DISPLAY); + ERR(EGL_BAD_SURFACE); + ERR(EGL_BAD_MATCH); + ERR(EGL_BAD_PARAMETER); + ERR(EGL_BAD_NATIVE_PIXMAP); + ERR(EGL_BAD_NATIVE_WINDOW); + ERR(EGL_CONTEXT_LOST); +#undef ERR + default: return "EGL_UNKNOWN_ERROR"; + } +} + +// Both OVERRIDE and NO_OVERRIDE are macros like N(required, return type, name, args definition in parentheses, args in parentheses). +// OVERRIDE is for functions we override, NO_OVERRIDE for functions we dlopen from Android implementation and use as is. +#define EGL_FUNCS(OVERRIDE, NO_OVERRIDE) \ + /* EGL_VERSION_1_0 */ \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, ChooseConfig, (EGLDisplay _dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config), (dpy, attrib_list, configs, config_size, num_config)) \ + OVERRIDE(EGL_TRUE, EGLBoolean, CopyBuffers, (EGLDisplay _dpy, EGLSurface surface, EGLNativePixmapType target), (dpy, surface, target)) \ + NO_OVERRIDE(EGL_TRUE, EGLContext, CreateContext, (EGLDisplay _dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list), (dpy, config, share_context, attrib_list)) \ + NO_OVERRIDE(EGL_TRUE, EGLSurface, CreatePbufferSurface, (EGLDisplay _dpy, EGLConfig config, const EGLint *attrib_list), (dpy, config, attrib_list)) \ + OVERRIDE(EGL_TRUE, EGLSurface, CreatePixmapSurface, (EGLDisplay _dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list), (dpy, config, pixmap, attrib_list)) \ + OVERRIDE(EGL_TRUE, EGLSurface, CreateWindowSurface, (EGLDisplay _dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list), (dpy, config, win, attrib_list)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, DestroyContext, (EGLDisplay _dpy, EGLContext ctx), (dpy, ctx)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, DestroySurface, (EGLDisplay _dpy, EGLSurface surface), (dpy, surface)) \ + OVERRIDE(EGL_TRUE, EGLBoolean, GetConfigAttrib, (EGLDisplay _dpy, EGLConfig config, EGLint attribute, EGLint *value), (dpy, config, attribute, value)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, GetConfigs, (EGLDisplay _dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config), (dpy, configs, config_size, num_config)) \ + NO_OVERRIDE(EGL_TRUE, EGLDisplay, GetCurrentDisplay, (void), ()) \ + NO_OVERRIDE(EGL_TRUE, EGLSurface, GetCurrentSurface, (EGLint readdraw), (readdraw)) \ + OVERRIDE(EGL_TRUE, EGLDisplay, GetDisplay, (EGLNativeDisplayType display_id), (display_id)) \ + NO_OVERRIDE(EGL_TRUE, EGLint, GetError, (void), ()) \ + OVERRIDE(EGL_TRUE, __eglMustCastToProperFunctionPointerType, GetProcAddress, (const char *procname), (procname)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, Initialize, (EGLDisplay _dpy, EGLint *major, EGLint *minor), (dpy, major, minor)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, MakeCurrent, (EGLDisplay _dpy, EGLSurface draw, EGLSurface read, EGLContext ctx), (dpy, draw, read, ctx)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, QueryContext, (EGLDisplay _dpy, EGLContext ctx, EGLint attribute, EGLint *value), (dpy, ctx, attribute, value)) \ + OVERRIDE(EGL_TRUE, const char *, QueryString, (EGLDisplay _dpy, EGLint name), (dpy, name)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, QuerySurface, (EGLDisplay _dpy, EGLSurface surface, EGLint attribute, EGLint *value), (dpy, surface, attribute, value)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, SwapBuffers, (EGLDisplay _dpy, EGLSurface surface), (dpy, surface)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, Terminate, (EGLDisplay _dpy), (dpy)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, WaitGL, (void), ()) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, WaitNative, (EGLint engine), (engine)) \ + /* EGL_VERSION_1_0 */ \ + /* EGL_VERSION_1_1 */ \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, BindTexImage, (EGLDisplay _dpy, EGLSurface surface, EGLint buffer), (dpy, surface, buffer)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, ReleaseTexImage, (EGLDisplay _dpy, EGLSurface surface, EGLint buffer), (dpy, surface, buffer)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, SurfaceAttrib, (EGLDisplay _dpy, EGLSurface surface, EGLint attribute, EGLint value), (dpy, surface, attribute, value)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, SwapInterval, (EGLDisplay _dpy, EGLint interval), (dpy, interval)) \ + /* EGL_VERSION_1_1 */ \ + /* EGL_VERSION_1_2 */ \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, BindAPI, (EGLenum api), (api)) \ + NO_OVERRIDE(EGL_TRUE, EGLenum, QueryAPI, (void), ()) \ + NO_OVERRIDE(EGL_TRUE, EGLSurface, CreatePbufferFromClientBuffer, (EGLDisplay _dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list), (dpy, buftype, buffer, config, attrib_list)) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, ReleaseThread, (void), ()) \ + NO_OVERRIDE(EGL_TRUE, EGLBoolean, WaitClient, (void), ()) \ + /* EGL_VERSION_1_2 */ \ + /* EGL_VERSION_1_4 */ \ + NO_OVERRIDE(EGL_TRUE, EGLContext, GetCurrentContext, (void), ()) \ + /* EGL_VERSION_1_4 */ \ + /* EGL_VERSION_1_5 */ \ + NO_OVERRIDE(EGL_FALSE, EGLSync, CreateSync, (EGLDisplay _dpy, EGLenum type, const EGLAttrib *attrib_list), (dpy, type, attrib_list)) \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, DestroySync, (EGLDisplay _dpy, EGLSync sync), (dpy, sync)) \ + NO_OVERRIDE(EGL_FALSE, EGLint, ClientWaitSync, (EGLDisplay _dpy, EGLSync sync, EGLint flags, EGLTime timeout), (dpy, sync, flags, timeout)) \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, GetSyncAttrib, (EGLDisplay _dpy, EGLSync sync, EGLint attribute, EGLAttrib *value), (dpy, sync, attribute, value)) \ + NO_OVERRIDE(EGL_FALSE, EGLImage, CreateImage, (EGLDisplay _dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list), (dpy, ctx, target, buffer, attrib_list)) \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, DestroyImage, (EGLDisplay _dpy, EGLImage image), (dpy, image)) \ + OVERRIDE(EGL_FALSE, EGLDisplay, GetPlatformDisplay, (EGLenum platform, void *native_display, const EGLAttrib *attrib_list), (platform, native_display, attrib_list)) \ + NO_OVERRIDE(EGL_FALSE, EGLSurface, CreatePlatformWindowSurface, (EGLDisplay _dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list), (dpy, config, native_window, attrib_list)) \ + NO_OVERRIDE(EGL_FALSE, EGLSurface, CreatePlatformPixmapSurface, (EGLDisplay _dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list), (dpy, config, native_pixmap, attrib_list)) \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, WaitSync, (EGLDisplay _dpy, EGLSync sync, EGLint flags), (dpy, sync, flags)) \ + /* EGL_VERSION_1_5 */ \ + /* EGL_KHR_debug */ \ + NO_OVERRIDE(EGL_FALSE, EGLint, DebugMessageControlKHR, (void* callback, const EGLAttrib *attrib_list), (callback, attrib_list)) \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, QueryDebugKHR, (EGLint attribute, EGLAttrib *value), (attribute, value)) \ + NO_OVERRIDE(EGL_FALSE, EGLint, LabelObjectKHR, (EGLDisplay _dpy, EGLenum objectType, EGLObjectKHR object, EGLLabelKHR label), (dpy, objectType, object, label)) \ + /* EGL_KHR_debug */ \ + /* EGL_KHR_display_reference */ \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, QueryDisplayAttribKHR, (EGLDisplay _dpy, EGLint name, EGLAttrib *value), (dpy, name, value)) \ + /* EGL_KHR_display_reference */ \ + /* EGL_EXT_device_base */ \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, QueryDeviceAttribEXT, (EGLDeviceEXT device, EGLint attribute, EGLAttrib *value), (device, attribute, value)) \ + NO_OVERRIDE(EGL_FALSE, const char *, QueryDeviceStringEXT, (EGLDeviceEXT device, EGLint name), (device, name)) \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, QueryDevicesEXT, (EGLint max_devices, EGLDeviceEXT *devices, EGLint *num_devices), (max_devices, devices, num_devices)) \ + NO_OVERRIDE(EGL_FALSE, EGLBoolean, QueryDisplayAttribEXT, (EGLDisplay _dpy, EGLint attribute, EGLAttrib *value), (dpy, attribute, value)) \ + /* EGL_EXT_device_base */ + +static struct { +#define FUNC(required, ret, name, argsdef, args) ret (*egl ## name) argsdef; + EGL_FUNCS(FUNC, FUNC) // NOLINT(*-reserved-identifier) +#undef FUNC +} android; +static void* GLESv2 = NULL; +static const char* extensions = NULL; + +static const __EGLapiExports *apiExports = NULL; +static EGLDisplay _dpy = EGL_NO_DISPLAY; // It will be shadowed by local function argument definition + +#define WRAP(required, ret, name, argsdef, args) \ + EGLAPI ret termuxEGL_ ## name argsdef { \ + apiExports->threadInit(); \ + EGLDisplay dpy = (_dpy != EGL_NO_DISPLAY) ? ((EGLDisplay_t*) _dpy)->egldpy : EGL_NO_DISPLAY; \ + ret r = android.egl ## name args; \ + EGLint err = android.eglGetError(); \ + apiExports->setEGLError(err); \ + if (err != EGL_SUCCESS) dprintf(2, "Error: Invoking " #name " ended with error %s (0x%X)\n", eglStrError(err), err); \ + return r; \ + } +#define NOWRAP(required, ret, name, argsdef, args) +EGL_FUNCS(NOWRAP, WRAP) +#undef WRAP +#undef NOWRAP + +EGLAPI EGLBoolean termuxEGL_CopyBuffers(EGLDisplay __unused dpy, EGLSurface __unused surface, EGLNativePixmapType __unused target) { + // eglCopyBuffers is not implemented on Android + apiExports->threadInit(); + apiExports->setEGLError(EGL_BAD_NATIVE_PIXMAP); + return EGL_FALSE; +} + +EGLAPI EGLSurface termuxEGL_CreatePixmapSurface(EGLDisplay __unused dpy, EGLSurface __unused surface, EGLNativePixmapType __unused target) { + // eglCreatePixmapSurface is not implemented on Android + apiExports->threadInit(); + apiExports->setEGLError(EGL_BAD_ALLOC); + return EGL_NO_SURFACE; +} + +EGLAPI EGLSurface termuxEGL_CreateWindowSurface (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list) { + apiExports->threadInit(); + EGLDisplay_t* _dpy = dpy; + if (_dpy == NULL || _dpy->egldpy == EGL_NO_DISPLAY || _dpy->x11dpy == NULL) { + apiExports->setEGLError(EGL_BAD_DISPLAY); + return EGL_NO_SURFACE; + } + + EGLSurface r = android.eglCreateWindowSurface(_dpy->egldpy, config, (EGLNativeWindowType) TermuxNativeWindow_create(_dpy->x11dpy, win), attrib_list); + EGLint err = android.eglGetError(); + if (err != EGL_SUCCESS) dprintf(2, "Error: Invoking eglCreateWindowSurface ended with error %s (0x%X)\n", eglStrError(err), err); + apiExports->setEGLError(err); + return r; +} + +EGLAPI EGLBoolean termuxEGL_GetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value) { + apiExports->threadInit(); + EGLDisplay_t* _dpy = dpy; + if (attribute == EGL_NATIVE_VISUAL_ID) { + if (_dpy == EGL_NO_DISPLAY || _dpy->x11dpy == NULL) { + *value = 0; + apiExports->setEGLError(EGL_SUCCESS); + return EGL_TRUE; + } + + int count = 0; + XVisualInfo t = { .depth = 24 }, *info = XGetVisualInfo (_dpy->x11dpy, VisualDepthMask, &t, &count); + + if (count) { + *value = (EGLint) info->visualid; + apiExports->setEGLError(EGL_SUCCESS); + return EGL_TRUE; + } + + apiExports->setEGLError(EGL_BAD_ATTRIBUTE); + return EGL_FALSE; + } + + EGLBoolean r = android.eglGetConfigAttrib(_dpy == EGL_NO_DISPLAY ? EGL_NO_DISPLAY : _dpy->egldpy, config, attribute, value); + EGLint err = android.eglGetError(); + if (err != EGL_SUCCESS) dprintf(2, "Error: Invoking GetConfigAttrib ended with error %s (0x%X)\n", eglStrError(err), err); + apiExports->setEGLError(err); + return r; +} + +EGLAPI const char * termuxEGL_QueryString(EGLDisplay dpy, EGLint name) { + apiExports->threadInit(); + EGLDisplay_t* _dpy = dpy; + if (name == EGL_EXTENSIONS) { + apiExports->setEGLError(EGL_SUCCESS); + return extensions; + } + if (name == EGL_CLIENT_APIS) { + apiExports->setEGLError(EGL_SUCCESS); + return "OpenGL_ES"; + } + + const char *r = android.eglQueryString(_dpy == EGL_NO_DISPLAY ? EGL_NO_DISPLAY : _dpy->egldpy, name); + EGLint err = android.eglGetError(); + apiExports->setEGLError(err); + if (err != EGL_SUCCESS) dprintf(2, "Error: Invoking eglQueryString ended with error %s (0x%X)\n", eglStrError(err), err); + return r; +} + +EGLAPI EGLDisplay termuxEGL_GetPlatformDisplay(EGLenum platform, void *nativeDisplay, const EGLAttrib *attrib_list) { + apiExports->threadInit(); + + if (platform == EGL_PLATFORM_GBM_KHR) { + dprintf(2, "GBM is not supported.\n"); + return EGL_NO_DISPLAY; + } + + if (platform == EGL_PLATFORM_WAYLAND_KHR) { + dprintf(2, "Wayland is not supported.\n"); + return EGL_NO_DISPLAY; + } + + if (platform == EGL_PLATFORM_DEVICE_EXT) { + dprintf(2, "Device surface extension is not supported.\n"); + return EGL_NO_DISPLAY; + } + + if (platform == EGL_PLATFORM_SURFACELESS_MESA) { + dprintf(2, "Surfaceless mesa extension is not supported.\n"); + return EGL_NO_DISPLAY; + } + + EGLDisplay_t *dpy = NULL; + EGLDisplay r = android.eglGetDisplay(EGL_DEFAULT_DISPLAY); + apiExports->setEGLError(android.eglGetError()); + + if (r != EGL_NO_DISPLAY) { + // ReSharper disable once CppDFAMemoryLeak + dpy = calloc(1, sizeof(*dpy)); + dpy->egldpy = r; + dpy->x11dpy = nativeDisplay; + } + + return (EGLDisplay) dpy; +} + +EGLAPI EGLDisplay termuxEGL_GetDisplay (EGLNativeDisplayType display_id) { + apiExports->threadInit(); + dprintf(2, "Error: termuxEGL_GetDisplay is not implemented by design. Report bug if you see this message\n"); + apiExports->setEGLError(EGL_BAD_ALLOC); + return EGL_NO_DISPLAY; +} + +EGLAPI void * termuxEGL_GetProcAddress(const char *procName) { + if (!strncmp("egl", procName, 3)) { + if (0) {} +#define FUNC(required, ret, name, argsdef, args) else if (!strcmp("egl" #name, procName)) return termuxEGL_ ## name; + EGL_FUNCS(FUNC, FUNC) +#undef FUNC + dprintf(2, "Error: failed to locate %s symbol using GetProcAddress since it is not implemented\n", procName); + return NULL; + } + if (!strncmp("gl", procName, 2)) { + void* f = dlsym(GLESv2, procName); + // if (!f) + // dprintf(2, "Error: failed to locate %s symbol due to linker error: %s\n", procName, dlerror()); + return f; + } + + dprintf(2, "Error: failed to locate %s symbol because it does not start with `gl` or `egl`\n", procName); + return NULL; +} + +EGLAPI EGLBoolean termuxEGL_GetSupportsAPI(EGLenum api) { + return api == EGL_OPENGL_ES_API ? EGL_TRUE : EGL_FALSE; +} + +EGLAPI const char * termuxEGL_GetVendorString(int name) { + if (name == __EGL_VENDOR_STRING_PLATFORM_EXTENSIONS) + return "EGL_KHR_platform_android EGL_KHR_platform_x11 EGL_EXT_platform_x11 EGL_EXT_platform_xcb"; + return NULL; +} + +EGLAPI void *termuxEGL_GetDispatchAddress(const char *procName) { + dprintf(2, "Error: %s is not implemented yet, call with arg \"%s\" is not effective\n", __FUNCTION__, procName); // TODO: implement me + return NULL; +} + +EGLAPI void termuxEGL_SetDispatchIndex(const char * __unused procName, int __unused index) { + // Not necessary to be implemented +} + +EGLAPI EGLBoolean __egl_Main(uint32_t version, const __EGLapiExports *exports, __EGLvendorInfo __unused *vendor, __EGLapiImports *imports) { + const char* useAngle = getenv("USE_ANGLE"); + void *EGL = NULL; + char eglpath[128] = {0}, gles2path[128] = {0}; + if (EGL_VENDOR_ABI_GET_MAJOR_VERSION(version) != EGL_VENDOR_ABI_MAJOR_VERSION) { + dprintf(2, "Error: GLVND abi version mismatch"); + return EGL_FALSE; + } + + if (apiExports != NULL) + return EGL_TRUE; // Already initialized. + + if (!AHardwareBuffer_allocate || !AHardwareBuffer_release || !AHardwareBuffer_describe || !AHardwareBuffer_sendHandleToUnixSocket + || (!AHardwareBuffer_to_ANativeWindowBuffer && !GraphicBuffer_getNativeBuffer)) { + dprintf(2, "Error: Required symbols not found. termux-wsi-layer is not being used.\n"); + dprintf(2, "AHardwareBuffer_allocate: %p\n", AHardwareBuffer_allocate); + dprintf(2, "AHardwareBuffer_release: %p\n", AHardwareBuffer_release); + dprintf(2, "AHardwareBuffer_describe: %p\n", AHardwareBuffer_describe); + dprintf(2, "AHardwareBuffer_sendHandleToUnixSocket: %p\n", AHardwareBuffer_sendHandleToUnixSocket); + dprintf(2, "AHardwareBuffer_to_ANativeWindowBuffer: %p\n", AHardwareBuffer_to_ANativeWindowBuffer); + dprintf(2, "GraphicBuffer_getNativeBuffer: %p\n", GraphicBuffer_getNativeBuffer); + return EGL_FALSE; + } + + if (useAngle) { + if (strcmp("gl", useAngle) != 0 && strcmp("vulkan", useAngle) != 0 && strcmp("vulkan-null", useAngle) != 0) { + dprintf(2, "Error: wrong USE_ANGLE variable, must be one of: gl, vulkan, vulkan-null.\n"); + dprintf(2, "Falling back to Android EGL/GLESv2.\n"); + useAngle = NULL; + } else { + snprintf(eglpath, sizeof(eglpath), "%s/opt/angle-android/%s/libEGL_angle.so", getenv("PREFIX"), useAngle); + snprintf(gles2path, sizeof(gles2path), "%s/opt/angle-android/%s/libGLESv2_angle.so", getenv("PREFIX"), useAngle); + } + } + + if (!useAngle) { +#ifdef __LP64 + snprintf(eglpath, sizeof(eglpath), "/system/lib/libEGL.so"); + snprintf(gles2path, sizeof(gles2path), "/system/lib/libGLESv2.so"); +#else + snprintf(eglpath, sizeof(eglpath), "/system/lib64/libEGL.so"); + snprintf(gles2path, sizeof(gles2path), "/system/lib64/libGLESv2.so"); +#endif + } + + EGL = dlopen(eglpath, RTLD_NOW | RTLD_GLOBAL); + if (!EGL) { + char* error; + while ((error = dlerror())) + dprintf(2, "Error: EGL linking: %s\n", error); + return EGL_FALSE; + } + + GLESv2 = dlopen(gles2path, RTLD_NOW | RTLD_GLOBAL); + if (!GLESv2) { + char* error; + while ((error = dlerror())) + dprintf(2, "Error: GLESv2 linking: %s\n", error); + return EGL_FALSE; + } + +#define FUNC(required, ret, name, argsdef, args) \ + android.egl ## name = dlsym(EGL, "egl" #name); \ + if (!android.egl ## name && required) { \ + dprintf(2, "Error: EGL symbol resolution: %s\n", dlerror()); \ + return EGL_FALSE; \ + } +EGL_FUNCS(FUNC, FUNC) +#undef FUNC + + asprintf((char**) &extensions, "%s %s", termuxEGL_GetVendorString(__EGL_VENDOR_STRING_PLATFORM_EXTENSIONS), android.eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS)); + + TermuxNativeWindow_init(); + + apiExports = exports; + + imports->getPlatformDisplay = termuxEGL_GetPlatformDisplay; + imports->getSupportsAPI = termuxEGL_GetSupportsAPI; + imports->getVendorString = termuxEGL_GetVendorString; + imports->getProcAddress = termuxEGL_GetProcAddress; + imports->getDispatchAddress = termuxEGL_GetDispatchAddress; + imports->setDispatchIndex = termuxEGL_SetDispatchIndex; + + return EGL_TRUE; +} diff --git a/packages/termux-wsi-layer/include/system/window.h b/packages/termux-wsi-layer/include/system/window.h new file mode 100644 index 000000000000000..eb8c8d3337fa565 --- /dev/null +++ b/packages/termux-wsi-layer/include/system/window.h @@ -0,0 +1,540 @@ + +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#define ANDROID_NATIVE_MAKE_CONSTANT(a,b,c,d) (((unsigned)(a)<<24)|((unsigned)(b)<<16)|((unsigned)(c)<<8)|(unsigned)(d)) +#define ANDROID_NATIVE_WINDOW_MAGIC ANDROID_NATIVE_MAKE_CONSTANT('_','w','n','d') + +// --------------------------------------------------------------------------- + +/* attributes queriable with query() */ +enum { + NATIVE_WINDOW_WIDTH = 0, + NATIVE_WINDOW_HEIGHT = 1, + NATIVE_WINDOW_FORMAT = 2, + + /* see ANativeWindowQuery in vndk/window.h */ + NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS = 3 /* ANATIVEWINDOW_QUERY_MIN_UNDEQUEUED_BUFFERS */, + + /* Check whether queueBuffer operations on the ANativeWindow send the buffer + * to the window compositor. The query sets the returned 'value' argument + * to 1 if the ANativeWindow DOES send queued buffers directly to the window + * compositor and 0 if the buffers do not go directly to the window + * compositor. + * + * This can be used to determine whether protected buffer content should be + * sent to the ANativeWindow. Note, however, that a result of 1 does NOT + * indicate that queued buffers will be protected from applications or users + * capturing their contents. If that behavior is desired then some other + * mechanism (e.g. the GRALLOC_USAGE_PROTECTED flag) should be used in + * conjunction with this query. + */ + NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER = 4, + + /* Get the concrete type of a ANativeWindow. See below for the list of + * possible return values. + * + * This query should not be used outside the Android framework and will + * likely be removed in the near future. + */ + NATIVE_WINDOW_CONCRETE_TYPE = 5, + + /* + * Default width and height of ANativeWindow buffers, these are the + * dimensions of the window buffers irrespective of the + * NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS call and match the native window + * size unless overridden by NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS. + */ + NATIVE_WINDOW_DEFAULT_WIDTH = 6 /* ANATIVEWINDOW_QUERY_DEFAULT_WIDTH */, + NATIVE_WINDOW_DEFAULT_HEIGHT = 7 /* ANATIVEWINDOW_QUERY_DEFAULT_HEIGHT */, + + /* see ANativeWindowQuery in vndk/window.h */ + NATIVE_WINDOW_TRANSFORM_HINT = 8 /* ANATIVEWINDOW_QUERY_TRANSFORM_HINT */, + + /* + * Boolean that indicates whether the consumer is running more than + * one buffer behind the producer. + */ + NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND = 9, + + /* + * The consumer gralloc usage bits currently set by the consumer. + * The values are defined in hardware/libhardware/include/gralloc.h. + */ + NATIVE_WINDOW_CONSUMER_USAGE_BITS = 10, /* deprecated */ + + /** + * Transformation that will by applied to buffers by the hwcomposer. + * This must not be set or checked by producer endpoints, and will + * disable the transform hint set in SurfaceFlinger (see + * NATIVE_WINDOW_TRANSFORM_HINT). + * + * INTENDED USE: + * Temporary - Please do not use this. This is intended only to be used + * by the camera's LEGACY mode. + * + * In situations where a SurfaceFlinger client wishes to set a transform + * that is not visible to the producer, and will always be applied in the + * hardware composer, the client can set this flag with + * native_window_set_buffers_sticky_transform. This can be used to rotate + * and flip buffers consumed by hardware composer without actually changing + * the aspect ratio of the buffers produced. + */ + NATIVE_WINDOW_STICKY_TRANSFORM = 11, + + /** + * The default data space for the buffers as set by the consumer. + * The values are defined in graphics.h. + */ + NATIVE_WINDOW_DEFAULT_DATASPACE = 12, + + /* see ANativeWindowQuery in vndk/window.h */ + NATIVE_WINDOW_BUFFER_AGE = 13 /* ANATIVEWINDOW_QUERY_BUFFER_AGE */, + + /* + * Returns the duration of the last dequeueBuffer call in microseconds + * Deprecated: please use NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION in + * perform() instead, which supports nanosecond precision. + */ + NATIVE_WINDOW_LAST_DEQUEUE_DURATION = 14, + + /* + * Returns the duration of the last queueBuffer call in microseconds + * Deprecated: please use NATIVE_WINDOW_GET_LAST_QUEUE_DURATION in + * perform() instead, which supports nanosecond precision. + */ + NATIVE_WINDOW_LAST_QUEUE_DURATION = 15, + + /* + * Returns the number of image layers that the ANativeWindow buffer + * contains. By default this is 1, unless a buffer is explicitly allocated + * to contain multiple layers. + */ + NATIVE_WINDOW_LAYER_COUNT = 16, + + /* + * Returns 1 if the native window is valid, 0 otherwise. native window is valid + * if it is safe (i.e. no crash will occur) to call any method on it. + */ + NATIVE_WINDOW_IS_VALID = 17, + + /* + * Returns 1 if NATIVE_WINDOW_GET_FRAME_TIMESTAMPS will return display + * present info, 0 if it won't. + */ + NATIVE_WINDOW_FRAME_TIMESTAMPS_SUPPORTS_PRESENT = 18, + + /* + * The consumer end is capable of handling protected buffers, i.e. buffer + * with GRALLOC_USAGE_PROTECTED usage bits on. + */ + NATIVE_WINDOW_CONSUMER_IS_PROTECTED = 19, + + /* + * Returns data space for the buffers. + */ + NATIVE_WINDOW_DATASPACE = 20, + + /* + * Returns maxBufferCount set by BufferQueueConsumer + */ + NATIVE_WINDOW_MAX_BUFFER_COUNT = 21, +}; + +/* Valid operations for the (*perform)() hook. + * + * Values marked as 'deprecated' are supported, but have been superceded by + * other functionality. + * + * Values marked as 'private' should be considered private to the framework. + * HAL implementation code with access to an ANativeWindow should not use these, + * as it may not interact properly with the framework's use of the + * ANativeWindow. + */ +enum { + // clang-format off + NATIVE_WINDOW_SET_USAGE = 0 /* ANATIVEWINDOW_PERFORM_SET_USAGE */, /* deprecated */ + NATIVE_WINDOW_CONNECT = 1, /* deprecated */ + NATIVE_WINDOW_DISCONNECT = 2, /* deprecated */ + NATIVE_WINDOW_SET_CROP = 3, /* private */ + NATIVE_WINDOW_SET_BUFFER_COUNT = 4, + NATIVE_WINDOW_SET_BUFFERS_GEOMETRY = 5 /* ANATIVEWINDOW_PERFORM_SET_BUFFERS_GEOMETRY*/, /* deprecated */ + NATIVE_WINDOW_SET_BUFFERS_TRANSFORM = 6, + NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP = 7, + NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS = 8, + NATIVE_WINDOW_SET_BUFFERS_FORMAT = 9 /* ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT */, + NATIVE_WINDOW_SET_SCALING_MODE = 10, /* private */ + NATIVE_WINDOW_LOCK = 11, /* private */ + NATIVE_WINDOW_UNLOCK_AND_POST = 12, /* private */ + NATIVE_WINDOW_API_CONNECT = 13, /* private */ + NATIVE_WINDOW_API_DISCONNECT = 14, /* private */ + NATIVE_WINDOW_SET_BUFFERS_USER_DIMENSIONS = 15, /* private */ + NATIVE_WINDOW_SET_POST_TRANSFORM_CROP = 16, /* deprecated, unimplemented */ + NATIVE_WINDOW_SET_BUFFERS_STICKY_TRANSFORM = 17, /* private */ + NATIVE_WINDOW_SET_SIDEBAND_STREAM = 18, + NATIVE_WINDOW_SET_BUFFERS_DATASPACE = 19, + NATIVE_WINDOW_SET_SURFACE_DAMAGE = 20, /* private */ + NATIVE_WINDOW_SET_SHARED_BUFFER_MODE = 21, + NATIVE_WINDOW_SET_AUTO_REFRESH = 22, + NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION = 23, + NATIVE_WINDOW_GET_NEXT_FRAME_ID = 24, + NATIVE_WINDOW_ENABLE_FRAME_TIMESTAMPS = 25, + NATIVE_WINDOW_GET_COMPOSITOR_TIMING = 26, + NATIVE_WINDOW_GET_FRAME_TIMESTAMPS = 27, + NATIVE_WINDOW_GET_WIDE_COLOR_SUPPORT = 28, + NATIVE_WINDOW_GET_HDR_SUPPORT = 29, + NATIVE_WINDOW_SET_USAGE64 = 30 /* ANATIVEWINDOW_PERFORM_SET_USAGE64 */, + NATIVE_WINDOW_GET_CONSUMER_USAGE64 = 31, + NATIVE_WINDOW_SET_BUFFERS_SMPTE2086_METADATA = 32, + NATIVE_WINDOW_SET_BUFFERS_CTA861_3_METADATA = 33, + NATIVE_WINDOW_SET_BUFFERS_HDR10_PLUS_METADATA = 34, + NATIVE_WINDOW_SET_AUTO_PREROTATION = 35, + NATIVE_WINDOW_GET_LAST_DEQUEUE_START = 36, /* private */ + NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT = 37, /* private */ + NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION = 38, /* private */ + NATIVE_WINDOW_GET_LAST_QUEUE_DURATION = 39, /* private */ + NATIVE_WINDOW_SET_FRAME_RATE = 40, + NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR = 41, /* private */ + NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR = 42, /* private */ + NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR = 43, /* private */ + NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR = 44, /* private */ + NATIVE_WINDOW_ALLOCATE_BUFFERS = 45, /* private */ + NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER = 46, /* private */ + NATIVE_WINDOW_SET_QUERY_INTERCEPTOR = 47, /* private */ + NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO = 48, /* private */ + NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2 = 49, /* private */ + NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS = 50, + // clang-format on +}; + +/* parameter for NATIVE_WINDOW_[API_][DIS]CONNECT */ +enum { + /* Buffers will be queued by EGL via eglSwapBuffers after being filled using + * OpenGL ES. + */ + NATIVE_WINDOW_API_EGL = 1, + + /* Buffers will be queued after being filled using the CPU + */ + NATIVE_WINDOW_API_CPU = 2, + + /* Buffers will be queued by Stagefright after being filled by a video + * decoder. The video decoder can either be a software or hardware decoder. + */ + NATIVE_WINDOW_API_MEDIA = 3, + + /* Buffers will be queued by the the camera HAL. + */ + NATIVE_WINDOW_API_CAMERA = 4, +}; + +/* parameter for NATIVE_WINDOW_SET_BUFFERS_TRANSFORM */ +enum { + /* flip source image horizontally */ + NATIVE_WINDOW_TRANSFORM_FLIP_H = 1 /* HAL_TRANSFORM_FLIP_H */, + /* flip source image vertically */ + NATIVE_WINDOW_TRANSFORM_FLIP_V = 2 /* HAL_TRANSFORM_FLIP_V */, + /* rotate source image 90 degrees clock-wise, and is applied after TRANSFORM_FLIP_{H|V} */ + NATIVE_WINDOW_TRANSFORM_ROT_90 = 4 /* HAL_TRANSFORM_ROT_90 */, + /* rotate source image 180 degrees */ + NATIVE_WINDOW_TRANSFORM_ROT_180 = 3 /* HAL_TRANSFORM_ROT_180 */, + /* rotate source image 270 degrees clock-wise */ + NATIVE_WINDOW_TRANSFORM_ROT_270 = 7 /* HAL_TRANSFORM_ROT_270 */, + /* transforms source by the inverse transform of the screen it is displayed onto. This + * transform is applied last */ + NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY = 0x08 +}; + +/* parameter for NATIVE_WINDOW_SET_SCALING_MODE + * keep in sync with Surface.java in frameworks/base */ +enum { + /* the window content is not updated (frozen) until a buffer of + * the window size is received (enqueued) + */ + NATIVE_WINDOW_SCALING_MODE_FREEZE = 0, + /* the buffer is scaled in both dimensions to match the window size */ + NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW = 1, + /* the buffer is scaled uniformly such that the smaller dimension + * of the buffer matches the window size (cropping in the process) + */ + NATIVE_WINDOW_SCALING_MODE_SCALE_CROP = 2, + /* the window is clipped to the size of the buffer's crop rectangle; pixels + * outside the crop rectangle are treated as if they are completely + * transparent. + */ + NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP = 3, +}; + +/* values returned by the NATIVE_WINDOW_CONCRETE_TYPE query */ +enum { + NATIVE_WINDOW_FRAMEBUFFER = 0, /* FramebufferNativeWindow */ + NATIVE_WINDOW_SURFACE = 1, /* Surface */ +}; + +/* parameter for NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP + * + * Special timestamp value to indicate that timestamps should be auto-generated + * by the native window when queueBuffer is called. This is equal to INT64_MIN, + * defined directly to avoid problems with C99/C++ inclusion of stdint.h. + */ +static const int64_t __unused NATIVE_WINDOW_TIMESTAMP_AUTO = (-9223372036854775807LL-1); + +/* parameter for NATIVE_WINDOW_GET_FRAME_TIMESTAMPS + * + * Special timestamp value to indicate the timestamps aren't yet known or + * that they are invalid. + */ +static const int64_t __unused NATIVE_WINDOW_TIMESTAMP_PENDING = -2; +static const int64_t __unused NATIVE_WINDOW_TIMESTAMP_INVALID = -1; + +struct ANativeWindowBuffer; + +typedef struct android_native_base_t +{ + /* a magic value defined by the actual EGL native type */ + int magic; + + /* the sizeof() of the actual EGL native type */ + int version; + + void* reserved[4]; + + /* reference-counting interface */ + void (*incRef)(struct android_native_base_t* base); + void (*decRef)(struct android_native_base_t* base); +} android_native_base_t; + +struct ANativeWindow +{ + #ifdef __cplusplus + ANativeWindow() + : flags(0), minSwapInterval(0), maxSwapInterval(0), xdpi(0), ydpi(0) + { + common.magic = ANDROID_NATIVE_WINDOW_MAGIC; + common.version = sizeof(ANativeWindow); + memset(common.reserved, 0, sizeof(common.reserved)); + } + + /* Implement the methods that sp expects so that it + * can be used to automatically refcount ANativeWindow's. */ + void incStrong(const void* /*id*/) const { + common.incRef(const_cast(&common)); + } + void decStrong(const void* /*id*/) const { + common.decRef(const_cast(&common)); + } + #endif + + struct android_native_base_t common; + + /* flags describing some attributes of this surface or its updater */ + const uint32_t flags; + + /* min swap interval supported by this updated */ + const int minSwapInterval; + + /* max swap interval supported by this updated */ + const int maxSwapInterval; + + /* horizontal and vertical resolution in DPI */ + const float xdpi; + const float ydpi; + + /* Some storage reserved for the OEM's driver. */ + intptr_t oem[4]; + + /* + * Set the swap interval for this surface. + * + * Returns 0 on success or -errno on error. + */ + int (*setSwapInterval)(struct ANativeWindow* window, + int interval); + + /* + * Hook called by EGL to acquire a buffer. After this call, the buffer + * is not locked, so its content cannot be modified. This call may block if + * no buffers are available. + * + * The window holds a reference to the buffer between dequeueBuffer and + * either queueBuffer or cancelBuffer, so clients only need their own + * reference if they might use the buffer after queueing or canceling it. + * Holding a reference to a buffer after queueing or canceling it is only + * allowed if a specific buffer count has been set. + * + * Returns 0 on success or -errno on error. + * + * XXX: This function is deprecated. It will continue to work for some + * time for binary compatibility, but the new dequeueBuffer function that + * outputs a fence file descriptor should be used in its place. + */ + int (*dequeueBuffer_DEPRECATED)(struct ANativeWindow* window, + struct ANativeWindowBuffer** buffer); + + /* + * hook called by EGL to lock a buffer. This MUST be called before modifying + * the content of a buffer. The buffer must have been acquired with + * dequeueBuffer first. + * + * Returns 0 on success or -errno on error. + * + * XXX: This function is deprecated. It will continue to work for some + * time for binary compatibility, but it is essentially a no-op, and calls + * to it should be removed. + */ + int (*lockBuffer_DEPRECATED)(struct ANativeWindow* window, + struct ANativeWindowBuffer* buffer); + + /* + * Hook called by EGL when modifications to the render buffer are done. + * This unlocks and post the buffer. + * + * The window holds a reference to the buffer between dequeueBuffer and + * either queueBuffer or cancelBuffer, so clients only need their own + * reference if they might use the buffer after queueing or canceling it. + * Holding a reference to a buffer after queueing or canceling it is only + * allowed if a specific buffer count has been set. + * + * Buffers MUST be queued in the same order than they were dequeued. + * + * Returns 0 on success or -errno on error. + * + * XXX: This function is deprecated. It will continue to work for some + * time for binary compatibility, but the new queueBuffer function that + * takes a fence file descriptor should be used in its place (pass a value + * of -1 for the fence file descriptor if there is no valid one to pass). + */ + int (*queueBuffer_DEPRECATED)(struct ANativeWindow* window, + struct ANativeWindowBuffer* buffer); + + /* + * hook used to retrieve information about the native window. + * + * Returns 0 on success or -errno on error. + */ + int (*query)(const struct ANativeWindow* window, + int what, int* value); + + /* + * hook used to perform various operations on the surface. + * (*perform)() is a generic mechanism to add functionality to + * ANativeWindow while keeping backward binary compatibility. + * + * DO NOT CALL THIS HOOK DIRECTLY. Instead, use the helper functions + * defined below. + * + * (*perform)() returns -ENOENT if the 'what' parameter is not supported + * by the surface's implementation. + * + * See above for a list of valid operations, such as + * NATIVE_WINDOW_SET_USAGE or NATIVE_WINDOW_CONNECT + */ + int (*perform)(struct ANativeWindow* window, + int operation, ... ); + + /* + * Hook used to cancel a buffer that has been dequeued. + * No synchronization is performed between dequeue() and cancel(), so + * either external synchronization is needed, or these functions must be + * called from the same thread. + * + * The window holds a reference to the buffer between dequeueBuffer and + * either queueBuffer or cancelBuffer, so clients only need their own + * reference if they might use the buffer after queueing or canceling it. + * Holding a reference to a buffer after queueing or canceling it is only + * allowed if a specific buffer count has been set. + * + * XXX: This function is deprecated. It will continue to work for some + * time for binary compatibility, but the new cancelBuffer function that + * takes a fence file descriptor should be used in its place (pass a value + * of -1 for the fence file descriptor if there is no valid one to pass). + */ + int (*cancelBuffer_DEPRECATED)(struct ANativeWindow* window, + struct ANativeWindowBuffer* buffer); + + /* + * Hook called by EGL to acquire a buffer. This call may block if no + * buffers are available. + * + * The window holds a reference to the buffer between dequeueBuffer and + * either queueBuffer or cancelBuffer, so clients only need their own + * reference if they might use the buffer after queueing or canceling it. + * Holding a reference to a buffer after queueing or canceling it is only + * allowed if a specific buffer count has been set. + * + * The libsync fence file descriptor returned in the int pointed to by the + * fenceFd argument will refer to the fence that must signal before the + * dequeued buffer may be written to. A value of -1 indicates that the + * caller may access the buffer immediately without waiting on a fence. If + * a valid file descriptor is returned (i.e. any value except -1) then the + * caller is responsible for closing the file descriptor. + * + * Returns 0 on success or -errno on error. + */ + int (*dequeueBuffer)(struct ANativeWindow* window, + struct ANativeWindowBuffer** buffer, int* fenceFd); + + /* + * Hook called by EGL when modifications to the render buffer are done. + * This unlocks and post the buffer. + * + * The window holds a reference to the buffer between dequeueBuffer and + * either queueBuffer or cancelBuffer, so clients only need their own + * reference if they might use the buffer after queueing or canceling it. + * Holding a reference to a buffer after queueing or canceling it is only + * allowed if a specific buffer count has been set. + * + * The fenceFd argument specifies a libsync fence file descriptor for a + * fence that must signal before the buffer can be accessed. If the buffer + * can be accessed immediately then a value of -1 should be used. The + * caller must not use the file descriptor after it is passed to + * queueBuffer, and the ANativeWindow implementation is responsible for + * closing it. + * + * Returns 0 on success or -errno on error. + */ + int (*queueBuffer)(struct ANativeWindow* window, + struct ANativeWindowBuffer* buffer, int fenceFd); + + /* + * Hook used to cancel a buffer that has been dequeued. + * No synchronization is performed between dequeue() and cancel(), so + * either external synchronization is needed, or these functions must be + * called from the same thread. + * + * The window holds a reference to the buffer between dequeueBuffer and + * either queueBuffer or cancelBuffer, so clients only need their own + * reference if they might use the buffer after queueing or canceling it. + * Holding a reference to a buffer after queueing or canceling it is only + * allowed if a specific buffer count has been set. + * + * The fenceFd argument specifies a libsync fence file decsriptor for a + * fence that must signal before the buffer can be accessed. If the buffer + * can be accessed immediately then a value of -1 should be used. + * + * Note that if the client has not waited on the fence that was returned + * from dequeueBuffer, that same fence should be passed to cancelBuffer to + * ensure that future uses of the buffer are preceded by a wait on that + * fence. The caller must not use the file descriptor after it is passed + * to cancelBuffer, and the ANativeWindow implementation is responsible for + * closing it. + * + * Returns 0 on success or -errno on error. + */ + int (*cancelBuffer)(struct ANativeWindow* window, + struct ANativeWindowBuffer* buffer, int fenceFd); +}; diff --git a/packages/termux-wsi-layer/include/utils/Errors.h b/packages/termux-wsi-layer/include/utils/Errors.h new file mode 100644 index 000000000000000..0fafa870b2994be --- /dev/null +++ b/packages/termux-wsi-layer/include/utils/Errors.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +#include +namespace android { +#endif + + /** + * The type used to return success/failure from frameworks APIs. + * See the anonymous enum below for valid values. + */ + typedef int32_t status_t; + + /* + * Error codes. + * All error codes are negative values. + */ + + enum { + OK = 0, // Preferred constant for checking success. + #ifndef NO_ERROR + // Win32 #defines NO_ERROR as well. It has the same value, so there's no + // real conflict, though it's a bit awkward. + NO_ERROR = OK, // Deprecated synonym for `OK`. Prefer `OK` because it doesn't conflict with Windows. + #endif + + UNKNOWN_ERROR = (-2147483647-1), // INT32_MIN value + + NO_MEMORY = -ENOMEM, + INVALID_OPERATION = -ENOSYS, + BAD_VALUE = -EINVAL, + BAD_TYPE = (UNKNOWN_ERROR + 1), + NAME_NOT_FOUND = -ENOENT, + PERMISSION_DENIED = -EPERM, + NO_INIT = -ENODEV, + ALREADY_EXISTS = -EEXIST, + DEAD_OBJECT = -EPIPE, + FAILED_TRANSACTION = (UNKNOWN_ERROR + 2), + #if !defined(_WIN32) + BAD_INDEX = -EOVERFLOW, + NOT_ENOUGH_DATA = -ENODATA, + WOULD_BLOCK = -EWOULDBLOCK, + TIMED_OUT = -ETIMEDOUT, + UNKNOWN_TRANSACTION = -EBADMSG, + #else + BAD_INDEX = -E2BIG, + NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3), + WOULD_BLOCK = (UNKNOWN_ERROR + 4), + TIMED_OUT = (UNKNOWN_ERROR + 5), + UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6), + #endif + FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7), + UNEXPECTED_NULL = (UNKNOWN_ERROR + 8), + }; + +#ifdef __cplusplus + // Human readable name of error + std::string statusToString(status_t status); + +} // namespace android +#endif diff --git a/packages/termux-wsi-layer/sync.c b/packages/termux-wsi-layer/sync.c new file mode 100644 index 000000000000000..b6c927f0bf20f2c --- /dev/null +++ b/packages/termux-wsi-layer/sync.c @@ -0,0 +1,447 @@ +/* + * sync.c + * + * Copyright 2012 Google, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Workaround for `error: 'sync_file_info_free' is unavailable: introduced in Android 26` +#define sync_file_info_free _sync_file_info_free +#include +#undef sync_file_info_free +void sync_file_info_free(struct sync_file_info *info); + +/* Prototypes for deprecated functions that used to be declared in the legacy + * android/sync.h. They've been moved here to make sure new code does not use + * them, but the functions are still defined to avoid breaking existing + * binaries. Eventually they can be removed altogether. + */ +struct sync_fence_info_data { + uint32_t len; + char name[32]; + int32_t status; + uint8_t pt_info[0]; +}; +struct sync_pt_info { + uint32_t len; + char obj_name[32]; + char driver_name[32]; + int32_t status; + uint64_t timestamp_ns; + uint8_t driver_data[0]; +}; +struct sync_fence_info_data* sync_fence_info(int fd); +struct sync_pt_info* sync_pt_info(struct sync_fence_info_data* info, struct sync_pt_info* itr); +void sync_fence_info_free(struct sync_fence_info_data* info); + +/* Legacy Sync API */ + +struct sync_legacy_merge_data { + int32_t fd2; + char name[32]; + int32_t fence; +}; + +/** + * DOC: SYNC_IOC_MERGE - merge two fences + * + * Takes a struct sync_merge_data. Creates a new fence containing copies of + * the sync_pts in both the calling fd and sync_merge_data.fd2. Returns the + * new fence's fd in sync_merge_data.fence + * + * This is the legacy version of the Sync API before the de-stage that happened + * on Linux kernel 4.7. + */ +#define SYNC_IOC_LEGACY_MERGE _IOWR(SYNC_IOC_MAGIC, 1, \ +struct sync_legacy_merge_data) + +/** + * DOC: SYNC_IOC_LEGACY_FENCE_INFO - get detailed information on a fence + * + * Takes a struct sync_fence_info_data with extra space allocated for pt_info. + * Caller should write the size of the buffer into len. On return, len is + * updated to reflect the total size of the sync_fence_info_data including + * pt_info. + * + * pt_info is a buffer containing sync_pt_infos for every sync_pt in the fence. + * To iterate over the sync_pt_infos, use the sync_pt_info.len field. + * + * This is the legacy version of the Sync API before the de-stage that happened + * on Linux kernel 4.7. + */ +#define SYNC_IOC_LEGACY_FENCE_INFO _IOWR(SYNC_IOC_MAGIC, 2,\ +struct sync_fence_info_data) + +/* SW Sync API */ + +struct sw_sync_create_fence_data { + __u32 value; + char name[32]; + __s32 fence; +}; + +#define SW_SYNC_IOC_MAGIC 'W' +#define SW_SYNC_IOC_CREATE_FENCE _IOWR(SW_SYNC_IOC_MAGIC, 0, struct sw_sync_create_fence_data) +#define SW_SYNC_IOC_INC _IOW(SW_SYNC_IOC_MAGIC, 1, __u32) + +// --------------------------------------------------------------------------- +// Support for caching the sync uapi version. +// +// This library supports both legacy (android/staging) uapi and modern +// (mainline) sync uapi. Library calls first try one uapi, and if that fails, +// try the other. Since any given kernel only supports one uapi version, after +// the first successful syscall we know what the kernel supports and can skip +// trying the other. + +enum uapi_version { + UAPI_UNKNOWN, + UAPI_MODERN, + UAPI_LEGACY +}; +static atomic_int g_uapi_version = ATOMIC_VAR_INIT(UAPI_UNKNOWN); + +// --------------------------------------------------------------------------- + +int sync_wait(int fd, int timeout) +{ + struct pollfd fds; + int ret; + + if (fd < 0) { + errno = EINVAL; + return -1; + } + + fds.fd = fd; + fds.events = POLLIN; + + do { + ret = poll(&fds, 1, timeout); + if (ret > 0) { + if (fds.revents & (POLLERR | POLLNVAL)) { + errno = EINVAL; + return -1; + } + return 0; + } else if (ret == 0) { + errno = ETIME; + return -1; + } + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + + return ret; +} + +static int legacy_sync_merge(const char *name, int fd1, int fd2) +{ + struct sync_legacy_merge_data data; + int ret; + + data.fd2 = fd2; + strlcpy(data.name, name, sizeof(data.name)); + ret = ioctl(fd1, SYNC_IOC_LEGACY_MERGE, &data); + if (ret < 0) + return ret; + return data.fence; +} + +static int modern_sync_merge(const char *name, int fd1, int fd2) +{ + struct sync_merge_data data; + int ret; + + data.fd2 = fd2; + strlcpy(data.name, name, sizeof(data.name)); + data.flags = 0; + data.pad = 0; + + ret = ioctl(fd1, SYNC_IOC_MERGE, &data); + if (ret < 0) + return ret; + return data.fence; +} + +int sync_merge(const char *name, int fd1, int fd2) +{ + int uapi; + int ret; + + uapi = atomic_load_explicit(&g_uapi_version, memory_order_acquire); + + if (uapi == UAPI_MODERN || uapi == UAPI_UNKNOWN) { + ret = modern_sync_merge(name, fd1, fd2); + if (ret >= 0 || errno != ENOTTY) { + if (ret >= 0 && uapi == UAPI_UNKNOWN) { + atomic_store_explicit(&g_uapi_version, UAPI_MODERN, + memory_order_release); + } + return ret; + } + } + + ret = legacy_sync_merge(name, fd1, fd2); + if (ret >= 0 && uapi == UAPI_UNKNOWN) { + atomic_store_explicit(&g_uapi_version, UAPI_LEGACY, + memory_order_release); + } + return ret; +} + +static struct sync_fence_info_data *legacy_sync_fence_info(int fd) +{ + struct sync_fence_info_data *legacy_info; + struct sync_pt_info *legacy_pt_info; + int err; + + legacy_info = malloc(4096); + if (legacy_info == NULL) + return NULL; + + legacy_info->len = 4096; + err = ioctl(fd, SYNC_IOC_LEGACY_FENCE_INFO, legacy_info); + if (err < 0) { + free(legacy_info); + return NULL; + } + return legacy_info; +} + +static struct sync_file_info *modern_sync_file_info(int fd) +{ + struct sync_file_info local_info; + struct sync_file_info *info; + int err; + + memset(&local_info, 0, sizeof(local_info)); + err = ioctl(fd, SYNC_IOC_FILE_INFO, &local_info); + if (err < 0) + return NULL; + + info = calloc(1, sizeof(struct sync_file_info) + + local_info.num_fences * sizeof(struct sync_fence_info)); + if (!info) + return NULL; + + info->num_fences = local_info.num_fences; + info->sync_fence_info = (__u64)(uintptr_t)(info + 1); + + err = ioctl(fd, SYNC_IOC_FILE_INFO, info); + if (err < 0) { + free(info); + return NULL; + } + + return info; +} + +static struct sync_fence_info_data *sync_file_info_to_legacy_fence_info( + const struct sync_file_info *info) +{ + struct sync_fence_info_data *legacy_info; + struct sync_pt_info *legacy_pt_info; + const struct sync_fence_info *fence_info = sync_get_fence_info(info); + const uint32_t num_fences = info->num_fences; + + legacy_info = malloc(4096); + if (legacy_info == NULL) + return NULL; + legacy_info->len = sizeof(*legacy_info) + + num_fences * sizeof(struct sync_pt_info); + strlcpy(legacy_info->name, info->name, sizeof(legacy_info->name)); + legacy_info->status = info->status; + + legacy_pt_info = (struct sync_pt_info *)legacy_info->pt_info; + for (uint32_t i = 0; i < num_fences; i++) { + legacy_pt_info[i].len = sizeof(*legacy_pt_info); + strlcpy(legacy_pt_info[i].obj_name, fence_info[i].obj_name, + sizeof(legacy_pt_info->obj_name)); + strlcpy(legacy_pt_info[i].driver_name, fence_info[i].driver_name, + sizeof(legacy_pt_info->driver_name)); + legacy_pt_info[i].status = fence_info[i].status; + legacy_pt_info[i].timestamp_ns = fence_info[i].timestamp_ns; + } + + return legacy_info; +} + +static struct sync_file_info* legacy_fence_info_to_sync_file_info( + struct sync_fence_info_data *legacy_info) +{ + struct sync_file_info *info; + struct sync_pt_info *pt; + struct sync_fence_info *fence; + size_t num_fences; + int err; + + pt = NULL; + num_fences = 0; + while ((pt = sync_pt_info(legacy_info, pt)) != NULL) + num_fences++; + + info = calloc(1, sizeof(struct sync_file_info) + + num_fences * sizeof(struct sync_fence_info)); + if (!info) { + return NULL; + } + info->sync_fence_info = (__u64)(uintptr_t)(info + 1); + + strlcpy(info->name, legacy_info->name, sizeof(info->name)); + info->status = legacy_info->status; + info->num_fences = num_fences; + + pt = NULL; + fence = sync_get_fence_info(info); + while ((pt = sync_pt_info(legacy_info, pt)) != NULL) { + strlcpy(fence->obj_name, pt->obj_name, sizeof(fence->obj_name)); + strlcpy(fence->driver_name, pt->driver_name, + sizeof(fence->driver_name)); + fence->status = pt->status; + fence->timestamp_ns = pt->timestamp_ns; + fence++; + } + + return info; +} + +struct sync_fence_info_data *sync_fence_info(int fd) +{ + struct sync_fence_info_data *legacy_info; + int uapi; + + uapi = atomic_load_explicit(&g_uapi_version, memory_order_acquire); + + if (uapi == UAPI_LEGACY || uapi == UAPI_UNKNOWN) { + legacy_info = legacy_sync_fence_info(fd); + if (legacy_info || errno != ENOTTY) { + if (legacy_info && uapi == UAPI_UNKNOWN) { + atomic_store_explicit(&g_uapi_version, UAPI_LEGACY, + memory_order_release); + } + return legacy_info; + } + } + + struct sync_file_info* file_info; + file_info = modern_sync_file_info(fd); + if (!file_info) + return NULL; + if (uapi == UAPI_UNKNOWN) { + atomic_store_explicit(&g_uapi_version, UAPI_MODERN, + memory_order_release); + } + legacy_info = sync_file_info_to_legacy_fence_info(file_info); + sync_file_info_free(file_info); + return legacy_info; +} + +struct sync_file_info* sync_file_info(int32_t fd) +{ + struct sync_file_info *info; + int uapi; + + uapi = atomic_load_explicit(&g_uapi_version, memory_order_acquire); + + if (uapi == UAPI_MODERN || uapi == UAPI_UNKNOWN) { + info = modern_sync_file_info(fd); + if (info || errno != ENOTTY) { + if (info && uapi == UAPI_UNKNOWN) { + atomic_store_explicit(&g_uapi_version, UAPI_MODERN, + memory_order_release); + } + return info; + } + } + + struct sync_fence_info_data *legacy_info; + legacy_info = legacy_sync_fence_info(fd); + if (!legacy_info) + return NULL; + if (uapi == UAPI_UNKNOWN) { + atomic_store_explicit(&g_uapi_version, UAPI_LEGACY, + memory_order_release); + } + info = legacy_fence_info_to_sync_file_info(legacy_info); + sync_fence_info_free(legacy_info); + return info; +} + +struct sync_pt_info *sync_pt_info(struct sync_fence_info_data *info, + struct sync_pt_info *itr) +{ + if (itr == NULL) + itr = (struct sync_pt_info *) info->pt_info; + else + itr = (struct sync_pt_info *) ((__u8 *)itr + itr->len); + + if ((__u8 *)itr - (__u8 *)info >= (int)info->len) + return NULL; + + return itr; +} + +void sync_fence_info_free(struct sync_fence_info_data *info) +{ + free(info); +} + +void sync_file_info_free(struct sync_file_info *info) +{ + free(info); +} + + +int sw_sync_timeline_create(void) +{ + int ret; + + ret = open("/sys/kernel/debug/sync/sw_sync", O_RDWR); + if (ret < 0) + ret = open("/dev/sw_sync", O_RDWR); + + return ret; +} + +int sw_sync_timeline_inc(int fd, unsigned count) +{ + __u32 arg = count; + + return ioctl(fd, SW_SYNC_IOC_INC, &arg); +} + +int sw_sync_fence_create(int fd, const char *name, unsigned value) +{ + struct sw_sync_create_fence_data data; + int err; + + data.value = value; + strlcpy(data.name, name, sizeof(data.name)); + + err = ioctl(fd, SW_SYNC_IOC_CREATE_FENCE, &data); + if (err < 0) + return err; + + return data.fence; +} diff --git a/packages/termux-wsi-layer/test.c b/packages/termux-wsi-layer/test.c new file mode 100644 index 000000000000000..d9d253c495a687b --- /dev/null +++ b/packages/termux-wsi-layer/test.c @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2012 Carsten Munk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +GLuint create_program(const char* pVertexSource, const char* pFragmentSource); + +const char vertex_src [] = +" \ +attribute vec4 position; \ +varying mediump vec2 pos; \ +uniform vec4 offset; \ +\ +void main() \ +{ \ +gl_Position = position + offset; \ +pos = position.xy; \ +} \ +"; + +const char fragment_src [] = +" \ +varying mediump vec2 pos; \ +uniform mediump float phase; \ +\ +void main() \ +{ \ +gl_FragColor = vec4( 1., 0.9, 0.7, 1.0 ) * \ +cos( 30.*sqrt(pos.x*pos.x + 1.5*pos.y*pos.y) \ ++ atan(pos.y,pos.x) - phase ); \ +} \ +"; + +GLfloat norm_x = 0.0f; +GLfloat norm_y = 0.0f; +GLfloat offset_x = 0.0f; +GLfloat offset_y = 0.0f; +GLfloat p1_pos_x = 0.0f; +GLfloat p1_pos_y = 0.0f; + +GLint phase_loc; +GLint offset_loc; +GLint position_loc; + +const float vertexArray[] = { + 0.0f , 1.0f , 0.0f, + -1.0f, 0.0f , 0.0f, + 0.0f , -1.0f, 0.0f, + 1.0f , 0.0f , 0.0f, + 0.0f , 1.0f , 0.0f +}; + +int main(int __unused argc, char __unused **argv) { + EGLConfig ecfg; + EGLint num_config; + EGLint attr[] = { // some attributes to set up our egl-interface + EGL_BUFFER_SIZE, 32, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + EGLint ctxattr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + setenv("DISPLAY", ":0", 0); + + Display * dpy = XOpenDisplay(NULL); + if (NULL == dpy) { + dprintf(2, "Failed to initialize display"); + return EXIT_FAILURE; + } + + Window root = DefaultRootWindow(dpy); + if (None == root) { + dprintf(2, "No root window found"); + XCloseDisplay(dpy); + return EXIT_FAILURE; + } + + const Window window = XCreateSimpleWindow(dpy, root, 0, 0, 480, 800, 0, 0, 0xffffffff); + if (None == window) { + fprintf(stderr, "Failed to create window"); + XCloseDisplay(dpy); + return EXIT_FAILURE; + } + + XMapWindow(dpy, window); + + EGLDisplay display = eglGetDisplay(dpy); + assert(eglGetError() == EGL_SUCCESS); + assert(display != EGL_NO_DISPLAY); + + EGLBoolean rv = eglInitialize(display, 0, 0); + assert(eglGetError() == EGL_SUCCESS); + assert(rv == EGL_TRUE); + + eglChooseConfig(display, attr, &ecfg, 1, &num_config); + assert(eglGetError() == EGL_SUCCESS); + assert(rv == EGL_TRUE); + + EGLSurface surface; + if ((surface = eglCreateWindowSurface(display, ecfg, window, NULL)) == EGL_NO_SURFACE) { + dprintf(2, "eglCreateWindowSurface failed, error 0x%X\n", eglGetError()); + abort(); + } + + EGLContext context = eglCreateContext(display, ecfg, EGL_NO_CONTEXT, ctxattr); + assert(eglGetError() == EGL_SUCCESS); + assert(context != EGL_NO_CONTEXT); + + if (eglMakeCurrent(display, surface, surface, context) != EGL_TRUE) { + dprintf(2, "eglMakeCurrent failed, error 0x%X\n", eglGetError()); + abort(); + } + + const char *version = (const char *)glGetString(GL_VERSION); + assert(version); + printf("%s\n",version); + + GLuint shaderProgram = create_program(vertex_src, fragment_src); + glUseProgram ( shaderProgram ); // and select it for usage + + //// now get the locations (kind of handle) of the shaders variables + position_loc = glGetAttribLocation ( shaderProgram , "position" ); + phase_loc = glGetUniformLocation ( shaderProgram , "phase" ); + offset_loc = glGetUniformLocation ( shaderProgram , "offset" ); + if ( position_loc < 0 || phase_loc < 0 || offset_loc < 0 ) { + return EXIT_FAILURE; + } + + //glViewport ( 0 , 0 , 800, 600); // commented out so it uses the initial window dimensions + glClearColor ( 1.f, 1.f, 1.f, 1.f); // background color + float phase = 0; + int frames = -1; + if (argc == 2) { + frames = (int) strtol(argv[1], NULL, 10); + } + if(frames < 0) { + frames = 30 * 60; + } + + for (int i = 0; i < frames; ++i) { + if(i % 60 == 0) printf("frame:%i\n", i); + glClear(GL_COLOR_BUFFER_BIT); + glUniform1f ( phase_loc, phase ); // write the value of phase to the shaders phase + phase = fmodf ( phase + 0.5f, 2.f * 3.141f ); // and update the local variable + + glUniform4f ( offset_loc, offset_x, offset_y, 0.0f, 0.0f ); + + glVertexAttribPointer ( position_loc, 3, GL_FLOAT, GL_FALSE, 0, vertexArray ); + glEnableVertexAttribArray ( position_loc ); + glDrawArrays ( GL_TRIANGLE_STRIP, 0, 5 ); + + eglSwapBuffers (display, surface ); // get the rendered buffer to the screen + assert(eglGetError() == EGL_SUCCESS); + } + + printf("stop\n"); + + return EXIT_SUCCESS; +} + +GLuint load_shader(const GLenum shaderType, const char* pSource) +{ + GLuint shader = glCreateShader(shaderType); + + if (shader) { + glShaderSource(shader, 1, &pSource, NULL); + glCompileShader(shader); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen) { + char* buf = (char*) malloc(infoLen); + if (buf) { + glGetShaderInfoLog(shader, infoLen, NULL, buf); + fprintf(stderr, "Could not compile shader %d:\n%s\n", + shaderType, buf); + free(buf); + } + glDeleteShader(shader); + shader = 0; + } + } + } else { + printf("Error, during shader creation: %i\n", glGetError()); + } + + return shader; +} + +GLuint create_program(const char* pVertexSource, const char* pFragmentSource) +{ + GLuint vertexShader = load_shader(GL_VERTEX_SHADER, pVertexSource); + if (!vertexShader) { + printf("vertex shader not compiled\n"); + return 0; + } + + GLuint pixelShader = load_shader(GL_FRAGMENT_SHADER, pFragmentSource); + if (!pixelShader) { + printf("frag shader not compiled\n"); + return 0; + } + + GLuint program = glCreateProgram(); + if (program) { + glAttachShader(program, vertexShader); + glAttachShader(program, pixelShader); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus != GL_TRUE) { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) { + char* buf = (char*) malloc(bufLength); + if (buf) { + glGetProgramInfoLog(program, bufLength, NULL, buf); + fprintf(stderr, "Could not link program:\n%s\n", buf); + free(buf); + } + } + glDeleteProgram(program); + program = 0; + } + } + + return program; +} diff --git a/packages/termux-wsi-layer/window.c b/packages/termux-wsi-layer/window.c new file mode 100644 index 000000000000000..eea1865409efb3b --- /dev/null +++ b/packages/termux-wsi-layer/window.c @@ -0,0 +1,670 @@ +/* + * Copyright (c) 2024 Twaik Yont + * + * Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// ReSharper disable CppParameterMayBeConst + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "common.h" + +typedef int64_t nsecs_t; // nano-seconds + +static bool anw_trace_enabled = false; +#define ANW_TRACE(args, ...) do {\ + if (anw_trace_enabled) \ + dprintf(2, "%s:%d: %s(" args ")\n", __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); \ + } while (0) + +#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );}) +int sync_wait(int fd, int timeout); + +static const char* getQueryOpString(int query) { + switch(query) { + #define QUERY(name) case NATIVE_WINDOW_ ## name: return #name + QUERY(WIDTH); + QUERY(HEIGHT); + QUERY(FORMAT); + QUERY(MIN_UNDEQUEUED_BUFFERS); + QUERY(QUEUES_TO_WINDOW_COMPOSER); + QUERY(CONCRETE_TYPE); + QUERY(DEFAULT_WIDTH); + QUERY(DEFAULT_HEIGHT); + QUERY(TRANSFORM_HINT); + QUERY(CONSUMER_RUNNING_BEHIND); + QUERY(CONSUMER_USAGE_BITS); + QUERY(STICKY_TRANSFORM); + QUERY(DEFAULT_DATASPACE); + QUERY(BUFFER_AGE); + QUERY(LAST_DEQUEUE_DURATION); + QUERY(LAST_QUEUE_DURATION); + QUERY(LAYER_COUNT); + QUERY(IS_VALID); + QUERY(FRAME_TIMESTAMPS_SUPPORTS_PRESENT); + QUERY(CONSUMER_IS_PROTECTED); + QUERY(DATASPACE); + QUERY(MAX_BUFFER_COUNT); + #undef QUERY + default: return NULL; + } +} + +static const char* getPerformOpString(int op) { + switch(op) { + #define OP(name) case NATIVE_WINDOW_ ## name: return #name; + OP(SET_USAGE); + OP(CONNECT); + OP(DISCONNECT); + OP(SET_CROP); + OP(SET_BUFFER_COUNT); + OP(SET_BUFFERS_GEOMETRY); + OP(SET_BUFFERS_TRANSFORM); + OP(SET_BUFFERS_TIMESTAMP); + OP(SET_BUFFERS_DIMENSIONS); + OP(SET_BUFFERS_FORMAT); + OP(SET_SCALING_MODE); + OP(LOCK); + OP(UNLOCK_AND_POST); + OP(API_CONNECT); + OP(API_DISCONNECT); + OP(SET_BUFFERS_USER_DIMENSIONS); + OP(SET_POST_TRANSFORM_CROP); + OP(SET_BUFFERS_STICKY_TRANSFORM); + OP(SET_SIDEBAND_STREAM); + OP(SET_BUFFERS_DATASPACE); + OP(SET_SURFACE_DAMAGE); + OP(SET_SHARED_BUFFER_MODE); + OP(SET_AUTO_REFRESH); + OP(GET_REFRESH_CYCLE_DURATION); + OP(GET_NEXT_FRAME_ID); + OP(ENABLE_FRAME_TIMESTAMPS); + OP(GET_COMPOSITOR_TIMING); + OP(GET_FRAME_TIMESTAMPS); + OP(GET_WIDE_COLOR_SUPPORT); + OP(GET_HDR_SUPPORT) + OP(SET_USAGE64); + OP(GET_CONSUMER_USAGE64); + OP(SET_BUFFERS_SMPTE2086_METADATA); + OP(SET_BUFFERS_CTA861_3_METADATA); + OP(SET_BUFFERS_HDR10_PLUS_METADATA); + OP(SET_AUTO_PREROTATION); + OP(GET_LAST_DEQUEUE_START); + OP(SET_DEQUEUE_TIMEOUT); + OP(GET_LAST_DEQUEUE_DURATION); + OP(GET_LAST_QUEUE_DURATION); + OP(SET_FRAME_RATE); + OP(SET_CANCEL_INTERCEPTOR); + OP(SET_DEQUEUE_INTERCEPTOR); + OP(SET_PERFORM_INTERCEPTOR); + OP(SET_QUEUE_INTERCEPTOR); + OP(ALLOCATE_BUFFERS); + OP(GET_LAST_QUEUED_BUFFER); + OP(SET_QUERY_INTERCEPTOR); + OP(SET_FRAME_TIMELINE_INFO); + OP(GET_LAST_QUEUED_BUFFER2); + OP(SET_BUFFERS_ADDITIONAL_OPTIONS); + default: return NULL; + } +} + +static size_t ANativeWindowBufferOffset = 0; +#define to_ANativeWindowBuffer(addr) ((struct ANativeWindowBuffer*) ((void*) addr + ANativeWindowBufferOffset)) +#define from_ANativeWindowBuffer(addr) ((AHardwareBuffer*) ((void*) addr - ANativeWindowBufferOffset)) + +struct TermuxNativeWindow { + int refcount; + xcb_connection_t *conn; + xcb_special_event_t *special_event; + xcb_gcontext_t gc; + xcb_window_t win; + int32_t width, height, usage; + bool allocationNeeded; + uint8_t swapInterval; + nsecs_t vsyncPeriod; + struct{ + AHardwareBuffer* self; + uint32_t pixmap, serial; + uint8_t busy; + } buffers[2], *front, *back; + struct ANativeWindow base; + void* reserved[16]; // We do not know if vendor added anything to the end of struct or not. Let's reserve some space. +}; + +EGLAPI void TermuxNativeWindow_incRef(android_native_base_t* base) { + struct ANativeWindow *awin = container_of(base, struct ANativeWindow, common); + struct TermuxNativeWindow* win = container_of(awin, struct TermuxNativeWindow, base); + ANW_TRACE("base %p awin %p win %p", base, awin, win); + __sync_fetch_and_add(&win->refcount,1); +} + +EGLAPI void TermuxNativeWindow_decRef(android_native_base_t* base) { + struct ANativeWindow *awin = container_of(base, struct ANativeWindow, common); + struct TermuxNativeWindow* win = container_of(awin, struct TermuxNativeWindow, base); + ANW_TRACE("base %p awin %p win %p", base, awin, win); + if (__sync_fetch_and_sub(&win->refcount, 1) == 1) { + ANW_TRACE("destroying win %p/%p/%p", base, awin, win); + for (int i=0; ibuffers)/sizeof(win->buffers[0]); i++) { + if (!win->buffers[i].self) + continue; + + AHardwareBuffer_release(win->buffers[i].self); + xcb_free_pixmap_checked(win->conn, win->buffers[i].pixmap); + memset(&win->buffers[i], 0, sizeof(win->buffers[i])); + } + free(win); + } +} + +static nsecs_t TermuxNativeWindow_getVSyncPeriod(struct TermuxNativeWindow* win) { + nsecs_t period = 33333333; // Default, 30 GHz + if (!win || !win->conn) + return period; + + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(win->conn)); + xcb_screen_t *screen = iter.data; + + xcb_randr_output_t output = XCB_NONE; + xcb_randr_crtc_t crtc = XCB_NONE; + xcb_randr_get_screen_resources_reply_t *res_reply = NULL; + + // Requesting data in different blocks of code for readability, and to clear data at the end of scope. + + { + xcb_randr_get_screen_resources_cookie_t res_cookie = xcb_randr_get_screen_resources(win->conn, screen->root); + res_reply = xcb_randr_get_screen_resources_reply(win->conn, res_cookie, NULL); + + if (!res_reply) + return period; + + xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(res_reply); + int num_outputs = xcb_randr_get_screen_resources_outputs_length(res_reply); + + if (!num_outputs) { + free(res_reply); + return period; + } + + output = outputs[0]; + + if (output == XCB_NONE) + return period; + } + + { + xcb_randr_get_output_info_cookie_t info_cookie = xcb_randr_get_output_info(win->conn, output, XCB_CURRENT_TIME); + xcb_randr_get_output_info_reply_t *info_reply = xcb_randr_get_output_info_reply(win->conn, info_cookie, NULL); + + if (!info_reply || info_reply->connection != XCB_RANDR_CONNECTION_CONNECTED) { + free(info_reply); + free(res_reply); + return period; + } + + crtc = info_reply->crtc; + free(info_reply); + + if (crtc == XCB_NONE) { + free(res_reply); + return period; + } + } + + { + xcb_randr_get_crtc_info_cookie_t crtc_cookie = xcb_randr_get_crtc_info(win->conn, crtc, XCB_CURRENT_TIME); + xcb_randr_get_crtc_info_reply_t *crtc_reply = xcb_randr_get_crtc_info_reply(win->conn, crtc_cookie, NULL); + + if (!crtc_reply) { + free(res_reply); + return period; + } + + xcb_randr_mode_t mode = crtc_reply->mode; + xcb_randr_mode_info_t *modes = xcb_randr_get_screen_resources_modes(res_reply); + int num_modes = xcb_randr_get_screen_resources_modes_length(res_reply); + + for (int j = 0; j < num_modes; j++) { + if (modes[j].id == mode) { + double refresh_rate = (double)modes[j].dot_clock / + ((double)modes[j].htotal * (double)modes[j].vtotal); + period = (nsecs_t) (1000000000.0 / refresh_rate); + break; + } + } + + free(crtc_reply); + free(res_reply); + } + + return period; +} + +EGLAPI int TermuxNativeWindow_setSwapInterval(struct ANativeWindow *window, int interval) { + ANW_TRACE("win %p interval %d", window, interval); + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win) + return BAD_VALUE; + + win->swapInterval = interval<0 ? 0 : interval>1 ? 1 : interval; + + return NO_ERROR; +} + +EGLAPI int TermuxNativeWindow_dequeueBuffer_DEPRECATED(struct ANativeWindow *window, struct ANativeWindowBuffer **buffer) { + ANW_TRACE("win %p buffer %p", window, buffer); + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win) + return BAD_VALUE; + return win->base.dequeueBuffer(window, buffer, NULL); +} + +EGLAPI int TermuxNativeWindow_lockBuffer_DEPRECATED(struct ANativeWindow *window, struct ANativeWindowBuffer *buffer) { + ANW_TRACE("win %p buffer %p", window, buffer); + return NO_ERROR; +} + +EGLAPI int TermuxNativeWindow_queueBuffer_DEPRECATED(struct ANativeWindow *window, struct ANativeWindowBuffer *buffer) { + ANW_TRACE("win %p buffer %p", window, buffer); + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win) + return BAD_VALUE; + return win->base.queueBuffer(window, buffer, -1); +} + +EGLAPI int TermuxNativeWindow_query(const struct ANativeWindow *window, int what, int *value) { + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win) + return BAD_VALUE; + + const char* q = getQueryOpString(what); + ANW_TRACE("win %p what %s value %p", window, q, value); + if (!value) + return BAD_VALUE; + switch(what) { + case NATIVE_WINDOW_FORMAT: + *value = 5 /* PIXEL_FORMAT_BGRA_8888 */; + return NO_ERROR; + case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS: + *value = 2; + return NO_ERROR; + case NATIVE_WINDOW_WIDTH: + case NATIVE_WINDOW_DEFAULT_WIDTH: + *value = win->width; + return NO_ERROR; + case NATIVE_WINDOW_HEIGHT: + case NATIVE_WINDOW_DEFAULT_HEIGHT: + *value = win->height; + return NO_ERROR; + case NATIVE_WINDOW_TRANSFORM_HINT: + *value = 0; + return NO_ERROR; + case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND: + case NATIVE_WINDOW_IS_VALID: + *value = 1; + return NO_ERROR; + case NATIVE_WINDOW_DEFAULT_DATASPACE: + case NATIVE_WINDOW_DATASPACE: + *value = 0; + return NO_ERROR; + case NATIVE_WINDOW_CONSUMER_USAGE_BITS: + *value = win->usage; + return NO_ERROR; + case NATIVE_WINDOW_MAX_BUFFER_COUNT: + *value = 2; + return NO_ERROR; + default: + return INVALID_OPERATION; + } +} + +EGLAPI int TermuxNativeWindow_perform(struct ANativeWindow *window, int operation, ...) { + va_list args; + va_start(args, operation); + + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win) + return BAD_VALUE; + + const char *op = getPerformOpString(operation); + if (!op) { + ANW_TRACE("win %p unknown op %d", window, operation); + return BAD_VALUE; + } + + switch(operation) { + case NATIVE_WINDOW_SET_USAGE: { + uint64_t usage = va_arg(args, uint32_t); + ANW_TRACE("win %p %s %lu (no op)", window, op, usage); + return NO_ERROR; + } + case NATIVE_WINDOW_CONNECT: + case NATIVE_WINDOW_DISCONNECT: { + ANW_TRACE("win %p %s (no op)", window, op); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_BUFFER_COUNT: { + size_t bufferCount = va_arg(args, size_t); + ANW_TRACE("win %p %s %lu (no op)", window, op, bufferCount); + } + case NATIVE_WINDOW_SET_BUFFERS_TRANSFORM: { + int format = va_arg(args, int); + ANW_TRACE("win %p %s %d (no op)", window, op, format); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_BUFFERS_TIMESTAMP: { + long long timestamp = va_arg(args, long long); + ANW_TRACE("win %p %s %lld (no op)", window, op, timestamp); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_BUFFERS_DIMENSIONS: { + int w = va_arg(args, unsigned int), h = va_arg(args, unsigned int); + ANW_TRACE("win %p %s %d %d (no op)", window, op, w, h); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_BUFFERS_FORMAT: { + int format = va_arg(args, int); + ANW_TRACE("win %p %s %d (no op)", window, op, format); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_SCALING_MODE: { + int mode = va_arg(args, int); + ANW_TRACE("win %p %s %d (no op)", window, op, mode); + return NO_ERROR; + } + case NATIVE_WINDOW_API_CONNECT: { + int api = va_arg(args, int); + ANW_TRACE("win %p %s %d (no op)", window, op, api); + if (api != NATIVE_WINDOW_API_EGL && api != NATIVE_WINDOW_API_CPU + && api != NATIVE_WINDOW_API_MEDIA && api != NATIVE_WINDOW_API_CAMERA) { + dprintf(2, "%s:%d: perform(API_CONNECT): invalid api: %d\n", __FILE__, __LINE__, api); + return BAD_VALUE; + } + return NO_ERROR; + } + case NATIVE_WINDOW_API_DISCONNECT: + ANW_TRACE("win %p %s (no op)", window, op); + return NO_ERROR; + case NATIVE_WINDOW_SET_BUFFERS_DATASPACE: { + int dataspace = va_arg(args, int); + ANW_TRACE("win %p %s %d (no op)", window, op, dataspace); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_SHARED_BUFFER_MODE: { + bool sharedBufferMode = va_arg(args, int); + ANW_TRACE("win %p %s %d (no op)", window, op, sharedBufferMode); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_AUTO_REFRESH: { + bool autoRefresh = va_arg(args, int); + ANW_TRACE("win %p %s %d (no op)", window, op, autoRefresh); + return NO_ERROR; + } + case NATIVE_WINDOW_GET_REFRESH_CYCLE_DURATION: { + nsecs_t* outRefreshDuration = va_arg(args, int64_t*); + ANW_TRACE("win %p %s %p (%ld) (no op)", window, op, outRefreshDuration, win->vsyncPeriod); + if (outRefreshDuration) + *outRefreshDuration = win->vsyncPeriod; + return NO_ERROR; + } + case NATIVE_WINDOW_SET_USAGE64: { + uint64_t usage = va_arg(args, uint64_t); + ANW_TRACE("win %p %s %lu (no op)", window, op, usage); + return NO_ERROR; + } + case NATIVE_WINDOW_GET_CONSUMER_USAGE64: { + uint64_t* usage = va_arg(args, uint64_t*); + if (usage) + *usage = win->usage; + ANW_TRACE("win %p %s %p (%d) (no op)", window, op, usage, win->usage); + return NO_ERROR; + } + case NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT: { + int64_t timeout = va_arg(args, int64_t); + ANW_TRACE("win %p %s %ld (no op)", window, op, timeout); + return NO_ERROR; + } + + default: + break; + } + ANW_TRACE("win %p unimplemented operation %s (%d) (no op)", window, getPerformOpString(operation), operation); + return INVALID_OPERATION; +} + +EGLAPI int TermuxNativeWindow_cancelBuffer_DEPRECATED(struct ANativeWindow *window, struct ANativeWindowBuffer *buffer) { + ANW_TRACE("win %p buffer %p", window, buffer); + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win) + return BAD_VALUE; + return win->base.cancelBuffer(window, buffer, -1); +} + +EGLAPI int TermuxNativeWindow_dequeueBuffer(struct ANativeWindow *window, struct ANativeWindowBuffer **buffer, int *fenceFd) { + ANW_TRACE("win %p buffer %p fenceFd %p", window, buffer, fenceFd); + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win || !buffer) + return BAD_VALUE; + + if (win->allocationNeeded) { + AHardwareBuffer_Desc desc = { .width = win->width, .height = win->height, .format = 5, .layers = 1, .usage = win->usage }, desc1 = {0}; + int error = NO_ERROR; + + // Release existing buffers if any + for (int i=0; ibuffers)/sizeof(win->buffers[0]); i++) { + if (!win->buffers[i].self) + continue; + + AHardwareBuffer_release(win->buffers[i].self); + xcb_free_pixmap_checked(win->conn, win->buffers[i].pixmap); + memset(&win->buffers[i], 0, sizeof(win->buffers[i])); + } + + // TODO: probably we should implement copying data from old frontbuffer to the new one to avoid glitches when user resizes window. Not so sure it will help though. + + // Allocate new buffers + for (int i=0; ibuffers)/sizeof(win->buffers[0]); i++) { + int fds[] = { -1, -1 }; + uint8_t buf = 0; + + if ((error = AHardwareBuffer_allocate(&desc, &win->buffers[i].self)) != NO_ERROR) { + dprintf(2, "can not allocate buffer width %d height %d format %d usage %lu, error %d\n", + desc.width, desc.height, desc.format, desc.usage, error); + return NO_MEMORY; + } + + if (!win->buffers[i].self) { + dprintf(2, "AHardwareBuffer_allocate returned NO_ERROR but buffer was not allocated\n"); + return NO_MEMORY; + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) + return NO_MEMORY; + + win->buffers[i].pixmap = xcb_generate_id(win->conn); + AHardwareBuffer_describe(win->buffers[i].self, &desc1); + xcb_void_cookie_t cookie = xcb_dri3_pixmap_from_buffers_checked(win->conn, win->buffers[i].pixmap, win->win, 1, win->width, win->height, desc1.stride*4, 0, 0, 0, 0, 0, 0, 0, 24, 32, 1255, &fds[1]); + xcb_flush(win->conn); + + read(fds[0], &buf, 1); + AHardwareBuffer_sendHandleToUnixSocket(win->buffers[i].self, fds[0]); + xcb_generic_error_t *err = xcb_request_check(win->conn, cookie); + if (err) { + free(err); + win->buffers[i].pixmap = 0; + AHardwareBuffer_release(win->buffers[i].self); + win->buffers[i].self = NULL; + dprintf(2, "xcb error while doing xcb_dri3_pixmap_from_buffers\n"); + return NO_MEMORY; + } + } + + win->front = &win->buffers[0]; + win->back = &win->buffers[1]; + win->allocationNeeded = false; + } + + *buffer = to_ANativeWindowBuffer(win->back->self); + if (fenceFd) + *fenceFd = -1; + return NO_ERROR; +} + +EGLAPI void TermuxNativeWindow_handleEvents(struct TermuxNativeWindow* win, xcb_generic_event_t* event) { + xcb_present_generic_event_t* present = (xcb_present_generic_event_t*) event; + xcb_present_complete_notify_event_t* complete = (xcb_present_complete_notify_event_t*) event; + xcb_present_configure_notify_event_t *config = (xcb_present_configure_notify_event_t*) event; + switch(present->evtype) { + case XCB_PRESENT_EVENT_COMPLETE_NOTIFY: { + if (complete->kind != XCB_PRESENT_COMPLETE_KIND_PIXMAP) + break; + + for (int i=0; ibuffers)/sizeof(win->buffers[0]); i++) { + if (win->buffers[i].serial == complete->serial) { + win->buffers[i].busy = false; + break; + } + } + break; + } + case XCB_PRESENT_EVENT_CONFIGURE_NOTIFY: + win->width = config->pixmap_width; + win->height = config->pixmap_height; + win->allocationNeeded = true; + win->vsyncPeriod = TermuxNativeWindow_getVSyncPeriod(win); + break; + default: + dprintf(2, "got some weird present event %d\n", present->evtype); + xcb_flush(win->conn); + } +} + +EGLAPI int TermuxNativeWindow_queueBuffer(struct ANativeWindow *window, struct ANativeWindowBuffer *buffer, int fenceFd) { + ANW_TRACE("win %p buffer %p fenceFd %d", window, buffer, fenceFd); + static int serial = 1; + struct TermuxNativeWindow* win = container_of(window, struct TermuxNativeWindow, base); + if (!window || !win || !buffer) + return BAD_VALUE; + + if (fenceFd >= 0) { + sync_wait(fenceFd, 500); + close(fenceFd); + } + + xcb_generic_event_t* event; + + xcb_flush(win->conn); + while((event = xcb_poll_for_special_event(win->conn, win->special_event))) + TermuxNativeWindow_handleEvents(win, event); + + if (!win->swapInterval && win->front->busy) + return NO_ERROR; + + while (win->front->busy) { + event = xcb_wait_for_special_event(win->conn, win->special_event); + if (!event) { + dprintf(2, "failed to obtain special event\n"); + return DEAD_OBJECT; + } + + TermuxNativeWindow_handleEvents(win, event); + } + + win->back->serial = serial++; + win->back->busy = true; + xcb_void_cookie_t cookie = xcb_present_pixmap_checked(win->conn, win->win, win->back->pixmap, win->back->serial, 0, 0, 0, 0, 0, 0, 0, XCB_PRESENT_OPTION_NONE, 0, 0, 0, 0, NULL); + xcb_discard_reply(win->conn, cookie.sequence); + xcb_flush(win->conn); + + void* temp = win->front; + win->front = win->back; + win->back = temp; + + return NO_ERROR; +} + +EGLAPI int TermuxNativeWindow_cancelBuffer(struct ANativeWindow *window, struct ANativeWindowBuffer *buffer, int fenceFd) { + ANW_TRACE("win %p buffer %p fenceFd %d", window, buffer, fenceFd); + // We do not explicitly mark or register dequeued buffers so no action required here. + return NO_ERROR; +} + +EGLAPI struct ANativeWindow* TermuxNativeWindow_create(Display* dpy, Window win) { + // ReSharper disable once CppDFAMemoryLeak + struct TermuxNativeWindow* new = calloc(1, sizeof(struct TermuxNativeWindow)); + new->base.common.magic = ANDROID_NATIVE_WINDOW_MAGIC; + new->base.common.version = sizeof(struct ANativeWindow); + memset(new->base.common.reserved, 0, sizeof(new->base.common.reserved)); + *(int*) &new->base.minSwapInterval = 0; + *(int*) &new->base.maxSwapInterval = 1; + *(float*) &new->base.xdpi = 160; + *(float*) &new->base.ydpi = 160; + new->base.common.incRef = TermuxNativeWindow_incRef; + new->base.common.decRef = TermuxNativeWindow_decRef; + + new->base.setSwapInterval = TermuxNativeWindow_setSwapInterval; + new->base.dequeueBuffer_DEPRECATED = TermuxNativeWindow_dequeueBuffer_DEPRECATED; + new->base.lockBuffer_DEPRECATED = TermuxNativeWindow_lockBuffer_DEPRECATED; + new->base.queueBuffer_DEPRECATED = TermuxNativeWindow_queueBuffer_DEPRECATED; + new->base.query = TermuxNativeWindow_query; + new->base.perform = TermuxNativeWindow_perform; + new->base.cancelBuffer_DEPRECATED = TermuxNativeWindow_cancelBuffer_DEPRECATED; + new->base.dequeueBuffer = TermuxNativeWindow_dequeueBuffer; + new->base.queueBuffer = TermuxNativeWindow_queueBuffer; + new->base.cancelBuffer = TermuxNativeWindow_cancelBuffer; + + new->conn = XGetXCBConnection(dpy); + new->win = win; + xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(new->conn, xcb_get_geometry(new->conn, new->win), NULL); + new->width = geom ? geom->width : 800; + new->height = geom ? geom->height : 600; + free(geom); + new->usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER | AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE; + new->allocationNeeded = true; + __sync_fetch_and_add(&new->refcount,1); + + xcb_setup_t const *const setup = xcb_get_setup(new->conn); + xcb_screen_t *const screen = xcb_setup_roots_iterator(setup).data; + + new->gc = xcb_generate_id(new->conn); + xcb_create_gc(new->conn, new->gc, win, XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES, (const uint32_t[]) {screen->black_pixel, 0}); + + uint32_t eid = xcb_generate_id(new->conn); + new->special_event = xcb_register_for_special_xge(new->conn, &xcb_present_id, eid, NULL); + xcb_present_select_input(new->conn, eid, new->win, XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY | XCB_PRESENT_EVENT_MASK_CONFIGURE_NOTIFY); + new->vsyncPeriod = TermuxNativeWindow_getVSyncPeriod(new); + + return &new->base; +} + +EGLAPI void TermuxNativeWindow_init(void) { + anw_trace_enabled = !strcmp("1", getenv("ANW_TRACE") ? getenv("ANW_TRACE") : ""); + if (AHardwareBuffer_to_ANativeWindowBuffer) + ANativeWindowBufferOffset = AHardwareBuffer_to_ANativeWindowBuffer((void*) 0x1) - 0x1; // To avoid error `getNativeBuffer() called on NULL GraphicBuffer` + else if (GraphicBuffer_getNativeBuffer) + ANativeWindowBufferOffset = GraphicBuffer_getNativeBuffer((void*) 0x1) - 0x1; +}