From 678351afa0a6f95b9e0a74d13abd7fa0dafcbd61 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:10:42 +0800 Subject: [PATCH 001/116] Update benchmark.yml --- .github/workflows/benchmark.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 395dd8c16..e8a067a21 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,11 +1,7 @@ name: Benchmark on: - push: - branches: - - dev - - Feat/Backend-Direct-GLES - + workflow_dispatch: jobs: benchmark: runs-on: ubuntu-latest From 56e878c18f476415fbbb0972a3a13f768be6f2d8 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:11:04 +0800 Subject: [PATCH 002/116] Update test.yml --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac316d624..e7dbebf47 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,7 @@ name: Test on: - push: - branches: - - dev - - Feat/Backend-Direct-GLES + workflow_dispatch: jobs: test: From e788528e4920e2d30d9f925c8fc53157840c16e6 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:17:45 +0800 Subject: [PATCH 003/116] Create dry-run.yml --- .github/workflows/dry-run.yml | 120 ++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 .github/workflows/dry-run.yml diff --git a/.github/workflows/dry-run.yml b/.github/workflows/dry-run.yml new file mode 100644 index 000000000..b51ef185e --- /dev/null +++ b/.github/workflows/dry-run.yml @@ -0,0 +1,120 @@ +name: Clean All Workflows - Keep Last 1 Day + +on: + workflow_dispatch: + inputs: + dry_run: + description: '仅查看要删除的运行(不实际删除)' + required: true + default: 'false' + type: boolean + +jobs: + clean: + runs-on: ubuntu-latest + + permissions: + actions: write + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Delete all workflow runs older than 1 day + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + DRY_RUN: ${{ github.event.inputs.dry_run }} + run: | + # 计算1天前的日期 + CUTOFF_DATE=$(date -d "1 day ago" +%s) + + echo "清理配置:" + echo " 仓库: $REPO" + echo " 保留: 最近1天的工作流" + echo " 模拟运行: $DRY_RUN" + echo " 截止时间: $(date -d @$CUTOFF_DATE)" + echo "" + + # 获取所有工作流运行 + PAGE=1 + TOTAL_DELETED=0 + TOTAL_SKIPPED=0 + + while true; do + echo "获取第 $PAGE 页数据..." + + # 获取一页运行记录 + RESPONSE=$(gh api repos/$REPO/actions/runs?per_page=100\&page=$PAGE) + + # 检查是否有更多数据 + RUNS_COUNT=$(echo "$RESPONSE" | jq '.workflow_runs | length') + if [ "$RUNS_COUNT" -eq "0" ]; then + break + fi + + # 处理当前页的每个运行 + echo "$RESPONSE" | jq -c '.workflow_runs[]' | while read -r run; do + RUN_ID=$(echo "$run" | jq -r '.id') + RUN_NAME=$(echo "$run" | jq -r '.name // "未命名"') + RUN_DATE=$(echo "$run" | jq -r '.created_at') + RUN_TIMESTAMP=$(date -d "$RUN_DATE" +%s) + RUN_STATUS=$(echo "$run" | jq -r '.status') + RUN_WORKFLOW=$(echo "$run" | jq -r '.workflow_id') + + # 检查运行状态 + if [ "$RUN_STATUS" = "in_progress" ] || [ "$RUN_STATUS" = "queued" ] || [ "$RUN_STATUS" = "pending" ]; then + echo "⏳ 跳过进行中的运行 #$RUN_ID - $RUN_NAME (状态: $RUN_STATUS)" + TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1)) + continue + fi + + # 检查是否在1天内 + if [ "$RUN_TIMESTAMP" -gt "$CUTOFF_DATE" ]; then + echo "✅ 保留1天内的运行 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" + TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1)) + continue + fi + + # 删除超过1天的运行 + if [ "$DRY_RUN" = "true" ]; then + echo "🔍 [模拟] 将删除运行 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" + else + echo "🗑️ 正在删除运行 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" + if gh api -X DELETE repos/$REPO/actions/runs/$RUN_ID; then + echo " ✓ 删除成功" + else + echo " ✗ 删除失败" + fi + fi + TOTAL_DELETED=$((TOTAL_DELETED + 1)) + + # 添加小延迟避免API限流 + sleep 0.5 + done + + # 检查是否有下一页 + HAS_NEXT=$(echo "$RESPONSE" | jq '.total_count > 100 * '"$PAGE") + if [ "$HAS_NEXT" != "true" ]; then + break + fi + + PAGE=$((PAGE + 1)) + done + + echo "" + echo "========== 清理完成 ==========" + echo "总删除数: $TOTAL_DELETED" + echo "总保留数: $TOTAL_SKIPPED" + + if [ "$DRY_RUN" = "true" ]; then + echo "" + echo "⚠️ 这是模拟运行,没有实际删除任何内容" + echo "如需实际删除,请将 dry_run 设置为 false" + fi From b33db6c3caa7762f1927f64bf564ab39c68407e1 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:41:18 +0800 Subject: [PATCH 004/116] Update and rename dry-run.yml to dry-run.yaml --- .github/workflows/dry-run.yaml | 196 +++++++++++++++++++++++++++++++++ .github/workflows/dry-run.yml | 120 -------------------- 2 files changed, 196 insertions(+), 120 deletions(-) create mode 100644 .github/workflows/dry-run.yaml delete mode 100644 .github/workflows/dry-run.yml diff --git a/.github/workflows/dry-run.yaml b/.github/workflows/dry-run.yaml new file mode 100644 index 000000000..dacbeb9a7 --- /dev/null +++ b/.github/workflows/dry-run.yaml @@ -0,0 +1,196 @@ +name: Clean All Branches Workflows - Keep Last 1 Day + +on: + workflow_dispatch: + inputs: + dry_run: + description: '模拟运行(只查看不删除)' + required: true + default: 'true' + type: boolean + confirm_delete: + description: 'dry_run=false时,输入"YES"确认删除' + required: true + default: '' + type: string + concurrency: + description: '并发数(默认10)' + required: true + default: '10' + type: string + target_branch: + description: '指定分支(留空则处理所有分支)' + required: false + default: '' + type: string + +jobs: + clean: + runs-on: ubuntu-latest + + permissions: + actions: write + contents: read + + steps: + - name: Check confirmation + if: ${{ github.event.inputs.dry_run == 'false' && github.event.inputs.confirm_delete != 'YES' }} + run: | + echo "❌ 操作取消:未确认删除" + echo "dry_run=false 时需要输入 confirm_delete=YES" + exit 1 + + - name: Clean workflow runs from all branches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + DRY_RUN: ${{ github.event.inputs.dry_run }} + CONCURRENCY: ${{ github.event.inputs.concurrency }} + TARGET_BRANCH: ${{ github.event.inputs.target_branch }} + run: | + # 计算1天前的日期 + CUTOFF_DATE=$(date -d "1 day ago" -u +"%Y-%m-%dT%H:%M:%SZ") + echo "🔍 清理配置:" + echo " 仓库: $REPO" + if [ -n "$TARGET_BRANCH" ]; then + echo " 目标分支: $TARGET_BRANCH" + else + echo " 目标分支: 所有分支" + fi + echo " 保留: 1天内的运行(<$CUTOFF_DATE 的将被删除)" + echo " 模拟运行: $DRY_RUN" + echo " 并发数: $CONCURRENCY" + echo "" + + # 获取所有分支(如果不指定分支) + if [ -z "$TARGET_BRANCH" ]; then + echo "📋 获取所有分支..." + BRANCHES=$(gh api repos/$REPO/branches --paginate | jq -r '.[].name') + BRANCH_COUNT=$(echo "$BRANCHES" | wc -l) + echo "找到 $BRANCH_COUNT 个分支" + else + BRANCHES="$TARGET_BRANCH" + echo "仅处理指定分支: $TARGET_BRANCH" + fi + + TOTAL_DELETED=0 + TOTAL_SKIPPED=0 + TOTAL_RUNNING=0 + ALL_RUNS_TO_DELETE=() + + # 遍历每个分支 + for BRANCH in $BRANCHES; do + echo -e "\n📦 处理分支: $BRANCH" + + PAGE=1 + BRANCH_DELETED=0 + BRANCH_SKIPPED=0 + BRANCH_RUNNING=0 + + while true; do + # 获取该分支的工作流运行 + echo " 获取第 $PAGE 页..." + RESPONSE=$(gh api repos/$REPO/actions/runs \ + --method GET \ + -F branch="$BRANCH" \ + -F per_page=100 \ + -F page=$PAGE \ + --paginate) + + # 检查是否有运行 + RUNS_COUNT=$(echo "$RESPONSE" | jq '.workflow_runs | length' 2>/dev/null || echo "0") + if [ "$RUNS_COUNT" -eq "0" ]; then + break + fi + + # 处理每个运行 + echo "$RESPONSE" | jq -c '.workflow_runs[]' 2>/dev/null | while read -r run; do + RUN_ID=$(echo "$run" | jq -r '.id') + RUN_NAME=$(echo "$run" | jq -r '.name // "未命名"') + RUN_DATE=$(echo "$run" | jq -r '.created_at') + RUN_STATUS=$(echo "$run" | jq -r '.status') + RUN_BRANCH=$(echo "$run" | jq -r '.head_branch') + RUN_WORKFLOW=$(echo "$run" | jq -r '.workflow_id') + + # 检查运行状态 + if [ "$RUN_STATUS" = "in_progress" ] || [ "$RUN_STATUS" = "queued" ] || [ "$RUN_STATUS" = "pending" ]; then + echo " ⏳ 跳过进行中的运行 #$RUN_ID - $RUN_NAME (状态: $RUN_STATUS)" + BRANCH_RUNNING=$((BRANCH_RUNNING + 1)) + TOTAL_RUNNING=$((TOTAL_RUNNING + 1)) + continue + fi + + # 检查日期 + if [[ "$RUN_DATE" > "$CUTOFF_DATE" ]]; then + echo " ✅ 保留1天内的运行 #$RUN_ID (日期: $RUN_DATE)" + BRANCH_SKIPPED=$((BRANCH_SKIPPED + 1)) + TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1)) + continue + fi + + # 添加到删除列表 + echo " 🗑️ 标记删除 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE, 分支: $RUN_BRANCH)" + ALL_RUNS_TO_DELETE+=("$RUN_ID") + BRANCH_DELETED=$((BRANCH_DELETED + 1)) + TOTAL_DELETED=$((TOTAL_DELETED + 1)) + done + + # 检查是否有下一页 + if [ "$RUNS_COUNT" -lt 100 ]; then + break + fi + PAGE=$((PAGE + 1)) + sleep 0.5 + done + + echo " 分支 $BRANCH 统计: 删除: $BRANCH_DELETED, 保留: $BRANCH_SKIPPED, 进行中: $BRANCH_RUNNING" + done + + # 汇总统计 + echo -e "\n📊 总体统计:" + echo " 待删除: $TOTAL_DELETED 个" + echo " 1天内保留: $TOTAL_SKIPPED 个" + echo " 进行中: $TOTAL_RUNNING 个" + + if [ $TOTAL_DELETED -eq 0 ]; then + echo "✅ 没有需要删除的工作流运行" + exit 0 + fi + + # 模拟运行 + if [ "$DRY_RUN" = "true" ]; then + echo -e "\n🔍 [模拟模式] 将删除以上 $TOTAL_DELETED 个工作流运行" + echo "设置 dry_run=false 并输入 confirm_delete=YES 实际删除" + exit 0 + fi + + # 实际删除 + echo -e "\n🗑️ 开始删除 $TOTAL_DELETED 个工作流运行(并发: $CONCURRENCY)..." + + # 将ID写入文件 + printf "%s\n" "${ALL_RUNS_TO_DELETE[@]}" > /tmp/run_ids.txt + + # 并行删除 + DELETED=0 + FAILED=0 + cat /tmp/run_ids.txt | while read id; do + DELETED=$((DELETED + 1)) + echo -ne "\r进度: [$DELETED/$TOTAL_DELETED] 删除 #$id..." + + if gh api -X DELETE repos/$REPO/actions/runs/$id --silent 2>/dev/null; then + echo -e "\r✓ [$DELETED/$TOTAL_DELETED] 删除成功 #$id" + else + FAILED=$((FAILED + 1)) + echo -e "\r✗ [$DELETED/$TOTAL_DELETED] 删除失败 #$id" + fi + + # 控制并发 + if [ $((DELETED % CONCURRENCY)) -eq 0 ]; then + sleep 1 + fi + done + + echo -e "\n✅ 删除完成!" + echo " 总计: $TOTAL_DELETED" + echo " 成功: $((TOTAL_DELETED - FAILED))" + echo " 失败: $FAILED" diff --git a/.github/workflows/dry-run.yml b/.github/workflows/dry-run.yml deleted file mode 100644 index b51ef185e..000000000 --- a/.github/workflows/dry-run.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Clean All Workflows - Keep Last 1 Day - -on: - workflow_dispatch: - inputs: - dry_run: - description: '仅查看要删除的运行(不实际删除)' - required: true - default: 'false' - type: boolean - -jobs: - clean: - runs-on: ubuntu-latest - - permissions: - actions: write - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Delete all workflow runs older than 1 day - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - DRY_RUN: ${{ github.event.inputs.dry_run }} - run: | - # 计算1天前的日期 - CUTOFF_DATE=$(date -d "1 day ago" +%s) - - echo "清理配置:" - echo " 仓库: $REPO" - echo " 保留: 最近1天的工作流" - echo " 模拟运行: $DRY_RUN" - echo " 截止时间: $(date -d @$CUTOFF_DATE)" - echo "" - - # 获取所有工作流运行 - PAGE=1 - TOTAL_DELETED=0 - TOTAL_SKIPPED=0 - - while true; do - echo "获取第 $PAGE 页数据..." - - # 获取一页运行记录 - RESPONSE=$(gh api repos/$REPO/actions/runs?per_page=100\&page=$PAGE) - - # 检查是否有更多数据 - RUNS_COUNT=$(echo "$RESPONSE" | jq '.workflow_runs | length') - if [ "$RUNS_COUNT" -eq "0" ]; then - break - fi - - # 处理当前页的每个运行 - echo "$RESPONSE" | jq -c '.workflow_runs[]' | while read -r run; do - RUN_ID=$(echo "$run" | jq -r '.id') - RUN_NAME=$(echo "$run" | jq -r '.name // "未命名"') - RUN_DATE=$(echo "$run" | jq -r '.created_at') - RUN_TIMESTAMP=$(date -d "$RUN_DATE" +%s) - RUN_STATUS=$(echo "$run" | jq -r '.status') - RUN_WORKFLOW=$(echo "$run" | jq -r '.workflow_id') - - # 检查运行状态 - if [ "$RUN_STATUS" = "in_progress" ] || [ "$RUN_STATUS" = "queued" ] || [ "$RUN_STATUS" = "pending" ]; then - echo "⏳ 跳过进行中的运行 #$RUN_ID - $RUN_NAME (状态: $RUN_STATUS)" - TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1)) - continue - fi - - # 检查是否在1天内 - if [ "$RUN_TIMESTAMP" -gt "$CUTOFF_DATE" ]; then - echo "✅ 保留1天内的运行 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" - TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1)) - continue - fi - - # 删除超过1天的运行 - if [ "$DRY_RUN" = "true" ]; then - echo "🔍 [模拟] 将删除运行 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" - else - echo "🗑️ 正在删除运行 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" - if gh api -X DELETE repos/$REPO/actions/runs/$RUN_ID; then - echo " ✓ 删除成功" - else - echo " ✗ 删除失败" - fi - fi - TOTAL_DELETED=$((TOTAL_DELETED + 1)) - - # 添加小延迟避免API限流 - sleep 0.5 - done - - # 检查是否有下一页 - HAS_NEXT=$(echo "$RESPONSE" | jq '.total_count > 100 * '"$PAGE") - if [ "$HAS_NEXT" != "true" ]; then - break - fi - - PAGE=$((PAGE + 1)) - done - - echo "" - echo "========== 清理完成 ==========" - echo "总删除数: $TOTAL_DELETED" - echo "总保留数: $TOTAL_SKIPPED" - - if [ "$DRY_RUN" = "true" ]; then - echo "" - echo "⚠️ 这是模拟运行,没有实际删除任何内容" - echo "如需实际删除,请将 dry_run 设置为 false" - fi From 630581ed60d292b95159f6b1baa1def3bad0164d Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:45:13 +0800 Subject: [PATCH 005/116] Update dry-run.yaml --- .github/workflows/dry-run.yaml | 94 +++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/.github/workflows/dry-run.yaml b/.github/workflows/dry-run.yaml index dacbeb9a7..e8152d3d3 100644 --- a/.github/workflows/dry-run.yaml +++ b/.github/workflows/dry-run.yaml @@ -1,4 +1,4 @@ -name: Clean All Branches Workflows - Keep Last 1 Day +name: Clean All Branches Workflows - Keep Last 1 Day - FIXED on: workflow_dispatch: @@ -73,10 +73,15 @@ jobs: echo "仅处理指定分支: $TARGET_BRANCH" fi - TOTAL_DELETED=0 - TOTAL_SKIPPED=0 - TOTAL_RUNNING=0 - ALL_RUNS_TO_DELETE=() + # 创建临时文件来存储统计信息 + DELETED_FILE=$(mktemp) + SKIPPED_FILE=$(mktemp) + RUNNING_FILE=$(mktemp) + RUNS_TO_DELETE_FILE=$(mktemp) + + echo "0" > $DELETED_FILE + echo "0" > $SKIPPED_FILE + echo "0" > $RUNNING_FILE # 遍历每个分支 for BRANCH in $BRANCHES; do @@ -90,33 +95,43 @@ jobs: while true; do # 获取该分支的工作流运行 echo " 获取第 $PAGE 页..." - RESPONSE=$(gh api repos/$REPO/actions/runs \ + + # 直接使用gh获取运行列表 + RUNS=$(gh api repos/$REPO/actions/runs \ --method GET \ -F branch="$BRANCH" \ -F per_page=100 \ - -F page=$PAGE \ - --paginate) + -F page=$PAGE 2>/dev/null) + + # 检查是否成功 + if [ $? -ne 0 ]; then + echo " 警告: 获取分支 $BRANCH 第 $PAGE 页失败" + break + fi - # 检查是否有运行 - RUNS_COUNT=$(echo "$RESPONSE" | jq '.workflow_runs | length' 2>/dev/null || echo "0") + # 提取运行列表 + RUNS_COUNT=$(echo "$RUNS" | jq '.workflow_runs | length') if [ "$RUNS_COUNT" -eq "0" ]; then break fi + # 使用数组存储运行信息 + RUN_IDS=($(echo "$RUNS" | jq -r '.workflow_runs[].id')) + RUN_NAMES=($(echo "$RUNS" | jq -r '.workflow_runs[].name // "未命名"')) + RUN_DATES=($(echo "$RUNS" | jq -r '.workflow_runs[].created_at')) + RUN_STATUSES=($(echo "$RUNS" | jq -r '.workflow_runs[].status')) + # 处理每个运行 - echo "$RESPONSE" | jq -c '.workflow_runs[]' 2>/dev/null | while read -r run; do - RUN_ID=$(echo "$run" | jq -r '.id') - RUN_NAME=$(echo "$run" | jq -r '.name // "未命名"') - RUN_DATE=$(echo "$run" | jq -r '.created_at') - RUN_STATUS=$(echo "$run" | jq -r '.status') - RUN_BRANCH=$(echo "$run" | jq -r '.head_branch') - RUN_WORKFLOW=$(echo "$run" | jq -r '.workflow_id') + for i in "${!RUN_IDS[@]}"; do + RUN_ID="${RUN_IDS[$i]}" + RUN_NAME="${RUN_NAMES[$i]}" + RUN_DATE="${RUN_DATES[$i]}" + RUN_STATUS="${RUN_STATUSES[$i]}" # 检查运行状态 if [ "$RUN_STATUS" = "in_progress" ] || [ "$RUN_STATUS" = "queued" ] || [ "$RUN_STATUS" = "pending" ]; then echo " ⏳ 跳过进行中的运行 #$RUN_ID - $RUN_NAME (状态: $RUN_STATUS)" BRANCH_RUNNING=$((BRANCH_RUNNING + 1)) - TOTAL_RUNNING=$((TOTAL_RUNNING + 1)) continue fi @@ -124,19 +139,18 @@ jobs: if [[ "$RUN_DATE" > "$CUTOFF_DATE" ]]; then echo " ✅ 保留1天内的运行 #$RUN_ID (日期: $RUN_DATE)" BRANCH_SKIPPED=$((BRANCH_SKIPPED + 1)) - TOTAL_SKIPPED=$((TOTAL_SKIPPED + 1)) continue fi # 添加到删除列表 - echo " 🗑️ 标记删除 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE, 分支: $RUN_BRANCH)" - ALL_RUNS_TO_DELETE+=("$RUN_ID") + echo " 🗑️ 标记删除 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" + echo "$RUN_ID" >> $RUNS_TO_DELETE_FILE BRANCH_DELETED=$((BRANCH_DELETED + 1)) - TOTAL_DELETED=$((TOTAL_DELETED + 1)) done # 检查是否有下一页 - if [ "$RUNS_COUNT" -lt 100 ]; then + TOTAL_COUNT=$(echo "$RUNS" | jq '.total_count') + if [ $((PAGE * 100)) -ge "$TOTAL_COUNT" ]; then break fi PAGE=$((PAGE + 1)) @@ -144,16 +158,28 @@ jobs: done echo " 分支 $BRANCH 统计: 删除: $BRANCH_DELETED, 保留: $BRANCH_SKIPPED, 进行中: $BRANCH_RUNNING" + + # 更新全局统计 + echo $(($(cat $DELETED_FILE) + BRANCH_DELETED)) > $DELETED_FILE + echo $(($(cat $SKIPPED_FILE) + BRANCH_SKIPPED)) > $SKIPPED_FILE + echo $(($(cat $RUNNING_FILE) + BRANCH_RUNNING)) > $RUNNING_FILE done - # 汇总统计 + # 读取最终统计 + TOTAL_DELETED=$(cat $DELETED_FILE) + TOTAL_SKIPPED=$(cat $SKIPPED_FILE) + TOTAL_RUNNING=$(cat $RUNNING_FILE) + TOTAL_RUNS=$((TOTAL_DELETED + TOTAL_SKIPPED + TOTAL_RUNNING)) + echo -e "\n📊 总体统计:" + echo " 总运行数: $TOTAL_RUNS" echo " 待删除: $TOTAL_DELETED 个" echo " 1天内保留: $TOTAL_SKIPPED 个" echo " 进行中: $TOTAL_RUNNING 个" if [ $TOTAL_DELETED -eq 0 ]; then echo "✅ 没有需要删除的工作流运行" + rm -f $DELETED_FILE $SKIPPED_FILE $RUNNING_FILE $RUNS_TO_DELETE_FILE exit 0 fi @@ -161,27 +187,30 @@ jobs: if [ "$DRY_RUN" = "true" ]; then echo -e "\n🔍 [模拟模式] 将删除以上 $TOTAL_DELETED 个工作流运行" echo "设置 dry_run=false 并输入 confirm_delete=YES 实际删除" + rm -f $DELETED_FILE $SKIPPED_FILE $RUNNING_FILE + rm -f $RUNS_TO_DELETE_FILE exit 0 fi # 实际删除 echo -e "\n🗑️ 开始删除 $TOTAL_DELETED 个工作流运行(并发: $CONCURRENCY)..." - # 将ID写入文件 - printf "%s\n" "${ALL_RUNS_TO_DELETE[@]}" > /tmp/run_ids.txt + # 读取所有要删除的ID + mapfile -t RUN_IDS_TO_DELETE < $RUNS_TO_DELETE_FILE # 并行删除 DELETED=0 FAILED=0 - cat /tmp/run_ids.txt | while read id; do + + for RUN_ID in "${RUN_IDS_TO_DELETE[@]}"; do DELETED=$((DELETED + 1)) - echo -ne "\r进度: [$DELETED/$TOTAL_DELETED] 删除 #$id..." + echo -ne "\r进度: [$DELETED/$TOTAL_DELETED] 删除 #$RUN_ID..." - if gh api -X DELETE repos/$REPO/actions/runs/$id --silent 2>/dev/null; then - echo -e "\r✓ [$DELETED/$TOTAL_DELETED] 删除成功 #$id" + if gh api -X DELETE repos/$REPO/actions/runs/$RUN_ID --silent 2>/dev/null; then + echo -e "\r✓ [$DELETED/$TOTAL_DELETED] 删除成功 #$RUN_ID" else FAILED=$((FAILED + 1)) - echo -e "\r✗ [$DELETED/$TOTAL_DELETED] 删除失败 #$id" + echo -e "\r✗ [$DELETED/$TOTAL_DELETED] 删除失败 #$RUN_ID" fi # 控制并发 @@ -194,3 +223,6 @@ jobs: echo " 总计: $TOTAL_DELETED" echo " 成功: $((TOTAL_DELETED - FAILED))" echo " 失败: $FAILED" + + # 清理临时文件 + rm -f $DELETED_FILE $SKIPPED_FILE $RUNNING_FILE $RUNS_TO_DELETE_FILE From 15f902f5d43f09ca5bb34da47c79149d7038bd9b Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:47:54 +0800 Subject: [PATCH 006/116] Update dry-run.yaml --- .github/workflows/dry-run.yaml | 307 +++++++++++++-------------------- 1 file changed, 116 insertions(+), 191 deletions(-) diff --git a/.github/workflows/dry-run.yaml b/.github/workflows/dry-run.yaml index e8152d3d3..bcb8e93aa 100644 --- a/.github/workflows/dry-run.yaml +++ b/.github/workflows/dry-run.yaml @@ -1,4 +1,4 @@ -name: Clean All Branches Workflows - Keep Last 1 Day - FIXED +name: Python Clean - Delete All Old Runs on: workflow_dispatch: @@ -8,20 +8,10 @@ on: required: true default: 'true' type: boolean - confirm_delete: - description: 'dry_run=false时,输入"YES"确认删除' + days_old: + description: '删除多少天前的运行' required: true - default: '' - type: string - concurrency: - description: '并发数(默认10)' - required: true - default: '10' - type: string - target_branch: - description: '指定分支(留空则处理所有分支)' - required: false - default: '' + default: '1' type: string jobs: @@ -33,196 +23,131 @@ jobs: contents: read steps: - - name: Check confirmation - if: ${{ github.event.inputs.dry_run == 'false' && github.event.inputs.confirm_delete != 'YES' }} - run: | - echo "❌ 操作取消:未确认删除" - echo "dry_run=false 时需要输入 confirm_delete=YES" - exit 1 - - - name: Clean workflow runs from all branches + - 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: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} DRY_RUN: ${{ github.event.inputs.dry_run }} - CONCURRENCY: ${{ github.event.inputs.concurrency }} - TARGET_BRANCH: ${{ github.event.inputs.target_branch }} + DAYS_OLD: ${{ github.event.inputs.days_old }} run: | - # 计算1天前的日期 - CUTOFF_DATE=$(date -d "1 day ago" -u +"%Y-%m-%dT%H:%M:%SZ") - echo "🔍 清理配置:" - echo " 仓库: $REPO" - if [ -n "$TARGET_BRANCH" ]; then - echo " 目标分支: $TARGET_BRANCH" - else - echo " 目标分支: 所有分支" - fi - echo " 保留: 1天内的运行(<$CUTOFF_DATE 的将被删除)" - echo " 模拟运行: $DRY_RUN" - echo " 并发数: $CONCURRENCY" - echo "" + python - <<'EOF' + import os + import requests + from datetime import datetime, timedelta + import time - # 获取所有分支(如果不指定分支) - if [ -z "$TARGET_BRANCH" ]; then - echo "📋 获取所有分支..." - BRANCHES=$(gh api repos/$REPO/branches --paginate | jq -r '.[].name') - BRANCH_COUNT=$(echo "$BRANCHES" | wc -l) - echo "找到 $BRANCH_COUNT 个分支" - else - BRANCHES="$TARGET_BRANCH" - echo "仅处理指定分支: $TARGET_BRANCH" - fi + 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)) - # 创建临时文件来存储统计信息 - DELETED_FILE=$(mktemp) - SKIPPED_FILE=$(mktemp) - RUNNING_FILE=$(mktemp) - RUNS_TO_DELETE_FILE=$(mktemp) + headers = { + 'Authorization': f'token {token}', + 'Accept': 'application/vnd.github.v3+json' + } - echo "0" > $DELETED_FILE - echo "0" > $SKIPPED_FILE - echo "0" > $RUNNING_FILE + # 计算截止日期 + cutoff_date = (datetime.utcnow() - timedelta(days=days_old)).isoformat() - # 遍历每个分支 - for BRANCH in $BRANCHES; do - echo -e "\n📦 处理分支: $BRANCH" - - PAGE=1 - BRANCH_DELETED=0 - BRANCH_SKIPPED=0 - BRANCH_RUNNING=0 - - while true; do - # 获取该分支的工作流运行 - echo " 获取第 $PAGE 页..." - - # 直接使用gh获取运行列表 - RUNS=$(gh api repos/$REPO/actions/runs \ - --method GET \ - -F branch="$BRANCH" \ - -F per_page=100 \ - -F page=$PAGE 2>/dev/null) - - # 检查是否成功 - if [ $? -ne 0 ]; then - echo " 警告: 获取分支 $BRANCH 第 $PAGE 页失败" - break - fi - - # 提取运行列表 - RUNS_COUNT=$(echo "$RUNS" | jq '.workflow_runs | length') - if [ "$RUNS_COUNT" -eq "0" ]; then - break - fi + 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 + } - # 使用数组存储运行信息 - RUN_IDS=($(echo "$RUNS" | jq -r '.workflow_runs[].id')) - RUN_NAMES=($(echo "$RUNS" | jq -r '.workflow_runs[].name // "未命名"')) - RUN_DATES=($(echo "$RUNS" | jq -r '.workflow_runs[].created_at')) - RUN_STATUSES=($(echo "$RUNS" | jq -r '.workflow_runs[].status')) + 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', []) - # 处理每个运行 - for i in "${!RUN_IDS[@]}"; do - RUN_ID="${RUN_IDS[$i]}" - RUN_NAME="${RUN_NAMES[$i]}" - RUN_DATE="${RUN_DATES[$i]}" - RUN_STATUS="${RUN_STATUSES[$i]}" - - # 检查运行状态 - if [ "$RUN_STATUS" = "in_progress" ] || [ "$RUN_STATUS" = "queued" ] || [ "$RUN_STATUS" = "pending" ]; then - echo " ⏳ 跳过进行中的运行 #$RUN_ID - $RUN_NAME (状态: $RUN_STATUS)" - BRANCH_RUNNING=$((BRANCH_RUNNING + 1)) - continue - fi - - # 检查日期 - if [[ "$RUN_DATE" > "$CUTOFF_DATE" ]]; then - echo " ✅ 保留1天内的运行 #$RUN_ID (日期: $RUN_DATE)" - BRANCH_SKIPPED=$((BRANCH_SKIPPED + 1)) - continue - fi - - # 添加到删除列表 - echo " 🗑️ 标记删除 #$RUN_ID - $RUN_NAME (日期: $RUN_DATE)" - echo "$RUN_ID" >> $RUNS_TO_DELETE_FILE - BRANCH_DELETED=$((BRANCH_DELETED + 1)) - done + 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}]") - # 检查是否有下一页 - TOTAL_COUNT=$(echo "$RUNS" | jq '.total_count') - if [ $((PAGE * 100)) -ge "$TOTAL_COUNT" ]; then - break - fi - PAGE=$((PAGE + 1)) - sleep 0.5 - done - - echo " 分支 $BRANCH 统计: 删除: $BRANCH_DELETED, 保留: $BRANCH_SKIPPED, 进行中: $BRANCH_RUNNING" - - # 更新全局统计 - echo $(($(cat $DELETED_FILE) + BRANCH_DELETED)) > $DELETED_FILE - echo $(($(cat $SKIPPED_FILE) + BRANCH_SKIPPED)) > $SKIPPED_FILE - echo $(($(cat $RUNNING_FILE) + BRANCH_RUNNING)) > $RUNNING_FILE - done - - # 读取最终统计 - TOTAL_DELETED=$(cat $DELETED_FILE) - TOTAL_SKIPPED=$(cat $SKIPPED_FILE) - TOTAL_RUNNING=$(cat $RUNNING_FILE) - TOTAL_RUNS=$((TOTAL_DELETED + TOTAL_SKIPPED + TOTAL_RUNNING)) - - echo -e "\n📊 总体统计:" - echo " 总运行数: $TOTAL_RUNS" - echo " 待删除: $TOTAL_DELETED 个" - echo " 1天内保留: $TOTAL_SKIPPED 个" - echo " 进行中: $TOTAL_RUNNING 个" - - if [ $TOTAL_DELETED -eq 0 ]; then - echo "✅ 没有需要删除的工作流运行" - rm -f $DELETED_FILE $SKIPPED_FILE $RUNNING_FILE $RUNS_TO_DELETE_FILE - exit 0 - fi - - # 模拟运行 - if [ "$DRY_RUN" = "true" ]; then - echo -e "\n🔍 [模拟模式] 将删除以上 $TOTAL_DELETED 个工作流运行" - echo "设置 dry_run=false 并输入 confirm_delete=YES 实际删除" - rm -f $DELETED_FILE $SKIPPED_FILE $RUNNING_FILE - rm -f $RUNS_TO_DELETE_FILE - exit 0 - fi - - # 实际删除 - echo -e "\n🗑️ 开始删除 $TOTAL_DELETED 个工作流运行(并发: $CONCURRENCY)..." + if len(runs) < 100: + break + + page += 1 + time.sleep(0.5) - # 读取所有要删除的ID - mapfile -t RUN_IDS_TO_DELETE < $RUNS_TO_DELETE_FILE + total = len(runs_to_delete) + print(f"\n📊 找到 {total} 个需要删除的运行") - # 并行删除 - DELETED=0 - FAILED=0 + 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 RUN_ID in "${RUN_IDS_TO_DELETE[@]}"; do - DELETED=$((DELETED + 1)) - echo -ne "\r进度: [$DELETED/$TOTAL_DELETED] 删除 #$RUN_ID..." - - if gh api -X DELETE repos/$REPO/actions/runs/$RUN_ID --silent 2>/dev/null; then - echo -e "\r✓ [$DELETED/$TOTAL_DELETED] 删除成功 #$RUN_ID" - else - FAILED=$((FAILED + 1)) - echo -e "\r✗ [$DELETED/$TOTAL_DELETED] 删除失败 #$RUN_ID" - fi - - # 控制并发 - if [ $((DELETED % CONCURRENCY)) -eq 0 ]; then - sleep 1 - fi - done + 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) - echo -e "\n✅ 删除完成!" - echo " 总计: $TOTAL_DELETED" - echo " 成功: $((TOTAL_DELETED - FAILED))" - echo " 失败: $FAILED" + print(f"\n✅ 完成!") + print(f" 成功: {deleted}") + print(f" 失败: {failed}") - # 清理临时文件 - rm -f $DELETED_FILE $SKIPPED_FILE $RUNNING_FILE $RUNS_TO_DELETE_FILE + EOF From 6fc24ef5b3698cf2bcdc056509100758c68b6e79 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:53:54 +0800 Subject: [PATCH 007/116] Create build.yml --- .github/workflows/build.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..9df36f2c2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,31 @@ +name: Build + +on: + workflow_dispatch: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#configuring-a-build-matrix + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@main + with: + submodules: true + + - name: Configure CMake and Build + run: bash android_ci_build.bash + + - name: Upload build output + uses: actions/upload-artifact@main + with: + name: MobileGGGGGGGGGGGGGL + path: | + **/libMobileGL.so From 52ec178e924783264427eaf4b46d312fcfb83bda Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:54:44 +0800 Subject: [PATCH 008/116] Create android_ci_build.bash --- android_ci_build.bash | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 android_ci_build.bash diff --git a/android_ci_build.bash b/android_ci_build.bash new file mode 100644 index 000000000..3c712eebc --- /dev/null +++ b/android_ci_build.bash @@ -0,0 +1,16 @@ +#!/bin/bash +# set -e + +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 + 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 From fc3ef52f3ef200de40aba66225098fb645078bf4 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:55:07 +0800 Subject: [PATCH 009/116] Update android_ci_build.bash --- android_ci_build.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android_ci_build.bash b/android_ci_build.bash index 3c712eebc..7f50ce5cb 100644 --- a/android_ci_build.bash +++ b/android_ci_build.bash @@ -1,7 +1,7 @@ #!/bin/bash # set -e -git clone --depth 1 https://github.com/aaaapai/FastSTL.git ./include/FastSTL +# git clone --depth 1 https://github.com/aaaapai/FastSTL.git ./include/FastSTL cmake_build () { ANDROID_ABI=$1 mkdir -p build From 895cb0cdb934c5695a97990c324972a9346745cf Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 12:58:57 +0800 Subject: [PATCH 010/116] Update CMakeLists.txt --- CMakeLists.txt | 53 +++++++++----------------------------------------- 1 file changed, 9 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cece4a2ac..cbf53d109 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,59 +12,21 @@ 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_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} -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 -fno-emulated-tls -fwhole-program-vtables -fdata-sections -ffunction-sections -fmerge-all-constants -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 -DUseFastSTL -DUseJEMALLOC -pthread") +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 -fno-emulated-tls -fwhole-program-vtables -fdata-sections -ffunction-sections -fmerge-all-constants -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 -stdlib=libc++ -Wno-unicode-whitespace -Wno-format -Wno-unused-function -Wno-vla-cxx-extension -DUseFastSTL -frtti -DUseJEMALLOC -pthread") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -flto -O3 -Wl,-plugin-opt=-emulated-tls=0 -Wl,--strip-all -fvisibility=hidden -Wl,-O3 -Wl,--gc-sections -Wl,--as-needed -static-libstdc++") if (MSVC) set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}") @@ -315,6 +277,9 @@ if (ANDROID) android log ) + target_link_options(${CMAKE_PROJECT_NAME} PRIVATE + "LINKER:--version-script=${CMAKE_SOURCE_DIR}/MobileGL/mgl-version.script" + ) endif() if (MOBILEGL_BUILD_TEST) From d90ec9ec77d8f524e8ff75c4c8dd4a7d8a4be124 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:07:35 +0800 Subject: [PATCH 011/116] Update .gitmodules --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index bd45300f4..0c0272715 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 [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 From fc6b118b890266103ea7818d1339e4e43a3a2a43 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:12:05 +0800 Subject: [PATCH 012/116] Update Types.h --- MobileGL/MG_Util/Types.h | 53 +++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/MobileGL/MG_Util/Types.h b/MobileGL/MG_Util/Types.h index 9ba2fc86b..dbf7ee2de 100644 --- a/MobileGL/MG_Util/Types.h +++ b/MobileGL/MG_Util/Types.h @@ -20,16 +20,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; @@ -53,9 +53,40 @@ 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; +#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); From 300e30fe9e58f56912d4006c2c82a0408c1844db Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:14:26 +0800 Subject: [PATCH 013/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 5a0dd3c35..5b9976d8c 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -758,7 +758,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.glDrawElementsIndirect(mode, type, cmd); } @@ -772,7 +772,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); } From da1b88b5ee0d704465da1ec1b4749da0bb674556 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:16:30 +0800 Subject: [PATCH 014/116] Update EliminateFloatEqualsZeroPass.cpp --- .../SpirvPasses/EliminateFloatEqualsZeroPass.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 From 49196b6554716a39e05b30381ae4dda3f2f2b75f Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:17:54 +0800 Subject: [PATCH 015/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 5b9976d8c..27491da0c 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -35,7 +35,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) { From c71659ed8afb53f538387d9a441c377d0bcd0f25 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:40:38 +0800 Subject: [PATCH 016/116] Update Loader.cpp --- .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 7c8b78634..05aaeff86 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -10,7 +10,7 @@ #include "MG_Util/Types.h" namespace MobileGL::MG_Util::BackendLoader { - static void* OpenLib(const Vector& names) { + static void* OpenLib(const Vector& names, const char* overrides = nullptr) { #if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__) static const String LibPathPrefixes[] = { "/opt/vc/lib/", "/usr/local/lib/", "/usr/lib/", "/usr/lib/x86_64-linux-gnu/", @@ -19,7 +19,13 @@ namespace MobileGL::MG_Util::BackendLoader { void* lib = nullptr; - Int flags = RTLD_LOCAL | RTLD_NOW; + Int flags = RTLD_LOCAL | RTLD_LAZY; + if (overrides) { + if ((lib = dlopen(overrides, flags))) { + //strncpy(path_name, overrides, PATH_MAX); + return lib; + } + } for (const auto& prefix : LibPathPrefixes) { for (const auto& name : names) { String path_name = prefix + name; @@ -33,10 +39,16 @@ namespace MobileGL::MG_Util::BackendLoader { } inline void* ProcAddress(void* lib, const char* name) { + + if (MG_External::EGL::eglGetProcAddress) { + void* func = (void*)MG_External::EGL::eglGetProcAddress(name); + if (func) return func; + } + #if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__) return dlsym(lib, name); #else - return nullptrptr; + return nullptr; #endif } @@ -430,8 +442,21 @@ namespace MobileGL::MG_Util::BackendLoader { } Bool AcquireEGLFunctions(MG_External::EGLFunctionsTable& funcs) { + void* eglLib = nullptr; static const Vector EGLLibNames = {"libEGL.so"}; - void* eglLib = OpenLib(EGLLibNames); + const char* libgl_egl = std::getenv("LIBGL_EGL"); + if (libgl_egl && strcmp(libgl_egl, "libEGL_mesa.so") == 0) { + setenv("GALLIUM_DRIVER", "zink", 1); + setenv("MESA_LOADER_DRIVER_OVERRIDE", "zink", 1); + setenv("MESA_GL_VERSION_OVERRIDE", "3.2", 1); + setenv("MESA_GLSL_VERSION_OVERRIDE", "320", 1); + setenv("mesa_glthread", "true", 1); + } + if (libgl_egl) { + eglLib = OpenLib(EGLLibNames, libgl_egl); + } else { + eglLib = OpenLib(EGLLibNames); + } if (!eglLib) { MGLOG_E("Failed to open libEGL.so"); return false; @@ -513,6 +538,7 @@ namespace MobileGL::MG_Util::BackendLoader { eglDisplay = EGL_NO_DISPLAY; } } + inline Bool InitTmpEGLContext(const MG_External::EGLFunctionsTable& eglFuncs) { EGLint configAttribs[] = {EGL_RED_SIZE, 8, @@ -522,13 +548,27 @@ namespace MobileGL::MG_Util::BackendLoader { 8, EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, + 24, + EGL_BUFFER_SIZE, + 32, EGL_SURFACE_TYPE, - EGL_PBUFFER_BIT, + EGL_WINDOW_BIT|EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, + EGL_OPENGL_ES3_BIT, EGL_NONE}; - EGLint ctxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + + const char* libgl_egl = std::getenv("LIBGL_EGL"); + if (libgl_egl && strcmp(libgl_egl, "libEGL_mesa.so") == 0) { + LOG_W("Warning: You are using Mesa Zink!"); + + configAttribs[10] = EGL_RENDERABLE_TYPE; + configAttribs[11] = EGL_OPENGL_ES2_BIT; + configAttribs[15] = EGL_OPENGL_ES2_BIT; + + } + EGLint ctxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; EGLint pbAttribs[] = {EGL_WIDTH, 32, EGL_HEIGHT, 32, EGL_NONE}; From 99bb759d12e2a69fa086f33ebf9411968f03d010 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:44:18 +0800 Subject: [PATCH 017/116] Update Loader.cpp --- MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 05aaeff86..7bceff0ec 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -41,6 +41,7 @@ namespace MobileGL::MG_Util::BackendLoader { inline void* ProcAddress(void* lib, const char* name) { if (MG_External::EGL::eglGetProcAddress) { + LOG_W("Using eglGetProcAddress"); void* func = (void*)MG_External::EGL::eglGetProcAddress(name); if (func) return func; } From b1aff079f3a58db6dcc4bc70764491b67e484454 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:47:15 +0800 Subject: [PATCH 018/116] Update Init.cpp --- MobileGL/Init.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 19767622c..ace326b02 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -14,8 +14,21 @@ #include #include +#include +#include + namespace MobileGL { void MG_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_State::Init(); From 000b50a25690bba808726e3d7badb5632da7bdd8 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:53:17 +0800 Subject: [PATCH 019/116] Add files via upload --- MobileGL/MG_Util/Config/cJSON.c | 3143 ++++++++++++++++++++++++++ MobileGL/MG_Util/Config/cJSON.h | 300 +++ MobileGL/MG_Util/Config/config.cpp | 126 ++ MobileGL/MG_Util/Config/config.h | 31 + MobileGL/MG_Util/Config/settings.cpp | 320 +++ MobileGL/MG_Util/Config/settings.h | 180 ++ 6 files changed, 4100 insertions(+) create mode 100644 MobileGL/MG_Util/Config/cJSON.c create mode 100644 MobileGL/MG_Util/Config/cJSON.h create mode 100644 MobileGL/MG_Util/Config/config.cpp create mode 100644 MobileGL/MG_Util/Config/config.h create mode 100644 MobileGL/MG_Util/Config/settings.cpp create mode 100644 MobileGL/MG_Util/Config/settings.h 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..ecdefa7d9 --- /dev/null +++ b/MobileGL/MG_Util/Config/config.cpp @@ -0,0 +1,126 @@ +#include "config.h" + +#include "cJSON.h" +#include +#include +#include +#include +#include +#include + +#include "Includes.h" +#include +#include +#include + +#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..de3ab20d6 --- /dev/null +++ b/MobileGL/MG_Util/Config/settings.cpp @@ -0,0 +1,320 @@ +// +// Created by hanji on 2025/2/9. +// + +#include "settings.h" +#include "config.h" + +#include "Includes.h" +#include +#include +#include + +#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 From 5aa95494c015b7a2ba79166b8745f99894656af9 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:56:09 +0800 Subject: [PATCH 020/116] Update CMakeLists.txt --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbf53d109..a00e1fe01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,9 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") endif() enable_language(CXX) +enable_language(C) + +set(CMAKE_C_STANDARD 23) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_ANDROID_LD "lld") @@ -191,6 +194,9 @@ 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/config.cpp + MobileGL/MG_Util/Config/settings.cpp ) set(MOBILEGL_LINK_LIBRARIES From bd5eb8409a2f7f4e65272036bb98c36a95323dd0 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:01:14 +0800 Subject: [PATCH 021/116] Update Init.cpp --- MobileGL/MG_Backend/Init.cpp | 37 +++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index 67a8a4667..8273743a1 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -49,9 +49,44 @@ namespace MobileGL::MG_Backend { MGLOG_D("Initializing MobileGL Backend..."); switch (MG_Config::ActiveBackendType) { - case BackendType::DirectGLES: + case BackendType::DirectGLES: { + + RendererInfo directGLESInfo = DirectGLES::RendererInfo; + + // 检查环境变量LIBGL_GL + const char* envLibGL = std::getenv("LIBGL_GL"); + const char* envlibGL_compute = std::getenv("LIBGL_COMPUTE_SHADER"); + if ((envLibGL != nullptr) || (envlibGL_compute != nullptr)) { + std::string libglValue = ""; + if (envLibGL != nullptr) { + libglValue = envLibGL; + } + auto& extensions = directGLESInfo.RendererGLInfo.Extensions; + + if (envlibGL_compute != nullptr) { + extensions.push_back(E_GL_ARB_compute_shader); + } + + // 如果设置为"43",则使用OpenGL 4.3 + if ((!libglValue.empty()) && (libglValue == "43")) { + MGLOG_I("LIBGL_GL=43 detected, using OpenGL 4.3 configuration"); + + // 修改OpenGL版本 + directGLESInfo.RendererGLInfo.TargetGLVersion = {4, 3, 0}; + + // 添加OpenGL 4.x扩展 + extensions.push_back(V_OpenGL40); + extensions.push_back(V_OpenGL41); + extensions.push_back(V_OpenGL42); + extensions.push_back(V_OpenGL43); + + } + + } + pActiveBackendObject = MakeUnique(); break; + } case BackendType::Unknown: default: MGLOG_W("Unknown backend type, defaulting to unknown backend"); From c491ff16d2d8dd51af4eba8071a850bf489b5f26 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:04:23 +0800 Subject: [PATCH 022/116] Update BackendObject_DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 3f45da861..e5844bd5b 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -57,7 +57,7 @@ namespace MobileGL::MG_Backend::DirectGLES { .TargetGLVersion = {3, 3, 0}, // 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}, + V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_shader_image_load_store}, .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability From 206df872ca1d8d72079ab912473d85a1d8290bfd Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:10:15 +0800 Subject: [PATCH 023/116] Update GetProcAddress.cpp --- MobileGL/MG_Impl/GetProcAddress.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index a9f6f6990..7289e58b0 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 { @@ -1353,4 +1353,4 @@ namespace MobileGL { return nullptr; } } // namespace MG_Impl -} // namespace MobileGL \ No newline at end of file +} // namespace MobileGL From 635cfcc576f4c8cfdd921e6b4388d5b61e6a33f7 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:13:58 +0800 Subject: [PATCH 024/116] Update GL_Getter.cpp --- MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index a4233874d..c7a9311f6 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -883,6 +883,28 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_MAX_SAMPLES: *params = 16; // TODO break; + case GL_MAX_IMAGE_UNITS: + *params = 8; // 最小4,推荐8或16 + break; + case GL_MAX_VERTEX_IMAGE_UNIFORMS: + *params = 4; // ES 3.2 最小要求 + break; + + case GL_MAX_FRAGMENT_IMAGE_UNIFORMS: + *params = 8; // 通常比顶点多 + break; + + case GL_MAX_COMPUTE_IMAGE_UNIFORMS: + *params = 8; // 如果支持计算着色器 + break; + + case GL_MAX_COMBINED_IMAGE_UNIFORMS: + *params = 12; // 顶点+片段的总和 + break; + + case GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES: + *params = 16; // 图像单元 + SSBO 的总限制 + break; default: MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname); MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, From 2f1fd273ff684df4ae79b57b2cdd6a8a17e041ba Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:21:58 +0800 Subject: [PATCH 025/116] Update GL_Framebuffer.cpp --- MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index 8c5422a82..fbac8e4f5 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -584,13 +584,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, MakeShared("MG_Impl/GLImpl", "ReadPixels_State", "Pixel data not aligned for pixel pack buffer")); return; - } + }*/ } // Check multisampling From b56771dd374fbb54583e075c1ed4ed5b85a400c7 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:24:20 +0800 Subject: [PATCH 026/116] Update CMakeLists.txt --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a00e1fe01..2e9206596 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,6 +195,7 @@ set(SOURCE_FILES 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 ) From dfe790acd789d04e147bfa0c2fd96034fae6b4cb Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:30:33 +0800 Subject: [PATCH 027/116] Update GL_Framebuffer.cpp --- MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index fbac8e4f5..8c89e452a 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -356,6 +356,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; From 7888314f26fdcc614e7532755060c1a91f5f1ddf Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:32:08 +0800 Subject: [PATCH 028/116] Create UpdateSubmodules.yml --- .github/workflows/UpdateSubmodules.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/UpdateSubmodules.yml 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 + From aa2228a248c35677bb8a5a7ebdb0dcc9a052bb5b Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:32:46 +0800 Subject: [PATCH 029/116] Create update.bash --- update.bash | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 update.bash diff --git a/update.bash b/update.bash new file mode 100644 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 From be53bed36f9f33e862b3d7cb7ab14d2922d2665f Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 18 Feb 2026 08:33:58 +0000 Subject: [PATCH 030/116] Automated 3rdparty submodule update Wed Feb 18 08:33:58 UTC 2026 --- 3rdparty/DiligentCore | 2 +- 3rdparty/SPIRV-Cross | 2 +- 3rdparty/glslang | 2 +- 3rdparty/tracy | 2 +- update.bash | 0 5 files changed, 4 insertions(+), 4 deletions(-) mode change 100644 => 100755 update.bash diff --git a/3rdparty/DiligentCore b/3rdparty/DiligentCore index f36e63880..546088473 160000 --- a/3rdparty/DiligentCore +++ b/3rdparty/DiligentCore @@ -1 +1 @@ -Subproject commit f36e63880566649d9d8b2336a7c5aa9c8ddb174b +Subproject commit 5460884736a2f36ceb0a77478d5f39414db87429 diff --git a/3rdparty/SPIRV-Cross b/3rdparty/SPIRV-Cross index 072444287..bf6bb5ce3 160000 --- a/3rdparty/SPIRV-Cross +++ b/3rdparty/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 072444287f4e139c178d6d8fe32e04a0d2c34e8b +Subproject commit bf6bb5ce3c9fb141127a7d3289ebaaef86d3b065 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..6de054002 160000 --- a/3rdparty/tracy +++ b/3rdparty/tracy @@ -1 +1 @@ -Subproject commit e6b9ea460993a6f093283055ef3c98025b19f909 +Subproject commit 6de054002dc9caffd3b322a8e0c617b710122f48 diff --git a/update.bash b/update.bash old mode 100644 new mode 100755 From 96def5d59959281671d690898472288f5e3cb5da Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:35:45 +0800 Subject: [PATCH 031/116] Update android_ci_build.bash --- android_ci_build.bash | 3 +++ 1 file changed, 3 insertions(+) diff --git a/android_ci_build.bash b/android_ci_build.bash index 7f50ce5cb..529d1c74e 100644 --- a/android_ci_build.bash +++ b/android_ci_build.bash @@ -1,6 +1,9 @@ #!/bin/bash # set -e +vpython3 ./3rdparty/glslang/update_glslang_sources.py +cd ../.. + # git clone --depth 1 https://github.com/aaaapai/FastSTL.git ./include/FastSTL cmake_build () { ANDROID_ABI=$1 From 432d7355dc6b807ee2d8f45f633b190047462d93 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:43:09 +0800 Subject: [PATCH 032/116] Update build.yml --- .github/workflows/build.yml | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9df36f2c2..460e52be9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,28 +4,43 @@ on: workflow_dispatch: env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release + CCACHE_DIR: /tmp/ccache # 设置ccache缓存目录 jobs: build: - # The CMake configure and build commands are platform agnostic and should work equally - # well on Windows or Mac. You can convert this to a matrix build if you need - # cross-platform coverage. - # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#configuring-a-build-matrix runs-on: ubuntu-24.04 - + steps: - uses: actions/checkout@main with: submodules: true - + + # 添加ccache步骤 + - name: CCache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ runner.os }}-ndk-${{ env.BUILD_TYPE }} + max-size: 2G # 设置缓存大小,根据需要调整 + + # 将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: MobileGGGGGGGGGGGGGL + name: MobileGL path: | **/libMobileGL.so From cb1529d44742144db7729306bc096f02366647c5 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:44:00 +0800 Subject: [PATCH 033/116] Update build.yml --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 460e52be9..f4ce164c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,10 +18,10 @@ jobs: # 添加ccache步骤 - name: CCache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@main with: key: ${{ runner.os }}-ndk-${{ env.BUILD_TYPE }} - max-size: 2G # 设置缓存大小,根据需要调整 + max-size: 1G # 设置缓存大小,根据需要调整 # 将ccache添加到PATH - name: Setup ccache in PATH From e230ff298d567cbeff1c055b8dc85207da976d67 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:45:48 +0800 Subject: [PATCH 034/116] Update android_ci_build.bash --- android_ci_build.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android_ci_build.bash b/android_ci_build.bash index 529d1c74e..0e377da0f 100644 --- a/android_ci_build.bash +++ b/android_ci_build.bash @@ -1,7 +1,7 @@ #!/bin/bash # set -e -vpython3 ./3rdparty/glslang/update_glslang_sources.py +python ./3rdparty/glslang/update_glslang_sources.py cd ../.. # git clone --depth 1 https://github.com/aaaapai/FastSTL.git ./include/FastSTL From 8e22f47e22e62f1b19a3b46411c9708ae13d5f54 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:47:35 +0800 Subject: [PATCH 035/116] Update android_ci_build.bash --- android_ci_build.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android_ci_build.bash b/android_ci_build.bash index 0e377da0f..1af053223 100644 --- a/android_ci_build.bash +++ b/android_ci_build.bash @@ -1,7 +1,8 @@ #!/bin/bash # set -e -python ./3rdparty/glslang/update_glslang_sources.py +cd ./3rdparty/glslang +python ./update_glslang_sources.py cd ../.. # git clone --depth 1 https://github.com/aaaapai/FastSTL.git ./include/FastSTL From e1ec3618ac8020d50011c97c2139efc239211d6f Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:58:47 +0800 Subject: [PATCH 036/116] Update Loader.cpp --- MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 7bceff0ec..d7c8001f3 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -40,8 +40,8 @@ namespace MobileGL::MG_Util::BackendLoader { inline void* ProcAddress(void* lib, const char* name) { - if (MG_External::EGL::eglGetProcAddress) { - LOG_W("Using eglGetProcAddress"); + if (eglGetProcAddress) { + MGLOG_W("Using eglGetProcAddress"); void* func = (void*)MG_External::EGL::eglGetProcAddress(name); if (func) return func; } @@ -562,7 +562,7 @@ namespace MobileGL::MG_Util::BackendLoader { const char* libgl_egl = std::getenv("LIBGL_EGL"); if (libgl_egl && strcmp(libgl_egl, "libEGL_mesa.so") == 0) { - LOG_W("Warning: You are using Mesa Zink!"); + MGLOG_W("Warning: You are using Mesa Zink!"); configAttribs[10] = EGL_RENDERABLE_TYPE; configAttribs[11] = EGL_OPENGL_ES2_BIT; From 971f517446a27d1c1694734c0ab7df92f4d4638a Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:14:57 +0800 Subject: [PATCH 037/116] Update Loader.cpp --- MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index d7c8001f3..49be6ab1b 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -40,9 +40,11 @@ namespace MobileGL::MG_Util::BackendLoader { inline void* ProcAddress(void* lib, const char* name) { - if (eglGetProcAddress) { + bool has_eglGetProcAddress false; + if + if (?) { MGLOG_W("Using eglGetProcAddress"); - void* func = (void*)MG_External::EGL::eglGetProcAddress(name); + void* func = (void*)eglGetProcAddress(name); if (func) return func; } From faa5658af10d0b20acc1811d270810d73837edc1 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:42:38 +0800 Subject: [PATCH 038/116] Update Loader.cpp --- .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 49be6ab1b..96c72f068 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -39,20 +39,39 @@ namespace MobileGL::MG_Util::BackendLoader { } inline void* ProcAddress(void* lib, const char* name) { - - bool has_eglGetProcAddress false; - if - if (?) { - MGLOG_W("Using eglGetProcAddress"); - void* func = (void*)eglGetProcAddress(name); - if (func) return func; - } - -#if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__) - return dlsym(lib, name); -#else + static void* eglGetProcAddress_mgptr = nullptr; + + if (eglGetProcAddress_mgptr == nullptr) { + void* eglLib = nullptr; + static const Vector EGLLibNames = {"libEGL.so"}; + const char* libgl_egl = std::getenv("LIBGL_EGL"); + + if (libgl_egl) { + eglLib = OpenLib(EGLLibNames, libgl_egl); + } else { + eglLib = OpenLib(EGLLibNames); + } + + if (eglLib) { + eglGetProcAddress_mgptr = dlsym(eglLib, "eglGetProcAddress"); + } + } + + if (eglGetProcAddress_mgptr != nullptr) { + MGLOG_W("Using eglGetProcAddress"); + EGLGetProcAddressFunc eglGetProcAddress = + reinterpret_cast(eglGetProcAddress_mgptr); + + void* func = eglGetProcAddress(name); + if (func) return func; + } + + // Fallback to dlsym on non-Windows/non-Apple platforms + #if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__) + return dlsym(lib, name); + #else return nullptr; -#endif + #endif } Bool AcquireGLESFunctions(MG_External::GLESFunctionsTable& funcs, From 5b69b57fae6c3a9b6c30511da6438bc0ce24d829 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:51:04 +0800 Subject: [PATCH 039/116] Update Loader.cpp --- MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 96c72f068..6526732c4 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -59,6 +59,7 @@ namespace MobileGL::MG_Util::BackendLoader { if (eglGetProcAddress_mgptr != nullptr) { MGLOG_W("Using eglGetProcAddress"); + typedef void* (*EGLGetProcAddressFunc)(const char*); EGLGetProcAddressFunc eglGetProcAddress = reinterpret_cast(eglGetProcAddress_mgptr); From 7e73d05c7fc2d1113b61667bdf5905754027d209 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:57:15 +0800 Subject: [PATCH 040/116] Update Init.cpp --- MobileGL/MG_Backend/Init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index 8273743a1..78255bd9f 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -51,7 +51,7 @@ namespace MobileGL::MG_Backend { switch (MG_Config::ActiveBackendType) { case BackendType::DirectGLES: { - RendererInfo directGLESInfo = DirectGLES::RendererInfo; + const auto& directGLESInfo = DirectGLES::RendererInfo; // 检查环境变量LIBGL_GL const char* envLibGL = std::getenv("LIBGL_GL"); From 9f1ac1451bd789a7d1ee4803feea60ed4a0b9b21 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:00:56 +0800 Subject: [PATCH 041/116] Update Init.cpp --- MobileGL/MG_Backend/Init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index 78255bd9f..f175ae1e3 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -51,7 +51,7 @@ namespace MobileGL::MG_Backend { switch (MG_Config::ActiveBackendType) { case BackendType::DirectGLES: { - const auto& directGLESInfo = DirectGLES::RendererInfo; + const auto& directGLESInfo = pActiveBackendObject->GetRendererInfo(); // 检查环境变量LIBGL_GL const char* envLibGL = std::getenv("LIBGL_GL"); From e14b27e36c10813f201741803874a4fcde2aad70 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:14:36 +0800 Subject: [PATCH 042/116] Update Init.cpp --- MobileGL/MG_Backend/Init.cpp | 72 +++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index f175ae1e3..e746e52c5 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -48,42 +48,9 @@ namespace MobileGL::MG_Backend { void Init() { MGLOG_D("Initializing MobileGL Backend..."); + // 先创建后端对象 switch (MG_Config::ActiveBackendType) { case BackendType::DirectGLES: { - - const auto& directGLESInfo = pActiveBackendObject->GetRendererInfo(); - - // 检查环境变量LIBGL_GL - const char* envLibGL = std::getenv("LIBGL_GL"); - const char* envlibGL_compute = std::getenv("LIBGL_COMPUTE_SHADER"); - if ((envLibGL != nullptr) || (envlibGL_compute != nullptr)) { - std::string libglValue = ""; - if (envLibGL != nullptr) { - libglValue = envLibGL; - } - auto& extensions = directGLESInfo.RendererGLInfo.Extensions; - - if (envlibGL_compute != nullptr) { - extensions.push_back(E_GL_ARB_compute_shader); - } - - // 如果设置为"43",则使用OpenGL 4.3 - if ((!libglValue.empty()) && (libglValue == "43")) { - MGLOG_I("LIBGL_GL=43 detected, using OpenGL 4.3 configuration"); - - // 修改OpenGL版本 - directGLESInfo.RendererGLInfo.TargetGLVersion = {4, 3, 0}; - - // 添加OpenGL 4.x扩展 - extensions.push_back(V_OpenGL40); - extensions.push_back(V_OpenGL41); - extensions.push_back(V_OpenGL42); - extensions.push_back(V_OpenGL43); - - } - - } - pActiveBackendObject = MakeUnique(); break; } @@ -93,6 +60,43 @@ namespace MobileGL::MG_Backend { pActiveBackendObject = nullptr; } + if (!pActiveBackendObject) { + MGLOG_W("Failed to create backend object"); + return; + } + + // 检查环境变量并修改配置 + const char* envLibGL = std::getenv("LIBGL_GL"); + const char* envlibGL_compute = std::getenv("LIBGL_COMPUTE_SHADER"); + + if ((envLibGL != nullptr) || (envlibGL_compute != nullptr)) { + // 获取非const引用以修改 + auto& rendererInfo = pActiveBackendObject->GetRendererInfo(); + std::string libglValue = ""; + if (envLibGL != nullptr) { + libglValue = envLibGL; + } + auto& extensions = rendererInfo.RendererGLInfo.Extensions; + + if (envlibGL_compute != nullptr) { + extensions.push_back(E_GL_ARB_compute_shader); + } + + // 如果设置为"43",则使用OpenGL 4.3 + if ((!libglValue.empty()) && (libglValue == "43")) { + MGLOG_I("LIBGL_GL=43 detected, using OpenGL 4.3 configuration"); + + // 修改OpenGL版本 + rendererInfo.RendererGLInfo.TargetGLVersion = {4, 3, 0}; + + // 添加OpenGL 4.x扩展 + extensions.push_back(V_OpenGL40); + extensions.push_back(V_OpenGL41); + extensions.push_back(V_OpenGL42); + extensions.push_back(V_OpenGL43); + } + } + Bool result = InitSpecificBackendLibs(); if (!result) { MGLOG_W("Failed to initialize MobileGL backend libraries"); From df9e3255aa8daa1d73dd486a9f3a03c39fa12183 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:22:29 +0800 Subject: [PATCH 043/116] Update BackendObject_DirectGLES.cpp --- .../DirectGLES/BackendObject_DirectGLES.cpp | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index e5844bd5b..f4d167a6b 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 { BackendObject_DirectGLES::~BackendObject_DirectGLES() { @@ -48,17 +49,50 @@ namespace MobileGL::MG_Backend::DirectGLES { } const RendererInfo& BackendObject_DirectGLES::GetRendererInfo() const { + // 检查环境变量 + const char* envLibGL = std::getenv("LIBGL_GL"); + const char* envlibGL_compute = std::getenv("LIBGL_COMPUTE_SHADER"); + + // 创建基础扩展列表 + Vector extensions = { + V_OpenGL30, V_OpenGL31, V_OpenGL32, + V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_shader_image_load_store + }; + + // 根据环境变量添加扩展 + if (envlibGL_compute != nullptr) { + extensions.push_back(E_GL_ARB_compute_shader); + MGLOG_I("LIBGL_COMPUTE_SHADER detected, added compute shader support"); + } + + // 检查LIBGL_GL环境变量 + Version targetVersion = {3, 3, 0}; // 默认版本 + + if (envLibGL != nullptr) { + std::string libglValue = envLibGL; + if (libglValue == "43") { + MGLOG_I("LIBGL_GL=43 detected, using OpenGL 4.3 configuration"); + targetVersion = {4, 3, 0}; + + // 添加OpenGL 4.x扩展 + extensions.push_back(V_OpenGL40); + extensions.push_back(V_OpenGL41); + extensions.push_back(V_OpenGL42); + extensions.push_back(V_OpenGL43); + } + } + + // 创建并返回RendererInfo 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 - .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_shader_image_load_store}, - .IsCompatibilityProfile = false // Is Compatibility Profile + .TargetGLVersion = targetVersion, // Target OpenGL Version (动态) + .TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version + .Extensions = extensions, // OpenGL Extensions (动态) + .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability }; From e078c9fc750c30774830b63abf6981ecc1a53164 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:24:12 +0800 Subject: [PATCH 044/116] Update Init.cpp --- MobileGL/MG_Backend/Init.cpp | 41 +----------------------------------- 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index e746e52c5..67a8a4667 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -48,55 +48,16 @@ namespace MobileGL::MG_Backend { void Init() { MGLOG_D("Initializing MobileGL Backend..."); - // 先创建后端对象 switch (MG_Config::ActiveBackendType) { - case BackendType::DirectGLES: { + case BackendType::DirectGLES: pActiveBackendObject = MakeUnique(); break; - } case BackendType::Unknown: default: MGLOG_W("Unknown backend type, defaulting to unknown backend"); pActiveBackendObject = nullptr; } - if (!pActiveBackendObject) { - MGLOG_W("Failed to create backend object"); - return; - } - - // 检查环境变量并修改配置 - const char* envLibGL = std::getenv("LIBGL_GL"); - const char* envlibGL_compute = std::getenv("LIBGL_COMPUTE_SHADER"); - - if ((envLibGL != nullptr) || (envlibGL_compute != nullptr)) { - // 获取非const引用以修改 - auto& rendererInfo = pActiveBackendObject->GetRendererInfo(); - std::string libglValue = ""; - if (envLibGL != nullptr) { - libglValue = envLibGL; - } - auto& extensions = rendererInfo.RendererGLInfo.Extensions; - - if (envlibGL_compute != nullptr) { - extensions.push_back(E_GL_ARB_compute_shader); - } - - // 如果设置为"43",则使用OpenGL 4.3 - if ((!libglValue.empty()) && (libglValue == "43")) { - MGLOG_I("LIBGL_GL=43 detected, using OpenGL 4.3 configuration"); - - // 修改OpenGL版本 - rendererInfo.RendererGLInfo.TargetGLVersion = {4, 3, 0}; - - // 添加OpenGL 4.x扩展 - extensions.push_back(V_OpenGL40); - extensions.push_back(V_OpenGL41); - extensions.push_back(V_OpenGL42); - extensions.push_back(V_OpenGL43); - } - } - Bool result = InitSpecificBackendLibs(); if (!result) { MGLOG_W("Failed to initialize MobileGL backend libraries"); From 53d873d8c0ca597c40daf2fe6c21042a59260add Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:28:39 +0800 Subject: [PATCH 045/116] Update config.cpp --- MobileGL/MG_Util/Config/config.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/MobileGL/MG_Util/Config/config.cpp b/MobileGL/MG_Util/Config/config.cpp index ecdefa7d9..138138252 100644 --- a/MobileGL/MG_Util/Config/config.cpp +++ b/MobileGL/MG_Util/Config/config.cpp @@ -10,7 +10,6 @@ #include "Includes.h" #include -#include #include #define DEBUG 0 From a9f23f68dba518c57423193a060ba01fa3d82b91 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:29:07 +0800 Subject: [PATCH 046/116] Update settings.cpp --- MobileGL/MG_Util/Config/settings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/MobileGL/MG_Util/Config/settings.cpp b/MobileGL/MG_Util/Config/settings.cpp index de3ab20d6..65db1c3ba 100644 --- a/MobileGL/MG_Util/Config/settings.cpp +++ b/MobileGL/MG_Util/Config/settings.cpp @@ -7,7 +7,6 @@ #include "Includes.h" #include -#include #include #define DEBUG 0 From 1f10f13080cf74d48e0f20d0a921d950a18da205 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:34:48 +0800 Subject: [PATCH 047/116] Create mgl-version.script --- MobileGL/mgl-version.script | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 MobileGL/mgl-version.script 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: + *; +}; From c5f0a004ef75067eb259eece59a6ee3df824de27 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:22:46 +0800 Subject: [PATCH 048/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 113 +++++++++++++++++- 1 file changed, 108 insertions(+), 5 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 27491da0c..c0ba9ec02 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -737,17 +737,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) { From 95f3f532ba6a742d53c28e085b351ce51e499457 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:38:47 +0800 Subject: [PATCH 049/116] Update ShaderCompiler.cpp --- .../ShaderTranspiler/ShaderCompiler.cpp | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 803a93f35..4a5ed0e7a 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -145,7 +145,7 @@ 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_5); } else { @@ -158,12 +158,32 @@ 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_arrays_of_arrays : 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 +200,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 +231,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 +245,17 @@ namespace MobileGL { Result>> ShaderCompiler::GetSpirvBinaryFromProgram( const ProgramBinaryAttrib& attrib) { + + if (!attrib.program) { + ResultInfo r; + r.log = "Error: [GetSpirvBinary] Program is null"; + r.errc = -7; + return std::unexpected(r); + } + glslang::SpvOptions spvOptions; spvOptions.disableOptimizer = false; + spvOptions.generateDebugInfo = true; Vector> allSpirv; for (auto type : attrib.shaderTypes) { @@ -229,6 +269,7 @@ namespace MobileGL { bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector& inputBinary, Vector& outputBinary) { + using namespace spvtools; OptimizerOptions options; options.set_run_validator(false); @@ -246,6 +287,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); From 34bb6b1dd7009c67b2ba0c38741cf0c72aa3e008 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:40:49 +0800 Subject: [PATCH 050/116] Update ShaderSourceProcessor.cpp --- .../ShaderSourceProcessor.cpp | 99 ++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index f1ff29f1c..d7e3c68ff 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -40,6 +40,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"); @@ -56,14 +151,14 @@ namespace MobileGL { profile = ShaderProfile::Core; } else { profile = ShaderProfile::Core; - source.insert(0, "#version 460 core\n"); + source.insert(0, "#version 460 compatibility\n"); return; } 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 = From 1b9fe9ea8a9217435bdc8cb59d024ab020379b53 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:41:37 +0800 Subject: [PATCH 051/116] Add files via upload --- .../ShaderSourceProcessor_inline.h | 1110 +++++++++++++++++ 1 file changed, 1110 insertions(+) create mode 100644 MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor_inline.h 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 "的长度 + }*/ + + + +} + + + +} +} +} From 1916c16515b70124b813e84f638b60c1f1988c56 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:42:15 +0800 Subject: [PATCH 052/116] Update ShaderSourceProcessor.cpp --- MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index d7e3c68ff..5f40ae065 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "ShaderSourceProcessor.h" +#include "ShaderSourceProcessor_inline.h" namespace MobileGL { namespace MG_Util { From 2086e1332ea7cea904a53301747c465606b32a20 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:54:13 +0800 Subject: [PATCH 053/116] Update ShaderCompiler.cpp --- MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 4a5ed0e7a..6291dd9f4 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -15,6 +15,8 @@ #include #include +#include "../Config/EnvChecker.h" + namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { From 8f24f4cb14ba6a9a015d8084c9baf8a5fc70f20e Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:55:12 +0800 Subject: [PATCH 054/116] Create EnvChecker.h --- MobileGL/MG_Util/Config/EnvChecker.h | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 MobileGL/MG_Util/Config/EnvChecker.h 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 From c1598c93d2683d250e0af25f7ba6167504a8485f Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:55:57 +0800 Subject: [PATCH 055/116] Update ShaderSourceProcessor.cpp --- MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index 5f40ae065..1ee4d8d82 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -9,6 +9,8 @@ #include "ShaderSourceProcessor.h" #include "ShaderSourceProcessor_inline.h" +#include "../Config/EnvChecker.h" + namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { From 63846ebca6336c6147d640ba2f7ccd0443bffa51 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:00:48 +0800 Subject: [PATCH 056/116] Update ShaderCompiler.cpp --- MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 6291dd9f4..ebc2a2f4f 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -248,13 +248,6 @@ namespace MobileGL { Result>> ShaderCompiler::GetSpirvBinaryFromProgram( const ProgramBinaryAttrib& attrib) { - if (!attrib.program) { - ResultInfo r; - r.log = "Error: [GetSpirvBinary] Program is null"; - r.errc = -7; - return std::unexpected(r); - } - glslang::SpvOptions spvOptions; spvOptions.disableOptimizer = false; spvOptions.generateDebugInfo = true; From 1d2faf3d08305fe7d66618f467ded9af9009438b Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:41:29 +0800 Subject: [PATCH 057/116] Update Managers.cpp --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 4ad1521a1..723045e95 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -1076,7 +1076,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // TODO: check ESSL version supported by backend driver 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_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); From 0580d92997588481c851d6caa1ab4fb6ec5b8107 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 00:00:08 +0800 Subject: [PATCH 058/116] Update GL_Sync.cpp --- MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp | 29 ++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index d8cc8acb5..664549726 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -10,6 +10,11 @@ #include "MG_State/GLState/Core.h" +#include + +#include +#include + namespace MobileGL { namespace MG_Impl::GLImpl { GLsync FenceSync_Backend(GLenum condition, GLbitfield flags) { @@ -33,13 +38,29 @@ namespace MobileGL { void DeleteSync_State(GLsync sync) {} GLsync FenceSync(GLenum condition, GLbitfield flags) { - return 0; +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES + return g_GLESFuncs.glFenceSync(condition, flags); +#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN + return FenceSync_State(condition, flags); +#endif } GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { - return 0; +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES + return g_GLESFuncs.glClientWaitSync(sync, flags, timeout); +#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN + return ClientWaitSync_State(sync, flags, timeout); +#endif + + } + + void DeleteSync(GLsync sync) { +#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES + g_GLESFuncs.glDeleteSync(sync); +#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN + DeleteSync_State(sync); +#endif } - void DeleteSync(GLsync sync) {} } // namespace MG_Impl::GLImpl -} // namespace MobileGL \ No newline at end of file +} // namespace MobileGL From 57370cf33f509e58740588a4cb2d05725230f46a Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 00:01:18 +0800 Subject: [PATCH 059/116] Update Defines.h --- MobileGL/Defines.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index 80c7366e2..15a2a6c67 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -34,10 +34,10 @@ #define MOBILEGL_EGL_API MOBILEGL_API // ====================== MobileGL configurations ======================= // -#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_INFO +#define MOBILEGL_LOG_ACTIVE_LEVEL MOBILEGL_LOG_LEVEL_WARN -#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 @@ -48,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 From 9b1d07a56e3049d1b62d9e1c77147a52b48cd8f8 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 00:08:59 +0800 Subject: [PATCH 060/116] Update GL_Sync.cpp --- MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index 664549726..44237b5b3 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -11,6 +11,7 @@ #include "MG_State/GLState/Core.h" #include +#include #include #include @@ -39,7 +40,7 @@ namespace MobileGL { GLsync FenceSync(GLenum condition, GLbitfield flags) { #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - return g_GLESFuncs.glFenceSync(condition, flags); + return MG_Backend::DirectGLES::g_GLESFuncs.glFenceSync(condition, flags); #elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN return FenceSync_State(condition, flags); #endif @@ -47,7 +48,7 @@ namespace MobileGL { GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - return g_GLESFuncs.glClientWaitSync(sync, flags, timeout); + return MG_Backend::DirectGLES::g_GLESFuncs::g_GLESFuncs.glClientWaitSync(sync, flags, timeout); #elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN return ClientWaitSync_State(sync, flags, timeout); #endif @@ -56,7 +57,7 @@ namespace MobileGL { void DeleteSync(GLsync sync) { #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - g_GLESFuncs.glDeleteSync(sync); + MG_Backend::DirectGLES::g_GLESFuncs::g_GLESFuncs.glDeleteSync(sync); #elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN DeleteSync_State(sync); #endif From a4305b1e1366313c528643e45fcba36624ae9452 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 00:27:49 +0800 Subject: [PATCH 061/116] Update GL_Sync.cpp --- MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index 44237b5b3..9aa694f8c 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -48,7 +48,7 @@ namespace MobileGL { GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - return MG_Backend::DirectGLES::g_GLESFuncs::g_GLESFuncs.glClientWaitSync(sync, flags, timeout); + return MG_Backend::DirectGLES::g_GLESFuncs.glClientWaitSync(sync, flags, timeout); #elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN return ClientWaitSync_State(sync, flags, timeout); #endif @@ -57,7 +57,7 @@ namespace MobileGL { void DeleteSync(GLsync sync) { #if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - MG_Backend::DirectGLES::g_GLESFuncs::g_GLESFuncs.glDeleteSync(sync); + MG_Backend::DirectGLES::g_GLESFuncs.glDeleteSync(sync); #elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN DeleteSync_State(sync); #endif From 7491eb9daeed72ad344101ab4c8543533802091e Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:56:21 +0800 Subject: [PATCH 062/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index c0ba9ec02..8be42b10e 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1673,4 +1673,194 @@ namespace MobileGL::MG_Backend::DirectGLES { } } + namespace { + void ClearBufferSubDataImpl(GLenum target, GLintptr offset, GLsizeiptr size, + GLenum internalFormat, GLenum format, GLenum type, + const void* data) { + + GLint previousBinding = 0; + g_GLESFuncs.glGetIntegerv(GetBufferBindingEnum(target), &previousBinding); + + auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(ConvertGLEnumToBufferTarget(target)).GetBoundObject(); + if (!bufferObject) { + MGLOG_E("ClearBufferSubData: No buffer bound to target 0x%x", target); + return; + } + + BufferImpl::CreateAndSyncBufferObject(bufferObject); + + const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObject); + if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { + MGLOG_E("ClearBufferSubData: No backend buffer found"); + return; + } + + GLuint bufferId = backendBufferIt->second->GetBackendBufferId(); + + { + g_GLESFuncs.glBindBuffer(target, bufferId); + + void* mappedPtr = g_GLESFuncs.glMapBufferRange(target, offset, size, + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); + if (mappedPtr) { + if (data) { + memcpy(mappedPtr, data, size); + } else { + memset(mappedPtr, 0, size); + } + + g_GLESFuncs.glUnmapBuffer(target); + } else { + MGLOG_E("ClearBufferSubData: Failed to map buffer range"); + } + } + + g_GLESFuncs.glBindBuffer(target, previousBinding); + + bufferObject->MarkDirty(offset, size); + } + + GLenum GetBufferBindingEnum(GLenum target) { + switch(target) { + case GL_ARRAY_BUFFER: return GL_ARRAY_BUFFER_BINDING; + case GL_ELEMENT_ARRAY_BUFFER: return GL_ELEMENT_ARRAY_BUFFER_BINDING; + case GL_UNIFORM_BUFFER: return GL_UNIFORM_BUFFER_BINDING; + case GL_SHADER_STORAGE_BUFFER: return GL_SHADER_STORAGE_BUFFER_BINDING; + case GL_PIXEL_PACK_BUFFER: return GL_PIXEL_PACK_BUFFER_BINDING; + case GL_PIXEL_UNPACK_BUFFER: return GL_PIXEL_UNPACK_BUFFER_BINDING; + case GL_DRAW_INDIRECT_BUFFER: return GL_DRAW_INDIRECT_BUFFER_BINDING; + default: return 0; + } + } + + BufferTarget ConvertGLEnumToBufferTarget(GLenum target) { + switch(target) { + case GL_ARRAY_BUFFER: return BufferTarget::Array; + case GL_ELEMENT_ARRAY_BUFFER: return BufferTarget::ElementArray; + case GL_UNIFORM_BUFFER: return BufferTarget::Uniform; + case GL_SHADER_STORAGE_BUFFER: return BufferTarget::ShaderStorage; + case GL_PIXEL_PACK_BUFFER: return BufferTarget::PixelPack; + case GL_PIXEL_UNPACK_BUFFER: return BufferTarget::PixelUnpack; + case GL_DRAW_INDIRECT_BUFFER: return BufferTarget::DrawIndirect; + default: return BufferTarget::Unknown; + } + } +} + +void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data) { +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER + DebugImpl::OpenGLScopeMarker marker(__func__); +#endif + DebugImpl::ErrorLopper errorLopper; + + MGLOG_D("ClearBufferSubData: target=0x%x internalformat=%s offset=%lld size=%lld format=%s type=%s data=%p", + target, MG_Util::ConvertGLEnumToString(internalformat).c_str(), + (long long)offset, (long long)size, + MG_Util::ConvertGLEnumToString(format).c_str(), + MG_Util::ConvertGLEnumToString(type).c_str(), data); + + if (size <= 0) { + MGLOG_W("ClearBufferSubData: size must be positive"); + return; + } + + if (offset < 0) { + MGLOG_E("ClearBufferSubData: offset must be non-negative"); + return; + } + + auto bufferTarget = ConvertGLEnumToBufferTarget(target); + if (bufferTarget == BufferTarget::Unknown) { + MGLOG_E("ClearBufferSubData: Unsupported target 0x%x", target); + return; + } + + auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget).GetBoundObject(); + if (!bufferObject) { + MGLOG_E("ClearBufferSubData: No buffer bound to target 0x%x", target); + return; + } + + if (offset + size > bufferObject->GetSize()) { + MGLOG_E("ClearBufferSubData: Range [%lld, %lld) exceeds buffer size %llu", + (long long)offset, (long long)(offset + size), + (unsigned long long)bufferObject->GetSize()); + return; + } + + TextureImpl::SyncNeccessaryTextures(); + FramebufferImpl::SyncCurrentFBO(); + + if (data) { + ClearBufferSubDataImpl(target, offset, size, internalformat, format, type, data); + } else { + ClearBufferSubDataImpl(target, offset, size, internalformat, format, type, nullptr); + } + + errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { + MGLOG_E("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); + }); + + MGLOG_D("ClearBufferSubData: completed"); +} + +void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data) { +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER + DebugImpl::OpenGLScopeMarker marker(__func__); +#endif + + MGLOG_D("ClearNamedBufferSubData: buffer=%u internalformat=%s offset=%lld size=%lld format=%s type=%s data=%p", + buffer, MG_Util::ConvertGLEnumToString(internalformat).c_str(), + (long long)offset, (long long)size, + MG_Util::ConvertGLEnumToString(format).c_str(), + MG_Util::ConvertGLEnumToString(type).c_str(), data); + + SharedPtr targetBuffer; + for (const auto& pair : BufferImpl::g_backendBufferObjects) { + if (pair.second && pair.second->GetBackendBufferId() == buffer) { + targetBuffer = pair.first; + break; + } + } + + if (!targetBuffer) { + MGLOG_E("ClearNamedBufferSubData: Buffer %u not found", buffer); + return; + } + + GLint previousBinding = 0; + g_GLESFuncs.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &previousBinding); + + const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(targetBuffer); + if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) { + backendBufferIt->second->Bind(GL_ARRAY_BUFFER); + + void* mappedPtr = g_GLESFuncs.glMapBufferRange(GL_ARRAY_BUFFER, offset, size, + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); + if (mappedPtr) { + if (data) { + Memcpy(mappedPtr, data, size); + } else { + Memset(mappedPtr, 0, size); + } + g_GLESFuncs.glUnmapBuffer(GL_ARRAY_BUFFER); + } + } + + g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, previousBinding); + + targetBuffer->MarkDirty(offset, size); + + MGLOG_D("ClearNamedBufferSubData: completed"); +} + +void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { + auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(ConvertGLEnumToBufferTarget(target)).GetBoundObject(); + if (bufferObject) { + ClearBufferSubData(target, internalformat, 0, bufferObject->GetSize(), format, type, data); + } +} + } // namespace MobileGL::MG_Backend::DirectGLES From a00f6f4070424ef213272ec4ab80ced34de8e560 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:58:08 +0800 Subject: [PATCH 063/116] Update DirectGLES.h --- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 4b907fa1c..1d80069b4 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -62,6 +62,10 @@ namespace MobileGL::MG_Backend::DirectGLES { void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs); void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities); void DestroyEGLContext(); + void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data); + void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data); + void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data); + extern MG_External::EGLFunctionsTable g_EGLFuncs; extern MG_External::GLESFunctionsTable g_GLESFuncs; From cb6782507bb8349b8be981f8a0b6e48e1356f8c2 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:06:00 +0800 Subject: [PATCH 064/116] Update GL_Drawing.cpp --- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 70ebd6c0a..7005686bc 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -155,6 +155,22 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Backend::gBackendFunctionsTable.GL.DrawArraysIndirect(mode, indirect); } + void ClearBufferSubData_Backend(GLenum target, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data) { + MG_Backend::gBackendFunctionsTable.GL.ClearBufferSubData(target, internalformat, offset, + size, format, type, data); + } + + void ClearNamedBufferSubData_Backend(GLuint buffer, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data) { + MG_Backend::gBackendFunctionsTable.GL.ClearNamedBufferSubData(buffer, internalformat, offset, + size, format, type, data); + } + + void ClearBufferData_Backend(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { + MG_Backend::gBackendFunctionsTable.GL.ClearBufferData(target, internalformat, format, type, data); + } + /* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */ void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride); @@ -236,4 +252,20 @@ namespace MobileGL::MG_Impl::GLImpl { DrawElements_Backend(mode, count, type, indices); } + void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data) { + ClearBufferSubData_Backend(target, internalformat, offset, + size, format, type, data); + } + + void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data) { + ClearNamedBufferSubData_Backend(buffer, internalformat, offset, + size, format, type, data); + } + + void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { + ClearBufferData_Backend(target, internalformat, format, type, data); + } + } // namespace MobileGL::MG_Impl::GLImpl From d31706f128fb46a67f54c8b642ebd647b2213048 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:08:51 +0800 Subject: [PATCH 065/116] Update GL_Drawing.h --- MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index fe1f03391..abcdb4cfe 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -38,5 +38,14 @@ namespace MobileGL { GLsizei drawcount, const GLint* basevertex); void Clear(GLbitfield mask); void DrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); + + void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data); + + void ClearNamedBufferSubData(buffer, internalformat, offset, + size, format, type, data); + + void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data); + } // namespace MG_Impl::GLImpl } // namespace MobileGL From ffbf75cd2ce3ae254595e1237c5a02061948b27a Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:14:50 +0800 Subject: [PATCH 066/116] Update Definitions.cpp --- MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 30e876794..5fbc45ff7 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -16,6 +16,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__) { @@ -270,10 +271,10 @@ DECLARE_GL_FUNCTION_HEAD(void, GetActiveUniformBlockName, GLuint program, GLuint DECLARE_GL_FUNCTION_HEAD(void, UniformBlockBinding, GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, UniformBlockBinding, program, uniformBlockIndex, uniformBlockBinding) DECLARE_GL_FUNCTION_HEAD(void, DrawArraysInstanced, GLenum mode, GLint first, GLsizei count, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawArraysInstanced, mode, first, count, instancecount) DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstanced, GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsInstanced, mode, count, type, indices, instancecount) -DECLARE_GL_FUNCTION_STUB_HEAD(GLsync, FenceSync, GLenum condition, GLbitfield flags) DECLARE_GL_FUNCTION_STUB_END(GLsync, FenceSync, condition, flags) +DECLARE_GL_FUNCTION_HEAD(GLsync, FenceSync, GLenum condition, GLbitfield flags) DECLARE_GL_FUNCTION_END(GLsync, FenceSync, condition, flags) DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsSync, GLsync sync) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsSync, sync) -DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteSync, GLsync sync) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteSync, sync) -DECLARE_GL_FUNCTION_STUB_HEAD(GLenum, ClientWaitSync, GLsync sync, GLbitfield flags, GLuint64 timeout) DECLARE_GL_FUNCTION_STUB_END(GLenum, ClientWaitSync, sync, flags, timeout) +DECLARE_GL_FUNCTION_HEAD(void, DeleteSync, GLsync sync) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteSync, sync) +DECLARE_GL_FUNCTION_HEAD(GLenum, ClientWaitSync, GLsync sync, GLbitfield flags, GLuint64 timeout) DECLARE_GL_FUNCTION_END(GLenum, ClientWaitSync, sync, flags, timeout) DECLARE_GL_FUNCTION_STUB_HEAD(void, WaitSync, GLsync sync, GLbitfield flags, GLuint64 timeout) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WaitSync, sync, flags, timeout) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInteger64v, GLenum pname, GLint64* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInteger64v, pname, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetSynciv, GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetSynciv, sync, pname, bufSize, length, values) @@ -968,8 +969,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLen DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount) DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) +DECLARE_GL_FUNCTION_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) +DECLARE_GL_FUNCTION_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexImage, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexImage, texture, level) @@ -1003,7 +1004,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr s DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedBufferSubData, GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedBufferSubData, buffer, offset, size, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyNamedBufferSubData, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyNamedBufferSubData, readBuffer, writeBuffer, readOffset, writeOffset, size) DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedBufferData, GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedBufferData, buffer, internalformat, format, type, data) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedBufferSubData, GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedBufferSubData, buffer, internalformat, offset, size, format, type, data) +DECLARE_GL_FUNCTION_HEAD(void, ClearNamedBufferSubData, GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedBufferSubData, buffer, internalformat, offset, size, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, UnmapNamedBuffer, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END(GLboolean, UnmapNamedBuffer, buffer) DECLARE_GL_FUNCTION_STUB_HEAD(void, FlushMappedNamedBufferRange, GLuint buffer, GLintptr offset, GLsizeiptr length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FlushMappedNamedBufferRange, buffer, offset, length) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedBufferParameteriv, GLuint buffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedBufferParameteriv, buffer, pname, params) From 1dded660a7a31af203ee6c86fbe272d45d85dcd2 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:29:43 +0800 Subject: [PATCH 067/116] Update GL_Drawing.h --- MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index abcdb4cfe..b4776dfe4 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -42,8 +42,8 @@ namespace MobileGL { void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data); - void ClearNamedBufferSubData(buffer, internalformat, offset, - size, format, type, data); + void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data); void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data); From b928d9ba182cc732938aaeb743350dd3ba1bb357 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:05:39 +0800 Subject: [PATCH 068/116] Update BackendObject_DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index f4d167a6b..9ebdac6ca 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -147,6 +147,9 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.GenerateMipmap = GenerateMipmap; funcsTable.GL.ReadPixels = ReadPixels; funcsTable.GL.GetTexImage = GetTexImage; + funcsTable.GL.ClearBufferData = ClearBufferData; + funcsTable.GL.ClearBufferSubData = ClearBufferSubData; + funcsTable.GL.ClearNamedBufferSubData = ClearNamedBufferSubData; funcsTableInitialized = true; } return funcsTable; From e32740bfb437e0c9ada0b5b6bef24c905297e149 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:12:56 +0800 Subject: [PATCH 069/116] Update BackendObject.h --- MobileGL/MG_Backend/BackendObject.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 6833afe07..46bd7392b 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -64,6 +64,15 @@ namespace MobileGL { void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); void (*GetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); + + void (*ClearNamedBufferSubData)(GLuint buffer, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data); + + void (*ClearBufferData)(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data); + + void (*ClearBufferSubData)(GLenum target, GLenum internalformat, GLintptr offset, + GLsizeiptr size, GLenum format, GLenum type, const void* data); + }; struct GlobalBackendFunctionsTable { GLFunctionsTable GL; From 6f390ef8eb8795ebfa0fedf0d8f3f099f093f4a4 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:26:27 +0800 Subject: [PATCH 070/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 8be42b10e..b6b666c63 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1674,6 +1674,32 @@ namespace MobileGL::MG_Backend::DirectGLES { } namespace { + GLenum GetBufferBindingEnum(GLenum target) { + switch(target) { + case GL_ARRAY_BUFFER: return GL_ARRAY_BUFFER_BINDING; + case GL_ELEMENT_ARRAY_BUFFER: return GL_ELEMENT_ARRAY_BUFFER_BINDING; + case GL_UNIFORM_BUFFER: return GL_UNIFORM_BUFFER_BINDING; + case GL_SHADER_STORAGE_BUFFER: return GL_SHADER_STORAGE_BUFFER_BINDING; + case GL_PIXEL_PACK_BUFFER: return GL_PIXEL_PACK_BUFFER_BINDING; + case GL_PIXEL_UNPACK_BUFFER: return GL_PIXEL_UNPACK_BUFFER_BINDING; + case GL_DRAW_INDIRECT_BUFFER: return GL_DRAW_INDIRECT_BUFFER_BINDING; + default: return 0; + } + } + + BufferTarget ConvertGLEnumToBufferTarget(GLenum target) { + switch(target) { + case GL_ARRAY_BUFFER: return BufferTarget::Array; + case GL_ELEMENT_ARRAY_BUFFER: return BufferTarget::ElementArray; + case GL_UNIFORM_BUFFER: return BufferTarget::Uniform; + case GL_SHADER_STORAGE_BUFFER: return BufferTarget::ShaderStorage; + case GL_PIXEL_PACK_BUFFER: return BufferTarget::PixelPack; + case GL_PIXEL_UNPACK_BUFFER: return BufferTarget::PixelUnpack; + case GL_DRAW_INDIRECT_BUFFER: return BufferTarget::DrawIndirect; + default: return BufferTarget::Unknown; + } + } + void ClearBufferSubDataImpl(GLenum target, GLintptr offset, GLsizeiptr size, GLenum internalFormat, GLenum format, GLenum type, const void* data) { @@ -1720,31 +1746,6 @@ namespace MobileGL::MG_Backend::DirectGLES { bufferObject->MarkDirty(offset, size); } - GLenum GetBufferBindingEnum(GLenum target) { - switch(target) { - case GL_ARRAY_BUFFER: return GL_ARRAY_BUFFER_BINDING; - case GL_ELEMENT_ARRAY_BUFFER: return GL_ELEMENT_ARRAY_BUFFER_BINDING; - case GL_UNIFORM_BUFFER: return GL_UNIFORM_BUFFER_BINDING; - case GL_SHADER_STORAGE_BUFFER: return GL_SHADER_STORAGE_BUFFER_BINDING; - case GL_PIXEL_PACK_BUFFER: return GL_PIXEL_PACK_BUFFER_BINDING; - case GL_PIXEL_UNPACK_BUFFER: return GL_PIXEL_UNPACK_BUFFER_BINDING; - case GL_DRAW_INDIRECT_BUFFER: return GL_DRAW_INDIRECT_BUFFER_BINDING; - default: return 0; - } - } - - BufferTarget ConvertGLEnumToBufferTarget(GLenum target) { - switch(target) { - case GL_ARRAY_BUFFER: return BufferTarget::Array; - case GL_ELEMENT_ARRAY_BUFFER: return BufferTarget::ElementArray; - case GL_UNIFORM_BUFFER: return BufferTarget::Uniform; - case GL_SHADER_STORAGE_BUFFER: return BufferTarget::ShaderStorage; - case GL_PIXEL_PACK_BUFFER: return BufferTarget::PixelPack; - case GL_PIXEL_UNPACK_BUFFER: return BufferTarget::PixelUnpack; - case GL_DRAW_INDIRECT_BUFFER: return BufferTarget::DrawIndirect; - default: return BufferTarget::Unknown; - } - } } void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, From d9d59dc8ec0001c594f59f924ef206223d165dd6 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:30:41 +0800 Subject: [PATCH 071/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 255 ++++++++---------- 1 file changed, 118 insertions(+), 137 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index b6b666c63..2e7df91f8 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1673,151 +1673,139 @@ namespace MobileGL::MG_Backend::DirectGLES { } } - namespace { - GLenum GetBufferBindingEnum(GLenum target) { - switch(target) { - case GL_ARRAY_BUFFER: return GL_ARRAY_BUFFER_BINDING; - case GL_ELEMENT_ARRAY_BUFFER: return GL_ELEMENT_ARRAY_BUFFER_BINDING; - case GL_UNIFORM_BUFFER: return GL_UNIFORM_BUFFER_BINDING; - case GL_SHADER_STORAGE_BUFFER: return GL_SHADER_STORAGE_BUFFER_BINDING; - case GL_PIXEL_PACK_BUFFER: return GL_PIXEL_PACK_BUFFER_BINDING; - case GL_PIXEL_UNPACK_BUFFER: return GL_PIXEL_UNPACK_BUFFER_BINDING; - case GL_DRAW_INDIRECT_BUFFER: return GL_DRAW_INDIRECT_BUFFER_BINDING; - default: return 0; - } - } - - BufferTarget ConvertGLEnumToBufferTarget(GLenum target) { - switch(target) { - case GL_ARRAY_BUFFER: return BufferTarget::Array; - case GL_ELEMENT_ARRAY_BUFFER: return BufferTarget::ElementArray; - case GL_UNIFORM_BUFFER: return BufferTarget::Uniform; - case GL_SHADER_STORAGE_BUFFER: return BufferTarget::ShaderStorage; - case GL_PIXEL_PACK_BUFFER: return BufferTarget::PixelPack; - case GL_PIXEL_UNPACK_BUFFER: return BufferTarget::PixelUnpack; - case GL_DRAW_INDIRECT_BUFFER: return BufferTarget::DrawIndirect; + // ==================== 实现 glClearBufferSubData 相关函数 ==================== + +namespace { + // 将 OpenGL 枚举转换为你的 BufferTarget 枚举 + BufferTarget GLEnumToBufferTarget(GLenum target) { + switch (target) { + case GL_ARRAY_BUFFER: return BufferTarget::Vertex; + case GL_ELEMENT_ARRAY_BUFFER: return BufferTarget::Index; + case GL_UNIFORM_BUFFER: return BufferTarget::Uniform; + case GL_COPY_READ_BUFFER: return BufferTarget::CopyRead; + case GL_COPY_WRITE_BUFFER: return BufferTarget::CopyWrite; + case GL_PIXEL_PACK_BUFFER: return BufferTarget::PixelPack; + case GL_PIXEL_UNPACK_BUFFER: return BufferTarget::PixelUnpack; + case GL_QUERY_BUFFER: return BufferTarget::Query; + case GL_TEXTURE_BUFFER: return BufferTarget::Texture; + case GL_TRANSFORM_FEEDBACK_BUFFER: return BufferTarget::TransformFeedback; + case GL_ATOMIC_COUNTER_BUFFER: return BufferTarget::AtomicCounter; + case GL_DISPATCH_INDIRECT_BUFFER: return BufferTarget::DispatchIndirect; + case GL_DRAW_INDIRECT_BUFFER: return BufferTarget::DrawIndirect; + case GL_SHADER_STORAGE_BUFFER: return BufferTarget::ShaderStorage; default: return BufferTarget::Unknown; } } - void ClearBufferSubDataImpl(GLenum target, GLintptr offset, GLsizeiptr size, - GLenum internalFormat, GLenum format, GLenum type, - const void* data) { - - GLint previousBinding = 0; - g_GLESFuncs.glGetIntegerv(GetBufferBindingEnum(target), &previousBinding); - - auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(ConvertGLEnumToBufferTarget(target)).GetBoundObject(); - if (!bufferObject) { - MGLOG_E("ClearBufferSubData: No buffer bound to target 0x%x", target); - return; - } - - BufferImpl::CreateAndSyncBufferObject(bufferObject); - - const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObject); - if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) { - MGLOG_E("ClearBufferSubData: No backend buffer found"); - return; - } - - GLuint bufferId = backendBufferIt->second->GetBackendBufferId(); - - { - g_GLESFuncs.glBindBuffer(target, bufferId); - - void* mappedPtr = g_GLESFuncs.glMapBufferRange(target, offset, size, - GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); - if (mappedPtr) { - if (data) { - memcpy(mappedPtr, data, size); - } else { - memset(mappedPtr, 0, size); - } - - g_GLESFuncs.glUnmapBuffer(target); - } else { - MGLOG_E("ClearBufferSubData: Failed to map buffer range"); - } + // 获取对应目标的绑定查询枚举(用于保存/恢复状态) + GLenum GetBindingEnum(GLenum target) { + switch (target) { + case GL_ARRAY_BUFFER: return GL_ARRAY_BUFFER_BINDING; + case GL_ELEMENT_ARRAY_BUFFER: return GL_ELEMENT_ARRAY_BUFFER_BINDING; + case GL_UNIFORM_BUFFER: return GL_UNIFORM_BUFFER_BINDING; + case GL_COPY_READ_BUFFER: return GL_COPY_READ_BUFFER_BINDING; + case GL_COPY_WRITE_BUFFER: return GL_COPY_WRITE_BUFFER_BINDING; + case GL_PIXEL_PACK_BUFFER: return GL_PIXEL_PACK_BUFFER_BINDING; + case GL_PIXEL_UNPACK_BUFFER: return GL_PIXEL_UNPACK_BUFFER_BINDING; + case GL_QUERY_BUFFER: return GL_QUERY_BUFFER_BINDING; + case GL_TEXTURE_BUFFER: return GL_TEXTURE_BUFFER_BINDING; + case GL_TRANSFORM_FEEDBACK_BUFFER: return GL_TRANSFORM_FEEDBACK_BUFFER_BINDING; + case GL_ATOMIC_COUNTER_BUFFER: return GL_ATOMIC_COUNTER_BUFFER_BINDING; + case GL_DISPATCH_INDIRECT_BUFFER: return GL_DISPATCH_INDIRECT_BUFFER_BINDING; + case GL_DRAW_INDIRECT_BUFFER: return GL_DRAW_INDIRECT_BUFFER_BINDING; + case GL_SHADER_STORAGE_BUFFER: return GL_SHADER_STORAGE_BUFFER_BINDING; + default: return 0; } - - g_GLESFuncs.glBindBuffer(target, previousBinding); - - bufferObject->MarkDirty(offset, size); } - } -void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, +// 清除当前绑定到 target 的缓冲区的子数据范围 +void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER DebugImpl::OpenGLScopeMarker marker(__func__); #endif DebugImpl::ErrorLopper errorLopper; - - MGLOG_D("ClearBufferSubData: target=0x%x internalformat=%s offset=%lld size=%lld format=%s type=%s data=%p", - target, MG_Util::ConvertGLEnumToString(internalformat).c_str(), - (long long)offset, (long long)size, - MG_Util::ConvertGLEnumToString(format).c_str(), - MG_Util::ConvertGLEnumToString(type).c_str(), data); - - if (size <= 0) { - MGLOG_W("ClearBufferSubData: size must be positive"); - return; - } - - if (offset < 0) { - MGLOG_E("ClearBufferSubData: offset must be non-negative"); + + MGLOG_D("ClearBufferSubData: target=0x%x offset=%lld size=%lld", target, (long long)offset, (long long)size); + + // 参数检查 + if (size <= 0 || offset < 0) { + MGLOG_E("ClearBufferSubData: invalid offset or size"); return; } - - auto bufferTarget = ConvertGLEnumToBufferTarget(target); - if (bufferTarget == BufferTarget::Unknown) { - MGLOG_E("ClearBufferSubData: Unsupported target 0x%x", target); + + BufferTarget mgTarget = GLEnumToBufferTarget(target); + if (mgTarget == BufferTarget::Unknown) { + MGLOG_E("ClearBufferSubData: unsupported target 0x%x", target); return; } - - auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(bufferTarget).GetBoundObject(); - if (!bufferObject) { - MGLOG_E("ClearBufferSubData: No buffer bound to target 0x%x", target); + + // 获取当前绑定的缓冲区对象 + auto bufferObj = MG_State::pGLContext->GetBufferBindingSlot(mgTarget).GetBoundObject(); + if (!bufferObj) { + MGLOG_E("ClearBufferSubData: no buffer bound to target"); return; } - - if (offset + size > bufferObject->GetSize()) { - MGLOG_E("ClearBufferSubData: Range [%lld, %lld) exceeds buffer size %llu", - (long long)offset, (long long)(offset + size), - (unsigned long long)bufferObject->GetSize()); + + // 检查范围 + if (offset + size > bufferObj->GetSize()) { + MGLOG_E("ClearBufferSubData: range exceeds buffer size"); return; } - + + // 同步必要的资源 TextureImpl::SyncNeccessaryTextures(); FramebufferImpl::SyncCurrentFBO(); - - if (data) { - ClearBufferSubDataImpl(target, offset, size, internalformat, format, type, data); + BufferImpl::CreateAndSyncBufferObject(bufferObj); // 确保后端存在 + + // 获取后端缓冲区 ID + auto it = BufferImpl::g_backendBufferObjects.find(bufferObj); + if (it == BufferImpl::g_backendBufferObjects.end()) { + MGLOG_E("ClearBufferSubData: backend buffer not found"); + return; + } + GLuint bufferId = it->second->GetBackendBufferId(); + + // 保存当前绑定 + GLint prevBinding = 0; + GLenum bindingEnum = GetBindingEnum(target); + if (bindingEnum) g_GLESFuncs.glGetIntegerv(bindingEnum, &prevBinding); + + // 绑定并映射 + g_GLESFuncs.glBindBuffer(target, bufferId); + void* mapped = g_GLESFuncs.glMapBufferRange(target, offset, size, + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); + if (mapped) { + if (data) + Memcpy(mapped, data, size); + else + Memset(mapped, 0, size); + g_GLESFuncs.glUnmapBuffer(target); } else { - ClearBufferSubDataImpl(target, offset, size, internalformat, format, type, nullptr); + MGLOG_E("ClearBufferSubData: failed to map buffer range"); } - - errorLopper.Loop([file = __FILE__, line = __LINE__](auto err) { - MGLOG_E("ES error (%s:%d): %s", file, line, MG_Util::ConvertGLEnumToString(err).c_str()); + + // 恢复绑定 + g_GLESFuncs.glBindBuffer(target, prevBinding); + + // 标记缓冲区为脏(如果需要后续同步) + bufferObj->MarkDirty(offset, size); + + errorLopper.Loop([](GLenum err) { + MGLOG_D("ClearBufferSubData GL error: %s", MG_Util::ConvertGLEnumToString(err).c_str()); }); - - MGLOG_D("ClearBufferSubData: completed"); } +// 按名称清除缓冲区子数据(如果后端需要,通常前端不会直接调用此函数) void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER DebugImpl::OpenGLScopeMarker marker(__func__); #endif - - MGLOG_D("ClearNamedBufferSubData: buffer=%u internalformat=%s offset=%lld size=%lld format=%s type=%s data=%p", - buffer, MG_Util::ConvertGLEnumToString(internalformat).c_str(), - (long long)offset, (long long)size, - MG_Util::ConvertGLEnumToString(format).c_str(), - MG_Util::ConvertGLEnumToString(type).c_str(), data); - + MGLOG_D("ClearNamedBufferSubData: buffer=%u offset=%lld size=%lld", buffer, (long long)offset, (long long)size); + + // 查找对应的缓冲区对象(遍历所有后端缓冲区) SharedPtr targetBuffer; for (const auto& pair : BufferImpl::g_backendBufferObjects) { if (pair.second && pair.second->GetBackendBufferId() == buffer) { @@ -1825,36 +1813,29 @@ void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offs break; } } - + if (!targetBuffer) { - MGLOG_E("ClearNamedBufferSubData: Buffer %u not found", buffer); + MGLOG_E("ClearNamedBufferSubData: buffer %u not found", buffer); return; } - - GLint previousBinding = 0; - g_GLESFuncs.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &previousBinding); - - const auto& backendBufferIt = BufferImpl::g_backendBufferObjects.find(targetBuffer); - if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) { - backendBufferIt->second->Bind(GL_ARRAY_BUFFER); - - void* mappedPtr = g_GLESFuncs.glMapBufferRange(GL_ARRAY_BUFFER, offset, size, - GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); - if (mappedPtr) { - if (data) { - Memcpy(mappedPtr, data, size); - } else { - Memset(mappedPtr, 0, size); - } - g_GLESFuncs.glUnmapBuffer(GL_ARRAY_BUFFER); - } + + // 临时绑定到 ARRAY_BUFFER 进行操作 + GLint prevBinding = 0; + g_GLESFuncs.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevBinding); + g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, buffer); + + void* mapped = g_GLESFuncs.glMapBufferRange(GL_ARRAY_BUFFER, offset, size, + GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); + if (mapped) { + if (data) + Memcpy(mapped, data, size); + else + Memset(mapped, 0, size); + g_GLESFuncs.glUnmapBuffer(GL_ARRAY_BUFFER); } - - g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, previousBinding); - + + g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, prevBinding); targetBuffer->MarkDirty(offset, size); - - MGLOG_D("ClearNamedBufferSubData: completed"); } void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { From 1c0b09bef147ffb11404871e299a3e117b63369d Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:32:05 +0800 Subject: [PATCH 072/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 2e7df91f8..43a17ca60 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace MobileGL::MG_Backend::DirectGLES { MG_External::EGLFunctionsTable g_EGLFuncs; From 92fb9fa03dcba1619d070bfdd74bc0250c95ccbf Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:40:40 +0800 Subject: [PATCH 073/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 43a17ca60..cbb4ebf7a 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1791,7 +1791,7 @@ void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, g_GLESFuncs.glBindBuffer(target, prevBinding); // 标记缓冲区为脏(如果需要后续同步) - bufferObj->MarkDirty(offset, size); + //bufferObj->MarkDirty(offset, size); errorLopper.Loop([](GLenum err) { MGLOG_D("ClearBufferSubData GL error: %s", MG_Util::ConvertGLEnumToString(err).c_str()); @@ -1836,11 +1836,11 @@ void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offs } g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, prevBinding); - targetBuffer->MarkDirty(offset, size); + //targetBuffer->MarkDirty(offset, size); } void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { - auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(ConvertGLEnumToBufferTarget(target)).GetBoundObject(); + auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(GLEnumToBufferTarget(target)).GetBoundObject(); if (bufferObject) { ClearBufferSubData(target, internalformat, 0, bufferObject->GetSize(), format, type, data); } From 1b8576257385347400c67acad312e8a5e261cb8a Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:03:25 +0800 Subject: [PATCH 074/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index cbb4ebf7a..f24dda2c7 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1733,26 +1733,26 @@ void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, // 参数检查 if (size <= 0 || offset < 0) { MGLOG_E("ClearBufferSubData: invalid offset or size"); - return; + //return; } BufferTarget mgTarget = GLEnumToBufferTarget(target); if (mgTarget == BufferTarget::Unknown) { MGLOG_E("ClearBufferSubData: unsupported target 0x%x", target); - return; + //return; } // 获取当前绑定的缓冲区对象 auto bufferObj = MG_State::pGLContext->GetBufferBindingSlot(mgTarget).GetBoundObject(); if (!bufferObj) { MGLOG_E("ClearBufferSubData: no buffer bound to target"); - return; + //return; } // 检查范围 if (offset + size > bufferObj->GetSize()) { MGLOG_E("ClearBufferSubData: range exceeds buffer size"); - return; + //return; } // 同步必要的资源 @@ -1764,7 +1764,7 @@ void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, auto it = BufferImpl::g_backendBufferObjects.find(bufferObj); if (it == BufferImpl::g_backendBufferObjects.end()) { MGLOG_E("ClearBufferSubData: backend buffer not found"); - return; + //return; } GLuint bufferId = it->second->GetBackendBufferId(); @@ -1816,7 +1816,7 @@ void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offs } if (!targetBuffer) { - MGLOG_E("ClearNamedBufferSubData: buffer %u not found", buffer); + //MGLOG_E("ClearNamedBufferSubData: buffer %u not found", buffer); return; } From 485ff69449efe9a65f8d62bbe75957f5b3bcec30 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:10:29 +0800 Subject: [PATCH 075/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 74 +++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index f24dda2c7..f9a4c95c5 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1730,29 +1730,75 @@ void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, MGLOG_D("ClearBufferSubData: target=0x%x offset=%lld size=%lld", target, (long long)offset, (long long)size); - // 参数检查 - if (size <= 0 || offset < 0) { - MGLOG_E("ClearBufferSubData: invalid offset or size"); - //return; + + // 1. 基本参数有效性 + if (size == 0) { + MGLOG_D("ClearBufferSubData: size is 0, nothing to do"); + return; // 不是错误,直接返回 } - + + if (size < 0) { + MGLOG_E("ClearBufferSubData: size is negative: %lld", (long long)size); + return; + } + + if (offset < 0) { + MGLOG_E("ClearBufferSubData: offset is negative: %lld", (long long)offset); + return; + } + + // 2. target 有效性 BufferTarget mgTarget = GLEnumToBufferTarget(target); if (mgTarget == BufferTarget::Unknown) { MGLOG_E("ClearBufferSubData: unsupported target 0x%x", target); - //return; + return; } - - // 获取当前绑定的缓冲区对象 + + // 3. 缓冲区对象存在性 auto bufferObj = MG_State::pGLContext->GetBufferBindingSlot(mgTarget).GetBoundObject(); if (!bufferObj) { - MGLOG_E("ClearBufferSubData: no buffer bound to target"); - //return; + MGLOG_E("ClearBufferSubData: no buffer bound to target 0x%x", target); + return; + } + + // 4. 缓冲区状态 + if (!bufferObj->IsInitialized()) { + MGLOG_E("ClearBufferSubData: buffer not initialized"); + return; + } + + // 5. 缓冲区大小检查 + GLsizeiptr bufferSize = bufferObj->GetSize(); + if (bufferSize <= 0) { + MGLOG_E("ClearBufferSubData: buffer size is invalid: %lld", (long long)bufferSize); + return; + } + + // 6. offset 范围检查 + if (offset >= bufferSize) { + MGLOG_E("ClearBufferSubData: offset (%lld) >= buffer size (%lld)", + (long long)offset, (long long)bufferSize); + return; + } + + // 7. 整数溢出检查 + if (offset > MAX_BUFFER_SIZE - size) { + MGLOG_E("ClearBufferSubData: offset+size would cause integer overflow"); + return; + } + + // 8. 最终范围检查 + if (offset + size > bufferSize) { + MGLOG_E("ClearBufferSubData: range [%lld, %lld) exceeds buffer size %lld", + (long long)offset, (long long)(offset + size), (long long)bufferSize); + return; } - // 检查范围 - if (offset + size > bufferObj->GetSize()) { - MGLOG_E("ClearBufferSubData: range exceeds buffer size"); - //return; + // 确保起始位置在缓冲区范围内 + if (offset >= bufferSize) { + MGLOG_E("ClearBufferSubData: offset (%lld) outside buffer (size %lld)", + (long long)offset, (long long)bufferSize); + return; } // 同步必要的资源 From 7073e9eccdd62293d5870e0d03ecdba6a4d460c7 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sat, 21 Feb 2026 10:31:17 +0800 Subject: [PATCH 076/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index f9a4c95c5..3073f6d51 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1761,12 +1761,6 @@ void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, return; } - // 4. 缓冲区状态 - if (!bufferObj->IsInitialized()) { - MGLOG_E("ClearBufferSubData: buffer not initialized"); - return; - } - // 5. 缓冲区大小检查 GLsizeiptr bufferSize = bufferObj->GetSize(); if (bufferSize <= 0) { @@ -1781,12 +1775,6 @@ void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, return; } - // 7. 整数溢出检查 - if (offset > MAX_BUFFER_SIZE - size) { - MGLOG_E("ClearBufferSubData: offset+size would cause integer overflow"); - return; - } - // 8. 最终范围检查 if (offset + size > bufferSize) { MGLOG_E("ClearBufferSubData: range [%lld, %lld) exceeds buffer size %lld", From b44db414b3ed70a85b379168d1f26b6bad5a3c36 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 12:54:58 +0800 Subject: [PATCH 077/116] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index ed9737170..97a4576b7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,7 +6,7 @@ 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/aaaapai/FastSTL.git From c50bf565269084e5129d0211b0e6880512b88af8 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 22 Feb 2026 04:55:50 +0000 Subject: [PATCH 078/116] Automated 3rdparty submodule update Sun Feb 22 04:55:50 UTC 2026 --- 3rdparty/DiligentCore | 2 +- 3rdparty/SPIRV-Cross | 2 +- 3rdparty/tracy | 2 +- 3rdparty/xxHash | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/3rdparty/DiligentCore b/3rdparty/DiligentCore index 546088473..163fb0c5f 160000 --- a/3rdparty/DiligentCore +++ b/3rdparty/DiligentCore @@ -1 +1 @@ -Subproject commit 5460884736a2f36ceb0a77478d5f39414db87429 +Subproject commit 163fb0c5f3c5a77563db7f375b0200e90aea812a diff --git a/3rdparty/SPIRV-Cross b/3rdparty/SPIRV-Cross index bf6bb5ce3..157316dc0 160000 --- a/3rdparty/SPIRV-Cross +++ b/3rdparty/SPIRV-Cross @@ -1 +1 @@ -Subproject commit bf6bb5ce3c9fb141127a7d3289ebaaef86d3b065 +Subproject commit 157316dc0954f3b6606f17e945e97decabb80996 diff --git a/3rdparty/tracy b/3rdparty/tracy index 6de054002..8420a1d7d 160000 --- a/3rdparty/tracy +++ b/3rdparty/tracy @@ -1 +1 @@ -Subproject commit 6de054002dc9caffd3b322a8e0c617b710122f48 +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 From 926cc02b37c287e8b08a3b94a0b44b7ba105e729 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:04:05 +0800 Subject: [PATCH 079/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 3073f6d51..2b4a44571 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1616,7 +1616,7 @@ namespace MobileGL::MG_Backend::DirectGLES { g_EGLFuncs.eglBindAPI(EGL_OPENGL_ES_API); const EGLint configAttribs[] = {EGL_SURFACE_TYPE, - EGL_WINDOW_BIT, + EGL_WINDOW_BIT|EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RED_SIZE, @@ -1631,6 +1631,8 @@ namespace MobileGL::MG_Backend::DirectGLES { 24, EGL_STENCIL_SIZE, 8, + EGL_BUFFER_SIZE, + 32, EGL_NONE}; EGLint numConfigs = 0; From 4fb6e9a661d801658b4016c194df8aae75849760 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:06:11 +0800 Subject: [PATCH 080/116] Update config.cpp --- MobileGL/MG_Util/Config/config.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/MobileGL/MG_Util/Config/config.cpp b/MobileGL/MG_Util/Config/config.cpp index 138138252..93a989228 100644 --- a/MobileGL/MG_Util/Config/config.cpp +++ b/MobileGL/MG_Util/Config/config.cpp @@ -9,8 +9,6 @@ #include #include "Includes.h" -#include -#include #define DEBUG 0 From 938325963ab4d7cb0a8565a9f8ffba153b52ccb6 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:07:42 +0800 Subject: [PATCH 081/116] Update settings.cpp --- MobileGL/MG_Util/Config/settings.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/MobileGL/MG_Util/Config/settings.cpp b/MobileGL/MG_Util/Config/settings.cpp index 65db1c3ba..8032b859c 100644 --- a/MobileGL/MG_Util/Config/settings.cpp +++ b/MobileGL/MG_Util/Config/settings.cpp @@ -6,8 +6,6 @@ #include "config.h" #include "Includes.h" -#include -#include #define DEBUG 0 From c681fa9c85f5f30a4ce21b64551104f30e1f7e84 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:24:08 +0800 Subject: [PATCH 082/116] Update GL_Sync.cpp --- MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index 9aa694f8c..b41b6c7e0 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -39,28 +39,16 @@ namespace MobileGL { void DeleteSync_State(GLsync sync) {} GLsync FenceSync(GLenum condition, GLbitfield flags) { -#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - return MG_Backend::DirectGLES::g_GLESFuncs.glFenceSync(condition, flags); -#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN return FenceSync_State(condition, flags); -#endif } GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { -#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - return MG_Backend::DirectGLES::g_GLESFuncs.glClientWaitSync(sync, flags, timeout); -#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN return ClientWaitSync_State(sync, flags, timeout); -#endif } void DeleteSync(GLsync sync) { -#if MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_GLES - MG_Backend::DirectGLES::g_GLESFuncs.glDeleteSync(sync); -#elif MOBILEGL_BACKEND == MOBILEGL_BACKEND_TYPE_DIRECT_VULKAN DeleteSync_State(sync); -#endif } } // namespace MG_Impl::GLImpl From 50962406bb62bc59443a1c16d40f3f75e2043d7f Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:34:14 +0800 Subject: [PATCH 083/116] Update ShaderCompiler.cpp --- MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 606cf2d36..0e3fe3029 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -155,7 +155,7 @@ namespace MobileGL { // MobileGL runtime currently creates Vulkan 1.1 instance/device on Android path, // so generated SPIR-V must not exceed SPIR-V 1.3. tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); - tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); + tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_1); tshader->setEnvInputVulkanRulesRelaxed(); // using EXT_vulkan_glsl_relaxed for gl_VertexID and // gl_InstanceID? } @@ -174,7 +174,6 @@ namespace MobileGL { "#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_arrays_of_arrays : enable\n" "#extension GL_ARB_enhanced_layouts : enable\n" "#extension GL_ARB_fragment_coord_conventions : enable\n"; From 002d640e33ae756009238ebde94b663559408b33 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 13:55:48 +0800 Subject: [PATCH 084/116] Update ShaderCompiler.cpp --- MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 0e3fe3029..451950326 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -147,15 +147,15 @@ namespace MobileGL { tshader->setStrings(src, 1); tshader->setInvertY(true); if (attrib.flags & ShaderCompileBits::CompileForOpenGL) { - tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientOpenGL, 450); + tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientOpenGL, 460); 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); + tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 460); // MobileGL runtime currently creates Vulkan 1.1 instance/device on Android path, // so generated SPIR-V must not exceed SPIR-V 1.3. tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); - tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_1); + tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); tshader->setEnvInputVulkanRulesRelaxed(); // using EXT_vulkan_glsl_relaxed for gl_VertexID and // gl_InstanceID? } From 22c2b4384dc85049e7defe21b289e9f3e1a856ae Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:02:52 +0800 Subject: [PATCH 085/116] Update GL_Sync.cpp --- MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index b41b6c7e0..12ee78191 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -16,6 +16,8 @@ #include #include +#include + namespace MobileGL { namespace MG_Impl::GLImpl { GLsync FenceSync_Backend(GLenum condition, GLbitfield flags) { @@ -39,15 +41,24 @@ namespace MobileGL { void DeleteSync_State(GLsync sync) {} GLsync FenceSync(GLenum condition, GLbitfield flags) { + if(CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) + return MG_Backend::DirectGLES::g_GLESFuncs.glFenceSync(condition, flags); + else return FenceSync_State(condition, flags); } GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { + if(CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) + return MG_Backend::DirectGLES::g_GLESFuncs.glClientWaitSync(sync, flags, timeout); + else return ClientWaitSync_State(sync, flags, timeout); } void DeleteSync(GLsync sync) { + if (CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) + MG_Backend::DirectGLES::g_GLESFuncs.glDeleteSync(sync); + else DeleteSync_State(sync); } From 77691b75c7b394df4ffe18dc65f5870cd5902fec Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:12:21 +0800 Subject: [PATCH 086/116] Update GL_Sync.cpp --- MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index 12ee78191..40ab83894 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -41,14 +41,14 @@ namespace MobileGL { void DeleteSync_State(GLsync sync) {} GLsync FenceSync(GLenum condition, GLbitfield flags) { - if(CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) + if(MG_Util::CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) return MG_Backend::DirectGLES::g_GLESFuncs.glFenceSync(condition, flags); else return FenceSync_State(condition, flags); } GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { - if(CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) + if(MG_Util::CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) return MG_Backend::DirectGLES::g_GLESFuncs.glClientWaitSync(sync, flags, timeout); else return ClientWaitSync_State(sync, flags, timeout); @@ -56,7 +56,7 @@ namespace MobileGL { } void DeleteSync(GLsync sync) { - if (CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) + if (MG_Util::CheckEnv("MOBILEGL_BACKEND_TYPE", "DirectGLES", false)) MG_Backend::DirectGLES::g_GLESFuncs.glDeleteSync(sync); else DeleteSync_State(sync); From f72c6b1360446f336aac878372bbc76e14a8c887 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:37:52 +0800 Subject: [PATCH 087/116] Update Definitions.cpp --- MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 5fbc45ff7..9ea04405c 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -969,8 +969,8 @@ DECLARE_GL_FUNCTION_HEAD(void, DrawElementsInstancedBaseVertexBaseInstance, GLen DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint program, GLuint bufferIndex, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAtomicCounterBufferiv, program, bufferIndex, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount) DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount) -DECLARE_GL_FUNCTION_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) -DECLARE_GL_FUNCTION_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) +DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) +DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexImage, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexImage, texture, level) @@ -1004,7 +1004,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr s DECLARE_GL_FUNCTION_STUB_HEAD(void, NamedBufferSubData, GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, NamedBufferSubData, buffer, offset, size, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, CopyNamedBufferSubData, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CopyNamedBufferSubData, readBuffer, writeBuffer, readOffset, writeOffset, size) DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedBufferData, GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedBufferData, buffer, internalformat, format, type, data) -DECLARE_GL_FUNCTION_HEAD(void, ClearNamedBufferSubData, GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearNamedBufferSubData, buffer, internalformat, offset, size, format, type, data) +DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearNamedBufferSubData, GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearNamedBufferSubData, buffer, internalformat, offset, size, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, UnmapNamedBuffer, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END(GLboolean, UnmapNamedBuffer, buffer) DECLARE_GL_FUNCTION_STUB_HEAD(void, FlushMappedNamedBufferRange, GLuint buffer, GLintptr offset, GLsizeiptr length) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FlushMappedNamedBufferRange, buffer, offset, length) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetNamedBufferParameteriv, GLuint buffer, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetNamedBufferParameteriv, buffer, pname, params) From 356e3821dc98c4a3f0e6360e640c94aa96491aac Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:46:32 +0800 Subject: [PATCH 088/116] Update DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 206 ------------------ 1 file changed, 206 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index c6b119f9b..fb5f3edfe 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1664,210 +1664,4 @@ namespace MobileGL::MG_Backend::DirectGLES { } } - // ==================== 实现 glClearBufferSubData 相关函数 ==================== - -namespace { - // 将 OpenGL 枚举转换为你的 BufferTarget 枚举 - BufferTarget GLEnumToBufferTarget(GLenum target) { - switch (target) { - case GL_ARRAY_BUFFER: return BufferTarget::Vertex; - case GL_ELEMENT_ARRAY_BUFFER: return BufferTarget::Index; - case GL_UNIFORM_BUFFER: return BufferTarget::Uniform; - case GL_COPY_READ_BUFFER: return BufferTarget::CopyRead; - case GL_COPY_WRITE_BUFFER: return BufferTarget::CopyWrite; - case GL_PIXEL_PACK_BUFFER: return BufferTarget::PixelPack; - case GL_PIXEL_UNPACK_BUFFER: return BufferTarget::PixelUnpack; - case GL_QUERY_BUFFER: return BufferTarget::Query; - case GL_TEXTURE_BUFFER: return BufferTarget::Texture; - case GL_TRANSFORM_FEEDBACK_BUFFER: return BufferTarget::TransformFeedback; - case GL_ATOMIC_COUNTER_BUFFER: return BufferTarget::AtomicCounter; - case GL_DISPATCH_INDIRECT_BUFFER: return BufferTarget::DispatchIndirect; - case GL_DRAW_INDIRECT_BUFFER: return BufferTarget::DrawIndirect; - case GL_SHADER_STORAGE_BUFFER: return BufferTarget::ShaderStorage; - default: return BufferTarget::Unknown; - } - } - - // 获取对应目标的绑定查询枚举(用于保存/恢复状态) - GLenum GetBindingEnum(GLenum target) { - switch (target) { - case GL_ARRAY_BUFFER: return GL_ARRAY_BUFFER_BINDING; - case GL_ELEMENT_ARRAY_BUFFER: return GL_ELEMENT_ARRAY_BUFFER_BINDING; - case GL_UNIFORM_BUFFER: return GL_UNIFORM_BUFFER_BINDING; - case GL_COPY_READ_BUFFER: return GL_COPY_READ_BUFFER_BINDING; - case GL_COPY_WRITE_BUFFER: return GL_COPY_WRITE_BUFFER_BINDING; - case GL_PIXEL_PACK_BUFFER: return GL_PIXEL_PACK_BUFFER_BINDING; - case GL_PIXEL_UNPACK_BUFFER: return GL_PIXEL_UNPACK_BUFFER_BINDING; - case GL_QUERY_BUFFER: return GL_QUERY_BUFFER_BINDING; - case GL_TEXTURE_BUFFER: return GL_TEXTURE_BUFFER_BINDING; - case GL_TRANSFORM_FEEDBACK_BUFFER: return GL_TRANSFORM_FEEDBACK_BUFFER_BINDING; - case GL_ATOMIC_COUNTER_BUFFER: return GL_ATOMIC_COUNTER_BUFFER_BINDING; - case GL_DISPATCH_INDIRECT_BUFFER: return GL_DISPATCH_INDIRECT_BUFFER_BINDING; - case GL_DRAW_INDIRECT_BUFFER: return GL_DRAW_INDIRECT_BUFFER_BINDING; - case GL_SHADER_STORAGE_BUFFER: return GL_SHADER_STORAGE_BUFFER_BINDING; - default: return 0; - } - } -} - -// 清除当前绑定到 target 的缓冲区的子数据范围 -void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - DebugImpl::ErrorLopper errorLopper; - - MGLOG_D("ClearBufferSubData: target=0x%x offset=%lld size=%lld", target, (long long)offset, (long long)size); - - - // 1. 基本参数有效性 - if (size == 0) { - MGLOG_D("ClearBufferSubData: size is 0, nothing to do"); - return; // 不是错误,直接返回 - } - - if (size < 0) { - MGLOG_E("ClearBufferSubData: size is negative: %lld", (long long)size); - return; - } - - if (offset < 0) { - MGLOG_E("ClearBufferSubData: offset is negative: %lld", (long long)offset); - return; - } - - // 2. target 有效性 - BufferTarget mgTarget = GLEnumToBufferTarget(target); - if (mgTarget == BufferTarget::Unknown) { - MGLOG_E("ClearBufferSubData: unsupported target 0x%x", target); - return; - } - - // 3. 缓冲区对象存在性 - auto bufferObj = MG_State::pGLContext->GetBufferBindingSlot(mgTarget).GetBoundObject(); - if (!bufferObj) { - MGLOG_E("ClearBufferSubData: no buffer bound to target 0x%x", target); - return; - } - - // 5. 缓冲区大小检查 - GLsizeiptr bufferSize = bufferObj->GetSize(); - if (bufferSize <= 0) { - MGLOG_E("ClearBufferSubData: buffer size is invalid: %lld", (long long)bufferSize); - return; - } - - // 6. offset 范围检查 - if (offset >= bufferSize) { - MGLOG_E("ClearBufferSubData: offset (%lld) >= buffer size (%lld)", - (long long)offset, (long long)bufferSize); - return; - } - - // 8. 最终范围检查 - if (offset + size > bufferSize) { - MGLOG_E("ClearBufferSubData: range [%lld, %lld) exceeds buffer size %lld", - (long long)offset, (long long)(offset + size), (long long)bufferSize); - return; - } - - // 确保起始位置在缓冲区范围内 - if (offset >= bufferSize) { - MGLOG_E("ClearBufferSubData: offset (%lld) outside buffer (size %lld)", - (long long)offset, (long long)bufferSize); - return; - } - - // 同步必要的资源 - TextureImpl::SyncNeccessaryTextures(); - FramebufferImpl::SyncCurrentFBO(); - BufferImpl::CreateAndSyncBufferObject(bufferObj); // 确保后端存在 - - // 获取后端缓冲区 ID - auto it = BufferImpl::g_backendBufferObjects.find(bufferObj); - if (it == BufferImpl::g_backendBufferObjects.end()) { - MGLOG_E("ClearBufferSubData: backend buffer not found"); - //return; - } - GLuint bufferId = it->second->GetBackendBufferId(); - - // 保存当前绑定 - GLint prevBinding = 0; - GLenum bindingEnum = GetBindingEnum(target); - if (bindingEnum) g_GLESFuncs.glGetIntegerv(bindingEnum, &prevBinding); - - // 绑定并映射 - g_GLESFuncs.glBindBuffer(target, bufferId); - void* mapped = g_GLESFuncs.glMapBufferRange(target, offset, size, - GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); - if (mapped) { - if (data) - Memcpy(mapped, data, size); - else - Memset(mapped, 0, size); - g_GLESFuncs.glUnmapBuffer(target); - } else { - MGLOG_E("ClearBufferSubData: failed to map buffer range"); - } - - // 恢复绑定 - g_GLESFuncs.glBindBuffer(target, prevBinding); - - // 标记缓冲区为脏(如果需要后续同步) - //bufferObj->MarkDirty(offset, size); - - errorLopper.Loop([](GLenum err) { - MGLOG_D("ClearBufferSubData GL error: %s", MG_Util::ConvertGLEnumToString(err).c_str()); - }); -} - -// 按名称清除缓冲区子数据(如果后端需要,通常前端不会直接调用此函数) -void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data) { -#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER - DebugImpl::OpenGLScopeMarker marker(__func__); -#endif - MGLOG_D("ClearNamedBufferSubData: buffer=%u offset=%lld size=%lld", buffer, (long long)offset, (long long)size); - - // 查找对应的缓冲区对象(遍历所有后端缓冲区) - SharedPtr targetBuffer; - for (const auto& pair : BufferImpl::g_backendBufferObjects) { - if (pair.second && pair.second->GetBackendBufferId() == buffer) { - targetBuffer = pair.first; - break; - } - } - - if (!targetBuffer) { - //MGLOG_E("ClearNamedBufferSubData: buffer %u not found", buffer); - return; - } - - // 临时绑定到 ARRAY_BUFFER 进行操作 - GLint prevBinding = 0; - g_GLESFuncs.glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevBinding); - g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, buffer); - - void* mapped = g_GLESFuncs.glMapBufferRange(GL_ARRAY_BUFFER, offset, size, - GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); - if (mapped) { - if (data) - Memcpy(mapped, data, size); - else - Memset(mapped, 0, size); - g_GLESFuncs.glUnmapBuffer(GL_ARRAY_BUFFER); - } - - g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, prevBinding); - //targetBuffer->MarkDirty(offset, size); -} - -void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { - auto bufferObject = MG_State::pGLContext->GetBufferBindingSlot(GLEnumToBufferTarget(target)).GetBoundObject(); - if (bufferObject) { - ClearBufferSubData(target, internalformat, 0, bufferObject->GetSize(), format, type, data); - } -} - } // namespace MobileGL::MG_Backend::DirectGLES From 932ef8c2b97210910a918324412e03702b092c1c Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:01:52 +0800 Subject: [PATCH 089/116] Update Definitions.cpp --- MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 9ea04405c..09aad47bc 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -970,7 +970,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAtomicCounterBufferiv, GLuint progr DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackInstanced, GLenum mode, GLuint id, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackInstanced, mode, id, instancecount) DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackStreamInstanced, GLenum mode, GLuint id, GLuint stream, GLsizei instancecount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackStreamInstanced, mode, id, stream, instancecount) DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferData, GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferData, target, internalformat, format, type, data) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) +DECLARE_GL_FUNCTION_STUB_HEAD(void, ClearBufferSubData, GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClearBufferSubData, target, internalformat, offset, size, format, type, data) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetInternalformati64v, GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetInternalformati64v, target, internalformat, pname, count, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth) DECLARE_GL_FUNCTION_STUB_HEAD(void, InvalidateTexImage, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, InvalidateTexImage, texture, level) From 04e10dc3fe27c6f49747e6d7d4f746e449ae660d Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:08:23 +0800 Subject: [PATCH 090/116] Update BackendObject_DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index de9fca2e4..95531e4c1 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -209,9 +209,6 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.GenerateMipmap = GenerateMipmap; funcsTable.GL.ReadPixels = ReadPixels; funcsTable.GL.GetTexImage = GetTexImage; - funcsTable.GL.ClearBufferData = ClearBufferData; - funcsTable.GL.ClearBufferSubData = ClearBufferSubData; - funcsTable.GL.ClearNamedBufferSubData = ClearNamedBufferSubData; funcsTableInitialized = true; } return funcsTable; From 1092a01417558e4b1e76f2a1a956fad4bd748125 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:09:46 +0800 Subject: [PATCH 091/116] Update GL_Drawing.cpp --- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 7005686bc..70ebd6c0a 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -155,22 +155,6 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Backend::gBackendFunctionsTable.GL.DrawArraysIndirect(mode, indirect); } - void ClearBufferSubData_Backend(GLenum target, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data) { - MG_Backend::gBackendFunctionsTable.GL.ClearBufferSubData(target, internalformat, offset, - size, format, type, data); - } - - void ClearNamedBufferSubData_Backend(GLuint buffer, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data) { - MG_Backend::gBackendFunctionsTable.GL.ClearNamedBufferSubData(buffer, internalformat, offset, - size, format, type, data); - } - - void ClearBufferData_Backend(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { - MG_Backend::gBackendFunctionsTable.GL.ClearBufferData(target, internalformat, format, type, data); - } - /* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */ void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) { MultiDrawElementsIndirect_Backend(mode, type, indirect, drawcount, stride); @@ -252,20 +236,4 @@ namespace MobileGL::MG_Impl::GLImpl { DrawElements_Backend(mode, count, type, indices); } - void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data) { - ClearBufferSubData_Backend(target, internalformat, offset, - size, format, type, data); - } - - void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data) { - ClearNamedBufferSubData_Backend(buffer, internalformat, offset, - size, format, type, data); - } - - void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { - ClearBufferData_Backend(target, internalformat, format, type, data); - } - } // namespace MobileGL::MG_Impl::GLImpl From 78250886872570a2d8cbef0d4f7d6ebe9ce4cb74 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:42:08 +0800 Subject: [PATCH 092/116] Update DirectGLES.h --- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 1d80069b4..4b907fa1c 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -62,10 +62,6 @@ namespace MobileGL::MG_Backend::DirectGLES { void SetGLESFuncsTable(const MG_External::GLESFunctionsTable& glesFuncs); void SetGLESCapabilities(const MG_External::GLESCapabilities& capabilities); void DestroyEGLContext(); - void ClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data); - void ClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void* data); - void ClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data); - extern MG_External::EGLFunctionsTable g_EGLFuncs; extern MG_External::GLESFunctionsTable g_GLESFuncs; From af1f88af59173162619eee778f3f521931c5c53e Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:45:28 +0800 Subject: [PATCH 093/116] Update ShaderCompiler.cpp --- MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 451950326..68de536ab 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -147,11 +147,11 @@ namespace MobileGL { tshader->setStrings(src, 1); tshader->setInvertY(true); if (attrib.flags & ShaderCompileBits::CompileForOpenGL) { - tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientOpenGL, 460); + tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientOpenGL, 450); tshader->setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); tshader->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5); } else { - tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 460); + tshader->setEnvInput(glslang::EShSourceGlsl, lang, glslang::EShClientVulkan, 450); // MobileGL runtime currently creates Vulkan 1.1 instance/device on Android path, // so generated SPIR-V must not exceed SPIR-V 1.3. tshader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); From c7b007ffcce3f6917a6a4845585e8596dd5954c4 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:51:52 +0800 Subject: [PATCH 094/116] Update CMakeLists.txt --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e527b217f..32aa0369c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,8 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -pipe -integrated-as -mllvm -polly -mllv 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 -fno-emulated-tls -fwhole-program-vtables -fdata-sections -ffunction-sections -fmerge-all-constants -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 -stdlib=libc++ -Wno-unicode-whitespace -Wno-format -Wno-unused-function -Wno-vla-cxx-extension -DUseFastSTL -frtti -DUseJEMALLOC -pthread") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -flto -O3 -Wl,-plugin-opt=-emulated-tls=0 -Wl,--strip-all -fvisibility=hidden -Wl,-O3 -Wl,--gc-sections -Wl,--as-needed -static-libstdc++") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym=malloc=je_malloc -Wl,--defsym=calloc=je_calloc -Wl,--defsym=realloc=je_realloc -Wl,--defsym=free=je_free -Wl,--defsym=posix_memalign=je_posix_memalign -Wl,--defsym=aligned_alloc=je_aligned_alloc -Wl,--defsym=malloc_usable_size=je_malloc_usable_size -Wl,--defsym=mallocx=je_mallocx -Wl,--defsym=rallocx=je_rallocx -Wl,--defsym=xallocx=je_xallocx -Wl,--defsym=sallocx=je_sallocx -Wl,--defsym=dallocx=je_dallocx -Wl,--defsym=sdallocx=je_sdallocx -Wl,--defsym=nallocx=je_nallocx -Wl,--defsym=mallctl=je_mallctl -Wl,--defsym=mallctlnametomib=je_mallctlnametomib -Wl,--defsym=mallctlbymib=je_mallctlbymib -Wl,--defsym=malloc_stats_print=je_malloc_stats_print") + if (MSVC) set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}") endif() @@ -328,6 +330,7 @@ if (ANDROID) android log vulkan + ${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI}/libjemalloc.so ) target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "LINKER:--version-script=${CMAKE_SOURCE_DIR}/MobileGL/mgl-version.script" From 44da0f5fed60b864867c94df1ebfe25abf22db8d Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:53:23 +0800 Subject: [PATCH 095/116] Add files via upload --- libraries/android/arm64-v8a/libjemalloc.so | Bin 0 -> 407200 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 libraries/android/arm64-v8a/libjemalloc.so diff --git a/libraries/android/arm64-v8a/libjemalloc.so b/libraries/android/arm64-v8a/libjemalloc.so new file mode 100644 index 0000000000000000000000000000000000000000..a3482537f5f50fe8263f2223dcddf10577035047 GIT binary patch literal 407200 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�*_6|1OSG={3n_!f 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 z Date: Tue, 24 Feb 2026 22:57:44 +0800 Subject: [PATCH 096/116] Update CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 32aa0369c..7d2bbf683 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -pipe -integrated-as -mllvm -polly -mllv 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 -fno-emulated-tls -fwhole-program-vtables -fdata-sections -ffunction-sections -fmerge-all-constants -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 -stdlib=libc++ -Wno-unicode-whitespace -Wno-format -Wno-unused-function -Wno-vla-cxx-extension -DUseFastSTL -frtti -DUseJEMALLOC -pthread") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -flto -O3 -Wl,-plugin-opt=-emulated-tls=0 -Wl,--strip-all -fvisibility=hidden -Wl,-O3 -Wl,--gc-sections -Wl,--as-needed -static-libstdc++") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym=malloc=je_malloc -Wl,--defsym=calloc=je_calloc -Wl,--defsym=realloc=je_realloc -Wl,--defsym=free=je_free -Wl,--defsym=posix_memalign=je_posix_memalign -Wl,--defsym=aligned_alloc=je_aligned_alloc -Wl,--defsym=malloc_usable_size=je_malloc_usable_size -Wl,--defsym=mallocx=je_mallocx -Wl,--defsym=rallocx=je_rallocx -Wl,--defsym=xallocx=je_xallocx -Wl,--defsym=sallocx=je_sallocx -Wl,--defsym=dallocx=je_dallocx -Wl,--defsym=sdallocx=je_sdallocx -Wl,--defsym=nallocx=je_nallocx -Wl,--defsym=mallctl=je_mallctl -Wl,--defsym=mallctlnametomib=je_mallctlnametomib -Wl,--defsym=mallctlbymib=je_mallctlbymib -Wl,--defsym=malloc_stats_print=je_malloc_stats_print") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym=malloc=je_malloc -Wl,--defsym=calloc=je_calloc -Wl,--defsym=realloc=je_realloc -Wl,--defsym=free=je_free -Wl,--defsym=posix_memalign=je_posix_memalign -Wl,--defsym=aligned_alloc=je_aligned_alloc -Wl,--defsym=malloc_usable_size=je_malloc_usable_size -Wl,--defsym=mallocx=je_mallocx -Wl,--defsym=rallocx=je_rallocx -Wl,--defsym=xallocx=je_xallocx -Wl,--defsym=sallocx=je_sallocx -Wl,--defsym=dallocx=je_dallocx -Wl,--defsym=sdallocx=je_sdallocx -Wl,--defsym=nallocx=je_nallocx -Wl,--defsym=mallctl=je_mallctl -Wl,--defsym=mallctlnametomib=je_mallctlnametomib -Wl,--defsym=mallctlbymib=je_mallctlbymib -Wl,--defsym=malloc_stats_print=je_malloc_stats_print -L${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI} -ljemalloc") if (MSVC) set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}") From 981a4fc68851d06b56828324f66b8e3e4fa2b923 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:58:55 +0800 Subject: [PATCH 097/116] Update android_ci_build.bash --- android_ci_build.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android_ci_build.bash b/android_ci_build.bash index 1af053223..e761c78c3 100644 --- a/android_ci_build.bash +++ b/android_ci_build.bash @@ -10,7 +10,7 @@ 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 + 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 From 26f2c0db08e7230b0d7dfcff3d34fbd8e1e0bb77 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Fri, 20 Mar 2026 23:16:36 +0800 Subject: [PATCH 098/116] Update CMakeLists.txt --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb1b5eefe..a1188b0f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -pipe -integrated-as -mllvm -polly -mllv 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 -fno-emulated-tls -fwhole-program-vtables -fdata-sections -ffunction-sections -fmerge-all-constants -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 -stdlib=libc++ -Wno-unicode-whitespace -Wno-format -Wno-unused-function -Wno-vla-cxx-extension -DUseFastSTL -frtti -DUseJEMALLOC -pthread") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -flto -O3 -Wl,-plugin-opt=-emulated-tls=0 -Wl,--strip-all -fvisibility=hidden -Wl,-O3 -Wl,--gc-sections -Wl,--as-needed -static-libstdc++") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym=malloc=je_malloc -Wl,--defsym=calloc=je_calloc -Wl,--defsym=realloc=je_realloc -Wl,--defsym=free=je_free -Wl,--defsym=posix_memalign=je_posix_memalign -Wl,--defsym=aligned_alloc=je_aligned_alloc -Wl,--defsym=malloc_usable_size=je_malloc_usable_size -Wl,--defsym=mallocx=je_mallocx -Wl,--defsym=rallocx=je_rallocx -Wl,--defsym=xallocx=je_xallocx -Wl,--defsym=sallocx=je_sallocx -Wl,--defsym=dallocx=je_dallocx -Wl,--defsym=sdallocx=je_sdallocx -Wl,--defsym=nallocx=je_nallocx -Wl,--defsym=mallctl=je_mallctl -Wl,--defsym=mallctlnametomib=je_mallctlnametomib -Wl,--defsym=mallctlbymib=je_mallctlbymib -Wl,--defsym=malloc_stats_print=je_malloc_stats_print -L${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI} -ljemalloc") +# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym=malloc=je_malloc -Wl,--defsym=calloc=je_calloc -Wl,--defsym=realloc=je_realloc -Wl,--defsym=free=je_free -Wl,--defsym=posix_memalign=je_posix_memalign -Wl,--defsym=aligned_alloc=je_aligned_alloc -Wl,--defsym=malloc_usable_size=je_malloc_usable_size -Wl,--defsym=mallocx=je_mallocx -Wl,--defsym=rallocx=je_rallocx -Wl,--defsym=xallocx=je_xallocx -Wl,--defsym=sallocx=je_sallocx -Wl,--defsym=dallocx=je_dallocx -Wl,--defsym=sdallocx=je_sdallocx -Wl,--defsym=nallocx=je_nallocx -Wl,--defsym=mallctl=je_mallctl -Wl,--defsym=mallctlnametomib=je_mallctlnametomib -Wl,--defsym=mallctlbymib=je_mallctlbymib -Wl,--defsym=malloc_stats_print=je_malloc_stats_print -L${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI} -ljemalloc") if (MSVC) set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}") @@ -336,7 +336,7 @@ if (ANDROID) android log vulkan - ${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI}/libjemalloc.so + # ${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI}/libjemalloc.so ) target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "LINKER:--version-script=${CMAKE_SOURCE_DIR}/MobileGL/mgl-version.script" From a7b25cb3ae9d46d4bb2f39782cb2b19173fab285 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:34:21 +0800 Subject: [PATCH 099/116] Update GL_RenderState.cpp --- MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp index b32e7aab3..c89c883e1 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp @@ -261,6 +261,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)); } From 20ab2052c907bfba673bd50519bc6217a0eff0a0 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:38:59 +0800 Subject: [PATCH 100/116] Update BackendObject_DirectGLES.cpp --- .../DirectGLES/BackendObject_DirectGLES.cpp | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 9d8f075e1..caa4ff50b 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -121,40 +121,38 @@ namespace MobileGL::MG_Backend::DirectGLES { } const RendererInfo& BackendObject_DirectGLES::GetRendererInfo() const { - // 检查环境变量 const char* envLibGL = std::getenv("LIBGL_GL"); - const char* envlibGL_compute = std::getenv("LIBGL_COMPUTE_SHADER"); - - // 创建基础扩展列表 + Vector extensions = { - V_OpenGL30, V_OpenGL31, V_OpenGL32, - V_OpenGL33, E_GL_ARB_draw_buffers_blend, E_GL_ARB_shader_image_load_store - }; - - // 根据环境变量添加扩展 - if (envlibGL_compute != nullptr) { - extensions.push_back(E_GL_ARB_compute_shader); - MGLOG_I("LIBGL_COMPUTE_SHADER detected, added compute shader support"); - } + V_OpenGL30, V_OpenGL31, V_OpenGL32, + 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}; + - // 检查LIBGL_GL环境变量 - Version targetVersion = {3, 3, 0}; // 默认版本 + Version targetVersion = {3, 3, 0}; if (envLibGL != nullptr) { - std::string libglValue = envLibGL; - if (libglValue == "43") { - MGLOG_I("LIBGL_GL=43 detected, using OpenGL 4.3 configuration"); - targetVersion = {4, 3, 0}; - - // 添加OpenGL 4.x扩展 - extensions.push_back(V_OpenGL40); - extensions.push_back(V_OpenGL41); - extensions.push_back(V_OpenGL42); - extensions.push_back(V_OpenGL43); - } + 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)); + } } - // 创建并返回RendererInfo static RendererInfo RendererInfo = { .RendererName = "Espryt", // Renderer Name .BackendName = "Direct (OpenGL ES)", // Backend Name @@ -163,11 +161,7 @@ namespace MobileGL::MG_Backend::DirectGLES { { .TargetGLVersion = {3, 3, 0}, // 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}, + .Extensions = extensions, .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability From cd8a1e3e96932afdcb74c598f06b037ad81e80eb Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:39:48 +0800 Subject: [PATCH 101/116] Delete .github/workflows/plugin-apk.yml --- .github/workflows/plugin-apk.yml | 80 -------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 .github/workflows/plugin-apk.yml 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 From e2a936caa76eb3265ca31d420f99e706c2862ded Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:00:38 +0800 Subject: [PATCH 102/116] Update CMakeLists.txt --- CMakeLists.txt | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c89aaf8b7..eee21d753 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,18 @@ 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} -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 -U_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 -U_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") + + 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" OFF) @@ -17,22 +29,6 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") add_compile_options(/Zc:__cplusplus) endif() -enable_language(CXX) -enable_language(C) - -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} -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 -fno-emulated-tls -fwhole-program-vtables -fdata-sections -ffunction-sections -fmerge-all-constants -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 -DUseFastSTL -DUseJEMALLOC -pthread") -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 -fno-emulated-tls -fwhole-program-vtables -fdata-sections -ffunction-sections -fmerge-all-constants -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 -stdlib=libc++ -Wno-unicode-whitespace -Wno-format -Wno-unused-function -Wno-vla-cxx-extension -DUseFastSTL -frtti -DUseJEMALLOC -pthread") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld -flto -O3 -Wl,-plugin-opt=-emulated-tls=0 -Wl,--strip-all -fvisibility=hidden -Wl,-O3 -Wl,--gc-sections -Wl,--as-needed -static-libstdc++") - -# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym=malloc=je_malloc -Wl,--defsym=calloc=je_calloc -Wl,--defsym=realloc=je_realloc -Wl,--defsym=free=je_free -Wl,--defsym=posix_memalign=je_posix_memalign -Wl,--defsym=aligned_alloc=je_aligned_alloc -Wl,--defsym=malloc_usable_size=je_malloc_usable_size -Wl,--defsym=mallocx=je_mallocx -Wl,--defsym=rallocx=je_rallocx -Wl,--defsym=xallocx=je_xallocx -Wl,--defsym=sallocx=je_sallocx -Wl,--defsym=dallocx=je_dallocx -Wl,--defsym=sdallocx=je_sdallocx -Wl,--defsym=nallocx=je_nallocx -Wl,--defsym=mallctl=je_mallctl -Wl,--defsym=mallctlnametomib=je_mallctlnametomib -Wl,--defsym=mallctlbymib=je_mallctlbymib -Wl,--defsym=malloc_stats_print=je_malloc_stats_print -L${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI} -ljemalloc") - if (MSVC) set(CMAKE_CXX_FLAGS "/EHsc ${CMAKE_CXX_FLAGS}") endif() @@ -58,9 +54,9 @@ include_directories(${CMAKE_BINARY_DIR}/generated) set(ENABLE_RTTI ON CACHE BOOL "Enables RTTI (will be took by glslang)" FORCE) set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "Enable glslangValidator/spirv-remap" FORCE) -set(ENABLE_SPVREMAPPER OFF CACHE BOOL "Enable SPVRemapper" FORCE) +set(ENABLE_SPVREMAPPER ON CACHE BOOL "Enable SPVRemapper" FORCE) set(ENABLE_OPT ON CACHE BOOL "Enable SPIRV-Tools opt usage in glslang" FORCE) -set(BUILD_EXTERNAL ON CACHE BOOL "Build external deps in External/" FORCE) +set(BUILD_EXTERNAL OFF CACHE BOOL "Build external deps in External/" FORCE) set(ENABLE_GLSLANG_INSTALL OFF CACHE BOOL "Install glslang targets" FORCE) set(SPIRV_CROSS_C_API ON CACHE BOOL "Enable C API" FORCE) @@ -348,7 +344,6 @@ if (ANDROID) android log vulkan - # ${CMAKE_SOURCE_DIR}/libraries/android/${ANDROID_ABI}/libjemalloc.so ) target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "LINKER:--version-script=${CMAKE_SOURCE_DIR}/MobileGL/mgl-version.script" From c771dd9a783ffdfd1a84e182f63e27893f656cb7 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:07:30 +0800 Subject: [PATCH 103/116] Update Types.h --- MobileGL/MG_Util/Types.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MobileGL/MG_Util/Types.h b/MobileGL/MG_Util/Types.h index b1f4be569..dad71a906 100644 --- a/MobileGL/MG_Util/Types.h +++ b/MobileGL/MG_Util/Types.h @@ -85,6 +85,17 @@ template < typename T > using UnorderedMap = std::unordered_map; +#elif UseABSL +#include +#include +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> +> +using UnorderedMap = absl::flat_hash_map; #else #error The type of UnorderedMap to be used is not defined! #endif From ad5cb9f8d2139be7b380e925c0d61c873dde75c9 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:09:12 +0800 Subject: [PATCH 104/116] Update CMakeLists.txt --- CMakeLists.txt | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eee21d753..22cee8463 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,9 +9,26 @@ 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} -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 -U_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 -U_GNU_SOURCE -U__USE_GNU") +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 -U_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 -U_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 ) @@ -344,6 +361,7 @@ 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" From 94e25d25fed2f586cf2704eaa4e033a97be6c9d9 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:15:50 +0800 Subject: [PATCH 105/116] Update CMakeLists.txt --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22cee8463..a79947477 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,8 +71,8 @@ include_directories(${CMAKE_BINARY_DIR}/generated) set(ENABLE_RTTI ON CACHE BOOL "Enables RTTI (will be took by glslang)" FORCE) set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "Enable glslangValidator/spirv-remap" FORCE) -set(ENABLE_SPVREMAPPER ON CACHE BOOL "Enable SPVRemapper" FORCE) -set(ENABLE_OPT ON CACHE BOOL "Enable SPIRV-Tools opt usage in glslang" FORCE) +set(ENABLE_SPVREMAPPER OFF CACHE BOOL "Enable SPVRemapper" FORCE) +set(ENABLE_OPT OFF CACHE BOOL "Enable SPIRV-Tools opt usage in glslang" FORCE) set(BUILD_EXTERNAL OFF CACHE BOOL "Build external deps in External/" FORCE) set(ENABLE_GLSLANG_INSTALL OFF CACHE BOOL "Install glslang targets" FORCE) From 7336967d3e81427a2593b93433b5f740bb628795 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:21:01 +0800 Subject: [PATCH 106/116] Update CMakeLists.txt --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a79947477..10590246f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ 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 -U_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 -U_GNU_SOURCE -U__USE_GNU") +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( From e9275cccf2ca661c8483bed5b2f7cf297ee693d2 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:36:21 +0800 Subject: [PATCH 107/116] Update CMakeLists.txt --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 10590246f..753d4656b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ 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_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 -D__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 -D__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( From 246b6e9fd5dab9e883fa1391c35c081fb873a0b5 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:38:27 +0800 Subject: [PATCH 108/116] Update Types.h --- MobileGL/MG_Util/Types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MobileGL/MG_Util/Types.h b/MobileGL/MG_Util/Types.h index dad71a906..e8011e212 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" @@ -86,8 +88,6 @@ template < > using UnorderedMap = std::unordered_map; #elif UseABSL -#include -#include template < typename Key, typename T, From 38a69d2f89610e2f48ba86c4495b186c906d2d48 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:44:50 +0800 Subject: [PATCH 109/116] Update CMakeLists.txt --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 753d4656b..7d8f5c952 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ 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 -D__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 -D__USE_GNU") +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( @@ -72,8 +72,8 @@ set(ENABLE_RTTI ON CACHE BOOL "Enables RTTI (will be took by gls set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "Enable glslangValidator/spirv-remap" FORCE) set(ENABLE_SPVREMAPPER OFF CACHE BOOL "Enable SPVRemapper" FORCE) -set(ENABLE_OPT OFF CACHE BOOL "Enable SPIRV-Tools opt usage in glslang" FORCE) -set(BUILD_EXTERNAL OFF CACHE BOOL "Build external deps in External/" FORCE) +set(ENABLE_OPT ON CACHE BOOL "Enable SPIRV-Tools opt usage in glslang" FORCE) +set(BUILD_EXTERNAL ON CACHE BOOL "Build external deps in External/" FORCE) set(ENABLE_GLSLANG_INSTALL OFF CACHE BOOL "Install glslang targets" FORCE) set(SPIRV_CROSS_C_API ON CACHE BOOL "Enable C API" FORCE) From f0206d0d78d12fbe5c865271d3182fe581496090 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:55:30 +0800 Subject: [PATCH 110/116] Update GL_Getter.cpp --- MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 22 -------------------- 1 file changed, 22 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 3aa69640b..ebb969071 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -959,28 +959,6 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_MAX_SAMPLES: *params = 16; // TODO break; - case GL_MAX_IMAGE_UNITS: - *params = 8; // 最小4,推荐8或16 - break; - case GL_MAX_VERTEX_IMAGE_UNIFORMS: - *params = 4; // ES 3.2 最小要求 - break; - - case GL_MAX_FRAGMENT_IMAGE_UNIFORMS: - *params = 8; // 通常比顶点多 - break; - - case GL_MAX_COMPUTE_IMAGE_UNIFORMS: - *params = 8; // 如果支持计算着色器 - break; - - case GL_MAX_COMBINED_IMAGE_UNIFORMS: - *params = 12; // 顶点+片段的总和 - break; - - case GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES: - *params = 16; // 图像单元 + SSBO 的总限制 - break; default: MGLOG_E("glGetIntegerv: Invalid enum %s (0x%X)", MG_Util::ConvertGLEnumToString(pname).c_str(), pname); MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, From 852d2edff8d0da3b27a1d268548d3bc4f49a0799 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:05:49 +0800 Subject: [PATCH 111/116] Update BackendObject_DirectGLES.cpp --- .../DirectGLES/BackendObject_DirectGLES.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index caa4ff50b..571bf5b8b 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -120,6 +120,28 @@ 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"); From e5c56284339da3336dc48bf4d4339a85630d6a33 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:08:43 +0800 Subject: [PATCH 112/116] Update BackendObject_DirectGLES.cpp --- MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 571bf5b8b..4f166d63f 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -181,7 +181,7 @@ namespace MobileGL::MG_Backend::DirectGLES { .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 = extensions, .IsCompatibilityProfile = false // Is Compatibility Profile From 982f86ffc7c2de3a015012166355b03a6f126fba Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:18:52 +0800 Subject: [PATCH 113/116] Update Core.cpp --- MobileGL/MG_State/EGLState/Core.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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; From 519e2d480ea32c24f5ba4910690909b2b3a816c0 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:29:27 +0800 Subject: [PATCH 114/116] Update Loader.cpp --- .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 64 ++++--------------- 1 file changed, 11 insertions(+), 53 deletions(-) diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 64658f2f9..c930c860b 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -10,7 +10,7 @@ #include "MG_Util/Types.h" namespace MobileGL::MG_Util::BackendLoader { - static void* OpenLib(const Vector& names, const char* overrides = nullptr) { + static void* OpenLib(const Vector& names) { #if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__) static const String LibPathPrefixes[] = { "/opt/vc/lib/", "/usr/local/lib/", "/usr/lib/", "/usr/lib/x86_64-linux-gnu/", @@ -20,13 +20,7 @@ namespace MobileGL::MG_Util::BackendLoader { void* lib = nullptr; - Int flags = RTLD_LOCAL | RTLD_LAZY; - if (overrides) { - if ((lib = dlopen(overrides, flags))) { - //strncpy(path_name, overrides, PATH_MAX); - return lib; - } - } + Int flags = RTLD_GLOBAL | RTLD_LAZY; for (const auto& prefix : LibPathPrefixes) { for (const auto& name : names) { String path_name = prefix + name; @@ -40,40 +34,11 @@ namespace MobileGL::MG_Util::BackendLoader { } inline void* ProcAddress(void* lib, const char* name) { - static void* eglGetProcAddress_mgptr = nullptr; - - if (eglGetProcAddress_mgptr == nullptr) { - void* eglLib = nullptr; - static const Vector EGLLibNames = {"libEGL.so"}; - const char* libgl_egl = std::getenv("LIBGL_EGL"); - - if (libgl_egl) { - eglLib = OpenLib(EGLLibNames, libgl_egl); - } else { - eglLib = OpenLib(EGLLibNames); - } - - if (eglLib) { - eglGetProcAddress_mgptr = dlsym(eglLib, "eglGetProcAddress"); - } - } - - if (eglGetProcAddress_mgptr != nullptr) { - MGLOG_W("Using eglGetProcAddress"); - typedef void* (*EGLGetProcAddressFunc)(const char*); - EGLGetProcAddressFunc eglGetProcAddress = - reinterpret_cast(eglGetProcAddress_mgptr); - - void* func = eglGetProcAddress(name); - if (func) return func; - } - - // Fallback to dlsym on non-Windows/non-Apple platforms - #if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__) - return dlsym(lib, name); - #else +#if !defined(__WIN32) && !defined(_WIN32) && !defined(__APPLE__) + return dlsym(lib, name); +#else return nullptr; - #endif +#endif } Bool AcquireGLESFunctions(MG_External::GLESFunctionsTable& funcs, @@ -467,23 +432,16 @@ namespace MobileGL::MG_Util::BackendLoader { } Bool AcquireEGLFunctions(MG_External::EGLFunctionsTable& funcs) { + static const Vector EGLLibNames = {"libEGL.so", "libEGL_angle.so", "libEGL_mesa.so" }; void* eglLib = nullptr; - static const Vector EGLLibNames = {"libEGL.so"}; const char* libgl_egl = std::getenv("LIBGL_EGL"); - if (libgl_egl && strcmp(libgl_egl, "libEGL_mesa.so") == 0) { - setenv("GALLIUM_DRIVER", "zink", 1); - setenv("MESA_LOADER_DRIVER_OVERRIDE", "zink", 1); - setenv("MESA_GL_VERSION_OVERRIDE", "3.2", 1); - setenv("MESA_GLSL_VERSION_OVERRIDE", "320", 1); - setenv("mesa_glthread", "true", 1); - } - if (libgl_egl) { - eglLib = OpenLib(EGLLibNames, libgl_egl); - } else { + 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) \ From 4e8103527af4885d781b5aa00302737937a38bda Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:32:28 +0800 Subject: [PATCH 115/116] Update Loader.h --- MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 5e225ee2e1aaabfd9a0449c9aef988a3717a80c8 Mon Sep 17 00:00:00 2001 From: aaaapai <89151134+aaaapai@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:35:16 +0800 Subject: [PATCH 116/116] Update Types.h --- MobileGL/MG_Util/Types.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Util/Types.h b/MobileGL/MG_Util/Types.h index e8011e212..7e6088283 100644 --- a/MobileGL/MG_Util/Types.h +++ b/MobileGL/MG_Util/Types.h @@ -88,12 +88,19 @@ template < > using UnorderedMap = std::unordered_map; #elif UseABSL -template < +/*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