diff --git a/.github/workflows/UpdateSubmodules.yml b/.github/workflows/UpdateSubmodules.yml new file mode 100644 index 000000000..b91b56c30 --- /dev/null +++ b/.github/workflows/UpdateSubmodules.yml @@ -0,0 +1,17 @@ +name: Update Submodules +on: + workflow_dispatch: + +jobs: + update-submodules: + runs-on: ubuntu-slim + steps: + - uses: actions/checkout@main + with: + submodules: 'recursive' + + - name: Update all submodules + run: | + chmod +x ./update.bash + ./update.bash + diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 016f7ee61..f06b33714 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,12 +1,7 @@ name: Benchmark on: - push: - branches: - - dev - - Feat/Backend-Direct-GLES - - Feat/Backend-Direct-Vulkan - + workflow_dispatch: jobs: benchmark: runs-on: ubuntu-latest diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..f4ce164c6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,46 @@ +name: Build + +on: + workflow_dispatch: + +env: + BUILD_TYPE: Release + CCACHE_DIR: /tmp/ccache # 设置ccache缓存目录 + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@main + with: + submodules: true + + # 添加ccache步骤 + - name: CCache + uses: hendrikmuhs/ccache-action@main + with: + key: ${{ runner.os }}-ndk-${{ env.BUILD_TYPE }} + max-size: 1G # 设置缓存大小,根据需要调整 + + # 将ccache添加到PATH + - name: Setup ccache in PATH + run: | + echo "/usr/lib/ccache" >> $GITHUB_PATH + echo "CCACHE_DIR=/tmp/ccache" >> $GITHUB_ENV + echo "CCACHE_MAXSIZE=2G" >> $GITHUB_ENV + + - name: Configure CMake and Build + run: bash android_ci_build.bash + env: + CC: ccache clang + CXX: ccache clang++ + CMAKE_C_COMPILER_LAUNCHER: ccache + CMAKE_CXX_COMPILER_LAUNCHER: ccache + + - name: Upload build output + uses: actions/upload-artifact@main + with: + name: MobileGL + path: | + **/libMobileGL.so diff --git a/.github/workflows/dry-run.yaml b/.github/workflows/dry-run.yaml new file mode 100644 index 000000000..bcb8e93aa --- /dev/null +++ b/.github/workflows/dry-run.yaml @@ -0,0 +1,153 @@ +name: Python Clean - Delete All Old Runs + +on: + workflow_dispatch: + inputs: + dry_run: + description: '模拟运行(只查看不删除)' + required: true + default: 'true' + type: boolean + days_old: + description: '删除多少天前的运行' + required: true + default: '1' + type: string + +jobs: + clean: + runs-on: ubuntu-latest + + permissions: + actions: write + contents: read + + steps: + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install requests + run: pip install requests + + - name: Delete old runs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + DRY_RUN: ${{ github.event.inputs.dry_run }} + DAYS_OLD: ${{ github.event.inputs.days_old }} + run: | + python - <<'EOF' + import os + import requests + from datetime import datetime, timedelta + import time + + token = os.environ.get('GITHUB_TOKEN') + repo = os.environ.get('REPO') + dry_run = os.environ.get('DRY_RUN', 'true').lower() == 'true' + days_old = int(os.environ.get('DAYS_OLD', 1)) + + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/vnd.github.v3+json' + } + + # 计算截止日期 + cutoff_date = (datetime.utcnow() - timedelta(days=days_old)).isoformat() + + print(f"🔍 清理配置:") + print(f" 仓库: {repo}") + print(f" 删除 {days_old} 天前的运行 (截止: {cutoff_date})") + print(f" 模拟模式: {dry_run}") + print() + + # 获取所有运行 + print("获取所有运行...") + runs_to_delete = [] + page = 1 + + while True: + url = f"https://api.github.com/repos/{repo}/actions/runs" + params = { + 'per_page': 100, + 'page': page + } + + response = requests.get(url, headers=headers, params=params) + if response.status_code != 200: + print(f"错误: {response.status_code}") + break + + data = response.json() + runs = data.get('workflow_runs', []) + + if not runs: + break + + for run in runs: + run_id = run['id'] + created_at = run['created_at'] + status = run['status'] + branch = run['head_branch'] + name = run.get('name', '未命名') + + # 跳过进行中的 + if status in ['in_progress', 'queued', 'pending']: + continue + + # 检查日期 + if created_at < cutoff_date: + runs_to_delete.append({ + 'id': run_id, + 'name': name, + 'date': created_at, + 'branch': branch + }) + print(f" ✓ 标记删除 #{run_id} - {name} ({created_at}) [{branch}]") + + if len(runs) < 100: + break + + page += 1 + time.sleep(0.5) + + total = len(runs_to_delete) + print(f"\n📊 找到 {total} 个需要删除的运行") + + if total == 0: + print("✅ 没有需要删除的运行") + exit(0) + + if dry_run: + print("\n🔍 [模拟模式] 以上是将会删除的运行") + print("设置 dry_run=false 实际删除") + exit(0) + + # 删除 + print(f"\n🗑️ 开始删除 {total} 个运行...") + deleted = 0 + failed = 0 + + for i, run in enumerate(runs_to_delete, 1): + run_id = run['id'] + url = f"https://api.github.com/repos/{repo}/actions/runs/{run_id}" + + print(f"进度: [{i}/{total}] 删除 #{run_id}...", end=' ') + + response = requests.delete(url, headers=headers) + if response.status_code == 204: + deleted += 1 + print("✓ 成功") + else: + failed += 1 + print(f"✗ 失败 ({response.status_code})") + + time.sleep(0.2) + + print(f"\n✅ 完成!") + print(f" 成功: {deleted}") + print(f" 失败: {failed}") + + EOF diff --git a/.github/workflows/plugin-apk.yml b/.github/workflows/plugin-apk.yml deleted file mode 100644 index 2165b285d..000000000 --- a/.github/workflows/plugin-apk.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: MobileGL Plugin APK - -on: - push: - branches: - - dev - - Feat/Backend-Direct-GLES - - Feat/Backend-Direct-Vulkan - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set artifact metadata - run: | - echo "date_today=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" - echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_ENV" - - - name: Set up JDK - uses: actions/setup-java@v4 - with: - distribution: zulu - java-version: '17' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - with: - gradle-version: 8.10.2 - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Install Android NDK - run: | - sdkmanager "ndk;27.3.13750724" - echo "ndk.dir=$ANDROID_HOME/ndk/27.3.13750724" >> android-plugin/local.properties - - - name: Update glslang external sources - working-directory: 3rdparty/glslang - run: python update_glslang_sources.py - - - name: Build plugin APK - run: gradle --no-daemon -p android-plugin :app:assembleRelease - env: - SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} - SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} - SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} - - - name: Verify signed APK - run: | - APKSIGNER="$(find "$ANDROID_HOME/build-tools" -name apksigner -type f | sort -V | tail -n 1)" - mapfile -t APKS < <(find android-plugin/app/build/outputs/apk -type f -path '*/release/*.apk' | sort) - if [ "${#APKS[@]}" -eq 0 ]; then - echo "::error::No release APKs found under android-plugin/app/build/outputs/apk" - find android-plugin/app/build/outputs/apk -type f -print || true - exit 1 - fi - - for APK in "${APKS[@]}"; do - if [[ "$APK" == *-unsigned.apk ]]; then - echo "::error::Unsigned release APK produced: $APK" - exit 1 - fi - "$APKSIGNER" verify --verbose "$APK" - done - - - name: Upload plugin APK - uses: actions/upload-artifact@v4 - with: - name: MobileGL-plugin-${{ env.date_today }}-${{ env.short_sha }} - path: | - android-plugin/app/build/outputs/apk/release/*.apk - android-plugin/app/build/outputs/apk/**/release/*.apk diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f43f1030d..403a56986 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,11 +1,7 @@ name: Test on: - push: - branches: - - dev - - Feat/Backend-Direct-GLES - - Feat/Backend-Direct-Vulkan + workflow_dispatch: jobs: test: diff --git a/.gitmodules b/.gitmodules index cab80ed49..ec0535acf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,13 +3,13 @@ url = https://github.com/DiligentGraphics/DiligentCore.git [submodule "3rdparty/glslang"] path = 3rdparty/glslang - url = https://github.com/MobileGL-Dev/glslang.git + url = https://github.com/aaaapai/glslang.git [submodule "3rdparty/SPIRV-Cross"] path = 3rdparty/SPIRV-Cross - url = https://github.com/KhronosGroup/SPIRV-Cross.git + url = https://github.com/aaaapai/SPIRV-Cross.git [submodule "include/FastSTL"] path = include/FastSTL - url = https://github.com/MobileGL-Dev/FastSTL.git + url = https://github.com/aaaapai/FastSTL.git [submodule "3rdparty/tracy"] path = 3rdparty/tracy url = https://github.com/wolfpld/tracy.git diff --git a/3rdparty/DiligentCore b/3rdparty/DiligentCore index f36e63880..163fb0c5f 160000 --- a/3rdparty/DiligentCore +++ b/3rdparty/DiligentCore @@ -1 +1 @@ -Subproject commit f36e63880566649d9d8b2336a7c5aa9c8ddb174b +Subproject commit 163fb0c5f3c5a77563db7f375b0200e90aea812a diff --git a/3rdparty/SPIRV-Cross b/3rdparty/SPIRV-Cross index 072444287..157316dc0 160000 --- a/3rdparty/SPIRV-Cross +++ b/3rdparty/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 072444287f4e139c178d6d8fe32e04a0d2c34e8b +Subproject commit 157316dc0954f3b6606f17e945e97decabb80996 diff --git a/3rdparty/glslang b/3rdparty/glslang index 26fe5ceb4..aad8360c9 160000 --- a/3rdparty/glslang +++ b/3rdparty/glslang @@ -1 +1 @@ -Subproject commit 26fe5ceb4549010890628e09697f5c8a1da9be25 +Subproject commit aad8360c9247e7594fa0f77b3a52a720336548ed diff --git a/3rdparty/tracy b/3rdparty/tracy index e6b9ea460..8420a1d7d 160000 --- a/3rdparty/tracy +++ b/3rdparty/tracy @@ -1 +1 @@ -Subproject commit e6b9ea460993a6f093283055ef3c98025b19f909 +Subproject commit 8420a1d7d626362f02d24b52e53513564e926577 diff --git a/3rdparty/xxHash b/3rdparty/xxHash index 1d7b2a9d2..da35f833e 160000 --- a/3rdparty/xxHash +++ b/3rdparty/xxHash @@ -1 +1 @@ -Subproject commit 1d7b2a9d21bf9a740ead540aa93f4bc4caf11ae0 +Subproject commit da35f833ee5a5b2056471be546886660343e611c diff --git a/CMakeLists.txt b/CMakeLists.txt index ccf75d3a3..0675f9ab1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,35 @@ cmake_minimum_required(VERSION 3.22.1) project("MobileGL") +set(CMAKE_C_STANDARD 23) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_ANDROID_LD "lld") +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +option(MOBILEGL_FORCE_RELEASE_OPT "Enable Release optimization flags in Debug build" ON) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUseABSL -O3 -pipe -integrated-as -mllvm -polly -mllvm -polly-vectorizer=stripmine -mllvm -polly-invariant-load-hoisting -mllvm -polly-run-inliner -mllvm -polly-run-dce -mllvm -polly-invariant-load-hoisting -flto -mllvm -polly-run-inliner -mllvm -polly-run-dce -fdata-sections -ffunction-sections -mllvm -polly-detect-keep-going -mllvm -polly-ast-use-context -march=armv8-a+simd -fvisibility=hidden -Wall -Wextra -ferror-limit=0 -Wno-return-type -Wno-unused-parameter -Wno-c++98-compat -Wno-c++98-compat-pedantic -DGLM_FORCE_INTRINSICS -Wno-unicode-whitespace -Wno-format -Wno-unused-function -fmerge-all-constants -D_FILE_OFFSET_BITS=64 -D__USE_BSD -D_ALLBSD_SOURCE -mllvm -inlinehint-threshold=100000 -D_GNU_SOURCE -U__USE_GNU") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -pipe -integrated-as -mllvm -polly -mllvm -polly-vectorizer=stripmine -mllvm -polly-invariant-load-hoisting -mllvm -polly-run-inliner -mllvm -polly-run-dce -mllvm -polly-invariant-load-hoisting -flto -mllvm -polly-run-inliner -mllvm -polly-run-dce -fdata-sections -ffunction-sections -mllvm -polly-detect-keep-going -mllvm -polly-ast-use-context -march=armv8-a+simd -fvisibility=hidden -Wall -Wextra -ferror-limit=0 -Wno-return-type -Wno-unused-parameter -Wno-c++98-compat -Wno-c++98-compat-pedantic -DGLM_FORCE_INTRINSICS -Wno-unicode-whitespace -Wno-format -Wno-unused-function -Wno-vla-cxx-extension -frtti -fmerge-all-constants -D_FILE_OFFSET_BITS=64 -D__USE_BSD -D_ALLBSD_SOURCE -mllvm -inlinehint-threshold=100000 -DUseABSL -D_GNU_SOURCE -U__USE_GNU") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -flto -O3 -Wl,--strip-all -fvisibility=hidden -Wl,-O3 -Wl,--gc-sections -Wl,--as-needed -static-libstdc++ -Wl,--exclude-libs,ALL -Wl,--lto-O3") +include(FetchContent) +FetchContent_Declare( + abseil-cpp + GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git + GIT_SHALLOW TRUE +) +set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(ABSL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(abseil-cpp) +FetchContent_GetProperties(abseil-cpp SOURCE_DIR ABSEIL_SOURCE_DIR) +get_cmake_property(_all_targets TARGETS) +foreach(_target IN LISTS _all_targets) + if(_target MATCHES "^absl::") + message(STATUS "Found Abseil target: ${_target}") + endif() +endforeach() + + + option(MOBILEGL_BUILD_TEST "Build MobileGL tests" ON ) option(MOBILEGL_BUILD_BENCHMARK "Build MobileGL benchmarks" ON ) option(MOBILEGL_FORCE_RELEASE_OPT "Enable Release optimization flags in Debug build" ON ) @@ -12,60 +41,11 @@ if (ANDROID) set(MOBILEGL_BUILD_BENCHMARK OFF CACHE BOOL "Build MobileGL benchmarks" FORCE) endif() -if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug" OR MOBILEGL_FORCE_RELEASE_OPT) - # Check if ThinLTO or LTO is suppported - include(CheckIPOSupported) - include(CheckCCompilerFlag) - include(CheckCXXCompilerFlag) - - check_ipo_supported(RESULT LTOSupported OUTPUT LTOError) - - check_c_compiler_flag("-flto" HAS_LTO_C) - check_cxx_compiler_flag("-flto" HAS_LTO_CXX) - - if (LTOSupported OR (HAS_LTO_C AND HAS_LTO_CXX)) - # Check ThinLTO - check_c_compiler_flag("-flto=thin" HAS_THINLTO_C) - check_cxx_compiler_flag("-flto=thin" HAS_THINLTO_CXX) - if (HAS_THINLTO_C AND HAS_THINLTO_CXX) - message(STATUS "ThinLTO supported, using -flto=thin") - - add_compile_options(-flto=thin) - add_link_options(-flto=thin) - else() - # ThinLTO is not supported - message(STATUS "ThinLTO not available, fallback to CMAKE IPO") - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) - endif() - else() - message(STATUS "IPO not supported: ${LTOError}") - endif() - - if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT MATCHES "AppleClang") - add_compile_options(-O3 -ffunction-sections -fdata-sections) - add_link_options(-Wl,--gc-sections) - elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") -# add_compile_options(/O2) - else () - add_compile_options(-O2) - endif() -endif() - if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") add_compile_options(/Zc:preprocessor) add_compile_options(/Zc:__cplusplus) endif() -enable_language(CXX) - -set(CMAKE_CXX_STANDARD 23) - -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - if (MSVC) set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}") endif() @@ -269,6 +249,10 @@ set(SOURCE_FILES MobileGL/MG_State/GLState/SamplerState/SamplerState.cpp MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp MobileGL/MG_State/GLState/RenderbufferState/RenderbufferState.cpp + + MobileGL/MG_Util/Config/cJSON.c + MobileGL/MG_Util/Config/config.cpp + MobileGL/MG_Util/Config/settings.cpp ) set(MOBILEGL_LINK_LIBRARIES @@ -377,6 +361,10 @@ if (ANDROID) android log vulkan + absl::flat_hash_map + ) + target_link_options(${CMAKE_PROJECT_NAME} PRIVATE + "LINKER:--version-script=${CMAKE_SOURCE_DIR}/MobileGL/mgl-version.script" ) endif() diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index 96e1eb0e3..275efa3db 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -34,12 +34,10 @@ #define MOBILEGL_EGL_API MOBILEGL_API // ====================== MobileGL configurations ======================= // -#ifndef MOBILEGL_LOG_ACTIVE_LEVEL -#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_DEBUG -#endif +#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO -#define MOBILEGL_LOG_ENABLE_CONSOLE 0 -#define MOBILEGL_LOG_ENABLE_FILE 1 +#define MOBILEGL_LOG_ENABLE_CONSOLE 1 +#define MOBILEGL_LOG_ENABLE_FILE 0 #define MOBILEGL_LOG_ENABLE_ANDROID 1 #define MOBILEGL_ENABLE_SCOPE_MARKER 0 @@ -50,7 +48,7 @@ #endif #ifdef __ANDROID__ -#define MOBILEGL_LOG_FILE_PATH "/sdcard/MG/latest.log" +#define MOBILEGL_LOG_FILE_PATH "/sdcard/MGL/latest.log" #else #define MOBILEGL_LOG_FILE_PATH "" #endif diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 75a086ea1..58dd517da 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -14,8 +14,21 @@ #include #include +#include +#include + namespace MobileGL { void Initialize() { + + const char* mgl_config_in_plugin = std::getenv("MGL_CONFIG_IN_PLUGIN"); + if (mgl_config_in_plugin != nullptr) { + std::string_view sv(mgl_config_in_plugin); + if (sv == "true") { + if (check_path()) config_refresh(); + init_settings(); + } + } + MG_Util::Debug::InitFile(); MGLOG_I("Initializing MobileGL..."); MG_ConfigLoader::Init(); diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 5d7a764f4..7256bde2e 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace MobileGL::MG_Backend::DirectGLES { namespace { @@ -119,23 +120,73 @@ namespace MobileGL::MG_Backend::DirectGLES { BackendObject::ReleaseEGLResources(); } + GLExtension getVersionExtension(int major, int minor) { + // 组合版本号:major=4, minor=3 → 43 + int version = major * 10 + minor; + + switch (version) { + case 30: return V_OpenGL30; + case 31: return V_OpenGL31; + case 32: return V_OpenGL32; + case 33: return V_OpenGL33; + case 40: return V_OpenGL40; + case 41: return V_OpenGL41; + case 42: return V_OpenGL42; + case 43: return V_OpenGL43; + case 44: return V_OpenGL44; + case 45: return V_OpenGL45; + case 46: return V_OpenGL46; + default: + MGLOG_E("Unsupported OpenGL version: %d.%d, fallback to 3.3", major, minor); + return V_OpenGL33; + } + } + const RendererInfo& BackendObject_DirectGLES::GetRendererInfo() const { + const char* envLibGL = std::getenv("LIBGL_GL"); + + Vector extensions = { + V_OpenGL30, V_OpenGL31, V_OpenGL32, // OpenGL Extensions + V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, + E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, + E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, + E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, + E_GL_ARB_texture_storage, E_GL_ARB_direct_state_access, + E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, + E_GL_ARB_shader_draw_parameters}; + + + Version targetVersion = {3, 3, 0}; + + if (envLibGL != nullptr) { + std::string verStr = envLibGL; + int major = 0, minor = 0; + if (sscanf(verStr.c_str(), "%d.%d", &major, &minor) == 2) { + // 已经是 "4.3" 格式 + } else if (sscanf(verStr.c_str(), "%d%d", &major, &minor) == 2) { + // "43" -> major=4, minor=3 + } else { + MGLOG_E("Invalid LIBGL_GL format: %s", verStr.c_str()); + } + + targetVersion = {major, minor, 0}; + + // 动态添加从 3.0 到 targetVersion 的所有扩展(或按需添加) + // 例如:如果目标是 4.5,添加 4.5、4.4、4.3 ... + for (int v = 30; v <= major * 10 + minor; ++v) { + extensions.push_back(getVersionExtension(v / 10, v % 10)); + } + } + static RendererInfo RendererInfo = { .RendererName = "Espryt", // Renderer Name .BackendName = "Direct (OpenGL ES)", // Backend Name .ExtraVendor = Nullopt, // Extra vendor .RendererGLInfo = { - .TargetGLVersion = {3, 3, 0}, // Target OpenGL Version + .TargetGLVersion = targetVersion, // Target OpenGL Version .TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version - .Extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, // OpenGL Extensions - V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_compute_shader, - E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store, - E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, - E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, - E_GL_ARB_texture_storage, E_GL_ARB_direct_state_access, - E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, - E_GL_ARB_shader_draw_parameters}, + .Extensions = extensions, .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 91a6cea1c..f4aae7470 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -33,6 +33,7 @@ #pragma pop_macro("None") #pragma pop_macro("Bool") #endif +#include namespace MobileGL::MG_Backend::DirectGLES { MG_External::EGLFunctionsTable g_EGLFuncs; @@ -49,7 +50,7 @@ namespace MobileGL::MG_Backend::DirectGLES { }; inline DrawSyncBit operator|(DrawSyncBit a, DrawSyncBit b) { - return static_cast(static_cast(a) | static_cast(b)); + return static_cast(static_cast(a) | static_cast(b)); } inline DrawSyncBit& operator|=(DrawSyncBit& a, DrawSyncBit b) { @@ -968,17 +969,120 @@ namespace MobileGL::MG_Backend::DirectGLES { } } - void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, - GLsizei drawcount, const GLint* basevertex) { + + namespace { + struct DrawElementsIndirectCommand { + GLuint count; + GLuint instanceCount; + GLuint firstIndex; + GLuint baseVertex; + GLuint baseInstance; + }; + + thread_local struct { + GLuint bufferId = 0; + GLsizei capacity = 0; + GLuint previousBinding = 0; + bool bufferInitialized = false; + } s_indirectBuffer; + + void InitializeIndirectBuffer() { + if (!s_indirectBuffer.bufferInitialized) { + g_GLESFuncs.glGenBuffers(1, &s_indirectBuffer.bufferId); + s_indirectBuffer.bufferInitialized = true; + s_indirectBuffer.capacity = 0; + } + } + + void EnsureIndirectBufferCapacity(GLsizei requiredSize) { + InitializeIndirectBuffer(); + + if (s_indirectBuffer.capacity < requiredSize) { + GLsizei newCapacity = s_indirectBuffer.capacity; + if (newCapacity == 0) { + newCapacity = 16; + } + while (newCapacity < requiredSize) { + newCapacity *= 2; + } + + g_GLESFuncs.glBindBuffer(GL_DRAW_INDIRECT_BUFFER, s_indirectBuffer.bufferId); + g_GLESFuncs.glBufferData(GL_DRAW_INDIRECT_BUFFER, + newCapacity * sizeof(DrawElementsIndirectCommand), + nullptr, GL_DYNAMIC_DRAW); + + s_indirectBuffer.capacity = newCapacity; + MGLOG_D("Indirect buffer resized to capacity: %d commands", newCapacity); + } + } + + void SaveAndBindIndirectBuffer() { + g_GLESFuncs.glGetIntegerv(GL_DRAW_INDIRECT_BUFFER_BINDING, + reinterpret_cast(&s_indirectBuffer.previousBinding)); + g_GLESFuncs.glBindBuffer(GL_DRAW_INDIRECT_BUFFER, s_indirectBuffer.bufferId); + } + + void RestoreIndirectBuffer() { + g_GLESFuncs.glBindBuffer(GL_DRAW_INDIRECT_BUFFER, s_indirectBuffer.previousBinding); + } + } + + void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, + const GLvoid* const* indices, GLsizei drawcount, + const GLint* basevertex) { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER DebugImpl::OpenGLScopeMarker marker(__func__); #endif - DrawSyncBit syncBit = DrawSyncBit::IndexBuffer; + + DrawSyncBit syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::IndirectBuffer; PrepareForDraw(syncBit); - + + GLsizei elementSize = 4; + switch (type) { + case GL_UNSIGNED_BYTE: + elementSize = 1; + break; + case GL_UNSIGNED_SHORT: + elementSize = 2; + break; + case GL_UNSIGNED_INT: + elementSize = 4; + break; + default: + MGLOG_E("Unsupported index type: 0x%x", type); + return; + } + + Vector commands(drawcount); + + for (GLsizei i = 0; i < drawcount; ++i) { + commands[i].count = static_cast(count[i]); + commands[i].instanceCount = 1; + commands[i].baseVertex = basevertex ? static_cast(basevertex[i]) : 0; + commands[i].baseInstance = 0; + + if (indices && indices[i]) { + uintptr_t offset = reinterpret_cast(indices[i]); + commands[i].firstIndex = static_cast(offset / elementSize); + } else { + commands[i].firstIndex = 0; + } + } + + EnsureIndirectBufferCapacity(drawcount); + SaveAndBindIndirectBuffer(); + + g_GLESFuncs.glBufferSubData(GL_DRAW_INDIRECT_BUFFER, + 0, + drawcount * sizeof(DrawElementsIndirectCommand), + commands.data()); + for (GLsizei i = 0; i < drawcount; ++i) { - g_GLESFuncs.glDrawElementsBaseVertex(mode, count[i], type, indices[i], basevertex[i]); + const void* offset = reinterpret_cast(i * sizeof(DrawElementsIndirectCommand)); + g_GLESFuncs.glDrawElementsIndirect(mode, type, offset); } + + RestoreIndirectBuffer(); } void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { @@ -1116,7 +1220,7 @@ namespace MobileGL::MG_Backend::DirectGLES { PrepareForDraw(syncBit); for (GLsizei i = 0; i < drawcount; ++i) { - const GLvoid* cmd = reinterpret_cast(reinterpret_cast(indirect) + + const GLvoid* cmd = reinterpret_cast(reinterpret_cast(indirect) + i * (stride ? stride : sizeof(GLsizei) * 4)); g_GLESFuncs.glDrawArraysIndirect(mode, cmd); } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index e36d239de..65af96416 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -1280,7 +1280,8 @@ namespace MobileGL::MG_Backend::DirectGLES { spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, ResolveBackendEsslVersion()); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE); - spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); + spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ENABLE_420PACK_EXTENSION, SPVC_FALSE); + //spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); spvcSession.SetOptions(options); diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 5bea76ac8..d765ccf3e 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -17,6 +17,7 @@ #include "../RenderState/GL_RenderState.h" #include "../Framebuffer/GL_Framebuffer.h" #include "../VertexArray/GL_VertexArray.h" +#include "../Sync/GL_Sync.h" #define DECLARE_GL_FUNCTION_STUB_HEAD(type, name, ...) MOBILEGL_GL_API type gl##name(__VA_ARGS__) { diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index 33e3d1cfe..bf01ff324 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -734,6 +734,11 @@ namespace MobileGL::MG_Impl::GLImpl { } GLenum CheckFramebufferStatus_State(GLenum target) { + + if (std::getenv("MGL_CHEAT_CHECKFRAMEBUFFERSTATUS")) { + return GL_FRAMEBUFFER_COMPLETE; + } + FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target); if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return GL_FRAMEBUFFER_UNDEFINED; @@ -1085,13 +1090,13 @@ namespace MobileGL::MG_Impl::GLImpl { // Check alignment const SizeT typeSize = MG_Util::GetTexturePixelDataTypeSize(texturePixelDataType); - if (reinterpret_cast(pixels) % typeSize != 0) { + /*if (reinterpret_cast(pixels) % typeSize != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "ReadPixels_State", "Pixel data not aligned for pixel pack buffer")); return; - } + }*/ } // Check multisampling diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp index 0ef197099..0aa0e00bd 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp @@ -312,6 +312,8 @@ namespace MobileGL::MG_Impl::GLImpl { } void ClearDepth_State(GLclampd depth) { + if (depth < 0.0) depth = 0.0; + if (depth > 1.0) depth = 1.0; MG_State::pGLContext->SetClearDepth(static_cast(depth)); } diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index b3b96e886..53cfc22d1 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -9,7 +9,7 @@ #include "GetProcAddress.h" #define GETPROC(name, var) \ if (strcmp(#name, var) == 0) { \ - return (void*)name; \ + return reinterpret_cast(name); \ } namespace MobileGL::MG_Impl { diff --git a/MobileGL/MG_State/EGLState/Core.cpp b/MobileGL/MG_State/EGLState/Core.cpp index c9ef85d68..ba449ee0d 100644 --- a/MobileGL/MG_State/EGLState/Core.cpp +++ b/MobileGL/MG_State/EGLState/Core.cpp @@ -238,7 +238,7 @@ namespace MobileGL { for (auto contextIt = m_contexts.begin(); contextIt != m_contexts.end();) { if (contextIt->second.Display == display) { removedContexts.push_back(contextIt->first); - contextIt = m_contexts.erase(contextIt); + m_contexts.erase(contextIt); } else { ++contextIt; } @@ -250,7 +250,7 @@ namespace MobileGL { for (auto surfaceIt = m_surfaces.begin(); surfaceIt != m_surfaces.end();) { if (surfaceIt->second.Display == display) { - surfaceIt = m_surfaces.erase(surfaceIt); + m_surfaces.erase(surfaceIt); } else { ++surfaceIt; } @@ -258,7 +258,7 @@ namespace MobileGL { for (auto syncIt = m_syncs.begin(); syncIt != m_syncs.end();) { if (syncIt->second.Display == display) { - syncIt = m_syncs.erase(syncIt); + m_syncs.erase(syncIt); } else { ++syncIt; } @@ -266,7 +266,7 @@ namespace MobileGL { for (auto imageIt = m_images.begin(); imageIt != m_images.end();) { if (imageIt->second.Display == display) { - imageIt = m_images.erase(imageIt); + m_images.erase(imageIt); } else { ++imageIt; } @@ -280,7 +280,7 @@ namespace MobileGL { m_contextOwners.erase(ownerIt); } } - threadIt = m_threadCurrents.erase(threadIt); + m_threadCurrents.erase(threadIt); } else { ++threadIt; } @@ -926,7 +926,7 @@ namespace MobileGL { currentIt->second.DrawSurface == EGL_NO_SURFACE && currentIt->second.ReadSurface == EGL_NO_SURFACE; if (emptyCurrent) { - currentIt = m_threadCurrents.erase(currentIt); + m_threadCurrents.erase(currentIt); continue; } ++currentIt; diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 91e2ebf0a..c930c860b 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -20,7 +20,7 @@ namespace MobileGL::MG_Util::BackendLoader { void* lib = nullptr; - Int flags = RTLD_LOCAL | RTLD_NOW; + Int flags = RTLD_GLOBAL | RTLD_LAZY; for (const auto& prefix : LibPathPrefixes) { for (const auto& name : names) { String path_name = prefix + name; @@ -432,10 +432,16 @@ namespace MobileGL::MG_Util::BackendLoader { } Bool AcquireEGLFunctions(MG_External::EGLFunctionsTable& funcs) { - static const Vector EGLLibNames = {"libEGL.so"}; - void* eglLib = OpenLib(EGLLibNames); + static const Vector EGLLibNames = {"libEGL.so", "libEGL_angle.so", "libEGL_mesa.so" }; + void* eglLib = nullptr; + const char* libgl_egl = std::getenv("LIBGL_EGL"); + if (!libgl_egl) { + eglLib = OpenLib(EGLLibNames); + } else { + eglLib = dlopen(libgl_egl, RTLD_GLOBAL | RTLD_LAZY); + } if (!eglLib) { - MGLOG_E("Failed to open libEGL.so"); + MGLOG_E("Failed to open libEGL"); return false; } #define INIT_EGL_FUNC(name) \ diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index bc67087b1..c7453c47d 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -1007,7 +1007,7 @@ namespace MobileGL { }; struct GLESCapabilities { - Version GLESVersion{3, 0, 0}; + Version GLESVersion{3, 6, 0}; String GLESVersionString; String GLESVendorString; String GLESRendererString; diff --git a/MobileGL/MG_Util/Config/EnvChecker.h b/MobileGL/MG_Util/Config/EnvChecker.h new file mode 100644 index 000000000..5681c4b0a --- /dev/null +++ b/MobileGL/MG_Util/Config/EnvChecker.h @@ -0,0 +1,82 @@ +#pragma once +#include + +namespace MobileGL { +namespace MG_Util { + +static inline bool CheckEnvANGLE(bool forceRefresh = false) { + // 使用 static 变量缓存结果 + static const char* cachedValue = nullptr; + static bool initialized = false; + + // 强制刷新或第一次调用时获取环境变量 + if (forceRefresh || !initialized) { + cachedValue = std::getenv("LIBGL_EGL"); + initialized = true; + } + + // 如果环境变量不存在 + if (cachedValue == nullptr) { + return false; + } + + // 比较环境变量的值是否完全匹配 "libEGL_ANGLE.so" + return std::strcmp(cachedValue, "libEGL_ANGLE.so") == 0; +} + +static inline bool CheckGLESDriverEnvExists(bool forceRefresh = false) { + // 使用 static 变量缓存结果 + static const char* cachedValue = nullptr; + static bool initialized = false; + + // 强制刷新或第一次调用时获取环境变量 + if (forceRefresh || !initialized) { + cachedValue = std::getenv("LIBGL_EGL"); + initialized = true; + } + + return cachedValue != nullptr; +} + +static inline const char* GetGLESDriverEnvValue(bool forceRefresh = false) { + // 使用 static 变量缓存结果 + static const char* cachedValue = nullptr; + static bool initialized = false; + + // 强制刷新或第一次调用时获取环境变量 + if (forceRefresh || !initialized) { + cachedValue = std::getenv("LIBGL_EGL"); + initialized = true; + } + + return cachedValue; +} + +static inline bool CheckEnv(const char* envName, const char* expectedValue = nullptr, bool forceRefresh = true) { + static const char* cachedEnvName = nullptr; + static const char* cachedValue = nullptr; + static bool initialized = false; + + if (forceRefresh || !initialized || + (envName != cachedEnvName && + (envName == nullptr || cachedEnvName == nullptr || + std::strcmp(envName, cachedEnvName) != 0))) { + + cachedEnvName = envName; + cachedValue = std::getenv(envName); + initialized = true; + } + + if (cachedValue == nullptr) { + return false; + } + + if (expectedValue == nullptr) { + return true; + } + + return std::strcmp(cachedValue, expectedValue) == 0; +} + +} // namespace MG_Util +} // namespace MobileGL diff --git a/MobileGL/MG_Util/Config/cJSON.c b/MobileGL/MG_Util/Config/cJSON.c new file mode 100644 index 000000000..7c61980be --- /dev/null +++ b/MobileGL/MG_Util/Config/cJSON.c @@ -0,0 +1,3143 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { nullptr, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return nullptr; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two nullptr pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == nullptr) || (string2 == nullptr)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = nullptr; + + if (string == nullptr) + { + return nullptr; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == nullptr) + { + return nullptr; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == nullptr) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != nullptr) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != nullptr) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = nullptr; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = nullptr; + while (item != nullptr) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != nullptr)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != nullptr)) + { + global_hooks.deallocate(item->valuestring); + item->valuestring = nullptr; + } + if (!(item->type & cJSON_StringIsConst) && (item->string != nullptr)) + { + global_hooks.deallocate(item->string); + item->string = nullptr; + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != nullptr) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != nullptr) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = nullptr; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == nullptr) || (input_buffer->content == nullptr)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +/* Note: when passing a nullptr valuestring, cJSON_SetValuestring treats this as an error and return nullptr */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = nullptr; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if ((object == nullptr) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return nullptr; + } + /* return nullptr if the object is corrupted or valuestring is nullptr */ + if (object->valuestring == nullptr || valuestring == nullptr) + { + return nullptr; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == nullptr) + { + return nullptr; + } + if (object->valuestring != nullptr) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = nullptr; + size_t newsize = 0; + + if ((p == nullptr) || (p->buffer == nullptr)) + { + return nullptr; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return nullptr; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return nullptr; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return nullptr; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return nullptr; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != nullptr) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == nullptr) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = nullptr; + + return nullptr; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = nullptr; + + return nullptr; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = nullptr; + if ((buffer == nullptr) || (buffer->buffer == nullptr)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = nullptr; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == nullptr) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "nullptr"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == nullptr) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = nullptr; + unsigned char *output = nullptr; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == nullptr) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != nullptr) + { + input_buffer->hooks.deallocate(output); + output = nullptr; + } + + if (input_pointer != nullptr) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = nullptr; + unsigned char *output = nullptr; + unsigned char *output_pointer = nullptr; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == nullptr) + { + return false; + } + + /* empty string */ + if (input == nullptr) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == nullptr) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == nullptr) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == nullptr) || (buffer->content == nullptr)) + { + return nullptr; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == nullptr) || (buffer->content == nullptr) || (buffer->offset != 0)) + { + return nullptr; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (nullptr == value) + { + return nullptr; + } + + /* Adding nullptr character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = nullptr; + + /* reset error position */ + global_error.json = nullptr; + global_error.position = 0; + + if (value == nullptr || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == nullptr) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != nullptr) + { + cJSON_Delete(item); + } + + if (value != nullptr) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != nullptr) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return nullptr; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = nullptr; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == nullptr) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != nullptr) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == nullptr) { + goto fail; + } + buffer->buffer = nullptr; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == nullptr) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + buffer->buffer = nullptr; + } + + return printed; + +fail: + if (buffer->buffer != nullptr) + { + hooks->deallocate(buffer->buffer); + buffer->buffer = nullptr; + } + + if (printed != nullptr) + { + hooks->deallocate(printed); + printed = nullptr; + } + + return nullptr; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return nullptr; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return nullptr; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + p.buffer = nullptr; + return nullptr; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == nullptr)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == nullptr) || (input_buffer->content == nullptr)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = nullptr; + + if ((item == nullptr) || (output_buffer == nullptr)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == nullptr) + { + return false; + } + strcpy((char*)output, "nullptr"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == nullptr) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == nullptr) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == nullptr) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == nullptr) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = nullptr; /* head of the linked list */ + cJSON *current_item = nullptr; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == nullptr) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == nullptr) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != nullptr) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != nullptr) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = nullptr; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == nullptr) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == nullptr) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != nullptr) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == nullptr) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == nullptr) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = nullptr; /* linked list head */ + cJSON *current_item = nullptr; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == nullptr) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == nullptr) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = nullptr; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != nullptr) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != nullptr) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = nullptr; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == nullptr) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == nullptr) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == nullptr) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == nullptr) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == nullptr) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == nullptr) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = nullptr; + size_t size = 0; + + if (array == nullptr) + { + return 0; + } + + child = array->child; + + while(child != nullptr) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = nullptr; + + if (array == nullptr) + { + return nullptr; + } + + current_child = array->child; + while ((current_child != nullptr) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return nullptr; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = nullptr; + + if ((object == nullptr) || (name == nullptr)) + { + return nullptr; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != nullptr) && (current_element->string != nullptr) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != nullptr) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == nullptr) || (current_element->string == nullptr)) { + return nullptr; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = nullptr; + if (item == nullptr) + { + return nullptr; + } + + reference = cJSON_New_Item(hooks); + if (reference == nullptr) + { + return nullptr; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = nullptr; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = nullptr; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = nullptr; + + if ((item == nullptr) || (array == nullptr) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == nullptr) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = nullptr; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = nullptr; + int new_type = cJSON_Invalid; + + if ((object == nullptr) || (string == nullptr) || (item == nullptr) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == nullptr) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != nullptr)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == nullptr) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == nullptr) || (string == nullptr)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return nullptr; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return nullptr; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == nullptr) || (item == nullptr)) + { + return nullptr; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != nullptr) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == nullptr) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = nullptr; + item->next = nullptr; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return nullptr; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = nullptr; + + if (which < 0 || newitem == nullptr) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == nullptr) + { + return add_item_to_array(array, newitem); + } + + if (after_inserted != array->child && after_inserted->prev == nullptr) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == nullptr) || (parent->child == nullptr) || (replacement == nullptr) || (item == nullptr)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != nullptr) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != nullptr) + { + replacement->prev->next = replacement; + } + if (replacement->next == nullptr) + { + parent->child->prev = replacement; + } + } + + item->next = nullptr; + item->prev = nullptr; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == nullptr) || (string == nullptr)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != nullptr)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == nullptr) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return nullptr; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != nullptr) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != nullptr) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != nullptr) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return nullptr; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = nullptr; + cJSON *p = nullptr; + cJSON *a = nullptr; + + if ((count < 0) || (numbers == nullptr)) + { + return nullptr; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return nullptr; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = nullptr; + cJSON *p = nullptr; + cJSON *a = nullptr; + + if ((count < 0) || (numbers == nullptr)) + { + return nullptr; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return nullptr; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = nullptr; + cJSON *p = nullptr; + cJSON *a = nullptr; + + if ((count < 0) || (numbers == nullptr)) + { + return nullptr; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return nullptr; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = nullptr; + cJSON *p = nullptr; + cJSON *a = nullptr; + + if ((count < 0) || (strings == nullptr)) + { + return nullptr; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return nullptr; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = nullptr; + cJSON *child = nullptr; + cJSON *next = nullptr; + cJSON *newchild = nullptr; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != nullptr) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != nullptr) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != nullptr) + { + cJSON_Delete(newitem); + } + + return nullptr; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == nullptr) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and nullptr-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == nullptr) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == nullptr) || (b == nullptr) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == nullptr) || (b->valuestring == nullptr)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != nullptr) && (b_element != nullptr);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = nullptr; + cJSON *b_element = nullptr; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == nullptr) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == nullptr) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); + object = nullptr; +} diff --git a/MobileGL/MG_Util/Config/cJSON.h b/MobileGL/MG_Util/Config/cJSON.h new file mode 100644 index 000000000..88cf0bcfa --- /dev/null +++ b/MobileGL/MG_Util/Config/cJSON.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 18 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/MobileGL/MG_Util/Config/config.cpp b/MobileGL/MG_Util/Config/config.cpp new file mode 100644 index 000000000..93a989228 --- /dev/null +++ b/MobileGL/MG_Util/Config/config.cpp @@ -0,0 +1,123 @@ +#include "config.h" + +#include "cJSON.h" +#include +#include +#include +#include +#include +#include + +#include "Includes.h" + +#define DEBUG 0 + +const char* DEFAULT_MG_DIRECTORY_PATH = "/sdcard/MGL"; + +bool is_custom_mg_dir = false; +const char* mg_directory_path = nullptr; +const char* config_file_path = nullptr; +const char* log_file_path = nullptr; +const char* glsl_cache_file_path = nullptr; + +static cJSON* config_json = nullptr; + +int initialized = 0; + +const char* concatenate(const char* str1, const char* str2) { + std::string str = std::string(str1) + str2; + char* result = new char[str.size() + 1]; + strcpy(result, str.c_str()); + return result; +} + +int check_path(void) { + if (!mg_directory_path) { + char* var = std::getenv("MGL_DIR_PATH"); + is_custom_mg_dir = var ? true : false; + mg_directory_path = var ? strdup(var) : DEFAULT_MG_DIRECTORY_PATH; + unsetenv("MGL_DIR_PATH"); + } + config_file_path = concatenate(mg_directory_path, "/config.json"); + log_file_path = concatenate(mg_directory_path, "/latest.log"); + glsl_cache_file_path = concatenate(mg_directory_path, "/glsl_cache.tmp"); + + if (mkdir(mg_directory_path, 0755) != 0 && errno != EEXIST) { + MGLOG_E("Error creating MG directory.\n"); + return 0; + } + return 1; +} + +int config_refresh(void) { + MGLOG_D("MGL_DIRECTORY_PATH=%s", mg_directory_path); + MGLOG_D("CONFIG_FILE_PATH=%s", config_file_path); + MGLOG_D("LOG_FILE_PATH=%s", log_file_path); + + FILE* file = fopen(config_file_path, "r"); + if (file == nullptr) { + MGLOG_E("Unable to open config file %s", config_file_path); + return 0; + } + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char* file_content = (char*)malloc(file_size + 1); + if (file_content == nullptr) { + MGLOG_E("Unable to allocate memory for file content"); + fclose(file); + return 0; + } + + fread(file_content, 1, file_size, file); + fclose(file); + file_content[file_size] = '\0'; + + config_json = cJSON_Parse(file_content); + free(file_content); + + if (config_json == nullptr) { + MGLOG_E("Error parsing config JSON: %s\n", cJSON_GetErrorPtr()); + return 0; + } + + initialized = 1; + return 1; +} + +int config_get_int(const char* name) { + if (config_json == nullptr) { + return -1; + } + + cJSON* item = cJSON_GetObjectItem(config_json, name); + if (item == nullptr || !cJSON_IsNumber(item)) { + MGLOG_D("Config item '%s' not found or not an integer.\n", name); + return -1; + } + + return item->valueint; +} + +const char* config_get_string(const char* name) { + if (config_json == nullptr) { + return nullptr; + } + + cJSON* item = cJSON_GetObjectItem(config_json, name); + if (item == nullptr || !cJSON_IsString(item)) { + MGLOG_D("Config item '%s' not found or not a string.\n", name); + return ""; + } + + return item->valuestring; +} + +void config_cleanup(void) { + if (config_json != nullptr) { + cJSON_Delete(config_json); + config_json = nullptr; + } +} diff --git a/MobileGL/MG_Util/Config/config.h b/MobileGL/MG_Util/Config/config.h new file mode 100644 index 000000000..913d014d1 --- /dev/null +++ b/MobileGL/MG_Util/Config/config.h @@ -0,0 +1,31 @@ +#ifndef _MOBILEGLUES_CONFIG_H_ +#define _MOBILEGLUES_CONFIG_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern const char* mg_directory_path; +extern const char* config_file_path; +extern const char* log_file_path; +extern const char* glsl_cache_file_path; + +extern int initialized; + +const char* concatenate(const char* str1, const char* str2); + +int check_path(void); + +int config_refresh(void); +int config_get_int(const char* name); +const char* config_get_string(const char* name); +void config_cleanup(void); + +#ifdef __cplusplus +} +#endif + +extern bool is_custom_mg_dir; + +#endif // _MOBILEGLUES_CONFIG_H_ diff --git a/MobileGL/MG_Util/Config/settings.cpp b/MobileGL/MG_Util/Config/settings.cpp new file mode 100644 index 000000000..8032b859c --- /dev/null +++ b/MobileGL/MG_Util/Config/settings.cpp @@ -0,0 +1,317 @@ +// +// Created by hanji on 2025/2/9. +// + +#include "settings.h" +#include "config.h" + +#include "Includes.h" + +#define DEBUG 0 + +static const char* GetEnvVar(const char *name) +{ + return getenv(name); +} + +static int GetEnvVarInt(const char *name,int *i,int def) +{ + const char *s=GetEnvVar(name); + *i=s ? atoi(s) : def; + return s!=NULL; +} + + +global_settings_t global_settings; + +void init_settings() { +#if defined(__APPLE__) + global_settings.angle = AngleMode::Disabled; + global_settings.ignore_error = IgnoreErrorLevel::Partial; + global_settings.ext_gl43 = false; + global_settings.ext_compute_shader = false; + global_settings.max_glsl_cache_size = 30 * 1024 * 1024; + global_settings.multidraw_mode = multidraw_mode_t::DrawElements; + global_settings.angle_depth_clear_fix_mode = AngleDepthClearFixMode::Disabled; + global_settings.ext_direct_state_access = true; + global_settings.custom_gl_version = {0, 0, 0}; // will go default + global_settings.fsr1_setting = FSR1_Quality_Preset::Disabled; + global_settings.hide_mg_env_level = HideMGEnvLevel::Disabled; + +#else + + int success = initialized; + if (!success) { + success = config_refresh(); + if (!success) { + MGLOG_W("Failed to load config. Use default config."); + } + } + + AngleConfig angleConfig = + success ? static_cast(config_get_int("enableANGLE")) : AngleConfig::DisableIfPossible; + NoErrorConfig noErrorConfig = + success ? static_cast(config_get_int("enableNoError")) : NoErrorConfig::Auto; + bool enableExtGL43 = success ? (config_get_int("enableExtGL43") > 0) : false; + bool enableExtComputeShader = success ? (config_get_int("enableExtComputeShader") > 0) : false; + bool enableExtTimerQuery = success ? (config_get_int("enableExtTimerQuery") > 0) : false; + bool enableExtDirectStateAccess = success ? (config_get_int("enableExtDirectStateAccess") > 0) : false; + AngleDepthClearFixMode angleDepthClearFixMode = + success ? static_cast(config_get_int("angleDepthClearFixMode")) + : AngleDepthClearFixMode::Disabled; + int customGLVersionInt = success ? config_get_int("customGLVersion") : DEFAULT_GL_VERSION; + FSR1_Quality_Preset fsr1Setting = + success ? static_cast(config_get_int("fsr1Setting")) : FSR1_Quality_Preset::Disabled; + HideMGEnvLevel hideMGEnvLevel = + success ? static_cast(config_get_int("hideMGEnvLevel")) : HideMGEnvLevel::Disabled; + + if (customGLVersionInt < 0) { + customGLVersionInt = 0; + } + + size_t maxGlslCacheSize = 0; + if (config_get_int("maxGlslCacheSize") > 0) { + maxGlslCacheSize = success ? config_get_int("maxGlslCacheSize") * 1024 * 1024 : 0; + } + + if (static_cast(angleConfig) < 0 || static_cast(angleConfig) > 3) { + angleConfig = AngleConfig::EnableIfPossible; + } + if (static_cast(noErrorConfig) < 0 || static_cast(noErrorConfig) > 3) { + noErrorConfig = NoErrorConfig::Auto; + } + if (static_cast(angleDepthClearFixMode) < 0 || + static_cast(angleDepthClearFixMode) >= static_cast(AngleDepthClearFixMode::MaxValue)) { + angleDepthClearFixMode = AngleDepthClearFixMode::Disabled; + } + if (static_cast(fsr1Setting) < 0 || + static_cast(fsr1Setting) >= static_cast(FSR1_Quality_Preset::MaxValue)) { + fsr1Setting = FSR1_Quality_Preset::Disabled; + } + if (static_cast(hideMGEnvLevel) < 0 || + static_cast(hideMGEnvLevel) >= static_cast(HideMGEnvLevel::MaxValue)) { + hideMGEnvLevel = HideMGEnvLevel::Disabled; + } + + Version customGLVersion(customGLVersionInt); + + int isInPluginApp = 0; + GetEnvVarInt("MG_PLUGIN_STATUS", &isInPluginApp, 0); + int fclVersion = 0; + GetEnvVarInt("FCL_VERSION_CODE", &fclVersion, 0); + int zlVersion = 0; + GetEnvVarInt("ZALITH_VERSION_CODE", &zlVersion, 0); + int pgwVersion = 0; + GetEnvVarInt("PGW_VERSION_CODE", &pgwVersion, 0); + + MGLOG_D("MG_DIR_PATH = %s", mg_directory_path ? mg_directory_path : "(default)"); + + AngleMode finalAngleMode = AngleMode::Disabled; + + bool isANGLESupported = true; + + switch (angleConfig) { + case AngleConfig::ForceDisable: + finalAngleMode = AngleMode::Disabled; + MGLOG_D("ANGLE: Force disabled"); + break; + + case AngleConfig::ForceEnable: + finalAngleMode = AngleMode::Enabled; + MGLOG_D("ANGLE: Force enabled"); + break; + + case AngleConfig::EnableIfPossible: { + finalAngleMode = isANGLESupported ? AngleMode::Enabled : AngleMode::Disabled; + MGLOG_D("ANGLE: Conditionally %s", (finalAngleMode == AngleMode::Enabled) ? "enabled" : "disabled"); + break; + } + + case AngleConfig::DisableIfPossible: + default: + finalAngleMode = AngleMode::Disabled; + break; + } + + global_settings.angle = finalAngleMode; + MGLOG_D("Final ANGLE setting: %d", static_cast(global_settings.angle)); + global_settings.buffer_coherent_as_flush = (global_settings.angle == AngleMode::Disabled); + + if (global_settings.angle == AngleMode::Enabled) { + // setenv("LIBGL_GLES", "libGLESv2_angle.so", 1); + // setenv("LIBGL_EGL", "libEGL_angle.so", 1); + setenv("LIBGL_EGL", "libEGL_angle.so", 1); + } else if (angleConfig == AngleConfig::EnableIfPossible) { + setenv("LIBGL_EGL", "libEGL_mesa.so", 1); + } + + switch (noErrorConfig) { + case NoErrorConfig::Level1: + global_settings.ignore_error = IgnoreErrorLevel::Partial; + MGLOG_D("Error ignoring: Level 1 (Partial)"); + break; + + case NoErrorConfig::Level2: + global_settings.ignore_error = IgnoreErrorLevel::Full; + MGLOG_D("Error ignoring: Level 2 (Full)"); + break; + + case NoErrorConfig::Auto: + case NoErrorConfig::Disable: + default: + global_settings.ignore_error = IgnoreErrorLevel::None; + MGLOG_D("Error ignoring: Disabled"); + break; + } + + global_settings.ext_gl43 = enableExtGL43; + global_settings.ext_compute_shader = enableExtComputeShader; + global_settings.ext_timer_query = enableExtTimerQuery; + global_settings.ext_direct_state_access = enableExtDirectStateAccess; + global_settings.max_glsl_cache_size = maxGlslCacheSize; + global_settings.angle_depth_clear_fix_mode = angleDepthClearFixMode; + global_settings.custom_gl_version = customGLVersion; + global_settings.fsr1_setting = fsr1Setting; + global_settings.hide_mg_env_level = hideMGEnvLevel; +#endif + + MGLOG_D("[MobileGL] Setting: enableAngle = %s", + global_settings.angle == AngleMode::Enabled ? "true" : "false"); + MGLOG_D("[MobileGL] Setting: ignoreError = %i", static_cast(global_settings.ignore_error)); + MGLOG_D("[MobileGL] Setting: enableExtComputeShader is unsupported."); + MGLOG_D("[MobileGL] Setting: enableExtGL43 = %s", global_settings.ext_gl43 ? "true" : "false"); + MGLOG_D("[MobileGL] Setting: enableExtTimerQuery is unsupported."); + MGLOG_D("[MobileGL] Setting: enableExtDirectStateAccess is unsupported."); + MGLOG_D("[MobileGL] Setting: maxGlslCacheSize is unsupported."); + MGLOG_D("[MobileGL] Setting: angleDepthClearFixMode is unsupported."); + MGLOG_D("[MobileGL] Setting: bufferCoherentAsFlush is unsupported."); + if (global_settings.custom_gl_version.isEmpty()) { + MGLOG_D("[MobileGL] Setting: customGLVersion is unsupported."); + } else { + MGLOG_D("[MobileGL] Setting: customGLVersion is unsupported."); + } + MGLOG_D("[MobileGL] Setting: fsr1Setting is unsupported."); + MGLOG_D("[MobileGL] Setting: hideMGEnvLevel is unsupported."); + + if (global_settings.ext_gl43) { + setenv("LIBGL_GL", "43", 1); + } + + if (global_settings.ignore_error != IgnoreErrorLevel::None) { + setenv("MGL_CHEAT_CHECKFRAMEBUFFERSTATUS", "true", 1); + } + + if (global_settings.ext_compute_shader) { + setenv("LIBGL_COMPUTE_SHADER", "true", 1); + } +} + +void set_multidraw_setting() { // should be called after init_gles_target() + multidraw_mode_t multidrawMode = static_cast(config_get_int("multidrawMode")); + if (static_cast(multidrawMode) == -1) { + multidrawMode = multidraw_mode_t::Auto; + } + if (static_cast(multidrawMode) < 0 || + static_cast(multidrawMode) >= static_cast(multidraw_mode_t::MaxValue)) { + multidrawMode = multidraw_mode_t::Auto; + } + global_settings.multidraw_mode = multidrawMode; + std::string draw_mode_str; + switch (global_settings.multidraw_mode) { + case multidraw_mode_t::PreferIndirect: + draw_mode_str = "Indirect"; + break; + case multidraw_mode_t::PreferBaseVertex: + draw_mode_str = "Unroll"; + break; + case multidraw_mode_t::PreferMultidrawIndirect: + draw_mode_str = "Multidraw indirect"; + break; + case multidraw_mode_t::DrawElements: + draw_mode_str = "DrawElements"; + break; + case multidraw_mode_t::Compute: + draw_mode_str = "Compute"; + break; + case multidraw_mode_t::Auto: + draw_mode_str = "Auto"; + break; + default: + draw_mode_str = "(Unknown)"; + global_settings.multidraw_mode = multidraw_mode_t::Auto; + break; + } + MGLOG_D("[MobileGL] Setting: multidrawMode is supported.", /*draw_mode_str.c_str()*/); +} + +void init_settings_post() { + bool multidraw = true; + bool basevertex = true; + bool indirect = true; + + switch (global_settings.multidraw_mode) { + case multidraw_mode_t::PreferIndirect: + if (indirect) { + MGLOG_W("LIBGL_MULTIDRAW == Indirect"); + setenv("LIBGL_MULTIDRAW", "Indirect", 1); + global_settings.multidraw_mode = multidraw_mode_t::PreferIndirect; + } else if (basevertex) { + global_settings.multidraw_mode = multidraw_mode_t::PreferBaseVertex; + } else { + global_settings.multidraw_mode = multidraw_mode_t::DrawElements; + } + break; + case multidraw_mode_t::PreferBaseVertex: + if (basevertex) { + global_settings.multidraw_mode = multidraw_mode_t::PreferBaseVertex; + setenv("LIBGL_MULTIDRAW", "Vertex", 1); + } else if (multidraw) { + global_settings.multidraw_mode = multidraw_mode_t::PreferMultidrawIndirect; + } else if (indirect) { + global_settings.multidraw_mode = multidraw_mode_t::PreferIndirect; + } else { + global_settings.multidraw_mode = multidraw_mode_t::DrawElements; + } + break; + case multidraw_mode_t::DrawElements: + global_settings.multidraw_mode = multidraw_mode_t::DrawElements; + break; + case multidraw_mode_t::Compute: + global_settings.multidraw_mode = multidraw_mode_t::Compute; + break; + case multidraw_mode_t::Auto: + break; + default: + if (basevertex) { + global_settings.multidraw_mode = multidraw_mode_t::PreferBaseVertex; + } else if (multidraw) { + global_settings.multidraw_mode = multidraw_mode_t::PreferMultidrawIndirect; + } else if (indirect) { + global_settings.multidraw_mode = multidraw_mode_t::PreferIndirect; + } else { + global_settings.multidraw_mode = multidraw_mode_t::DrawElements; + } + break; + } +} + +std::string dump_settings_string(std::string prefix) { + std::stringstream ss; + + ss << prefix << "Angle: " << (global_settings.angle == AngleMode::Enabled ? "Enabled" : "Disabled") << "\n"; + ss << prefix << "IgnoreError: "; + switch (global_settings.ignore_error) { + case IgnoreErrorLevel::None: + ss << "None"; + break; + case IgnoreErrorLevel::Partial: + ss << "Partial"; + break; + case IgnoreErrorLevel::Full: + ss << "Full"; + break; + } + ss << "\n"; + + return ss.str(); +} diff --git a/MobileGL/MG_Util/Config/settings.h b/MobileGL/MG_Util/Config/settings.h new file mode 100644 index 000000000..53acb6c78 --- /dev/null +++ b/MobileGL/MG_Util/Config/settings.h @@ -0,0 +1,180 @@ +// +// Created by hanji on 2025/2/9. +// + +#ifndef MOBILEGLUES_PLUGIN_SETTINGS_H +#define MOBILEGLUES_PLUGIN_SETTINGS_H + +#include +#include +#include +#include +#include + +#if !defined(__APPLE__) +#include <__stddef_size_t.h> +#else +typedef unsigned long size_t; +#endif + +#define DEFAULT_GL_VERSION 40 + +enum class multidraw_mode_t : int { + Auto = 0, + PreferIndirect, + PreferBaseVertex, + PreferMultidrawIndirect, + DrawElements, + Compute, + MaxValue +}; + +enum class AngleConfig : int { + DisableIfPossible = 0, + EnableIfPossible = 1, + ForceDisable = 2, + ForceEnable = 3 +}; + +enum class AngleMode : int { + Disabled = 0, + Enabled = 1 +}; + +enum class IgnoreErrorLevel : int { + None = 0, + Partial = 1, + Full = 2 +}; + +enum class NoErrorConfig : int { + Auto = 0, + Disable = 1, + Level1 = 2, + Level2 = 3 +}; + +enum class AngleDepthClearFixMode : int { + Disabled = 0, + Mode1 = 1, + Mode2 = 2, + MaxValue +}; + +enum class HideMGEnvLevel : int { + Disabled = 0, + Level1 = 1, // Hide MG extensions and randomise OpenGL version/renderer, + MaxValue +}; + +enum class FSR1_Quality_Preset : int { // may be useless + Disabled = 0, + UltraQuality, // 1 + Quality, // 2 + Balanced, // 3 + Performance, // 4 + MaxValue // 5 +}; + +struct Version { + int Major{0}; + int Minor{0}; + int Patch{0}; + + Version() = default; + + Version(int major, int minor, int patch) : Major(major), Minor(minor), Patch(patch) {} + + Version(const std::string& str) { + size_t start = str.find_first_not_of(" \t\n\r"); + size_t end = str.find_last_not_of(" \t\n\r"); + if (start == std::string::npos) { + return; + } + std::string s = str.substr(start, end - start + 1); + + std::vector parts; + std::istringstream iss(s); + std::string token; + while (std::getline(iss, token, '.') && parts.size() < 3) { + try { + parts.push_back(std::stoi(token)); + } + catch (...) { + parts.push_back(0); + } + } + while (parts.size() < 3) { + parts.push_back(0); + } + Major = parts[0]; + Minor = parts[1]; + Patch = parts[2]; + } + + explicit Version(int code) { + if (code < 0) code = -code; + std::string s = std::to_string(code); + for (size_t i = 0; i < 3; ++i) { + int digit = 0; + if (i < s.size() && std::isdigit(s[i])) { + digit = s[i] - '0'; + } + switch (i) { + case 0: + Major = digit; + break; + case 1: + Minor = digit; + break; + case 2: + Patch = digit; + break; + } + } + } + + std::string toString() const { + std::ostringstream oss; + oss << Major << '.' << Minor << '.' << Patch; + return oss.str(); + } + + int toInt(int digit_count = 3) const { + switch (digit_count) { + case 1: + return Major; + case 2: + return Major * 10 + Minor; + default: + return Major * 100 + Minor * 10 + Patch; + } + } + + bool isEmpty() const { return Major == 0 && Minor == 0 && Patch == 0; } +}; + +struct global_settings_t { + AngleMode angle; + IgnoreErrorLevel ignore_error; + bool ext_gl43; + bool ext_compute_shader; + bool ext_timer_query; + bool ext_direct_state_access; + bool buffer_coherent_as_flush; + size_t max_glsl_cache_size; + multidraw_mode_t multidraw_mode; + AngleDepthClearFixMode angle_depth_clear_fix_mode; + Version custom_gl_version; + FSR1_Quality_Preset fsr1_setting; + HideMGEnvLevel hide_mg_env_level; +}; + +extern global_settings_t global_settings; + +void init_settings(); +void init_settings_post(); +std::string dump_settings_string(std::string prefix = ""); +void set_multidraw_setting(); + +#endif // MOBILEGLUES_PLUGIN_SETTINGS_H diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 38e38c77b..9868f719e 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -16,6 +16,8 @@ #include #include +#include "../Config/EnvChecker.h" + namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { @@ -146,9 +148,9 @@ namespace MobileGL { tshader->setStrings(src, 1); tshader->setInvertY(true); if (attrib.flags & ShaderCompileBits::CompileForOpenGL) { - tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); + tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientOpenGL, 450); tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); - tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); + tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5); } else { tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); // MobileGL runtime currently creates Vulkan 1.1 instance/device on Android path, @@ -158,12 +160,31 @@ namespace MobileGL { tshader->setEnvInputVulkanRulesRelaxed(); // using EXT_vulkan_glsl_relaxed for gl_VertexID and // gl_InstanceID? } + + EShMessages messages = static_cast( + EShMsgDefault | + EShMsgRelaxedErrors + ); + + std::string preamble = + "#extension GL_ARB_separate_shader_objects : enable\n" + "#extension GL_ARB_shading_language_420pack : enable\n" + "#extension GL_ARB_explicit_attrib_location : enable\n" + "#extension GL_ARB_shader_texture_image_samples : enable\n" + "#extension GL_ARB_gpu_shader5 : enable\n" + "#extension GL_ARB_texture_cube_map_array : enable\n" + "#extension GL_ARB_shader_storage_buffer_object : enable\n" + "#extension GL_ARB_shader_image_load_store : enable\n" + "#extension GL_ARB_enhanced_layouts : enable\n" + "#extension GL_ARB_fragment_coord_conventions : enable\n"; + + tshader->setPreamble(preamble.c_str()); tshader->setAutoMapLocations(true); tshader->setAutoMapBindings(true); tshader->setGlobalUniformBlockName(GLOBAL_UBO_NAME); - if (!tshader->parse(&GetTBuiltInResourceInstance(), 460, ECoreProfile, - /*forceDefaultVersionAndProfile: */ false, - /*forwardCompatible: */ true, EShMsgDefault)) { + if (!tshader->parse(&GetTBuiltInResourceInstance(), 460, ECompatibilityProfile, + /*forceDefaultVersionAndProfile: */ true, + /*forwardCompatible: */ true, messages)) { ResultInfo r; r.log += "Error: [glslang] Cannot compile " + ConvertGLEnumToString(shaderType) + ":\n" + std::string(tshader->getInfoLog()); @@ -180,10 +201,16 @@ namespace MobileGL { program->addShader(s.get()); } - if (!program->link(EShMsgDefault)) { + EShMessages linkMessages = static_cast(EShMsgDefault | EShMsgDebugInfo); + + if (!program->link(linkMessages)) { ResultInfo r; r.log = "Error: [glslang] Cannot link the program:\n" + std::string(program->getInfoLog()); - r.errc = -3; + + MGLOG_W("glslang linker warnings: %s", program->getInfoDebugLog()); + r.log += "\nLinker Warnings:\n" + std::string(program->getInfoDebugLog()); + + r.errc = -5; return std::unexpected(r); } @@ -205,7 +232,12 @@ namespace MobileGL { if (!program->mapIO(resolver.get(), ioMapper.get())) { ResultInfo r; r.log = "Error: [glslang] Cannot mapIO:\n" + std::string(program->getInfoLog()); - r.errc = -4; + + // 打印 IO 映射警告 + MGLOG_W("glslang IO mapping warnings: %s", program->getInfoDebugLog()); + r.log += "\nIO Mapping Warnings:\n" + std::string(program->getInfoDebugLog()); + + r.errc = -6; return std::unexpected(r); } @@ -214,8 +246,10 @@ namespace MobileGL { Result>> ShaderCompiler::GetSpirvBinaryFromProgram( const ProgramBinaryAttrib& attrib) { + glslang::SpvOptions spvOptions; spvOptions.disableOptimizer = false; + spvOptions.generateDebugInfo = true; Vector> allSpirv; for (auto type : attrib.shaderTypes) { @@ -229,6 +263,7 @@ namespace MobileGL { bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector& inputBinary, Vector& outputBinary) { + using namespace spvtools; OptimizerOptions options; options.set_run_validator(false); @@ -247,6 +282,7 @@ namespace MobileGL { spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 320); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE); + spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ENABLE_420PACK_EXTENSION, SPVC_FALSE); spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); session.SetOptions(options); diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index e6c88619f..d34a6f292 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -7,6 +7,9 @@ // End of Source File Header #include "ShaderSourceProcessor.h" +#include "ShaderSourceProcessor_inline.h" + +#include "../Config/EnvChecker.h" #include #include @@ -343,6 +346,101 @@ namespace MobileGL { noperspectivePos = source.find(str_np); } + const char* str_textureQueryLod = "textureQueryLod"; + if (source.find(str_textureQueryLod) != std::string::npos && CheckEnvANGLE()) { + // 检查是否已经定义了 mg_textureQueryLod + const char* str_mg_textureQueryLod = "mg_textureQueryLod"; + if (source.find(str_mg_textureQueryLod) == std::string::npos) { + // 检查是否已经有 #define textureQueryLod + const char* str_textureQueryLodDefine = "#define textureQueryLod"; + if (source.find(str_textureQueryLodDefine) == std::string::npos) { + // 在顶部插入 textureQueryLod 实现 + const char* textureQueryLodImpl = R"( +#define textureQueryLod mg_textureQueryLod + +vec2 mg_textureQueryLod(sampler2D tex, vec2 uv) { + vec2 texSizeF = vec2(textureSize(tex, 0)); + vec2 dFdx_uv = dFdx(uv * texSizeF); + vec2 dFdy_uv = dFdy(uv * texSizeF); + float maxDerivative = max(length(dFdx_uv), length(dFdy_uv)); + float lod = log2(maxDerivative); + return vec2(lod); +} +)"; + + SizeT versionPos = source.find("#version"); + if (versionPos != std::string::npos) { + SizeT lineEnd = source.find('\n', versionPos); + if (lineEnd != std::string::npos) { + source = source.insert(lineEnd + 1, textureQueryLodImpl); + } + } + } + } + } + + // 替换attribute关键字(顶点着色器) + if (stage == ShaderStage::Vertex) { + const char* str_attribute = "attribute"; + const SizeT len_attribute = strlen(str_attribute); + SizeT attributePos = source.find(str_attribute); + while (attributePos != String::npos) { + // 检查是否是独立的关键字(前后有空格或换行) + bool isKeyword = false; + if (attributePos > 0) { + char before = source[attributePos - 1]; + if (before == ' ' || before == '\t' || before == '\n' || before == '\r') { + isKeyword = true; + } + } else { + isKeyword = true; + } + + if (isKeyword && attributePos + len_attribute < source.length()) { + char after = source[attributePos + len_attribute]; + if (after == ' ' || after == '\t' || after == '\n' || after == '\r') { + source = source.replace(attributePos, len_attribute, "in"); + attributePos = source.find(str_attribute, attributePos + 2); // 2 = strlen("in") + continue; + } + } + attributePos = source.find(str_attribute, attributePos + len_attribute); + } + } + + // 替换varying关键字 + const char* str_varying = "varying"; + const SizeT len_varying = strlen(str_varying); + SizeT varyingPos = source.find(str_varying); + while (varyingPos != String::npos) { + bool isKeyword = false; + if (varyingPos > 0) { + char before = source[varyingPos - 1]; + if (before == ' ' || before == '\t' || before == '\n' || before == '\r') { + isKeyword = true; + } + } else { + isKeyword = true; + } + + if (isKeyword && varyingPos + len_varying < source.length()) { + char after = source[varyingPos + len_varying]; + if (after == ' ' || after == '\t' || after == '\n' || after == '\r') { + // 根据着色器阶段替换 + if (stage == ShaderStage::Vertex) { + source = source.replace(varyingPos, len_varying, "out"); + varyingPos = source.find(str_varying, varyingPos + 3); // 3 = strlen("out") + } else if (stage == ShaderStage::Fragment) { + source = source.replace(varyingPos, len_varying, "in"); + varyingPos = source.find(str_varying, varyingPos + 2); // 2 = strlen("in") + } + } + } + + } + + inject_glsl_replacements(stage, source); + // force #version ShaderProfile profile = ShaderProfile::Core; SizeT versionPos = source.find("#version"); @@ -359,7 +457,7 @@ namespace MobileGL { profile = ShaderProfile::Core; } else { profile = ShaderProfile::Core; - source.insert(0, "#version 460 core\n"); + source.insert(0, "#version 460 compatibility\n"); versionPos = 0; lineEnd = source.find('\n', versionPos); } @@ -367,7 +465,7 @@ namespace MobileGL { SizeT firstLineEnd = lineEnd; if (profile != ShaderProfile::ES) { - constexpr const char* versionDirectiveCore = "#version 460 core\n"; + constexpr const char* versionDirectiveCore = "#version 460 compatibility\n"; constexpr const char* versionDirectiveCompat = "#version 460 compatibility\n"; const char* replacement = diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor_inline.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor_inline.h new file mode 100644 index 000000000..3b3e3c096 --- /dev/null +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor_inline.h @@ -0,0 +1,1110 @@ +#pragma once +#include +#include + +namespace MobileGL { + + namespace MG_Util { + namespace ShaderTranspiler { + +// 综合GLSL代码注入和替换函数 +static inline void inject_glsl_replacements(ShaderStage stage, std::string& source) { + + // ==================== 2. 注入gl_DepthRange替换 ==================== + const char* str_depthrange = "gl_DepthRange"; + if (source.find(str_depthrange) != String::npos) { + // 检查是否已经定义了mg_DepthRange + const char* str_mg_depthrange_def = "mg_DepthRangeParameters mg_DepthRange;"; + if (source.find(str_mg_depthrange_def) == String::npos) { + // 替换所有gl_DepthRange为mg_DepthRange + SizeT depthrangePos = source.find(str_depthrange); + while (depthrangePos != String::npos) { + source = source.replace(depthrangePos, strlen(str_depthrange), "mg_DepthRange"); + depthrangePos = source.find(str_depthrange); + } + + // 在#version后插入mg_DepthRange定义 + const std::string gl_DepthRangeImpl = R"( +struct mg_DepthRangeParameters { + float near; + float far; + float diff; +}; +uniform mg_DepthRangeParameters mg_DepthRange; +)"; + + // 找到插入点(在#version行之后) + SizeT versionPos = source.find("#version"); + if (versionPos != String::npos) { + SizeT lineEnd = source.find("\n", versionPos); + if (lineEnd != String::npos) { + source.insert(lineEnd + 1, "\n" + gl_DepthRangeImpl + "\n"); + } + } + } + } + + // ==================== 3. 注入subgroup_BigGiftPackage ==================== + // 检查是否使用了任何subgroup相关标识符 + bool hasSubgroupIdentifiers = false; + const char* subgroupKeywords[] = { + "subgroupBallot", "activeMask", "gl_SubgroupInvocationID", + "subgroupAdd", "gl_NumSubgroups", "gl_SubgroupID", + "subgroupAny", "subgroupAll", "subgroupElect", + "subgroupExclusiveAdd", "subgroupExclusiveMax", "gl_SubgroupSize" + }; + + for (const char* keyword : subgroupKeywords) { + if (source.find(keyword) != String::npos) { + hasSubgroupIdentifiers = true; + break; + } + } + + if (hasSubgroupIdentifiers) { + // 检查是否已经定义了SUBGROUP_SIZE + const char* str_subgroup_size = "#define SUBGROUP_SIZE"; + if (source.find(str_subgroup_size) == String::npos) { + // 注释掉所有KHR_shader_subgroup扩展 + const char* extensionKeywords[] = { + "#extension GL_KHR_shader_subgroup", + "#extension GL_KHR_shader_subgroup_basic", + "#extension GL_KHR_shader_subgroup_ballot", + "#extension GL_KHR_shader_subgroup_arithmetic", + "#extension GL_KHR_shader_subgroup_clustered" + }; + + for (const char* ext : extensionKeywords) { + SizeT extPos = source.find(ext); + while (extPos != String::npos) { + source = source.replace(extPos, strlen(ext), (std::string("// ") + ext).c_str()); + extPos = source.find(ext); + } + } + + // 替换所有subgroup相关标识符 + const char* replacePairs[][2] = { + {"subgroupBallot", "mg_subgroupBallot"}, + {"activeMask", "mg_activeMask"}, + {"gl_SubgroupInvocationID", "mg_gl_SubgroupInvocationID()"}, + {"subgroupAdd", "mg_subgroupAdd"}, + {"gl_NumSubgroups", "mg_gl_NumSubgroups()"}, + {"gl_SubgroupID", "mg_gl_SubgroupID()"}, + {"subgroupAny", "mg_subgroupAny"}, + {"subgroupAll", "mg_subgroupAll"}, + {"subgroupElect", "mg_subgroupElect"}, + {"subgroupExclusiveAdd", "mg_subgroupExclusiveAdd"}, + {"subgroupExclusiveMax", "mg_subgroupExclusiveMax"}, + {"gl_SubgroupSize", "mg_gl_SubgroupSize()"} + }; + + for (const auto& pair : replacePairs) { + const char* oldStr = pair[0]; + const char* newStr = pair[1]; + SizeT pos = source.find(oldStr); + while (pos != String::npos) { + source = source.replace(pos, strlen(oldStr), newStr); + pos = source.find(oldStr); + } + } + + // 插入subgroup_BigGiftPackage实现(完整的模拟实现代码) + const std::string subgroupImpl = R"( +#define SUBGROUP_SIZE 32 + +// ==================== 基础子组模拟 ==================== +uint mg_gl_SubgroupID() { + return gl_LocalInvocationIndex / SUBGROUP_SIZE; +} + +uint mg_gl_SubgroupInvocationID() { + return gl_LocalInvocationIndex % SUBGROUP_SIZE; +} + +uint mg_gl_NumSubgroups() { + return (gl_WorkGroupSize.x * gl_WorkGroupSize.y * gl_WorkGroupSize.z + SUBGROUP_SIZE - 1u) / SUBGROUP_SIZE; +} + +uint mg_gl_SubgroupSize() { + return SUBGROUP_SIZE; +} + +// ==================== 投票功能模拟 ==================== +shared uint s_ballot; + +uvec2 mg_subgroupBallot(bool condition) { + if (gl_LocalInvocationID.x == 0 && gl_LocalInvocationID.y == 0 && gl_LocalInvocationID.z == 0) { + s_ballot = 0u; + } + memoryBarrierShared(); + barrier(); + + if (condition) { + uint mask = 1u << mg_gl_SubgroupInvocationID(); + atomicOr(s_ballot, mask); + } + memoryBarrierShared(); + barrier(); + + return uvec2(s_ballot, 0u); +} + +uvec2 activeMask() { + return mg_subgroupBallot(true); +} + +bool mg_subgroupAny(bool value) { + uvec2 mask = mg_subgroupBallot(value); + return mask.x != 0u; +} + +bool mg_subgroupAll(bool value) { + uvec2 fullMask = activeMask(); + uvec2 valueMask = mg_subgroupBallot(value); + return fullMask == valueMask; +} + +bool mg_subgroupElect() { + return (mg_gl_SubgroupInvocationID() == 0u); +} + +// ==================== 全局共享内存声明 ==================== +const uint total_workgroup_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y * gl_WorkGroupSize.z; +const uint num_subgroups = (total_workgroup_size + SUBGROUP_SIZE - 1u) / SUBGROUP_SIZE; + +shared float s_reduceAdd_float[num_subgroups * SUBGROUP_SIZE]; +shared uint s_reduceAdd_uint[num_subgroups * SUBGROUP_SIZE]; +shared int s_reduceAdd_int[num_subgroups * SUBGROUP_SIZE]; +shared vec2 s_reduceAdd_vec2[num_subgroups * SUBGROUP_SIZE]; +shared vec3 s_reduceAdd_vec3[num_subgroups * SUBGROUP_SIZE]; +shared vec4 s_reduceAdd_vec4[num_subgroups * SUBGROUP_SIZE]; + +shared float s_exclusiveAdd_float[num_subgroups * SUBGROUP_SIZE]; +shared uint s_exclusiveAdd_uint[num_subgroups * SUBGROUP_SIZE]; +shared int s_exclusiveAdd_int[num_subgroups * SUBGROUP_SIZE]; +shared float s_exclusiveMax_float[num_subgroups * SUBGROUP_SIZE]; +shared uint s_exclusiveMax_uint[num_subgroups * SUBGROUP_SIZE]; +shared int s_exclusiveMax_int[num_subgroups * SUBGROUP_SIZE]; +shared vec2 s_exclusiveAdd_vec2[num_subgroups * SUBGROUP_SIZE]; +shared vec3 s_exclusiveAdd_vec3[num_subgroups * SUBGROUP_SIZE]; +shared vec4 s_exclusiveAdd_vec4[num_subgroups * SUBGROUP_SIZE]; +shared vec2 s_exclusiveMax_vec2[num_subgroups * SUBGROUP_SIZE]; +shared vec3 s_exclusiveMax_vec3[num_subgroups * SUBGROUP_SIZE]; +shared vec4 s_exclusiveMax_vec4[num_subgroups * SUBGROUP_SIZE]; + +// ==================== 子组加法归约模拟 ==================== +float mg_subgroupAdd(float value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_reduceAdd_float[i] = 0.0; + } + memoryBarrierShared(); + barrier(); + + s_reduceAdd_float[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = SUBGROUP_SIZE / 2; stride > 0; stride >>= 1) { + if (laneID < stride) { + s_reduceAdd_float[offset] += s_reduceAdd_float[offset + stride]; + } + memoryBarrierShared(); + barrier(); + } + + return s_reduceAdd_float[subgroupID * SUBGROUP_SIZE]; +} + +uint mg_subgroupAdd(uint value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_reduceAdd_uint[i] = 0u; + } + memoryBarrierShared(); + barrier(); + + s_reduceAdd_uint[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = SUBGROUP_SIZE / 2; stride > 0; stride >>= 1) { + if (laneID < stride) { + s_reduceAdd_uint[offset] += s_reduceAdd_uint[offset + stride]; + } + memoryBarrierShared(); + barrier(); + } + + return s_reduceAdd_uint[subgroupID * SUBGROUP_SIZE]; +} + +int mg_subgroupAdd(int value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_reduceAdd_int[i] = 0; + } + memoryBarrierShared(); + barrier(); + + s_reduceAdd_int[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = SUBGROUP_SIZE / 2; stride > 0; stride >>= 1) { + if (laneID < stride) { + s_reduceAdd_int[offset] += s_reduceAdd_int[offset + stride]; + } + memoryBarrierShared(); + barrier(); + } + + return s_reduceAdd_int[subgroupID * SUBGROUP_SIZE]; +} + +vec2 mg_subgroupAdd(vec2 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_reduceAdd_vec2[i] = vec2(0.0); + } + memoryBarrierShared(); + barrier(); + + s_reduceAdd_vec2[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = SUBGROUP_SIZE / 2; stride > 0; stride >>= 1) { + if (laneID < stride) { + s_reduceAdd_vec2[offset] += s_reduceAdd_vec2[offset + stride]; + } + memoryBarrierShared(); + barrier(); + } + + return s_reduceAdd_vec2[subgroupID * SUBGROUP_SIZE]; +} + +vec3 mg_subgroupAdd(vec3 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_reduceAdd_vec3[i] = vec3(0.0); + } + memoryBarrierShared(); + barrier(); + + s_reduceAdd_vec3[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = SUBGROUP_SIZE / 2; stride > 0; stride >>= 1) { + if (laneID < stride) { + s_reduceAdd_vec3[offset] += s_reduceAdd_vec3[offset + stride]; + } + memoryBarrierShared(); + barrier(); + } + + return s_reduceAdd_vec3[subgroupID * SUBGROUP_SIZE]; +} + +vec4 mg_subgroupAdd(vec4 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_reduceAdd_vec4[i] = vec4(0.0); + } + memoryBarrierShared(); + barrier(); + + s_reduceAdd_vec4[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = SUBGROUP_SIZE / 2; stride > 0; stride >>= 1) { + if (laneID < stride) { + s_reduceAdd_vec4[offset] += s_reduceAdd_vec4[offset + stride]; + } + memoryBarrierShared(); + barrier(); + } + + return s_reduceAdd_vec4[subgroupID * SUBGROUP_SIZE]; +} + +// ==================== 子组排他性加法模拟 ==================== +float mg_subgroupExclusiveAdd(float value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveAdd_float[i] = 0.0; + } + memoryBarrierShared(); + barrier(); + + s_exclusiveAdd_float[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + float temp = 0.0; + if (laneID >= stride) { + temp = s_exclusiveAdd_float[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveAdd_float[offset] += temp; + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return 0.0; + } else { + return s_exclusiveAdd_float[offset - 1]; + } +} + +uint mg_subgroupExclusiveAdd(uint value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveAdd_uint[i] = 0u; + } + memoryBarrierShared(); + barrier(); + + s_exclusiveAdd_uint[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + uint temp = 0u; + if (laneID >= stride) { + temp = s_exclusiveAdd_uint[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveAdd_uint[offset] += temp; + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return 0u; + } else { + return s_exclusiveAdd_uint[offset - 1]; + } +} + +int mg_subgroupExclusiveAdd(int value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveAdd_int[i] = 0; + } + memoryBarrierShared(); + barrier(); + + s_exclusiveAdd_int[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + int temp = 0; + if (laneID >= stride) { + temp = s_exclusiveAdd_int[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveAdd_int[offset] += temp; + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return 0; + } else { + return s_exclusiveAdd_int[offset - 1]; + } +} + +vec2 mg_subgroupExclusiveAdd(vec2 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveAdd_vec2[i] = vec2(0.0); + } + memoryBarrierShared(); + barrier(); + + s_exclusiveAdd_vec2[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + vec2 temp = vec2(0.0); + if (laneID >= stride) { + temp = s_exclusiveAdd_vec2[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveAdd_vec2[offset] += temp; + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return vec2(0.0); + } else { + return s_exclusiveAdd_vec2[offset - 1]; + } +} + +vec3 mg_subgroupExclusiveAdd(vec3 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveAdd_vec3[i] = vec3(0.0); + } + memoryBarrierShared(); + barrier(); + + s_exclusiveAdd_vec3[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + vec3 temp = vec3(0.0); + if (laneID >= stride) { + temp = s_exclusiveAdd_vec3[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveAdd_vec3[offset] += temp; + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return vec3(0.0); + } else { + return s_exclusiveAdd_vec3[offset - 1]; + } +} + +vec4 mg_subgroupExclusiveAdd(vec4 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveAdd_vec4[i] = vec4(0.0); + } + memoryBarrierShared(); + barrier(); + + s_exclusiveAdd_vec4[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + vec4 temp = vec4(0.0); + if (laneID >= stride) { + temp = s_exclusiveAdd_vec4[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveAdd_vec4[offset] += temp; + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return vec4(0.0); + } else { + return s_exclusiveAdd_vec4[offset - 1]; + } +} + +// ==================== 子组排他性最大值模拟 ==================== +float mg_subgroupExclusiveMax(float value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveMax_float[i] = -1.0 / 0.0; + } + memoryBarrierShared(); + barrier(); + + s_exclusiveMax_float[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + float temp = -1.0 / 0.0; + if (laneID >= stride) { + temp = s_exclusiveMax_float[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveMax_float[offset] = max(s_exclusiveMax_float[offset], temp); + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return -1.0 / 0.0; + } else { + return s_exclusiveMax_float[offset - 1]; + } +} + +uint mg_subgroupExclusiveMax(uint value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveMax_uint[i] = 0u; + } + memoryBarrierShared(); + barrier(); + + s_exclusiveMax_uint[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + uint temp = 0u; + if (laneID >= stride) { + temp = s_exclusiveMax_uint[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveMax_uint[offset] = max(s_exclusiveMax_uint[offset], temp); + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return 0u; + } else { + return s_exclusiveMax_uint[offset - 1]; + } +} + +int mg_subgroupExclusiveMax(int value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveMax_int[i] = (-1 << 31); + } + memoryBarrierShared(); + barrier(); + + s_exclusiveMax_int[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + int temp = (-1 << 31); + if (laneID >= stride) { + temp = s_exclusiveMax_int[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveMax_int[offset] = max(s_exclusiveMax_int[offset], temp); + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return (-1 << 31); + } else { + return s_exclusiveMax_int[offset - 1]; + } +} + +vec2 mg_subgroupExclusiveMax(vec2 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveMax_vec2[i] = vec2(-1.0 / 0.0); + } + memoryBarrierShared(); + barrier(); + + s_exclusiveMax_vec2[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + vec2 temp = vec2(-1.0 / 0.0); + if (laneID >= stride) { + temp = s_exclusiveMax_vec2[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveMax_vec2[offset] = max(s_exclusiveMax_vec2[offset], temp); + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return vec2(-1.0 / 0.0); + } else { + return s_exclusiveMax_vec2[offset - 1]; + } +} + +vec3 mg_subgroupExclusiveMax(vec3 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveMax_vec3[i] = vec3(-1.0 / 0.0); + } + memoryBarrierShared(); + barrier(); + + s_exclusiveMax_vec3[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + vec3 temp = vec3(-1.0 / 0.0); + if (laneID >= stride) { + temp = s_exclusiveMax_vec3[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveMax_vec3[offset] = max(s_exclusiveMax_vec3[offset], temp); + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return vec3(-1.0 / 0.0); + } else { + return s_exclusiveMax_vec3[offset - 1]; + } +} + +vec4 mg_subgroupExclusiveMax(vec4 value) { + uint subgroupID = mg_gl_SubgroupID(); + uint laneID = mg_gl_SubgroupInvocationID(); + uint offset = subgroupID * SUBGROUP_SIZE + laneID; + + for (uint i = gl_LocalInvocationIndex; i < num_subgroups * SUBGROUP_SIZE; i += total_workgroup_size) { + s_exclusiveMax_vec4[i] = vec4(-1.0 / 0.0); + } + memoryBarrierShared(); + barrier(); + + s_exclusiveMax_vec4[offset] = value; + memoryBarrierShared(); + barrier(); + + for (uint stride = 1; stride < SUBGROUP_SIZE; stride <<= 1) { + vec4 temp = vec4(-1.0 / 0.0); + if (laneID >= stride) { + temp = s_exclusiveMax_vec4[offset - stride]; + } + memoryBarrierShared(); + barrier(); + if (laneID >= stride) { + s_exclusiveMax_vec4[offset] = max(s_exclusiveMax_vec4[offset], temp); + } + memoryBarrierShared(); + barrier(); + } + + if (laneID == 0) { + return vec4(-1.0 / 0.0); + } else { + return s_exclusiveMax_vec4[offset - 1]; + } +} +)"; + + // 在uniform声明后或void main()前插入 + SizeT mainPos = source.find("void main()"); + if (mainPos != String::npos) { + // 在main函数前插入 + source.insert(mainPos, "\n" + subgroupImpl + "\n"); + } else { + // 否则在文件末尾插入 + source += "\n" + subgroupImpl + "\n"; + } + } + } + + // ==================== 4. 注入subgroup_clustered ==================== + // 检查是否使用了subgroupClustered相关标识符 + bool hasClusteredIdentifiers = false; + const char* clusteredKeywords[] = { + "subgroupClusteredMax", "subgroupMemoryBarrier", "subgroupBarrier", + "subgroupClusteredAllEqual", "subgroupClusteredAny", "subgroupClusteredAll", + "subgroupClusteredXor", "subgroupClusteredOr", "subgroupClusteredAnd", + "subgroupClusteredMin", "subgroupClusteredMul", "subgroupClusteredAdd" + }; + + for (const char* keyword : clusteredKeywords) { + if (source.find(keyword) != String::npos) { + hasClusteredIdentifiers = true; + break; + } + } + + if (hasClusteredIdentifiers) { + // 检查是否已经定义了_cluster_shared_data + const char* str_cluster_data = "_cluster_shared_data["; + if (source.find(str_cluster_data) == String::npos) { + // 注释掉所有clustered扩展 + const char* extensionVariants[] = { + "#extension GL_KHR_shader_subgroup_clustered :enable", + "#extension GL_KHR_shader_subgroup_clustered : enable", + "#extension GL_KHR_shader_subgroup_clustered: enable", + "#extension GL_KHR_shader_subgroup_clustered : require", + "#extension GL_KHR_shader_subgroup_clustered :require" + }; + + for (const char* ext : extensionVariants) { + SizeT extPos = source.find(ext); + while (extPos != String::npos) { + source = source.replace(extPos, strlen(ext), (std::string("// ") + ext).c_str()); + extPos = source.find(ext); + } + } + + // 替换所有clustered相关标识符 + const char* clusteredReplacePairs[][2] = { + {"subgroupClusteredMax", "mg_subgroupClusteredMax"}, + {"subgroupMemoryBarrier", "mg_subgroupMemoryBarrier"}, + {"subgroupBarrier", "mg_subgroupBarrier"}, + {"subgroupClusteredAllEqual", "mg_subgroupClusteredAllEqual"}, + {"subgroupClusteredAny", "mg_subgroupClusteredAny"}, + {"subgroupClusteredAll", "mg_subgroupClusteredAll"}, + {"subgroupClusteredXor", "mg_subgroupClusteredXor"}, + {"subgroupClusteredOr", "mg_subgroupClusteredOr"}, + {"subgroupClusteredAnd", "mg_subgroupClusteredAnd"}, + {"subgroupClusteredMin", "mg_subgroupClusteredMin"}, + {"subgroupClusteredMul", "mg_subgroupClusteredMul"}, + {"subgroupClusteredAdd", "mg_subgroupClusteredAdd"} + }; + + for (const auto& pair : clusteredReplacePairs) { + const char* oldStr = pair[0]; + const char* newStr = pair[1]; + SizeT pos = source.find(oldStr); + while (pos != String::npos) { + source = source.replace(pos, strlen(oldStr), newStr); + pos = source.find(oldStr); + } + } + + // 插入subgroup_clustered实现 + const std::string clusteredImpl = R"( +precision highp float; +precision highp int; + +shared uint _cluster_shared_data[gl_WorkGroupSize.x * gl_WorkGroupSize.y * gl_WorkGroupSize.z]; + +uint _get_linear_index() { + return gl_LocalInvocationID.x; +} + +uint _clustered_reduce(uint value, uint clusterSize, uint op) { + uint idx = _get_linear_index(); + uint clusterIdx = idx / clusterSize; + uint offset = idx % clusterSize; + uint base = clusterIdx * clusterSize; + + _cluster_shared_data[idx] = value; + barrier(); + memoryBarrierShared(); + + for (uint stride = 1; stride < clusterSize; stride *= 2) { + if ((offset & (2u * stride - 1u)) == 0u) { + uint otherIdx = idx + stride; + if (offset + stride < clusterSize) { + uint a = _cluster_shared_data[idx]; + uint b = _cluster_shared_data[otherIdx]; + + switch (op) { + case 0: a += b; break; + case 1: a *= b; break; + case 2: a = min(a, b); break; + case 3: a = max(a, b); break; + case 4: a &= b; break; + case 5: a |= b; break; + case 6: a ^= b; break; + default: break; + } + _cluster_shared_data[idx] = a; + } + } + barrier(); + memoryBarrierShared(); + } + return _cluster_shared_data[base]; +} + +float mg_subgroupClusteredAdd(float val, uint clusterSize) { + uint u = floatBitsToUint(val); + u = _clustered_reduce(u, clusterSize, 0); + return uintBitsToFloat(u); +} + +float mg_subgroupClusteredMul(float val, uint clusterSize) { + uint u = floatBitsToUint(val); + u = _clustered_reduce(u, clusterSize, 1); + return uintBitsToFloat(u); +} + +float mg_subgroupClusteredMin(float val, uint clusterSize) { + uint u = floatBitsToUint(val); + u = _clustered_reduce(u, clusterSize, 2); + return uintBitsToFloat(u); +} + +float mg_subgroupClusteredMax(float val, uint clusterSize) { + uint u = floatBitsToUint(val); + u = _clustered_reduce(u, clusterSize, 3); + return uintBitsToFloat(u); +} + +uint mg_subgroupClusteredAnd(uint val, uint clusterSize) { + return _clustered_reduce(val, clusterSize, 4); +} + +uint mg_subgroupClusteredOr(uint val, uint clusterSize) { + return _clustered_reduce(val, clusterSize, 5); +} + +uint mg_subgroupClusteredXor(uint val, uint clusterSize) { + return _clustered_reduce(val, clusterSize, 6); +} + +bool mg_subgroupClusteredAll(bool condition, uint clusterSize) { + uint val = condition ? 0xFFFFFFFFu : 0u; + uint result = _clustered_reduce(val, clusterSize, 4); + return (result == 0xFFFFFFFFu); +} + +bool mg_subgroupClusteredAny(bool condition, uint clusterSize) { + uint val = condition ? 0xFFFFFFFFu : 0u; + uint result = _clustered_reduce(val, clusterSize, 5); + return (result != 0u); +} + +bool mg_subgroupClusteredAllEqual(float value, uint clusterSize) { + uint idx = _get_linear_index(); + uint clusterIdx = idx / clusterSize; + uint offset = idx % clusterSize; + uint base = clusterIdx * clusterSize; + + _cluster_shared_data[idx] = floatBitsToUint(value); + barrier(); + memoryBarrierShared(); + + uint ref = _cluster_shared_data[base]; + bool equal = (floatBitsToUint(value) == ref); + uint u = equal ? 0xFFFFFFFFu : 0u; + uint result = _clustered_reduce(u, clusterSize, 4); + + return (result == 0xFFFFFFFFu); +} + +void mg_subgroupBarrier() { + barrier(); + memoryBarrierShared(); +} + +void mg_subgroupMemoryBarrier() { + memoryBarrierShared(); +} +)"; + + // 在subgroup_BigGiftPackage实现之后或void main()前插入 + SizeT mainPos2 = source.find("void main()"); + if (mainPos2 != String::npos) { + source.insert(mainPos2, "\n" + clusteredImpl + "\n"); + } else { + source += "\n" + clusteredImpl + "\n"; + } + } + } + + const char* arbKeywords[] = { + "gl_DrawIDARB", + "gl_BaseVertexARB", + "gl_BaseInstanceARB" + }; + + const char* standardKeywords[] = { + "gl_DrawID", + "gl_BaseVertex", + "gl_BaseInstance" + }; + + // 直接替换ARB后缀为标准名 + for (int i = 0; i < 3; i++) { + size_t pos = 0; + while ((pos = source.find(arbKeywords[i], pos)) != String::npos) { + // 简单检查一下是不是独立标识符 + bool isWord = true; + if (pos > 0) { + char prev = source[pos - 1]; + isWord = !(isalnum(prev) || prev == '_'); + } + if (isWord && pos + strlen(arbKeywords[i]) < source.length()) { + char next = source[pos + strlen(arbKeywords[i])]; + isWord = !(isalnum(next) || next == '_'); + } + + if (isWord) { + source.replace(pos, strlen(arbKeywords[i]), standardKeywords[i]); + pos += strlen(standardKeywords[i]); + } else { + pos += strlen(arbKeywords[i]); + } + } + } + + // ==================== 6. 注入temporal_filter ==================== + const char* str_temporal_filter = "GI_TemporalFilter"; + if (source.find(str_temporal_filter) != String::npos) { + // 检查是否已经定义了GI_TemporalFilter函数 + const char* str_temporal_filter_def = "vec4 GI_TemporalFilter()"; + const char* str_GI_RSM_def = "#define GI_RSM"; + if (source.find(str_temporal_filter_def) == String::npos && source.find(str_GI_RSM_def) == String::npos) { + const std::string temporalFilterImpl = R"( +#define GI_RSM + +)"; + + // 在uniform声明后或void main()前插入 + SizeT mainPos3 = source.find("void main()"); + if (mainPos3 != String::npos) { + source.insert(mainPos3, "\n" + temporalFilterImpl + "\n"); + } else { + source += "\n" + temporalFilterImpl + "\n"; + } + } + } + + // 替换texture2D为texture + size_t pos = 0; + while ((pos = source.find("texture2D", pos)) != String::npos) { + source.replace(pos, 9, "texture"); + pos += 7; // "texture"的长度 + } + + // 为transpose()函数添加polyfill实现 + const std::string transposeTarget = "const mat3 rotInverse = transpose(rot);"; + const std::string transposeReplacement = "const mat3 rotInverse = mat3(rot[0][0], rot[1][0], rot[2][0], rot[0][1], rot[1][1], rot[2][1], rot[0][2], rot[1][2], rot[2][2]);"; + + pos = source.find(transposeTarget); + if (pos != String::npos) { + source.replace(pos, transposeTarget.length(), transposeReplacement); + } + + // 修复vec3数组声明问题 + const std::string vec3ArrayTarget = "vec3[3](vWorldPos[0] - vWorldPos[1]"; + const std::string vec3ArrayReplacement = "vec4[3](vWorldPos[0] - vWorldPos[1]"; + + pos = source.find(vec3ArrayTarget); + if (pos != String::npos) { + source.replace(pos, vec3ArrayTarget.length(), vec3ArrayReplacement); + } + + // 初始化未初始化的变量 + const std::string reflectionTarget = "vec3 reflection;"; + const std::string reflectionReplacement = "vec3 reflection=vec3(0,0,0);"; + + pos = source.find(reflectionTarget); + if (pos != String::npos) { + source.replace(pos, reflectionTarget.length(), reflectionReplacement); + } + + // 注释掉#error指令以避免编译中断 + pos = 0; + while ((pos = source.find("#error ", pos)) != String::npos) { + source.replace(pos, 7, "// #error "); + pos += 10; // "// #error "的长度 + } + + // 注释#extension指令以避免编译中断 + /*pos = 0; + while ((pos = source.find("#extension ", pos)) != String::npos) { + source.replace(pos, 11, "// #extension "); + pos += 14; // "// #extension "的长度 + }*/ + + + +} + + + +} +} +} diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp index 11ef61880..6c4cd4dad 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp @@ -31,7 +31,7 @@ namespace MobileGL { analysis::TypeManager* type_mgr = context()->get_type_mgr(); // 2. Import `GLSL.std.450` extension ID (for abs() func) - uint32_t glsl_std_450_id = context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); + std::uint32_t glsl_std_450_id = context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); if (glsl_std_450_id == 0) { return Status::SuccessWithoutChange; } @@ -65,10 +65,10 @@ namespace MobileGL { // check if operand is "float 0.0" // OpFOrdEqual ResultType ResultID Operand1 Operand2 - uint32_t op1_id = inst.GetSingleWordInOperand(0); - uint32_t op2_id = inst.GetSingleWordInOperand(1); + std::uint32_t op1_id = inst.GetSingleWordInOperand(0); + std::uint32_t op2_id = inst.GetSingleWordInOperand(1); - uint32_t var_id = 0; + std::uint32_t var_id = 0; auto is_float_zero = [&](uint32_t id) -> bool { const analysis::Constant* c = const_mgr->FindDeclaredConstant(id); @@ -91,13 +91,13 @@ namespace MobileGL { MGLOG_D("Found 1 occurrence of `FloatEqualsZero`, patching"); // 1. Get var type (Float) and result type (Bool) - uint32_t float_type_id = def_use_mgr->GetDef(var_id)->type_id(); - uint32_t bool_type_id = inst.type_id(); + std::uint32_t float_type_id = def_use_mgr->GetDef(var_id)->type_id(); + std::uint32_t bool_type_id = inst.type_id(); // 2. Create constant ID for `Epsilon` const analysis::Constant* eps_const = const_mgr->GetConstant( type_mgr->GetType(float_type_id), {*(reinterpret_cast(&K_EPSILON))}); - uint32_t eps_id = const_mgr->GetDefiningInstruction(eps_const)->result_id(); + std::uint32_t eps_id = const_mgr->GetDefiningInstruction(eps_const)->result_id(); // 3. Build Abs(x) inst // OpExtInst %float_type %glsl_import Abs %x @@ -149,4 +149,4 @@ namespace MobileGL { } } // namespace ShaderTranspiler } // namespace MG_Util -} // namespace MobileGL \ No newline at end of file +} // namespace MobileGL diff --git a/MobileGL/MG_Util/Types.h b/MobileGL/MG_Util/Types.h index aff143de7..7e6088283 100644 --- a/MobileGL/MG_Util/Types.h +++ b/MobileGL/MG_Util/Types.h @@ -8,6 +8,8 @@ #pragma once +#include +#include #include #include "GLExtensions.h" @@ -20,16 +22,16 @@ namespace MobileGL { using String = std::string; using StringStream = std::stringstream; - using Int8 = int8_t; - using Uint8 = uint8_t; - using Int16 = int16_t; - using Uint16 = uint16_t; - using Int32 = int32_t; - using Uint32 = uint32_t; + using Int8 = std::int8_t; + using Uint8 = std::uint8_t; + using Int16 = std::int16_t; + using Uint16 = std::uint16_t; + using Int32 = std::int32_t; + using Uint32 = std::uint32_t; using Int = Int32; using Uint = Uint32; - using Int64 = int64_t; - using Uint64 = uint64_t; + using Int64 = std::int64_t; + using Uint64 = std::uint64_t; using Bool = bool; using Float = float; using Double = double; @@ -55,9 +57,56 @@ namespace MobileGL { using SizeT = std::size_t; template using Array = std::array; - template , class KeyEqual = std::equal_to, - class Allocator = std::allocator>> - using UnorderedMap = FastSTL::unordered_map; +#if UseFastSTL +#include +template < + typename Key, + typename T, + class Hash = std::hash, + class KeyEqual = std::equal_to, + class Allocator = std::allocator> +> +using UnorderedMap = std::unordered_map; + +#elif UseAnkerl +#include + +template < + typename Key, + typename T, + class Hash = ankerl::unordered_dense::hash, + class KeyEqual = std::equal_to, + class Allocator = std::allocator> +> +using UnorderedMap = ankerl::unordered_dense::map; + +#elif UseStandard +#include +template < + typename Key, + typename T +> +using UnorderedMap = std::unordered_map; +#elif UseABSL +/*template < + typename Key, + typename T, + class Hash = absl::container_internal::hash_default_hash, + class KeyEqual = absl::container_internal::hash_default_eq, + class Allocator = std::allocator> +>*/ +template < + typename Key, + typename T, + class Hash= std::hash, + class KeyEqual = std::equal_to, + class Allocator = std::allocator> +> +using UnorderedMap = absl::flat_hash_map; +#else +#error The type of UnorderedMap to be used is not defined! +#endif + template inline constexpr std::remove_reference_t&& Move(T&& t) noexcept { return static_cast&&>(t); diff --git a/MobileGL/mgl-version.script b/MobileGL/mgl-version.script new file mode 100644 index 000000000..2b3f994a4 --- /dev/null +++ b/MobileGL/mgl-version.script @@ -0,0 +1,7 @@ +{ + global: + glX*; + egl*; + local: + *; +}; diff --git a/android_ci_build.bash b/android_ci_build.bash new file mode 100644 index 000000000..e761c78c3 --- /dev/null +++ b/android_ci_build.bash @@ -0,0 +1,20 @@ +#!/bin/bash +# set -e + +cd ./3rdparty/glslang +python ./update_glslang_sources.py +cd ../.. + +# git clone --depth 1 https://github.com/aaaapai/FastSTL.git ./include/FastSTL +cmake_build () { + ANDROID_ABI=$1 + mkdir -p build + cd build + cmake $GITHUB_WORKSPACE -DANDROID_PLATFORM=26 -DANDROID_ABI=$ANDROID_ABI -DCMAKE_ANDROID_STL_TYPE=c++_static -DCMAKE_SYSTEM_NAME=Android -DANDROID_TOOLCHAIN=clang -DCMAKE_MAKE_PROGRAM=$ANDROID_NDK_LATEST_HOME/prebuilt/linux-x86_64/bin/make -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake -DThreads_FOUND=ON -DCMAKE_THREAD_LIBS_INIT="-pthread" -DCMAKE_USE_PTHREADS_INIT=ON + cmake --build . --config Release --parallel 6 + # 在bash中启用globstar + shopt -s globstar + $ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip $GITHUB_WORKSPACE/**/libMobileGL.so +} + +cmake_build arm64-v8a diff --git a/libraries/android/arm64-v8a/libjemalloc.so b/libraries/android/arm64-v8a/libjemalloc.so new file mode 100644 index 000000000..a3482537f Binary files /dev/null and b/libraries/android/arm64-v8a/libjemalloc.so differ diff --git a/update.bash b/update.bash new file mode 100755 index 000000000..34206a614 --- /dev/null +++ b/update.bash @@ -0,0 +1,35 @@ +#!/bin/bash + +# 配置 Git 用户 +git config --global user.name "GitHub Actions" +git config --global user.email "actions@github.com" + +# 只初始化 3rdparty 下的子模块 +echo "初始化 3rdparty 子模块..." +git submodule update --init 3rdparty/* + +# 进入 3rdparty 目录更新子模块 +cd 3rdparty +for dir in */; do + if [ -d "$dir/.git" ]; then + echo "更新子模块: $dir" + cd "$dir" + git pull origin $(git branch --show-current || echo "main") + cd .. + fi +done +cd .. + +# 更新父仓库中的子模块引用 +echo "更新父仓库中的子模块引用..." +git submodule update --remote --recursive 3rdparty/* + +# 提交更新 +git add --all + +if ! git diff-index --quiet HEAD --; then + git commit -m "Automated 3rdparty submodule update $(date)" + git push +else + echo "没有 3rdparty 子模块更新" +fi