From 18541865f037dd2824b0aec1d4de7469c1be9ff5 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 12 Sep 2026 10:45:04 +1000 Subject: [PATCH] [client] egl: add portable texture upload fallback --- client/CMakeLists.txt | 58 ++- client/renderers/EGL/CMakeLists.txt | 29 +- client/renderers/EGL/desktop.c | 7 - client/renderers/EGL/egl.c | 77 ++-- client/renderers/EGL/postprocess.c | 26 +- client/renderers/EGL/postprocess_order.c | 57 +++ client/renderers/EGL/postprocess_order.h | 30 ++ client/renderers/EGL/texture.c | 1 - client/renderers/EGL/texture.h | 3 + client/renderers/EGL/texture_buffer.c | 23 +- client/renderers/EGL/texture_dmabuf.c | 24 +- client/renderers/EGL/texture_dmabuf_stub.c | 43 ++ client/renderers/EGL/texture_util.c | 81 +++- client/renderers/EGL/texture_util.h | 17 +- client/tests/CMakeLists.txt | 84 +++- client/tests/postprocess_order_test.c | 73 +++ client/tests/texture_upload_test.c | 493 +++++++++++++++++++++ 17 files changed, 1010 insertions(+), 116 deletions(-) create mode 100644 client/renderers/EGL/postprocess_order.c create mode 100644 client/renderers/EGL/postprocess_order.h create mode 100644 client/renderers/EGL/texture_dmabuf_stub.c create mode 100644 client/tests/postprocess_order_test.c create mode 100644 client/tests/texture_upload_test.c diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 36bdb592..65150e71 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -269,11 +269,61 @@ endif() add_subdirectory("${PROJECT_TOP}/resources" "${CMAKE_BINARY_DIR}/resources") add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" ) add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" ) -add_subdirectory("${PROJECT_TOP}/repos/gui" "${CMAKE_BINARY_DIR}/gui" EXCLUDE_FROM_ALL) -if(LG_CLIENT_PLATFORM_WINDOWS AND ENABLE_EGL AND - NOT LG_EGL_PROVIDER_TARGET) - find_package(ANGLE REQUIRED) +set(LG_EGL_PROVIDER_TARGET "" CACHE STRING + "CMake target that provides EGL and OpenGL ES") + +if(ENABLE_EGL) + set(LG_EGL_PROVIDER_LIBRARIES) + if(LG_EGL_PROVIDER_TARGET) + if(NOT TARGET ${LG_EGL_PROVIDER_TARGET}) + message(FATAL_ERROR + "LG_EGL_PROVIDER_TARGET does not name an existing CMake target: " + "${LG_EGL_PROVIDER_TARGET}") + endif() + + list(APPEND LG_EGL_PROVIDER_LIBRARIES ${LG_EGL_PROVIDER_TARGET}) + elseif(LG_CLIENT_PLATFORM_WINDOWS) + find_package(ANGLE REQUIRED) + list(APPEND LG_EGL_PROVIDER_LIBRARIES + ANGLE::EGL + ANGLE::GLESv2 + ) + else() + pkg_check_modules(RENDERER_EGL REQUIRED IMPORTED_TARGET + egl + gl + ) + pkg_check_modules(RENDERER_EGL_OPT IMPORTED_TARGET + wayland-egl + ) + + list(APPEND LG_EGL_PROVIDER_LIBRARIES PkgConfig::RENDERER_EGL) + if(RENDERER_EGL_OPT_FOUND) + list(APPEND LG_EGL_PROVIDER_LIBRARIES PkgConfig::RENDERER_EGL_OPT) + endif() + endif() + + add_library(lg_egl_provider INTERFACE) + target_link_libraries(lg_egl_provider INTERFACE + ${LG_EGL_PROVIDER_LIBRARIES} + ) +endif() + +add_subdirectory("${PROJECT_TOP}/repos/gui" "${CMAKE_BINARY_DIR}/gui" + EXCLUDE_FROM_ALL) + +target_sources(lg_gui PRIVATE + "${PROJECT_TOP}/repos/gui/cimgui/imgui/imgui_demo.cpp" + "${PROJECT_TOP}/repos/gui/cimplot/implot/implot_demo.cpp" +) +target_compile_definitions(lg_gui PRIVATE IMGUI_DISABLE_DEMO_WINDOWS) + +if(ENABLE_EGL) + # CMake 3.10 does not allow target_link_libraries to update a target from + # another directory. LINK_LIBRARIES provides the equivalent dependency. + set_property(TARGET lg_gui APPEND PROPERTY + LINK_LIBRARIES lg_egl_provider) endif() add_subdirectory(platform) diff --git a/client/renderers/EGL/CMakeLists.txt b/client/renderers/EGL/CMakeLists.txt index 592474a1..716dd89b 100644 --- a/client/renderers/EGL/CMakeLists.txt +++ b/client/renderers/EGL/CMakeLists.txt @@ -1,16 +1,6 @@ cmake_minimum_required(VERSION 3.10) project(renderer_EGL LANGUAGES C CXX) -find_package(PkgConfig) -pkg_check_modules(RENDERER_EGL REQUIRED IMPORTED_TARGET - egl - gl -) - -pkg_check_modules(RENDERER_EGL_OPT IMPORTED_TARGET - wayland-egl -) - find_program(AWK NAMES gawk mawk original-awk awk) if(AWK MATCHES ".+-NOTFOUND") @@ -75,7 +65,7 @@ make_defines( "${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h" ) -add_library(renderer_EGL STATIC +set(RENDERER_EGL_SOURCES egl.c egldebug.c state.c @@ -84,7 +74,6 @@ add_library(renderer_EGL STATIC texture.c texture_buffer.c texture_framebuffer.c - texture_dmabuf.c model.c desktop.c desktop_rects.c @@ -95,6 +84,7 @@ add_library(renderer_EGL STATIC framebuffer.c effect.c postprocess.c + postprocess_order.c ffx.c filter_glsl.c filter_24bit.c @@ -106,19 +96,22 @@ add_library(renderer_EGL STATIC "${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h" ) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + list(APPEND RENDERER_EGL_SOURCES texture_dmabuf.c) +else() + list(APPEND RENDERER_EGL_SOURCES texture_dmabuf_stub.c) +endif() + +add_library(renderer_EGL STATIC ${RENDERER_EGL_SOURCES}) + target_compile_definitions(renderer_EGL PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 IMGUI_IMPL_OPENGL_ES3) target_link_libraries(renderer_EGL - PkgConfig::RENDERER_EGL + lg_egl_provider lg_common lg_gui ) -if(RENDERER_EGL_OPT_FOUND) - target_link_libraries(renderer_EGL - PkgConfig::RENDERER_EGL_OPT - ) -endif() target_include_directories(renderer_EGL PRIVATE diff --git a/client/renderers/EGL/desktop.c b/client/renderers/EGL/desktop.c index c447444a..2f1e41bd 100644 --- a/client/renderers/EGL/desktop.c +++ b/client/renderers/EGL/desktop.c @@ -424,13 +424,6 @@ bool egl_desktopUpdate(EGL_Desktop * desktop, desktop->useDMA = false; - const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS); - if (!util_hasGLExt(gl_exts, "GL_EXT_buffer_storage")) - { - DEBUG_ERROR("GL_EXT_buffer_storage is needed to use EGL backend"); - return false; - } - egl_textureFree(&desktop->texture); if (!egl_textureInit(&desktop->texture, desktop->display, EGL_TEXTYPE_FRAMEBUFFER)) diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index fe0814b3..6bd29662 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -38,14 +38,18 @@ #include "cimgui.h" #include "cimgui_impl.h" +#include #include #include #include #include "egl_dynprocs.h" +#include "egldebug.h" #include "model.h" #include "shader.h" #include "state.h" +#include "texture.h" +#include "texture_util.h" #include "damage.h" #include "desktop.h" #include "cursor.h" @@ -267,7 +271,8 @@ static bool egl_create(LG_Renderer ** renderer, const LG_RendererParams params, struct Inst * this = calloc(1, sizeof(*this)); if (!this) { - DEBUG_INFO("Failed to allocate %lu bytes", sizeof(*this)); + DEBUG_INFO("Failed to allocate %" PRIu64 " bytes", + (uint64_t)sizeof(*this)); return false; } *renderer = &this->base; @@ -776,8 +781,14 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo /* this event runs in a second thread so we need to init it here */ if (unlikely(!this->frameContext)) { + if (!eglBindAPI(EGL_OPENGL_ES_API)) + { + DEBUG_EGL_ERROR("Failed to select the OpenGL ES API for frame uploads"); + return false; + } + static EGLint attrs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; @@ -1005,6 +1016,12 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA) return false; } + if (!eglBindAPI(EGL_OPENGL_ES_API)) + { + DEBUG_EGL_ERROR("Failed to select the OpenGL ES API"); + return false; + } + int maxSamples = 1; if (option_get_bool("egl", "multisample")) { @@ -1063,7 +1080,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA) EGL_CONFIG_ATTR(EGL_BUFFER_SIZE , configs[i].depth); if (hasFloatConfigs) EGL_CONFIG_ATTR(EGL_COLOR_COMPONENT_TYPE_EXT, configs[i].componentType); - EGL_CONFIG_ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT); + EGL_CONFIG_ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT); EGL_CONFIG_ATTR(EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0); EGL_CONFIG_ATTR(EGL_SAMPLES , maxSamples); attr[ai] = EGL_NONE; @@ -1125,7 +1142,7 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA) int ctxidx = 0; ctxattr[ctxidx++] = EGL_CONTEXT_CLIENT_VERSION; - ctxattr[ctxidx++] = 2; + ctxattr[ctxidx++] = 3; if (maj > 1 || (maj == 1 && min >= 5)) { @@ -1166,8 +1183,27 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA) break; } - eglMakeCurrent(this->display, this->surface, this->surface, this->context); + if (!eglMakeCurrent(this->display, this->surface, this->surface, + this->context)) + { + DEBUG_EGL_ERROR("Failed to make the EGL context current"); + return false; + } + egl_stateInvalidate(); + egl_dynProcsInit(); + + GLint esMaj = 0; + GLint esMin = 0; + glGetIntegerv(GL_MAJOR_VERSION, &esMaj); + glGetIntegerv(GL_MINOR_VERSION, &esMin); + if (esMaj < 3) + { + DEBUG_ERROR("OpenGL ES 3.0 or newer is required, got %d.%d", + esMaj, esMin); + return false; + } + const char * gl_exts = (const char *)glGetString(GL_EXTENSIONS); if (!gl_exts) { @@ -1194,10 +1230,6 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA) DEBUG_INFO("GL Exts : %s", gl_exts); } - GLint esMaj, esMin; - glGetIntegerv(GL_MAJOR_VERSION, &esMaj); - glGetIntegerv(GL_MINOR_VERSION, &esMin); - if (!util_hasGLExt(gl_exts, "GL_EXT_texture_format_BGRA8888")) { DEBUG_ERROR("GL_EXT_texture_format_BGRA8888 is needed to use EGL backend"); @@ -1229,26 +1261,17 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA) this->scalePointer = option_get_bool("egl", "scalePointer"); - if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES) - DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled"); - else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage) - DEBUG_INFO("eglCreateImage or eglDestroyImage unavailable, DMA support disabled"); - else if (!util_hasGLExt(client_exts, "EGL_EXT_image_dma_buf_import")) - DEBUG_INFO("EGL_EXT_image_dma_buf_import unavailable, DMA support disabled"); - else if ((maj < 1 || (maj == 1 && min < 5)) && !util_hasGLExt(client_exts, "EGL_KHR_image_base")) - DEBUG_INFO("Need EGL 1.5+ or EGL_KHR_image_base for eglCreateImage(KHR)"); - else - this->dmaSupport = true; - + this->dmaSupport = egl_texDMABUFSupported(this->display, maj, min); if (!this->dmaSupport) - { useDMA = false; - if (!util_hasGLExt(gl_exts, "GL_EXT_buffer_storage")) - { - DEBUG_ERROR("GL_EXT_buffer_storage is needed to use EGL backend"); - return false; - } - } + + const bool persistentUploads = + util_hasGLExt(gl_exts, "GL_EXT_buffer_storage") && + g_egl_dynProcs.glBufferStorageEXT; + egl_texUtilSetBufferMode(persistentUploads ? + EGL_TEX_BUFFER_MODE_PERSISTENT : EGL_TEX_BUFFER_MODE_DIRECT); + DEBUG_INFO("Texture upload method: %s", + persistentUploads ? "persistent PBO" : "direct"); if (debug) { diff --git a/client/renderers/EGL/postprocess.c b/client/renderers/EGL/postprocess.c index 1f734228..f4d05d26 100644 --- a/client/renderers/EGL/postprocess.c +++ b/client/renderers/EGL/postprocess.c @@ -18,14 +18,13 @@ * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define _GNU_SOURCE #include "postprocess.h" +#include "postprocess_order.h" #include "filters.h" #include "app.h" #include "cimgui.h" #include -#include #include #include "common/debug.h" @@ -252,24 +251,6 @@ static bool savePreset(struct EGL_PostProcess * this, const char * name) return true; } -static int stringListIndex(StringList list, const char * str) -{ - unsigned int count = stringlist_count(list); - for (unsigned int i = 0; i < count; ++i) - if (strcmp(stringlist_at(list, i), str) == 0) - return i; - return INT_MAX; -} - -static int compareFilterOrder(const void * a_, const void * b_, void * opaque) -{ - const EGL_Filter * a = *(const EGL_Filter **)a_; - const EGL_Filter * b = *(const EGL_Filter **)b_; - StringList order = opaque; - - return stringListIndex(order, a->ops.id) - stringListIndex(order, b->ops.id); -} - static void reorderFilters(struct EGL_PostProcess * this) { StringList order = stringlist_new(false); @@ -305,8 +286,9 @@ static void reorderFilters(struct EGL_PostProcess * this) p = end + 1; } - qsort_r(vector_data(&this->filters), vector_size(&this->filters), - sizeof(EGL_Filter *), compareFilterOrder, order); + EGL_Filter ** filters = vector_data(&this->filters); + const size_t count = vector_size(&this->filters); + egl_postProcessOrderFilters(filters, count, order); stringlist_free(&order); free(orderStr); diff --git a/client/renderers/EGL/postprocess_order.c b/client/renderers/EGL/postprocess_order.c new file mode 100644 index 00000000..792612ca --- /dev/null +++ b/client/renderers/EGL/postprocess_order.c @@ -0,0 +1,57 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "postprocess_order.h" + +#include +#include + +static unsigned int stringListIndex(StringList list, const char * str) +{ + const unsigned int count = stringlist_count(list); + for (unsigned int i = 0; i < count; ++i) + if (strcmp(stringlist_at(list, i), str) == 0) + return i; + return UINT_MAX; +} + +static bool filterAfter(const EGL_Filter * a, const EGL_Filter * b, + StringList order) +{ + const unsigned int aIndex = stringListIndex(order, a->ops.id); + const unsigned int bIndex = stringListIndex(order, b->ops.id); + return aIndex > bIndex; +} + +void egl_postProcessOrderFilters(EGL_Filter ** filters, size_t count, + StringList order) +{ + for (size_t i = 1; i < count; ++i) + { + EGL_Filter * filter = filters[i]; + size_t j = i; + while (j > 0 && filterAfter(filters[j - 1], filter, order)) + { + filters[j] = filters[j - 1]; + --j; + } + filters[j] = filter; + } +} diff --git a/client/renderers/EGL/postprocess_order.h b/client/renderers/EGL/postprocess_order.h new file mode 100644 index 00000000..606c2d97 --- /dev/null +++ b/client/renderers/EGL/postprocess_order.h @@ -0,0 +1,30 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#pragma once + +#include "filter.h" + +#include "common/stringlist.h" + +#include + +void egl_postProcessOrderFilters(EGL_Filter ** filters, size_t count, + StringList order); diff --git a/client/renderers/EGL/texture.c b/client/renderers/EGL/texture.c index 16ac79e5..9b5f416f 100644 --- a/client/renderers/EGL/texture.c +++ b/client/renderers/EGL/texture.c @@ -36,7 +36,6 @@ extern const EGL_TextureOps EGL_TextureBuffer; extern const EGL_TextureOps EGL_TextureBufferStream; extern const EGL_TextureOps EGL_TextureFrameBuffer; -extern const EGL_TextureOps EGL_TextureDMABUF; bool egl_textureInit(EGL_Texture ** texture_, EGLDisplay * display, EGL_TexType type) diff --git a/client/renderers/EGL/texture.h b/client/renderers/EGL/texture.h index a63daad4..1bdc95b9 100644 --- a/client/renderers/EGL/texture.h +++ b/client/renderers/EGL/texture.h @@ -125,6 +125,9 @@ struct EGL_Texture EGL_TexFormat format; }; +extern const EGL_TextureOps EGL_TextureDMABUF; +bool egl_texDMABUFSupported(EGLDisplay display, EGLint major, EGLint minor); + bool egl_textureInit(EGL_Texture ** texture, EGLDisplay * display, EGL_TexType type); void egl_textureFree(EGL_Texture ** tex); diff --git a/client/renderers/EGL/texture_buffer.c b/client/renderers/EGL/texture_buffer.c index cf242226..e9040810 100644 --- a/client/renderers/EGL/texture_buffer.c +++ b/client/renderers/EGL/texture_buffer.c @@ -454,14 +454,17 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture, this->bufIndex = 0; buffer->updated = false; - /* With more than one slot the producer can fill the next PBO while this - * upload is queued. A single-slot texture must remain locked until its - * fence exists so the mapped buffer cannot be overwritten concurrently. */ + /* With more than one slot the producer can fill the next upload buffer + * while this one is submitted. Keep a single-slot texture locked until the + * upload is issued and, for persistent storage, its fence exists. */ const bool keepLocked = this->texCount == 1; if (!keepLocked) LG_UNLOCK(this->copyLock); - egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); + const bool persistent = + buffer->mode == EGL_TEX_BUFFER_MODE_PERSISTENT; + egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, + persistent ? buffer->pbo : 0); egl_stateBindTexture(0, GL_TEXTURE_2D, tex); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); @@ -475,7 +478,7 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture, texture->format.height, texture->format.format, texture->format.dataType, - (const void *)0); + persistent ? (const void *)0 : buffer->map); else for (int i = 0; i < damage->count; ++i) { @@ -491,11 +494,19 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture, rect->height, texture->format.format, texture->format.dataType, - (const void *)offset); + persistent ? (const void *)offset : + (const uint8_t *)buffer->map + offset); } /* Keep damage intact until all of its upload commands have been issued. */ egl_texBufferDamageReset(damage); + if (!persistent) + { + if (keepLocked) + LG_UNLOCK(this->copyLock); + return EGL_TEX_STATUS_UPDATED; + } + this->sync[index] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); if (unlikely(!this->sync[index])) { diff --git a/client/renderers/EGL/texture_dmabuf.c b/client/renderers/EGL/texture_dmabuf.c index e6f99eb7..95db4925 100644 --- a/client/renderers/EGL/texture_dmabuf.c +++ b/client/renderers/EGL/texture_dmabuf.c @@ -86,12 +86,32 @@ struct SnapshotRelease uint64_t handle; }; -EGL_TextureOps EGL_TextureDMABUF; +const EGL_TextureOps EGL_TextureDMABUF; static bool initDone = false; static bool has24BitSupport = true; static bool hasImportModifiers = true; +bool egl_texDMABUFSupported(EGLDisplay display, EGLint major, EGLint minor) +{ + const char * extensions = eglQueryString(display, EGL_EXTENSIONS); + + if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES) + DEBUG_INFO("glEGLImageTargetTexture2DOES unavailable, DMA support disabled"); + else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage) + DEBUG_INFO("eglCreateImage or eglDestroyImage unavailable, DMA support disabled"); + else if (!extensions || + !util_hasGLExt(extensions, "EGL_EXT_image_dma_buf_import")) + DEBUG_INFO("EGL_EXT_image_dma_buf_import unavailable, DMA support disabled"); + else if ((major < 1 || (major == 1 && minor < 5)) && + !util_hasGLExt(extensions, "EGL_KHR_image_base")) + DEBUG_INFO("Need EGL 1.5+ or EGL_KHR_image_base for eglCreateImage(KHR)"); + else + return true; + + return false; +} + // internal functions static void egl_texDMABUFFree(EGL_Texture * texture); @@ -695,7 +715,7 @@ static void egl_texDMABUFMarkUsed(EGL_Texture * texture) LG_UNLOCK(parent->copyLock); } -EGL_TextureOps EGL_TextureDMABUF = +const EGL_TextureOps EGL_TextureDMABUF = { .init = egl_texDMABUFInit, .free = egl_texDMABUFFree, diff --git a/client/renderers/EGL/texture_dmabuf_stub.c b/client/renderers/EGL/texture_dmabuf_stub.c new file mode 100644 index 00000000..c413b51a --- /dev/null +++ b/client/renderers/EGL/texture_dmabuf_stub.c @@ -0,0 +1,43 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "texture.h" + +static bool egl_texDMABUFInitUnsupported(EGL_Texture ** texture, + EGL_TexType type, EGLDisplay * display) +{ + (void)texture; + (void)type; + (void)display; + return false; +} + +bool egl_texDMABUFSupported(EGLDisplay display, EGLint major, EGLint minor) +{ + (void)display; + (void)major; + (void)minor; + return false; +} + +const EGL_TextureOps EGL_TextureDMABUF = +{ + .init = egl_texDMABUFInitUnsupported, +}; diff --git a/client/renderers/EGL/texture_util.c b/client/renderers/EGL/texture_util.c index 5b148da6..62735370 100644 --- a/client/renderers/EGL/texture_util.c +++ b/client/renderers/EGL/texture_util.c @@ -29,6 +29,11 @@ #include "egldebug.h" #include "egl_dynprocs.h" +#include +#include + +static EGL_TexBufferMode bufferMode = EGL_TEX_BUFFER_MODE_DIRECT; + bool egl_texUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt) { fmt->pixFmt = setup->pixFmt; @@ -37,7 +42,7 @@ bool egl_texUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt) fmt->stride = setup->stride; fmt->pitch = setup->pitch; - switch(setup->pixFmt) + switch (setup->pixFmt) { case EGL_PF_BGRA: fmt->bpp = 4; @@ -104,14 +109,33 @@ bool egl_texUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt) return true; } -bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers, - int count) +void egl_texUtilSetBufferMode(EGL_TexBufferMode mode) { - for(int i = 0; i < count; ++i) + bufferMode = mode; +} + +static bool egl_texUtilGenBuffersMode(const EGL_TexFormat * fmt, + EGL_TexBuffer * buffers, int count, EGL_TexBufferMode mode) +{ + for (int i = 0; i < count; ++i) { - EGL_TexBuffer *buffer = &buffers[i]; + EGL_TexBuffer * buffer = &buffers[i]; buffer->size = fmt->dataSize; + buffer->mode = mode; + if (mode == EGL_TEX_BUFFER_MODE_DIRECT) + { + buffer->map = malloc(buffer->size); + if (!buffer->map) + { + DEBUG_ERROR("Failed to allocate %" PRIu64 + "-byte texture upload buffer", (uint64_t)buffer->size); + return false; + } + + continue; + } + glGenBuffers(1, &buffer->pbo); egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); g_egl_dynProcs.glBufferStorageEXT( @@ -130,12 +154,47 @@ bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers, return true; } +bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers, + int count) +{ + EGL_TexBufferMode mode = bufferMode; + if (mode == EGL_TEX_BUFFER_MODE_PERSISTENT && + !g_egl_dynProcs.glBufferStorageEXT) + { + DEBUG_WARN("Persistent texture uploads unavailable, using direct uploads"); + mode = EGL_TEX_BUFFER_MODE_DIRECT; + } + + if (egl_texUtilGenBuffersMode(fmt, buffers, count, mode)) + return true; + + egl_texUtilFreeBuffers(buffers, count); + if (mode != EGL_TEX_BUFFER_MODE_PERSISTENT) + return false; + + DEBUG_WARN("Persistent texture upload buffers failed, using direct uploads"); + mode = EGL_TEX_BUFFER_MODE_DIRECT; + if (egl_texUtilGenBuffersMode(fmt, buffers, count, mode)) + return true; + + egl_texUtilFreeBuffers(buffers, count); + return false; +} + void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count) { bool deleted = false; - for(int i = 0; i < count; ++i) + for (int i = 0; i < count; ++i) { - EGL_TexBuffer *buffer = &buffers[i]; + EGL_TexBuffer * buffer = &buffers[i]; + + if (buffer->mode == EGL_TEX_BUFFER_MODE_DIRECT) + { + free(buffer->map); + buffer->map = NULL; + buffer->size = 0; + continue; + } if (!buffer->pbo) continue; @@ -143,7 +202,8 @@ void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count) egl_texUtilUnmapBuffer(buffer); glDeleteBuffers(1, &buffer->pbo); deleted = true; - buffer->pbo = 0; + buffer->pbo = 0; + buffer->size = 0; } if (deleted) @@ -152,6 +212,7 @@ void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count) bool egl_texUtilMapBuffer(EGL_TexBuffer * buffer) { + DEBUG_ASSERT(buffer->mode == EGL_TEX_BUFFER_MODE_PERSISTENT); egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); buffer->map = glMapBufferRange( GL_PIXEL_UNPACK_BUFFER, @@ -164,7 +225,8 @@ bool egl_texUtilMapBuffer(EGL_TexBuffer * buffer) GL_MAP_COHERENT_BIT_EXT); if (!buffer->map) - DEBUG_GL_ERROR("glMapBufferRange failed of %lu bytes", buffer->size); + DEBUG_GL_ERROR("glMapBufferRange failed of %" PRIu64 " bytes", + (uint64_t)buffer->size); egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); return buffer->map; @@ -175,6 +237,7 @@ void egl_texUtilUnmapBuffer(EGL_TexBuffer * buffer) if (!buffer->map) return; + DEBUG_ASSERT(buffer->mode == EGL_TEX_BUFFER_MODE_PERSISTENT); egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->pbo); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); egl_stateBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); diff --git a/client/renderers/EGL/texture_util.h b/client/renderers/EGL/texture_util.h index 6980becb..1d223f1b 100644 --- a/client/renderers/EGL/texture_util.h +++ b/client/renderers/EGL/texture_util.h @@ -40,16 +40,25 @@ typedef struct EGL_TexFormat } EGL_TexFormat; +typedef enum EGL_TexBufferMode +{ + EGL_TEX_BUFFER_MODE_DIRECT, + EGL_TEX_BUFFER_MODE_PERSISTENT +} +EGL_TexBufferMode; + typedef struct EGL_TexBuffer { - size_t size; - GLuint pbo; - void * map; - bool updated; + size_t size; + GLuint pbo; + void * map; + bool updated; + EGL_TexBufferMode mode; } EGL_TexBuffer; bool egl_texUtilGetFormat(const EGL_TexSetup * setup, EGL_TexFormat * fmt); +void egl_texUtilSetBufferMode(EGL_TexBufferMode mode); bool egl_texUtilGenBuffers(const EGL_TexFormat * fmt, EGL_TexBuffer * buffers, int count); void egl_texUtilFreeBuffers(EGL_TexBuffer * buffers, int count); diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index 1c1f2be8..18be548c 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -1007,29 +1007,81 @@ if(ENABLE_EGL) ) endforeach() - add_executable(texture-dmabuf-tests - texture_dmabuf_test.c + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_executable(texture-dmabuf-tests + texture_dmabuf_test.c + ) + add_dependencies(texture-dmabuf-tests renderer_EGL) + target_include_directories(texture-dmabuf-tests PRIVATE + "${CMAKE_BINARY_DIR}/renderers/EGL/shader" + ) + target_link_libraries(texture-dmabuf-tests + ${EXE_FLAGS} + lg_common + ) + set(TEXTURE_DMABUF_CASES + select + poll + saturation + release + identity + ) + foreach(name IN LISTS TEXTURE_DMABUF_CASES) + add_test(NAME texture-dmabuf-${name} + COMMAND texture-dmabuf-tests ${name} + ) + set_tests_properties(texture-dmabuf-${name} PROPERTIES + TIMEOUT 10 + ) + endforeach() + endif() + + add_executable(postprocess-order-tests + postprocess_order_test.c + ../renderers/EGL/postprocess_order.c ) - add_dependencies(texture-dmabuf-tests renderer_EGL) - target_include_directories(texture-dmabuf-tests PRIVATE - "${CMAKE_BINARY_DIR}/renderers/EGL/shader" + target_include_directories(postprocess-order-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../renderers/EGL" ) - target_link_libraries(texture-dmabuf-tests + target_link_libraries(postprocess-order-tests ${EXE_FLAGS} lg_common + lg_egl_provider ) - set(TEXTURE_DMABUF_CASES - select - poll - saturation - release - identity + add_test(NAME postprocess-order-tests + COMMAND postprocess-order-tests ) - foreach(name IN LISTS TEXTURE_DMABUF_CASES) - add_test(NAME texture-dmabuf-${name} - COMMAND texture-dmabuf-tests ${name} + set_tests_properties(postprocess-order-tests PROPERTIES + TIMEOUT 10 + ) + + add_executable(texture-upload-tests + texture_upload_test.c + ../renderers/EGL/texture_buffer.c + ) + target_include_directories(texture-upload-tests PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../renderers/EGL" + "${PROJECT_TOP}/common/include" + ) + target_link_libraries(texture-upload-tests + ${EXE_FLAGS} + LGProtocol::LGProtocol + lg_egl_provider + ) + set(TEXTURE_UPLOAD_CASES + direct + direct-full + direct-rect + persistent + persistent-full + persistent-rect + fallback + ) + foreach(name IN LISTS TEXTURE_UPLOAD_CASES) + add_test(NAME texture-upload-${name} + COMMAND texture-upload-tests ${name} ) - set_tests_properties(texture-dmabuf-${name} PROPERTIES + set_tests_properties(texture-upload-${name} PROPERTIES TIMEOUT 10 ) endforeach() diff --git a/client/tests/postprocess_order_test.c b/client/tests/postprocess_order_test.c new file mode 100644 index 00000000..1393a915 --- /dev/null +++ b/client/tests/postprocess_order_test.c @@ -0,0 +1,73 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" + +#include "../renderers/EGL/postprocess_order.h" + +static void checkFilterOrder(EGL_Filter ** filters, EGL_Filter ** expected, + size_t count) +{ + for (size_t i = 0; i < count; ++i) + CHECK(filters[i] == expected[i]); +} + +int main(void) +{ + EGL_Filter first = { .ops = { .id = "first" } }; + EGL_Filter second = { .ops = { .id = "second" } }; + EGL_Filter third = { .ops = { .id = "third" } }; + EGL_Filter unknownA = { .ops = { .id = "unknown-a" } }; + EGL_Filter unknownB = { .ops = { .id = "unknown-b" } }; + + EGL_Filter * filters [] = + { + &unknownA, + &second, + &unknownB, + &first, + &third, + }; + EGL_Filter * expected[] = + { + &first, + &second, + &third, + &unknownA, + &unknownB, + }; + + StringList order = stringlist_new(false); + CHECK(order); + CHECK(stringlist_push(order, "first") == 0); + CHECK(stringlist_push(order, "second") == 1); + CHECK(stringlist_push(order, "third") == 2); + + const size_t count = sizeof(filters) / sizeof(*filters); + egl_postProcessOrderFilters(filters, count, order); + checkFilterOrder(filters, expected, count); + + stringlist_clear(order); + egl_postProcessOrderFilters(filters, count, order); + checkFilterOrder(filters, expected, count); + + stringlist_free(&order); + return EXIT_SUCCESS; +} diff --git a/client/tests/texture_upload_test.c b/client/tests/texture_upload_test.c new file mode 100644 index 00000000..8dfdbbd8 --- /dev/null +++ b/client/tests/texture_upload_test.c @@ -0,0 +1,493 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" + +#include +#include +#include +#include + +#include "../renderers/EGL/texture_buffer.h" +#include "../renderers/EGL/texture_util.c" + +struct Trace +{ + unsigned int nextBuffer; + unsigned int storage; + unsigned int map; + unsigned int unmap; + unsigned int deleted; + unsigned int bufferBinds; + unsigned int textureBinds; + unsigned int uploads; + unsigned int fences; + unsigned int finishes; + unsigned int flushes; + GLuint boundPBO; + GLuint boundTexture; + GLint unpackAlignment; + GLint unpackRowLength; + GLint uploadX; + GLint uploadY; + GLsizei uploadWidth; + GLsizei uploadHeight; + const void * uploadData; + bool failMap; +}; + +static struct Trace trace; +static uint8_t mappedStorage[4096]; + +struct EGLDynProcs g_egl_dynProcs; +_Thread_local EGL_StateCache g_eglState; + +void debug_level(enum DebugLevel level, const char * file, unsigned int line, + const char * function, const char * format, ...) +{ +} + +static void fakeBufferStorage(GLenum target, GLsizeiptr size, + const void * data, GLbitfield flags) +{ + CHECK(target == GL_PIXEL_UNPACK_BUFFER); + CHECK(size > 0); + CHECK(!data); + CHECK(flags & GL_MAP_PERSISTENT_BIT_EXT); + ++trace.storage; +} + +void glGenBuffers(GLsizei count, GLuint * buffers) +{ + for (GLsizei i = 0; i < count; ++i) + buffers[i] = ++trace.nextBuffer; +} + +void glBindBuffer(GLenum target, GLuint buffer) +{ + if (target == GL_PIXEL_UNPACK_BUFFER) + { + trace.boundPBO = buffer; + ++trace.bufferBinds; + } +} + +void glActiveTexture(GLenum texture) +{ + CHECK(texture == GL_TEXTURE0); +} + +void glBindTexture(GLenum target, GLuint texture) +{ + CHECK(target == GL_TEXTURE_2D); + trace.boundTexture = texture; + ++trace.textureBinds; +} + +void glPixelStorei(GLenum pname, GLint param) +{ + switch (pname) + { + case GL_UNPACK_ALIGNMENT: + trace.unpackAlignment = param; + break; + + case GL_UNPACK_ROW_LENGTH: + trace.unpackRowLength = param; + break; + + default: + CHECK(false); + } +} + +void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, + GLint yoffset, GLsizei width, GLsizei height, GLenum format, + GLenum type, const void * pixels) +{ + CHECK(target == GL_TEXTURE_2D); + CHECK(level == 0); + CHECK(format == GL_RGBA); + CHECK(type == GL_UNSIGNED_BYTE); + + trace.uploadX = xoffset; + trace.uploadY = yoffset; + trace.uploadWidth = width; + trace.uploadHeight = height; + trace.uploadData = pixels; + ++trace.uploads; +} + +GLsync glFenceSync(GLenum condition, GLbitfield flags) +{ + CHECK(condition == GL_SYNC_GPU_COMMANDS_COMPLETE); + CHECK(flags == 0); + ++trace.fences; + return (GLsync)(uintptr_t)1; +} + +void glFinish(void) +{ + ++trace.finishes; +} + +void glFlush(void) +{ + ++trace.flushes; +} + +void * glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, + GLbitfield access) +{ + CHECK(target == GL_PIXEL_UNPACK_BUFFER); + CHECK(offset == 0); + CHECK(length <= (GLsizeiptr)sizeof(mappedStorage)); + CHECK(access & GL_MAP_PERSISTENT_BIT_EXT); + ++trace.map; + return trace.failMap ? NULL : mappedStorage; +} + +GLboolean glUnmapBuffer(GLenum target) +{ + CHECK(target == GL_PIXEL_UNPACK_BUFFER); + ++trace.unmap; + return GL_TRUE; +} + +void glDeleteBuffers(GLsizei count, const GLuint * buffers) +{ + trace.deleted += count; +} + +void egl_stateInvalidateShared(void) +{ + memset(&g_eglState, 0, sizeof(g_eglState)); +} + +const char * gl_getErrorStr(void) +{ + return "fake"; +} + +static void reset(void) +{ + memset(&trace, 0, sizeof(trace)); + memset(&g_eglState, 0, sizeof(g_eglState)); + memset(&g_egl_dynProcs, 0, sizeof(g_egl_dynProcs)); + g_egl_dynProcs.glBufferStorageEXT = fakeBufferStorage; +} + +static EGL_TexFormat testFormat(void) +{ + return (EGL_TexFormat) + { + .dataSize = 1024, + }; +} + +static void initTexture(TextureBuffer * texture, EGL_TexBufferMode mode) +{ + memset(texture, 0, sizeof(*texture)); + + texture->base.format.width = 8; + texture->base.format.height = 4; + texture->base.format.stride = 10; + texture->base.format.pitch = 40; + texture->base.format.bpp = 4; + texture->base.format.format = GL_RGBA; + texture->base.format.dataType = GL_UNSIGNED_BYTE; + texture->base.frameToken = LG_RENDERER_FRAME_TOKEN_NONE; + texture->texCount = 1; + texture->tex[0] = 17; + texture->buf[0].mode = mode; + texture->buf[0].pbo = + mode == EGL_TEX_BUFFER_MODE_PERSISTENT ? 23 : 0; + texture->buf[0].map = mappedStorage; + texture->buf[0].size = sizeof(mappedStorage); + texture->buf[0].updated = true; + texture->slotToken[0] = 42; + texture->rIndex = -1; + LG_LOCK_INIT(texture->copyLock); +} + +static void checkLockReleased(TextureBuffer * texture) +{ + const bool locked = atomic_flag_test_and_set_explicit( + &texture->copyLock, memory_order_acquire); + CHECK(!locked); + atomic_flag_clear_explicit(&texture->copyLock, memory_order_release); +} + +static void testDirect(void) +{ + reset(); + egl_texUtilSetBufferMode(EGL_TEX_BUFFER_MODE_DIRECT); + + EGL_TexBuffer buffers[2] = { 0 }; + const EGL_TexFormat format = testFormat(); + CHECK(egl_texUtilGenBuffers(&format, buffers, 2)); + CHECK(buffers[0].mode == EGL_TEX_BUFFER_MODE_DIRECT); + CHECK(buffers[1].mode == EGL_TEX_BUFFER_MODE_DIRECT); + CHECK(buffers[0].map); + CHECK(buffers[1].map); + CHECK(!buffers[0].pbo); + CHECK(!buffers[1].pbo); + CHECK(trace.storage == 0); + CHECK(trace.map == 0); + + egl_texUtilFreeBuffers(buffers, 2); + CHECK(!buffers[0].map); + CHECK(!buffers[1].map); + CHECK(trace.deleted == 0); +} + +static void testPersistent(void) +{ + reset(); + egl_texUtilSetBufferMode(EGL_TEX_BUFFER_MODE_PERSISTENT); + + EGL_TexBuffer buffer = { 0 }; + const EGL_TexFormat format = testFormat(); + CHECK(egl_texUtilGenBuffers(&format, &buffer, 1)); + CHECK(buffer.mode == EGL_TEX_BUFFER_MODE_PERSISTENT); + CHECK(buffer.map == mappedStorage); + CHECK(buffer.pbo == 1); + CHECK(trace.storage == 1); + CHECK(trace.map == 1); + + egl_texUtilFreeBuffers(&buffer, 1); + CHECK(!buffer.map); + CHECK(!buffer.pbo); + CHECK(trace.unmap == 1); + CHECK(trace.deleted == 1); +} + +static void testFallback(void) +{ + reset(); + trace.failMap = true; + egl_texUtilSetBufferMode(EGL_TEX_BUFFER_MODE_PERSISTENT); + + EGL_TexBuffer buffer = { 0 }; + const EGL_TexFormat format = testFormat(); + CHECK(egl_texUtilGenBuffers(&format, &buffer, 1)); + CHECK(buffer.mode == EGL_TEX_BUFFER_MODE_DIRECT); + CHECK(buffer.map); + CHECK(!buffer.pbo); + CHECK(trace.storage == 1); + CHECK(trace.map == 1); + CHECK(trace.unmap == 0); + CHECK(trace.deleted == 1); + + egl_texUtilFreeBuffers(&buffer, 1); + CHECK(!buffer.map); + + reset(); + EGL_TexBuffer next = { 0 }; + CHECK(egl_texUtilGenBuffers(&format, &next, 1)); + CHECK(next.mode == EGL_TEX_BUFFER_MODE_PERSISTENT); + CHECK(next.map == mappedStorage); + CHECK(next.pbo == 1); + egl_texUtilFreeBuffers(&next, 1); +} + +static void testDirectFull(void) +{ + reset(); + + TextureBuffer texture; + initTexture(&texture, EGL_TEX_BUFFER_MODE_DIRECT); + texture.damage[0].full = true; + + CHECK(egl_texBufferStreamProcess(&texture.base, 42) == + EGL_TEX_STATUS_UPDATED); + CHECK(trace.bufferBinds == 1); + CHECK(trace.boundPBO == 0); + CHECK(trace.textureBinds == 1); + CHECK(trace.boundTexture == texture.tex[0]); + CHECK(trace.unpackAlignment == 1); + CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride); + CHECK(trace.uploads == 1); + CHECK(trace.uploadX == 0); + CHECK(trace.uploadY == 0); + CHECK(trace.uploadWidth == (GLsizei)texture.base.format.width); + CHECK(trace.uploadHeight == (GLsizei)texture.base.format.height); + CHECK(trace.uploadData == mappedStorage); + CHECK(trace.fences == 0); + CHECK(trace.finishes == 0); + CHECK(trace.flushes == 0); + CHECK(!texture.buf[0].updated); + CHECK(!texture.damage[0].full); + CHECK(texture.damage[0].count == 0); + CHECK(texture.rIndex == 0); + CHECK(texture.bufIndex == 0); + CHECK(texture.base.frameToken == 42); + checkLockReleased(&texture); +} + +static void testDirectRect(void) +{ + reset(); + + TextureBuffer texture; + initTexture(&texture, EGL_TEX_BUFFER_MODE_DIRECT); + texture.damage[0].count = 1; + texture.damage[0].rects[0].x = 2; + texture.damage[0].rects[0].y = 1; + texture.damage[0].rects[0].width = 3; + texture.damage[0].rects[0].height = 2; + + const size_t offset = + texture.damage[0].rects[0].y * texture.base.format.pitch + + texture.damage[0].rects[0].x * texture.base.format.bpp; + + CHECK(egl_texBufferStreamProcess(&texture.base, 42) == + EGL_TEX_STATUS_UPDATED); + CHECK(trace.bufferBinds == 1); + CHECK(trace.boundPBO == 0); + CHECK(trace.textureBinds == 1); + CHECK(trace.boundTexture == texture.tex[0]); + CHECK(trace.unpackAlignment == 1); + CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride); + CHECK(trace.uploads == 1); + CHECK(trace.uploadX == 2); + CHECK(trace.uploadY == 1); + CHECK(trace.uploadWidth == 3); + CHECK(trace.uploadHeight == 2); + CHECK(trace.uploadData == mappedStorage + offset); + CHECK(trace.fences == 0); + CHECK(trace.finishes == 0); + CHECK(trace.flushes == 0); + CHECK(!texture.buf[0].updated); + CHECK(!texture.damage[0].full); + CHECK(texture.damage[0].count == 0); + CHECK(texture.rIndex == 0); + CHECK(texture.bufIndex == 0); + CHECK(texture.base.frameToken == 42); + checkLockReleased(&texture); +} + +static void testPersistentFull(void) +{ + reset(); + + TextureBuffer texture; + initTexture(&texture, EGL_TEX_BUFFER_MODE_PERSISTENT); + texture.damage[0].full = true; + + CHECK(egl_texBufferStreamProcess(&texture.base, 42) == + EGL_TEX_STATUS_UPDATED); + CHECK(trace.bufferBinds == 1); + CHECK(trace.boundPBO == texture.buf[0].pbo); + CHECK(trace.textureBinds == 1); + CHECK(trace.boundTexture == texture.tex[0]); + CHECK(trace.unpackAlignment == 1); + CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride); + CHECK(trace.uploads == 1); + CHECK(trace.uploadX == 0); + CHECK(trace.uploadY == 0); + CHECK(trace.uploadWidth == (GLsizei)texture.base.format.width); + CHECK(trace.uploadHeight == (GLsizei)texture.base.format.height); + CHECK(!trace.uploadData); + CHECK(trace.fences == 1); + CHECK(trace.finishes == 0); + CHECK(trace.flushes == 1); + CHECK(texture.sync[0] == (GLsync)(uintptr_t)1); + CHECK(!texture.buf[0].updated); + CHECK(!texture.damage[0].full); + CHECK(texture.damage[0].count == 0); + CHECK(texture.rIndex == 0); + CHECK(texture.bufIndex == 0); + CHECK(texture.base.frameToken == 42); + checkLockReleased(&texture); +} + +static void testPersistentRect(void) +{ + reset(); + + TextureBuffer texture; + initTexture(&texture, EGL_TEX_BUFFER_MODE_PERSISTENT); + texture.damage[0].count = 1; + texture.damage[0].rects[0].x = 2; + texture.damage[0].rects[0].y = 1; + texture.damage[0].rects[0].width = 3; + texture.damage[0].rects[0].height = 2; + + const uintptr_t offset = + texture.damage[0].rects[0].y * texture.base.format.pitch + + texture.damage[0].rects[0].x * texture.base.format.bpp; + + CHECK(egl_texBufferStreamProcess(&texture.base, 42) == + EGL_TEX_STATUS_UPDATED); + CHECK(trace.bufferBinds == 1); + CHECK(trace.boundPBO == texture.buf[0].pbo); + CHECK(trace.textureBinds == 1); + CHECK(trace.boundTexture == texture.tex[0]); + CHECK(trace.unpackAlignment == 1); + CHECK(trace.unpackRowLength == (GLint)texture.base.format.stride); + CHECK(trace.uploads == 1); + CHECK(trace.uploadX == 2); + CHECK(trace.uploadY == 1); + CHECK(trace.uploadWidth == 3); + CHECK(trace.uploadHeight == 2); + CHECK(trace.uploadData == (const void *)offset); + CHECK(trace.fences == 1); + CHECK(trace.finishes == 0); + CHECK(trace.flushes == 1); + CHECK(texture.sync[0] == (GLsync)(uintptr_t)1); + CHECK(!texture.buf[0].updated); + CHECK(!texture.damage[0].full); + CHECK(texture.damage[0].count == 0); + CHECK(texture.rIndex == 0); + CHECK(texture.bufIndex == 0); + CHECK(texture.base.frameToken == 42); + checkLockReleased(&texture); +} + +struct Test +{ + const char * name; + void (*run)(void); +}; + +static const struct Test tests[] = +{ + { "direct" , testDirect }, + { "persistent" , testPersistent }, + { "fallback" , testFallback }, + { "direct-full" , testDirectFull }, + { "direct-rect" , testDirectRect }, + { "persistent-full" , testPersistentFull }, + { "persistent-rect" , testPersistentRect }, +}; + +int main(int argc, char ** argv) +{ + CHECK(argc == 2); + for (unsigned int i = 0; i < sizeof(tests) / sizeof(tests[0]); ++i) + if (!strcmp(argv[1], tests[i].name)) + { + tests[i].run(); + return EXIT_SUCCESS; + } + + return EXIT_FAILURE; +}