From 86dec657f9d2bd60d566224791a13fa7e133c2ef Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 13 Dec 2023 18:11:28 +0100 Subject: [PATCH 01/96] Update Python Qt macOS CI (#1955) --- .github/workflows/qt-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/qt-ci.yml b/.github/workflows/qt-ci.yml index 7fb230b7e1f..e812c19b11f 100644 --- a/.github/workflows/qt-ci.yml +++ b/.github/workflows/qt-ci.yml @@ -180,7 +180,8 @@ jobs: env: MLN_COMPILER: ${{ matrix.compiler }} run: | - brew install --overwrite python@3.11 + # https://github.com/actions/runner-images/issues/8838#issuecomment-1817486924 + brew link --overwrite python@3.12 brew install "$MLN_COMPILER" echo "/usr/local/opt/${MLN_COMPILER}/bin" >> "$GITHUB_PATH" { From a7eb744976d74e233e1cd207651dc8a89cfcc263 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Sun, 17 Dec 2023 19:26:28 +0100 Subject: [PATCH 02/96] Add Bloaty iOS size test for PRs (#1966) --- .github/workflows/ios-ci.yml | 39 ++++++++++++++ .github/workflows/pr-bloaty-ios.yml | 80 +++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 .github/workflows/pr-bloaty-ios.yml diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 068a460f376..4c3131d57e7 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -164,6 +164,45 @@ jobs: ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTests.xctest.zip ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTestsApp.ipa + # Size test (Bloaty) + + - name: Build dynamic library for size test (Bloaty) + if: matrix.renderer == 'drawable' + run: | + bazel build //platform/ios:MapLibre.dynamic --//:renderer=metal --//:maplibre_platform=ios --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym + bazel_bin="$(bazel info --compilation_mode="opt" bazel-bin)" + unzip "$bazel_bin"/platform/ios/MapLibre.dynamic.xcframework.zip + cp "$bazel_bin"/platform/ios/MapLibre.dynamic_dsyms/MapLibre_ios_device.framework.dSYM/Contents/Resources/DWARF/MapLibre_ios_device MapLibre_DWARF + cp MapLibre.xcframework/ios-arm64/MapLibre.framework/MapLibre MapLibre_dynamic + + - name: Upload size test as artifact (Bloaty) + uses: actions/upload-artifact@v3 + if: matrix.renderer == 'drawable' && github.event_name == 'pull_request' + with: + name: ios-size-test-files + retention-days: 3 + if-no-files-found: error + path: | + platform/ios/MapLibre_DWARF + platform/ios/MapLibre_dynamic + + - name: Configure AWS Credentials + if: matrix.renderer == 'drawable' && github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-west-2 + role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} + role-session-name: ${{ github.run_id }} + + - name: Upload MapLibre_DWARF & MapLibre_dynamic to S3 + if: matrix.renderer == 'drawable' && github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + run: | + aws s3 cp MapLibre_DWARF s3://maplibre-native/size-test-ios/MapLibre_DWARF-main + aws s3 cp MapLibre_dynamic s3://maplibre-native/size-test-ios/MapLibre_dynamic-main + + - if: github.event_name == 'pull_request' + uses: ./.github/actions/save-pr-number + # Make Metal XCFramework release - name: Should make release? if: ${{ github.ref == 'refs/heads/main' && matrix.renderer == 'metal' }} diff --git a/.github/workflows/pr-bloaty-ios.yml b/.github/workflows/pr-bloaty-ios.yml new file mode 100644 index 00000000000..e8d8af7b905 --- /dev/null +++ b/.github/workflows/pr-bloaty-ios.yml @@ -0,0 +1,80 @@ +name: pr-bloaty-ios + +on: + workflow_run: + workflows: [ios-ci] + types: + - completed +env: + download_url: https://maplibre-native.s3.eu-central-1.amazonaws.com + +permissions: + pull-requests: write + id-token: write # This is required for requesting the AWS JWT + +jobs: + pr-bloaty-ios: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Get latest CMake and Ninja + uses: lukka/get-cmake@latest + with: + cmakeVersion: latest + ninjaVersion: latest + + - name: Cache Bloaty + id: cache-bloaty + uses: actions/cache@v3 + with: + path: bloaty/build/bloaty + key: bloaty-${{ env.bloaty_sha }} + + # because Google is not making a release... + # https://github.com/google/bloaty/issues/334 + - name: Compile Bloaty + if: ${{ !steps.cache-bloaty.outputs.cache-hit }} + run: | + git clone https://github.com/google/bloaty.git + cd bloaty + git checkout "$bloaty_sha" + cmake -B build -G Ninja -S . + cmake --build build + + - name: Download MapLibre_dynamic-main, MapLibre_DWARF-main + run: | + wget -O MapLibre_dynamic-main "${download_url}/size-test-ios/MapLibre_dynamic-main" + wget -O MapLibre_DWARF-main "${download_url}/size-test-ios/MapLibre_DWARF-main" + + - uses: ./.github/actions/get-pr-number + id: get-pr-number + + - uses: ./.github/actions/download-workflow-run-artifact + with: + artifact-name: ios-size-test-files + expect-files: "MapLibre_dynamic,MapLibre_DWARF" + + - name: Run Bloaty + run: bloaty/build/bloaty --debug-file MapLibre_DWARF --debug-file MapLibre_DWARF-main MapLibre_dynamic -n 0 -s vm -d compileunits -- MapLibre_dynamic-main > bloaty_diff.txt + + - name: Prepare Bloaty message + run: | + report_path=bloaty-results-ios/pr-${{ steps.get-pr-number.outputs.pr-number }}-compared-to-main.txt + aws s3 cp bloaty_diff.txt s3://maplibre-native/"$report_path" + { + echo "# Bloaty Results (iOS) 🐋" + echo 'Compared to main' + echo '```' + awk 'NR <= 2; END { print }' bloaty_diff.txt + echo '```' + echo "Full report: $download_url/$report_path" + echo "---" + } >> message.md + + - name: Leave a comment with Bloaty results + uses: marocchino/sticky-pull-request-comment@v2 + with: + number: ${{ steps.get-pr-number.outputs.pr-number }} + header: bloaty-ios + path: message.md From d39e048b3481c8929926adbc161e3b3f37e22b7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:10:24 +0100 Subject: [PATCH 03/96] Bump github/codeql-action from 2 to 3 (#1957) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/linux-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 3a8ccd3bf80..d74a1ba3f83 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -56,7 +56,7 @@ jobs: fetch-depth: 0 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: cpp @@ -131,7 +131,7 @@ jobs: # CodeQL - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:cpp" From 2a52c534fe0ecfed042dcd395b04c82801da02e8 Mon Sep 17 00:00:00 2001 From: Oliver Wipfli Date: Sun, 17 Dec 2023 22:32:14 +0300 Subject: [PATCH 04/96] Update Linux build instructions (#1965) Co-authored-by: Bart Louwers --- platform/linux/Dockerfile | 7 +++++++ platform/linux/README.md | 35 ++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 platform/linux/Dockerfile diff --git a/platform/linux/Dockerfile b/platform/linux/Dockerfile new file mode 100644 index 00000000000..f7e5366474c --- /dev/null +++ b/platform/linux/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:22.04 + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y libcurl4-openssl-dev libglfw3-dev libuv1-dev libpng-dev libicu-dev libjpeg-turbo8-dev libwebp-dev xvfb clang git cmake ccache ninja-build pkg-config && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /root diff --git a/platform/linux/README.md b/platform/linux/README.md index 7e1d8ad96bd..146abd4994a 100644 --- a/platform/linux/README.md +++ b/platform/linux/README.md @@ -1,23 +1,37 @@ # Linux -This guide explains how to get started building and running MapLibre Native on Linux. The guide focusses on **Debian 11**, but should be adaptible to other distributions. The build process should give you a set of `.a` files that you can use to include MapLibre Native in other C++ projects, as well as a set of executables that you can run to render map tile images and test the project. +This guide explains how to get started building and running MapLibre Native on Linux. The guide focusses on a Ubuntu 22.04. The build process should give you a set of `.a` files that you can use to include MapLibre Native in other C++ projects, as well as a set of executables that you can run to render map tile images and test the project. -## Prerequisites +## Requirements The following system libraries need to be installed. ```bash -apt install libcurl4-openssl-dev libglfw3-dev libuv1-dev libpng-dev libicu-dev libjpeg62-turbo libwebp-dev +apt install libcurl4-openssl-dev libglfw3-dev libuv1-dev libpng-dev libicu-dev libjpeg-turbo8-dev libwebp-dev xvfb ``` Optional: `libsqlite3-dev` (also available as vendored dependency). -### Build tools - The following tools need to be available. ```bash -apt install g++ git cmake ccache ninja-build pkg-config +apt install clang git cmake ccache ninja-build pkg-config +``` + +## Docker + +You can use a Docker container to build MapLibre Native. A `Dockerfile` that installes the required dependencies when the image is built is provided in this directory. + +Build image with: +```bash +# in platform/linux +docker build -t maplibre-native-image . +``` + +Run image with: +```bash +# in repo root directory +docker run --rm -it -v "$(pwd)":/root/ maplibre-native-image ``` ## Build @@ -29,11 +43,14 @@ git clone --recurse-submodules -j8 https://github.com/maplibre/maplibre-native.g cd maplibre-native ``` -To create the build, run the following commands from the root of the project. +To create the build, run the following commands from the root of the project: + +```bash +cmake -B build -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMLN_WITH_CLANG_TIDY=OFF -DMLN_WITH_COVERAGE=OFF -DMLN_DRAWABLE_RENDERER=ON -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON +``` ```bash -cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -cmake --build build -j $(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null) +cmake --build build --target mbgl-render -j $(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null) ``` ## `mbgl-render` From 2a6f7c3ef309d61d3b96e31788b7e80936c20c6e Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 18 Dec 2023 00:06:48 +0100 Subject: [PATCH 05/96] Add id-token permissions to iOS workflow (#1968) --- .github/workflows/ios-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 4c3131d57e7..961d129b3e1 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -17,6 +17,9 @@ on: branches: - '*' +permissions: + id-token: write # needed for AWS + jobs: pre_job: runs-on: ubuntu-latest From 29126ecee0d9032d88efb1f37810879b0bd14647 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 00:23:09 +0100 Subject: [PATCH 06/96] Bump actions/upload-artifact from 2 to 4 (#1964) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android-ci.yml | 8 ++++---- .github/workflows/gh-pages-mdbook.yml | 2 +- .github/workflows/ios-ci.yml | 6 +++--- .github/workflows/linux-ci.yml | 8 ++++---- .github/workflows/node-ci-mac.yml | 2 +- .github/workflows/node-ci.yml | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 81d789aeb37..97f8915752a 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -126,7 +126,7 @@ jobs: cp MapboxGLAndroidSDKTestApp/build/outputs/apk/androidTest/drawable/release/MapboxGLAndroidSDKTestApp-drawable-release-androidTest.apk . - name: Create artifact for benchmark APKs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: github.ref != 'refs/heads/main' with: if-no-files-found: error @@ -184,7 +184,7 @@ jobs: echo "ANDROID_APP_ARN=${{ steps.upload-android-app.outputs.arn }}" >> uploads.env echo "ANDROID_TEST_ARN=${{ steps.upload-android-test.outputs.arn }}" >> uploads.env - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: github.ref == 'refs/heads/main' with: if-no-files-found: error @@ -200,7 +200,7 @@ jobs: working-directory: ./render-test/android - name: Store Render Test .apk files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: android-render-tests if-no-files-found: error @@ -209,7 +209,7 @@ jobs: ./render-test/android/RenderTests.apk - name: Store debug artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debug-artifacts path: | diff --git a/.github/workflows/gh-pages-mdbook.yml b/.github/workflows/gh-pages-mdbook.yml index ffcc3c06716..4fcfefc23ad 100644 --- a/.github/workflows/gh-pages-mdbook.yml +++ b/.github/workflows/gh-pages-mdbook.yml @@ -22,7 +22,7 @@ jobs: working-directory: docs/mdbook shell: bash run: mdbook build - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: book path: docs/mdbook/book/ diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 961d129b3e1..9af823526f9 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -128,7 +128,7 @@ jobs: zip -r "$render_test_app_dir"/RenderTest.xctest.zip RenderTest.xctest echo render_test_artifacts_dir="$render_test_app_dir" >> "$GITHUB_ENV" - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: matrix.renderer == 'legacy' with: name: ios-render-test @@ -157,7 +157,7 @@ jobs: zip -r "$ios_cpp_test_app_dir"/CppUnitTests.xctest.zip CppUnitTests.xctest echo ios_cpp_test_artifacts_dir="$ios_cpp_test_app_dir" >> "$GITHUB_ENV" - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: matrix.renderer == 'legacy' with: name: ios-cpp-unit-tests @@ -179,7 +179,7 @@ jobs: cp MapLibre.xcframework/ios-arm64/MapLibre.framework/MapLibre MapLibre_dynamic - name: Upload size test as artifact (Bloaty) - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: matrix.renderer == 'drawable' && github.event_name == 'pull_request' with: name: ios-size-test-files diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index d74a1ba3f83..72d9e37f50e 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -100,7 +100,7 @@ jobs: - name: Upload mbgl-render as artifact if: matrix.renderer == 'drawable' && github.event_name == 'pull_request' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mbgl-render path: | @@ -108,7 +108,7 @@ jobs: - name: Upload mbgl-benchmark-runner as artifact if: matrix.renderer == 'drawable' && github.event_name == 'pull_request' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: mbgl-benchmark-runner path: | @@ -152,7 +152,7 @@ jobs: - name: Upload render test result if: always() && steps.render_test.outcome == 'failure' && matrix.renderer == 'drawable' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: render-test-result path: | @@ -213,7 +213,7 @@ jobs: echo coverage_report="$(bazel info output_path)"/_coverage/_coverage_report.dat >> "$GITHUB_ENV" - name: Upload coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-report path: ${{ env.coverage_report }} diff --git a/.github/workflows/node-ci-mac.yml b/.github/workflows/node-ci-mac.yml index 769dbabd7b7..4f4181f0f42 100644 --- a/.github/workflows/node-ci-mac.yml +++ b/.github/workflows/node-ci-mac.yml @@ -227,7 +227,7 @@ jobs: - name: Upload render test artifacts (MacOS) if: runner.os == 'MacOS' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: render-query-test-results path: metrics/macos-xcode11-release-style.html diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 3c847505c73..d50b8f98f92 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -252,7 +252,7 @@ jobs: - name: Upload render test artifacts (MacOS) if: runner.os == 'MacOS' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: render-query-test-results path: metrics/macos-xcode11-release-style.html From 9952c01d337a1b5e58c62344a4e4e3fe41e28e06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 00:23:26 +0100 Subject: [PATCH 07/96] Bump actions/download-artifact from 3 to 4 (#1963) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android-ci.yml | 2 +- .github/workflows/gh-pages-mdbook.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 97f8915752a..498deb6ad39 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -227,7 +227,7 @@ jobs: runs-on: ubuntu-latest if: github.repository_owner == 'maplibre' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/android-annotations') steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: uploadsEnv diff --git a/.github/workflows/gh-pages-mdbook.yml b/.github/workflows/gh-pages-mdbook.yml index 4fcfefc23ad..3d7dc6392b9 100644 --- a/.github/workflows/gh-pages-mdbook.yml +++ b/.github/workflows/gh-pages-mdbook.yml @@ -34,7 +34,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download book - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: book path: artifacts/book From 70114d714538b99bf4ec39835dcec8d23cd2d762 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 01:30:08 +0100 Subject: [PATCH 08/96] Update dependency gradle to v8.5 (#1901) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Bart Louwers --- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 43462 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 +- benchmark/android/gradlew | 311 +++++++++++------- benchmark/android/gradlew.bat | 66 ++-- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 43462 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 +- platform/android/gradlew | 282 ++++++++++------ platform/android/gradlew.bat | 34 +- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 43462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- render-test/android/gradlew | 283 +++++++++------- render-test/android/gradlew.bat | 38 +-- .../android/gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 43462 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- test/android/gradlew | 283 +++++++++------- test/android/gradlew.bat | 38 +-- 16 files changed, 803 insertions(+), 552 deletions(-) diff --git a/benchmark/android/gradle/wrapper/gradle-wrapper.jar b/benchmark/android/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -82,83 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/benchmark/android/gradlew.bat b/benchmark/android/gradlew.bat index aec99730b4e..6689b85beec 100644 --- a/benchmark/android/gradlew.bat +++ b/benchmark/android/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -8,20 +24,24 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,44 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/platform/android/gradle/wrapper/gradle-wrapper.jar b/platform/android/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 58910 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 diff --git a/platform/android/gradle/wrapper/gradle-wrapper.properties b/platform/android/gradle/wrapper/gradle-wrapper.properties index 408d627bf00..1af9e0930b8 100644 --- a/platform/android/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Thu Dec 15 15:24:50 PST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/platform/android/gradlew b/platform/android/gradlew index fbd7c515832..1aa94a42690 100755 --- a/platform/android/gradlew +++ b/platform/android/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,99 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/platform/android/gradlew.bat b/platform/android/gradlew.bat index a9f778a7a96..6689b85beec 100644 --- a/platform/android/gradlew.bat +++ b/platform/android/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/render-test/android/gradle/wrapper/gradle-wrapper.jar b/render-test/android/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 55616 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/render-test/android/gradlew.bat b/render-test/android/gradlew.bat index 24467a141f7..6689b85beec 100644 --- a/render-test/android/gradlew.bat +++ b/render-test/android/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,10 +25,14 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/test/android/gradle/wrapper/gradle-wrapper.jar b/test/android/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 55616 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/test/android/gradlew.bat b/test/android/gradlew.bat index 24467a141f7..6689b85beec 100644 --- a/test/android/gradlew.bat +++ b/test/android/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,10 +25,14 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 8820165ccafb4f3db109b0b4ae91739d577d7ece Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 18 Dec 2023 05:50:51 +0100 Subject: [PATCH 09/96] contents: write for ios-ci (#1971) --- .github/workflows/ios-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 9af823526f9..e89a7d34a51 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -19,6 +19,7 @@ on: permissions: id-token: write # needed for AWS + contents: write # allow making a release jobs: pre_job: From df30e7ef169da3942c3fa66388d468072a68a17f Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 18 Dec 2023 17:35:01 +0100 Subject: [PATCH 10/96] Fix dangling reference `file_source.cpp` Android (#1970) --- platform/android/MapboxGLAndroidSDK/src/cpp/file_source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/file_source.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/file_source.cpp index 776f42d3032..6ad4ee95d6d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/file_source.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/file_source.cpp @@ -93,7 +93,7 @@ void FileSource::setAPIBaseUrl(jni::JNIEnv& env, const jni::String& url) { jni::Local FileSource::getAPIBaseUrl(jni::JNIEnv& env) { if (auto* url = onlineSource->getProperty(mbgl::API_BASE_URL_KEY).getString()) { - return jni::Make(env, *url); + return jni::Make(env, std::string(*url)); } ThrowNew(env, jni::FindClass(env, "java/lang/IllegalStateException"), "Online functionality is disabled."); From 5420095e1b3e82693e1f4a80b54b7067b6c39d55 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 18 Dec 2023 18:46:06 +0100 Subject: [PATCH 11/96] Sunset non-Metal iOS builds on CI (#1967) --- .github/workflows/ios-ci.yml | 36 +++++++------------ .github/workflows/pr-bloaty-ios.yml | 8 +++++ .../test/MLNDocumentationExampleTests.swift | 4 ++- platform/darwin/test/MLNMapSnapshotterTests.m | 4 +++ 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index e89a7d34a51..047ac044960 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -45,14 +45,10 @@ jobs: ios-build: needs: pre_job if: needs.pre_job.outputs.should_skip != 'true' - strategy: - fail-fast: false - matrix: - renderer: [legacy, drawable, metal] runs-on: [self-hosted, macOS, ARM64] concurrency: # cancel jobs on PRs only - group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.renderer }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} env: BUILDTYPE: Debug @@ -98,25 +94,23 @@ jobs: - run: cp bazel/example_config.bzl bazel/config.bzl - name: Check debug symbols - run: bazel run //platform:check-public-symbols --//:renderer=${{ matrix.renderer }} + run: bazel run //platform:check-public-symbols --//:renderer=metal - name: Lint plist files - run: bazel run //platform/ios:lint-plists --//:renderer=${{ matrix.renderer }} + run: bazel run //platform/ios:lint-plists --//:renderer=metal - name: Running iOS tests - if: matrix.renderer != 'metal' - run: bazel test //platform/ios/test:ios_test --test_output=errors --//:renderer=${{ matrix.renderer }} + run: bazel test //platform/ios/test:ios_test --test_output=errors --//:renderer=metal - name: Running iOS UI tests - run: bazel test //platform/ios/iosapp-UITests:uitest --test_output=errors --//:renderer=${{ matrix.renderer }} + run: bazel test //platform/ios/iosapp-UITests:uitest --test_output=errors --//:renderer=metal # render test - name: Build RenderTest .ipa and .xctest for AWS Device Farm - if: matrix.renderer == 'legacy' run: | set -e - bazel run //platform/ios:xcodeproj + bazel run --//:renderer=metal //platform/ios:xcodeproj build_dir="$(mktemp -d)" xcodebuild build-for-testing -scheme RenderTest -project MapLibre.xcodeproj -derivedDataPath "$build_dir" render_test_app_dir="$(dirname "$(find "$build_dir" -name RenderTestApp.app)")" @@ -130,7 +124,6 @@ jobs: echo render_test_artifacts_dir="$render_test_app_dir" >> "$GITHUB_ENV" - uses: actions/upload-artifact@v4 - if: matrix.renderer == 'legacy' with: name: ios-render-test retention-days: 3 @@ -142,10 +135,9 @@ jobs: # C++ unit tests - name: Build CppUnitTests .ipa and .xctest for AWS Device Farm - if: matrix.renderer == 'legacy' run: | set -e - bazel run //platform/ios:xcodeproj + bazel run --//:renderer=metal //platform/ios:xcodeproj build_dir="$(mktemp -d)" xcodebuild build-for-testing -scheme CppUnitTests -project MapLibre.xcodeproj -derivedDataPath "$build_dir" ios_cpp_test_app_dir="$(dirname "$(find "$build_dir" -name CppUnitTestsApp.app)")" @@ -159,7 +151,6 @@ jobs: echo ios_cpp_test_artifacts_dir="$ios_cpp_test_app_dir" >> "$GITHUB_ENV" - uses: actions/upload-artifact@v4 - if: matrix.renderer == 'legacy' with: name: ios-cpp-unit-tests retention-days: 3 @@ -171,7 +162,6 @@ jobs: # Size test (Bloaty) - name: Build dynamic library for size test (Bloaty) - if: matrix.renderer == 'drawable' run: | bazel build //platform/ios:MapLibre.dynamic --//:renderer=metal --//:maplibre_platform=ios --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym bazel_bin="$(bazel info --compilation_mode="opt" bazel-bin)" @@ -180,8 +170,8 @@ jobs: cp MapLibre.xcframework/ios-arm64/MapLibre.framework/MapLibre MapLibre_dynamic - name: Upload size test as artifact (Bloaty) + if: github.event_name == 'pull_request' uses: actions/upload-artifact@v4 - if: matrix.renderer == 'drawable' && github.event_name == 'pull_request' with: name: ios-size-test-files retention-days: 3 @@ -191,7 +181,7 @@ jobs: platform/ios/MapLibre_dynamic - name: Configure AWS Credentials - if: matrix.renderer == 'drawable' && github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME uses: aws-actions/configure-aws-credentials@v4 with: aws-region: us-west-2 @@ -199,7 +189,7 @@ jobs: role-session-name: ${{ github.run_id }} - name: Upload MapLibre_DWARF & MapLibre_dynamic to S3 - if: matrix.renderer == 'drawable' && github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME run: | aws s3 cp MapLibre_DWARF s3://maplibre-native/size-test-ios/MapLibre_DWARF-main aws s3 cp MapLibre_dynamic s3://maplibre-native/size-test-ios/MapLibre_dynamic-main @@ -209,13 +199,13 @@ jobs: # Make Metal XCFramework release - name: Should make release? - if: ${{ github.ref == 'refs/heads/main' && matrix.renderer == 'metal' }} + if: ${{ github.ref == 'refs/heads/main' }} run: echo make_release=true >> "$GITHUB_ENV" - name: Build XCFramework run: | - bazel build --compilation_mode=opt --//:renderer=${{ matrix.renderer }} --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic - echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=${{ matrix.renderer }} --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" + bazel build --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic + echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" - name: Get version (release) if: env.make_release && github.event.inputs.release diff --git a/.github/workflows/pr-bloaty-ios.yml b/.github/workflows/pr-bloaty-ios.yml index e8d8af7b905..acb0fc9493f 100644 --- a/.github/workflows/pr-bloaty-ios.yml +++ b/.github/workflows/pr-bloaty-ios.yml @@ -58,6 +58,14 @@ jobs: - name: Run Bloaty run: bloaty/build/bloaty --debug-file MapLibre_DWARF --debug-file MapLibre_DWARF-main MapLibre_dynamic -n 0 -s vm -d compileunits -- MapLibre_dynamic-main > bloaty_diff.txt + - name: Configure AWS Credentials + if: vars.OIDC_AWS_ROLE_TO_ASSUME + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-west-2 + role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} + role-session-name: ${{ github.run_id }} + - name: Prepare Bloaty message run: | report_path=bloaty-results-ios/pr-${{ steps.get-pr-number.outputs.pr-number }}-compared-to-main.txt diff --git a/platform/darwin/test/MLNDocumentationExampleTests.swift b/platform/darwin/test/MLNDocumentationExampleTests.swift index 33a9908602c..c5a81a16451 100644 --- a/platform/darwin/test/MLNDocumentationExampleTests.swift +++ b/platform/darwin/test/MLNDocumentationExampleTests.swift @@ -458,7 +458,9 @@ class MLNDocumentationExampleTests: XCTestCase, MLNMapViewDelegate { //#-end-example-code } - func testMLNMapSnapshotter() { + func testMLNMapSnapshotter() throws { + throw XCTSkip("Snapshotter not implemented yet for Metal. See https://github.com/maplibre/maplibre-native/issues/1862") + let expectation = self.expectation(description: "MLNMapSnapshotter should produce a snapshot") #if os(macOS) var image: NSImage? { diff --git a/platform/darwin/test/MLNMapSnapshotterTests.m b/platform/darwin/test/MLNMapSnapshotterTests.m index 267b5e1c58b..400dd1986e4 100644 --- a/platform/darwin/test/MLNMapSnapshotterTests.m +++ b/platform/darwin/test/MLNMapSnapshotterTests.m @@ -75,6 +75,7 @@ - (void)tearDown { } - (void)testOverlayHandler { + XCTSkip(@"Snapshotter not implemented yet for Metal. See https://github.com/maplibre/maplibre-native/issues/1862"); self.styleLoadingExpectation = [self expectationWithDescription:@"Style should finish loading."]; XCTestExpectation *overlayExpectation = [self expectationWithDescription:@"Overlay handler should get called."]; XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Completion handler should get called."]; @@ -118,6 +119,7 @@ - (void)testOverlayHandler { } - (void)testDelegate { + XCTSkip(@"Snapshotter not implemented yet for Metal. See https://github.com/maplibre/maplibre-native/issues/1862"); self.styleLoadingExpectation = [self expectationWithDescription:@"Style should finish loading."]; XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Completion handler should get called."]; @@ -141,6 +143,7 @@ - (void)testDelegate { } - (void)testRuntimeStyling { + XCTSkip(@"Snapshotter not implemented yet for Metal. See https://github.com/maplibre/maplibre-native/issues/1862"); [self testStyleURL:nil camera:[MLNMapCamera camera] applyingRuntimeStylingActions:^(MLNStyle *style) { MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background"]; backgroundLayer.backgroundColor = [NSExpression expressionForConstantValue:[MLNColor orangeColor]]; @@ -149,6 +152,7 @@ - (void)testRuntimeStyling { } - (void)testLocalGlyphRendering { + XCTSkip(@"Snapshotter not implemented yet for Metal. See https://github.com/maplibre/maplibre-native/issues/1862"); [[NSUserDefaults standardUserDefaults] setObject:@[@"PingFang TC"] forKey:@"MLNIdeographicFontFamilyName"]; NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"mixed" withExtension:@"json"]; [self testStyleURL:styleURL camera:nil applyingRuntimeStylingActions:^(MLNStyle *style) {} expectedImageName:@"Fixtures/MLNMapSnapshotterTests/PingFang"]; From ff35e38c6fc37bc36392b0e6e089f93ca4d6ca5f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 22:27:42 +0100 Subject: [PATCH 12/96] [pre-commit.ci] pre-commit autoupdate (#1975) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0279f2aaf8d..390dd595262 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: files: '.*\.(hpp|cpp|h)' exclude: '(vendor/.*|platform/(ios|darwin|macos)/.*|test/ios/.*|render-test/ios/.*|benchmark/ios/.*)' - repo: https://github.com/keith/pre-commit-buildifier - rev: 6.3.3.1 + rev: 6.4.0 hooks: - id: buildifier - repo: https://github.com/Mateusz-Grzelinski/actionlint-py From 47028a62e2afc488f608656268c7d43f451e3abe Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 18 Dec 2023 23:31:38 +0100 Subject: [PATCH 13/96] Use debug build for Android UI tests (CI) (#1976) --- .github/workflows/android-ci.yml | 9 +++++---- .../android/MapboxGLAndroidSDKTestApp/build.gradle | 12 +++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 498deb6ad39..0e919a54fe7 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -121,7 +121,7 @@ jobs: - name: Build Benchmark, copy to platform/android if: github.ref != 'refs/heads/main' run: | - ./gradlew assembleDrawableRelease assembleDrawableReleaseAndroidTest + ./gradlew assembleDrawableRelease assembleDrawableReleaseAndroidTest -PtestBuildType=release cp MapboxGLAndroidSDKTestApp/build/outputs/apk/drawable/release/MapboxGLAndroidSDKTestApp-drawable-release.apk . cp MapboxGLAndroidSDKTestApp/build/outputs/apk/androidTest/drawable/release/MapboxGLAndroidSDKTestApp-drawable-release-androidTest.apk . @@ -140,7 +140,8 @@ jobs: - name: Build UI tests if: github.ref == 'refs/heads/main' - run: make android-ui-test-arm-v8 + run: | + ./gradlew assembleLegacyDebug assembleLegacyDebugAndroidTest -PtestBuildType=debug - name: Configure AWS Credentials if: github.ref == 'refs/heads/main' @@ -174,8 +175,8 @@ jobs: - name: Upload Android UI test if: github.ref == 'refs/heads/main' run: | - curl -T MapboxGLAndroidSDKTestApp/build/outputs/apk/legacy/release/MapboxGLAndroidSDKTestApp-legacy-debug.apk '${{ steps.upload-android-app.outputs.url }}' - curl -T MapboxGLAndroidSDKTestApp/build/outputs/apk/androidTest/legacy/release/MapboxGLAndroidSDKTestApp-legacy-debug-androidTest.apk '${{ steps.upload-android-test.outputs.url }}' + curl -T MapboxGLAndroidSDKTestApp/build/outputs/apk/legacy/debug/MapboxGLAndroidSDKTestApp-legacy-debug.apk '${{ steps.upload-android-app.outputs.url }}' + curl -T MapboxGLAndroidSDKTestApp/build/outputs/apk/androidTest/legacy/debug/MapboxGLAndroidSDKTestApp-legacy-debug-androidTest.apk '${{ steps.upload-android-test.outputs.url }}' - name: Write uploads.env if: github.ref == 'refs/heads/main' diff --git a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle index 139154ce61b..ef87a583e1f 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle +++ b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle @@ -7,6 +7,16 @@ plugins { apply from: "${rootDir}/gradle/native-build.gradle" +def obtainTestBuildType() { + def result = "debug"; + + if (project.hasProperty("testBuildType")) { + result = project.getProperties().get("testBuildType") + } + + result +} + android { compileSdkVersion androidVersions.compileSdkVersion @@ -55,7 +65,7 @@ android { } } - testBuildType "release" // for benchmark + testBuildType obtainTestBuildType() flavorDimensions += "renderer" productFlavors { From 612b5dd8a9d4f15c9884a2280de88f3e9ba53073 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 14:37:30 +0100 Subject: [PATCH 14/96] Bump tj-actions/changed-files from 40 to 41 (#1983) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android-ci.yml | 2 +- .github/workflows/ios-ci.yml | 2 +- .github/workflows/linux-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 0e919a54fe7..f1302914389 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -27,7 +27,7 @@ jobs: - name: Get all Android files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files - uses: tj-actions/changed-files@v40 + uses: tj-actions/changed-files@v41 with: files_yaml_from_source_file: .github/changed-files.yml diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 047ac044960..3caa4aa0af9 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -32,7 +32,7 @@ jobs: - name: Get all iOS files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files-yaml - uses: tj-actions/changed-files@v40 + uses: tj-actions/changed-files@v41 with: files_yaml_from_source_file: .github/changed-files.yml diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 72d9e37f50e..f605d8650c0 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -32,7 +32,7 @@ jobs: - name: Get all Linux files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files - uses: tj-actions/changed-files@v40 + uses: tj-actions/changed-files@v41 with: files_yaml_from_source_file: .github/changed-files.yml From f1098b24040e27d9a692ea4cf873be5a705d44be Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 1 Jan 2024 11:35:19 +0100 Subject: [PATCH 15/96] Add warning about changing names in Android getting started docs (#1987) --- docs/mdbook/src/android/getting-started-guide.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/mdbook/src/android/getting-started-guide.md b/docs/mdbook/src/android/getting-started-guide.md index 9ff6eacf970..6ff8114bc81 100644 --- a/docs/mdbook/src/android/getting-started-guide.md +++ b/docs/mdbook/src/android/getting-started-guide.md @@ -1,5 +1,11 @@ # Quickstart +

+ 1. Add bintray Maven repositories to your project-level Gradle file (usually `//build.gradle`). ```gradle From 39bcb210a00bd1151a54d80242f8b547d69a1c9e Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Thu, 4 Jan 2024 10:48:30 +0100 Subject: [PATCH 16/96] Automated CocoaPods release (#1991) --- .github/workflows/ios-ci.yml | 13 +++++++++++-- platform/ios/MapLibre.podspec | 13 +++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 3caa4aa0af9..4f6aa354d4d 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -221,14 +221,23 @@ jobs: git tag -a ios-v${{ env.version }} -m "Publish ios-v${{ env.version }}" ${{ github.sha }} git push origin ios-v${{ env.version }} - - name: Release + - name: Release (GitHub) if: env.make_release + id: release uses: softprops/action-gh-release@v1 with: name: ios-v${{ env.version }} - files: ${{ env.xcframework }} + files: | + ${{ env.xcframework }} + LICENSE.md tag_name: ios-v${{ env.version }} prerelease: ${{ !github.event.inputs.release }} + fail_on_unmatched_files: true + + - name: Release (CocoaPods) + if: env.make_release + run: | + VERSION=${{ env.version }} COCOAPODS_TRUNK_TOKEN=${{ secrets.COCOAPODS_PASSWORD }} pod trunk push MapLibre.podspec ios-ci-result: runs-on: ubuntu-latest diff --git a/platform/ios/MapLibre.podspec b/platform/ios/MapLibre.podspec index ccf6528badb..bdb2cea5f23 100644 --- a/platform/ios/MapLibre.podspec +++ b/platform/ios/MapLibre.podspec @@ -1,18 +1,19 @@ Pod::Spec.new do |s| - version = '6.0.0-pre0' + version = "#{ENV['VERSION']}" s.name = 'MapLibre' s.version = version - s.license = { :type => 'BSD', :text => '' } + s.license = { :type => 'BSD', :file => "LICENSE.md" } s.homepage = 'https://maplibre.org/' s.authors = { 'MapLibre' => '' } s.summary = 'Open source vector map solution for iOS with full styling capabilities.' s.platform = :ios s.source = { - :http => "https://github.com/maplibre/maplibre-native/releases/download/ios-v#{version.to_s}/MapLibre-#{version.to_s}.zip", - :flatten => false + :http => "https://github.com/maplibre/maplibre-native/releases/download/ios-v#{version.to_s}/MapLibre.dynamic.xcframework.zip", + :type => "zip" } s.social_media_url = 'https://mastodon.social/@maplibre' - s.ios.deployment_target = '11.0' - s.ios.vendored_frameworks = "**/MapLibre.xcframework" + s.ios.deployment_target = '12.0' + s.ios.vendored_frameworks = "MapLibre.xcframework" end + From 9599200f2529de44ba62d4662cddb445dc19397d Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Thu, 4 Jan 2024 13:01:52 +0100 Subject: [PATCH 17/96] Fix Cocoapods release (#1992) --- .github/workflows/ios-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 4f6aa354d4d..3b92b7cc42f 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -227,16 +227,16 @@ jobs: uses: softprops/action-gh-release@v1 with: name: ios-v${{ env.version }} - files: | - ${{ env.xcframework }} - LICENSE.md + files: ${{ env.xcframework }} tag_name: ios-v${{ env.version }} prerelease: ${{ !github.event.inputs.release }} fail_on_unmatched_files: true - name: Release (CocoaPods) + shell: bash -leo pipefail {0} # so pod is found if: env.make_release run: | + zip ${{ env.xcframework }} ../../LICENSE.md # add license to zip VERSION=${{ env.version }} COCOAPODS_TRUNK_TOKEN=${{ secrets.COCOAPODS_PASSWORD }} pod trunk push MapLibre.podspec ios-ci-result: From e92cae0f312c49decba1d127bb760758228d24df Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Thu, 4 Jan 2024 15:28:41 +0100 Subject: [PATCH 18/96] Automated release to Swift Package Index (#1993) --- .github/workflows/ios-ci.yml | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 3b92b7cc42f..678c1812079 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -221,17 +221,46 @@ jobs: git tag -a ios-v${{ env.version }} -m "Publish ios-v${{ env.version }}" ${{ github.sha }} git push origin ios-v${{ env.version }} + - name: Add license to XCFramework zip + if: env.make_release + run: | + cp ${{ env.xcframework }} MapLibre.dynamic.xcframework.zip + zip MapLibre.dynamic.xcframework.zip LICENSE.md # add license to zip + working-directory: . + - name: Release (GitHub) if: env.make_release - id: release + id: github_release uses: softprops/action-gh-release@v1 with: name: ios-v${{ env.version }} - files: ${{ env.xcframework }} + files: MapLibre.dynamic.xcframework.zip tag_name: ios-v${{ env.version }} prerelease: ${{ !github.event.inputs.release }} fail_on_unmatched_files: true + # needed to trigger workflow for Swift Package Index release + - name: Generate token + if: env.make_release + id: generate_token + uses: tibdex/github-app-token@v2 + with: + app_id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} + private_key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} + + - name: Release (Swift Package Index) + if: env.make_release + run: | + echo "::add-mask::${{ steps.generate_token.outputs.token }}" + release_workflow_id=81221759 # id of release.yml + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: token ${{ steps.generate_token.outputs.token }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ + -d '{"ref":"main","inputs":{"version":"${{ env.version }}","download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' + - name: Release (CocoaPods) shell: bash -leo pipefail {0} # so pod is found if: env.make_release From 210eb45b44f2cabf08aff483685043898155aaff Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Fri, 5 Jan 2024 16:43:57 +0100 Subject: [PATCH 19/96] Add changelog to iOS releases (#1996) --- .github/workflows/ios-ci.yml | 12 +++++++++++- platform/ios/CHANGELOG.md | 5 ++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 678c1812079..8f13af96830 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -215,6 +215,11 @@ jobs: if: env.make_release && !github.event.inputs.release run: echo version="$(cat VERSION)"-pre${{ github.sha }} >> "$GITHUB_ENV" + - name: Extract changelog for version + run: | + awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "## {{ env.version }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md + cat changelog_for_version.md + - name: Create tag if: env.make_release run: | @@ -225,6 +230,7 @@ jobs: if: env.make_release run: | cp ${{ env.xcframework }} MapLibre.dynamic.xcframework.zip + chmod +w MapLibre.dynamic.xcframework.zip zip MapLibre.dynamic.xcframework.zip LICENSE.md # add license to zip working-directory: . @@ -237,6 +243,7 @@ jobs: files: MapLibre.dynamic.xcframework.zip tag_name: ios-v${{ env.version }} prerelease: ${{ !github.event.inputs.release }} + body_path: platform/ios/changelog_for_version.md fail_on_unmatched_files: true # needed to trigger workflow for Swift Package Index release @@ -259,7 +266,10 @@ jobs: -H "Authorization: token ${{ steps.generate_token.outputs.token }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ - -d '{"ref":"main","inputs":{"version":"${{ env.version }}","download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' + -d '{"ref":"main","inputs":{ \ + "changelog": '"$(cat changelog_for_version.md)"' \ + "version":"${{ env.version }}", \ + "download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' - name: Release (CocoaPods) shell: bash -leo pipefail {0} # so pod is found diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 115f603502c..04775a902dc 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,8 +4,11 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C ## main -## 6.0.0-pre0 +## 6.0.0 +* This is the first release that uses **Metal** for rendering. This is a graphics API from Apple that replaces OpenGL ES on Apple platforms. + * Note that the [snapshotter](https://github.com/maplibre/maplibre-native/issues/1862) has not been implemented yet for the Metal renderer. Hold off updating if your application requires this functionality. + * While we had a long period of pre-releases and testing leading up to this release, and no crashes have been reported, it is possible that you come across inconsistencies or problems in production apps. Please report them on [GitHub](https://github.com/maplibre/maplibre-native/issues/1609). * 💥 Breaking: Changed the prefix of files, classes, methods, variables and everything from `MGL` to `MLN`. ([#919](https://github.com/maplibre/maplibre-native/pull/919)). > To migrate: From fe728ebdd8b58edc1d01ce538fcec8dbec247031 Mon Sep 17 00:00:00 2001 From: Nathan Gass Date: Fri, 5 Jan 2024 17:29:26 +0100 Subject: [PATCH 20/96] add methods getTiles and setTiles to mimick maplibre-gl-js interface (#149) --- include/mbgl/style/sources/vector_source.hpp | 8 ++++ src/mbgl/style/sources/vector_source.cpp | 19 +++++++++ test/style/source.test.cpp | 44 ++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/include/mbgl/style/sources/vector_source.hpp b/include/mbgl/style/sources/vector_source.hpp index 0adb529a73b..1b57851269e 100644 --- a/include/mbgl/style/sources/vector_source.hpp +++ b/include/mbgl/style/sources/vector_source.hpp @@ -26,6 +26,14 @@ class VectorSource final : public Source { void loadDescription(FileSource&) final; + /// @brief Gets the tile urls for this vector source. + /// @return List of tile urls. + const std::vector getTiles() const; + + /// @brief Sets the tile urls for this vector source. + /// @param tiles List of tile urls. + void setTiles(const std::vector& tiles); + bool supportsLayerType(const mbgl::style::LayerTypeInfo*) const override; mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } diff --git a/src/mbgl/style/sources/vector_source.cpp b/src/mbgl/style/sources/vector_source.cpp index b3ad9513c3c..47630bf3e9f 100644 --- a/src/mbgl/style/sources/vector_source.cpp +++ b/src/mbgl/style/sources/vector_source.cpp @@ -92,6 +92,25 @@ void VectorSource::loadDescription(FileSource& fileSource) { }); } +const std::vector VectorSource::getTiles() const { + auto tileset = impl().tileset; + if (tileset.has_value()) { + return tileset->tiles; + } else { + return {}; + } +} + +void VectorSource::setTiles(const std::vector& tiles) { + auto& tileset = impl().tileset; + if (!tileset.has_value()) return; + if (tileset->tiles == tiles) return; + Tileset newtileset(*tileset); + newtileset.tiles = tiles; + baseImpl = makeMutable(impl(), newtileset); + observer->onSourceChanged(*this); +} + bool VectorSource::supportsLayerType(const mbgl::style::LayerTypeInfo* info) const { return mbgl::underlying_type(Tile::Kind::Geometry) == mbgl::underlying_type(info->tileKind); } diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index 1aefe5332ff..b14f53af199 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -919,6 +919,50 @@ TEST(Source, RenderTileSetSourceUpdate) { renderSource->update(uninitialized.baseImpl, layers, true, true, test.tileParameters()); } +TEST(Source, VectorSourceSetTiles) { + SourceTest test; + test.styleObserver.sourceChanged = [&](Source& source) { + EXPECT_EQ(source.as()->getTiles(), std::vector{"new"}); + test.end(); + }; + + VectorSource source("source", Tileset{{"url"}}); + source.setObserver(&test.styleObserver); + source.setTiles({"unused"}); // make sure early setTiles does not segfault + source.loadDescription(*test.fileSource); + EXPECT_EQ(source.as()->getTiles(), std::vector{"url"}); + source.setTiles({"new"}); + test.run(); +} + +TEST(Source, VectorSourceUrlSetTiles) { + SourceTest test; + test.styleObserver.sourceLoaded = [&](Source& source) { + EXPECT_EQ(source.as()->getTiles(), std::vector{"tiles"}); + source.as()->setTiles({"new"}); + }; + int count = 0; + test.styleObserver.sourceChanged = [&](Source& source) { + if (count++) { + EXPECT_EQ(source.as()->getTiles(), std::vector{"new"}); + test.end(); + } + }; + + test.fileSource->sourceResponse = [&](const Resource& resource) { + EXPECT_EQ("url", resource.url); + Response response; + response.data = std::make_unique(R"({"tiles": ["tiles"]})"); + return response; + }; + + VectorSource source("source", "url"); + source.setObserver(&test.styleObserver); + source.setTiles({"unused"}); + source.loadDescription(*test.fileSource); + test.run(); +} + TEST(Source, GeoJSONSourceTilesAfterDataReset) { SourceTest test; GeoJSONSource source("source"); From a479c84b9b749e2eaa9c19d862afc74673697272 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Fri, 5 Jan 2024 11:56:16 -0800 Subject: [PATCH 21/96] Stencil clear (#1994) --- src/mbgl/renderer/paint_parameters.cpp | 42 ++++++-------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index cd9841da1f5..0e801a0421a 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -101,37 +101,12 @@ using GetTileIDFunc = const UnwrappedTileID& (*)(const typename TIter::value_typ using TileMaskIDMap = std::map; -// `std::includes` adapted to extract tile IDs from arbitrary collection iterators. -// This is needed because, although it accepts iterators of different types, they must be: -// "...such that an object of type InputIt can be dereferenced and then implicitly converted to both of them.​" -template -bool includes(I1 beg1, const I1 end1, I2 beg2, const I2 end2, const GetTileIDFunc& unwrap) { - for (; beg2 != end2; ++beg1) { - if (beg1 == end1) { - return false; - } - const auto id2 = unwrap(*beg2); - if (id2 < beg1->first) { - return false; - } else if (!(beg1->first < id2)) { - ++beg2; - } - } - return true; -} - -const UnwrappedTileID& unwrap(const RenderTiles::element_type::value_type& iter) { - return iter.get().id; -} - -// Check whether the given set of tile IDs is a subset of the ones already rendered +// Check whether we can reuse a clip mask for a new set of tiles bool tileIDsCovered(const RenderTiles& tiles, const TileMaskIDMap& idMap) { - if (idMap.size() < tiles->size()) { - return false; - } - assert(std::is_sorted( - tiles->begin(), tiles->end(), [=](const auto& a, const auto& b) { return unwrap(a) < unwrap(b); })); - return includes(idMap.cbegin(), idMap.cend(), tiles->cbegin(), tiles->cend(), &unwrap); + return idMap.size() == tiles->size() && + std::equal(idMap.cbegin(), idMap.cend(), tiles->cbegin(), tiles->cend(), [=](const auto& a, const auto& b) { + return a.first == b.get().id; + }); } } // namespace @@ -162,14 +137,17 @@ void PaintParameters::clearStencil() { } void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) { - // If the current stencil mask covers this source already, there's no need to draw another one. + // We can avoid updating the mask if it already contains the same set of tiles. if (!renderTiles || !renderPass || tileIDsCovered(renderTiles, tileClippingMaskIDs)) { return; } + tileClippingMaskIDs.clear(); + + // If the stencil value will overflow, clear the target to ensure ensure that none of the new + // values remain set somewhere in it. Otherwise we can continue to overwrite it incrementally. const auto count = renderTiles->size(); if (nextStencilID + count > maxStencilValue) { - // we'll run out of fresh IDs so we need to clear and start from scratch clearStencil(); } From 1811e91e3d40d14c860e9ff4e53d95fae68945c7 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Fri, 5 Jan 2024 21:48:33 +0100 Subject: [PATCH 22/96] Skip instead of fail workflows (#1997) --- .github/workflows/android-device-test.yml | 3 +-- .github/workflows/ios-ci.yml | 2 +- .github/workflows/pr-bloaty-ios.yml | 1 + .github/workflows/pr-render-test-result.yml | 13 ++++++++++++- .github/workflows/pr-tests.yml | 2 ++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index 40858c3c2b1..d436b8cbcfc 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -40,6 +40,7 @@ jobs: } ] runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' steps: - uses: actions/checkout@v4 @@ -50,8 +51,6 @@ jobs: app_id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} private_key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} - - run: echo "${{ toJSON(github.event.workflow_run) }}" - - uses: ./.github/actions/get-pr-number id: get-pr-number diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 8f13af96830..b42bca71858 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -216,6 +216,7 @@ jobs: run: echo version="$(cat VERSION)"-pre${{ github.sha }} >> "$GITHUB_ENV" - name: Extract changelog for version + if: env.make_release run: | awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "## {{ env.version }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md cat changelog_for_version.md @@ -275,7 +276,6 @@ jobs: shell: bash -leo pipefail {0} # so pod is found if: env.make_release run: | - zip ${{ env.xcframework }} ../../LICENSE.md # add license to zip VERSION=${{ env.version }} COCOAPODS_TRUNK_TOKEN=${{ secrets.COCOAPODS_PASSWORD }} pod trunk push MapLibre.podspec ios-ci-result: diff --git a/.github/workflows/pr-bloaty-ios.yml b/.github/workflows/pr-bloaty-ios.yml index acb0fc9493f..bf15007031b 100644 --- a/.github/workflows/pr-bloaty-ios.yml +++ b/.github/workflows/pr-bloaty-ios.yml @@ -14,6 +14,7 @@ permissions: jobs: pr-bloaty-ios: + if: github.event.workflow_run.event == 'pull_request' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr-render-test-result.yml b/.github/workflows/pr-render-test-result.yml index e2d72e97002..5461753ee70 100644 --- a/.github/workflows/pr-render-test-result.yml +++ b/.github/workflows/pr-render-test-result.yml @@ -13,6 +13,7 @@ on: jobs: upload-render-test-result: runs-on: ubuntu-22.04 + if: github.event.workflow_run.event == 'pull_request' env: html_filename: "linux-drawable.html" steps: @@ -24,9 +25,17 @@ jobs: - uses: ./.github/actions/download-workflow-run-artifact with: artifact-name: render-test-result - expect-files: ${{ env.html_filename }} + + # when there are no results, the render test succeeded + # in this case the subsequent steps can be skipped + - name: "Check existence render test results HTML" + id: render_test_results + uses: andstor/file-existence-action@v2.0.0 + with: + files: ${{ env.html_filename }} - name: Configure AWS Credentials + if: steps.render_test_results.outputs.files_exists uses: aws-actions/configure-aws-credentials@v4 with: aws-region: us-west-2 @@ -34,6 +43,7 @@ jobs: role-session-name: ${{ github.run_id }} - name: Upload render test results to S3 + if: steps.render_test_results.outputs.files_exists id: upload_render_test_results run: | aws s3 cp metrics/${{ env.html_filename }} \ @@ -41,6 +51,7 @@ jobs: --expires "$(date -d '+30 days' --utc +'%Y-%m-%dT%H:%M:%SZ')" - name: 'Leave comment on PR with test results' + if: steps.render_test_results.outputs.files_exists uses: marocchino/sticky-pull-request-comment@v2 with: header: render-test-result diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index c34c1f17690..8ab85c52e47 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -17,6 +17,7 @@ permissions: jobs: pr-bloaty: + if: github.event.workflow_run.event == 'pull_request' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -110,6 +111,7 @@ jobs: path: message.md pr-benchmark: + if: github.event.workflow_run.event == 'pull_request' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 From ab83392d13e3b4259e8faa9db56ab17671aa112e Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 8 Jan 2024 01:07:40 +0100 Subject: [PATCH 23/96] Add instructions iOS release and small fix (#2002) --- .github/workflows/ios-ci.yml | 4 ++-- SECURITY.md | 2 +- platform/ios/RELEASE.md | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 platform/ios/RELEASE.md diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index b42bca71858..fa06c03f00a 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -268,13 +268,13 @@ jobs: -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ -d '{"ref":"main","inputs":{ \ - "changelog": '"$(cat changelog_for_version.md)"' \ + "changelog": '"$(cat changelog_for_version.md)"', \ "version":"${{ env.version }}", \ "download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' - name: Release (CocoaPods) shell: bash -leo pipefail {0} # so pod is found - if: env.make_release + if: env.make_release && github.event.inputs.release run: | VERSION=${{ env.version }} COCOAPODS_TRUNK_TOKEN=${{ secrets.COCOAPODS_PASSWORD }} pod trunk push MapLibre.podspec diff --git a/SECURITY.md b/SECURITY.md index b5aa7049161..a03f85bd0d7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -24,7 +24,7 @@ The maintainers of MapLibre Native are committed to a fast and efficient resolut 6. `[Committer]` A new release is pushed out from the branch that now contains the fix. The release process is automated and documented and can be done by anyone with write access. - Android: [platform/android/RELEASE.md](https://github.com/maplibre/maplibre-native/blob/main/platform/android/RELEASE.md) - - iOS: TODO, please see this issue: https://github.com/maplibre/maplibre-native/issues/1581 + - iOS: [platform/ios/RELEASE.md](https://github.com/maplibre/maplibre-native/blob/main/platform/android/RELEASE.md) 7. `[Maintainer]` The security advisory is [published](https://github.com/maplibre/maplibre-native/security/advisories?state=published). diff --git a/platform/ios/RELEASE.md b/platform/ios/RELEASE.md new file mode 100644 index 00000000000..3be88c5aadb --- /dev/null +++ b/platform/ios/RELEASE.md @@ -0,0 +1,19 @@ +# Instructions for making an iOS release + +1. Make sure the `VERSION` file in `platform/ios/VERSION` contains the version to be released. We use semantic versioning, so any breaking changes require a major version bump. + +2. Update the changelog, which can be found in `platform/ios/CHANGELOG.md`. The heading must match `## ` exactly, or it will not be picked up. For example, for version 6.0.0: + +``` +## 6.0.0 +``` + +3. Run the `ios-ci` workflow. You can use the [GitHub CLI](https://cli.github.com/manual/gh_workflow_run): + +``` +gh workflow run ios-ci.yml -f release=true --ref main +``` + +Or run the workflow from the Actions tab on GitHub: + +Screenshot 2024-01-07 at 16 23 50 From 8c940cb7a65ad4c865cc51d1b8ce88dd425c52b8 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 8 Jan 2024 14:18:52 +0100 Subject: [PATCH 24/96] Switch to manual iOS pre-releases (#2003) --- .github/workflows/ios-ci.yml | 30 +++++++++++++++++++----------- platform/ios/RELEASE.md | 22 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index fa06c03f00a..c79bb85b735 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -4,9 +4,13 @@ on: workflow_dispatch: inputs: release: - required: false - type: boolean - description: Makes a release with version platform/ios/VERSION + type: choice + default: no + options: + - full + - pre + - no + description: Whether to make a release, choose full release (uses platform/ios/VERSION) or pre-release push: branches: - main @@ -199,7 +203,7 @@ jobs: # Make Metal XCFramework release - name: Should make release? - if: ${{ github.ref == 'refs/heads/main' }} + if: github.event.inputs.release == 'full' || github.event.inputs.release == 'pre' run: echo make_release=true >> "$GITHUB_ENV" - name: Build XCFramework @@ -208,17 +212,21 @@ jobs: echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" - name: Get version (release) - if: env.make_release && github.event.inputs.release - run: echo version="$(cat VERSION)" >> "$GITHUB_ENV" + if: github.event.inputs.release == 'full' + run: | + echo version="$(head VERSION)" >> "$GITHUB_ENV" + echo changelog_version_heading="## {{ env.version }}" >> "$GITHUB_ENV" - name: Get version (pre-release) - if: env.make_release && !github.event.inputs.release - run: echo version="$(cat VERSION)"-pre${{ github.sha }} >> "$GITHUB_ENV" + if: github.event.inputs.release == 'pre' + run: | + echo version="$(head VERSION)"-pre${{ github.sha }} >> "$GITHUB_ENV" + echo changelog_version_heading="## main" >> "$GITHUB_ENV" - name: Extract changelog for version if: env.make_release run: | - awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "## {{ env.version }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md + awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "{{ env.changelog_version_heading }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md cat changelog_for_version.md - name: Create tag @@ -268,13 +276,13 @@ jobs: -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ -d '{"ref":"main","inputs":{ \ - "changelog": '"$(cat changelog_for_version.md)"', \ + "changelog": "'"$(cat changelog_for_version.md)"'", \ "version":"${{ env.version }}", \ "download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' - name: Release (CocoaPods) shell: bash -leo pipefail {0} # so pod is found - if: env.make_release && github.event.inputs.release + if: env.make_release run: | VERSION=${{ env.version }} COCOAPODS_TRUNK_TOKEN=${{ secrets.COCOAPODS_PASSWORD }} pod trunk push MapLibre.podspec diff --git a/platform/ios/RELEASE.md b/platform/ios/RELEASE.md index 3be88c5aadb..a6efffc68f2 100644 --- a/platform/ios/RELEASE.md +++ b/platform/ios/RELEASE.md @@ -1,19 +1,35 @@ # Instructions for making an iOS release +We make iOS releases to GitHub (a downloadable XCFramework), the [Swift Package Index](https://swiftpackageindex.com/maplibre/maplibre-gl-native-distribution) and [CocoaPods](https://cocoapods.org/). Everyone with write access to the repository is able to make releases using the instructions below. + +## Pre-release + +Run the `ios-ci` workflow. You can use the [GitHub CLI](https://cli.github.com/manual/gh_workflow_run): + +``` +gh workflow run ios-ci.yml -f release=pre --ref main +``` + +Or run the workflow from the Actions tab on GitHub. + +The items under the `## main` heading in `platform/ios/CHANGELOG.md` will be used as changelog for the pre-release. + +## Full release + 1. Make sure the `VERSION` file in `platform/ios/VERSION` contains the version to be released. We use semantic versioning, so any breaking changes require a major version bump. 2. Update the changelog, which can be found in `platform/ios/CHANGELOG.md`. The heading must match `## ` exactly, or it will not be picked up. For example, for version 6.0.0: -``` +```md ## 6.0.0 ``` 3. Run the `ios-ci` workflow. You can use the [GitHub CLI](https://cli.github.com/manual/gh_workflow_run): ``` -gh workflow run ios-ci.yml -f release=true --ref main +gh workflow run ios-ci.yml -f release=full --ref main ``` Or run the workflow from the Actions tab on GitHub: -Screenshot 2024-01-07 at 16 23 50 +Screenshot 2024-01-08 at 11 00 30 \ No newline at end of file From 43e52d054e234992ba030ffae3076b682d410324 Mon Sep 17 00:00:00 2001 From: Andrew Calcutt Date: Mon, 8 Jan 2024 12:51:31 -0500 Subject: [PATCH 25/96] Update node release based on maplibre-js release workflow - v2 with changelog (#1995) Co-authored-by: mwilsnd Co-authored-by: Bart Louwers Co-authored-by: Bart Louwers --- .github/workflows/node-ci-mac.yml | 259 ----------------------------- .github/workflows/node-ci.yml | 13 +- .github/workflows/node-release.yml | 123 ++++++++------ .nvmrc | 1 + package-lock.json | 14 +- package.json | 4 +- platform/node/CHANGELOG.md | 115 ++++++------- platform/node/README.md | 4 +- platform/node/RELEASE.md | 13 ++ 9 files changed, 159 insertions(+), 387 deletions(-) delete mode 100644 .github/workflows/node-ci-mac.yml create mode 100644 .nvmrc create mode 100644 platform/node/RELEASE.md diff --git a/.github/workflows/node-ci-mac.yml b/.github/workflows/node-ci-mac.yml deleted file mode 100644 index 4f4181f0f42..00000000000 --- a/.github/workflows/node-ci-mac.yml +++ /dev/null @@ -1,259 +0,0 @@ -# Note: this workflow has been copied from node-ci.yml -# That workflow runs for main, this one runs for opengl-2 due to -# the lacking OpenGL ES 3.0 support on macOS. -# Once the Metal backend is completed this workflow can be removed. - -name: node-ci - -on: - workflow_dispatch: - push: - branches: - - opengl-2 - tags: - - 'node-*' - paths: - - CMakeLists.txt - - "platform/default/**" - - 'platform/node/**' - - 'platform/windows/**' - - 'platform/darwin/**' - - 'platform/macos/**' - - 'platform/ios/platform/darwin/**' - - 'platform/ios/platform/macos/**' - - ".github/workflows/node-ci.yml" - - "bin/**" - - "expression-test/**" - - "include/**" - - "metrics/**" - - "render-test/**" - - "scripts/**" - - "src/**" - - "test/**" - - "vendor/**" - - ".gitmodules" - - "!**/*.md" - - pull_request: - branches: - - opengl-2 - paths: - - CMakeLists.txt - - "platform/default/**" - - 'platform/node/**' - - 'platform/windows/**' - - 'platform/darwin/**' - - 'platform/macos/**' - - 'platform/ios/platform/darwin/**' - - 'platform/ios/platform/macos/**' - - ".github/workflows/node-ci.yml" - - "bin/**" - - "expression-test/**" - - "include/**" - - "metrics/**" - - "render-test/**" - - "scripts/**" - - "src/**" - - "test/**" - - "vendor/**" - - ".gitmodules" - - "!**/*.md" - -concurrency: - # cancel jobs on PRs only - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - -jobs: - test: - runs-on: ${{ matrix.runs-on }} - strategy: - fail-fast: false - matrix: - include: - - runs-on: macos-12 - arch: x86_64 - continue-on-error: true - env: - BUILDTYPE: 'Release' - - defaults: - run: - working-directory: ./ - shell: bash - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup submodules - shell: bash - run: | - auth_header="$(git config --local --get http.https://github.com/.extraheader)" - git submodule sync --recursive - git -c core.longpaths=true -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive || true - - - name: Get OS Architecture - if: runner.os == 'MacOS' || runner.os == 'Linux' - run: uname -m - - - name: Install dependencies (MacOS) - if: runner.os == 'MacOS' - env: - HOMEBREW_NO_AUTO_UPDATE: 1 - HOMEBREW_NO_INSTALL_CLEANUP: 1 - run: | - brew list cmake || brew install cmake - brew list ccache || brew install ccache - brew list ninja || brew install ninja - brew list pkg-config || brew install pkg-config - brew list glfw || brew install glfw - brew list libuv || brew install libuv - - - name: Install dependencies (Linux) - if: runner.os == 'Linux' - env: - DEBIAN_FRONTEND: noninteractive - run: | - sudo apt-get update - sudo apt-get install -y \ - ccache \ - cmake \ - ninja-build \ - pkg-config \ - xvfb \ - libcurl4-openssl-dev \ - libglfw3-dev \ - libuv1-dev \ - g++-10 \ - libc++-9-dev \ - libc++abi-9-dev \ - libjpeg-dev \ - libpng-dev - /usr/sbin/update-ccache-symlinks - - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: npm ci - run: npm ci --ignore-scripts - - - name: Set up msvc dev cmd (Windows) - if: runner.os == 'Windows' - uses: ilammy/msvc-dev-cmd@v1 - - - name: Set up ccache (MacOS/Linux) - if: runner.os == 'MacOS' || runner.os == 'Linux' - uses: hendrikmuhs/ccache-action@v1 - with: - key: ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }}-${{ github.ref }}-${{ github.sha }}-${{ github.head_ref }} - restore-keys: | - ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }}-${{ github.ref }}-${{ github.sha }} - ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }}-${{ github.ref }} - ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }} - - - name: Set up ccache (Windows) - if: runner.os == 'Windows' - uses: hendrikmuhs/ccache-action@v1 - with: - variant: 'sccache' - key: ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }}-${{ github.ref }}-${{ github.sha }}-${{ github.head_ref }} - restore-keys: | - ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }}-${{ github.ref }}-${{ github.sha }} - ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }}-${{ github.ref }} - ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }} - - - name: Configure maplibre-native (MacOS) - if: runner.os == 'MacOS' - run: | - cmake . -B build \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=${{ env.BUILDTYPE }} \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - - - name: Configure maplibre-native (Linux) - if: runner.os == 'Linux' - run: | - cmake . -B build \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=${{ env.BUILDTYPE }} \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DCMAKE_C_COMPILER=gcc-10 \ - -DCMAKE_CXX_COMPILER=g++-10 - - - name: "Create directory '${{ github.workspace }}/platform/windows/vendor/vcpkg/bincache' (Windows)" - if: runner.os == 'Windows' - run: mkdir -p ${{ github.workspace }}/platform/windows/vendor/vcpkg/bincache - shell: bash - - - name: Restore vcpkg cache (Windows) - if: runner.os == 'Windows' - uses: actions/cache@v3 - with: - path: | - ${{ github.workspace }}/platform/windows/vendor/vcpkg - !${{ github.workspace }}/platform/windows/vendor/vcpkg/buildtrees - !${{ github.workspace }}/platform/windows/vendor/vcpkg/packages - !${{ github.workspace }}/platform/windows/vendor/vcpkg/downloads - !${{ github.workspace }}/platform/windows/vendor/vcpkg/installed - key: | - ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }}-${{ hashFiles( '.git/modules/platform/windows/vendor/vcpkg/HEAD' ) }}-${{ hashFiles( 'platform/windows/Get-VendorPackages.ps1' ) }} - - - name: Configure maplibre-native (Windows) - if: runner.os == 'Windows' - run: | - cmake . -B build \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=${{ env.BUILDTYPE }} \ - -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - - - name: Build maplibre-native (MacOS/Linux) - if: runner.os == 'MacOS' || runner.os == 'Linux' - run: | - cmake --build build -j "$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null)" - - - name: Build maplibre-native (Windows) - if: runner.os == 'Windows' - run: | - cmake --build build - - - name: Run render tests on macOS - if: runner.os == 'macOS' - run: set -o pipefail && ./build/mbgl-render-test-runner --manifestPath metrics/macos-xcode11-release-style.json - - - name: Upload render test artifacts (MacOS) - if: runner.os == 'MacOS' - uses: actions/upload-artifact@v4 - with: - name: render-query-test-results - path: metrics/macos-xcode11-release-style.html - - - name: Test (Linux) - if: runner.os == 'Linux' - run: xvfb-run --auto-servernum npm test - - - name: Test (MacOS) - if: runner.os == 'MacOS' - run: npm test - - - name: Test (Windows) - if: runner.os == 'Windows' - shell: pwsh - env: - LIBGL_ALWAYS_SOFTWARE: true - GALLIUM_DRIVER: softpipe - run: | - Invoke-WebRequest https://github.com/pal1000/mesa-dist-win/releases/download/22.3.5/mesa3d-22.3.5-release-msvc.7z -OutFile mesa3d.7z - & 'C:\Program Files\7-Zip\7z.exe' e -olib\node-v108 .\mesa3d.7z x64\opengl32.dll x64\libgallium_wgl.dll x64\libGLESv2.dll x64\libglapi.dll - npm test - - # On PRs make sure that the npm package can be packaged. - - name: Pack - if: github.ref != 'refs/heads/main' - run: | - npm pack --dry-run - diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index d50b8f98f92..93e4e88183c 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -73,6 +73,12 @@ jobs: arch: x86_64 - runs-on: [self-hosted, linux, ARM64] arch: arm64 + # Disabled until Metal backend is complete + # A release for macOS can be made from the opengl-2 branch + # - runs-on: macos-12 + # arch: x86_64 + # - runs-on: [self-hosted, macOS, ARM64] + # arch: arm64 - runs-on: windows-2022 arch: x86_64 continue-on-error: true @@ -134,10 +140,10 @@ jobs: libwebp-dev /usr/sbin/update-ccache-symlinks - - name: Setup node + - name: Use Node.js from nvmrc uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: '.nvmrc' - name: npm ci run: npm ci --ignore-scripts @@ -155,7 +161,7 @@ jobs: "PATH=$env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Append - name: Setup cmake (Linux) - if: runner.os == 'Linux' && runner.arch != 'arm64' + if: runner.os == 'Linux' && matrix.arch != 'arm64' uses: jwlawson/actions-setup-cmake@v1.14 with: cmake-version: "3.19.x" @@ -278,6 +284,5 @@ jobs: # On PRs make sure that the npm package can be packaged. - name: Pack - if: github.ref != 'refs/heads/main' run: | npm pack --dry-run diff --git a/.github/workflows/node-release.yml b/.github/workflows/node-release.yml index 9db3a4d2cce..71d743354a3 100644 --- a/.github/workflows/node-release.yml +++ b/.github/workflows/node-release.yml @@ -1,54 +1,44 @@ name: node-release on: + push: + branches: [main] workflow_dispatch: - inputs: - version: - description: Choose a release type - required: true - type: choice - options: - - prerelease - - prepatch - - preminor - - premajor - - patch - - minor - - major jobs: - bump_version: - runs-on: ubuntu-20.04 - + release-check: + name: Check if version is published + runs-on: ubuntu-latest defaults: run: - working-directory: ./ shell: bash - steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.NODE_PRE_GYP_GITHUB_TOKEN }} - - name: Setup node + - name: Use Node.js from nvmrc uses: actions/setup-node@v4 with: - node-version: 18 - - - name: npm version - run: | - echo "NPM_VERSION=$(npm version ${{ github.event.inputs.version }} --preid pre --no-git-tag-version)" >> "$GITHUB_ENV" + node-version-file: '.nvmrc' - - name: Push version to git + - name: Check if version is published + id: check run: | - git config user.name github-actions - git config user.email github-actions@github.com - git commit -a -m "Update node version to ${{ env.NPM_VERSION }} (${{ github.event.inputs.version }})" - git push + currentVersion="$( node -e "console.log(require('./package.json').version)" )" + isPublished="$( npm view @maplibre/maplibre-gl-native versions --json | jq -c --arg cv "$currentVersion" 'any(. == $cv)' )" + echo "published=$isPublished" >> "$GITHUB_OUTPUT" + echo "currentVersion: $currentVersion" + echo "isPublished: $isPublished" + outputs: + published: ${{ steps.check.outputs.published }} publish_binaries: + needs: release-check + if: ${{ needs.release-check.outputs.published == 'false' }} runs-on: ${{ matrix.runs-on }} + permissions: + contents: write strategy: fail-fast: false matrix: @@ -59,14 +49,12 @@ jobs: arch: arm64 # Disabled until Metal backend is complete # A release for macOS can be made from the opengl-2 branch - # - runs-on: macos-12 # arch: x86_64 - # - runs-on: macos-12-arm + # - runs-on: [self-hosted, macOS, ARM64] # arch: arm64 - runs-on: windows-2022 arch: x86_64 - needs: bump_version continue-on-error: true env: BUILDTYPE: "Release" @@ -82,9 +70,6 @@ jobs: with: fetch-depth: 0 - - name: Get Latest Version - run: git pull - - name: Setup submodules shell: bash run: | @@ -129,10 +114,10 @@ jobs: libwebp-dev /usr/sbin/update-ccache-symlinks - - name: Setup node + - name: Use Node.js from nvmrc uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: '.nvmrc' - name: npm ci run: npm ci --ignore-scripts @@ -150,7 +135,7 @@ jobs: "PATH=$env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Append - name: Setup cmake (Linux) - if: runner.os == 'Linux' && runner.arch != 'arm64' + if: runner.os == 'Linux' && matrix.arch != 'arm64' uses: jwlawson/actions-setup-cmake@v1.14 with: cmake-version: "3.19.x" @@ -242,30 +227,30 @@ jobs: cmake --build build - name: Publish X64 Release to Github - if: github.ref == 'refs/heads/main' && matrix.arch == 'x86_64' + if: matrix.arch == 'x86_64' env: PUBLISH: true BUILDTYPE: RelWithDebInfo - NODE_PRE_GYP_GITHUB_TOKEN: ${{ secrets.NODE_PRE_GYP_GITHUB_TOKEN }} + NODE_PRE_GYP_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | ./platform/node/scripts/publish.sh - name: Publish ARM Release to Github - if: github.ref == 'refs/heads/main' && matrix.arch == 'arm64' + if: matrix.arch == 'arm64' env: PUBLISH: true BUILDTYPE: RelWithDebInfo - NODE_PRE_GYP_GITHUB_TOKEN: ${{ secrets.NODE_PRE_GYP_GITHUB_TOKEN }} + NODE_PRE_GYP_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | ./platform/node/scripts/publish.sh --target_arch=arm64 publish_npm: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest needs: publish_binaries - + permissions: + contents: write defaults: run: - working-directory: ./ shell: bash steps: @@ -273,16 +258,48 @@ jobs: with: fetch-depth: 0 - - name: Get Latest Version - run: git pull - - - name: Setup node + - name: Use Node.js from nvmrc uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: '.nvmrc' + + - name: Get version + id: package-version + uses: martinbeentjes/npm-get-version-action@v1.3.1 + + - name: npm ci + run: npm ci --ignore-scripts + + - name: Prepare release + id: prepare_release + run: | + RELEASE_TYPE="$(node -e "console.log(require('semver').prerelease('${{ steps.package-version.outputs.current-version }}') ? 'prerelease' : 'regular')")" + if [[ $RELEASE_TYPE == 'regular' ]]; then + echo "prerelease=false" >> "$GITHUB_OUTPUT" + else + echo "prerelease=true" >> "$GITHUB_OUTPUT" + fi + + - name: Extract changelog for version + run: | + awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "## ${{ steps.package-version.outputs.current-version }}" { p = 1 };' platform/node/CHANGELOG.md > changelog_for_version.md + cat changelog_for_version.md + + - name: Update Release Notes + id: update_release_notes + uses: ncipollo/release-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag: node-v${{ steps.package-version.outputs.current-version }} + name: node-v${{ steps.package-version.outputs.current-version }} + bodyFile: changelog_for_version.md + allowUpdates: true + draft: false + prerelease: ${{ steps.prepare_release.outputs.prerelease }} - name: Publish to NPM (release) - if: github.ref == 'refs/heads/main' && (github.event.inputs.version == 'patch' || github.event.inputs.version == 'minor' || github.event.inputs.version == 'major') + if: ${{ steps.prepare_release.outputs.prerelease == 'false' }} run: | npm config set //registry.npmjs.org/:_authToken "${NPM_TOKEN}" npm publish --access public @@ -290,7 +307,7 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_ORG_TOKEN }} - name: Publish to NPM (prerelease) - if: github.ref == 'refs/heads/main' && (github.event.inputs.version == 'prerelease' || github.event.inputs.version == 'prepatch' || github.event.inputs.version == 'preminor' || github.event.inputs.version == 'premajor') + if: ${{ steps.prepare_release.outputs.prerelease == 'true' }} run: | npm config set //registry.npmjs.org/:_authToken "${NPM_TOKEN}" npm publish --tag next --access public diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..89e0c3dba3b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.10 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7d218ba3549..b092bb7e497 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "BSD-2-Clause", "dependencies": { - "@acalcutt/node-pre-gyp": "^1.0.11", + "@acalcutt/node-pre-gyp": "^1.0.14", "@acalcutt/node-pre-gyp-github": "1.4.8", "minimatch": "^7.2.0", "npm-run-all": "^4.0.2" @@ -48,9 +48,9 @@ } }, "node_modules/@acalcutt/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@acalcutt/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-aeT0m5l3TdO0Mzs0lVIN6Qq+RYGyUu8XoE46r/o2mpZ/ve5o9B/AQT12S1ACIrQGHf0BrGGSy9yhV9SUe7ogJw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@acalcutt/node-pre-gyp/-/node-pre-gyp-1.0.14.tgz", + "integrity": "sha512-P+xIiJefMa2ylrqPDwCw1S4Xr6ULKvF5TqmbZKifPSzId9jiSgLoplKfTmoP/y3Mq2kWts/rZDX1N9wMaSl6ZA==", "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -4353,9 +4353,9 @@ }, "dependencies": { "@acalcutt/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@acalcutt/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-aeT0m5l3TdO0Mzs0lVIN6Qq+RYGyUu8XoE46r/o2mpZ/ve5o9B/AQT12S1ACIrQGHf0BrGGSy9yhV9SUe7ogJw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@acalcutt/node-pre-gyp/-/node-pre-gyp-1.0.14.tgz", + "integrity": "sha512-P+xIiJefMa2ylrqPDwCw1S4Xr6ULKvF5TqmbZKifPSzId9jiSgLoplKfTmoP/y3Mq2kWts/rZDX1N9wMaSl6ZA==", "requires": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", diff --git a/package.json b/package.json index 5a277be86f3..030c62afc5a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "license": "BSD-2-Clause", "dependencies": { - "@acalcutt/node-pre-gyp": "^1.0.11", + "@acalcutt/node-pre-gyp": "^1.0.14", "@acalcutt/node-pre-gyp-github": "1.4.8", "minimatch": "^7.2.0", "npm-run-all": "^4.0.2" @@ -58,7 +58,7 @@ "node": ">=6" }, "scripts": { - "install": "node-pre-gyp install --fallback-to-build=true", + "install": "node-pre-gyp install --fallback-to-build=false", "test": "tape platform/node/test/js/**/*.test.js", "test-memory": "node --expose-gc platform/node/test/memory.test.js", "test-expressions": "node -r esm platform/node/test/expression.test.js", diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index a9217440249..48c4d2cb872 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,15 +1,10 @@ -# main +## main -### ✨ New features -- *...Add new stuff here...* * Make Node Map object options "request" property optional by @tdcosta100 in https://github.com/maplibre/maplibre-native/pull/904 - -### 🐞 Bug fixes -- *...Add new stuff here...* * Compile Node targets without -std=c++11 option by @tdcosta100 in https://github.com/maplibre/maplibre-native/pull/926 -# 5.2.0 +## 5.2.0 * Adjust Typings for Node Platform by @etnav in https://github.com/maplibre/maplibre-native/pull/871 * Node platform improvements (added setSize and a new render call without render options object) by @tdcosta100 in https://github.com/maplibre/maplibre-native/pull/891 * Move node ci+release to self hosted Ubuntu arm64 by @acalcutt in https://github.com/maplibre/maplibre-native/pull/873 @@ -25,14 +20,14 @@ * Use `*_t` and `*_v` trait helpers from C++17 STL by @louwers in https://github.com/maplibre/maplibre-native/pull/731 * Avoid implicit casts and portable printf with size_t by @tdcosta100 in https://github.com/maplibre/maplibre-native/pull/722 -# 5.1.1 +## 5.1.1 * Fix memory access violation exception in vector_tile_data.cpp by @tdcosta100 in https://github.com/maplibre/maplibre-native/pull/632 -# 5.1.0 +## 5.1.0 * First Maplibre Native Node Stable Release * Node workflow - build linux arm64 in a container #520 https://github.com/maplibre/maplibre-native/pull/590 -# 5.0.1 +## 5.0.1 * Exclude Node 19 (ABI 111) because it breaks the node build by @acalcutt in https://github.com/maplibre/maplibre-native/pull/542 * Fix mode switch not working in node version by @acalcutt in https://github.com/maplibre/maplibre-native/pull/415 * Node release workflow by @acalcutt in https://github.com/maplibre/maplibre-native/pull/378 https://github.com/maplibre/maplibre-native/pull/459 https://github.com/maplibre/maplibre-native/pull/505 https://github.com/maplibre/maplibre-native/pull/512 https://github.com/maplibre/maplibre-native/pull/514 @@ -40,11 +35,11 @@ * [Breaking] Remove node 10 support. v5.0.1-pre.0 of the node package can be used a compatibility version. * Bring back node support by @jutaz in https://github.com/maplibre/maplibre-native/pull/217 -# 5.0.0 +## 5.0.0 * No longer supporting source-compile fallback ([#15748](https://github.com/mapbox/mapbox-gl-native/pull/15748)) * Add support for feature state APIs. ([#15480](https://github.com/mapbox/mapbox-gl-native/pull/15480)) -# 4.3.0 +## 4.3.0 * Introduce `text-writing-mode` layout property for symbol layer ([#14932](https://github.com/mapbox/mapbox-gl-native/pull/14932)). The `text-writing-mode` layout property allows control over symbol's preferred writing mode. The new property value is an array, whose values are enumeration values from a ( `horizontal` | `vertical` ) set. * Fixed rendering and collision detection issues with using `text-variable-anchor` and `icon-text-fit` properties on the same layer ([#15367](https://github.com/mapbox/mapbox-gl-native/pull/15367)). * Fixed a rendering issue that non-SDF icon would be treated as SDF icon if they are in the same layer. ([#15456](https://github.com/mapbox/mapbox-gl-native/pull/15456)) @@ -53,17 +48,17 @@ * Add typechecking while constructing legacy filter to prevent converting an unexpected filter type [#15389](https://github.com/mapbox/mapbox-gl-native/pull/15389). * Fixed an issue that `maxzoom` in style `Sources` option was ignored when URL resource is provided. It may cause problems such as extra tiles downloading at higher zoom level than `maxzoom`, or problems that wrong setting of `overscaledZ` in `OverscaledTileID` that will be passed to `SymbolLayout`, leading wrong rendering appearance. ([#15581](https://github.com/mapbox/mapbox-gl-native/pull/15581)) -# 4.2.0 +## 4.2.0 - Add an option to set whether or not an image should be treated as a SDF ([#15054](https://github.com/mapbox/mapbox-gl-native/issues/15054)) - Fix problems associated with node 10 and NAN ([#14847](https://github.com/mapbox/mapbox-gl-native/pull/14847)) -# 4.1.0 +## 4.1.0 - Add `symbol-z-order` symbol layout property to style spec ([#12783](https://github.com/mapbox/mapbox-gl-native/pull/12783)) - Add `crossSourceCollisions` map option, with default of `true`. When set to `false`, cross-source collision detection is disabled. ([#12820](https://github.com/mapbox/mapbox-gl-native/issues/12820)) - Fixed bugs in coercion expression operators ("to-array" applied to empty arrays, "to-color" applied to colors, and "to-number" applied to null) ([#12864](https://github.com/mapbox/mapbox-gl-native/pull/12864)) - Fixed an issue where fill and line layers would occasionally flicker on zoom ([#12982](https://github.com/mapbox/mapbox-gl-native/pull/12982)) -# 4.0.0 +## 4.0.0 - Many new features and enhancements, including: - Expressions - Hillshade layer type @@ -81,125 +76,125 @@ - Fix rendering of fill outlines that have a different color than the fill itself ([#9699](https://github.com/mapbox/mapbox-gl-native/pull/9699)) - Add support for feature expressions in `line-pattern`, `fill-pattern`, and `fill-extrusion-pattern` properties. [#12284](https://github.com/mapbox/mapbox-gl-native/pull/12284) -# 3.5.8 - October 19, 2017 +## 3.5.8 - October 19, 2017 - Fixes an issue that causes memory leaks when not deleting the frontend object in NodeMap::release() - Fixes a crash in Earcut: [#10245](https://github.com/mapbox/mapbox-gl-native/pull/10245) -# 3.5.7 - October 9, 2017 +## 3.5.7 - October 9, 2017 - Fixed an issue causing synchronous resource requests to stall [#10153](https://github.com/mapbox/mapbox-gl-native/pull/10153) -# 3.5.6 - September 29, 2017 +## 3.5.6 - September 29, 2017 - Protects against requests which throw [#9554](https://github.com/mapbox/mapbox-gl-native/pull/9554) - Fixed an issue around reusing a map object [#9554](https://github.com/mapbox/mapbox-gl-native/pull/9554) - Fixed an issue in test [#9553](https://github.com/mapbox/mapbox-gl-native/pull/9553) - Increased the default maximum zoom level from 20 to 22 ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) -# 3.5.5 - July 14, 2017 +## 3.5.5 - July 14, 2017 - Provide debuggable release builds for node packages [#9497](https://github.com/mapbox/mapbox-gl-native/pull/9497) -# 3.5.4 - June 6, 2017 +## 3.5.4 - June 6, 2017 - Add support for ImageSource [#8968](https://github.com/mapbox/mapbox-gl-native/pull/8968) - Fixed an issue with `map.addImage()` which would cause added images to randomly be replaced with images found the style's sprite sheet ([#9119](https://github.com/mapbox/mapbox-gl-native/pull/9119)) -# 3.5.3 - May 30, 2017 +## 3.5.3 - May 30, 2017 - Fixed a regression around `line-dasharrary` and `fill-pattern` that caused these properties to sometimes not render correctly ([#9130](https://github.com/mapbox/mapbox-gl-native/pull/9130)) -# 3.5.2 - May 18, 2017 +## 3.5.2 - May 18, 2017 - Fixed a memory leak ([#8884](https://github.com/mapbox/mapbox-gl-native/pull/9035)) -# 3.5.1 - May 8, 2017 +## 3.5.1 - May 8, 2017 - Adds Node v6 binaries. **Note, Node v4 binaries will be removed on August 1st.** ([#8884](https://github.com/mapbox/mapbox-gl-native/pull/8884)) - Adds linux debug binaries ([#8865](https://github.com/mapbox/mapbox-gl-native/pull/8865)) -# 3.5.0 - April 20, 2017 +## 3.5.0 - April 20, 2017 - Fixed an issue where raster tiles that were not found caused `map.render()` to hang ([#8769](https://github.com/mapbox/mapbox-gl-native/pull/8769)) - Adds method `map.cancel()` which cancels an ongoing `render` call. ([#8249](https://github.com/mapbox/mapbox-gl-native/pull/8249)) -# 3.4.7 - March 15, 2017 +## 3.4.7 - March 15, 2017 - Fixed MacOS Release builds ([8409](https://github.com/mapbox/mapbox-gl-native/pull/8409)) -# 3.4.6 - March 14, 2017 +## 3.4.6 - March 14, 2017 - Publishes `Release` build on Mac ([#8407](https://github.com/mapbox/mapbox-gl-native/pull/8407)) - Fixes the publish binary build process ([#8406](https://github.com/mapbox/mapbox-gl-native/pull/8406)) -# 3.4.5 - March 14, 2017 +## 3.4.5 - March 14, 2017 - Fixed a memory hang issue after GlyphAtlas was refactored ([#8394](https://github.com/mapbox/mapbox-gl-native/pull/8394)) -# 3.4.4 - January 10, 2017 +## 3.4.4 - January 10, 2017 - Updates the node binary publish location on s3 to reflect new package name ([#7653](https://github.com/mapbox/mapbox-gl-native/pull/7653)) -# 3.4.3 - January 9, 2017 +## 3.4.3 - January 9, 2017 - Adds `map.addImage()` and `map.removeImage()` APIs ([#7610](https://github.com/mapbox/mapbox-gl-native/pull/7610)) -# 3.4.2 - November 15, 2016 +## 3.4.2 - November 15, 2016 - Switches back to publishing Linux binaries with GLX, to eliminate a runtime dependency on `libOSMesa.so.8` and enable dynamically linking against `libGL.so` provided by an alternate implementation, such as the NVIDIA proproetary drivers ([#7503](https://github.com/mapbox/mapbox-gl-native/pull/7053)) -# 3.4.1 - November 10, 2016 +## 3.4.1 - November 10, 2016 - Skips assigning clip IDs to tiles that won't be rendered, mitigating a `stencil mask overflow` error ([#6871](https://github.com/mapbox/mapbox-gl-native/pull/6871)) - Fixes camera logic to avoid unnecessary or redundant setting of camera options ([#6990](https://github.com/mapbox/mapbox-gl-native/pull/6990)) -# 3.4.0 - November 2, 2016 +## 3.4.0 - November 2, 2016 - Fixes Bitrise configuration to automatically publish macOS binaries ([#6789](https://github.com/mapbox/mapbox-gl-native/pull/6789)) - Switches from using individual thread pools for each `mbgl::Map` object to sharing the built-in Node.js thread pool for NodeMap implementations ([#6687](https://github.com/mapbox/mapbox-gl-native/pull/6687)) -# 3.3.3 - September 6, 2016 +## 3.3.3 - September 6, 2016 - Switches to using a NodeRequest member function (with a JavaScript shim in front to preserve the API) instead of a new `v8::Context` to avoid a memory leak ([#5704](https://github.com/mapbox/mapbox-gl-native/pull/5704)) - `map.load` can now throw when failing to parse an invalid style ([#6151](https://github.com/mapbox/mapbox-gl-native/pull/6151)) - Explicitly links the OpenGL framework for compatibility with macOS Sierra ([#6015](https://github.com/mapbox/mapbox-gl-native/pull/6015)) -# 3.3.2 - August 1, 2016 +## 3.3.2 - August 1, 2016 - Fixes Node.js binary publishing to build with `BUILDTYPE=Release` ([#5838](https://github.com/mapbox/mapbox-gl-native/pull/5838)) -# 3.3.1 - July 29, 2016 +## 3.3.1 - July 29, 2016 - Fixes `minzoom` and `maxzoom` properties ([#5828](https://github.com/mapbox/mapbox-gl-native/pull/5828)) - Fixes `RunLoop::runOnce()` to use `UV_RUN_NOWAIT` instead of `UV_RUN_ONCE` (which can block the libuv threadpool) ([#5758](https://github.com/mapbox/mapbox-gl-native/pull/5758)) - Map debug options 'overdraw' and 'stencil clip' are now disabled (no-ops) in release mode ([#5555](https://github.com/mapbox/mapbox-gl-native/pull/5555)) -# 3.3.0 - July 14, 2016 +## 3.3.0 - July 14, 2016 - Adds runtime styling API ([#5318](https://github.com/mapbox/mapbox-gl-native/pull/5318), [#5380](https://github.com/mapbox/mapbox-gl-native/pull/5380), [#5428](https://github.com/mapbox/mapbox-gl-native/pull/5428), [#5429](https://github.com/mapbox/mapbox-gl-native/pull/5429), [#5462](https://github.com/mapbox/mapbox-gl-native/pull/5462), [#5614](https://github.com/mapbox/mapbox-gl-native/pull/5614), [#5670](https://github.com/mapbox/mapbox-gl-native/pull/5670)) - Adds `BUILDTYPE=Debug` support to `make node` ([#5474](https://github.com/mapbox/mapbox-gl-native/pull/5474)) - Fixes a memory leak in `NodeRequest` ([#5529](https://github.com/mapbox/mapbox-gl-native/pull/5529)) -# 3.2.1 - June 7, 2016 +## 3.2.1 - June 7, 2016 - Fixes a memory leak in raster image data ([#5269](https://github.com/mapbox/mapbox-gl-native/pull/5269)) -# 3.2.0 - June 3, 2016 +## 3.2.0 - June 3, 2016 - Switches to [earcut.hpp](https://github.com/mapbox/earcut.hpp) for tessellation ([#2444](https://github.com/mapbox/mapbox-gl-native/pull/2444)) -# 3.1.3 - May 27, 2016 +## 3.1.3 - May 27, 2016 - Fixes a leak in TexturePoolHolder ([#5141](https://github.com/mapbox/mapbox-gl-native/pull/5141)) - Fixes a bug where a callback would be fired after an AsyncRequest had been cancelled ([#5162](https://github.com/mapbox/mapbox-gl-native/pull/5162)) -# 3.1.2 - April 26, 2016 +## 3.1.2 - April 26, 2016 - Fixes a race condition with animated transitions ([#4836](https://github.com/mapbox/mapbox-gl-native/pull/4836)) -# 3.1.1 - April 11, 2016 +## 3.1.1 - April 11, 2016 - Moves node-pre-gyp from `bundledDependencies` to `preinstall` ([#4680](https://github.com/mapbox/mapbox-gl-native/pull/4680)) -# 3.1.0 - April 8, 2016 +## 3.1.0 - April 8, 2016 - Adds debug render options ([#3840](https://github.com/mapbox/mapbox-gl-native/pull/3840)) - Fixes circle bucket rendering on tile boundaries ([#3764](https://github.com/mapbox/mapbox-gl-native/issues/3764)) @@ -209,44 +204,44 @@ - Fixes intermittent `stencil mask overflow` error ([#962](https://github.com/mapbox/mapbox-gl-native/issues/962)) - Drops support for Node.js v5.x prebuilt binaries due to ongoing npm3 instability ([#4370](https://github.com/mapbox/mapbox-gl-native/issues/4370)) -# 3.0.2 - February 4, 2016 +## 3.0.2 - February 4, 2016 - Fixes a memory leak in `NodeMap::request` ([#3829](https://github.com/mapbox/mapbox-gl-native/pull/3829)) - Increases default max zoom level from 18 to 20 ([#3712](https://github.com/mapbox/mapbox-gl-native/pull/3712)) - Support tiles with non-4096 extents ([#3766](https://github.com/mapbox/mapbox-gl-native/pull/3766)) -# 3.0.1 - January 26, 2016 +## 3.0.1 - January 26, 2016 - Fixes missing icon collision boxes ([#3672](https://github.com/mapbox/mapbox-gl-native/pull/3672)) - Fixes texture filtering to draw sharper icons ([#3669](https://github.com/mapbox/mapbox-gl-native/pull/3669)) -# 3.0.0 - January 21, 2016 +## 3.0.0 - January 21, 2016 - Drops support for Node.js v0.10.x ([#3635](https://github.com/mapbox/mapbox-gl-native/pull/3635)) - Fixes label clipping issues with `symbol-avoid-edges` ([#3623](https://github.com/mapbox/mapbox-gl-native/pull/3623)) - Avoids label placement around sharp zig-zags ([#3640](https://github.com/mapbox/mapbox-gl-native/pull/3640)) -# 2.2.2 - January 19, 2016 +## 2.2.2 - January 19, 2016 - Fixes a bug with non-deterministic label placement [#3543](https://github.com/mapbox/mapbox-gl-native/pull/3543) -# 2.2.1 - January 7, 2016 +## 2.2.1 - January 7, 2016 - Fixes a bug which clipped labels at tile boundaries [#2829](https://github.com/mapbox/mapbox-gl-native/pull/2829) -# 2.2.0 - December 16, 2015 +## 2.2.0 - December 16, 2015 - Adds support for GeoJSON sources [#2161](https://github.com/mapbox/mapbox-gl-native/pull/2161) -# 2.1.0 - December 8, 2015 +## 2.1.0 - December 8, 2015 - Adds [`line-offset`](https://github.com/mapbox/mapbox-gl/issues/3) style property support -# 2.0.1 - November 25, 2015 +## 2.0.1 - November 25, 2015 - Test and publish binaries for Node.js v5.x. ([#3129](https://github.com/mapbox/mapbox-gl-native/pull/3129)) -# 2.0.0 - November 24, 2015 +## 2.0.0 - November 24, 2015 - Integrates Node.js bindings into core mapbox-gl-native project. ([#2179](https://github.com/mapbox/mapbox-gl-native/pull/2179)) - Adds Node.js v4.x and io.js v3.x support. ([#2261](https://github.com/mapbox/mapbox-gl-native/pull/2261)) @@ -268,11 +263,11 @@ - Fade transitions are now ignored to prevent half faded labels. ([#942](https://github.com/mapbox/mapbox-gl-native/pull/942)) - Labels can now line wrap on hyphens and other punctuation. ([#2598](https://github.com/mapbox/mapbox-gl-native/pull/2598)) -# 1.1.3 - June 25, 2015 +## 1.1.3 - June 25, 2015 - Removes deprecated mbgl::Environment from NodeLogObserver. -# 1.1.2 - June 22, 2015 +## 1.1.2 - June 22, 2015 - Check libuv version semver-ishly, fixes segfaults in Node.js 0.12.x and io.js. @@ -280,11 +275,11 @@ render without first loading a style. - Bumps mbgl submodule to v0.4.0 -# 1.1.1 - June 16, 2015 +## 1.1.1 - June 16, 2015 - Bumps mbgl submodule to v0.3.5 -# 1.1.0 - June 15, 2015 +## 1.1.0 - June 15, 2015 - Adds Node.js v0.12.x and io.js support. - Adds `map.release()` method for manual cleanup of map resources. @@ -300,23 +295,23 @@ - Fixes uncaught exception from missing sprites. - Fixes Unicode glyph range end. -# 1.0.3 - April 3, 2015 +## 1.0.3 - April 3, 2015 - Fixes crash during garbage collection by assigning FileSource handle to a v8::Persistent in NodeMap constructor. -# 1.0.2 - April 2, 2015 +## 1.0.2 - April 2, 2015 - Initialize shared display connection at module load time to avoid race condition when display connection is initialized on-demand. -# 1.0.1 - March 19, 2015 +## 1.0.1 - March 19, 2015 - Adapts NodeFileSource around mbgl::Environment additions. - Adapts to minor changes in mapbox-gl-test-suite. - Adds tests for gzipped vector tile handling. - Cleans up documentation. -# 1.0.0 - February 25, 2015 +## 1.0.0 - February 25, 2015 -- Initial release. +- Initial release. \ No newline at end of file diff --git a/platform/node/README.md b/platform/node/README.md index 7063bc39600..6ddd16730a4 100644 --- a/platform/node/README.md +++ b/platform/node/README.md @@ -8,10 +8,10 @@ Binaries are available and downloaded during install for the following platforms: - Operating systems: - - Ubuntu 20.04 (amd64/arm64) + - Ubuntu 22.04 (amd64/arm64) - macOS 12 (amd64/arm64) - Windows (amd64) -- Node.js 14, 16, 18 +- Node.js 16, 18, 20 Run: diff --git a/platform/node/RELEASE.md b/platform/node/RELEASE.md new file mode 100644 index 00000000000..3a9087cd295 --- /dev/null +++ b/platform/node/RELEASE.md @@ -0,0 +1,13 @@ +# Instructions for making a Node release + +1. Change the version number in package.json. on the command line, in the package root directory, run the following command, replacing with one of the semantic versioning release types (prerelease, prepatch, preminor, premajor, patch, minor, major): + +npm version --preid pre --no-git-tag-version + +2. Update the changelog, which can be found in `platform/node/CHANGELOG.md`. The heading must match `## ` exactly, or it will not be picked up. For example, for version 5.2.0: + +``` +## 5.2.0 +``` + +3. Commit and push the changes. On push the 'node-release' workflow will automaticlly check if the release has been published on npm. If the release has not yet been published, the workflow will build the node binaries and upload them to a new github release, then publish a new npm release. From d332088b981d43e471fb0012648e19813f63aea9 Mon Sep 17 00:00:00 2001 From: Alex Cristici Date: Thu, 11 Jan 2024 18:11:44 +0200 Subject: [PATCH 26/96] UBO fixes (#2008) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- shaders/drawable.fill_outline.vertex.glsl | 1 + .../renderer/layers/render_line_layer.cpp | 140 ++++++++++++------ src/mbgl/shaders/mtl/line.cpp | 6 +- src/mbgl/shaders/mtl/line_gradient.cpp | 2 +- 4 files changed, 96 insertions(+), 53 deletions(-) diff --git a/shaders/drawable.fill_outline.vertex.glsl b/shaders/drawable.fill_outline.vertex.glsl index b201f79a484..3af22f32b94 100644 --- a/shaders/drawable.fill_outline.vertex.glsl +++ b/shaders/drawable.fill_outline.vertex.glsl @@ -1,6 +1,7 @@ layout (std140) uniform FillOutlineDrawableUBO { highp mat4 u_matrix; highp vec2 u_world; + highp vec2 pad; }; layout (std140) uniform FillOutlineEvaluatedPropsUBO { highp vec4 u_outline_color; diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index 2975eb084b4..b7bb9b95768 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -485,50 +485,92 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, // interpolation UBOs const float zoom = static_cast(state.getZoom()); - const LineInterpolationUBO lineInterpolationUBO{ - /*color_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - 0, - 0}; - const LineGradientInterpolationUBO lineGradientInterpolationUBO{ - /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - 0, - 0, - 0}; - const LinePatternInterpolationUBO linePatternInterpolationUBO{ - /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*pattern_from_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*pattern_to_t =*/std::get<1>(paintPropertyBinders.get()->interpolationFactor(zoom)), - 0}; - const LineSDFInterpolationUBO lineSDFInterpolationUBO{ - /*color_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - /*floorwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), - 0}; + + std::optional lineInterpolationUBO = std::nullopt; + std::optional lineGradientInterpolationUBO = std::nullopt; + std::optional linePatternInterpolationUBO = std::nullopt; + std::optional lineSDFInterpolationUBO = std::nullopt; + + auto getLineInterpolationUBO = [&]() -> const LineInterpolationUBO& { + if (!lineInterpolationUBO) { + lineInterpolationUBO = { + /*color_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + 0, + 0}; + } + + return *lineInterpolationUBO; + }; + + auto getLineGradientInterpolationUBO = [&]() -> const LineGradientInterpolationUBO& { + if (!lineGradientInterpolationUBO) { + lineGradientInterpolationUBO = { + /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + 0, + 0, + 0}; + } + + return *lineGradientInterpolationUBO; + }; + + auto getLinePatternInterpolationUBO = [&]() -> const LinePatternInterpolationUBO& { + if (!linePatternInterpolationUBO) { + linePatternInterpolationUBO = { + /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*pattern_from_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*pattern_to_t =*/std::get<1>(paintPropertyBinders.get()->interpolationFactor(zoom)), + 0}; + } + + return *linePatternInterpolationUBO; + }; + + auto getLineSDFInterpolationUBO = [&]() -> const LineSDFInterpolationUBO& { + if (!lineSDFInterpolationUBO) { + lineSDFInterpolationUBO = { + /*color_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*blur_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*opacity_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*gapwidth_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*offset_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*width_t =*/std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + /*floorwidth_t =*/ + std::get<0>(paintPropertyBinders.get()->interpolationFactor(zoom)), + 0}; + } + + return *lineSDFInterpolationUBO; + }; // tile dependent properties UBOs: + std::optional linePatternTilePropertiesUBO = std::nullopt; const auto& linePatternValue = evaluated.get().constantOr(Faded{"", ""}); const std::optional patternPosA = tile.getPattern(linePatternValue.from.id()); const std::optional patternPosB = tile.getPattern(linePatternValue.to.id()); - const LinePatternTilePropertiesUBO linePatternTilePropertiesUBO{ - /*pattern_from =*/patternPosA ? util::cast(patternPosA->tlbr()) : std::array{0}, - /*pattern_to =*/patternPosB ? util::cast(patternPosB->tlbr()) : std::array{0}}; + + auto getLinePatternTilePropertiesUBO = [&]() -> const LinePatternTilePropertiesUBO& { + if (!linePatternTilePropertiesUBO) { + linePatternTilePropertiesUBO = { + /*pattern_from =*/patternPosA ? util::cast(patternPosA->tlbr()) : std::array{0}, + /*pattern_to =*/patternPosB ? util::cast(patternPosB->tlbr()) : std::array{0}}; + } + + return *linePatternTilePropertiesUBO; + }; auto updateExisting = [&](gfx::Drawable& drawable) { if (drawable.getLayerTweaker() != layerTweaker) { @@ -542,25 +584,25 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, // simple line interpolation UBO if (shaderUniforms.get(idLineInterpolationUBOName)) { - drawableUniforms.createOrUpdate(idLineInterpolationUBOName, &lineInterpolationUBO, context); + drawableUniforms.createOrUpdate(idLineInterpolationUBOName, &getLineInterpolationUBO(), context); } // gradient line interpolation UBO else if (shaderUniforms.get(idLineGradientInterpolationUBOName)) { drawableUniforms.createOrUpdate( - idLineGradientInterpolationUBOName, &lineGradientInterpolationUBO, context); + idLineGradientInterpolationUBOName, &getLineGradientInterpolationUBO(), context); } // pattern line interpolation UBO else if (shaderUniforms.get(idLinePatternInterpolationUBOName)) { // interpolation drawableUniforms.createOrUpdate( - idLinePatternInterpolationUBOName, &linePatternInterpolationUBO, context); + idLinePatternInterpolationUBOName, &getLinePatternInterpolationUBO(), context); // tile properties drawableUniforms.createOrUpdate( - idLinePatternTilePropertiesUBOName, &linePatternTilePropertiesUBO, context); + idLinePatternTilePropertiesUBOName, &getLinePatternTilePropertiesUBO(), context); } // SDF line interpolation UBO else if (shaderUniforms.get(idLineSDFInterpolationUBOName)) { - drawableUniforms.createOrUpdate(idLineSDFInterpolationUBOName, &lineSDFInterpolationUBO, context); + drawableUniforms.createOrUpdate(idLineSDFInterpolationUBOName, &getLineSDFInterpolationUBO(), context); } // TODO: vertex attributes or `propertiesAsUniforms` updated, is that needed? @@ -608,7 +650,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setLayerTweaker(layerTweaker); drawable->setData(std::make_unique(cap)); drawable->mutableUniformBuffers().createOrUpdate( - idLineSDFInterpolationUBOName, &lineSDFInterpolationUBO, context); + idLineSDFInterpolationUBOName, &getLineSDFInterpolationUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; @@ -665,9 +707,9 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setTileID(tileID); drawable->setLayerTweaker(layerTweaker); drawable->mutableUniformBuffers().createOrUpdate( - idLinePatternInterpolationUBOName, &linePatternInterpolationUBO, context); + idLinePatternInterpolationUBOName, &getLinePatternInterpolationUBO(), context); drawable->mutableUniformBuffers().createOrUpdate( - idLinePatternTilePropertiesUBOName, &linePatternTilePropertiesUBO, context); + idLinePatternTilePropertiesUBOName, &getLinePatternTilePropertiesUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; @@ -717,7 +759,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setTileID(tileID); drawable->setLayerTweaker(layerTweaker); drawable->mutableUniformBuffers().createOrUpdate( - idLineGradientInterpolationUBOName, &lineGradientInterpolationUBO, context); + idLineGradientInterpolationUBOName, &getLineGradientInterpolationUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; @@ -754,7 +796,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setTileID(tileID); drawable->setLayerTweaker(layerTweaker); drawable->mutableUniformBuffers().createOrUpdate( - idLineInterpolationUBOName, &lineInterpolationUBO, context); + idLineInterpolationUBOName, &getLineInterpolationUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; diff --git a/src/mbgl/shaders/mtl/line.cpp b/src/mbgl/shaders/mtl/line.cpp index d7412dd7e62..5de6346307d 100644 --- a/src/mbgl/shaders/mtl/line.cpp +++ b/src/mbgl/shaders/mtl/line.cpp @@ -14,7 +14,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{8, true, false, sizeof(LineGradientUBO), "LineDynamicUBO"}, + UniformBlockInfo{8, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, UniformBlockInfo{9, true, true, sizeof(LineUBO), "LineUBO"}, UniformBlockInfo{10, true, true, sizeof(LinePropertiesUBO), "LinePropertiesUBO"}, UniformBlockInfo{11, true, false, sizeof(LineInterpolationUBO), "LineInterpolationUBO"}, @@ -33,7 +33,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{9, true, false, sizeof(LineGradientUBO), "LineDynamicUBO"}, + UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, UniformBlockInfo{10, true, true, sizeof(LinePatternUBO), "LinePatternUBO"}, UniformBlockInfo{11, true, true, sizeof(LinePatternPropertiesUBO), "LinePatternPropertiesUBO"}, UniformBlockInfo{12, true, false, sizeof(LinePatternInterpolationUBO), "LinePatternInterpolationUBO"}, @@ -55,7 +55,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{9, true, false, sizeof(LineGradientUBO), "LineDynamicUBO"}, + UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, UniformBlockInfo{10, true, true, sizeof(LineSDFUBO), "LineSDFUBO"}, UniformBlockInfo{11, true, true, sizeof(LineSDFPropertiesUBO), "LineSDFPropertiesUBO"}, UniformBlockInfo{12, true, false, sizeof(LineSDFInterpolationUBO), "LineSDFInterpolationUBO"}, diff --git a/src/mbgl/shaders/mtl/line_gradient.cpp b/src/mbgl/shaders/mtl/line_gradient.cpp index 9fcb5d65d29..f0c8d9eac0d 100644 --- a/src/mbgl/shaders/mtl/line_gradient.cpp +++ b/src/mbgl/shaders/mtl/line_gradient.cpp @@ -13,7 +13,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{7, true, false, sizeof(LineGradientUBO), "LineDynamicUBO"}, + UniformBlockInfo{7, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, UniformBlockInfo{8, true, true, sizeof(LineGradientUBO), "LineGradientUBO"}, UniformBlockInfo{9, true, true, sizeof(LineGradientPropertiesUBO), "LineGradientPropertiesUBO"}, UniformBlockInfo{10, true, false, sizeof(LineGradientInterpolationUBO), "LineGradientInterpolationUBO"}, From 5c07ab7d67a451ccfa5aba2c612d8e10e59a3d19 Mon Sep 17 00:00:00 2001 From: Andrew Calcutt Date: Sun, 14 Jan 2024 12:32:44 -0500 Subject: [PATCH 27/96] [node][main] Update latest release to node-v5.3.0 (#2018) --- package-lock.json | 4 ++-- package.json | 2 +- platform/node/CHANGELOG.md | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b092bb7e497..ca0752336c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@maplibre/maplibre-gl-native", - "version": "5.2.0", + "version": "5.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@maplibre/maplibre-gl-native", - "version": "5.2.0", + "version": "5.3.0", "hasInstallScript": true, "license": "BSD-2-Clause", "dependencies": { diff --git a/package.json b/package.json index 030c62afc5a..4a2e055ece2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@maplibre/maplibre-gl-native", - "version": "5.2.0", + "version": "5.3.0", "description": "Renders map tiles with MapLibre Native", "keywords": [ "maplibre", diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 48c4d2cb872..51e27995f04 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,6 +1,11 @@ ## main +## 5.3.0 + +* [Note] This is a OpenGL-2 release. It does not include metal support. +* [Breaking] Removes node 14 binary build and adds node 20 binary build. We are now building binaries for node 16,18,20 @acalcutt https://github.com/maplibre/maplibre-native/pull/1941 +* [Breaking] Linux binary is now built on Ubuntu 22.04 instead of Ubuntu 20.04. it could require a OS update to use this update on linux. @acalcutt https://github.com/maplibre/maplibre-native/pull/1941 * Make Node Map object options "request" property optional by @tdcosta100 in https://github.com/maplibre/maplibre-native/pull/904 * Compile Node targets without -std=c++11 option by @tdcosta100 in https://github.com/maplibre/maplibre-native/pull/926 From f6f8d27593f32fa18723ddbb33c2896d75ea33a7 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Mon, 15 Jan 2024 03:10:41 -0500 Subject: [PATCH 28/96] Remove unused private header import (#2016) --- platform/darwin/src/MLNConversion.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/platform/darwin/src/MLNConversion.h b/platform/darwin/src/MLNConversion.h index 2150c6036a2..3acdf8a7b85 100644 --- a/platform/darwin/src/MLNConversion.h +++ b/platform/darwin/src/MLNConversion.h @@ -1,5 +1,3 @@ -#import "MLNShape_Private.h" - #include NS_ASSUME_NONNULL_BEGIN From dec1df446268aa87a04693c5ee1229f4ae301c34 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Mon, 15 Jan 2024 03:11:11 -0500 Subject: [PATCH 29/96] Ignore MODULE.bazel.lock (#2015) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bf54006f5d3..2b50826bcea 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ node_modules /lib /bazel-* +/MODULE.bazel.lock CMakeLists.txt.user CMakeFiles __generated__ From 29c16a4a81ed6f363ca7c1f3460abaf4513e87f4 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Mon, 15 Jan 2024 03:12:42 -0500 Subject: [PATCH 30/96] Enable incompatible_disallow_empty_glob (#2014) --- .bazelrc | 3 +++ platform/BUILD.bazel | 7 ------- platform/ios/benchmark/BUILD.bazel | 9 ++++----- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.bazelrc b/.bazelrc index 20b03e38887..bca2641901b 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,4 +1,7 @@ # TODO: remove with bazel 7.x common --enable_bzlmod +# TODO: remove once bazel flips this flag +common --incompatible_disallow_empty_glob + coverage --experimental_ui_max_stdouterr_bytes=10485760 diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index 284e461d7b3..f5896879a78 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -100,10 +100,6 @@ objc_library( # Common headers between objc and objcpp libraries. objc_library( name = "objc-headers", - srcs = glob([ - "darwin/src/*.h", - "ios/src/*.h", - ]), includes = [ "darwin/src", "ios/src", @@ -203,9 +199,6 @@ objc_library( "//platform/darwin:app/LimeGreenStyleLayer.m", ], }), - data = glob([ - "app/Assets.xcassets/**", - ]), defines = ["GLES_SILENCE_DEPRECATION"], includes = [ "darwin/app", diff --git a/platform/ios/benchmark/BUILD.bazel b/platform/ios/benchmark/BUILD.bazel index af5d09d07bb..680ef909bef 100644 --- a/platform/ios/benchmark/BUILD.bazel +++ b/platform/ios/benchmark/BUILD.bazel @@ -24,11 +24,10 @@ filegroup( filegroup( name = "bundle_resources", srcs = glob([ - "benchmark/*.lproj/**", - "benchmark/Assets.xcassets/**", - "benchmark/assets/**/*.pbf", - "benchmark/assets/**/*.json", - "benchmark/assets/**/*.png", + "*.lproj/**", + "Assets.xcassets/**", + "assets/**/*.json", + "assets/**/*.png", ]), visibility = ["//platform/ios:__pkg__"], ) From d6c2b148ddd4d2004db14608905b6eea72facc81 Mon Sep 17 00:00:00 2001 From: mwilsnd <53413200+mwilsnd@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:31:44 -0500 Subject: [PATCH 31/96] Metal layer depth range support (#2013) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alex Cristici --- include/mbgl/gfx/drawable.hpp | 4 ++++ include/mbgl/renderer/layer_group.hpp | 16 +++++++++++++++- include/mbgl/renderer/layer_tweaker.hpp | 1 + src/mbgl/gfx/depth_mode.hpp | 6 ++++++ src/mbgl/gl/context.cpp | 6 ++++++ src/mbgl/gl/context.hpp | 2 ++ src/mbgl/gl/value.cpp | 2 ++ src/mbgl/gl/value.hpp | 2 ++ src/mbgl/mtl/context.cpp | 7 ++----- src/mbgl/renderer/layer_group.cpp | 1 + src/mbgl/renderer/layer_tweaker.cpp | 17 ++++++++++++++--- .../layers/background_layer_tweaker.cpp | 3 ++- .../renderer/layers/circle_layer_tweaker.cpp | 3 ++- .../renderer/layers/collision_layer_tweaker.cpp | 3 ++- .../layers/fill_extrusion_layer_tweaker.cpp | 3 ++- src/mbgl/renderer/layers/fill_layer_tweaker.cpp | 4 +++- .../renderer/layers/heatmap_layer_tweaker.cpp | 2 +- .../renderer/layers/hillshade_layer_tweaker.cpp | 2 +- src/mbgl/renderer/layers/line_layer_tweaker.cpp | 3 ++- .../renderer/layers/raster_layer_tweaker.cpp | 9 ++++++++- src/mbgl/renderer/layers/render_fill_layer.cpp | 2 +- .../renderer/layers/symbol_layer_tweaker.cpp | 3 ++- src/mbgl/renderer/paint_parameters.cpp | 11 ++++++++++- src/mbgl/renderer/paint_parameters.hpp | 6 +++++- src/mbgl/renderer/render_target.cpp | 6 ++++-- src/mbgl/renderer/renderer_impl.cpp | 10 ++++++---- .../renderer/sources/render_tile_source.cpp | 2 +- src/mbgl/style/layers/custom_drawable_layer.cpp | 4 ++-- 28 files changed, 109 insertions(+), 31 deletions(-) diff --git a/include/mbgl/gfx/drawable.hpp b/include/mbgl/gfx/drawable.hpp index c84f4af3c7b..be68a343e0a 100644 --- a/include/mbgl/gfx/drawable.hpp +++ b/include/mbgl/gfx/drawable.hpp @@ -152,6 +152,9 @@ class Drawable { /// Set sub-layer index virtual void setSubLayerIndex(int32_t value) { subLayerIndex = value; } + void setLayerIndex(int32_t value) { layerIndex = value; } + int32_t getLayerIndex() const { return layerIndex; } + /// Depth writability for 2D drawables DepthMaskType getDepthType() const { return depthType; } @@ -252,6 +255,7 @@ class Drawable { DrawPriority drawPriority = 0; int32_t lineWidth = 1; int32_t subLayerIndex = 0; + int32_t layerIndex = 0; DepthMaskType depthType; // = DepthMaskType::ReadOnly; UniqueDrawableData drawableData{}; gfx::VertexAttributeArrayPtr vertexAttributes; diff --git a/include/mbgl/renderer/layer_group.hpp b/include/mbgl/renderer/layer_group.hpp index 4acdfbef9ba..dd9c0b00b7c 100644 --- a/include/mbgl/renderer/layer_group.hpp +++ b/include/mbgl/renderer/layer_group.hpp @@ -72,7 +72,7 @@ class LayerGroupBase : public util::SimpleIdentifiable { int32_t getLayerIndex() const { return layerIndex; } /// Update the layer index to a new value - void updateLayerIndex(int32_t value) { layerIndex = value; } + virtual void updateLayerIndex(int32_t value) { layerIndex = value; } /// Get the number of drawables contained virtual std::size_t getDrawableCount() const = 0; @@ -163,6 +163,13 @@ class TileLayerGroup : public LayerGroupBase { void setStencilTiles(RenderTiles); + void updateLayerIndex(int32_t value) override { + layerIndex = value; + for (auto* drawable : sortedDrawables) { + drawable->setLayerIndex(value); + } + } + protected: // When stencil clipping is enabled for the layer, this is the set // of tile IDs that need to be rendered to the stencil buffer. @@ -232,6 +239,13 @@ class LayerGroup : public LayerGroupBase { std::size_t clearDrawables() override; + void updateLayerIndex(int32_t value) override { + layerIndex = value; + for (auto& drawable : drawables) { + drawable->setLayerIndex(value); + } + } + protected: using DrawableCollection = std::set; DrawableCollection drawables; diff --git a/include/mbgl/renderer/layer_tweaker.hpp b/include/mbgl/renderer/layer_tweaker.hpp index 7107de38861..347580466e2 100644 --- a/include/mbgl/renderer/layer_tweaker.hpp +++ b/include/mbgl/renderer/layer_tweaker.hpp @@ -57,6 +57,7 @@ class LayerTweaker { style::TranslateAnchorType, bool nearClipped, bool inViewportPixelUnits, + const gfx::Drawable& drawable, bool aligned = false); protected: diff --git a/src/mbgl/gfx/depth_mode.hpp b/src/mbgl/gfx/depth_mode.hpp index 4548240c999..d1ece4e06cd 100644 --- a/src/mbgl/gfx/depth_mode.hpp +++ b/src/mbgl/gfx/depth_mode.hpp @@ -10,9 +10,15 @@ class DepthMode { public: DepthFunctionType func; DepthMaskType mask; +#if MLN_RENDER_BACKEND_OPENGL Range range; +#endif +#if MLN_RENDER_BACKEND_OPENGL static DepthMode disabled() { return DepthMode{DepthFunctionType::Always, DepthMaskType::ReadOnly, {0.0, 1.0}}; } +#else + static DepthMode disabled() { return DepthMode{DepthFunctionType::Always, DepthMaskType::ReadOnly}; } +#endif }; } // namespace gfx diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 1fc9ccf58aa..3f7b36c526b 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -490,7 +490,9 @@ void Context::setDirtyState() { stencilMask.setDirty(); stencilTest.setDirty(); stencilOp.setDirty(); +#if MLN_RENDER_BACKEND_OPENGL depthRange.setDirty(); +#endif depthMask.setDirty(); depthTest.setDirty(); depthFunc.setDirty(); @@ -615,12 +617,16 @@ void Context::setDepthMode(const gfx::DepthMode& depth) { // https://github.com/mapbox/mapbox-gl-native/issues/9164 depthFunc = depth.func; depthMask = depth.mask; +#if MLN_RENDER_BACKEND_OPENGL depthRange = depth.range; +#endif } else { depthTest = true; depthFunc = depth.func; depthMask = depth.mask; +#if MLN_RENDER_BACKEND_OPENGL depthRange = depth.range; +#endif } } diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 1c90020023f..a0d684da84d 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -169,7 +169,9 @@ class Context final : public gfx::Context { State stencilMask; State stencilTest; State stencilOp; +#if MLN_RENDER_BACKEND_OPENGL State depthRange; +#endif State depthMask; State depthTest; State depthFunc; diff --git a/src/mbgl/gl/value.cpp b/src/mbgl/gl/value.cpp index 69d6053d171..d77e7b5016d 100644 --- a/src/mbgl/gl/value.cpp +++ b/src/mbgl/gl/value.cpp @@ -133,6 +133,7 @@ StencilOp::Type StencilOp::Get() { Enum::from(dppass)}; } +#if MLN_RENDER_BACKEND_OPENGL const constexpr DepthRange::Type DepthRange::Default; void DepthRange::Set(const Type& value) { @@ -144,6 +145,7 @@ DepthRange::Type DepthRange::Get() { MBGL_CHECK_ERROR(glGetFloatv(GL_DEPTH_RANGE, floats)); return {floats[0], floats[1]}; } +#endif const constexpr DepthTest::Type DepthTest::Default; diff --git a/src/mbgl/gl/value.hpp b/src/mbgl/gl/value.hpp index feda4410c6e..cea3136a008 100644 --- a/src/mbgl/gl/value.hpp +++ b/src/mbgl/gl/value.hpp @@ -98,12 +98,14 @@ constexpr bool operator!=(const StencilOp::Type& a, const StencilOp::Type& b) { return a.sfail != b.sfail || a.dpfail != b.dpfail || a.dppass != b.dppass; } +#if MLN_RENDER_BACKEND_OPENGL struct DepthRange { using Type = Range; static const constexpr Type Default = {0, 1}; static void Set(const Type&); static Type Get(); }; +#endif struct DepthTest { using Type = bool; diff --git a/src/mbgl/mtl/context.cpp b/src/mbgl/mtl/context.cpp index ee4ad699503..7a58732a9b7 100644 --- a/src/mbgl/mtl/context.cpp +++ b/src/mbgl/mtl/context.cpp @@ -269,11 +269,8 @@ const auto clipMaskStencilMode = gfx::StencilMode{ /*.depthFail=*/gfx::StencilOpType::Keep, /*.pass=*/gfx::StencilOpType::Replace, }; -const auto clipMaskDepthMode = gfx::DepthMode{ - /*.func=*/gfx::DepthFunctionType::Always, - /*.mask=*/gfx::DepthMaskType::ReadOnly, - /*.range=*/{0, 1}, -}; +const auto clipMaskDepthMode = gfx::DepthMode{/*.func=*/gfx::DepthFunctionType::Always, + /*.mask=*/gfx::DepthMaskType::ReadOnly}; } // namespace bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass, diff --git a/src/mbgl/renderer/layer_group.cpp b/src/mbgl/renderer/layer_group.cpp index caf0cfc2f3c..49e869fc202 100644 --- a/src/mbgl/renderer/layer_group.cpp +++ b/src/mbgl/renderer/layer_group.cpp @@ -10,6 +10,7 @@ namespace mbgl { void LayerGroupBase::addDrawable(gfx::UniqueDrawable& drawable) { + drawable->setLayerIndex(layerIndex); // init their tweakers for (const auto& tweaker : drawable->getTweakers()) { tweaker->init(*drawable); diff --git a/src/mbgl/renderer/layer_tweaker.cpp b/src/mbgl/renderer/layer_tweaker.cpp index 013d6ec886d..426fd57b385 100644 --- a/src/mbgl/renderer/layer_tweaker.cpp +++ b/src/mbgl/renderer/layer_tweaker.cpp @@ -32,15 +32,26 @@ mat4 LayerTweaker::getTileMatrix(const UnwrappedTileID& tileID, style::TranslateAnchorType anchor, bool nearClipped, bool inViewportPixelUnits, + [[maybe_unused]] const gfx::Drawable& drawable, bool aligned) { // from RenderTile::prepare mat4 tileMatrix; parameters.state.matrixFor(/*out*/ tileMatrix, tileID); // nearClippedMatrix has near plane moved further, to enhance depth buffer precision - const auto& projMatrix = aligned ? parameters.transformParams.alignedProjMatrix - : (nearClipped ? parameters.transformParams.nearClippedProjMatrix - : parameters.transformParams.projMatrix); + auto projMatrix = aligned ? parameters.transformParams.alignedProjMatrix + : (nearClipped ? parameters.transformParams.nearClippedProjMatrix + : parameters.transformParams.projMatrix); + +#if !MLN_RENDER_BACKEND_OPENGL + // Offset the projection matrix NDC depth range for the drawable's layer and sublayer. + if (!drawable.getIs3D()) { + projMatrix[14] -= ((1 + drawable.getLayerIndex()) * PaintParameters::numSublayers - + drawable.getSubLayerIndex()) * + PaintParameters::depthEpsilon; + } +#endif + matrix::multiply(tileMatrix, projMatrix, tileMatrix); return RenderTile::translateVtxMatrix( diff --git a/src/mbgl/renderer/layers/background_layer_tweaker.cpp b/src/mbgl/renderer/layers/background_layer_tweaker.cpp index 7949be8ea5c..3e9c93d322b 100644 --- a/src/mbgl/renderer/layers/background_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/background_layer_tweaker.cpp @@ -64,7 +64,8 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara (shader == context.getGenericShader(parameters.shaders, std::string(BackgroundPatternShaderName)))); const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); - const auto matrix = parameters.matrixForTile(tileID); + const auto matrix = getTileMatrix( + tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, false, false, drawable); const BackgroundDrawableUBO drawableUBO = {/* .matrix = */ util::cast(matrix)}; diff --git a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp index 2b450e31d7d..1755175412a 100644 --- a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp @@ -87,7 +87,8 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete const auto anchor = evaluated.get(); constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix constexpr bool nearClipped = false; - const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix( + tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits, drawable); const auto pixelsToTileUnits = tileID.pixelsToTileUnits(1.0f, static_cast(zoom)); const auto extrudeScale = pitchWithMap ? std::array{pixelsToTileUnits, pixelsToTileUnits} diff --git a/src/mbgl/renderer/layers/collision_layer_tweaker.cpp b/src/mbgl/renderer/layers/collision_layer_tweaker.cpp index a5b9f9a5143..5e5f9700618 100644 --- a/src/mbgl/renderer/layers/collision_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/collision_layer_tweaker.cpp @@ -49,7 +49,8 @@ void CollisionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam const auto anchor = data.translateAnchor; constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; - const auto matrix = getTileMatrix(tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix( + tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits, drawable); // extrude scale const auto pixelRatio = tileID.pixelsToTileUnits(1.0f, static_cast(parameters.state.getZoom())); diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp index 30455c5950b..71ceba343fb 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp @@ -90,7 +90,8 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintP const auto anchor = evaluated.get(); constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix constexpr bool nearClipped = true; - const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix( + tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits, drawable); const auto tileRatio = 1 / tileID.pixelsToTileUnits(1, state.getIntegerZoom()); const auto zoomScale = state.zoomScale(tileID.canonical.z); diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp index f3f65a466c9..bbd34ca1000 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp @@ -153,7 +153,9 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix constexpr bool nearClipped = false; - const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); + + const auto matrix = getTileMatrix( + tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits, drawable); // from FillPatternProgram::layoutUniformValues const auto tileRatio = 1.0f / tileID.pixelsToTileUnits(1.0f, intZoom); diff --git a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp index 9042a589067..a7e55df952d 100644 --- a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp @@ -61,7 +61,7 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; const auto matrix = getTileMatrix( - tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, nearClipped, inViewportPixelUnits); + tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, nearClipped, inViewportPixelUnits, drawable); const HeatmapDrawableUBO drawableUBO = { /* .matrix = */ util::cast(matrix), /* .extrude_scale = */ tileID.pixelsToTileUnits(1.0f, static_cast(zoom)), diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp index f14787e6429..702b94d9a3c 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp @@ -62,7 +62,7 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam drawable.mutableUniformBuffers().addOrReplace(idHillshadeEvaluatedPropsUBOName, evaluatedPropsUniformBuffer); const auto matrix = getTileMatrix( - tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, false, false, true); + tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, false, false, drawable, true); HillshadeDrawableUBO drawableUBO = {/* .matrix = */ util::cast(matrix), /* .latrange = */ getLatRange(tileID), /* .light = */ getLight(parameters, evaluated)}; diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.cpp b/src/mbgl/renderer/layers/line_layer_tweaker.cpp index bcb96430044..57929104cf1 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.cpp @@ -130,7 +130,8 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix auto& uniforms = drawable.mutableUniformBuffers(); - const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix( + tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits, drawable); uniforms.addOrReplace(idLineDynamicUBOName, dynamicBuffer); diff --git a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp index 80d0caed14f..eb91f380ec3 100644 --- a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp @@ -65,7 +65,14 @@ void RasterLayerTweaker::execute([[maybe_unused]] LayerGroupBase& layerGroup, } else { // this is a tile drawable const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); - matrix = parameters.matrixForTile(tileID, !parameters.state.isChanging()); + matrix = getTileMatrix(tileID, + parameters, + {0.f, 0.f}, + TranslateAnchorType::Viewport, + false, + false, + drawable, + !parameters.state.isChanging()); } const RasterDrawableUBO drawableUBO{ diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index d3bd88c6799..dd1436651ea 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -368,7 +368,7 @@ class OutlineDrawableTweaker : public gfx::DrawableTweaker { static const StringIdentity idLineUBOName = stringIndexer().get("LineBasicUBO"); { const auto matrix = LayerTweaker::getTileMatrix( - tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, false); + tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); const shaders::LineBasicUBO lineUBO{ /*matrix = */ util::cast(matrix), diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp index 715bc738a7e..aa737811093 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp @@ -128,7 +128,8 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete : evaluated.get(); constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; - const auto matrix = getTileMatrix(tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix( + tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits, drawable); // from symbol_program, makeValues const auto currentZoom = static_cast(parameters.state.getZoom()); diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 0e801a0421a..77ba42d4821 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -82,16 +82,25 @@ mat4 PaintParameters::matrixForTile(const UnwrappedTileID& tileID, bool aligned) return matrix; } -gfx::DepthMode PaintParameters::depthModeForSublayer(uint8_t n, gfx::DepthMaskType mask) const { +gfx::DepthMode PaintParameters::depthModeForSublayer([[maybe_unused]] uint8_t n, gfx::DepthMaskType mask) const { if (currentLayer < opaquePassCutoff) { return gfx::DepthMode::disabled(); } + +#if MLN_RENDER_BACKEND_OPENGL float depth = depthRangeSize + ((1 + currentLayer) * numSublayers + n) * depthEpsilon; return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, mask, {depth, depth}}; +#else + return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, mask}; +#endif } gfx::DepthMode PaintParameters::depthModeFor3D() const { +#if MLN_RENDER_BACKEND_OPENGL return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, gfx::DepthMaskType::ReadWrite, {0.0, depthRangeSize}}; +#else + return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, gfx::DepthMaskType::ReadWrite}; +#endif } namespace { diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index d2bab39879b..6bb5a13fcde 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -121,14 +121,18 @@ class PaintParameters { int32_t nextStencilID = 1; public: - const int numSublayers = 3; uint32_t currentLayer; float depthRangeSize; uint32_t opaquePassCutoff = 0; float symbolFadeChange; const uint64_t frameCount; + static constexpr int numSublayers = 3; +#if MLN_RENDER_BACKEND_OPENGL static constexpr float depthEpsilon = 1.0f / (1 << 16); +#else + static constexpr float depthEpsilon = 1.0f / (1 << 12); +#endif static constexpr int maxStencilValue = 255; }; diff --git a/src/mbgl/renderer/render_target.cpp b/src/mbgl/renderer/render_target.cpp index 5ceb64a21d6..61bad83696d 100644 --- a/src/mbgl/renderer/render_target.cpp +++ b/src/mbgl/renderer/render_target.cpp @@ -74,7 +74,8 @@ void RenderTarget::render(RenderOrchestrator& orchestrator, const RenderTree& re // draw layer groups, opaque pass parameters.pass = RenderPass::Opaque; parameters.currentLayer = 0; - parameters.depthRangeSize = 1 - (numLayerGroups() + 2) * parameters.numSublayers * PaintParameters::depthEpsilon; + parameters.depthRangeSize = 1 - + (numLayerGroups() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.render(orchestrator, parameters); @@ -84,7 +85,8 @@ void RenderTarget::render(RenderOrchestrator& orchestrator, const RenderTree& re // draw layer groups, translucent pass parameters.pass = RenderPass::Translucent; parameters.currentLayer = static_cast(numLayerGroups()) - 1; - parameters.depthRangeSize = 1 - (numLayerGroups() + 2) * parameters.numSublayers * PaintParameters::depthEpsilon; + parameters.depthRangeSize = 1 - + (numLayerGroups() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.render(orchestrator, parameters); diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 22d9704761d..0546d96afad 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -330,7 +330,8 @@ void Renderer::Impl::render(const RenderTree& renderTree, const auto maxLayerIndex = orchestrator.maxLayerIndex(); parameters.pass = RenderPass::Opaque; parameters.currentLayer = 0; - parameters.depthRangeSize = 1 - (maxLayerIndex + 3) * parameters.numSublayers * PaintParameters::depthEpsilon; + parameters.depthRangeSize = 1 - + (maxLayerIndex + 3) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; // draw layer groups, opaque pass orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { @@ -343,7 +344,8 @@ void Renderer::Impl::render(const RenderTree& renderTree, const auto debugGroup(parameters.renderPass->createDebugGroup("drawables-translucent")); const auto maxLayerIndex = orchestrator.maxLayerIndex(); parameters.pass = RenderPass::Translucent; - parameters.depthRangeSize = 1 - (maxLayerIndex + 3) * parameters.numSublayers * PaintParameters::depthEpsilon; + parameters.depthRangeSize = 1 - + (maxLayerIndex + 3) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; // draw layer groups, translucent pass orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { @@ -358,7 +360,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const auto renderLayerOpaquePass = [&] { const auto debugGroup(parameters.renderPass->createDebugGroup("opaque")); parameters.pass = RenderPass::Opaque; - parameters.depthRangeSize = 1 - (layerRenderItems.size() + 2) * parameters.numSublayers * + parameters.depthRangeSize = 1 - (layerRenderItems.size() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; uint32_t i = 0; @@ -376,7 +378,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, const auto renderLayerTranslucentPass = [&] { const auto debugGroup(parameters.renderPass->createDebugGroup("translucent")); parameters.pass = RenderPass::Translucent; - parameters.depthRangeSize = 1 - (layerRenderItems.size() + 2) * parameters.numSublayers * + parameters.depthRangeSize = 1 - (layerRenderItems.size() + 2) * PaintParameters::numSublayers * PaintParameters::depthEpsilon; int32_t i = static_cast(layerRenderItems.size()) - 1; diff --git a/src/mbgl/renderer/sources/render_tile_source.cpp b/src/mbgl/renderer/sources/render_tile_source.cpp index b05eb756754..44203838483 100644 --- a/src/mbgl/renderer/sources/render_tile_source.cpp +++ b/src/mbgl/renderer/sources/render_tile_source.cpp @@ -208,7 +208,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr parameters.state.matrixFor(/*out*/ tileMatrix, tileID); const auto matrix = LayerTweaker::getTileMatrix( - tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, false); + tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); static const StringIdentity idLineDynamicUBOName = stringIndexer().get("LineDynamicUBO"); const shaders::LineDynamicUBO dynamicUBO = { diff --git a/src/mbgl/style/layers/custom_drawable_layer.cpp b/src/mbgl/style/layers/custom_drawable_layer.cpp index 37422228574..0b48648ba82 100644 --- a/src/mbgl/style/layers/custom_drawable_layer.cpp +++ b/src/mbgl/style/layers/custom_drawable_layer.cpp @@ -95,7 +95,7 @@ class LineDrawableTweaker : public gfx::DrawableTweaker { parameters.state.matrixFor(/*out*/ tileMatrix, tileID); const auto matrix = LayerTweaker::getTileMatrix( - tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, false); + tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); static const StringIdentity idLineDynamicUBOName = stringIndexer().get("LineDynamicUBO"); const shaders::LineDynamicUBO dynamicUBO = { @@ -149,7 +149,7 @@ class FillDrawableTweaker : public gfx::DrawableTweaker { parameters.state.matrixFor(/*out*/ tileMatrix, tileID); const auto matrix = LayerTweaker::getTileMatrix( - tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, false); + tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); static const StringIdentity idFillDrawableUBOName = stringIndexer().get("FillDrawableUBO"); const shaders::FillDrawableUBO fillUBO{/*matrix = */ util::cast(matrix)}; From 7479d8c899995ca9ccc643b0d3b621f1445a3ed4 Mon Sep 17 00:00:00 2001 From: Alex Cristici Date: Tue, 16 Jan 2024 19:13:57 +0200 Subject: [PATCH 32/96] Metal enable an ignored render test: map-text-depthtest (#2025) --- metrics/ignores/platform-ios-metal.json | 1 - 1 file changed, 1 deletion(-) diff --git a/metrics/ignores/platform-ios-metal.json b/metrics/ignores/platform-ios-metal.json index f4fb4c2c9c8..76bf51d0784 100644 --- a/metrics/ignores/platform-ios-metal.json +++ b/metrics/ignores/platform-ios-metal.json @@ -1,6 +1,5 @@ { "render-tests/fill-outline-color/zoom-and-property-function": "It will be fixed after triangulated lines will support data driven properties", "render-tests/fill-outline-color/property-function": "It will be fixed after triangulated lines will support data driven properties", - "render-tests/text-pitch-alignment/map-text-depthtest": "Layer Z Fighting: https://github.com/maplibre/maplibre-native/issues/1847", "render-tests/fill-extrusion-color/function": "Layer Z Fighting: https://github.com/maplibre/maplibre-native/issues/1847" } From f1cdc20daa0b04402f8b3fc4626b610387038459 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 17 Jan 2024 01:28:40 +0700 Subject: [PATCH 33/96] Last changes before Metal release (#2024) --- .github/workflows/ios-ci.yml | 2 +- platform/ios/BUILD.bazel | 13 +++++++++++++ platform/ios/CHANGELOG.md | 5 ++--- platform/ios/app/MBXViewController.m | 25 ------------------------- platform/ios/src/MLNMapView.h | 12 ------------ platform/ios/src/MLNMapView.mm | 18 ------------------ 6 files changed, 16 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index c79bb85b735..cb595dc2bd1 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -208,7 +208,7 @@ jobs: - name: Build XCFramework run: | - bazel build --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic + bazel build --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic --embed_label=maplibre_ios_"$(cat VERSION)" echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" - name: Get version (release) diff --git a/platform/ios/BUILD.bazel b/platform/ios/BUILD.bazel index f6869eb4251..146f3ea3289 100644 --- a/platform/ios/BUILD.bazel +++ b/platform/ios/BUILD.bazel @@ -1,6 +1,7 @@ load("@build_bazel_rules_apple//apple:apple.bzl", "apple_static_xcframework", "apple_xcframework") load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_framework") load("@build_bazel_rules_apple//apple:resources.bzl", "apple_resource_bundle") +load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version") load( "@rules_xcodeproj//xcodeproj:defs.bzl", "top_level_target", @@ -120,6 +121,17 @@ apple_static_xcframework( deps = ["//platform:ios-sdk"], ) +apple_bundle_version( + name = "maplibre_ios_version", + build_label_pattern = "maplibre_ios_{version}", + build_version = "{version}", + capture_groups = { + "version": "[0-9]+.[0-9]+.[0-9]+", + }, + fallback_build_label = "maplibre_ios_1.2.3456789", + short_version_string = "{version}", +) + apple_xcframework( name = "MapLibre.dynamic", bundle_id = "com.maplibre.mapbox", @@ -139,6 +151,7 @@ apple_xcframework( minimum_os_versions = {"ios": "12.0"}, public_hdrs = public_hdrs, umbrella_header = "umbrella_header", + version = ":maplibre_ios_version", visibility = ["//visibility:public"], deps = ["//platform:ios-sdk-dynamic"], ) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 04775a902dc..824732d36c8 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -8,14 +8,13 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C * This is the first release that uses **Metal** for rendering. This is a graphics API from Apple that replaces OpenGL ES on Apple platforms. * Note that the [snapshotter](https://github.com/maplibre/maplibre-native/issues/1862) has not been implemented yet for the Metal renderer. Hold off updating if your application requires this functionality. - * While we had a long period of pre-releases and testing leading up to this release, and no crashes have been reported, it is possible that you come across inconsistencies or problems in production apps. Please report them on [GitHub](https://github.com/maplibre/maplibre-native/issues/1609). + * While we had a long period of pre-releases and testing leading up to this release, and no crashes have been reported, it is possible that you come across inconsistencies or problems in production apps. Please report them on GitHub. * 💥 Breaking: Changed the prefix of files, classes, methods, variables and everything from `MGL` to `MLN`. ([#919](https://github.com/maplibre/maplibre-native/pull/919)). > To migrate: > Change all your `MGL` prefixes to `MLN`. If you are using `NSKeyedArchiver` or similar mechanishm to save the state, the app may crash after this change when trying to unarchive the state using old names of the classes. You need to clean the saved state of the app and save it using new classes. * 💥 Breaking: The OpenGL ES renderer now uses OpenGL ES 3.0. This means that only iOS Devices with an Apple A7 GPU or later are supported. https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/BestPracticesforAppleA7GPUsandLater/BestPracticesforAppleA7GPUsandLater.html -* Add `MLNMapView.setLatLngBounds` and `MLNMapView.clearLatLnBounds` -* Add `flyToCamera` with `edgePadding` for `MLNMapView` +* The Swift package needs to be imported with `import MapLibre` instead of `import Mapbox`. ## 5.13.0 - January 05, 2023 diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 1eb9f60fe04..fa94621cc7a 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -38,11 +38,6 @@ .ne = { .latitude = 40.989329, .longitude = -102.062592}, }; -static const MLNCoordinateBounds areaAroundBelgium = { - .sw = { .latitude = 52.2782, .longitude = 8.289179999999988}, - .ne = { .latitude = 48.5584, .longitude = 1.0162300000000073}, -}; - static NSString * const MBXViewControllerAnnotationViewReuseIdentifer = @"MBXViewControllerAnnotationViewReuseIdentifer"; typedef NS_ENUM(NSInteger, MBXSettingsSections) { @@ -118,7 +113,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { }; typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - MBXSettingsMiscellaneousLatLngBoundsConstraints, MBXSettingsMiscellaneousWorldTour, MBXSettingsMiscellaneousRandomTour, MBXSettingsMiscellaneousScrollView, @@ -248,7 +242,6 @@ @implementation MBXViewController BOOL _isTouringWorld; BOOL _contentInsetsEnabled; UIEdgeInsets _originalContentInsets; - BOOL _hasLatLngBoundConstraints; } // MARK: - Setup & Teardown @@ -455,7 +448,6 @@ - (void)dismissSettings:(__unused id)sender break; case MBXSettingsMiscellaneous: [settingsTitles addObjectsFromArray:@[ - _hasLatLngBoundConstraints ? @"Remove LatLng bound constraints" : @"Set LatLng bound box constraint", @"Start World Tour", @"Random Tour", @"Embedded Map View", @@ -689,9 +681,6 @@ - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath case MBXSettingsMiscellaneous: switch (indexPath.row) { - case MBXSettingsMiscellaneousLatLngBoundsConstraints: - [self setLatLngBoundsConstraints]; - break; case MBXSettingsMiscellaneousLocalizeLabels: [self toggleStyleLabelsLanguage]; break; @@ -1506,20 +1495,6 @@ - (void)updateAnimatedImageSource:(NSTimer *)timer { } } --(void)setLatLngBoundsConstraints -{ - if(_hasLatLngBoundConstraints) { - [self.mapView clearLatLnBounds]; - [self.mapView resetPosition]; - } else { - MLNMapCamera *newCamera = [self.mapView cameraThatFitsCoordinateBounds: areaAroundBelgium]; - [self.mapView setCamera: newCamera]; - [self.mapView setLatLngBounds: areaAroundBelgium]; - } - - _hasLatLngBoundConstraints = !_hasLatLngBoundConstraints; -} - -(void)toggleStyleLabelsLanguage { _localizingLabels = !_localizingLabels; diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index ddeb9bb6e46..d0daffcca28 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -1383,18 +1383,6 @@ MLN_EXPORT */ - (CGPoint)anchorPointForGesture:(UIGestureRecognizer *)gesture; - -/** - * Sets a LatLngBounds that constraints map transformations to this bounds. - * @param latLngBounds the bounds to constrain the map with - */ -- (void)setLatLngBounds:(MLNCoordinateBounds)latLngBounds; - -/** - * Clears the bounds that were set via `setLatLngBounds` - */ -- (void)clearLatLnBounds; - /** The distance from the edges of the map view’s frame to the edges of the map view’s logical viewport. diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index ef0e4ff6fd7..196284ad7d6 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -2695,7 +2695,6 @@ - (MLNMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGP currentCameraOptions.anchor = anchor; MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(self.mbglMap.latLngBoundsForCamera(currentCameraOptions)); - return [self cameraThatFitsCoordinateBounds:bounds]; } @@ -3834,23 +3833,6 @@ - (void)setMinimumZoomLevel:(double)minimumZoomLevel self.mbglMap.setBounds(mbgl::BoundOptions().withMinZoom(minimumZoomLevel)); } - - -- (void)clearLatLnBounds -{ - mbgl::BoundOptions newBounds = mbgl::BoundOptions().withLatLngBounds(mbgl::LatLngBounds()); - self.mbglMap.setBounds(newBounds); -} - -- (void)setLatLngBounds:(MLNCoordinateBounds)latLngBounds -{ - mbgl::LatLng sw = {latLngBounds.sw.latitude, latLngBounds.sw.longitude}; - mbgl::LatLng ne = {latLngBounds.ne.latitude, latLngBounds.ne.longitude}; - mbgl::BoundOptions newBounds = mbgl::BoundOptions().withLatLngBounds(mbgl::LatLngBounds::hull(sw, ne)); - - self.mbglMap.setBounds(newBounds); -} - - (double)minimumZoomLevel { return *self.mbglMap.getBounds().minZoom; From d1844628deaedb0d41225259e877c731e1906dba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 08:02:23 +0100 Subject: [PATCH 34/96] Bump tj-actions/changed-files from 41 to 42 (#2037) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android-ci.yml | 2 +- .github/workflows/ios-ci.yml | 2 +- .github/workflows/linux-ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index f1302914389..7485b65ae2f 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -27,7 +27,7 @@ jobs: - name: Get all Android files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files - uses: tj-actions/changed-files@v41 + uses: tj-actions/changed-files@v42 with: files_yaml_from_source_file: .github/changed-files.yml diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index cb595dc2bd1..7977667674e 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -36,7 +36,7 @@ jobs: - name: Get all iOS files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files-yaml - uses: tj-actions/changed-files@v41 + uses: tj-actions/changed-files@v42 with: files_yaml_from_source_file: .github/changed-files.yml diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index f605d8650c0..d56b0bf4f8e 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -32,7 +32,7 @@ jobs: - name: Get all Linux files that have changed if: github.event_name != 'workflow_dispatch' id: changed-files - uses: tj-actions/changed-files@v41 + uses: tj-actions/changed-files@v42 with: files_yaml_from_source_file: .github/changed-files.yml From eb18a3cefc36d47c7fd1a0f6509441f1ef77116e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 08:55:26 +0100 Subject: [PATCH 35/96] Bump actions/cache from 3 to 4 (#2038) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android-ci.yml | 4 ++-- .github/workflows/android-release.yml | 6 +++--- .github/workflows/ios-ci.yml | 4 ++-- .github/workflows/ios-pre-release.yml | 4 ++-- .github/workflows/linux-ci.yml | 2 +- .github/workflows/macos-release.yml | 4 ++-- .github/workflows/node-ci.yml | 4 ++-- .github/workflows/node-release.yml | 4 ++-- .github/workflows/pr-bloaty-ios.yml | 2 +- .github/workflows/pr-tests.yml | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 7485b65ae2f..fc6837c8830 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -70,7 +70,7 @@ jobs: java-version: "17" - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -90,7 +90,7 @@ jobs: key: ${{ github.job }} - name: restore-gradle-cache - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: gradle-v1 with: diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index c5d24cc448b..c9b9dddfe58 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -32,7 +32,7 @@ jobs: shell: bash - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -54,7 +54,7 @@ jobs: run: ccache --clear - name: Cache ccache - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: ccache-v1 with: @@ -72,7 +72,7 @@ jobs: ccache --show-stats - name: restore-gradle-cache - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: gradle-v1 with: diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 7977667674e..1940e67cf06 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -69,7 +69,7 @@ jobs: fetch-depth: 0 - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -81,7 +81,7 @@ jobs: ${{ runner.os }}- - name: Cache Bazel - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ runner.os }}-bazel-${{ hashFiles('.bazelversion', '.bazelrc', 'WORKSPACE', 'WORKSPACE.bazel', 'MODULE.bazel') }} restore-keys: | diff --git a/.github/workflows/ios-pre-release.yml b/.github/workflows/ios-pre-release.yml index 19c0c6811a5..6de22e0679a 100644 --- a/.github/workflows/ios-pre-release.yml +++ b/.github/workflows/ios-pre-release.yml @@ -33,7 +33,7 @@ jobs: gem list -i xcpretty || sudo gem install xcpretty - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -51,7 +51,7 @@ jobs: run: ccache --clear - name: Cache ccache - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: ccache-v1 with: diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index d56b0bf4f8e..d8ab69346d2 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -191,7 +191,7 @@ jobs: x11-xserver-utils - name: Cache Bazel - uses: actions/cache@v3 + uses: actions/cache@v4 with: key: ${{ runner.os }}-bazel-${{ hashFiles('.bazelversion', '.bazelrc', 'WORKSPACE', 'WORKSPACE.bazel', 'MODULE.bazel') }} restore-keys: | diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml index babdfddb898..16521afdd97 100644 --- a/.github/workflows/macos-release.yml +++ b/.github/workflows/macos-release.yml @@ -31,7 +31,7 @@ jobs: brew list glfw || brew install glfw - name: Cache node modules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -49,7 +49,7 @@ jobs: run: ccache --clear - name: Cache ccache - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: ccache-v1 with: diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 93e4e88183c..665dc4fe033 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -188,7 +188,7 @@ jobs: ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }} - name: Cache cmake-node-module deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: # downloaded with platform/node/cmake/module.cmake path: build/headers @@ -222,7 +222,7 @@ jobs: - name: Restore vcpkg cache (Windows) if: runner.os == 'Windows' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ${{ github.workspace }}/platform/windows/vendor/vcpkg diff --git a/.github/workflows/node-release.yml b/.github/workflows/node-release.yml index 71d743354a3..018f532d006 100644 --- a/.github/workflows/node-release.yml +++ b/.github/workflows/node-release.yml @@ -162,7 +162,7 @@ jobs: ${{ matrix.runs-on }}-${{ env.BUILDTYPE }}-${{ github.job }} - name: Cache cmake-node-module deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: # downloaded with platform/node/cmake/module.cmake path: build/headers @@ -196,7 +196,7 @@ jobs: - name: Restore vcpkg cache (Windows) if: runner.os == 'Windows' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ${{ github.workspace }}/platform/windows/vendor/vcpkg diff --git a/.github/workflows/pr-bloaty-ios.yml b/.github/workflows/pr-bloaty-ios.yml index bf15007031b..bbc4f14eeef 100644 --- a/.github/workflows/pr-bloaty-ios.yml +++ b/.github/workflows/pr-bloaty-ios.yml @@ -27,7 +27,7 @@ jobs: - name: Cache Bloaty id: cache-bloaty - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: bloaty/build/bloaty key: bloaty-${{ env.bloaty_sha }} diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 8ab85c52e47..c77e1fe4c87 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -46,7 +46,7 @@ jobs: - name: Cache Bloaty id: cache-bloaty - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: bloaty/build/bloaty key: bloaty-${{ env.bloaty_sha }} From 802c47dd3b7ff99a954f26b78e3bb151cb1b0d77 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Thu, 18 Jan 2024 10:22:53 -0500 Subject: [PATCH 36/96] Remove allow_empty = False from BUILD files (#2032) --- BUILD.bazel | 10 ++---- expression-test/BUILD.bazel | 1 - metrics/BUILD.bazel | 16 +++------ platform/darwin/BUILD.bazel | 18 +++------- platform/ios/iosapp-UITests/BUILD.bazel | 7 +--- platform/ios/test/BUILD.bazel | 7 +--- platform/ios/vendor/BUILD.bazel | 6 +--- test/BUILD.bazel | 7 +--- vendor/BUILD.bazel | 44 +++++-------------------- 9 files changed, 24 insertions(+), 92 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 16c46b455bb..d040234bebf 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -22,10 +22,7 @@ load("//bazel:flags.bzl", "CPP_FLAGS", "MAPLIBRE_FLAGS") # Generate code required by the core filegroup( name = "style_data", - srcs = glob( - ["**/*.ejs"], - allow_empty = False, - ), + srcs = glob(["**/*.ejs"]), ) genrule( @@ -40,10 +37,7 @@ genrule( filegroup( name = "shader_data", - srcs = ["shaders/manifest.json"] + glob( - ["shaders/*.glsl"], - allow_empty = False, - ), + srcs = ["shaders/manifest.json"] + glob(["shaders/*.glsl"]), ) genrule( diff --git a/expression-test/BUILD.bazel b/expression-test/BUILD.bazel index 2d5b7daca2a..ed11c7779c0 100644 --- a/expression-test/BUILD.bazel +++ b/expression-test/BUILD.bazel @@ -27,7 +27,6 @@ cc_test( "*.cpp", "*.hpp", ], - allow_empty = False, ), copts = CPP_FLAGS + MAPLIBRE_FLAGS, data = [ diff --git a/metrics/BUILD.bazel b/metrics/BUILD.bazel index 0a8c8378605..97def557b0e 100644 --- a/metrics/BUILD.bazel +++ b/metrics/BUILD.bazel @@ -9,17 +9,16 @@ filegroup( "ios-render-test-runner/**", # iOS only "expectations/platform-ios/**", # iOS only ], - allow_empty = False, - ) + select({ - "//:metal_renderer": glob(["expectations/platform-ios-metal/**"]), # iOS Metal only + ) + select({ + "//:metal_renderer": glob(["expectations/platform-ios-metal/**"]), # iOS Metal only "//conditions:default": [], }) + [ "cache-metrics.db", "cache-style.db", - "ios-render-test-runner-metrics.json", # iOS only - "ios-render-test-runner-style.json", # iOS only "ios-metal-render-test-runner-metrics.json", # iOS Metal only "ios-metal-render-test-runner-style.json", # iOS Metal only + "ios-render-test-runner-metrics.json", # iOS only + "ios-render-test-runner-style.json", # iOS only "linux-gcc8-release-metrics.json", "linux-gcc8-release-style.json", ], @@ -31,12 +30,7 @@ filegroup( filegroup( name = "expression-test-files", - srcs = glob( - [ - "integration/expression-tests/**", - ], - allow_empty = False, - ) + [ + srcs = glob(["integration/expression-tests/**"]) + [ "ignores/platform-all.json", ], visibility = [ diff --git a/platform/darwin/BUILD.bazel b/platform/darwin/BUILD.bazel index 79edfbee25b..7eeea973641 100644 --- a/platform/darwin/BUILD.bazel +++ b/platform/darwin/BUILD.bazel @@ -4,9 +4,9 @@ load( "MLN_DARWIN_OBJCPP_HEADERS", "MLN_DARWIN_OBJC_HEADERS", "MLN_DARWIN_PRIVATE_HEADERS", - "MLN_DARWIN_PUBLIC_OBJCPP_SOURCE", - "MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE", "MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE", + "MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE", + "MLN_DARWIN_PUBLIC_OBJCPP_SOURCE", "MLN_DARWIN_PUBLIC_OBJC_SOURCE", "MLN_GENERATED_DARWIN_STYLE_HEADERS", "MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS", @@ -147,14 +147,10 @@ objc_library( "test/*.m", "test/*.h", ], - allow_empty = False, ), copts = MAPLIBRE_FLAGS + WARNING_FLAGS["ios"], data = glob( - [ - "test/Media.xcassets/**", - ], - allow_empty = False, + ["test/Media.xcassets/**"], ), sdk_frameworks = [ "MapKit", @@ -175,7 +171,6 @@ objc_library( "test/*.h", "test/*.mm", ], - allow_empty = False, exclude = [ "test/MLNOfflineStorageTests.mm", ], @@ -192,12 +187,7 @@ objc_library( swift_library( name = "shared_tests_swift_srcs", testonly = True, - srcs = glob( - [ - "test/*.swift", - ], - allow_empty = False, - ), + srcs = glob(["test/*.swift"]), data = [ "test/amsterdam.geojson", "test/one-liner.json", diff --git a/platform/ios/iosapp-UITests/BUILD.bazel b/platform/ios/iosapp-UITests/BUILD.bazel index ac541f2f6a0..26facea8b3b 100644 --- a/platform/ios/iosapp-UITests/BUILD.bazel +++ b/platform/ios/iosapp-UITests/BUILD.bazel @@ -10,12 +10,7 @@ configure_device_profiles() swift_library( name = "uitest_srcs", testonly = True, - srcs = glob( - [ - "*.swift", - ], - allow_empty = False, - ), + srcs = glob(["*.swift"]), ) ios_ui_test( diff --git a/platform/ios/test/BUILD.bazel b/platform/ios/test/BUILD.bazel index 2809711f5c7..2263751c3bc 100644 --- a/platform/ios/test/BUILD.bazel +++ b/platform/ios/test/BUILD.bazel @@ -12,7 +12,6 @@ objc_library( "*.h", "*.m", ], - allow_empty = False, exclude = [ "MLNMapViewContentInsetTests.m", ], @@ -30,7 +29,6 @@ objc_library( "*.mm", "*.h", ], - allow_empty = False, ), copts = CPP_FLAGS + MAPLIBRE_FLAGS + WARNING_FLAGS["ios"], deps = [ @@ -41,10 +39,7 @@ objc_library( swift_library( name = "ios_tests_swift_srcs", testonly = True, - srcs = glob( - ["*.swift"], - allow_empty = False, - ) + [ + srcs = glob(["*.swift"]) + [ "//platform/darwin:test/MLNSDKTestHelpers.swift", ], deps = [ diff --git a/platform/ios/vendor/BUILD.bazel b/platform/ios/vendor/BUILD.bazel index edbf56efd7b..004cfccd1e1 100644 --- a/platform/ios/vendor/BUILD.bazel +++ b/platform/ios/vendor/BUILD.bazel @@ -8,13 +8,9 @@ objc_library( "SMCalloutView/*.h", "SMCalloutView/*.m", ], - allow_empty = False, ), hdrs = glob( - [ - "SMCalloutView/*.h", - ], - allow_empty = False, + ["SMCalloutView/*.h"], ), copts = WARNING_FLAGS["ios"], includes = ["SMCalloutView"], diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 24c66d8f000..2738b322c95 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -14,13 +14,9 @@ cc_library( cc_library( name = "testlib", srcs = select({ - "//:ios": glob( - ["src/mbgl/test/*.cpp"], - allow_empty = False, - ), + "//:ios": glob(["src/mbgl/test/*.cpp"]), "//conditions:default": glob( ["src/mbgl/test/*.cpp"], - allow_empty = False, exclude = ["src/mbgl/test/http_server.cpp"], ), }) + [ @@ -74,7 +70,6 @@ filegroup( name = "fixtures", srcs = glob( ["fixtures/**/*"], - allow_empty = False, ), visibility = [ "//platform/ios/test/core:__pkg__", diff --git a/vendor/BUILD.bazel b/vendor/BUILD.bazel index 0d15016e496..070cf325a96 100644 --- a/vendor/BUILD.bazel +++ b/vendor/BUILD.bazel @@ -3,7 +3,9 @@ load("//bazel:flags.bzl", "CPP_FLAGS") # vendor/mapbox-base-files.json cc_library( name = "mapbox-base", - hdrs = glob( + hdrs = [ + "mapbox-base/extras/args/args.hxx", + ] + glob( [ "mapbox-base/deps/cheap-ruler-cpp/include/mapbox/*.hpp", "mapbox-base/deps/geojson-vt-cpp/include/**/*.hpp", @@ -18,10 +20,8 @@ cc_library( "mapbox-base/extras/filesystem/include/ghc/*.hpp", "mapbox-base/extras/kdbush.hpp/include/*.hpp", "mapbox-base/extras/rapidjson/include/**/*.h", - "mapbox-base/extras/args/args.hxx", "mapbox-base/include/mapbox/**/*.hpp", ], - allow_empty = False, exclude = ["mapbox-base/deps/jni.hpp/include/jni/string_conversion.hpp"], ), copts = CPP_FLAGS, @@ -51,12 +51,7 @@ cc_library( cc_library( name = "parsedate", srcs = ["parsedate/parsedate.cpp"], - hdrs = glob( - [ - "parsedate/**/*.hpp", - ], - allow_empty = False, - ), + hdrs = glob(["parsedate/**/*.hpp"]), copts = CPP_FLAGS, includes = ["parsedate"], visibility = ["//visibility:public"], @@ -65,12 +60,7 @@ cc_library( # vendor/optional cc_library( name = "optional", - hdrs = glob( - [ - "mapbox-base/deps/optional/*.hpp", - ], - allow_empty = False, - ), + hdrs = glob(["mapbox-base/deps/optional/*.hpp"]), copts = CPP_FLAGS, includes = ["mapbox-base/deps/optional"], visibility = ["//visibility:public"], @@ -84,7 +74,6 @@ cc_library( "csscolorparser/**/*.hpp", "csscolorparser/**/*.cpp", ], - allow_empty = False, ), copts = CPP_FLAGS, includes = ["csscolorparser"], @@ -95,10 +84,7 @@ cc_library( # vendor/wagyu-files.json cc_library( name = "wagyu", - hdrs = glob( - ["wagyu/include/mapbox/geometry/wagyu/*.hpp"], - allow_empty = False, - ), + hdrs = glob(["wagyu/include/mapbox/geometry/wagyu/*.hpp"]), copts = CPP_FLAGS, includes = ["wagyu/include"], visibility = ["//visibility:public"], @@ -115,7 +101,6 @@ objc_library( "zip-archive/SSZipArchive/minizip/*.c", "zip-archive/SSZipArchive/minizip/*.h", ], - allow_empty = False, ), defines = ["ZLIB_COMPAT"], includes = ["zip-archive/SSZipArchive"], @@ -130,7 +115,6 @@ cc_library( "boost/include/boost/**/*.hpp", "boost/include/boost/**/*.h", ], - allow_empty = False, ), copts = CPP_FLAGS, includes = ["boost/include"], @@ -148,10 +132,7 @@ cc_library( # vendor/protozero-files.json cc_library( name = "protozero", - hdrs = glob( - ["protozero/include/protozero/*.hpp"], - allow_empty = False, - ), + hdrs = glob(["protozero/include/protozero/*.hpp"]), copts = CPP_FLAGS, includes = ["protozero/include"], visibility = ["//visibility:public"], @@ -178,10 +159,7 @@ cc_library( # vendor/metal-cpp.json cc_library( name = "metal-cpp", - hdrs = glob( - ["metal-cpp/**/*.hpp"], - allow_empty = False, - ), + hdrs = glob(["metal-cpp/**/*.hpp"]), copts = CPP_FLAGS, includes = ["metal-cpp"], visibility = ["//visibility:public"], @@ -208,10 +186,7 @@ cc_library( # vendor/vector-tile-files.json cc_library( name = "vector-tile", - hdrs = glob( - ["vector-tile/include/mapbox/**/*.hpp"], - allow_empty = False, - ), + hdrs = glob(["vector-tile/include/mapbox/**/*.hpp"]), copts = CPP_FLAGS, includes = ["vector-tile/include"], visibility = ["//visibility:public"], @@ -225,7 +200,6 @@ cc_library( "icu/src/*.cpp", "icu/src/*.h", ], - allow_empty = False, ), hdrs = glob(["icu/include/unicode/*.h"]), copts = CPP_FLAGS + [ From cd3de6d3ad25499bd1c96f7f5b99cc3452fdec34 Mon Sep 17 00:00:00 2001 From: Stefan Karschti Date: Fri, 19 Jan 2024 18:00:38 +0200 Subject: [PATCH 37/96] Fix Custom Drawable Layer usage of private headers (#2039) --- .../app/ExampleCustomDrawableStyleLayer.h | 7 +------ .../app/ExampleCustomDrawableStyleLayer.mm | 6 +----- .../darwin/src/MLNCustomDrawableStyleLayer.h | 11 ++++++++++- .../darwin/src/MLNCustomDrawableStyleLayer.mm | 18 ++++++------------ 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/platform/darwin/app/ExampleCustomDrawableStyleLayer.h b/platform/darwin/app/ExampleCustomDrawableStyleLayer.h index 2c5e558bc89..4dfde0d9cc3 100644 --- a/platform/darwin/app/ExampleCustomDrawableStyleLayer.h +++ b/platform/darwin/app/ExampleCustomDrawableStyleLayer.h @@ -1,11 +1,6 @@ #import "Mapbox.h" -#import "MLNFoundation.h" -#import "MLNStyleValue.h" -#import "MLNStyleLayer.h" -#import "MLNGeometry.h" - -@interface ExampleCustomDrawableStyleLayer : MLNStyleLayer +@interface ExampleCustomDrawableStyleLayer : MLNCustomDrawableStyleLayer - (instancetype)initWithIdentifier:(NSString *)identifier; diff --git a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm index a3756dce8bf..dce804d73eb 100644 --- a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm +++ b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm @@ -1,10 +1,6 @@ #import "ExampleCustomDrawableStyleLayer.h" #import "MLNStyleLayer.h" - -#import "MLNCustomDrawableStyleLayer_Private.h" -#import "MLNStyle_Private.h" -#import "MLNStyleLayer_Private.h" -#import "MLNGeometry_Private.h" +#import "MLNCustomDrawableStyleLayer.h" #include #include diff --git a/platform/darwin/src/MLNCustomDrawableStyleLayer.h b/platform/darwin/src/MLNCustomDrawableStyleLayer.h index 6c581b3730a..bd84e13aee3 100644 --- a/platform/darwin/src/MLNCustomDrawableStyleLayer.h +++ b/platform/darwin/src/MLNCustomDrawableStyleLayer.h @@ -1,10 +1,19 @@ +#import + +#import "MLNFoundation.h" #import "MLNFoundation.h" #import "MLNStyleValue.h" #import "MLNStyleLayer.h" #import "MLNGeometry.h" +#ifdef __cplusplus +#include +#endif + @interface MLNCustomDrawableStyleLayer : MLNStyleLayer -- (instancetype)initWithIdentifier:(NSString *)identifier; +#ifdef __cplusplus +- (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer; +#endif @end diff --git a/platform/darwin/src/MLNCustomDrawableStyleLayer.mm b/platform/darwin/src/MLNCustomDrawableStyleLayer.mm index be3698b1316..b8ddaae46aa 100644 --- a/platform/darwin/src/MLNCustomDrawableStyleLayer.mm +++ b/platform/darwin/src/MLNCustomDrawableStyleLayer.mm @@ -14,18 +14,12 @@ @implementation MLNCustomDrawableStyleLayer -/// @note -/// Inherit MLNCustomDrawableStyleLayer class and override initWithIdentifier method to create and attach a valid CustomDrawableLayerHost instance -/// Example: -/// - (instancetype)initWithIdentifier:(NSString *)identifier { -/// auto layer = std::make_unique(identifier.UTF8String, -/// std::make_unique(self)); -/// return self = [super initWithPendingLayer:std::move(layer)]; -/// } -/// -- (instancetype)initWithIdentifier:(NSString *)identifier { - auto layer = std::make_unique(identifier.UTF8String, nullptr); - return self = [super initWithPendingLayer:std::move(layer)]; +- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer { + return [super initWithRawLayer:rawLayer]; +} + +- (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer { + return [super initWithPendingLayer:std::move(pendingLayer)]; } @end From 5c304614d6770ff6a1ce8f0ea7fa2ecf0cc72697 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Mon, 22 Jan 2024 07:02:36 -0800 Subject: [PATCH 38/96] Avoid redundant bindings/states based on Metal profiler feedback. (#2006) --- include/mbgl/mtl/render_pass.hpp | 19 ++++++++++ include/mbgl/mtl/texture2d.hpp | 4 +-- src/mbgl/mtl/buffer_resource.cpp | 2 +- src/mbgl/mtl/context.cpp | 8 ++--- src/mbgl/mtl/drawable.cpp | 6 ++-- src/mbgl/mtl/render_pass.cpp | 59 +++++++++++++++++++++++++++---- src/mbgl/mtl/texture2d.cpp | 9 +++-- src/mbgl/mtl/tile_layer_group.cpp | 6 ++-- src/mbgl/mtl/upload_pass.cpp | 12 +++---- 9 files changed, 90 insertions(+), 35 deletions(-) diff --git a/include/mbgl/mtl/render_pass.hpp b/include/mbgl/mtl/render_pass.hpp index f87691132c3..e13d6b5f576 100644 --- a/include/mbgl/mtl/render_pass.hpp +++ b/include/mbgl/mtl/render_pass.hpp @@ -27,6 +27,19 @@ class RenderPass final : public gfx::RenderPass { const MTLRenderCommandEncoderPtr& getMetalEncoder() const { return encoder; } const gfx::RenderPassDescriptor& getDescriptor() const { return descriptor; } + /// Apply the given depth/stencil state, if different from the current value + /// The state may be null, restoring the default state. + void setDepthStencilState(const MTLDepthStencilStatePtr&); + + /// Apply the given stencil reference value, if different from the current value + void setStencilReference(int32_t referenceValue); + + /// Bind a texture to the fragment location + void setFragmentTexture(const MTLTexturePtr&, int32_t location); + + /// Set the sampler for a texture binding + void setFragmentSamplerState(const MTLSamplerStatePtr&, int32_t location); + void endEncoding(); void addDebugSignpost(const char* name) override; @@ -43,16 +56,22 @@ class RenderPass final : public gfx::RenderPass { gfx::RenderPassDescriptor descriptor; mtl::CommandEncoder& commandEncoder; MTLRenderCommandEncoderPtr encoder; + MTLDepthStencilStatePtr currentDepthStencilState; + int32_t currentStencilReferenceValue = 0; std::vector> debugGroups; struct BindInfo { const BufferResource* buf = nullptr; + NS::UInteger size = 0; NS::UInteger offset = 0; std::uint16_t version = 0; }; static constexpr auto maxBinds = 32; std::array, maxBinds> vertexBinds; std::array, maxBinds> fragmentBinds; + + std::array fragmentTextureBindings; + std::array fragmentSamplerStates; }; } // namespace mtl diff --git a/include/mbgl/mtl/texture2d.hpp b/include/mbgl/mtl/texture2d.hpp index 8288fa1872e..41b7e7667c5 100644 --- a/include/mbgl/mtl/texture2d.hpp +++ b/include/mbgl/mtl/texture2d.hpp @@ -54,12 +54,12 @@ class Texture2D : public gfx::Texture2D { /// @brief Bind this texture to the specified location /// @param renderPass Render pass on which the texture will be assign /// @param location Location index of texture sampler in a shader - void bind(const RenderPass& renderPass, int32_t location) noexcept; + void bind(RenderPass& renderPass, int32_t location) noexcept; /// @brief Unbind the texture, if it was bound /// @param renderPass Render pass from which the texture will be removed /// @param location Location index of texture sampler in a shader - void unbind(const RenderPass& renderPass, int32_t location) noexcept; + void unbind(RenderPass& renderPass, int32_t location) noexcept; private: MTL::PixelFormat getMetalPixelFormat() const noexcept; diff --git a/src/mbgl/mtl/buffer_resource.cpp b/src/mbgl/mtl/buffer_resource.cpp index 3ed03798005..5ac4071670a 100644 --- a/src/mbgl/mtl/buffer_resource.cpp +++ b/src/mbgl/mtl/buffer_resource.cpp @@ -147,7 +147,7 @@ void BufferResource::update(const void* newData, std::size_t updateSize, std::si bool BufferResource::needReBind(VersionType version_) const noexcept { // If we're using a raw buffer, an update means we have to re-bind. // For a MTLBuffer, the binding can be left alone. - return (!buffer || version != version_); + return (version != version_); } void BufferResource::bindVertex(const MTLRenderCommandEncoderPtr& encoder, diff --git a/src/mbgl/mtl/context.cpp b/src/mbgl/mtl/context.cpp index 7a58732a9b7..edd8e60e1ff 100644 --- a/src/mbgl/mtl/context.cpp +++ b/src/mbgl/mtl/context.cpp @@ -324,12 +324,8 @@ bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass, clipMaskDepthStencilState = std::move(depthStencilState); } } - if (clipMaskDepthStencilState) { - encoder->setDepthStencilState(clipMaskDepthStencilState.get()); - } else { - assert(!"Failed to create depth-stencil state for clip masking"); - return false; - } + assert(clipMaskDepthStencilState || !"Failed to create depth-stencil state for clip masking"); + mtlRenderPass.setDepthStencilState(clipMaskDepthStencilState); if (!clipMaskPipelineState) { // A vertex descriptor tells Metal what's in the vertex buffer diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index ab8287e5964..1cae17c942f 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -233,10 +233,8 @@ void Drawable::draw(PaintParameters& parameters) const { impl->depthStencilState = context.makeDepthStencilState(depthMode, stencilMode, renderable); impl->previousStencilMode = *newStencilMode; } - if (impl->depthStencilState) { - encoder->setDepthStencilState(impl->depthStencilState.get()); - encoder->setStencilReferenceValue(impl->previousStencilMode.ref); - } + renderPass.setDepthStencilState(impl->depthStencilState); + renderPass.setStencilReference(impl->previousStencilMode.ref); } for (const auto& seg_ : impl->segments) { diff --git a/src/mbgl/mtl/render_pass.cpp b/src/mbgl/mtl/render_pass.cpp index 6a06762f75f..f09feb4654e 100644 --- a/src/mbgl/mtl/render_pass.cpp +++ b/src/mbgl/mtl/render_pass.cpp @@ -63,6 +63,15 @@ void RenderPass::endEncoding() { encoder->endEncoding(); encoder.reset(); } + + currentDepthStencilState.reset(); + currentStencilReferenceValue = 0; + for (int i = 0; i < maxBinds; ++i) { + vertexBinds[i].reset(); + fragmentBinds[i].reset(); + fragmentTextureBindings[i].reset(); + fragmentSamplerStates[i].reset(); + } } namespace { @@ -94,6 +103,8 @@ void RenderPass::addDebugSignpost(const char* name) { } void RenderPass::bindVertex(const BufferResource& buf, std::size_t offset, std::size_t index, std::size_t size) { + const auto actualSize = size ? size : buf.getSizeInBytes() - offset; + assert(actualSize <= buf.getSizeInBytes()); assert(0 <= index && index < maxBinds); if (0 <= index && index < maxBinds) { if (auto& bind = vertexBinds[index]) { @@ -102,18 +113,20 @@ void RenderPass::bindVertex(const BufferResource& buf, std::size_t offset, std:: // Yes, but is the offset different? if (bind->offset != offset) { // Yes, update just the offset - buf.updateVertexBindOffset(encoder, offset, index, size); + buf.updateVertexBindOffset(encoder, offset, index, actualSize); bind->offset = offset; } return; } } - vertexBinds[index] = BindInfo{&buf, offset}; + vertexBinds[index] = BindInfo{&buf, actualSize, offset}; } - buf.bindVertex(encoder, offset, index, size); + buf.bindVertex(encoder, offset, index, actualSize); } void RenderPass::bindFragment(const BufferResource& buf, std::size_t offset, std::size_t index, std::size_t size) { + const auto actualSize = size ? size : buf.getSizeInBytes() - offset; + assert(actualSize <= buf.getSizeInBytes()); assert(0 <= index && index < maxBinds); if (0 <= index && index < maxBinds) { if (auto& bind = fragmentBinds[index]) { @@ -122,15 +135,49 @@ void RenderPass::bindFragment(const BufferResource& buf, std::size_t offset, std // Yes, but is the offset different? if (bind->offset != offset) { // Yes, update just the offset - buf.updateFragmentBindOffset(encoder, offset, index, size); + buf.updateFragmentBindOffset(encoder, offset, index, actualSize); bind->offset = offset; } return; } } - fragmentBinds[index] = BindInfo{&buf, offset}; + fragmentBinds[index] = BindInfo{&buf, actualSize, offset}; + } + buf.bindFragment(encoder, offset, index, actualSize); +} + +void RenderPass::setDepthStencilState(const MTLDepthStencilStatePtr& state) { + if (state != currentDepthStencilState) { + currentDepthStencilState = state; + encoder->setDepthStencilState(currentDepthStencilState.get()); + } +} + +void RenderPass::setStencilReference(int32_t referenceValue) { + if (referenceValue != currentStencilReferenceValue) { + currentStencilReferenceValue = referenceValue; + encoder->setStencilReferenceValue(currentStencilReferenceValue); + } +} + +void RenderPass::setFragmentTexture(const MTLTexturePtr& texture, int32_t location) { + assert(0 <= location && location < maxBinds); + if (0 <= location && location < maxBinds) { + if (fragmentTextureBindings[location] != texture) { + fragmentTextureBindings[location] = texture; + encoder->setFragmentTexture(texture.get(), location); + } + } +} + +void RenderPass::setFragmentSamplerState(const MTLSamplerStatePtr& state, int32_t location) { + assert(0 <= location && location < maxBinds); + if (0 <= location && location < maxBinds) { + if (fragmentSamplerStates[location] != state) { + fragmentSamplerStates[location] = state; + encoder->setFragmentSamplerState(state.get(), location); + } } - buf.bindFragment(encoder, offset, index, size); } } // namespace mtl diff --git a/src/mbgl/mtl/texture2d.cpp b/src/mbgl/mtl/texture2d.cpp index 9aafe545011..6d10d2d282c 100644 --- a/src/mbgl/mtl/texture2d.cpp +++ b/src/mbgl/mtl/texture2d.cpp @@ -186,22 +186,21 @@ void Texture2D::updateSamplerConfiguration() noexcept { metalSamplerState = context.createMetalSamplerState(samplerDescriptor); } -void Texture2D::bind(const RenderPass& renderPass, int32_t location) noexcept { +void Texture2D::bind(RenderPass& renderPass, int32_t location) noexcept { assert(!textureDirty); - const auto& encoder = renderPass.getMetalEncoder(); // Update the sampler state if it was changed after resource creation if (samplerStateDirty) { updateSamplerConfiguration(); } - encoder->setFragmentTexture(metalTexture.get(), location); - encoder->setFragmentSamplerState(metalSamplerState.get(), location); + renderPass.setFragmentTexture(metalTexture, location); + renderPass.setFragmentSamplerState(metalSamplerState, location); context.renderingStats().numTextureBindings++; } -void Texture2D::unbind(const RenderPass&, int32_t /*location*/) noexcept { +void Texture2D::unbind(RenderPass&, int32_t /*location*/) noexcept { context.renderingStats().numTextureBindings--; } diff --git a/src/mbgl/mtl/tile_layer_group.cpp b/src/mbgl/mtl/tile_layer_group.cpp index e2183aeb820..adb334d8ed3 100644 --- a/src/mbgl/mtl/tile_layer_group.cpp +++ b/src/mbgl/mtl/tile_layer_group.cpp @@ -43,7 +43,7 @@ void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { } auto& context = static_cast(parameters.context); - const auto& renderPass = static_cast(*parameters.renderPass); + auto& renderPass = static_cast(*parameters.renderPass); const auto& encoder = renderPass.getMetalEncoder(); const auto& renderable = renderPass.getDescriptor().renderable; @@ -101,9 +101,7 @@ void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { // 2D drawables will set their own stencil mode within `draw`. if (features3d) { const auto& state = drawable.getEnableStencil() ? stateWithStencil : stateWithoutStencil; - if (state) { - encoder->setDepthStencilState(state.get()); - } + renderPass.setDepthStencilState(state); } drawable.draw(parameters); diff --git a/src/mbgl/mtl/upload_pass.cpp b/src/mbgl/mtl/upload_pass.cpp index e189915794f..cf1ef9cf604 100644 --- a/src/mbgl/mtl/upload_pass.cpp +++ b/src/mbgl/mtl/upload_pass.cpp @@ -25,13 +25,13 @@ UploadPass::UploadPass(gfx::Renderable& renderable, CommandEncoder& commandEncod if (const auto& buffer_ = resource.getCommandBuffer()) { buffer = buffer_; - if (auto upd = resource.getUploadPassDescriptor()) { - encoder = NS::RetainPtr(buffer->blitCommandEncoder(upd.get())); - } + // blit encoder is not being used yet + // if (auto upd = resource.getUploadPassDescriptor()) { + // encoder = NS::RetainPtr(buffer->blitCommandEncoder(upd.get())); + //} + // assert(encoder); } - assert(encoder); - // Push the groups already accumulated by the encoder commandEncoder.visitDebugGroups([this](const auto& group) { debugGroups.emplace_back(gfx::DebugGroup{*this, group.c_str()}); @@ -253,14 +253,12 @@ NS::String* toNSString(const char* str) { } // namespace void UploadPass::pushDebugGroup(const char* name) { - assert(encoder); if (encoder) { encoder->pushDebugGroup(toNSString(name)); } } void UploadPass::popDebugGroup() { - assert(encoder); if (encoder) { encoder->popDebugGroup(); } From ec9afb5256ba7f90950d983530a4cc9cc59067a0 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Mon, 22 Jan 2024 10:53:41 -0800 Subject: [PATCH 39/96] UV timer hack (#2042) --- platform/default/src/mbgl/util/timer.cpp | 13 +++++++-- test/util/timer.test.cpp | 34 ++++++++++++++---------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/platform/default/src/mbgl/util/timer.cpp b/platform/default/src/mbgl/util/timer.cpp index 92eb4aa6993..fd8f94f659c 100644 --- a/platform/default/src/mbgl/util/timer.cpp +++ b/platform/default/src/mbgl/util/timer.cpp @@ -1,11 +1,12 @@ #include -#include - #include +#include #include +#include + namespace mbgl { namespace util { @@ -28,6 +29,14 @@ class Timer::Impl { void start(uint64_t timeout, uint64_t repeat, std::function&& cb_) { cb = std::move(cb_); + + // Update the event loop’s concept of “now”. + // This resolves test failures on some systems, where only the following sequence of tests fails: + // Thread.InvokeBeforeChildStarts:Thread.DeleteBeforeChildStarts:Timer.Basic + // This implies that "you have callbacks that block the event loop for ... on the order of a millisecond + // or more," so this may be masking some unexpected interaction between successive `RunLoop` instances. + uv_update_time(reinterpret_cast(RunLoop::getLoopHandle())); + if (uv_timer_start(timer, timerCallback, timeout, repeat) != 0) { throw std::runtime_error("Failed to start timer."); } diff --git a/test/util/timer.test.cpp b/test/util/timer.test.cpp index c3e0d176143..4b4010405d7 100644 --- a/test/util/timer.test.cpp +++ b/test/util/timer.test.cpp @@ -1,37 +1,43 @@ #include #include -#include +#include #include +#include +#include #include +#include using namespace mbgl::util; TEST(Timer, TEST_REQUIRES_ACCURATE_TIMING(Basic)) { RunLoop loop; - Timer timer; + const auto interval = mbgl::Milliseconds(300); + const auto expectedTotalTime = interval; - auto callback = [&loop] { + const auto first = MonotonicTimer::now(); + const auto elapsed = [=] { + return std::chrono::duration_cast(MonotonicTimer::now() - first); + }; + std::optional callbackTime; + auto callback = [&] { + callbackTime = elapsed(); loop.stop(); }; - auto interval = mbgl::Milliseconds(300); - auto expectedTotalTime = interval; - - auto first = mbgl::Clock::now(); - timer.start(interval, mbgl::Duration::zero(), callback); + Timer timer; + timer.start(interval, mbgl::Duration::zero(), std::move(callback)); loop.run(); - auto totalTime = std::chrono::duration_cast(mbgl::Clock::now() - first); + const auto totalTime = elapsed(); - // These are not high precision timers. Especially libuv uses - // cached time from the beginning of of the main loop iteration - // and it is very prone to fire earlier, which is, odd. - EXPECT_GE(totalTime, expectedTotalTime * 0.8); - EXPECT_LE(totalTime, expectedTotalTime * 1.2); + SCOPED_TRACE("Timer callback: " + (callbackTime ? toString(callbackTime->count()) : "(never)")); + + EXPECT_GE(totalTime.count(), (expectedTotalTime * 0.99).count()); + EXPECT_LE(totalTime.count(), (expectedTotalTime * 1.10).count()); } TEST(Timer, TEST_REQUIRES_ACCURATE_TIMING(Repeat)) { From e7d6f6a84df879ed2729667d0c2425b5c465e3e3 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Thu, 25 Jan 2024 10:57:53 -0800 Subject: [PATCH 40/96] Line tweaker (#2050) --- .../layers/background_layer_tweaker.cpp | 3 ++ .../renderer/layers/circle_layer_tweaker.cpp | 12 ++---- .../layers/fill_extrusion_layer_tweaker.cpp | 7 +--- .../renderer/layers/heatmap_layer_tweaker.cpp | 27 ++++++++------ .../layers/heatmap_texture_layer_tweaker.cpp | 30 ++++++++------- .../layers/heatmap_texture_layer_tweaker.hpp | 1 + .../layers/hillshade_layer_tweaker.cpp | 34 ++++++++++------- .../hillshade_prepare_layer_tweaker.cpp | 19 +++++----- .../renderer/layers/line_layer_tweaker.cpp | 37 ++++++++++--------- .../renderer/layers/raster_layer_tweaker.cpp | 2 + .../layers/render_background_layer.cpp | 5 +-- .../renderer/layers/render_circle_layer.cpp | 5 +-- .../layers/render_fill_extrusion_layer.cpp | 5 +-- .../renderer/layers/render_heatmap_layer.cpp | 26 +++++++------ .../renderer/layers/render_heatmap_layer.hpp | 4 ++ .../layers/render_hillshade_layer.cpp | 31 +++++++--------- .../layers/render_hillshade_layer.hpp | 5 +++ .../renderer/layers/render_line_layer.cpp | 5 +-- .../renderer/layers/render_raster_layer.cpp | 5 +-- src/mbgl/renderer/render_layer.cpp | 25 ------------- src/mbgl/renderer/render_layer.hpp | 5 --- 21 files changed, 141 insertions(+), 152 deletions(-) diff --git a/src/mbgl/renderer/layers/background_layer_tweaker.cpp b/src/mbgl/renderer/layers/background_layer_tweaker.cpp index 3e9c93d322b..faffc5249ec 100644 --- a/src/mbgl/renderer/layers/background_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/background_layer_tweaker.cpp @@ -51,6 +51,9 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara } layerGroup.setEnabled(true); + // properties are re-evaluated every time + propertiesUpdated = false; + std::optional samplerLocation{}; visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { assert(drawable.getTileID()); diff --git a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp index 1755175412a..91a1ef0e9be 100644 --- a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp @@ -45,19 +45,14 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete /* .padding = */ 0, 0, 0}; - - if (!paintParamsUniformBuffer) { - paintParamsUniformBuffer = context.createUniformBuffer(&paintParamsUBO, sizeof(paintParamsUBO)); - } else { - paintParamsUniformBuffer->update(&paintParamsUBO, sizeof(CirclePaintParamsUBO)); - } + context.emplaceOrUpdateUniformBuffer(paintParamsUniformBuffer, &paintParamsUBO); const auto zoom = parameters.state.getZoom(); const bool pitchWithMap = evaluated.get() == AlignmentType::Map; const bool scaleWithMap = evaluated.get() == CirclePitchScaleType::Map; // Updated only with evaluated properties - if (!evaluatedPropsUniformBuffer) { + if (!evaluatedPropsUniformBuffer || propertiesUpdated) { const CircleEvaluatedPropsUBO evaluatedPropsUBO = { /* .color = */ constOrDefault(evaluated), /* .stroke_color = */ constOrDefault(evaluated), @@ -69,8 +64,9 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete /* .scale_with_map = */ scaleWithMap, /* .pitch_with_map = */ pitchWithMap, /* .padding = */ 0}; - evaluatedPropsUniformBuffer = context.createUniformBuffer(&evaluatedPropsUBO, sizeof(evaluatedPropsUBO)); + context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &evaluatedPropsUBO); } + propertiesUpdated = false; visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { assert(drawable.getTileID() || !"Circles only render with tiles"); diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp index 71ceba343fb..4b1e97510ee 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp @@ -70,11 +70,8 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintP /* .pad = */ 0, 0, 0}; - if (!propsBuffer) { - propsBuffer = context.createUniformBuffer(¶msUBO, sizeof(paramsUBO)); - } else { - propsBuffer->update(¶msUBO, sizeof(paramsUBO)); - } + context.emplaceOrUpdateUniformBuffer(propsBuffer, ¶msUBO); + propertiesUpdated = false; visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { if (!drawable.getTileID() || !checkTweakDrawable(drawable)) { diff --git a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp index a7e55df952d..ed9ced5f4cf 100644 --- a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp @@ -25,6 +25,7 @@ static const StringIdentity idHeatmapEvaluatedPropsUBOName = stringIndexer().get void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; + const auto zoom = parameters.state.getZoom(); const auto& evaluated = static_cast(*evaluatedProperties).evaluated; if (layerGroup.empty()) { @@ -36,17 +37,17 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet const auto debugGroup = parameters.encoder->createDebugGroup(label.c_str()); #endif - const auto zoom = parameters.state.getZoom(); - - if (!evaluatedPropsUniformBuffer) { - const HeatmapEvaluatedPropsUBO evaluatedPropsUBO = { - /* .weight = */ evaluated.get().constantOr(HeatmapWeight::defaultValue()), - /* .radius = */ evaluated.get().constantOr(HeatmapRadius::defaultValue()), - /* .intensity = */ evaluated.get(), - /* .padding = */ 0}; - evaluatedPropsUniformBuffer = parameters.context.createUniformBuffer(&evaluatedPropsUBO, - sizeof(evaluatedPropsUBO)); - } + const auto getPropsBuffer = [&]() -> auto& { + if (!evaluatedPropsUniformBuffer || propertiesUpdated) { + const HeatmapEvaluatedPropsUBO evaluatedPropsUBO = { + /* .weight = */ evaluated.get().constantOr(HeatmapWeight::defaultValue()), + /* .radius = */ evaluated.get().constantOr(HeatmapRadius::defaultValue()), + /* .intensity = */ evaluated.get(), + /* .padding = */ 0}; + parameters.context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &evaluatedPropsUBO); + } + return evaluatedPropsUniformBuffer; + }; visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { if (!drawable.getTileID() || !checkTweakDrawable(drawable)) { @@ -56,7 +57,7 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.addOrReplace(idHeatmapEvaluatedPropsUBOName, evaluatedPropsUniformBuffer); + uniforms.addOrReplace(idHeatmapEvaluatedPropsUBOName, getPropsBuffer()); constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; @@ -69,6 +70,8 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet uniforms.createOrUpdate(idHeatmapDrawableUBOName, &drawableUBO, context); }); + + propertiesUpdated = false; } } // namespace mbgl diff --git a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp index 3f622b90fcb..d964bbddd6d 100644 --- a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp @@ -30,23 +30,27 @@ void HeatmapTextureLayerTweaker::execute(LayerGroupBase& layerGroup, const Paint const auto debugGroup = parameters.encoder->createDebugGroup(label.c_str()); #endif + const auto getDrawableUBO = [&]() -> auto& { + if (!drawableBuffer) { + const auto& size = parameters.staticData.backendSize; + mat4 viewportMat; + matrix::ortho(viewportMat, 0, size.width, size.height, 0, -1, 1); + const HeatmapTextureDrawableUBO drawableUBO = { + /* .matrix = */ util::cast(viewportMat), + /* .world = */ {static_cast(size.width), static_cast(size.height)}, + /* .opacity = */ evaluated.get(), + /* .pad1 = */ 0, + }; + parameters.context.emplaceOrUpdateUniformBuffer(drawableBuffer, &drawableUBO); + } + return drawableBuffer; + }; + visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { if (!checkTweakDrawable(drawable)) { return; } - - const auto& size = parameters.staticData.backendSize; - mat4 viewportMat; - matrix::ortho(viewportMat, 0, size.width, size.height, 0, -1, 1); - const HeatmapTextureDrawableUBO drawableUBO = { - /* .matrix = */ util::cast(viewportMat), - /* .world = */ {static_cast(size.width), static_cast(size.height)}, - /* .opacity = */ evaluated.get(), - /* .pad1 = */ 0, - }; - - drawable.mutableUniformBuffers().createOrUpdate( - idHeatmapTextureDrawableUBOName, &drawableUBO, parameters.context); + drawable.mutableUniformBuffers().addOrReplace(idHeatmapTextureDrawableUBOName, getDrawableUBO()); }); } diff --git a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp index 62c5a12f98b..350cac89a59 100644 --- a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp @@ -18,6 +18,7 @@ class HeatmapTextureLayerTweaker : public LayerTweaker { void execute(LayerGroupBase&, const PaintParameters&) override; protected: + gfx::UniformBufferPtr drawableBuffer; }; } // namespace mbgl diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp index 702b94d9a3c..28d2d0c3584 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp @@ -15,8 +15,9 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idHillshadeDrawableUBOName = stringIndexer().get("HillshadeDrawableUBO"); -static const StringIdentity idHillshadeEvaluatedPropsUBOName = stringIndexer().get("HillshadeEvaluatedPropsUBO"); +namespace { +const StringIdentity idHillshadeDrawableUBOName = stringIndexer().get("HillshadeDrawableUBO"); +const StringIdentity idHillshadeEvaluatedPropsUBOName = stringIndexer().get("HillshadeEvaluatedPropsUBO"); std::array getLatRange(const UnwrappedTileID& id) { const LatLng latlng0 = LatLng(id); @@ -27,10 +28,12 @@ std::array getLatRange(const UnwrappedTileID& id) { std::array getLight(const PaintParameters& parameters, const HillshadePaintProperties::PossiblyEvaluated& evaluated) { float azimuthal = util::deg2radf(evaluated.get()); - if (evaluated.get() == HillshadeIlluminationAnchorType::Viewport) + if (evaluated.get() == HillshadeIlluminationAnchorType::Viewport) { azimuthal = azimuthal - static_cast(parameters.state.getBearing()); + } return {{evaluated.get(), azimuthal}}; } +} // namespace void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; @@ -44,13 +47,16 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam const auto debugGroup = parameters.encoder->createDebugGroup(label.c_str()); #endif - if (!evaluatedPropsUniformBuffer) { - HillshadeEvaluatedPropsUBO evaluatedPropsUBO = {/* .highlight = */ evaluated.get(), - /* .shadow = */ evaluated.get(), - /* .accent = */ evaluated.get()}; - evaluatedPropsUniformBuffer = parameters.context.createUniformBuffer(&evaluatedPropsUBO, - sizeof(evaluatedPropsUBO)); - } + const auto getPropsBuffer = [&]() -> auto& { + if (!evaluatedPropsUniformBuffer || propertiesUpdated) { + const HillshadeEvaluatedPropsUBO evaluatedPropsUBO = { + /* .highlight = */ evaluated.get(), + /* .shadow = */ evaluated.get(), + /* .accent = */ evaluated.get()}; + parameters.context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &evaluatedPropsUBO); + } + return evaluatedPropsUniformBuffer; + }; visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { if (!drawable.getTileID() || !checkTweakDrawable(drawable)) { @@ -59,16 +65,18 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); - drawable.mutableUniformBuffers().addOrReplace(idHillshadeEvaluatedPropsUBOName, evaluatedPropsUniformBuffer); + auto& uniforms = drawable.mutableUniformBuffers(); + uniforms.addOrReplace(idHillshadeEvaluatedPropsUBOName, getPropsBuffer()); const auto matrix = getTileMatrix( tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, false, false, drawable, true); HillshadeDrawableUBO drawableUBO = {/* .matrix = */ util::cast(matrix), /* .latrange = */ getLatRange(tileID), /* .light = */ getLight(parameters, evaluated)}; - - drawable.mutableUniformBuffers().createOrUpdate(idHillshadeDrawableUBOName, &drawableUBO, parameters.context); + uniforms.createOrUpdate(idHillshadeDrawableUBOName, &drawableUBO, parameters.context); }); + + propertiesUpdated = false; } } // namespace mbgl diff --git a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp index 80aa6ff29af..4823460a50c 100644 --- a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp @@ -16,20 +16,21 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idHillshadePrepareDrawableUBOName = stringIndexer().get("HillshadePrepareDrawableUBO"); +namespace { +const StringIdentity idHillshadePrepareDrawableUBOName = stringIndexer().get("HillshadePrepareDrawableUBO"); -const std::array& getUnpackVector(Tileset::DEMEncoding encoding) { - // https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb - static const std::array unpackMapbox = {{6553.6f, 25.6f, 0.1f, 10000.0f}}; - // https://aws.amazon.com/public-datasets/terrain/ - static const std::array unpackTerrarium = {{256.0f, 1.0f, 1.0f / 256.0f, 32768.0f}}; +// https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb +constexpr std::array unpackMapbox = {{6553.6f, 25.6f, 0.1f, 10000.0f}}; - return encoding == Tileset::DEMEncoding::Terrarium ? unpackTerrarium : unpackMapbox; +// https://aws.amazon.com/public-datasets/terrain/ +constexpr std::array unpackTerrarium = {{256.0f, 1.0f, 1.0f / 256.0f, 32768.0f}}; + +constexpr const std::array& getUnpackVector(const Tileset::DEMEncoding encoding) { + return (encoding == Tileset::DEMEncoding::Terrarium) ? unpackTerrarium : unpackMapbox; } +} // namespace void HillshadePrepareLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { - // const auto& evaluated = static_cast(*evaluatedProperties).evaluated; - if (layerGroup.empty()) { return; } diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.cpp b/src/mbgl/renderer/layers/line_layer_tweaker.cpp index 57929104cf1..f24ceccf1b3 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.cpp @@ -38,13 +38,19 @@ static const StringIdentity idTexImageName = stringIndexer().get("u_image"); void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; + const auto zoom = parameters.state.getZoom(); const auto& evaluated = static_cast(*evaluatedProperties).evaluated; const auto& crossfade = static_cast(*evaluatedProperties).crossfade; - const auto zoom = parameters.state.getZoom(); + // Each property UBO is updated at most once if new evaluated properties were set + bool simplePropertiesUpdated = propertiesUpdated; + bool gradientPropertiesUpdated = propertiesUpdated; + bool patternPropertiesUpdated = propertiesUpdated; + bool sdfPropertiesUpdated = propertiesUpdated; + propertiesUpdated = false; const auto getLinePropsBuffer = [&]() { - if (!linePropertiesBuffer) { + if (!linePropertiesBuffer || simplePropertiesUpdated) { const LinePropertiesUBO linePropertiesUBO{ /*color =*/evaluated.get().constantOr(LineColor::defaultValue()), /*blur =*/evaluated.get().constantOr(LineBlur::defaultValue()), @@ -55,12 +61,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, 0, 0}; - linePropertiesBuffer = context.createUniformBuffer(&linePropertiesUBO, sizeof(linePropertiesUBO)); + context.emplaceOrUpdateUniformBuffer(linePropertiesBuffer, &linePropertiesUBO); + simplePropertiesUpdated = false; } return linePropertiesBuffer; }; const auto getLineGradientPropsBuffer = [&]() { - if (!lineGradientPropertiesBuffer) { + if (!lineGradientPropertiesBuffer || gradientPropertiesUpdated) { const LineGradientPropertiesUBO lineGradientPropertiesUBO{ /*blur =*/evaluated.get().constantOr(LineBlur::defaultValue()), /*opacity =*/evaluated.get().constantOr(LineOpacity::defaultValue()), @@ -70,13 +77,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, 0, 0}; - lineGradientPropertiesBuffer = context.createUniformBuffer(&lineGradientPropertiesUBO, - sizeof(lineGradientPropertiesUBO)); + context.emplaceOrUpdateUniformBuffer(lineGradientPropertiesBuffer, &lineGradientPropertiesUBO); + gradientPropertiesUpdated = false; } return lineGradientPropertiesBuffer; }; const auto getLinePatternPropsBuffer = [&]() { - if (!linePatternPropertiesBuffer) { + if (!linePatternPropertiesBuffer || patternPropertiesUpdated) { const LinePatternPropertiesUBO linePatternPropertiesUBO{ /*blur =*/evaluated.get().constantOr(LineBlur::defaultValue()), /*opacity =*/evaluated.get().constantOr(LineOpacity::defaultValue()), @@ -86,13 +93,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, 0, 0}; - linePatternPropertiesBuffer = context.createUniformBuffer(&linePatternPropertiesUBO, - sizeof(linePatternPropertiesUBO)); + context.emplaceOrUpdateUniformBuffer(linePatternPropertiesBuffer, &linePatternPropertiesUBO); + patternPropertiesUpdated = false; } return linePatternPropertiesBuffer; }; const auto getLineSDFPropsBuffer = [&]() { - if (!lineSDFPropertiesBuffer) { + if (!lineSDFPropertiesBuffer || sdfPropertiesUpdated) { const LineSDFPropertiesUBO lineSDFPropertiesUBO{ /*color =*/evaluated.get().constantOr(LineColor::defaultValue()), /*blur =*/evaluated.get().constantOr(LineBlur::defaultValue()), @@ -103,19 +110,15 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters /*floorwidth =*/evaluated.get().constantOr(LineFloorWidth::defaultValue()), 0, 0}; - lineSDFPropertiesBuffer = context.createUniformBuffer(&lineSDFPropertiesUBO, sizeof(lineSDFPropertiesUBO)); + context.emplaceOrUpdateUniformBuffer(lineSDFPropertiesBuffer, &lineSDFPropertiesUBO); + sdfPropertiesUpdated = false; } return lineSDFPropertiesBuffer; }; const LineDynamicUBO dynamicUBO = { /*units_to_pixels = */ {1.0f / parameters.pixelsToGLUnits[0], 1.0f / parameters.pixelsToGLUnits[1]}, 0, 0}; - - if (!dynamicBuffer) { - dynamicBuffer = parameters.context.createUniformBuffer(&dynamicUBO, sizeof(dynamicUBO)); - } else { - dynamicBuffer->update(&dynamicUBO, sizeof(dynamicUBO)); - } + context.emplaceOrUpdateUniformBuffer(dynamicBuffer, &dynamicUBO); visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { const auto shader = drawable.getShader(); diff --git a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp index eb91f380ec3..665562d1e69 100644 --- a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp @@ -23,6 +23,8 @@ void RasterLayerTweaker::execute([[maybe_unused]] LayerGroupBase& layerGroup, [[maybe_unused]] const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; + propertiesUpdated = false; + visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { if (!checkTweakDrawable(drawable)) { return; diff --git a/src/mbgl/renderer/layers/render_background_layer.cpp b/src/mbgl/renderer/layers/render_background_layer.cpp index 5fecbcd64d4..437c92dcefb 100644 --- a/src/mbgl/renderer/layers/render_background_layer.cpp +++ b/src/mbgl/renderer/layers/render_background_layer.cpp @@ -66,9 +66,8 @@ void RenderBackgroundLayer::evaluate(const PropertyEvaluationParameters& paramet evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER - if (layerGroup) { - auto newTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(layerTweaker, std::move(newTweaker), {layerGroup}); + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); } #endif } diff --git a/src/mbgl/renderer/layers/render_circle_layer.cpp b/src/mbgl/renderer/layers/render_circle_layer.cpp index b9a1a4371aa..a1343eeec73 100644 --- a/src/mbgl/renderer/layers/render_circle_layer.cpp +++ b/src/mbgl/renderer/layers/render_circle_layer.cpp @@ -86,9 +86,8 @@ void RenderCircleLayer::evaluate(const PropertyEvaluationParameters& parameters) evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER - if (layerGroup) { - auto newTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(layerTweaker, std::move(newTweaker), {layerGroup}); + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); } #endif // MLN_DRAWABLE_RENDERER } diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp index f3aebbb2a15..296e73d51e8 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -75,9 +75,8 @@ void RenderFillExtrusionLayer::evaluate(const PropertyEvaluationParameters& para evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER - if (layerGroup) { - auto newTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(layerTweaker, std::move(newTweaker), {layerGroup}); + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); } #endif // MLN_DRAWABLE_RENDERER } diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp index cc69d3e0994..c5af6665eb1 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.cpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -53,6 +53,15 @@ void RenderHeatmapLayer::transition(const TransitionParameters& parameters) { updateColorRamp(); } +#if MLN_DRAWABLE_RENDERER +void RenderHeatmapLayer::layerChanged(const TransitionParameters& parameters, + const Immutable& impl, + UniqueChangeRequestVec& changes) { + RenderLayer::layerChanged(parameters, impl, changes); + textureTweaker.reset(); +} +#endif + void RenderHeatmapLayer::evaluate(const PropertyEvaluationParameters& parameters) { auto properties = makeMutable(staticImmutableCast(baseImpl), unevaluated.evaluate(parameters)); @@ -63,16 +72,11 @@ void RenderHeatmapLayer::evaluate(const PropertyEvaluationParameters& parameters evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER - if (layerGroup) { - auto newTextureTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(textureTweaker, std::move(newTextureTweaker), {layerGroup}); + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); } - - if (renderTarget) { - if (auto tileLayerGroup = renderTarget->getLayerGroup(0)) { - auto newTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(layerTweaker, std::move(newTweaker), {std::move(tileLayerGroup)}); - } + if (textureTweaker) { + textureTweaker->updateProperties(evaluatedProperties); } #endif } @@ -499,8 +503,6 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, // TODO: Don't rebuild drawables every time textureLayerGroup->clearDrawables(); - std::unique_ptr heatmapTextureBuilder; - if (!sharedTextureVertices) { sharedTextureVertices = std::make_shared(RenderStaticData::heatmapTextureVertices()); } @@ -515,7 +517,7 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, gfx::AttributeDataType::Short2); } - heatmapTextureBuilder = context.createDrawableBuilder("heatmapTexture"); + auto heatmapTextureBuilder = context.createDrawableBuilder("heatmapTexture"); heatmapTextureBuilder->setShader(heatmapTextureShader); heatmapTextureBuilder->setEnableDepth(false); heatmapTextureBuilder->setColorMode(gfx::ColorMode::alphaBlended()); diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.hpp b/src/mbgl/renderer/layers/render_heatmap_layer.hpp index 1fecb7f0a62..7f04191925b 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.hpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp @@ -56,6 +56,10 @@ class RenderHeatmapLayer final : public RenderLayer { void updateColorRamp(); #if MLN_DRAWABLE_RENDERER + void layerChanged(const TransitionParameters& parameters, + const Immutable& impl, + UniqueChangeRequestVec& changes) override; + /// Remove all drawables for the tile from the layer group /// @return The number of drawables actually removed. std::size_t removeTile(RenderPass, const OverscaledTileID&) override; diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index a0ab12ac628..5d3151e7ed6 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -64,6 +64,15 @@ void RenderHillshadeLayer::transition(const TransitionParameters& parameters) { unevaluated = impl_cast(baseImpl).paint.transitioned(parameters, std::move(unevaluated)); } +#if MLN_DRAWABLE_RENDERER +void RenderHillshadeLayer::layerChanged(const TransitionParameters& parameters, + const Immutable& impl, + UniqueChangeRequestVec& changes) { + RenderLayer::layerChanged(parameters, impl, changes); + prepareLayerTweaker.reset(); +} +#endif + void RenderHillshadeLayer::evaluate(const PropertyEvaluationParameters& parameters) { auto properties = makeMutable(staticImmutableCast(baseImpl), unevaluated.evaluate(parameters)); @@ -73,25 +82,11 @@ void RenderHillshadeLayer::evaluate(const PropertyEvaluationParameters& paramete properties->renderPasses = mbgl::underlying_type(passes); evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER - if (layerGroup) { - auto newTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(layerTweaker, std::move(newTweaker), {layerGroup}); + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); } - - if (!activatedRenderTargets.empty()) { - auto newTweaker2 = std::make_shared(getID(), evaluatedProperties); - - std::vector groups; - for (const auto& target : activatedRenderTargets) { - if (const auto& group = target->getLayerGroup(0)) { - groups.push_back(group); - } - } - if (groups.empty()) { - prepareLayerTweaker = newTweaker2; - } else { - replaceTweaker(prepareLayerTweaker, std::move(newTweaker2), groups); - } + if (prepareLayerTweaker) { + prepareLayerTweaker->updateProperties(evaluatedProperties); } #endif } diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.hpp b/src/mbgl/renderer/layers/render_hillshade_layer.hpp index 0c7faadbad6..83fa357ed7b 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.hpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.hpp @@ -45,6 +45,11 @@ class RenderHillshadeLayer : public RenderLayer { #if MLN_DRAWABLE_RENDERER void updateLayerTweaker(); + + void layerChanged(const TransitionParameters& parameters, + const Immutable& impl, + UniqueChangeRequestVec& changes) override; + #endif // MLN_DRAWABLE_RENDERER void prepare(const LayerPrepareParameters&) override; diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index b7bb9b95768..c3a69b9eb6a 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -81,9 +81,8 @@ void RenderLineLayer::evaluate(const PropertyEvaluationParameters& parameters) { evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER - if (layerGroup) { - auto newTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(layerTweaker, std::move(newTweaker), {layerGroup}); + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); } #endif } diff --git a/src/mbgl/renderer/layers/render_raster_layer.cpp b/src/mbgl/renderer/layers/render_raster_layer.cpp index e0831cddf44..51f1920aa37 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.cpp +++ b/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -54,9 +54,8 @@ void RenderRasterLayer::evaluate(const PropertyEvaluationParameters& parameters) evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER - if (layerGroup) { - auto newTweaker = std::make_shared(getID(), evaluatedProperties); - replaceTweaker(layerTweaker, std::move(newTweaker), {layerGroup, imageLayerGroup}); + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); } #endif } diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp index 4e1f3e2082f..5a52417694f 100644 --- a/src/mbgl/renderer/render_layer.cpp +++ b/src/mbgl/renderer/render_layer.cpp @@ -70,31 +70,6 @@ std::optional RenderLayer::getSolidBackground() const { } #if MLN_DRAWABLE_RENDERER -void RenderLayer::replaceTweaker(LayerTweakerPtr& curTweaker, - LayerTweakerPtr newTweaker, - const std::vector& layerGroups) { - const auto prevTweaker = curTweaker; - - // We need to re-create the tweaker because it doesn't yet support modifying evaluated - // properties, but we don't want to stop updating drawables (as in the `layerChanged` case) - // so we need to update the tweaker reference on the outstanding drawables so that they - // pass the check in `updateExisting`. - // TODO: Once the tweaker doesn't need to be re-created on each property evaluation, this won't be needed. - for (const auto& group : layerGroups) { - if (group) { - group->addLayerTweaker(newTweaker); - - visitLayerGroupDrawables(*group, [&](gfx::Drawable& drawable) { - if (drawable.getLayerTweaker() == prevTweaker) { - drawable.setLayerTweaker(newTweaker); - } - }); - } - } - - curTweaker = std::move(newTweaker); -} - void RenderLayer::layerChanged(const TransitionParameters&, const Immutable&, UniqueChangeRequestVec&) { diff --git a/src/mbgl/renderer/render_layer.hpp b/src/mbgl/renderer/render_layer.hpp index 8d2ae82fac8..ba1e06cdc41 100644 --- a/src/mbgl/renderer/render_layer.hpp +++ b/src/mbgl/renderer/render_layer.hpp @@ -266,11 +266,6 @@ class RenderLayer { /// unchanged bool setRenderTileBucketID(const OverscaledTileID&, util::SimpleIdentity bucketID); - /// Update the layer tweaker and drawables which reference it - static void replaceTweaker(LayerTweakerPtr& toReplace, - LayerTweakerPtr newTweaker, - const std::vector&); - #endif // MLN_DRAWABLE_RENDERER static bool applyColorRamp(const style::ColorRampPropertyValue&, PremultipliedImage&); From 8ce238c85708a0d425f9056e1297176e6fda7a58 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Thu, 25 Jan 2024 10:58:23 -0800 Subject: [PATCH 41/96] Eliminate an extra vector allocation for many attributes (#2049) --- include/mbgl/gfx/vertex_attribute.hpp | 12 ++-- include/mbgl/shaders/mtl/shader_program.hpp | 3 +- src/mbgl/gfx/vertex_attribute.cpp | 15 +++-- src/mbgl/shaders/mtl/background.cpp | 2 +- src/mbgl/shaders/mtl/background_pattern.cpp | 2 +- src/mbgl/shaders/mtl/circle.cpp | 16 +++--- src/mbgl/shaders/mtl/clipping_mask.cpp | 2 +- src/mbgl/shaders/mtl/collision_box.cpp | 10 ++-- src/mbgl/shaders/mtl/collision_circle.cpp | 8 +-- src/mbgl/shaders/mtl/debug.cpp | 2 +- src/mbgl/shaders/mtl/fill.cpp | 28 +++++----- src/mbgl/shaders/mtl/fill_extrusion.cpp | 10 ++-- .../shaders/mtl/fill_extrusion_pattern.cpp | 12 ++-- src/mbgl/shaders/mtl/heatmap.cpp | 6 +- src/mbgl/shaders/mtl/heatmap_texture.cpp | 2 +- src/mbgl/shaders/mtl/hillshade.cpp | 4 +- src/mbgl/shaders/mtl/hillshade_prepare.cpp | 4 +- src/mbgl/shaders/mtl/line.cpp | 56 +++++++++---------- src/mbgl/shaders/mtl/line_gradient.cpp | 14 ++--- src/mbgl/shaders/mtl/raster.cpp | 4 +- src/mbgl/shaders/mtl/shader_program.cpp | 8 +-- src/mbgl/shaders/mtl/symbol_icon.cpp | 12 ++-- src/mbgl/shaders/mtl/symbol_sdf.cpp | 20 +++---- src/mbgl/shaders/mtl/symbol_text_and_icon.cpp | 18 +++--- 24 files changed, 135 insertions(+), 135 deletions(-) diff --git a/include/mbgl/gfx/vertex_attribute.hpp b/include/mbgl/gfx/vertex_attribute.hpp index 0ecec50cc89..741e1ee4895 100644 --- a/include/mbgl/gfx/vertex_attribute.hpp +++ b/include/mbgl/gfx/vertex_attribute.hpp @@ -33,7 +33,6 @@ class VertexAttributeArray; class VertexVectorBase; using UniqueVertexAttribute = std::unique_ptr; -using UniqueVertexAttributeArray = std::unique_ptr; class VertexAttribute { public: @@ -304,17 +303,18 @@ class VertexAttributeArray { /// Add a new attribute element. /// Returns a pointer to the new element on success, or null if the attribute already exists. /// The result is valid only until the next non-const method call on this class. + /// @param count Number of items, zero for shared data const std::unique_ptr& add(const StringIdentity id, int index = -1, - AttributeDataType = AttributeDataType::Invalid, - std::size_t count = 1); + AttributeDataType type = AttributeDataType::Invalid, + std::size_t count = 0); /// Add a new attribute element if it doesn't already exist. /// Returns a pointer to the new element on success, or null if the type or count conflict with an existing entry. /// The result is valid only until the next non-const method call on this class. /// @param index index to match, or -1 for any /// @param type type to match, or `Invalid` for any - /// @param count type to match, or 0 for any + /// @param count Number of items, zero for shared data const std::unique_ptr& getOrAdd(const StringIdentity id, int index = -1, AttributeDataType type = AttributeDataType::Invalid, @@ -407,7 +407,9 @@ class VertexAttributeArray { // Apply the property, or add it to the uniforms collection if it's constant. if (!isConstant && binder->getVertexCount() > 0) { using Attribute = typename DataDrivenPaintProperty::Attribute; - applyPaintProperty(attrIndex, getOrAdd(*attributeNameID), binder); + const auto& attr = getOrAdd( + *attributeNameID, /*index=*/-1, /*type=*/gfx::AttributeDataType::Invalid, /*count=*/0); + applyPaintProperty(attrIndex, attr, binder); } else { propertiesAsUniforms.emplace(*attributeNameID); } diff --git a/include/mbgl/shaders/mtl/shader_program.hpp b/include/mbgl/shaders/mtl/shader_program.hpp index 357a32549f8..7a63be32345 100644 --- a/include/mbgl/shaders/mtl/shader_program.hpp +++ b/include/mbgl/shaders/mtl/shader_program.hpp @@ -14,10 +14,9 @@ namespace mbgl { namespace shaders { struct AttributeInfo { - AttributeInfo(std::size_t index, gfx::AttributeDataType dataType, std::size_t count, std::string_view name); + AttributeInfo(std::size_t index, gfx::AttributeDataType dataType, std::string_view name); std::size_t index; gfx::AttributeDataType dataType; - std::size_t count; std::string_view name; StringIdentity nameID; }; diff --git a/src/mbgl/gfx/vertex_attribute.cpp b/src/mbgl/gfx/vertex_attribute.cpp index 7d3e4c4ac86..111e3e0b424 100644 --- a/src/mbgl/gfx/vertex_attribute.cpp +++ b/src/mbgl/gfx/vertex_attribute.cpp @@ -116,13 +116,16 @@ const std::unique_ptr& VertexAttributeArray::getOrAdd(const Str AttributeDataType dataType, std::size_t count) { const auto result = attrs.insert(std::make_pair(id, std::unique_ptr())); - if (auto& attr = result.first->second; result.second) { - return attr = create(index, dataType, count); - } else if ((dataType == AttributeDataType::Invalid || attr->getDataType() == dataType) && - (index < 0 || attr->getIndex() == index) && (count == 0 || attr->getCount() == count)) { - return attr; + auto& attr = result.first->second; + if (result.second) { + // inserted + attr = create(index, dataType, count); + } else { + // already present + assert(dataType == AttributeDataType::Invalid || attr->getDataType() == dataType); + assert((index < 0 || attr->getIndex() == index) && (count == 0 || attr->getCount() == count)); } - return nullref; + return attr; } std::size_t VertexAttributeArray::getTotalSize() const { diff --git a/src/mbgl/shaders/mtl/background.cpp b/src/mbgl/shaders/mtl/background.cpp index 53f79a122e9..a542af6afc4 100644 --- a/src/mbgl/shaders/mtl/background.cpp +++ b/src/mbgl/shaders/mtl/background.cpp @@ -5,7 +5,7 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, 1, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Float3, "a_pos"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{1, true, false, sizeof(BackgroundDrawableUBO), "BackgroundDrawableUBO"}, diff --git a/src/mbgl/shaders/mtl/background_pattern.cpp b/src/mbgl/shaders/mtl/background_pattern.cpp index f7429e5283a..44ebf54b794 100644 --- a/src/mbgl/shaders/mtl/background_pattern.cpp +++ b/src/mbgl/shaders/mtl/background_pattern.cpp @@ -6,7 +6,7 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, 1, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Float3, "a_pos"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/circle.cpp b/src/mbgl/shaders/mtl/circle.cpp index 757f001b338..949a05c7b65 100644 --- a/src/mbgl/shaders/mtl/circle.cpp +++ b/src/mbgl/shaders/mtl/circle.cpp @@ -4,14 +4,14 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float4, 1, "a_color"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, 1, "a_radius"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, 1, "a_blur"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, 1, "a_opacity"}, - AttributeInfo{5, gfx::AttributeDataType::Float4, 1, "a_stroke_color"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, 1, "a_stroke_width"}, - AttributeInfo{7, gfx::AttributeDataType::Float2, 1, "a_stroke_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Float4, "a_color"}, + AttributeInfo{2, gfx::AttributeDataType::Float2, "a_radius"}, + AttributeInfo{3, gfx::AttributeDataType::Float2, "a_blur"}, + AttributeInfo{4, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{5, gfx::AttributeDataType::Float4, "a_stroke_color"}, + AttributeInfo{6, gfx::AttributeDataType::Float2, "a_stroke_width"}, + AttributeInfo{7, gfx::AttributeDataType::Float2, "a_stroke_opacity"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{8, true, false, sizeof(CircleDrawableUBO), "CircleDrawableUBO"}, diff --git a/src/mbgl/shaders/mtl/clipping_mask.cpp b/src/mbgl/shaders/mtl/clipping_mask.cpp index 107537d95a8..28585accc3f 100644 --- a/src/mbgl/shaders/mtl/clipping_mask.cpp +++ b/src/mbgl/shaders/mtl/clipping_mask.cpp @@ -6,7 +6,7 @@ namespace shaders { using ShaderType = ShaderSource; const std::array ShaderType::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, 1, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Float3, "a_pos"}, }; const std::array ShaderType::uniforms = { UniformBlockInfo{1, true, false, sizeof(ClipUBO), "ClipUBO"}, diff --git a/src/mbgl/shaders/mtl/collision_box.cpp b/src/mbgl/shaders/mtl/collision_box.cpp index dfc1a034298..205f7fe6e9f 100644 --- a/src/mbgl/shaders/mtl/collision_box.cpp +++ b/src/mbgl/shaders/mtl/collision_box.cpp @@ -4,11 +4,11 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, 1, "a_anchor_pos"}, - AttributeInfo{2, gfx::AttributeDataType::Short2, 1, "a_extrude"}, - AttributeInfo{3, gfx::AttributeDataType::UShort2, 1, "a_placed"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, 1, "a_shift"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Short2, "a_anchor_pos"}, + AttributeInfo{2, gfx::AttributeDataType::Short2, "a_extrude"}, + AttributeInfo{3, gfx::AttributeDataType::UShort2, "a_placed"}, + AttributeInfo{4, gfx::AttributeDataType::Float2, "a_shift"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{5, true, true, sizeof(CollisionUBO), "CollisionBoxUBO"}, diff --git a/src/mbgl/shaders/mtl/collision_circle.cpp b/src/mbgl/shaders/mtl/collision_circle.cpp index 5d43ffeb691..b0c36b07546 100644 --- a/src/mbgl/shaders/mtl/collision_circle.cpp +++ b/src/mbgl/shaders/mtl/collision_circle.cpp @@ -5,10 +5,10 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, 1, "a_anchor_pos"}, - AttributeInfo{2, gfx::AttributeDataType::Short2, 1, "a_extrude"}, - AttributeInfo{3, gfx::AttributeDataType::UShort2, 1, "a_placed"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Short2, "a_anchor_pos"}, + AttributeInfo{2, gfx::AttributeDataType::Short2, "a_extrude"}, + AttributeInfo{3, gfx::AttributeDataType::UShort2, "a_placed"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/debug.cpp b/src/mbgl/shaders/mtl/debug.cpp index 28d9737b8c6..f47f5ff1ae8 100644 --- a/src/mbgl/shaders/mtl/debug.cpp +++ b/src/mbgl/shaders/mtl/debug.cpp @@ -4,7 +4,7 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{1, true, true, sizeof(DebugUBO), "DebugUBO"}, diff --git a/src/mbgl/shaders/mtl/fill.cpp b/src/mbgl/shaders/mtl/fill.cpp index 2e656fb0c50..5fcee811486 100644 --- a/src/mbgl/shaders/mtl/fill.cpp +++ b/src/mbgl/shaders/mtl/fill.cpp @@ -4,9 +4,9 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float4, 1, "a_color"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, 1, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Float4, "a_color"}, + AttributeInfo{2, gfx::AttributeDataType::Float2, "a_opacity"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{3, true, false, sizeof(FillDrawableUBO), "FillDrawableUBO"}, @@ -16,9 +16,9 @@ const std::array ShaderSource ShaderSource::textures = {}; const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float4, 1, "a_outline_color"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, 1, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Float4, "a_outline_color"}, + AttributeInfo{2, gfx::AttributeDataType::Float2, "a_opacity"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{3, true, false, sizeof(FillOutlineDrawableUBO), "FillOutlineDrawableUBO"}, @@ -28,10 +28,10 @@ const std::array ShaderSource ShaderSource::textures = {}; const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, 1, "a_pattern_from"}, - AttributeInfo{2, gfx::AttributeDataType::UShort4, 1, "a_pattern_to"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, 1, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_pattern_from"}, + AttributeInfo{2, gfx::AttributeDataType::UShort4, "a_pattern_to"}, + AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{4, true, true, sizeof(FillPatternDrawableUBO), "FillPatternDrawableUBO"}, @@ -45,10 +45,10 @@ const std::array ShaderSource ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, 1, "a_pattern_from"}, - AttributeInfo{2, gfx::AttributeDataType::UShort4, 1, "a_pattern_to"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, 1, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_pattern_from"}, + AttributeInfo{2, gfx::AttributeDataType::UShort4, "a_pattern_to"}, + AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/fill_extrusion.cpp b/src/mbgl/shaders/mtl/fill_extrusion.cpp index ba1c1671e47..3b80c261aaa 100644 --- a/src/mbgl/shaders/mtl/fill_extrusion.cpp +++ b/src/mbgl/shaders/mtl/fill_extrusion.cpp @@ -4,11 +4,11 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short4, 1, "a_normal_ed"}, - AttributeInfo{2, gfx::AttributeDataType::Float4, 1, "a_color"}, - AttributeInfo{3, gfx::AttributeDataType::Float, 1, "a_base"}, - AttributeInfo{4, gfx::AttributeDataType::Float, 1, "a_height"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Short4, "a_normal_ed"}, + AttributeInfo{2, gfx::AttributeDataType::Float4, "a_color"}, + AttributeInfo{3, gfx::AttributeDataType::Float, "a_base"}, + AttributeInfo{4, gfx::AttributeDataType::Float, "a_height"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp index 6f235b6085a..6e5a78e2803 100644 --- a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp +++ b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp @@ -6,12 +6,12 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short4, 1, "a_normal_ed"}, - AttributeInfo{2, gfx::AttributeDataType::Float, 1, "a_base"}, - AttributeInfo{3, gfx::AttributeDataType::Float, 1, "a_height"}, - AttributeInfo{4, gfx::AttributeDataType::UShort4, 1, "a_pattern_from"}, - AttributeInfo{5, gfx::AttributeDataType::UShort4, 1, "a_pattern_to"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Short4, "a_normal_ed"}, + AttributeInfo{2, gfx::AttributeDataType::Float, "a_base"}, + AttributeInfo{3, gfx::AttributeDataType::Float, "a_height"}, + AttributeInfo{4, gfx::AttributeDataType::UShort4, "a_pattern_from"}, + AttributeInfo{5, gfx::AttributeDataType::UShort4, "a_pattern_to"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/heatmap.cpp b/src/mbgl/shaders/mtl/heatmap.cpp index a4590017806..23f179b5ce1 100644 --- a/src/mbgl/shaders/mtl/heatmap.cpp +++ b/src/mbgl/shaders/mtl/heatmap.cpp @@ -4,9 +4,9 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float2, 1, "a_weight"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, 1, "a_radius"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Float2, "a_weight"}, + AttributeInfo{2, gfx::AttributeDataType::Float2, "a_radius"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{3, true, false, sizeof(HeatmapDrawableUBO), "HeatmapDrawableUBO"}, diff --git a/src/mbgl/shaders/mtl/heatmap_texture.cpp b/src/mbgl/shaders/mtl/heatmap_texture.cpp index 6d9546363d0..689ba08fe07 100644 --- a/src/mbgl/shaders/mtl/heatmap_texture.cpp +++ b/src/mbgl/shaders/mtl/heatmap_texture.cpp @@ -5,7 +5,7 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/hillshade.cpp b/src/mbgl/shaders/mtl/hillshade.cpp index a7b0f32a50f..88713ea10ac 100644 --- a/src/mbgl/shaders/mtl/hillshade.cpp +++ b/src/mbgl/shaders/mtl/hillshade.cpp @@ -4,8 +4,8 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, 1, "a_texture_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Short2, "a_texture_pos"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{2, true, true, sizeof(HillshadeDrawableUBO), "HillshadeDrawableUBO"}, diff --git a/src/mbgl/shaders/mtl/hillshade_prepare.cpp b/src/mbgl/shaders/mtl/hillshade_prepare.cpp index 6ae47049866..d4b6cb301bc 100644 --- a/src/mbgl/shaders/mtl/hillshade_prepare.cpp +++ b/src/mbgl/shaders/mtl/hillshade_prepare.cpp @@ -5,8 +5,8 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, 1, "a_texture_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Short2, "a_texture_pos"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/line.cpp b/src/mbgl/shaders/mtl/line.cpp index 5de6346307d..f99d466fcb2 100644 --- a/src/mbgl/shaders/mtl/line.cpp +++ b/src/mbgl/shaders/mtl/line.cpp @@ -4,14 +4,14 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, 1, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float4, 1, "a_color"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, 1, "a_blur"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, 1, "a_opacity"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, 1, "a_gapwidth"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, 1, "a_offset"}, - AttributeInfo{7, gfx::AttributeDataType::Float2, 1, "a_width"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, + AttributeInfo{2, gfx::AttributeDataType::Float4, "a_color"}, + AttributeInfo{3, gfx::AttributeDataType::Float2, "a_blur"}, + AttributeInfo{4, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{5, gfx::AttributeDataType::Float2, "a_gapwidth"}, + AttributeInfo{6, gfx::AttributeDataType::Float2, "a_offset"}, + AttributeInfo{7, gfx::AttributeDataType::Float2, "a_width"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{8, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, @@ -22,15 +22,15 @@ const std::array ShaderSource ShaderSource::textures = {}; const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, 1, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, 1, "a_blur"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, 1, "a_opacity"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, 1, "a_gapwidth"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, 1, "a_offset"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, 1, "a_width"}, - AttributeInfo{7, gfx::AttributeDataType::UShort4, 1, "a_pattern_from"}, - AttributeInfo{8, gfx::AttributeDataType::UShort4, 1, "a_pattern_to"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, + AttributeInfo{2, gfx::AttributeDataType::Float2, "a_blur"}, + AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{4, gfx::AttributeDataType::Float2, "a_gapwidth"}, + AttributeInfo{5, gfx::AttributeDataType::Float2, "a_offset"}, + AttributeInfo{6, gfx::AttributeDataType::Float2, "a_width"}, + AttributeInfo{7, gfx::AttributeDataType::UShort4, "a_pattern_from"}, + AttributeInfo{8, gfx::AttributeDataType::UShort4, "a_pattern_to"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, @@ -44,15 +44,15 @@ const std::array ShaderSource ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, 1, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float4, 1, "a_color"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, 1, "a_blur"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, 1, "a_opacity"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, 1, "a_gapwidth"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, 1, "a_offset"}, - AttributeInfo{7, gfx::AttributeDataType::Float2, 1, "a_width"}, - AttributeInfo{8, gfx::AttributeDataType::Float2, 1, "a_floorwidth"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, + AttributeInfo{2, gfx::AttributeDataType::Float4, "a_color"}, + AttributeInfo{3, gfx::AttributeDataType::Float2, "a_blur"}, + AttributeInfo{4, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{5, gfx::AttributeDataType::Float2, "a_gapwidth"}, + AttributeInfo{6, gfx::AttributeDataType::Float2, "a_offset"}, + AttributeInfo{7, gfx::AttributeDataType::Float2, "a_width"}, + AttributeInfo{8, gfx::AttributeDataType::Float2, "a_floorwidth"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, @@ -65,8 +65,8 @@ const std::array ShaderSource ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, 1, "a_data"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{2, true, true, sizeof(LineBasicUBO), "LineBasicUBO"}, diff --git a/src/mbgl/shaders/mtl/line_gradient.cpp b/src/mbgl/shaders/mtl/line_gradient.cpp index f0c8d9eac0d..e981fe15719 100644 --- a/src/mbgl/shaders/mtl/line_gradient.cpp +++ b/src/mbgl/shaders/mtl/line_gradient.cpp @@ -4,13 +4,13 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, 1, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, 1, "a_blur"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, 1, "a_opacity"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, 1, "a_gapwidth"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, 1, "a_offset"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, 1, "a_width"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, + AttributeInfo{2, gfx::AttributeDataType::Float2, "a_blur"}, + AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{4, gfx::AttributeDataType::Float2, "a_gapwidth"}, + AttributeInfo{5, gfx::AttributeDataType::Float2, "a_offset"}, + AttributeInfo{6, gfx::AttributeDataType::Float2, "a_width"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{7, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, diff --git a/src/mbgl/shaders/mtl/raster.cpp b/src/mbgl/shaders/mtl/raster.cpp index c0ba8042248..0b74b219509 100644 --- a/src/mbgl/shaders/mtl/raster.cpp +++ b/src/mbgl/shaders/mtl/raster.cpp @@ -4,8 +4,8 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, 1, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, 1, "a_texture_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Short2, "a_texture_pos"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{2, true, true, sizeof(RasterDrawableUBO), "RasterDrawableUBO"}, diff --git a/src/mbgl/shaders/mtl/shader_program.cpp b/src/mbgl/shaders/mtl/shader_program.cpp index 01d90cfec26..bcb7df20430 100644 --- a/src/mbgl/shaders/mtl/shader_program.cpp +++ b/src/mbgl/shaders/mtl/shader_program.cpp @@ -24,13 +24,9 @@ using namespace std::string_literals; namespace mbgl { -shaders::AttributeInfo::AttributeInfo(std::size_t index_, - gfx::AttributeDataType dataType_, - std::size_t count_, - std::string_view name_) +shaders::AttributeInfo::AttributeInfo(std::size_t index_, gfx::AttributeDataType dataType_, std::string_view name_) : index(index_), dataType(dataType_), - count(count_), name(name_), nameID(stringIndexer().get(name_)) {} @@ -208,7 +204,7 @@ void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) { [&](auto, const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); uniformBlocks.visit([&](auto, const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); #endif - vertexAttributes.add(stringIndexer().get(info.name), index, info.dataType, info.count); + vertexAttributes.add(stringIndexer().get(info.name), index, info.dataType, 1); } void ShaderProgram::initUniformBlock(const shaders::UniformBlockInfo& info) { diff --git a/src/mbgl/shaders/mtl/symbol_icon.cpp b/src/mbgl/shaders/mtl/symbol_icon.cpp index a84c029f641..e1b77655688 100644 --- a/src/mbgl/shaders/mtl/symbol_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_icon.cpp @@ -5,14 +5,14 @@ namespace shaders { const std::array ShaderSource::attributes = { // always attributes - AttributeInfo{0, gfx::AttributeDataType::Short4, 1, "a_pos_offset"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, 1, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Short4, 1, "a_pixeloffset"}, - AttributeInfo{3, gfx::AttributeDataType::Float3, 1, "a_projected_pos"}, - AttributeInfo{4, gfx::AttributeDataType::Float, 1, "a_fade_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short4, "a_pos_offset"}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_data"}, + AttributeInfo{2, gfx::AttributeDataType::Short4, "a_pixeloffset"}, + AttributeInfo{3, gfx::AttributeDataType::Float3, "a_projected_pos"}, + AttributeInfo{4, gfx::AttributeDataType::Float, "a_fade_opacity"}, // sometimes uniforms - AttributeInfo{5, gfx::AttributeDataType::Float, 1, "a_opacity"}, + AttributeInfo{5, gfx::AttributeDataType::Float, "a_opacity"}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{8, true, true, sizeof(SymbolDrawableUBO), "SymbolDrawableUBO"}, diff --git a/src/mbgl/shaders/mtl/symbol_sdf.cpp b/src/mbgl/shaders/mtl/symbol_sdf.cpp index cd4b8a6d4b8..74998b262ea 100644 --- a/src/mbgl/shaders/mtl/symbol_sdf.cpp +++ b/src/mbgl/shaders/mtl/symbol_sdf.cpp @@ -6,18 +6,18 @@ namespace shaders { const std::array ShaderSource::attributes = { // always attributes - AttributeInfo{0, gfx::AttributeDataType::Short4, 1, "a_pos_offset"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, 1, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Short4, 1, "a_pixeloffset"}, - AttributeInfo{3, gfx::AttributeDataType::Float3, 1, "a_projected_pos"}, - AttributeInfo{4, gfx::AttributeDataType::Float, 1, "a_fade_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short4, "a_pos_offset"}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_data"}, + AttributeInfo{2, gfx::AttributeDataType::Short4, "a_pixeloffset"}, + AttributeInfo{3, gfx::AttributeDataType::Float3, "a_projected_pos"}, + AttributeInfo{4, gfx::AttributeDataType::Float, "a_fade_opacity"}, // sometimes uniforms - AttributeInfo{5, gfx::AttributeDataType::Float4, 1, "a_fill_color"}, - AttributeInfo{6, gfx::AttributeDataType::Float4, 1, "a_halo_color"}, - AttributeInfo{7, gfx::AttributeDataType::Float, 1, "a_opacity"}, - AttributeInfo{8, gfx::AttributeDataType::Float, 1, "a_halo_width"}, - AttributeInfo{9, gfx::AttributeDataType::Float, 1, "a_halo_blur"}, + AttributeInfo{5, gfx::AttributeDataType::Float4, "a_fill_color"}, + AttributeInfo{6, gfx::AttributeDataType::Float4, "a_halo_color"}, + AttributeInfo{7, gfx::AttributeDataType::Float, "a_opacity"}, + AttributeInfo{8, gfx::AttributeDataType::Float, "a_halo_width"}, + AttributeInfo{9, gfx::AttributeDataType::Float, "a_halo_blur"}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp index 408a83033e9..9c6ebdeb20e 100644 --- a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp @@ -6,17 +6,17 @@ namespace shaders { const std::array ShaderSource::attributes = { // always attributes - AttributeInfo{0, gfx::AttributeDataType::Short4, 1, "a_pos_offset"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, 1, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float3, 1, "a_projected_pos"}, - AttributeInfo{3, gfx::AttributeDataType::Float, 1, "a_fade_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short4, "a_pos_offset"}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_data"}, + AttributeInfo{2, gfx::AttributeDataType::Float3, "a_projected_pos"}, + AttributeInfo{3, gfx::AttributeDataType::Float, "a_fade_opacity"}, // sometimes uniforms - AttributeInfo{4, gfx::AttributeDataType::Float4, 1, "a_fill_color"}, - AttributeInfo{5, gfx::AttributeDataType::Float4, 1, "a_halo_color"}, - AttributeInfo{6, gfx::AttributeDataType::Float, 1, "a_opacity"}, - AttributeInfo{7, gfx::AttributeDataType::Float, 1, "a_halo_width"}, - AttributeInfo{8, gfx::AttributeDataType::Float, 1, "a_halo_blur"}, + AttributeInfo{4, gfx::AttributeDataType::Float4, "a_fill_color"}, + AttributeInfo{5, gfx::AttributeDataType::Float4, "a_halo_color"}, + AttributeInfo{6, gfx::AttributeDataType::Float, "a_opacity"}, + AttributeInfo{7, gfx::AttributeDataType::Float, "a_halo_width"}, + AttributeInfo{8, gfx::AttributeDataType::Float, "a_halo_blur"}, }; const std::array ShaderSource::uniforms = { From d7f2431524ea9c77aaeb9e18ba05c548e80ffb15 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Thu, 25 Jan 2024 14:44:40 -0800 Subject: [PATCH 42/96] =?UTF-8?q?Don't=20save=20an=20extra=20copy=20of=20p?= =?UTF-8?q?roperties-as-uniforms=20set=20with=20symbol=20da=E2=80=A6=20(#2?= =?UTF-8?q?054)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/mbgl/gfx/vertex_attribute.hpp | 30 ++++++++++++---- src/mbgl/gfx/symbol_drawable_data.hpp | 7 ++-- .../renderer/layers/render_symbol_layer.cpp | 35 ++++--------------- 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/include/mbgl/gfx/vertex_attribute.hpp b/include/mbgl/gfx/vertex_attribute.hpp index 741e1ee4895..dae588c77a0 100644 --- a/include/mbgl/gfx/vertex_attribute.hpp +++ b/include/mbgl/gfx/vertex_attribute.hpp @@ -359,9 +359,25 @@ class VertexAttributeArray { VertexAttributeArray& operator=(VertexAttributeArray&&); VertexAttributeArray& operator=(const VertexAttributeArray&) = delete; - /// Clone the collection - - /// Copy another collection into this one + /// Specialized DataDrivenPaintProperty reader + /// @param binders Property binders for the target shader + /// @param evaluated Evaluated properties + /// @param propertiesAsUniforms [out] A set of string identities for the properties which will be constant, not + /// attributes. + /// @details The property name IDs refer to the "a\_" prefixed values to match the shader definitions. + template + void readDataDrivenPaintProperties(const Binders& binders, + const Evaluated& evaluated, + mbgl::unordered_set* propertiesAsUniforms) { + // Read each property in the type pack + if (propertiesAsUniforms) { + propertiesAsUniforms->reserve(sizeof...(DataDrivenPaintProperty)); + } + (readDataDrivenPaintProperty(binders.template get(), + isConstant(evaluated), + propertiesAsUniforms), + ...); + } /// Specialized DataDrivenPaintProperty reader /// @param binders Property binders for the target shader @@ -377,7 +393,7 @@ class VertexAttributeArray { propertiesAsUniforms.reserve(sizeof...(DataDrivenPaintProperty)); (readDataDrivenPaintProperty(binders.template get(), isConstant(evaluated), - propertiesAsUniforms), + &propertiesAsUniforms), ...); } @@ -391,7 +407,7 @@ class VertexAttributeArray { template void readDataDrivenPaintProperty(const Binder& binder, const bool isConstant, - mbgl::unordered_set& propertiesAsUniforms) { + mbgl::unordered_set* propertiesAsUniforms) { if (!binder) { return; } @@ -410,8 +426,8 @@ class VertexAttributeArray { const auto& attr = getOrAdd( *attributeNameID, /*index=*/-1, /*type=*/gfx::AttributeDataType::Invalid, /*count=*/0); applyPaintProperty(attrIndex, attr, binder); - } else { - propertiesAsUniforms.emplace(*attributeNameID); + } else if (propertiesAsUniforms) { + propertiesAsUniforms->emplace(*attributeNameID); } } } diff --git a/src/mbgl/gfx/symbol_drawable_data.hpp b/src/mbgl/gfx/symbol_drawable_data.hpp index 6febd039ce4..2231064328f 100644 --- a/src/mbgl/gfx/symbol_drawable_data.hpp +++ b/src/mbgl/gfx/symbol_drawable_data.hpp @@ -22,16 +22,14 @@ struct SymbolDrawableData : public DrawableData { const style::AlignmentType pitchAlignment_, const style::AlignmentType rotationAlignment_, const style::SymbolPlacementType placement_, - const style::IconTextFitType textFit_, - PropertyMapType&& propertiesAsUniforms_) + const style::IconTextFitType textFit_) : isHalo(isHalo_), bucketVariablePlacement(bucketVariablePlacement_), symbolType(symbolType_), pitchAlignment(pitchAlignment_), rotationAlignment(rotationAlignment_), placement(placement_), - textFit(textFit_), - propertiesAsUniforms(std::move(propertiesAsUniforms_)) {} + textFit(textFit_) {} ~SymbolDrawableData() override = default; const bool isHalo; @@ -41,7 +39,6 @@ struct SymbolDrawableData : public DrawableData { const style::AlignmentType rotationAlignment; const style::SymbolPlacementType placement; const style::IconTextFitType textFit; - const PropertyMapType propertiesAsUniforms; }; using UniqueSymbolDrawableData = std::unique_ptr; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 0381216107a..063bcdfd2ab 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -748,13 +748,6 @@ SymbolDrawableTilePropsUBO buildTileUBO(const SymbolBucket& bucket, }; } -// Convert a properties-as-uniforms set to the type expected by `SymbolDrawableData` -gfx::SymbolDrawableData::PropertyMapType toMap(const mbgl::unordered_set& set) { - // can we do this without allocating? - auto values = std::vector(set.size()); - return gfx::SymbolDrawableData::PropertyMapType(set.begin(), set.end(), values.begin(), values.end()); -} - const auto idDataAttibName = stringIndexer().get("a_data"); const auto posOffsetAttribName = "a_pos_offset"; const auto idPosOffsetAttribName = stringIndexer().get(posOffsetAttribName); @@ -769,7 +762,7 @@ void updateTileAttributes(const SymbolBucket::Buffer& buffer, const SymbolBucket::PaintProperties& paintProps, const SymbolPaintProperties::PossiblyEvaluated& evaluated, gfx::VertexAttributeArray& attribs, - mbgl::unordered_set& propertiesAsUniforms) { + mbgl::unordered_set* propertiesAsUniforms) { if (const auto& attr = attribs.getOrAdd(idPosOffsetAttribName)) { attr->setSharedRawData(buffer.sharedVertices, offsetof(SymbolLayoutVertex, a1), @@ -825,8 +818,7 @@ void updateTileDrawable(gfx::Drawable& drawable, const SymbolPaintProperties::PossiblyEvaluated& evaluated, const TransformState& state, gfx::UniformBufferPtr& textInterpUBO, - gfx::UniformBufferPtr& iconInterpUBO, - mbgl::unordered_set& propertiesAsUniforms) { + gfx::UniformBufferPtr& iconInterpUBO) { if (!drawable.getData()) { return; } @@ -868,7 +860,7 @@ void updateTileDrawable(gfx::Drawable& drawable, // See `Placement::updateBucketDynamicVertices` if (auto& attribs = drawable.getVertexAttributes()) { - updateTileAttributes(buffer, isText, paintProps, evaluated, *attribs, propertiesAsUniforms); + updateTileAttributes(buffer, isText, paintProps, evaluated, *attribs, nullptr); } } @@ -1159,20 +1151,8 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, propertiesAsUniforms.clear(); const auto& evaluated = getEvaluated(renderData.layerProperties); - updateTileDrawable(drawable, - context, - bucket, - bucketPaintProperties, - evaluated, - state, - textInterpUBO, - iconInterpUBO, - propertiesAsUniforms); - - // We assume that the properties-as-uniforms doesn't change without the style changing. - // That would require updating the shader as well. - [[maybe_unused]] const auto& drawData = static_cast(*drawable.getData()); - assert(drawData.propertiesAsUniforms == toMap(propertiesAsUniforms)); + updateTileDrawable( + drawable, context, bucket, bucketPaintProperties, evaluated, state, textInterpUBO, iconInterpUBO); return true; }; if (updateTile(passes, tileID, std::move(updateExisting))) { @@ -1259,7 +1239,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, propertiesAsUniforms.clear(); auto attribs = context.createVertexAttributeArray(); - updateTileAttributes(buffer, isText, bucketPaintProperties, evaluated, *attribs, propertiesAsUniforms); + updateTileAttributes(buffer, isText, bucketPaintProperties, evaluated, *attribs, &propertiesAsUniforms); const auto textHalo = evaluated.get().constantOr(Color::black()).a > 0.0f && evaluated.get().constantOr(1); @@ -1355,8 +1335,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, /*.pitchAlignment=*/values.pitchAlignment, /*.rotationAlignment=*/values.rotationAlignment, /*.placement=*/layout.get(), - /*.textFit=*/layout.get(), - /*propertiesAsUniforms=*/toMap(propertiesAsUniforms)); + /*.textFit=*/layout.get()); const auto tileUBO = buildTileUBO(bucket, *drawData, currentZoom); drawable->setData(std::move(drawData)); From e4f293e404c2a865b67ca7e765a53b9dffd8c2d1 Mon Sep 17 00:00:00 2001 From: Oliver Wipfli Date: Sat, 27 Jan 2024 11:12:01 +0300 Subject: [PATCH 43/96] Fix sponsorship tier (#2063) --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5917af34aff..49fdeb50c46 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,10 @@ We thank everyone who supported us financially in the past and special thanks to Read more about the MapLibre Sponsorship Program at [https://maplibre.org/sponsors/](https://maplibre.org/sponsors/). -Platinum: +Gold: Logo AWS -Gold: - Logo Meta Silver: From 82c1adfe22350262144eb6101730835430601d5f Mon Sep 17 00:00:00 2001 From: Andrew Calcutt Date: Sat, 27 Jan 2024 11:20:49 -0500 Subject: [PATCH 44/96] [node][main] Update latest release to node-v5.3.1 (#2065) --- package-lock.json | 4 ++-- package.json | 2 +- platform/node/CHANGELOG.md | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca0752336c0..3f384457721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@maplibre/maplibre-gl-native", - "version": "5.3.0", + "version": "5.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@maplibre/maplibre-gl-native", - "version": "5.3.0", + "version": "5.3.1", "hasInstallScript": true, "license": "BSD-2-Clause", "dependencies": { diff --git a/package.json b/package.json index 4a2e055ece2..700f1b1a89e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@maplibre/maplibre-gl-native", - "version": "5.3.0", + "version": "5.3.1", "description": "Renders map tiles with MapLibre Native", "keywords": [ "maplibre", diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 51e27995f04..e457f9a5c7c 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,6 +1,12 @@ ## main +## 5.3.1 + +* [Note] This is a OpenGL-2 release. It does not include metal support. +* Add WebP decoding support to Linux and Windows. @mwilsnd @acalcutt https://github.com/maplibre/maplibre-native/pull/2044 +* Add support for slice and index-of expression @SiarheiFedartsou @acalcutt https://github.com/maplibre/maplibre-native/pull/2023 + ## 5.3.0 * [Note] This is a OpenGL-2 release. It does not include metal support. From 83b2348edfae35d0a6e9fc5273b8e8dbead8713f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 13:43:24 +0700 Subject: [PATCH 45/96] Bump LouisBrunner/checks-action from 1.6.2 to 2.0.0 (#2056) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android-device-test.yml | 4 ++-- .github/workflows/ios-device-test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index d436b8cbcfc..41c1aa513eb 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -61,7 +61,7 @@ jobs: issue-number: ${{ steps.get-pr-number.outputs.pr-number }} body-regex: '^!benchmark.*android.*$' - - uses: LouisBrunner/checks-action@v1.6.2 + - uses: LouisBrunner/checks-action@v2.0.0 if: matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id id: create_check with: @@ -101,7 +101,7 @@ jobs: externalData: ${{ matrix.test.externalData }} testSpecArn: ${{ matrix.test.testSpecArn }} - - uses: LouisBrunner/checks-action@v1.6.2 + - uses: LouisBrunner/checks-action@v2.0.0 if: always() && (matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id) with: token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/ios-device-test.yml b/.github/workflows/ios-device-test.yml index ecb90bd5d85..f02d89af0ba 100644 --- a/.github/workflows/ios-device-test.yml +++ b/.github/workflows/ios-device-test.yml @@ -27,7 +27,7 @@ jobs: app_id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} private_key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} - - uses: LouisBrunner/checks-action@v1.6.2 + - uses: LouisBrunner/checks-action@v2.0.0 id: create_check with: token: ${{ steps.generate_token.outputs.token }} @@ -61,7 +61,7 @@ jobs: AWS_DEVICE_FARM_PROJECT_ARN: ${{ vars.AWS_DEVICE_FARM_PROJECT_ARN }} AWS_DEVICE_FARM_DEVICE_POOL_ARN: ${{ vars.AWS_DEVICE_FARM_IPHONE_DEVICE_POOL_ARN }} - - uses: LouisBrunner/checks-action@v1.6.2 + - uses: LouisBrunner/checks-action@v2.0.0 if: always() with: token: ${{ steps.generate_token.outputs.token }} From 1d5a58bb52791d89f7b33abba5f7e532ce952824 Mon Sep 17 00:00:00 2001 From: Nuno Goncalves Date: Sun, 28 Jan 2024 14:04:46 +0000 Subject: [PATCH 46/96] remove unused member variable (#2066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nuno Gonçalves --- src/mbgl/layout/symbol_instance.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index 066db8a64c5..2082b6d2a20 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -113,7 +113,6 @@ class SymbolInstance { std::array textOffset; std::array iconOffset; std::u16string key; - bool isDuplicate; std::optional placedRightTextIndex; std::optional placedCenterTextIndex; std::optional placedLeftTextIndex; From 34e37d925653184de17ae817a925138ed8200dcd Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Sun, 28 Jan 2024 09:12:39 -0500 Subject: [PATCH 47/96] [bazel] Update to 7.0.0 (#1956) Co-authored-by: Bart Louwers --- .bazelversion | 2 +- MODULE.bazel | 1 + render-test/BUILD.bazel | 1 + test/BUILD.bazel | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.bazelversion b/.bazelversion index 19b860c1872..66ce77b7ead 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.4.0 +7.0.0 diff --git a/MODULE.bazel b/MODULE.bazel index d05d4ad36e0..ba62316896f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,5 +1,6 @@ module(name = "maplibre") +bazel_dep(name = "apple_support", version = "1.11.1", repo_name = "build_bazel_apple_support") bazel_dep(name = "bazel_skylib", version = "1.5.0") bazel_dep(name = "rules_apple", version = "3.1.1", repo_name = "build_bazel_rules_apple") bazel_dep(name = "rules_swift", version = "1.13.0", repo_name = "build_bazel_rules_swift") diff --git a/render-test/BUILD.bazel b/render-test/BUILD.bazel index ba14c52b1b8..3eb9d9bf879 100644 --- a/render-test/BUILD.bazel +++ b/render-test/BUILD.bazel @@ -49,6 +49,7 @@ cc_test( data = [ "//metrics:render-test-files", ], + tags = ["no-sandbox"], deps = [ "render-test-srcs", "//platform/default:render-test-bin", diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 2738b322c95..5a3e4725de8 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -84,6 +84,7 @@ cc_test( data = glob(["fixtures/**/*"]) + [ "//:scripts/style-spec-reference/v8.json", ], + tags = ["no-sandbox"], deps = [ "tests", "//platform/linux:impl", From c3a734aa116ed17f606c1752a5e8df17e31b3a3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 28 Jan 2024 21:21:35 +0700 Subject: [PATCH 48/96] Bump peter-evans/find-comment from 2 to 3 (#2055) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/android-device-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index 41c1aa513eb..0967a6cff5b 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -55,7 +55,7 @@ jobs: id: get-pr-number - name: Check if comment on PR contains '!benchmark android' - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@v3 id: fc with: issue-number: ${{ steps.get-pr-number.outputs.pr-number }} From c4f852b3f4a82949644df2c7d1f59c57fefc76eb Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Tue, 30 Jan 2024 03:44:59 -0500 Subject: [PATCH 49/96] Remove custom platforms setting (#2058) --- .github/workflows/ios-ci.yml | 6 +- .github/workflows/linux-ci.yml | 2 +- BUILD.bazel | 36 +-------- bazel/flags.bzl | 100 ++++++++++++------------ platform/default/BUILD.bazel | 8 +- platform/ios/scripts/bazel-package.sh | 3 +- platform/ios/scripts/bazel-xcodeproj.sh | 2 +- render-test/BUILD.bazel | 4 +- test/BUILD.bazel | 6 +- 9 files changed, 65 insertions(+), 102 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 1940e67cf06..17832fa97e2 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -167,7 +167,7 @@ jobs: - name: Build dynamic library for size test (Bloaty) run: | - bazel build //platform/ios:MapLibre.dynamic --//:renderer=metal --//:maplibre_platform=ios --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym + bazel build //platform/ios:MapLibre.dynamic --//:renderer=metal --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym bazel_bin="$(bazel info --compilation_mode="opt" bazel-bin)" unzip "$bazel_bin"/platform/ios/MapLibre.dynamic.xcframework.zip cp "$bazel_bin"/platform/ios/MapLibre.dynamic_dsyms/MapLibre_ios_device.framework.dSYM/Contents/Resources/DWARF/MapLibre_ios_device MapLibre_DWARF @@ -208,8 +208,8 @@ jobs: - name: Build XCFramework run: | - bazel build --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic --embed_label=maplibre_ios_"$(cat VERSION)" - echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal --//:maplibre_platform=ios //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" + bazel build --compilation_mode=opt --//:renderer=metal //platform/ios:MapLibre.dynamic --embed_label=maplibre_ios_"$(cat VERSION)" + echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" - name: Get version (release) if: github.event.inputs.release == 'full' diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index d8ab69346d2..69269a943ea 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -206,7 +206,7 @@ jobs: - name: Generate coverage report run: | xvfb-run -a \ - bazel coverage --combined_report=lcov --instrumentation_filter="//:mbgl-core" --//:maplibre_platform=linux \ + bazel coverage --combined_report=lcov --instrumentation_filter="//:mbgl-core" \ --test_output=errors --local_test_jobs=1 \ --test_env=DISPLAY --test_env=XAUTHORITY --copt="-DCI_BUILD" \ //test:core //render-test:render-test //expression-test:test diff --git a/BUILD.bazel b/BUILD.bazel index d040234bebf..d0d8df9c205 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -113,7 +113,7 @@ cc_library( "MLN_DRAWABLE_RENDERER=1", ], }) + select({ - "//:ios": ["GLES_SILENCE_DEPRECATION=1"], + "@platforms//os:ios": ["GLES_SILENCE_DEPRECATION=1"], "//conditions:default": [], }), includes = [ @@ -145,7 +145,7 @@ cc_library( "//vendor:vector-tile", "//vendor:wagyu", ] + select({ - "//:ios": [ + "@platforms//os:ios": [ "//vendor:icu", ], "//conditions:default": [], @@ -167,38 +167,6 @@ genrule( visibility = ["//visibility:public"], ) -# The next three rules are a bit of a hack -# they are needed until rules_apple has platforms support -# https://github.com/bazelbuild/rules_apple/issues/1658 -# Allows passing a command line flag to set the Platform -# bazel build [target] --//:maplibre_platform=ios - -string_flag( - name = "maplibre_platform", - build_setting_default = "ios", -) - -config_setting( - name = "linux", - flag_values = { - ":maplibre_platform": "linux", - }, -) - -config_setting( - name = "ios", - flag_values = { - ":maplibre_platform": "ios", - }, -) - -config_setting( - name = "windows", - flag_values = { - ":maplibre_platform": "windows", - }, -) - # Selects the rendering implementation to utilize in the core string_flag( diff --git a/bazel/flags.bzl b/bazel/flags.bzl index 8fd0fb733be..78687119e2c 100644 --- a/bazel/flags.bzl +++ b/bazel/flags.bzl @@ -20,43 +20,43 @@ GCC_CLANG_COMMON_FLAGS = [ MSVC_FLAGS = [ "/Wall", "/WX", - "/wd4068", # Unknown pragma - "/wd4820", # Padding - "/wd5045", # Compiler will insert Spectre mitigation for memory load - "/wd4643", # Non-conformant forward decl - "/wd4623", # Default ctor implicitly deleted - "/wd5027", # Default move ctor implicitly deleted - "/wd4626", # Assignment operator implicitly deleted - "/wd4514", # Unreferenced inline function has been removed - "/wd4582", # Constructor is not implicitly called - "/wd4365", # Signed/unsigned mismatch - "/wd4388", # Signed/unsigned mismatch - "/wd4244", # Conversion, possible loss of data - "/wd4242", # Conversion, possible loss of data - "/wd4625", # Copy constructor was implicitly defined as deleted - "/wd5026", # Move constructor was implicitly defined as deleted - "/wd5219", # Implicit conversion, possible loss of data - "/wd5246", # Initialization of a subobject should be wrapped in braces - "/wd4201", # Nonstandard extension used: nameless struct/union - "/wd4868", # Compiler may not enforce left-to-right evaluation order in braced initializer list - "/wd4061", # Eenumerator in switch of enum is not explicitly handled by a case label - "/wd4464", # Relative include path contains '..' - "/wd4668", # is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' - "/wd4800", # Implicit conversion from 'unsigned __int64' to bool. Possible information loss - "/wd4355", # 'this': used in base member initializer list - "/wd5204", # Class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed - "/wd5220", # A non-static data member with a volatile qualified type no longer implies - "/wd4619", # (boost) #pragma warning: there is no warning number - "/wd5031", # (boost) #pragma warning(pop): likely mismatch, popping warning state pushed in different file - "/wd5243", # (boost) using incomplete class can cause potential one definition rule violation due to ABI limitation - "/wd4371", # (boost) layout of class may have changed from a previous version of the compiler due to better packing of member - "/wd4435", # (boost) Object layout under /vd2 will change due to virtual base - "/wd4702", # Unreachable code - "/wd4710", # Function not inlined - "/wd5041", # out-of-line definition for constexpr static data member is not needed and is deprecated in C++17 - "/wd4946", # reinterpret_cast used between related classes - "/wd4459", # declaration of 'x' hides global declaration - "/wd4373", # virtual function overrides 'x', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers + "/wd4068", # Unknown pragma + "/wd4820", # Padding + "/wd5045", # Compiler will insert Spectre mitigation for memory load + "/wd4643", # Non-conformant forward decl + "/wd4623", # Default ctor implicitly deleted + "/wd5027", # Default move ctor implicitly deleted + "/wd4626", # Assignment operator implicitly deleted + "/wd4514", # Unreferenced inline function has been removed + "/wd4582", # Constructor is not implicitly called + "/wd4365", # Signed/unsigned mismatch + "/wd4388", # Signed/unsigned mismatch + "/wd4244", # Conversion, possible loss of data + "/wd4242", # Conversion, possible loss of data + "/wd4625", # Copy constructor was implicitly defined as deleted + "/wd5026", # Move constructor was implicitly defined as deleted + "/wd5219", # Implicit conversion, possible loss of data + "/wd5246", # Initialization of a subobject should be wrapped in braces + "/wd4201", # Nonstandard extension used: nameless struct/union + "/wd4868", # Compiler may not enforce left-to-right evaluation order in braced initializer list + "/wd4061", # Eenumerator in switch of enum is not explicitly handled by a case label + "/wd4464", # Relative include path contains '..' + "/wd4668", # is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' + "/wd4800", # Implicit conversion from 'unsigned __int64' to bool. Possible information loss + "/wd4355", # 'this': used in base member initializer list + "/wd5204", # Class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed + "/wd5220", # A non-static data member with a volatile qualified type no longer implies + "/wd4619", # (boost) #pragma warning: there is no warning number + "/wd5031", # (boost) #pragma warning(pop): likely mismatch, popping warning state pushed in different file + "/wd5243", # (boost) using incomplete class can cause potential one definition rule violation due to ABI limitation + "/wd4371", # (boost) layout of class may have changed from a previous version of the compiler due to better packing of member + "/wd4435", # (boost) Object layout under /vd2 will change due to virtual base + "/wd4702", # Unreachable code + "/wd4710", # Function not inlined + "/wd5041", # out-of-line definition for constexpr static data member is not needed and is deprecated in C++17 + "/wd4946", # reinterpret_cast used between related classes + "/wd4459", # declaration of 'x' hides global declaration + "/wd4373", # virtual function overrides 'x', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers ] WARNING_FLAGS = { @@ -72,9 +72,7 @@ WARNING_FLAGS = { "windows": MSVC_FLAGS, } -""" -Compilation flags used for all .cpp and .mm targets. -""" +# Compilation flags used for all .cpp and .mm targets. GCC_CLANG_CPP_FLAGS = [ "-fexceptions", @@ -92,21 +90,19 @@ MSVC_CPP_FLAGS = [ ] CPP_FLAGS = select({ - "//:ios": GCC_CLANG_CPP_FLAGS + WARNING_FLAGS["ios"], - "//:linux": GCC_CLANG_CPP_FLAGS + WARNING_FLAGS["linux"], - "//:windows": MSVC_CPP_FLAGS + WARNING_FLAGS["windows"], + "//conditions:default": GCC_CLANG_CPP_FLAGS + WARNING_FLAGS["ios"], + "@platforms//os:linux": GCC_CLANG_CPP_FLAGS + WARNING_FLAGS["linux"], + "@platforms//os:windows": MSVC_CPP_FLAGS + WARNING_FLAGS["windows"], }) -""" -Compilation flags related to the Maplibre codebase. Relevant for all .cpp .mm and .m code - - src/* - - include/* - - platform/* -Not important for any vendors that are imported. -""" +# Compilation flags related to the Maplibre codebase. Relevant for all .cpp .mm and .m code +# - src/* +# - include/* +# - platform/* +# Not important for any vendors that are imported. MAPLIBRE_FLAGS = select({ - "//:windows": [ + "@platforms//os:windows": [ "/DMBGL_USE_GLES2=1", "/DMBGL_RENDER_BACKEND_OPENGL=1", "/D_USE_MATH_DEFINES", @@ -117,5 +113,5 @@ MAPLIBRE_FLAGS = select({ "-DMBGL_RENDER_BACKEND_OPENGL=1", "-DGLES_SILENCE_DEPRECATION", "-DMLN_USE_UNORDERED_DENSE", - ] + ], }) diff --git a/platform/default/BUILD.bazel b/platform/default/BUILD.bazel index 2d3cd2ad7d2..cf82bcf0a91 100644 --- a/platform/default/BUILD.bazel +++ b/platform/default/BUILD.bazel @@ -58,7 +58,7 @@ cc_library( "//:metal_renderer": ["src/mbgl/mtl/headless_backend.cpp"], "//conditions:default": ["src/mbgl/gl/headless_backend.cpp"], }) + select({ - "//:linux": [ + "@platforms//os:linux": [ "src/mbgl/i18n/number_format.cpp", "src/mbgl/layermanager/layer_manager.cpp", "src/mbgl/storage/http_file_source.cpp", @@ -74,7 +74,7 @@ cc_library( "src/mbgl/util/timer.cpp", "src/mbgl/util/webp_reader.cpp", ], - "//:ios": [], + "@platforms//os:ios": [], }), hdrs = [ "include/mbgl/gfx/headless_backend.hpp", @@ -106,10 +106,10 @@ cc_library( deps = [ "//:mbgl-core", ] + select({ - "//:ios": [ + "@platforms//os:ios": [ "//platform/darwin:darwin-loop", ], - "//:linux": [ + "@platforms//os:linux": [ "default-collator", ], }), diff --git a/platform/ios/scripts/bazel-package.sh b/platform/ios/scripts/bazel-package.sh index 54c627f8a05..fc8dc3736c3 100644 --- a/platform/ios/scripts/bazel-package.sh +++ b/platform/ios/scripts/bazel-package.sh @@ -76,8 +76,7 @@ bazel build //platform/ios:"$target" --apple_platform_type=ios \ --copt=-Wall --copt=-Wextra --copt=-Wpedantic \ --copt=-Werror \ --jobs "$ncpu" \ - --//:renderer=$flavor \ - --//:maplibre_platform=ios + --//:renderer=$flavor echo "Done." echo "Package will be available in \"/bazel-bin/platform/ios/$target.xcframework.zip\"" diff --git a/platform/ios/scripts/bazel-xcodeproj.sh b/platform/ios/scripts/bazel-xcodeproj.sh index bad5efd4c52..f90763caebb 100755 --- a/platform/ios/scripts/bazel-xcodeproj.sh +++ b/platform/ios/scripts/bazel-xcodeproj.sh @@ -20,4 +20,4 @@ done # Generate the Xcode project # Example invocation: bazel-xcodeproj.sh flavor split -bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=$flavor --//:maplibre_platform=ios" +bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=$flavor" diff --git a/render-test/BUILD.bazel b/render-test/BUILD.bazel index 3eb9d9bf879..efe77226b88 100644 --- a/render-test/BUILD.bazel +++ b/render-test/BUILD.bazel @@ -34,8 +34,8 @@ cc_library( deps = [ "//expression-test:test_runner_common", ] + select({ - "//:ios": ["//platform:ios-sdk"], - "//:linux": ["//platform/linux:impl"], + "@platforms//os:ios": ["//platform:ios-sdk"], + "@platforms//os:linux": ["//platform/linux:impl"], }), ) diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 5a3e4725de8..8c3c3046b26 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -14,7 +14,7 @@ cc_library( cc_library( name = "testlib", srcs = select({ - "//:ios": glob(["src/mbgl/test/*.cpp"]), + "@platforms//os:ios": glob(["src/mbgl/test/*.cpp"]), "//conditions:default": glob( ["src/mbgl/test/*.cpp"], exclude = ["src/mbgl/test/http_server.cpp"], @@ -26,7 +26,7 @@ cc_library( "src/mbgl/test/*.hpp", ]), copts = CPP_FLAGS + MAPLIBRE_FLAGS + select({ - "//:ios": ["-DUSE_CPP_TEST_SERVER"], + "@platforms//os:ios": ["-DUSE_CPP_TEST_SERVER"], "//conditions:default": [], }), includes = [ @@ -40,7 +40,7 @@ cc_library( "testutils", "//:mbgl-core", ] + select({ - "//:ios": ["//vendor:httplib"], + "@platforms//os:ios": ["//vendor:httplib"], "//conditions:default": [], }), ) From bf15332c5e3d1b411cc2a6a8bfa7b908b17a6479 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Tue, 30 Jan 2024 08:02:05 -0800 Subject: [PATCH 50/96] Combine multiple segments into a drawable when `sortFeaturesByKey` is not used (#2060) --- src/mbgl/mtl/drawable.cpp | 14 +++-- .../renderer/layers/render_symbol_layer.cpp | 52 ++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index 1cae17c942f..c8f948a4f61 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -24,6 +24,9 @@ #include #include +#if !defined(NDEBUG) +#include +#endif namespace mbgl { namespace mtl { @@ -72,11 +75,16 @@ MTL::PrimitiveType getPrimitiveType(const gfx::DrawModeType type) noexcept { #if !defined(NDEBUG) std::string debugLabel(const gfx::Drawable& drawable) { - std::string result = drawable.getName(); + std::ostringstream oss; + oss << drawable.getID().id() << "/" << drawable.getName() << "/tile="; + if (const auto& tileID = drawable.getTileID()) { - result.append("/tile=").append(util::toString(*tileID)); + oss << util::toString(*tileID); + } else { + oss << "(none)"; } - return result; + + return oss.str(); } #endif // !defined(NDEBUG) diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 063bcdfd2ab..a59e0082092 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -88,15 +88,14 @@ struct RenderableSegment { const LayerRenderData& renderData_, const SymbolBucket::PaintProperties& bucketPaintProperties_, float sortKey_, - const SymbolType type_, - const uint8_t overscaledZ_ = 0) + const SymbolType type_) : segment(segment_), tile(tile_), renderData(renderData_), bucketPaintProperties(bucketPaintProperties_), sortKey(sortKey_), type(type_), - overscaledZ(overscaledZ_) {} + overscaledZ(tile.getOverscaledTileID().overscaledZ) {} SegmentWrapper segment; const RenderTile& tile; @@ -129,6 +128,19 @@ struct RenderableSegment { } }; +struct SegmentGroup { + // A reference to the first or only segment + RenderableSegment renderable; + // A reference to multiple segments, or none + SegmentVectorWrapper segments; + + bool operator<(const SegmentGroup& other) const { return renderable < other.renderable; } +}; + +namespace { +const SegmentVector emptySegmentVector; +} + #if MLN_LEGACY_RENDERER template @@ -1038,7 +1050,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, } const bool sortFeaturesByKey = !impl_cast(baseImpl).layout.get().isUndefined(); - std::multiset renderableSegments; + std::multiset renderableSegments; std::unique_ptr builder; const auto currentZoom = static_cast(state.getZoom()); @@ -1169,13 +1181,20 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, } float serialKey = 1.0f; - auto addRenderables = [&, it = renderableSegments.begin()](const SymbolBucket::Buffer& buffer, - const SymbolType type) mutable { - for (const auto& segment : buffer.segments) { - const auto key = sortFeaturesByKey ? segment.sortKey : (serialKey += 1.0); - assert(segment.vertexOffset + segment.vertexLength <= buffer.vertices().elements()); - it = renderableSegments.emplace_hint( - it, std::ref(segment), tile, renderData, bucketPaintProperties, key, type, tileID.overscaledZ); + auto addRenderables = [&](const SymbolBucket::Buffer& buffer, const SymbolType type) mutable { + if (sortFeaturesByKey) { + // Features need to be rendered in a specific order, so we add each segment individually + for (const auto& segment : buffer.segments) { + assert(segment.vertexOffset + segment.vertexLength <= buffer.vertices().elements()); + renderableSegments.emplace(SegmentGroup{ + {segment, tile, renderData, bucketPaintProperties, segment.sortKey, type}, emptySegmentVector}); + } + } else if (!buffer.segments.empty()) { + // Features can be rendered in the order produced, and as grouped by the bucket + const auto& firstSeg = buffer.segments.front(); + renderableSegments.emplace(SegmentGroup{ + {firstSeg, tile, renderData, bucketPaintProperties, serialKey, type}, buffer.segments}); + serialKey += 1.0; } }; @@ -1208,7 +1227,10 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, }; std::unordered_map tileCache; - for (auto& renderable : renderableSegments) { + for (auto& group : renderableSegments) { + const auto& renderable = group.renderable; + const auto& segments = group.segments.get(); + const auto isText = (renderable.type == SymbolType::Text); const auto sdfIcons = (renderable.type == SymbolType::IconSDF); @@ -1320,7 +1342,11 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, builder->setDrawableName(layerPrefix + std::string(suffix)); builder->setVertexAttributes(attribs); - builder->setSegments(gfx::Triangles(), buffer.sharedTriangles, &renderable.segment.get(), 1); + if (segments.empty()) { + builder->setSegments(gfx::Triangles(), buffer.sharedTriangles, &renderable.segment.get(), 1); + } else { + builder->setSegments(gfx::Triangles(), buffer.sharedTriangles, segments.data(), segments.size()); + } builder->flush(context); From ea15e7b6131ec80a19b0fdd425884957e0fb8165 Mon Sep 17 00:00:00 2001 From: mwilsnd <53413200+mwilsnd@users.noreply.github.com> Date: Tue, 30 Jan 2024 14:59:44 -0500 Subject: [PATCH 51/96] Use C++17 in csscolorparser (#2071) --- vendor/csscolorparser/csscolorparser.cpp | 2 +- vendor/csscolorparser/csscolorparser/csscolorparser.hpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/vendor/csscolorparser/csscolorparser.cpp b/vendor/csscolorparser/csscolorparser.cpp index b0ff5f84e01..74764a22e24 100644 --- a/vendor/csscolorparser/csscolorparser.cpp +++ b/vendor/csscolorparser/csscolorparser.cpp @@ -176,7 +176,7 @@ std::vector split(const std::string& s, char delim) { return elems; } -std::experimental::optional parse(const std::string& css_str) { +std::optional parse(const std::string& css_str) { std::string str = css_str; // Remove all whitespace, not compliant, but should just be more accepting. diff --git a/vendor/csscolorparser/csscolorparser/csscolorparser.hpp b/vendor/csscolorparser/csscolorparser/csscolorparser.hpp index 30b308022a5..7b41bfa5de2 100644 --- a/vendor/csscolorparser/csscolorparser/csscolorparser.hpp +++ b/vendor/csscolorparser/csscolorparser/csscolorparser.hpp @@ -25,8 +25,7 @@ #ifndef CSS_COLOR_PARSER_CPP #define CSS_COLOR_PARSER_CPP -#include - +#include #include #include @@ -50,7 +49,7 @@ inline bool operator!=(const Color& lhs, const Color& rhs) { return !(lhs == rhs); } -std::experimental::optional parse(const std::string& css_str); +std::optional parse(const std::string& css_str); } // namespace CSSColorParser From 61a6b66435f54316a66dcd07c5a4959c803db9c1 Mon Sep 17 00:00:00 2001 From: Alex Cristici Date: Wed, 31 Jan 2024 22:02:05 +0200 Subject: [PATCH 52/96] UBO by index instead of map (#1980) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CMakeLists.txt | 4 + bazel/core.bzl | 3 + include/mbgl/gfx/uniform_block.hpp | 51 ++--- include/mbgl/gfx/uniform_buffer.hpp | 37 ++-- include/mbgl/shaders/background_layer_ubo.hpp | 6 + include/mbgl/shaders/circle_layer_ubo.hpp | 8 + include/mbgl/shaders/collision_layer_ubo.hpp | 5 + include/mbgl/shaders/debug_layer_ubo.hpp | 5 + .../mbgl/shaders/fill_extrusion_layer_ubo.hpp | 48 +++-- include/mbgl/shaders/fill_layer_ubo.hpp | 30 +++ include/mbgl/shaders/gl/shader_group_gl.hpp | 10 +- include/mbgl/shaders/gl/shader_info.hpp | 147 +++++++++++++++ include/mbgl/shaders/gl/shader_program_gl.hpp | 4 +- include/mbgl/shaders/heatmap_layer_ubo.hpp | 7 + .../shaders/heatmap_texture_layer_ubo.hpp | 5 + include/mbgl/shaders/hillshade_layer_ubo.hpp | 6 + .../shaders/hillshade_prepare_layer_ubo.hpp | 5 + include/mbgl/shaders/line_layer_ubo.hpp | 158 ++++++++++------ include/mbgl/shaders/mtl/shader_program.hpp | 5 +- include/mbgl/shaders/mtl/symbol_icon.hpp | 14 +- include/mbgl/shaders/raster_layer_ubo.hpp | 5 + include/mbgl/shaders/symbol_layer_ubo.hpp | 9 + include/mbgl/shaders/ubo_max_count.hpp | 44 +++++ src/mbgl/gfx/uniform_block.cpp | 26 ++- src/mbgl/gfx/uniform_buffer.cpp | 45 ++--- src/mbgl/gl/drawable_gl.cpp | 25 ++- src/mbgl/mtl/drawable.cpp | 19 +- .../layers/background_layer_tweaker.cpp | 9 +- .../renderer/layers/circle_layer_tweaker.cpp | 10 +- .../layers/collision_layer_tweaker.cpp | 16 +- .../layers/collision_layer_tweaker.hpp | 5 - .../layers/fill_extrusion_layer_tweaker.cpp | 11 +- .../layers/fill_extrusion_layer_tweaker.hpp | 3 - .../renderer/layers/fill_layer_tweaker.cpp | 137 +++++++------- .../renderer/layers/fill_layer_tweaker.hpp | 4 - .../renderer/layers/heatmap_layer_tweaker.cpp | 7 +- .../layers/heatmap_texture_layer_tweaker.cpp | 4 +- .../layers/hillshade_layer_tweaker.cpp | 7 +- .../hillshade_prepare_layer_tweaker.cpp | 4 +- .../renderer/layers/line_layer_tweaker.cpp | 39 ++-- .../renderer/layers/raster_layer_tweaker.cpp | 4 +- .../renderer/layers/render_circle_layer.cpp | 5 +- .../layers/render_fill_extrusion_layer.cpp | 10 +- .../renderer/layers/render_fill_layer.cpp | 56 +++--- .../renderer/layers/render_heatmap_layer.cpp | 6 +- .../renderer/layers/render_line_layer.cpp | 73 ++++--- .../renderer/layers/render_symbol_layer.cpp | 12 +- .../renderer/layers/symbol_layer_tweaker.cpp | 14 +- .../renderer/layers/symbol_layer_tweaker.hpp | 6 - .../renderer/sources/render_tile_source.cpp | 18 +- src/mbgl/shaders/gl/shader_info.cpp | 178 ++++++++++++++++++ src/mbgl/shaders/gl/shader_program_gl.cpp | 30 +-- src/mbgl/shaders/mtl/background.cpp | 4 +- src/mbgl/shaders/mtl/background_pattern.cpp | 4 +- src/mbgl/shaders/mtl/circle.cpp | 8 +- src/mbgl/shaders/mtl/clipping_mask.cpp | 2 +- src/mbgl/shaders/mtl/collision_box.cpp | 2 +- src/mbgl/shaders/mtl/collision_circle.cpp | 2 +- src/mbgl/shaders/mtl/debug.cpp | 2 +- src/mbgl/shaders/mtl/fill.cpp | 28 +-- src/mbgl/shaders/mtl/fill_extrusion.cpp | 6 +- .../shaders/mtl/fill_extrusion_pattern.cpp | 8 +- src/mbgl/shaders/mtl/heatmap.cpp | 6 +- src/mbgl/shaders/mtl/heatmap_texture.cpp | 2 +- src/mbgl/shaders/mtl/hillshade.cpp | 4 +- src/mbgl/shaders/mtl/hillshade_prepare.cpp | 2 +- src/mbgl/shaders/mtl/line.cpp | 30 +-- src/mbgl/shaders/mtl/line_gradient.cpp | 8 +- src/mbgl/shaders/mtl/raster.cpp | 2 +- src/mbgl/shaders/mtl/shader_program.cpp | 11 +- src/mbgl/shaders/mtl/symbol_icon.cpp | 10 +- src/mbgl/shaders/mtl/symbol_sdf.cpp | 10 +- src/mbgl/shaders/mtl/symbol_text_and_icon.cpp | 10 +- .../style/layers/custom_drawable_layer.cpp | 24 +-- 74 files changed, 986 insertions(+), 598 deletions(-) create mode 100644 include/mbgl/shaders/gl/shader_info.hpp create mode 100644 include/mbgl/shaders/ubo_max_count.hpp create mode 100644 src/mbgl/shaders/gl/shader_info.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b8df248e3b3..f99fb436d2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,6 +159,7 @@ if(MLN_DRAWABLE_RENDERER) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_program_base.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/identity.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/suppress_copies.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_info.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/buffer_allocator.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl.hpp @@ -1139,6 +1140,8 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/line_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/raster_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/symbol_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/ubo_max_count.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl_builder.hpp @@ -1149,6 +1152,7 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/gl/texture2d.hpp ) list(APPEND SRC_FILES + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_info.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_program_gl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl_builder.cpp diff --git a/bazel/core.bzl b/bazel/core.bzl index 21341a2da1c..34e514003c9 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -990,6 +990,7 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/raster_layer_ubo.hpp", "include/mbgl/shaders/shader_program_base.hpp", "include/mbgl/shaders/symbol_layer_ubo.hpp", + "include/mbgl/shaders/ubo_max_count.hpp", "include/mbgl/util/identity.hpp", "include/mbgl/util/suppress_copies.hpp", "include/mbgl/style/layers/custom_drawable_layer.hpp", @@ -1006,6 +1007,7 @@ MLN_DRAWABLES_GL_SOURCE = [ "src/mbgl/gl/uniform_block_gl.cpp", "src/mbgl/gl/uniform_buffer_gl.cpp", "src/mbgl/gl/vertex_attribute_gl.cpp", + "src/mbgl/shaders/gl/shader_info.cpp", "src/mbgl/shaders/gl/shader_program_gl.cpp", ] @@ -1018,6 +1020,7 @@ MLN_DRAWABLES_GL_HEADERS = [ "include/mbgl/gl/uniform_buffer_gl.hpp", "include/mbgl/gl/vertex_attribute_gl.hpp", "include/mbgl/gl/texture2d.hpp", + "include/mbgl/shaders/gl/shader_info.hpp", "include/mbgl/shaders/gl/shader_program_gl.hpp", "include/mbgl/shaders/gl/shader_group_gl.hpp", ] diff --git a/include/mbgl/gfx/uniform_block.hpp b/include/mbgl/gfx/uniform_block.hpp index e64a62133af..d93d49acf5f 100644 --- a/include/mbgl/gfx/uniform_block.hpp +++ b/include/mbgl/gfx/uniform_block.hpp @@ -1,18 +1,14 @@ #pragma once -#include -#include - -#include -#include -#include -#include -#include +#include namespace mbgl { namespace gfx { class UniformBuffer; +class UniformBlock; + +using UniqueUniformBlock = std::unique_ptr; /// @brief This class represents an uniform block class UniformBlock { @@ -60,11 +56,9 @@ class UniformBlock { std::size_t size; }; -/// Stores a collection of uniform blocks by name +/// Stores a collection of uniform blocks by id class UniformBlockArray { public: - using UniformBlockMap = mbgl::unordered_map>; - /// @brief Constructor UniformBlockArray() = default; @@ -77,22 +71,19 @@ class UniformBlockArray { /// @brief Destructor virtual ~UniformBlockArray() = default; - /// @brief Get map of elements. - const UniformBlockMap& getMap() const { return uniformBlockMap; } - - /// @brief Number of elements - std::size_t size() const { return uniformBlockMap.size(); } + /// @brief Number of maximum allocated elements + std::size_t allocatedSize() const { return uniformBlockVector.size(); } /// @brief Get an uniform block element. /// @return Pointer to the element on success, or null if the uniform block doesn't exists. - const std::unique_ptr& get(const StringIdentity id) const; + const std::unique_ptr& get(const size_t id) const; - /// @brief Add a new uniform block element. - /// @param name + /// @brief Set a new uniform block element. + /// @param id /// @param index /// @param size /// @return Pointer to the new element on success, or null if the uniform block already exists. - const std::unique_ptr& add(const StringIdentity id, int index, std::size_t size); + const std::unique_ptr& set(const size_t id, const size_t index, std::size_t size); /// @brief Move assignment operator UniformBlockArray& operator=(UniformBlockArray&&); @@ -101,31 +92,21 @@ class UniformBlockArray { UniformBlockArray& operator=(const UniformBlockArray&); /// Do something with each block - template + template void visit(Func f) { - std::for_each(uniformBlockMap.begin(), uniformBlockMap.end(), [&](const auto& kv) { - if (kv.second) { - f(kv.first, *kv.second); + std::for_each(uniformBlockVector.begin(), uniformBlockVector.end(), [&](const auto& block) { + if (block) { + f(*block); } }); } protected: - const std::unique_ptr& add(const StringIdentity id, std::unique_ptr&& uniformBlock) { - const auto result = uniformBlockMap.insert(std::make_pair(id, std::unique_ptr())); - if (result.second) { - result.first->second = std::move(uniformBlock); - return result.first->second; - } else { - return nullref; - } - } - virtual std::unique_ptr create(int index, std::size_t size) = 0; virtual std::unique_ptr copy(const UniformBlock& uniformBlock) = 0; protected: - UniformBlockMap uniformBlockMap; + std::array uniformBlockVector; static std::unique_ptr nullref; }; diff --git a/include/mbgl/gfx/uniform_buffer.hpp b/include/mbgl/gfx/uniform_buffer.hpp index 46aeddad8bc..8b21ee69e02 100644 --- a/include/mbgl/gfx/uniform_buffer.hpp +++ b/include/mbgl/gfx/uniform_buffer.hpp @@ -1,12 +1,7 @@ #pragma once +#include #include -#include - -#include -#include -#include -#include namespace mbgl { namespace gfx { @@ -15,6 +10,7 @@ class Context; class UniformBuffer; class UniformBufferArray; +using UniformBufferPtr = std::shared_ptr; using UniqueUniformBuffer = std::unique_ptr; using UniqueUniformBufferArray = std::unique_ptr; @@ -46,37 +42,30 @@ class UniformBuffer { std::size_t size; }; -/// Stores a collection of uniform buffers by name +/// Stores a collection of uniform buffers by id class UniformBufferArray { public: - using UniformBufferMap = mbgl::unordered_map>; - UniformBufferArray() = default; UniformBufferArray(UniformBufferArray&&); // Would need to use the virtual assignment operator UniformBufferArray(const UniformBufferArray&) = delete; virtual ~UniformBufferArray() = default; - /// Number of elements - std::size_t size() const { return uniformBufferMap.size(); } + /// Number of maximum allocated elements + std::size_t allocatedSize() const { return uniformBufferVector.size(); } /// Get an uniform buffer element. /// Returns a pointer to the element on success, or null if the uniform buffer doesn't exists. - const std::shared_ptr& get(const StringIdentity id) const; + const std::shared_ptr& get(const size_t id) const; - /// Add a new uniform buffer element or replace the existing one. - const std::shared_ptr& addOrReplace(const StringIdentity id, - std::shared_ptr uniformBuffer); + /// Set a new uniform buffer element or replace the existing one. + const std::shared_ptr& set(const size_t id, std::shared_ptr uniformBuffer); /// Create and add a new buffer or update an existing one - void createOrUpdate(const StringIdentity id, - const std::vector& data, - gfx::Context&, - bool persistent = false); - void createOrUpdate( - const StringIdentity id, const void* data, std::size_t size, gfx::Context&, bool persistent = false); + void createOrUpdate(const size_t id, const std::vector& data, gfx::Context&, bool persistent = false); + void createOrUpdate(const size_t id, const void* data, std::size_t size, gfx::Context&, bool persistent = false); template - std::enable_if_t> createOrUpdate(const StringIdentity id, + std::enable_if_t> createOrUpdate(const size_t id, const T* data, gfx::Context& context, bool persistent = false) { @@ -87,12 +76,10 @@ class UniformBufferArray { UniformBufferArray& operator=(const UniformBufferArray&); protected: - const std::shared_ptr& add(const StringIdentity id, std::shared_ptr&&); - virtual std::unique_ptr copy(const UniformBuffer&) = 0; protected: - UniformBufferMap uniformBufferMap; + std::array uniformBufferVector; static std::shared_ptr nullref; }; diff --git a/include/mbgl/shaders/background_layer_ubo.hpp b/include/mbgl/shaders/background_layer_ubo.hpp index a7a5c6203fe..167d6eee4dd 100644 --- a/include/mbgl/shaders/background_layer_ubo.hpp +++ b/include/mbgl/shaders/background_layer_ubo.hpp @@ -40,5 +40,11 @@ struct alignas(16) BackgroundPatternLayerUBO { }; static_assert(sizeof(BackgroundPatternLayerUBO) == 96); +enum { + idBackgroundDrawableUBO, + idBackgroundLayerUBO, + backgroundUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/circle_layer_ubo.hpp b/include/mbgl/shaders/circle_layer_ubo.hpp index 9c347b807e1..7b52c4c5c9e 100644 --- a/include/mbgl/shaders/circle_layer_ubo.hpp +++ b/include/mbgl/shaders/circle_layer_ubo.hpp @@ -46,5 +46,13 @@ struct alignas(16) CircleInterpolateUBO { }; static_assert(sizeof(CircleInterpolateUBO) % 16 == 0); +enum { + idCircleDrawableUBO, + idCirclePaintParamsUBO, + idCircleEvaluatedPropsUBO, + idCircleInterpolateUBO, + circleUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/collision_layer_ubo.hpp b/include/mbgl/shaders/collision_layer_ubo.hpp index ac7d762d0bf..ea1c9d790a6 100644 --- a/include/mbgl/shaders/collision_layer_ubo.hpp +++ b/include/mbgl/shaders/collision_layer_ubo.hpp @@ -14,5 +14,10 @@ struct alignas(16) CollisionUBO { static_assert(sizeof(CollisionUBO) % 16 == 0); static_assert(sizeof(CollisionUBO) == 80); +enum { + idCollisionUBO, + collisionUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/debug_layer_ubo.hpp b/include/mbgl/shaders/debug_layer_ubo.hpp index d0f6adffa38..11ef0edf974 100644 --- a/include/mbgl/shaders/debug_layer_ubo.hpp +++ b/include/mbgl/shaders/debug_layer_ubo.hpp @@ -13,5 +13,10 @@ struct alignas(16) DebugUBO { }; static_assert(sizeof(DebugUBO) % 16 == 0); +enum { + idDebugUBO, + debugUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp b/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp index 7861a3bcc2f..10dcb65436c 100644 --- a/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp +++ b/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp @@ -7,26 +7,6 @@ namespace mbgl { namespace shaders { -/// Evaluated properties that depend on the tile -struct alignas(16) FillExtrusionDrawableTilePropsUBO { - /* 0 */ std::array pattern_from; - /* 16 */ std::array pattern_to; - /* 32 */ -}; -static_assert(sizeof(FillExtrusionDrawableTilePropsUBO) == 2 * 16); - -/// Attribute interpolations -struct alignas(16) FillExtrusionInterpolateUBO { - /* 0 */ float base_t; - /* 4 */ float height_t; - /* 8 */ float color_t; - /* 12 */ float pattern_from_t; - /* 16 */ float pattern_to_t; - /* 20 */ float pad1, pad2, pad3; - /* 32 */ -}; -static_assert(sizeof(FillExtrusionInterpolateUBO) == 2 * 16); - struct alignas(16) FillExtrusionDrawableUBO { /* 0 */ std::array matrix; /* 64 */ std::array scale; @@ -56,5 +36,33 @@ struct alignas(16) FillExtrusionDrawablePropsUBO { }; static_assert(sizeof(FillExtrusionDrawablePropsUBO) == 5 * 16); +/// Evaluated properties that depend on the tile +struct alignas(16) FillExtrusionDrawableTilePropsUBO { + /* 0 */ std::array pattern_from; + /* 16 */ std::array pattern_to; + /* 32 */ +}; +static_assert(sizeof(FillExtrusionDrawableTilePropsUBO) == 2 * 16); + +/// Attribute interpolations +struct alignas(16) FillExtrusionInterpolateUBO { + /* 0 */ float base_t; + /* 4 */ float height_t; + /* 8 */ float color_t; + /* 12 */ float pattern_from_t; + /* 16 */ float pattern_to_t; + /* 20 */ float pad1, pad2, pad3; + /* 32 */ +}; +static_assert(sizeof(FillExtrusionInterpolateUBO) == 2 * 16); + +enum { + idFillExtrusionDrawableUBO, + idFillExtrusionDrawablePropsUBO, + idFillExtrusionDrawableTilePropsUBO, + idFillExtrusionInterpolateUBO, + fillExtrusionUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/fill_layer_ubo.hpp b/include/mbgl/shaders/fill_layer_ubo.hpp index 789d1fa68e3..1959a401bbb 100644 --- a/include/mbgl/shaders/fill_layer_ubo.hpp +++ b/include/mbgl/shaders/fill_layer_ubo.hpp @@ -27,6 +27,13 @@ struct alignas(16) FillInterpolateUBO { }; static_assert(sizeof(FillInterpolateUBO) % 16 == 0); +enum { + idFillDrawableUBO, + idFillEvaluatedPropsUBO, + idFillInterpolateUBO, + fillUBOCount +}; + // // Fill outline @@ -52,6 +59,13 @@ struct alignas(16) FillOutlineInterpolateUBO { }; static_assert(sizeof(FillOutlineInterpolateUBO) == 1 * 16); +enum { + idFillOutlineDrawableUBO, + idFillOutlineEvaluatedPropsUBO, + idFillOutlineInterpolateUBO, + fillOutlineUBOCount +}; + // // Fill Pattern @@ -87,6 +101,14 @@ struct alignas(16) FillPatternInterpolateUBO { }; static_assert(sizeof(FillPatternInterpolateUBO) == 1 * 16); +enum { + idFillPatternDrawableUBO, + idFillPatternTilePropsUBO, + idFillPatternEvaluatedPropsUBO, + idFillPatternInterpolateUBO, + fillPatternUBOCount +}; + // // Fill pattern outline @@ -121,5 +143,13 @@ struct alignas(16) FillOutlinePatternInterpolateUBO { }; static_assert(sizeof(FillOutlinePatternInterpolateUBO) == 1 * 16); +enum { + idFillOutlinePatternDrawableUBO, + idFillOutlinePatternTilePropsUBO, + idFillOutlinePatternEvaluatedPropsUBO, + idFillOutlinePatternInterpolateUBO, + fillOutlinePatternUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/gl/shader_group_gl.hpp b/include/mbgl/shaders/gl/shader_group_gl.hpp index 8a517470d0c..1c64db0c3f0 100644 --- a/include/mbgl/shaders/gl/shader_group_gl.hpp +++ b/include/mbgl/shaders/gl/shader_group_gl.hpp @@ -53,8 +53,14 @@ class ShaderGroupGL final : public gfx::ShaderGroup { } auto& glContext = static_cast(context); - shader = ShaderProgramGL::create( - glContext, programParameters, shaderName, firstAttribName, vert, frag, additionalDefines); + shader = ShaderProgramGL::create(glContext, + programParameters, + shaderName, + firstAttribName, + shaders::ShaderInfo::uniformBlocks, + vert, + frag, + additionalDefines); if (!shader || !registerShader(shader, shaderName)) { throw std::runtime_error("Failed to register " + shaderName + " with shader group!"); } diff --git a/include/mbgl/shaders/gl/shader_info.hpp b/include/mbgl/shaders/gl/shader_info.hpp new file mode 100644 index 00000000000..980e4faa15d --- /dev/null +++ b/include/mbgl/shaders/gl/shader_info.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include + +#include +#include + +namespace mbgl { +namespace shaders { + +struct UniformBlockInfo { + UniformBlockInfo(std::string_view name, std::size_t id); + std::string_view name; + std::size_t id; + std::size_t binding; +}; + +template +struct ShaderInfo; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/gl/shader_program_gl.hpp b/include/mbgl/shaders/gl/shader_program_gl.hpp index 9b6dc6bd660..4836a730e25 100644 --- a/include/mbgl/shaders/gl/shader_program_gl.hpp +++ b/include/mbgl/shaders/gl/shader_program_gl.hpp @@ -3,8 +3,9 @@ #include #include #include +#include #include -#include +#include #include @@ -33,6 +34,7 @@ class ShaderProgramGL final : public gfx::ShaderProgramBase { const ProgramParameters& programParameters, const std::string& name, const std::string_view firstAttribName, + const std::vector& uniformBlocksInfo, const std::string& vertexSource, const std::string& fragmentSource, const std::string& additionalDefines = "") noexcept(false); diff --git a/include/mbgl/shaders/heatmap_layer_ubo.hpp b/include/mbgl/shaders/heatmap_layer_ubo.hpp index 68ac43ea8c9..5b6bce58dca 100644 --- a/include/mbgl/shaders/heatmap_layer_ubo.hpp +++ b/include/mbgl/shaders/heatmap_layer_ubo.hpp @@ -27,5 +27,12 @@ struct alignas(16) HeatmapInterpolateUBO { }; static_assert(sizeof(HeatmapInterpolateUBO) % 16 == 0); +enum { + idHeatmapDrawableUBO, + idHeatmapEvaluatedPropsUBO, + idHeatmapInterpolateUBO, + heatmapUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/heatmap_texture_layer_ubo.hpp b/include/mbgl/shaders/heatmap_texture_layer_ubo.hpp index 73d4f89bce1..f11a550da89 100644 --- a/include/mbgl/shaders/heatmap_texture_layer_ubo.hpp +++ b/include/mbgl/shaders/heatmap_texture_layer_ubo.hpp @@ -13,5 +13,10 @@ struct alignas(16) HeatmapTextureDrawableUBO { }; static_assert(sizeof(HeatmapTextureDrawableUBO) % 16 == 0); +enum { + idHeatmapTextureDrawableUBO, + heatmapTextureUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/hillshade_layer_ubo.hpp b/include/mbgl/shaders/hillshade_layer_ubo.hpp index 77fd1a993ee..89d3f118cae 100644 --- a/include/mbgl/shaders/hillshade_layer_ubo.hpp +++ b/include/mbgl/shaders/hillshade_layer_ubo.hpp @@ -19,5 +19,11 @@ struct alignas(16) HillshadeEvaluatedPropsUBO { }; static_assert(sizeof(HillshadeEvaluatedPropsUBO) % 16 == 0); +enum { + idHillshadeDrawableUBO, + idHillshadeEvaluatedPropsUBO, + hillshadeUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp b/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp index 57ac65f3e97..7daa8338f85 100644 --- a/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp +++ b/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp @@ -14,5 +14,10 @@ struct alignas(16) HillshadePrepareDrawableUBO { }; static_assert(sizeof(HillshadePrepareDrawableUBO) % 16 == 0); +enum { + idHillshadePrepareDrawableUBO, + hillshadePrepareUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/line_layer_ubo.hpp b/include/mbgl/shaders/line_layer_ubo.hpp index 0b20b6d6e14..152daa5a555 100644 --- a/include/mbgl/shaders/line_layer_ubo.hpp +++ b/include/mbgl/shaders/line_layer_ubo.hpp @@ -5,6 +5,9 @@ namespace mbgl { namespace shaders { +// +// Line + struct alignas(16) LineDynamicUBO { /* 0 */ std::array units_to_pixels; /* 8 */ float pad1, pad2; @@ -19,8 +22,6 @@ struct alignas(16) LineUBO { }; static_assert(sizeof(LineUBO) % 16 == 0); -using LineGradientUBO = LineUBO; - struct alignas(16) LinePropertiesUBO { Color color; float blur; @@ -32,6 +33,30 @@ struct alignas(16) LinePropertiesUBO { }; static_assert(sizeof(LinePropertiesUBO) % 16 == 0); +struct alignas(16) LineInterpolationUBO { + float color_t; + float blur_t; + float opacity_t; + float gapwidth_t; + float offset_t; + float width_t; + float pad1, pad2; +}; +static_assert(sizeof(LineInterpolationUBO) % 16 == 0); + +enum { + idLineDynamicUBO, + idLineUBO, + idLinePropertiesUBO, + idLineInterpolationUBO, + lineUBOCount +}; + +// +// Line gradient + +using LineGradientUBO = LineUBO; + struct alignas(16) LineGradientPropertiesUBO { float blur; float opacity; @@ -42,6 +67,27 @@ struct alignas(16) LineGradientPropertiesUBO { }; static_assert(sizeof(LineGradientPropertiesUBO) % 16 == 0); +struct alignas(16) LineGradientInterpolationUBO { + float blur_t; + float opacity_t; + float gapwidth_t; + float offset_t; + float width_t; + float pad1, pad2, pad3; +}; +static_assert(sizeof(LineGradientInterpolationUBO) % 16 == 0); + +enum { + idLineGradientDynamicUBO, + idLineGradientUBO, + idLineGradientPropertiesUBO, + idLineGradientInterpolationUBO, + lineGradientUBOCount +}; + +// +// Line pattern + struct alignas(16) LinePatternUBO { std::array matrix; std::array scale; @@ -61,6 +107,36 @@ struct alignas(16) LinePatternPropertiesUBO { }; static_assert(sizeof(LinePatternPropertiesUBO) % 16 == 0); +struct alignas(16) LinePatternInterpolationUBO { + float blur_t; + float opacity_t; + float offset_t; + float gapwidth_t; + float width_t; + float pattern_from_t; + float pattern_to_t; + float pad1; +}; +static_assert(sizeof(LinePatternInterpolationUBO) % 16 == 0); + +struct alignas(16) LinePatternTilePropertiesUBO { + std::array pattern_from; + std::array pattern_to; +}; +static_assert(sizeof(LinePatternTilePropertiesUBO) % 16 == 0); + +enum { + idLinePatternDynamicUBO, + idLinePatternUBO, + idLinePatternPropertiesUBO, + idLinePatternInterpolationUBO, + idLinePatternTilePropertiesUBO, + linePatternUBOCount +}; + +// +// Line SDF + struct alignas(16) LineSDFUBO { std::array matrix; std::array patternscale_a; @@ -86,6 +162,29 @@ struct alignas(16) LineSDFPropertiesUBO { }; static_assert(sizeof(LineSDFPropertiesUBO) % 16 == 0); +struct alignas(16) LineSDFInterpolationUBO { + float color_t; + float blur_t; + float opacity_t; + float gapwidth_t; + float offset_t; + float width_t; + float floorwidth_t; + float pad1; +}; +static_assert(sizeof(LineSDFInterpolationUBO) % 16 == 0); + +enum { + idLineSDFDynamicUBO, + idLineSDFUBO, + idLineSDFPropertiesUBO, + idLineSDFInterpolationUBO, + lineSDFUBOCount +}; + +// +// Line basic + struct alignas(16) LineBasicUBO { std::array matrix; std::array units_to_pixels; @@ -102,58 +201,11 @@ struct alignas(16) LineBasicPropertiesUBO { }; static_assert(sizeof(LineBasicPropertiesUBO) % 16 == 0); -/// Property interpolation UBOs -struct alignas(16) LineInterpolationUBO { - float color_t; - float blur_t; - float opacity_t; - float gapwidth_t; - float offset_t; - float width_t; - float pad1, pad2; -}; -static_assert(sizeof(LineInterpolationUBO) % 16 == 0); - -struct alignas(16) LineGradientInterpolationUBO { - float blur_t; - float opacity_t; - float gapwidth_t; - float offset_t; - float width_t; - float pad1, pad2, pad3; -}; -static_assert(sizeof(LineGradientInterpolationUBO) % 16 == 0); - -struct alignas(16) LinePatternInterpolationUBO { - float blur_t; - float opacity_t; - float offset_t; - float gapwidth_t; - float width_t; - float pattern_from_t; - float pattern_to_t; - float pad1; +enum { + idLineBasicUBO, + idLineBasicPropertiesUBO, + lineBasicUBOCount }; -static_assert(sizeof(LinePatternInterpolationUBO) % 16 == 0); - -struct alignas(16) LineSDFInterpolationUBO { - float color_t; - float blur_t; - float opacity_t; - float gapwidth_t; - float offset_t; - float width_t; - float floorwidth_t; - float pad1; -}; -static_assert(sizeof(LineSDFInterpolationUBO) % 16 == 0); - -/// Evaluated properties that depend on the tile -struct alignas(16) LinePatternTilePropertiesUBO { - std::array pattern_from; - std::array pattern_to; -}; -static_assert(sizeof(LinePatternTilePropertiesUBO) % 16 == 0); } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/mtl/shader_program.hpp b/include/mbgl/shaders/mtl/shader_program.hpp index 7a63be32345..bea1380c957 100644 --- a/include/mbgl/shaders/mtl/shader_program.hpp +++ b/include/mbgl/shaders/mtl/shader_program.hpp @@ -21,13 +21,12 @@ struct AttributeInfo { StringIdentity nameID; }; struct UniformBlockInfo { - UniformBlockInfo(std::size_t index, bool vertex, bool fragment, std::size_t size, std::string_view name); + UniformBlockInfo(std::size_t index, bool vertex, bool fragment, std::size_t size, std::size_t id); std::size_t index; bool vertex; bool fragment; std::size_t size; - std::string_view name; - StringIdentity nameID; + std::size_t id; }; struct TextureInfo { TextureInfo(std::size_t index, std::string_view name); diff --git a/include/mbgl/shaders/mtl/symbol_icon.hpp b/include/mbgl/shaders/mtl/symbol_icon.hpp index 896cc55b57f..9c658ae122a 100644 --- a/include/mbgl/shaders/mtl/symbol_icon.hpp +++ b/include/mbgl/shaders/mtl/symbol_icon.hpp @@ -45,11 +45,11 @@ struct FragmentStage { }; FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], - device const SymbolDrawableUBO& drawable [[buffer(8)]], - device const SymbolDynamicUBO& dynamic [[buffer(9)]], - device const SymbolDrawablePaintUBO& paint [[buffer(10)]], - device const SymbolDrawableTilePropsUBO& props [[buffer(11)]], - device const SymbolDrawableInterpolateUBO& interp [[buffer(12)]]) { + device const SymbolDrawableUBO& drawable [[buffer(6)]], + device const SymbolDynamicUBO& dynamic [[buffer(7)]], + device const SymbolDrawablePaintUBO& paint [[buffer(8)]], + device const SymbolDrawableTilePropsUBO& props [[buffer(9)]], + device const SymbolDrawableInterpolateUBO& interp [[buffer(10)]]) { const float2 a_pos = vertx.pos_offset.xy; const float2 a_offset = vertx.pos_offset.zw; @@ -122,8 +122,8 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], } half4 fragment fragmentMain(FragmentStage in [[stage_in]], - device const SymbolDrawableUBO& drawable [[buffer(8)]], - device const SymbolDrawablePaintUBO& paint [[buffer(10)]], + device const SymbolDrawableUBO& drawable [[buffer(6)]], + device const SymbolDrawablePaintUBO& paint [[buffer(8)]], texture2d image [[texture(0)]], sampler image_sampler [[sampler(0)]]) { #if defined(OVERDRAW_INSPECTOR) diff --git a/include/mbgl/shaders/raster_layer_ubo.hpp b/include/mbgl/shaders/raster_layer_ubo.hpp index f289db53dba..4a8f20d2631 100644 --- a/include/mbgl/shaders/raster_layer_ubo.hpp +++ b/include/mbgl/shaders/raster_layer_ubo.hpp @@ -22,5 +22,10 @@ struct alignas(16) RasterDrawableUBO { static_assert(sizeof(RasterDrawableUBO) == 128); static_assert(sizeof(RasterDrawableUBO) % 16 == 0); +enum { + idRasterDrawableUBO, + rasterUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/symbol_layer_ubo.hpp b/include/mbgl/shaders/symbol_layer_ubo.hpp index 7c93fb56764..82376a5c6e9 100644 --- a/include/mbgl/shaders/symbol_layer_ubo.hpp +++ b/include/mbgl/shaders/symbol_layer_ubo.hpp @@ -69,5 +69,14 @@ struct alignas(16) SymbolDrawablePaintUBO { }; static_assert(sizeof(SymbolDrawablePaintUBO) == 3 * 16); +enum { + idSymbolDrawableUBO, + idSymbolDynamicUBO, + idSymbolDrawablePaintUBO, + idSymbolDrawableTilePropsUBO, + idSymbolDrawableInterpolateUBO, + symbolUBOCount +}; + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/ubo_max_count.hpp b/include/mbgl/shaders/ubo_max_count.hpp new file mode 100644 index 00000000000..32f2620978f --- /dev/null +++ b/include/mbgl/shaders/ubo_max_count.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace shaders { + +static constexpr auto maxUBOCountPerShader = std::max({static_cast(backgroundUBOCount), + static_cast(circleUBOCount), + static_cast(collisionUBOCount), + static_cast(debugUBOCount), + static_cast(fillUBOCount), + static_cast(fillOutlineUBOCount), + static_cast(fillPatternUBOCount), + static_cast(fillOutlinePatternUBOCount), + static_cast(fillExtrusionUBOCount), + static_cast(heatmapUBOCount), + static_cast(heatmapTextureUBOCount), + static_cast(hillshadeUBOCount), + static_cast(hillshadePrepareUBOCount), + static_cast(lineUBOCount), + static_cast(lineGradientUBOCount), + static_cast(linePatternUBOCount), + static_cast(lineSDFUBOCount), + static_cast(lineBasicUBOCount), + static_cast(rasterUBOCount), + static_cast(symbolUBOCount)}); + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/gfx/uniform_block.cpp b/src/mbgl/gfx/uniform_block.cpp index 5710c8e9180..c8023071148 100644 --- a/src/mbgl/gfx/uniform_block.cpp +++ b/src/mbgl/gfx/uniform_block.cpp @@ -6,34 +6,32 @@ namespace gfx { std::unique_ptr UniformBlockArray::nullref = nullptr; UniformBlockArray::UniformBlockArray(UniformBlockArray&& other) - : uniformBlockMap(std::move(other.uniformBlockMap)) {} + : uniformBlockVector(std::move(other.uniformBlockVector)) {} UniformBlockArray& UniformBlockArray::operator=(UniformBlockArray&& other) { - uniformBlockMap = std::move(other.uniformBlockMap); + uniformBlockVector = std::move(other.uniformBlockVector); return *this; } UniformBlockArray& UniformBlockArray::operator=(const UniformBlockArray& other) { - uniformBlockMap.clear(); - for (const auto& kv : other.uniformBlockMap) { - add(kv.first, copy(*kv.second)); + for (size_t id = 0; id < other.uniformBlockVector.size(); id++) { + uniformBlockVector[id] = copy(*other.uniformBlockVector[id]); } return *this; } -const std::unique_ptr& UniformBlockArray::get(const StringIdentity id) const { - const auto result = uniformBlockMap.find(id); - return (result != uniformBlockMap.end()) ? result->second : nullref; +const std::unique_ptr& UniformBlockArray::get(const size_t id) const { + const auto& result = (id < uniformBlockVector.size()) ? uniformBlockVector[id] : nullref; + return (result != nullptr) ? result : nullref; } -const std::unique_ptr& UniformBlockArray::add(const StringIdentity id, int index, std::size_t size) { - const auto result = uniformBlockMap.insert(std::make_pair(id, std::unique_ptr())); - if (result.second) { - result.first->second = create(index, size); - return result.first->second; - } else { +const std::unique_ptr& UniformBlockArray::set(const size_t id, const size_t index, std::size_t size) { + assert(id < uniformBlockVector.size()); + if (id >= uniformBlockVector.size()) { return nullref; } + uniformBlockVector[id] = create(static_cast(index), size); + return uniformBlockVector[id]; } } // namespace gfx diff --git a/src/mbgl/gfx/uniform_buffer.cpp b/src/mbgl/gfx/uniform_buffer.cpp index b38b70b87a0..8370eee4c24 100644 --- a/src/mbgl/gfx/uniform_buffer.cpp +++ b/src/mbgl/gfx/uniform_buffer.cpp @@ -8,34 +8,36 @@ namespace gfx { std::shared_ptr UniformBufferArray::nullref = nullptr; UniformBufferArray::UniformBufferArray(UniformBufferArray&& other) - : uniformBufferMap(std::move(other.uniformBufferMap)) {} + : uniformBufferVector(std::move(other.uniformBufferVector)) {} UniformBufferArray& UniformBufferArray::operator=(UniformBufferArray&& other) { - uniformBufferMap = std::move(other.uniformBufferMap); + uniformBufferVector = std::move(other.uniformBufferVector); return *this; } UniformBufferArray& UniformBufferArray::operator=(const UniformBufferArray& other) { - uniformBufferMap.clear(); - for (const auto& kv : other.uniformBufferMap) { - add(kv.first, copy(*kv.second)); + for (size_t id = 0; id < other.uniformBufferVector.size(); id++) { + uniformBufferVector[id] = other.uniformBufferVector[id]; } return *this; } -const std::shared_ptr& UniformBufferArray::get(const StringIdentity id) const { - const auto result = uniformBufferMap.find(id); - return (result != uniformBufferMap.end()) ? result->second : nullref; +const std::shared_ptr& UniformBufferArray::get(const size_t id) const { + const auto& result = (id < uniformBufferVector.size()) ? uniformBufferVector[id] : nullref; + return (result != nullptr) ? result : nullref; } -const std::shared_ptr& UniformBufferArray::addOrReplace(const StringIdentity id, - std::shared_ptr uniformBuffer) { - const auto result = uniformBufferMap.insert(std::make_pair(id, std::shared_ptr())); - result.first->second = std::move(uniformBuffer); - return result.first->second; +const std::shared_ptr& UniformBufferArray::set(const size_t id, + std::shared_ptr uniformBuffer) { + assert(id < uniformBufferVector.size()); + if (id >= uniformBufferVector.size()) { + return nullref; + } + uniformBufferVector[id] = std::move(uniformBuffer); + return uniformBufferVector[id]; } -void UniformBufferArray::createOrUpdate(const StringIdentity id, +void UniformBufferArray::createOrUpdate(const size_t id, const std::vector& data, gfx::Context& context, bool persistent) { @@ -43,22 +45,11 @@ void UniformBufferArray::createOrUpdate(const StringIdentity id, } void UniformBufferArray::createOrUpdate( - const StringIdentity id, const void* data, const std::size_t size, gfx::Context& context, bool persistent) { + const size_t id, const void* data, const std::size_t size, gfx::Context& context, bool persistent) { if (auto& ubo = get(id); ubo && ubo->getSize() == size) { ubo->update(data, size); } else { - add(id, context.createUniformBuffer(data, size, persistent)); - } -} - -const std::shared_ptr& UniformBufferArray::add(const StringIdentity id, - std::shared_ptr&& uniformBuffer) { - const auto result = uniformBufferMap.insert(std::make_pair(id, std::shared_ptr())); - if (result.second) { - result.first->second = std::move(uniformBuffer); - return result.first->second; - } else { - return nullref; + uniformBufferVector[id] = context.createUniformBuffer(data, size, persistent); } } diff --git a/src/mbgl/gl/drawable_gl.cpp b/src/mbgl/gl/drawable_gl.cpp index f9b2728d338..4560f46651c 100644 --- a/src/mbgl/gl/drawable_gl.cpp +++ b/src/mbgl/gl/drawable_gl.cpp @@ -104,29 +104,34 @@ void DrawableGL::setVertexAttrNameId(const StringIdentity id) { void DrawableGL::bindUniformBuffers() const { if (shader) { - const auto& shaderGL = static_cast(*shader); - for (const auto& element : shaderGL.getUniformBlocks().getMap()) { - const auto& uniformBuffer = getUniformBuffers().get(element.first); + const auto& uniformBlocks = shader->getUniformBlocks(); + for (size_t id = 0; id < uniformBlocks.allocatedSize(); id++) { + const auto& block = uniformBlocks.get(id); + if (!block) continue; + const auto& uniformBuffer = getUniformBuffers().get(id); + assert(uniformBuffer && "UBO missing, drawable skipped"); if (!uniformBuffer) { using namespace std::string_literals; const auto tileIDStr = getTileID() ? util::toString(*getTileID()) : ""; Log::Error(Event::General, - "bindUniformBuffers: UBO "s + std::string(stringIndexer().get(element.first)) + - " not found for " + util::toString(getID()) + " / " + getName() + " / " + tileIDStr + - ". skipping."); + "bindUniformBuffers: UBO "s + util::toString(block->getIndex()) + " not found for " + + util::toString(getID()) + " / " + getName() + " / " + tileIDStr + ". skipping."); assert(false); continue; } - element.second->bindBuffer(*uniformBuffer); + block->bindBuffer(*uniformBuffer); } } } void DrawableGL::unbindUniformBuffers() const { if (shader) { - const auto& shaderGL = static_cast(*shader); - for (const auto& element : shaderGL.getUniformBlocks().getMap()) { - element.second->unbindBuffer(); + const auto& uniformBlocks = shader->getUniformBlocks(); + for (size_t id = 0; id < uniformBlocks.allocatedSize(); id++) { + const auto& block = uniformBlocks.get(id); + if (block) { + block->unbindBuffer(); + } } } } diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index c8f948a4f61..79a5caed8f6 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -361,21 +361,22 @@ void Drawable::bindAttributes(RenderPass& renderPass) const noexcept { void Drawable::bindUniformBuffers(RenderPass& renderPass) const noexcept { if (shader) { - const auto& shaderMTL = static_cast(*shader); - for (const auto& element : shaderMTL.getUniformBlocks().getMap()) { - const auto& uniformBuffer = getUniformBuffers().get(element.first); + const auto& uniformBlocks = shader->getUniformBlocks(); + for (size_t id = 0; id < uniformBlocks.allocatedSize(); id++) { + const auto& block = uniformBlocks.get(id); + if (!block) continue; + const auto& uniformBuffer = getUniformBuffers().get(id); assert(uniformBuffer && "UBO missing, drawable skipped"); if (uniformBuffer) { const auto& buffer = static_cast(*uniformBuffer.get()); const auto& resource = buffer.getBufferResource(); - const auto& block = static_cast(*element.second); - const auto index = block.getIndex(); + const auto& mtlBlock = static_cast(*block); - if (block.getBindVertex()) { - renderPass.bindVertex(resource, /*offset=*/0, index); + if (mtlBlock.getBindVertex()) { + renderPass.bindVertex(resource, /*offset=*/0, block->getIndex()); } - if (block.getBindFragment()) { - renderPass.bindFragment(resource, /*offset=*/0, index); + if (mtlBlock.getBindFragment()) { + renderPass.bindFragment(resource, /*offset=*/0, block->getIndex()); } } } diff --git a/src/mbgl/renderer/layers/background_layer_tweaker.cpp b/src/mbgl/renderer/layers/background_layer_tweaker.cpp index faffc5249ec..1fb49074f7e 100644 --- a/src/mbgl/renderer/layers/background_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/background_layer_tweaker.cpp @@ -19,8 +19,7 @@ using namespace shaders; #if !defined(NDEBUG) constexpr auto BackgroundPatternShaderName = "BackgroundPatternShader"; #endif -static const StringIdentity idBackgroundDrawableUBOName = stringIndexer().get("BackgroundDrawableUBO"); -static const StringIdentity idBackgroundLayerUBOName = stringIndexer().get("BackgroundLayerUBO"); + static const StringIdentity idTexUniformName = stringIndexer().get("u_image"); void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { @@ -73,7 +72,7 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara const BackgroundDrawableUBO drawableUBO = {/* .matrix = */ util::cast(matrix)}; auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idBackgroundDrawableUBOName, &drawableUBO, context); + uniforms.createOrUpdate(idBackgroundDrawableUBO, &drawableUBO, context); if (hasPattern) { if (!samplerLocation.has_value()) { @@ -115,7 +114,7 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara /* .opacity = */ evaluated.get(), /* .pad1 = */ 0, }; - uniforms.createOrUpdate(idBackgroundLayerUBOName, &layerUBO, context); + uniforms.createOrUpdate(idBackgroundLayerUBO, &layerUBO, context); } else { // UBOs can be shared if (!backgroundLayerBuffer) { @@ -126,7 +125,7 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara 0}; backgroundLayerBuffer = context.createUniformBuffer(&layerUBO, sizeof(layerUBO)); } - uniforms.addOrReplace(idBackgroundLayerUBOName, backgroundLayerBuffer); + uniforms.set(idBackgroundLayerUBO, backgroundLayerBuffer); } }); } diff --git a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp index 91a1ef0e9be..0af7eefd837 100644 --- a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp @@ -22,10 +22,6 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idCircleDrawableUBOName = stringIndexer().get("CircleDrawableUBO"); -static const StringIdentity idCirclePaintParamsUBOName = stringIndexer().get("CirclePaintParamsUBO"); -static const StringIdentity idCircleEvaluatedPropsUBOName = stringIndexer().get("CircleEvaluatedPropsUBO"); - void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& evaluated = static_cast(*evaluatedProperties).evaluated; @@ -76,8 +72,8 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.addOrReplace(idCirclePaintParamsUBOName, paintParamsUniformBuffer); - uniforms.addOrReplace(idCircleEvaluatedPropsUBOName, evaluatedPropsUniformBuffer); + uniforms.set(idCirclePaintParamsUBO, paintParamsUniformBuffer); + uniforms.set(idCircleEvaluatedPropsUBO, evaluatedPropsUniformBuffer); const auto& translation = evaluated.get(); const auto anchor = evaluated.get(); @@ -95,7 +91,7 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete /* .extrude_scale = */ extrudeScale, /* .padding = */ 0}; - uniforms.createOrUpdate(idCircleDrawableUBOName, &drawableUBO, context); + uniforms.createOrUpdate(idCircleDrawableUBO, &drawableUBO, context); }); } diff --git a/src/mbgl/renderer/layers/collision_layer_tweaker.cpp b/src/mbgl/renderer/layers/collision_layer_tweaker.cpp index 5e5f9700618..53aed5a541c 100644 --- a/src/mbgl/renderer/layers/collision_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/collision_layer_tweaker.cpp @@ -21,9 +21,6 @@ namespace mbgl { using namespace style; using namespace shaders; -const StringIdentity CollisionLayerTweaker::idCollisionCircleUBOName = stringIndexer().get(CollisionCircleUBOName); -const StringIdentity CollisionLayerTweaker::idCollisionBoxUBOName = stringIndexer().get(CollisionBoxUBOName); - void CollisionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { if (layerGroup.empty()) { return; @@ -65,19 +62,8 @@ void CollisionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam /*.camera_to_center_distance*/ parameters.state.getCameraToCenterDistance(), /*.overscale_factor*/ static_cast(drawable.getTileID()->overscaleFactor())}; - const auto shader = drawable.getShader(); - const auto& shaderUniforms = shader->getUniformBlocks(); auto& uniforms = drawable.mutableUniformBuffers(); - - if (shaderUniforms.get(idCollisionBoxUBOName)) { - // collision box - uniforms.createOrUpdate(idCollisionBoxUBOName, &drawableUBO, context); - } else if (shaderUniforms.get(idCollisionCircleUBOName)) { - // collision circle - uniforms.createOrUpdate(idCollisionCircleUBOName, &drawableUBO, context); - } else { - Log::Error(Event::General, "Collision shader uniform name unknown."); - } + uniforms.createOrUpdate(idCollisionUBO, &drawableUBO, context); }); } diff --git a/src/mbgl/renderer/layers/collision_layer_tweaker.hpp b/src/mbgl/renderer/layers/collision_layer_tweaker.hpp index 42c26c9c6fc..34ade8bf978 100644 --- a/src/mbgl/renderer/layers/collision_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/collision_layer_tweaker.hpp @@ -19,11 +19,6 @@ class CollisionLayerTweaker : public LayerTweaker { ~CollisionLayerTweaker() override = default; void execute(LayerGroupBase&, const PaintParameters&) override; - - static constexpr auto CollisionCircleUBOName = "CollisionCircleUBO"; - static const StringIdentity idCollisionCircleUBOName; - static constexpr auto CollisionBoxUBOName = "CollisionBoxUBO"; - static const StringIdentity idCollisionBoxUBOName; }; } // namespace mbgl diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp index 4b1e97510ee..b65cb907276 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp @@ -27,17 +27,10 @@ using namespace shaders; using namespace style; namespace { -const StringIdentity idFillExtrusionDrawableUBOName = stringIndexer().get("FillExtrusionDrawableUBO"); -const StringIdentity idFillExtrusionDrawablePropsUBOName = stringIndexer().get("FillExtrusionDrawablePropsUBO"); const StringIdentity idTexImageName = stringIndexer().get("u_image"); } // namespace -const StringIdentity FillExtrusionLayerTweaker::idFillExtrusionTilePropsUBOName = stringIndexer().get( - "FillExtrusionDrawableTilePropsUBO"); -const StringIdentity FillExtrusionLayerTweaker::idFillExtrusionInterpolateUBOName = stringIndexer().get( - "FillExtrusionInterpolateUBO"); - void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& props = static_cast(*evaluatedProperties); @@ -81,7 +74,7 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintP const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.addOrReplace(idFillExtrusionDrawablePropsUBOName, propsBuffer); + uniforms.set(idFillExtrusionDrawablePropsUBO, propsBuffer); const auto& translation = evaluated.get(); const auto anchor = evaluated.get(); @@ -119,7 +112,7 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintP /* .height_factor = */ heightFactor, /* .pad = */ 0}; - uniforms.createOrUpdate(idFillExtrusionDrawableUBOName, &drawableUBO, context); + uniforms.createOrUpdate(idFillExtrusionDrawableUBO, &drawableUBO, context); }); } diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp index 85304035920..d6fffe21981 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp @@ -20,9 +20,6 @@ class FillExtrusionLayerTweaker : public LayerTweaker { void execute(LayerGroupBase&, const PaintParameters&) override; - static const StringIdentity idFillExtrusionTilePropsUBOName; - static const StringIdentity idFillExtrusionInterpolateUBOName; - private: gfx::UniformBufferPtr propsBuffer; diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp index bbd34ca1000..63565a87d7f 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -24,33 +25,6 @@ namespace mbgl { using namespace style; -static const StringIdentity idFillDrawableUBOName = stringIndexer().get("FillDrawableUBO"); -static const StringIdentity idFillDrawablePropsUBOName = stringIndexer().get("FillDrawablePropsUBO"); -static const StringIdentity idFillEvaluatedPropsUBOName = stringIndexer().get("FillEvaluatedPropsUBO"); - -const StringIdentity FillLayerTweaker::idFillTilePropsUBOName = stringIndexer().get("FillDrawableTilePropsUBO"); -const StringIdentity FillLayerTweaker::idFillInterpolateUBOName = stringIndexer().get("FillInterpolateUBO"); -const StringIdentity FillLayerTweaker::idFillOutlineInterpolateUBOName = stringIndexer().get( - "FillOutlineInterpolateUBO"); - -static const StringIdentity idFillOutlineDrawableUBOName = stringIndexer().get("FillOutlineDrawableUBO"); -static const StringIdentity idFillOutlineEvaluatedPropsUBOName = stringIndexer().get("FillOutlineEvaluatedPropsUBO"); - -static const StringIdentity idFillOutlineInterpolateUBOName = stringIndexer().get("FillOutlineInterpolateUBO"); - -static const StringIdentity idFillPatternDrawableUBOName = stringIndexer().get("FillPatternDrawableUBO"); -static const StringIdentity idFillPatternInterpolateUBOName = stringIndexer().get("FillPatternInterpolateUBO"); -static const StringIdentity idFillPatternEvaluatedPropsUBOName = stringIndexer().get("FillPatternEvaluatedPropsUBO"); -static const StringIdentity idFillPatternTilePropsUBOName = stringIndexer().get("FillPatternTilePropsUBO"); - -static const StringIdentity idFillOutlinePatternDrawableUBOName = stringIndexer().get("FillOutlinePatternDrawableUBO"); -static const StringIdentity idFillOutlinePatternInterpolateUBOName = stringIndexer().get( - "FillOutlinePatternInterpolateUBO"); -static const StringIdentity idFillOutlinePatternEvaluatedPropsUBOName = stringIndexer().get( - "FillOutlinePatternEvaluatedPropsUBO"); -static const StringIdentity idFillOutlinePatternTilePropsUBOName = stringIndexer().get( - "FillOutlinePatternTilePropsUBO"); - static const StringIdentity idTexImageName = stringIndexer().get("u_image"); using namespace shaders; @@ -176,53 +150,68 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters } auto& uniforms = drawable.mutableUniformBuffers(); - if (uniforms.get(idFillInterpolateUBOName)) { - UpdateFillUniformBuffers(); - - uniforms.addOrReplace(idFillEvaluatedPropsUBOName, fillPropsUniformBuffer); - - const FillDrawableUBO drawableUBO = {/*.matrix=*/util::cast(matrix)}; - uniforms.createOrUpdate(idFillDrawableUBOName, &drawableUBO, context); - } else if (uniforms.get(idFillOutlineInterpolateUBOName)) { - UpdateFillOutlineUniformBuffers(); - - uniforms.addOrReplace(idFillOutlineEvaluatedPropsUBOName, fillOutlinePropsUniformBuffer); - - const FillOutlineDrawableUBO drawableUBO = { - /*.matrix=*/util::cast(matrix), - /*.world=*/{(float)renderableSize.width, (float)renderableSize.height}, - /* pad1 */ 0, - /* pad2 */ 0}; - uniforms.createOrUpdate(idFillOutlineDrawableUBOName, &drawableUBO, context); - } else if (uniforms.get(idFillPatternInterpolateUBOName)) { - UpdateFillPatternUniformBuffers(); - - uniforms.addOrReplace(idFillPatternEvaluatedPropsUBOName, fillPatternPropsUniformBuffer); - - const FillPatternDrawableUBO drawableUBO = { - /*.matrix=*/util::cast(matrix), - /*.scale=*/{pixelRatio, tileRatio, crossfade.fromScale, crossfade.toScale}, - /*.pixel_coord_upper=*/{static_cast(pixelX >> 16), static_cast(pixelY >> 16)}, - /*.pixel_coord_lower=*/{static_cast(pixelX & 0xFFFF), static_cast(pixelY & 0xFFFF)}, - /*.texsize=*/{static_cast(textureSize.width), static_cast(textureSize.height)}, - 0, - 0, - }; - uniforms.createOrUpdate(idFillPatternDrawableUBOName, &drawableUBO, context); - } else if (uniforms.get(idFillOutlinePatternInterpolateUBOName)) { - UpdateFillOutlinePatternUniformBuffers(); - - uniforms.addOrReplace(idFillOutlinePatternEvaluatedPropsUBOName, fillOutlinePatternPropsUniformBuffer); - - const FillOutlinePatternDrawableUBO drawableUBO = { - /*.matrix=*/util::cast(matrix), - /*.scale=*/{pixelRatio, tileRatio, crossfade.fromScale, crossfade.toScale}, - /*.world=*/{(float)renderableSize.width, (float)renderableSize.height}, - /*.pixel_coord_upper=*/{static_cast(pixelX >> 16), static_cast(pixelY >> 16)}, - /*.pixel_coord_lower=*/{static_cast(pixelX & 0xFFFF), static_cast(pixelY & 0xFFFF)}, - /*.texsize=*/{static_cast(textureSize.width), static_cast(textureSize.height)}, - }; - uniforms.createOrUpdate(idFillOutlinePatternDrawableUBOName, &drawableUBO, context); + switch (static_cast(drawable.getType())) { + case RenderFillLayer::FillVariant::Fill: { + UpdateFillUniformBuffers(); + + uniforms.set(idFillEvaluatedPropsUBO, fillPropsUniformBuffer); + + const FillDrawableUBO drawableUBO = {/*.matrix=*/util::cast(matrix)}; + uniforms.createOrUpdate(idFillDrawableUBO, &drawableUBO, context); + break; + } + case RenderFillLayer::FillVariant::FillOutline: { + UpdateFillOutlineUniformBuffers(); + + uniforms.set(idFillOutlineEvaluatedPropsUBO, fillOutlinePropsUniformBuffer); + + const FillOutlineDrawableUBO drawableUBO = { + /*.matrix=*/util::cast(matrix), + /*.world=*/{(float)renderableSize.width, (float)renderableSize.height}, + /* pad1 */ 0, + /* pad2 */ 0}; + uniforms.createOrUpdate(idFillOutlineDrawableUBO, &drawableUBO, context); + break; + } + case RenderFillLayer::FillVariant::FillPattern: { + UpdateFillPatternUniformBuffers(); + + uniforms.set(idFillPatternEvaluatedPropsUBO, fillPatternPropsUniformBuffer); + + const FillPatternDrawableUBO drawableUBO = { + /*.matrix=*/util::cast(matrix), + /*.scale=*/{pixelRatio, tileRatio, crossfade.fromScale, crossfade.toScale}, + /*.pixel_coord_upper=*/{static_cast(pixelX >> 16), static_cast(pixelY >> 16)}, + /*.pixel_coord_lower=*/{static_cast(pixelX & 0xFFFF), static_cast(pixelY & 0xFFFF)}, + /*.texsize=*/{static_cast(textureSize.width), static_cast(textureSize.height)}, + 0, + 0, + }; + uniforms.createOrUpdate(idFillPatternDrawableUBO, &drawableUBO, context); + break; + } + case RenderFillLayer::FillVariant::FillOutlinePattern: { + UpdateFillOutlinePatternUniformBuffers(); + + uniforms.set(idFillOutlinePatternEvaluatedPropsUBO, fillOutlinePatternPropsUniformBuffer); + + const FillOutlinePatternDrawableUBO drawableUBO = { + /*.matrix=*/util::cast(matrix), + /*.scale=*/{pixelRatio, tileRatio, crossfade.fromScale, crossfade.toScale}, + /*.world=*/{(float)renderableSize.width, (float)renderableSize.height}, + /*.pixel_coord_upper=*/{static_cast(pixelX >> 16), static_cast(pixelY >> 16)}, + /*.pixel_coord_lower=*/{static_cast(pixelX & 0xFFFF), static_cast(pixelY & 0xFFFF)}, + /*.texsize=*/{static_cast(textureSize.width), static_cast(textureSize.height)}, + }; + uniforms.createOrUpdate(idFillOutlinePatternDrawableUBO, &drawableUBO, context); + break; + } + default: { +#ifndef NDEBUG + mbgl::Log::Error(mbgl::Event::Render, "Invalid fill variant type supplied during drawable update!"); +#endif + break; + } } }); diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp index f2fb7583f45..92d98340482 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp @@ -20,10 +20,6 @@ class FillLayerTweaker : public LayerTweaker { void execute(LayerGroupBase&, const PaintParameters&) override; - static const StringIdentity idFillTilePropsUBOName; - static const StringIdentity idFillInterpolateUBOName; - static const StringIdentity idFillOutlineInterpolateUBOName; - private: gfx::UniformBufferPtr fillPropsUniformBuffer; gfx::UniformBufferPtr fillOutlinePropsUniformBuffer; diff --git a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp index ed9ced5f4cf..a699bed7ec8 100644 --- a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp @@ -20,9 +20,6 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idHeatmapDrawableUBOName = stringIndexer().get("HeatmapDrawableUBO"); -static const StringIdentity idHeatmapEvaluatedPropsUBOName = stringIndexer().get("HeatmapEvaluatedPropsUBO"); - void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto zoom = parameters.state.getZoom(); @@ -57,7 +54,7 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.addOrReplace(idHeatmapEvaluatedPropsUBOName, getPropsBuffer()); + uniforms.set(idHeatmapEvaluatedPropsUBO, getPropsBuffer()); constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; @@ -68,7 +65,7 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet /* .extrude_scale = */ tileID.pixelsToTileUnits(1.0f, static_cast(zoom)), /* .padding = */ {0}}; - uniforms.createOrUpdate(idHeatmapDrawableUBOName, &drawableUBO, context); + uniforms.createOrUpdate(idHeatmapDrawableUBO, &drawableUBO, context); }); propertiesUpdated = false; diff --git a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp index d964bbddd6d..436e871be66 100644 --- a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp @@ -16,8 +16,6 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idHeatmapTextureDrawableUBOName = stringIndexer().get("HeatmapTextureDrawableUBO"); - void HeatmapTextureLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; @@ -50,7 +48,7 @@ void HeatmapTextureLayerTweaker::execute(LayerGroupBase& layerGroup, const Paint if (!checkTweakDrawable(drawable)) { return; } - drawable.mutableUniformBuffers().addOrReplace(idHeatmapTextureDrawableUBOName, getDrawableUBO()); + drawable.mutableUniformBuffers().set(idHeatmapTextureDrawableUBO, getDrawableUBO()); }); } diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp index 28d2d0c3584..5a38e2aaebd 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp @@ -16,9 +16,6 @@ using namespace style; using namespace shaders; namespace { -const StringIdentity idHillshadeDrawableUBOName = stringIndexer().get("HillshadeDrawableUBO"); -const StringIdentity idHillshadeEvaluatedPropsUBOName = stringIndexer().get("HillshadeEvaluatedPropsUBO"); - std::array getLatRange(const UnwrappedTileID& id) { const LatLng latlng0 = LatLng(id); const LatLng latlng1 = LatLng(UnwrappedTileID(id.canonical.z, id.canonical.x, id.canonical.y + 1)); @@ -66,14 +63,14 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.addOrReplace(idHillshadeEvaluatedPropsUBOName, getPropsBuffer()); + uniforms.set(idHillshadeEvaluatedPropsUBO, getPropsBuffer()); const auto matrix = getTileMatrix( tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, false, false, drawable, true); HillshadeDrawableUBO drawableUBO = {/* .matrix = */ util::cast(matrix), /* .latrange = */ getLatRange(tileID), /* .light = */ getLight(parameters, evaluated)}; - uniforms.createOrUpdate(idHillshadeDrawableUBOName, &drawableUBO, parameters.context); + uniforms.createOrUpdate(idHillshadeDrawableUBO, &drawableUBO, parameters.context); }); propertiesUpdated = false; diff --git a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp index 4823460a50c..6ba4d46649e 100644 --- a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp @@ -17,8 +17,6 @@ using namespace style; using namespace shaders; namespace { -const StringIdentity idHillshadePrepareDrawableUBOName = stringIndexer().get("HillshadePrepareDrawableUBO"); - // https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb constexpr std::array unpackMapbox = {{6553.6f, 25.6f, 0.1f, 10000.0f}}; @@ -59,7 +57,7 @@ void HillshadePrepareLayerTweaker::execute(LayerGroupBase& layerGroup, const Pai /* .maxzoom = */ static_cast(drawableData.maxzoom)}; drawable.mutableUniformBuffers().createOrUpdate( - idHillshadePrepareDrawableUBOName, &drawableUBO, parameters.context); + idHillshadePrepareDrawableUBO, &drawableUBO, parameters.context); }); } diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.cpp b/src/mbgl/renderer/layers/line_layer_tweaker.cpp index f24ceccf1b3..d3256c706bd 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.cpp @@ -25,15 +25,6 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idLineUBOName = stringIndexer().get("LineUBO"); -static const StringIdentity idLinePropertiesUBOName = stringIndexer().get("LinePropertiesUBO"); -static const StringIdentity idLineGradientUBOName = stringIndexer().get("LineGradientUBO"); -static const StringIdentity idLineGradientPropertiesUBOName = stringIndexer().get("LineGradientPropertiesUBO"); -static const StringIdentity idLinePatternUBOName = stringIndexer().get("LinePatternUBO"); -static const StringIdentity idLinePatternPropertiesUBOName = stringIndexer().get("LinePatternPropertiesUBO"); -static const StringIdentity idLineSDFUBOName = stringIndexer().get("LineSDFUBO"); -static const StringIdentity idLineSDFPropertiesUBOName = stringIndexer().get("LineSDFPropertiesUBO"); -static const StringIdentity idLineDynamicUBOName = stringIndexer().get("LineDynamicUBO"); static const StringIdentity idTexImageName = stringIndexer().get("u_image"); void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { @@ -136,8 +127,6 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters const auto matrix = getTileMatrix( tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits, drawable); - uniforms.addOrReplace(idLineDynamicUBOName, dynamicBuffer); - const LineType type = static_cast(drawable.getType()); switch (type) { case LineType::Simple: { @@ -146,10 +135,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, 0, 0}; - uniforms.createOrUpdate(idLineUBOName, &lineUBO, context); + uniforms.createOrUpdate(idLineUBO, &lineUBO, context); // properties UBO - uniforms.addOrReplace(idLinePropertiesUBOName, getLinePropsBuffer()); + uniforms.set(idLinePropertiesUBO, getLinePropsBuffer()); + + // dynamic UBO + uniforms.set(idLineDynamicUBO, dynamicBuffer); } break; case LineType::Gradient: { @@ -159,10 +151,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, 0, 0}; - uniforms.createOrUpdate(idLineGradientUBOName, &lineGradientUBO, context); + uniforms.createOrUpdate(idLineGradientUBO, &lineGradientUBO, context); // properties UBO - uniforms.addOrReplace(idLineGradientPropertiesUBOName, getLineGradientPropsBuffer()); + uniforms.set(idLineGradientPropertiesUBO, getLineGradientPropsBuffer()); + + // dynamic UBO + uniforms.set(idLineGradientDynamicUBO, dynamicBuffer); } break; case LineType::Pattern: { @@ -182,10 +177,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters /*texsize =*/{static_cast(textureSize.width), static_cast(textureSize.height)}, /*ratio =*/1.0f / tileID.pixelsToTileUnits(1.0f, static_cast(zoom)), /*fade =*/crossfade.t}; - uniforms.createOrUpdate(idLinePatternUBOName, &linePatternUBO, context); + uniforms.createOrUpdate(idLinePatternUBO, &linePatternUBO, context); // properties UBO - uniforms.addOrReplace(idLinePatternPropertiesUBOName, getLinePatternPropsBuffer()); + uniforms.set(idLinePatternPropertiesUBO, getLinePatternPropsBuffer()); + + // dynamic UBO + uniforms.set(idLinePatternDynamicUBO, dynamicBuffer); } break; @@ -229,10 +227,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, 0, 0}; - uniforms.createOrUpdate(idLineSDFUBOName, &lineSDFUBO, context); + uniforms.createOrUpdate(idLineSDFUBO, &lineSDFUBO, context); // properties UBO - uniforms.addOrReplace(idLineSDFPropertiesUBOName, getLineSDFPropsBuffer()); + uniforms.set(idLineSDFPropertiesUBO, getLineSDFPropsBuffer()); + + // dynamic UBO + uniforms.set(idLineSDFDynamicUBO, dynamicBuffer); } } break; diff --git a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp index 665562d1e69..de19f3914c1 100644 --- a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp @@ -17,8 +17,6 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idRasterDrawableUBOName = stringIndexer().get("RasterDrawableUBO"); - void RasterLayerTweaker::execute([[maybe_unused]] LayerGroupBase& layerGroup, [[maybe_unused]] const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; @@ -92,7 +90,7 @@ void RasterLayerTweaker::execute([[maybe_unused]] LayerGroupBase& layerGroup, 0, 0}; auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idRasterDrawableUBOName, &drawableUBO, parameters.context); + uniforms.createOrUpdate(idRasterDrawableUBO, &drawableUBO, parameters.context); }); } diff --git a/src/mbgl/renderer/layers/render_circle_layer.cpp b/src/mbgl/renderer/layers/render_circle_layer.cpp index a1343eeec73..9a11ca91993 100644 --- a/src/mbgl/renderer/layers/render_circle_layer.cpp +++ b/src/mbgl/renderer/layers/render_circle_layer.cpp @@ -264,7 +264,6 @@ bool RenderCircleLayer::queryIntersectsFeature(const GeometryCoordinates& queryG namespace { constexpr auto CircleShaderGroupName = "CircleShader"; -const StringIdentity idCircleInterpolateUBOName = stringIndexer().get("CircleInterpolateUBO"); const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); } // namespace @@ -361,7 +360,7 @@ void RenderCircleLayer::update(gfx::ShaderRegistry& shaders, } auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idCircleInterpolateUBOName, &interpolateUBO, context); + uniforms.createOrUpdate(idCircleInterpolateUBO, &interpolateUBO, context); return true; }; if (updateTile(renderPass, tileID, std::move(updateExisting))) { @@ -417,7 +416,7 @@ void RenderCircleLayer::update(gfx::ShaderRegistry& shaders, drawable->setLayerTweaker(layerTweaker); auto& uniforms = drawable->mutableUniformBuffers(); - uniforms.addOrReplace(idCircleInterpolateUBOName, interpBuffer); + uniforms.set(idCircleInterpolateUBO, interpBuffer); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp index 296e73d51e8..f6bf877ce39 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -402,8 +402,8 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, } auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(FillExtrusionLayerTweaker::idFillExtrusionTilePropsUBOName, &tilePropsUBO, context); - uniforms.createOrUpdate(FillExtrusionLayerTweaker::idFillExtrusionInterpolateUBOName, &interpUBO, context); + uniforms.createOrUpdate(idFillExtrusionDrawableTilePropsUBO, &tilePropsUBO, context); + uniforms.createOrUpdate(idFillExtrusionInterpolateUBO, &interpUBO, context); return true; }; if (updateTile(drawPass, tileID, std::move(updateExisting))) { @@ -515,10 +515,8 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, drawable->setLayerTweaker(layerTweaker); auto& uniforms = drawable->mutableUniformBuffers(); - uniforms.createOrUpdate( - FillExtrusionLayerTweaker::idFillExtrusionTilePropsUBOName, &tilePropsUBO, context); - uniforms.createOrUpdate( - FillExtrusionLayerTweaker::idFillExtrusionInterpolateUBOName, &interpUBO, context); + uniforms.createOrUpdate(idFillExtrusionDrawableTilePropsUBO, &tilePropsUBO, context); + uniforms.createOrUpdate(idFillExtrusionInterpolateUBO, &interpUBO, context); tileLayerGroup->addDrawable(drawPass, tileID, std::move(drawable)); ++stats.drawablesAdded; diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index dd1436651ea..7ade9731d37 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -50,12 +50,6 @@ constexpr auto FillOutlineShaderName = "FillOutlineShader"; constexpr auto FillPatternShaderName = "FillPatternShader"; constexpr auto FillOutlinePatternShaderName = "FillOutlinePatternShader"; -const StringIdentity idFillOutlineInterpolateUBOName = stringIndexer().get("FillOutlineInterpolateUBO"); -const StringIdentity idFillPatternInterpolateUBOName = stringIndexer().get("FillPatternInterpolateUBO"); -const StringIdentity idFillPatternTilePropsUBOName = stringIndexer().get("FillPatternTilePropsUBO"); -const StringIdentity idFillOutlinePatternInterpolateUBOName = stringIndexer().get("FillOutlinePatternInterpolateUBO"); -const StringIdentity idFillOutlinePatternTilePropsUBOName = stringIndexer().get("FillOutlinePatternTilePropsUBO"); - const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); const StringIdentity idIconTextureName = stringIndexer().get("u_image"); #endif // MLN_DRAWABLE_RENDERER @@ -365,21 +359,19 @@ class OutlineDrawableTweaker : public gfx::DrawableTweaker { const auto zoom = parameters.state.getZoom(); auto& uniforms = drawable.mutableUniformBuffers(); - static const StringIdentity idLineUBOName = stringIndexer().get("LineBasicUBO"); { const auto matrix = LayerTweaker::getTileMatrix( tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); - const shaders::LineBasicUBO lineUBO{ + const shaders::LineBasicUBO lineBasicUBO{ /*matrix = */ util::cast(matrix), /*units_to_pixels = */ {1.0f / parameters.pixelsToGLUnits[0], 1.0f / parameters.pixelsToGLUnits[1]}, /*ratio = */ 1.0f / tileID.pixelsToTileUnits(1.0f, zoom), 0}; - parameters.context.emplaceOrUpdateUniformBuffer(lineUniformBuffer, &lineUBO); + parameters.context.emplaceOrUpdateUniformBuffer(lineUniformBuffer, &lineBasicUBO); } - uniforms.addOrReplace(idLineUBOName, lineUniformBuffer); + uniforms.set(idLineBasicUBO, lineUniformBuffer); - static const StringIdentity idLinePropertiesUBOName = stringIndexer().get("LineBasicPropertiesUBO"); if (!linePropertiesUniformBuffer) { const shaders::LineBasicPropertiesUBO linePropertiesUBO{/*color =*/color, /*opacity =*/opacity, @@ -388,8 +380,8 @@ class OutlineDrawableTweaker : public gfx::DrawableTweaker { 0}; parameters.context.emplaceOrUpdateUniformBuffer(linePropertiesUniformBuffer, &linePropertiesUBO); } - if (!uniforms.get(idLinePropertiesUBOName)) { - uniforms.addOrReplace(idLinePropertiesUBOName, linePropertiesUniformBuffer); + if (!uniforms.get(idLineBasicPropertiesUBO)) { + uniforms.set(idLineBasicPropertiesUBO, linePropertiesUniformBuffer); } }; @@ -640,24 +632,23 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, auto& uniforms = drawable.mutableUniformBuffers(); switch (static_cast(drawable.getType())) { case FillVariant::Fill: { - uniforms.createOrUpdate( - FillLayerTweaker::idFillInterpolateUBOName, &getFillInterpolateUBO(), context); + uniforms.createOrUpdate(idFillInterpolateUBO, &getFillInterpolateUBO(), context); break; } case FillVariant::FillOutline: { - uniforms.createOrUpdate(idFillOutlineInterpolateUBOName, &getFillOutlineInterpolateUBO(), context); + uniforms.createOrUpdate(idFillOutlineInterpolateUBO, &getFillOutlineInterpolateUBO(), context); break; } case FillVariant::FillPattern: { - uniforms.createOrUpdate(idFillPatternInterpolateUBOName, &getFillPatternInterpolateUBO(), context); - uniforms.createOrUpdate(idFillPatternTilePropsUBOName, &getFillPatternTilePropsUBO(), context); + uniforms.createOrUpdate(idFillPatternInterpolateUBO, &getFillPatternInterpolateUBO(), context); + uniforms.createOrUpdate(idFillPatternTilePropsUBO, &getFillPatternTilePropsUBO(), context); break; } case FillVariant::FillOutlinePattern: { uniforms.createOrUpdate( - idFillOutlinePatternInterpolateUBOName, &getFillOutlinePatternInterpolateUBO(), context); + idFillOutlinePatternInterpolateUBO, &getFillOutlinePatternInterpolateUBO(), context); uniforms.createOrUpdate( - idFillOutlinePatternTilePropsUBOName, &getFillOutlinePatternTilePropsUBO(), context); + idFillOutlinePatternTilePropsUBO, &getFillOutlinePatternTilePropsUBO(), context); break; } default: { @@ -779,7 +770,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, } const auto finish = [&](gfx::DrawableBuilder& builder, - const StringIdentity interpolateUBONameId, + const size_t interpolateUBOId, const auto& interpolateUBO, FillVariant type) { builder.setVertexAttrNameId(idPosAttribName); @@ -791,7 +782,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, drawable->setType(static_cast(type)); auto& uniforms = drawable->mutableUniformBuffers(); - uniforms.createOrUpdate(interpolateUBONameId, &interpolateUBO, context); + uniforms.createOrUpdate(interpolateUBOId, &interpolateUBO, context); fillTileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; } @@ -819,10 +810,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, bucket.sharedTriangles, bucket.triangleSegments.data(), bucket.triangleSegments.size()); - finish(*fillBuilder, - FillLayerTweaker::idFillInterpolateUBOName, - getFillInterpolateUBO(), - FillVariant::Fill); + finish(*fillBuilder, idFillInterpolateUBO, getFillInterpolateUBO(), FillVariant::Fill); } #if MLN_TRIANGULATE_FILL_OUTLINES @@ -841,7 +829,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, bucket.basicLineSegments.data(), bucket.basicLineSegments.size()); finish(*outlineBuilder, - FillLayerTweaker::idFillOutlineInterpolateUBOName, + idFillOutlineInterpolateUBO, getFillOutlineInterpolateUBO(), FillVariant::FillOutline); } @@ -856,7 +844,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, bucket.basicLineSegments.data(), bucket.basicLineSegments.size()); finish(*outlineBuilder, - FillLayerTweaker::idFillOutlineInterpolateUBOName, + idFillOutlineInterpolateUBO, getFillOutlineInterpolateUBO(), FillVariant::FillOutline); } @@ -914,7 +902,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, const auto finish = [&](gfx::DrawableBuilder& builder, const StringIdentity interpolateNameId, const auto& interpolateUBO, - const StringIdentity tileUBONameId, + const size_t tileUBOId, const auto& tileUBO, FillVariant type) { builder.flush(context); @@ -926,7 +914,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, auto& uniforms = drawable->mutableUniformBuffers(); uniforms.createOrUpdate(interpolateNameId, &interpolateUBO, context); - uniforms.createOrUpdate(tileUBONameId, &tileUBO, context); + uniforms.createOrUpdate(tileUBOId, &tileUBO, context); fillTileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; } @@ -948,9 +936,9 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, bucket.triangleSegments.size()); finish(*patternBuilder, - idFillPatternInterpolateUBOName, + idFillPatternInterpolateUBO, getFillPatternInterpolateUBO(), - idFillPatternTilePropsUBOName, + idFillPatternTilePropsUBO, getFillPatternTilePropsUBO(), FillVariant::FillPattern); } @@ -965,9 +953,9 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, bucket.basicLineSegments.size()); finish(*outlinePatternBuilder, - idFillOutlinePatternInterpolateUBOName, + idFillOutlinePatternInterpolateUBO, getFillOutlinePatternInterpolateUBO(), - idFillOutlinePatternTilePropsUBOName, + idFillOutlinePatternTilePropsUBO, getFillOutlinePatternTilePropsUBO(), FillVariant::FillOutlinePattern); } diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp index c5af6665eb1..20413c70886 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.cpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -277,7 +277,7 @@ namespace { constexpr auto HeatmapShaderGroupName = "HeatmapShader"; constexpr auto HeatmapTextureShaderGroupName = "HeatmapTextureShader"; -const StringIdentity idHeatmapInterpolateUBOName = stringIndexer().get("HeatmapInterpolateUBO"); + const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); const StringIdentity idTexImageName = stringIndexer().get("u_image"); const StringIdentity idTexColorRampName = stringIndexer().get("u_color_ramp"); @@ -402,7 +402,7 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, auto& uniforms = drawable.mutableUniformBuffers(); if (auto buffer = getInterpolateBuffer()) { - uniforms.addOrReplace(idHeatmapInterpolateUBOName, std::move(buffer)); + uniforms.set(idHeatmapInterpolateUBO, std::move(buffer)); } }); @@ -467,7 +467,7 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, auto& uniforms = drawable->mutableUniformBuffers(); if (auto buffer = getInterpolateBuffer()) { - uniforms.addOrReplace(idHeatmapInterpolateUBOName, std::move(buffer)); + uniforms.set(idHeatmapInterpolateUBO, std::move(buffer)); } tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index c3a69b9eb6a..a6c90f2e9f3 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -345,15 +345,6 @@ float RenderLineLayer::getLineWidth(const GeometryTileFeature& feature, } #if MLN_DRAWABLE_RENDERER -/// Property interpolation UBOs -static const StringIdentity idLineInterpolationUBOName = stringIndexer().get("LineInterpolationUBO"); -static const StringIdentity idLineGradientInterpolationUBOName = stringIndexer().get("LineGradientInterpolationUBO"); -static const StringIdentity idLinePatternInterpolationUBOName = stringIndexer().get("LinePatternInterpolationUBO"); -static const StringIdentity idLineSDFInterpolationUBOName = stringIndexer().get("LineSDFInterpolationUBO"); - -/// Evaluated properties that depend on the tile -static const StringIdentity idLinePatternTilePropertiesUBOName = stringIndexer().get("LinePatternTilePropertiesUBO"); - static const StringIdentity idLineImageUniformName = stringIndexer().get("u_image"); void RenderLineLayer::update(gfx::ShaderRegistry& shaders, @@ -577,34 +568,36 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, return false; } - const auto& shader = drawable.getShader(); - const auto& shaderUniforms = shader->getUniformBlocks(); auto& drawableUniforms = drawable.mutableUniformBuffers(); - - // simple line interpolation UBO - if (shaderUniforms.get(idLineInterpolationUBOName)) { - drawableUniforms.createOrUpdate(idLineInterpolationUBOName, &getLineInterpolationUBO(), context); - } - // gradient line interpolation UBO - else if (shaderUniforms.get(idLineGradientInterpolationUBOName)) { - drawableUniforms.createOrUpdate( - idLineGradientInterpolationUBOName, &getLineGradientInterpolationUBO(), context); + const LineLayerTweaker::LineType type = static_cast(drawable.getType()); + switch (type) { + case LineLayerTweaker::LineType::Simple: { + drawableUniforms.createOrUpdate(idLineInterpolationUBO, &getLineInterpolationUBO(), context); + } break; + + case LineLayerTweaker::LineType::Gradient: { + drawableUniforms.createOrUpdate( + idLineGradientInterpolationUBO, &getLineGradientInterpolationUBO(), context); + } break; + + case LineLayerTweaker::LineType::Pattern: { + drawableUniforms.createOrUpdate( + idLinePatternInterpolationUBO, &getLinePatternInterpolationUBO(), context); + + drawableUniforms.createOrUpdate( + idLinePatternTilePropertiesUBO, &getLinePatternTilePropertiesUBO(), context); + } break; + + case LineLayerTweaker::LineType::SDF: { + drawableUniforms.createOrUpdate(idLineSDFInterpolationUBO, &getLineSDFInterpolationUBO(), context); + } break; + + default: { + using namespace std::string_literals; + Log::Error(Event::General, + "RenderLineLayer: unknown line type: "s + std::to_string(mbgl::underlying_type(type))); + } break; } - // pattern line interpolation UBO - else if (shaderUniforms.get(idLinePatternInterpolationUBOName)) { - // interpolation - drawableUniforms.createOrUpdate( - idLinePatternInterpolationUBOName, &getLinePatternInterpolationUBO(), context); - // tile properties - drawableUniforms.createOrUpdate( - idLinePatternTilePropertiesUBOName, &getLinePatternTilePropertiesUBO(), context); - } - // SDF line interpolation UBO - else if (shaderUniforms.get(idLineSDFInterpolationUBOName)) { - drawableUniforms.createOrUpdate(idLineSDFInterpolationUBOName, &getLineSDFInterpolationUBO(), context); - } - - // TODO: vertex attributes or `propertiesAsUniforms` updated, is that needed? return true; }; if (updateTile(renderPass, tileID, std::move(updateExisting))) { @@ -649,7 +642,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setLayerTweaker(layerTweaker); drawable->setData(std::make_unique(cap)); drawable->mutableUniformBuffers().createOrUpdate( - idLineSDFInterpolationUBOName, &getLineSDFInterpolationUBO(), context); + idLineSDFInterpolationUBO, &getLineSDFInterpolationUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; @@ -706,9 +699,9 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setTileID(tileID); drawable->setLayerTweaker(layerTweaker); drawable->mutableUniformBuffers().createOrUpdate( - idLinePatternInterpolationUBOName, &getLinePatternInterpolationUBO(), context); + idLinePatternInterpolationUBO, &getLinePatternInterpolationUBO(), context); drawable->mutableUniformBuffers().createOrUpdate( - idLinePatternTilePropertiesUBOName, &getLinePatternTilePropertiesUBO(), context); + idLinePatternTilePropertiesUBO, &getLinePatternTilePropertiesUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; @@ -758,7 +751,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setTileID(tileID); drawable->setLayerTweaker(layerTweaker); drawable->mutableUniformBuffers().createOrUpdate( - idLineGradientInterpolationUBOName, &getLineGradientInterpolationUBO(), context); + idLineGradientInterpolationUBO, &getLineGradientInterpolationUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; @@ -795,7 +788,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, drawable->setTileID(tileID); drawable->setLayerTweaker(layerTweaker); drawable->mutableUniformBuffers().createOrUpdate( - idLineInterpolationUBOName, &getLineInterpolationUBO(), context); + idLineInterpolationUBO, &getLineInterpolationUBO(), context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); ++stats.drawablesAdded; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index a59e0082092..f35b745d0dd 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -848,20 +848,20 @@ void updateTileDrawable(gfx::Drawable& drawable, // Create or update the shared interpolation UBO gfx::UniformBufferPtr& interpUBO = isText ? textInterpUBO : iconInterpUBO; if (interpUBO) { - uniforms.addOrReplace(SymbolLayerTweaker::idSymbolDrawableInterpolateUBOName, interpUBO); + uniforms.set(idSymbolDrawableInterpolateUBO, interpUBO); } else { const auto ubo = buildInterpUBO(paintProps, isText, currentZoom); - interpUBO = uniforms.get(SymbolLayerTweaker::idSymbolDrawableInterpolateUBOName); + interpUBO = uniforms.get(idSymbolDrawableInterpolateUBO); if (interpUBO) { interpUBO->update(&ubo, sizeof(ubo)); } else { interpUBO = context.createUniformBuffer(&ubo, sizeof(ubo)); - uniforms.addOrReplace(SymbolLayerTweaker::idSymbolDrawableInterpolateUBOName, interpUBO); + uniforms.set(idSymbolDrawableInterpolateUBO, interpUBO); } } const auto tileUBO = buildTileUBO(bucket, drawData, currentZoom); - uniforms.createOrUpdate(SymbolLayerTweaker::idSymbolDrawableTilePropsUBOName, &tileUBO, context); + uniforms.createOrUpdate(idSymbolDrawableTilePropsUBO, &tileUBO, context); const auto& buffer = isText ? bucket.text : (sdfIcons ? bucket.sdfIcon : bucket.icon); const auto vertexCount = buffer.vertices().elements(); @@ -1367,8 +1367,8 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, drawable->setData(std::move(drawData)); auto& uniforms = drawable->mutableUniformBuffers(); - uniforms.createOrUpdate(SymbolLayerTweaker::idSymbolDrawableTilePropsUBOName, &tileUBO, context); - uniforms.addOrReplace(SymbolLayerTweaker::idSymbolDrawableInterpolateUBOName, interpUBO); + uniforms.createOrUpdate(idSymbolDrawableTilePropsUBO, &tileUBO, context); + uniforms.set(idSymbolDrawableInterpolateUBO, interpUBO); tileLayerGroup->addDrawable(passes, tileID, std::move(drawable)); ++stats.drawablesAdded; diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp index aa737811093..120e9c20b54 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp @@ -62,14 +62,6 @@ SymbolDrawablePaintUBO buildPaintUBO(bool isText, const SymbolPaintProperties::P } // namespace -const StringIdentity SymbolLayerTweaker::idSymbolDrawableUBOName = stringIndexer().get("SymbolDrawableUBO"); -const StringIdentity SymbolLayerTweaker::idSymbolDynamicUBOName = stringIndexer().get("SymbolDynamicUBO"); -const StringIdentity SymbolLayerTweaker::idSymbolDrawablePaintUBOName = stringIndexer().get("SymbolDrawablePaintUBO"); -const StringIdentity SymbolLayerTweaker::idSymbolDrawableTilePropsUBOName = stringIndexer().get( - "SymbolDrawableTilePropsUBO"); -const StringIdentity SymbolLayerTweaker::idSymbolDrawableInterpolateUBOName = stringIndexer().get( - "SymbolDrawableInterpolateUBO"); - void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& state = parameters.state; @@ -169,10 +161,10 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete }; auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idSymbolDrawableUBOName, &drawableUBO, context); + uniforms.createOrUpdate(idSymbolDrawableUBO, &drawableUBO, context); - uniforms.addOrReplace(idSymbolDynamicUBOName, dynamicBuffer); - uniforms.addOrReplace(idSymbolDrawablePaintUBOName, isText ? textPaintBuffer : iconPaintBuffer); + uniforms.set(idSymbolDynamicUBO, dynamicBuffer); + uniforms.set(idSymbolDrawablePaintUBO, isText ? textPaintBuffer : iconPaintBuffer); }); } diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp index 2b57d7be744..341d627efff 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp @@ -20,12 +20,6 @@ class SymbolLayerTweaker : public LayerTweaker { void execute(LayerGroupBase&, const PaintParameters&) override; - static const StringIdentity idSymbolDrawableUBOName; - static const StringIdentity idSymbolDynamicUBOName; - static const StringIdentity idSymbolDrawablePaintUBOName; - static const StringIdentity idSymbolDrawableTilePropsUBOName; - static const StringIdentity idSymbolDrawableInterpolateUBOName; - private: gfx::UniformBufferPtr textPaintBuffer; gfx::UniformBufferPtr iconPaintBuffer; diff --git a/src/mbgl/renderer/sources/render_tile_source.cpp b/src/mbgl/renderer/sources/render_tile_source.cpp index 44203838483..959dbff4330 100644 --- a/src/mbgl/renderer/sources/render_tile_source.cpp +++ b/src/mbgl/renderer/sources/render_tile_source.cpp @@ -66,7 +66,6 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr return; } static const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); - static const StringIdentity idDebugUBOName = stringIndexer().get("DebugUBO"); std::unique_ptr debugBuilder = [&]() -> std::unique_ptr { auto builder = context.createDrawableBuilder("debug-builder"); builder->setShader(debugShader); @@ -151,7 +150,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr auto updatedCount = tileLayerGroup->visitDrawables(renderPass, tileID, [&](gfx::Drawable& drawable) { // update existing drawable auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idDebugUBOName, &debugUBO, context); + uniforms.createOrUpdate(idDebugUBO, &debugUBO, context); }); return updatedCount; }; @@ -180,7 +179,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr for (auto& drawable : debugBuilder->clearDrawables()) { drawable->setTileID(tileID); auto& uniforms = drawable->mutableUniformBuffers(); - uniforms.createOrUpdate(idDebugUBOName, &debugUBO, context); + uniforms.createOrUpdate(idDebugUBO, &debugUBO, context); tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); } @@ -210,22 +209,17 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr const auto matrix = LayerTweaker::getTileMatrix( tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); - static const StringIdentity idLineDynamicUBOName = stringIndexer().get("LineDynamicUBO"); const shaders::LineDynamicUBO dynamicUBO = { /*units_to_pixels = */ {1.0f / parameters.pixelsToGLUnits[0], 1.0f / parameters.pixelsToGLUnits[1]}, 0, 0}; - static const StringIdentity idLineUBOName = stringIndexer().get("LineUBO"); const shaders::LineUBO lineUBO{/*matrix = */ util::cast(matrix), /*ratio = */ 1.0f / tileID.pixelsToTileUnits(1.0f, zoom), 0, 0, 0}; - static const StringIdentity idLinePropertiesUBOName = stringIndexer().get("LinePropertiesUBO"); - - static const StringIdentity idLineInterpolationUBOName = stringIndexer().get("LineInterpolationUBO"); const shaders::LineInterpolationUBO lineInterpolationUBO{/*color_t =*/0.f, /*blur_t =*/0.f, /*opacity_t =*/0.f, @@ -235,10 +229,10 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr 0, 0}; auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idLineDynamicUBOName, &dynamicUBO, parameters.context); - uniforms.createOrUpdate(idLineUBOName, &lineUBO, parameters.context); - uniforms.createOrUpdate(idLinePropertiesUBOName, &linePropertiesUBO, parameters.context); - uniforms.createOrUpdate(idLineInterpolationUBOName, &lineInterpolationUBO, parameters.context); + uniforms.createOrUpdate(idLineDynamicUBO, &dynamicUBO, parameters.context); + uniforms.createOrUpdate(idLineUBO, &lineUBO, parameters.context); + uniforms.createOrUpdate(idLinePropertiesUBO, &linePropertiesUBO, parameters.context); + uniforms.createOrUpdate(idLineInterpolationUBO, &lineInterpolationUBO, parameters.context); }; private: diff --git a/src/mbgl/shaders/gl/shader_info.cpp b/src/mbgl/shaders/gl/shader_info.cpp new file mode 100644 index 00000000000..5d90eb2fc41 --- /dev/null +++ b/src/mbgl/shaders/gl/shader_info.cpp @@ -0,0 +1,178 @@ +#include + +#include + +namespace mbgl { +namespace shaders { + +UniformBlockInfo::UniformBlockInfo(std::string_view name_, std::size_t id_) + : name(name_), + id(id_), + binding(id_) {} + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"BackgroundDrawableUBO", idBackgroundDrawableUBO}, + UniformBlockInfo{"BackgroundLayerUBO", idBackgroundLayerUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"BackgroundDrawableUBO", idBackgroundDrawableUBO}, + UniformBlockInfo{"BackgroundLayerUBO", idBackgroundLayerUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"CircleDrawableUBO", idCircleDrawableUBO}, + UniformBlockInfo{"CirclePaintParamsUBO", idCirclePaintParamsUBO}, + UniformBlockInfo{"CircleEvaluatedPropsUBO", idCircleEvaluatedPropsUBO}, + UniformBlockInfo{"CircleInterpolateUBO", idCircleInterpolateUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = + { + UniformBlockInfo{"CollisionBoxUBO", idCollisionUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"CollisionCircleUBO", idCollisionUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"DebugUBO", idDebugUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"FillDrawableUBO", idFillDrawableUBO}, + UniformBlockInfo{"FillEvaluatedPropsUBO", idFillEvaluatedPropsUBO}, + UniformBlockInfo{"FillInterpolateUBO", idFillInterpolateUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = + { + UniformBlockInfo{"FillOutlineDrawableUBO", idFillOutlineDrawableUBO}, + UniformBlockInfo{"FillOutlineEvaluatedPropsUBO", idFillOutlineEvaluatedPropsUBO}, + UniformBlockInfo{"FillOutlineInterpolateUBO", idFillOutlineInterpolateUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = + { + UniformBlockInfo{"FillPatternDrawableUBO", idFillPatternDrawableUBO}, + UniformBlockInfo{"FillPatternTilePropsUBO", idFillPatternTilePropsUBO}, + UniformBlockInfo{"FillPatternEvaluatedPropsUBO", idFillPatternEvaluatedPropsUBO}, + UniformBlockInfo{"FillPatternInterpolateUBO", idFillPatternInterpolateUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"FillOutlinePatternDrawableUBO", idFillOutlinePatternDrawableUBO}, + UniformBlockInfo{"FillOutlinePatternTilePropsUBO", idFillOutlinePatternTilePropsUBO}, + UniformBlockInfo{"FillOutlinePatternEvaluatedPropsUBO", idFillOutlinePatternEvaluatedPropsUBO}, + UniformBlockInfo{"FillOutlinePatternInterpolateUBO", idFillOutlinePatternInterpolateUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"FillExtrusionDrawableUBO", idFillExtrusionDrawableUBO}, + UniformBlockInfo{"FillExtrusionDrawablePropsUBO", idFillExtrusionDrawablePropsUBO}, + UniformBlockInfo{"FillExtrusionDrawableTilePropsUBO", idFillExtrusionDrawableTilePropsUBO}, + UniformBlockInfo{"FillExtrusionInterpolateUBO", idFillExtrusionInterpolateUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"FillExtrusionDrawableUBO", idFillExtrusionDrawableUBO}, + UniformBlockInfo{"FillExtrusionDrawablePropsUBO", idFillExtrusionDrawablePropsUBO}, + UniformBlockInfo{"FillExtrusionDrawableTilePropsUBO", idFillExtrusionDrawableTilePropsUBO}, + UniformBlockInfo{"FillExtrusionInterpolateUBO", idFillExtrusionInterpolateUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"HeatmapDrawableUBO", idHeatmapDrawableUBO}, + UniformBlockInfo{"HeatmapEvaluatedPropsUBO", idHeatmapEvaluatedPropsUBO}, + UniformBlockInfo{"HeatmapInterpolateUBO", idHeatmapInterpolateUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"HeatmapTextureDrawableUBO", idHeatmapTextureDrawableUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"HillshadePrepareDrawableUBO", idHillshadePrepareDrawableUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"HillshadeDrawableUBO", idHillshadeDrawableUBO}, + UniformBlockInfo{"HillshadeEvaluatedPropsUBO", idHillshadeEvaluatedPropsUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = + { + UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, + UniformBlockInfo{"LineGradientUBO", idLineGradientUBO}, + UniformBlockInfo{"LineGradientPropertiesUBO", idLineGradientPropertiesUBO}, + UniformBlockInfo{"LineGradientInterpolationUBO", idLineGradientInterpolationUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = + { + UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, + UniformBlockInfo{"LinePatternUBO", idLinePatternUBO}, + UniformBlockInfo{"LinePatternPropertiesUBO", idLinePatternPropertiesUBO}, + UniformBlockInfo{"LinePatternInterpolationUBO", idLinePatternInterpolationUBO}, + UniformBlockInfo{"LinePatternTilePropertiesUBO", idLinePatternTilePropertiesUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, + UniformBlockInfo{"LineSDFUBO", idLineSDFUBO}, + UniformBlockInfo{"LineSDFPropertiesUBO", idLineSDFPropertiesUBO}, + UniformBlockInfo{"LineSDFInterpolationUBO", idLineSDFInterpolationUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, + UniformBlockInfo{"LineUBO", idLineUBO}, + UniformBlockInfo{"LinePropertiesUBO", idLinePropertiesUBO}, + UniformBlockInfo{"LineInterpolationUBO", idLineInterpolationUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"LineBasicUBO", idLineBasicUBO}, + UniformBlockInfo{"LineBasicPropertiesUBO", idLineBasicPropertiesUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"RasterDrawableUBO", idRasterDrawableUBO}, +}; + +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, + UniformBlockInfo{"SymbolDynamicUBO", idSymbolDynamicUBO}, + UniformBlockInfo{"SymbolDrawablePaintUBO", idSymbolDrawablePaintUBO}, + UniformBlockInfo{"SymbolDrawableTilePropsUBO", idSymbolDrawableTilePropsUBO}, + UniformBlockInfo{"SymbolDrawableInterpolateUBO", idSymbolDrawableInterpolateUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, + UniformBlockInfo{"SymbolDynamicUBO", idSymbolDynamicUBO}, + UniformBlockInfo{"SymbolDrawablePaintUBO", idSymbolDrawablePaintUBO}, + UniformBlockInfo{"SymbolDrawableTilePropsUBO", idSymbolDrawableTilePropsUBO}, + UniformBlockInfo{"SymbolDrawableInterpolateUBO", idSymbolDrawableInterpolateUBO}, +}; + +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, + UniformBlockInfo{"SymbolDynamicUBO", idSymbolDynamicUBO}, + UniformBlockInfo{"SymbolDrawablePaintUBO", idSymbolDrawablePaintUBO}, + UniformBlockInfo{"SymbolDrawableTilePropsUBO", idSymbolDrawableTilePropsUBO}, + UniformBlockInfo{"SymbolDrawableInterpolateUBO", idSymbolDrawableInterpolateUBO}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/gl/shader_program_gl.cpp b/src/mbgl/shaders/gl/shader_program_gl.cpp index 9688f1eeeb0..0e3ce2f41ab 100644 --- a/src/mbgl/shaders/gl/shader_program_gl.cpp +++ b/src/mbgl/shaders/gl/shader_program_gl.cpp @@ -113,13 +113,15 @@ std::optional ShaderProgramGL::getSamplerLocation(const StringIdentity return result; } -std::shared_ptr ShaderProgramGL::create(Context& context, - const ProgramParameters& programParameters, - const std::string& /*name*/, - const std::string_view firstAttribName, - const std::string& vertexSource, - const std::string& fragmentSource, - const std::string& additionalDefines) noexcept(false) { +std::shared_ptr ShaderProgramGL::create( + Context& context, + const ProgramParameters& programParameters, + const std::string& /*name*/, + const std::string_view firstAttribName, + const std::vector& uniformBlocksInfo, + const std::string& vertexSource, + const std::string& fragmentSource, + const std::string& additionalDefines) noexcept(false) { // throws on compile error auto vertProg = context.createShader( ShaderType::Vertex, @@ -149,23 +151,21 @@ std::shared_ptr ShaderProgramGL::create(Context& context, MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &count)); MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &maxLength)); - auto name = std::vector(maxLength); - for (GLint index = 0; index < count; ++index) { - GLsizei length = 0; + for (const auto& blockInfo : uniformBlocksInfo) { + GLint index = MBGL_CHECK_ERROR(glGetUniformBlockIndex(program, blockInfo.name.data())); GLint size = 0; - GLint binding = index; - MBGL_CHECK_ERROR(glGetActiveUniformBlockName(program, index, maxLength, &length, name.data())); MBGL_CHECK_ERROR(glGetActiveUniformBlockiv(program, index, GL_UNIFORM_BLOCK_DATA_SIZE, &size)); - assert(length > 0 && size > 0); + assert(size > 0); + GLint binding = static_cast(blockInfo.binding); MBGL_CHECK_ERROR(glUniformBlockBinding(program, index, binding)); - uniformBlocks.add(stringIndexer().get(name.data()), index, size); + uniformBlocks.set(blockInfo.id, binding, size); } SamplerLocationMap samplerLocations; GLint numActiveUniforms = 0; MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numActiveUniforms)); MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLength)); - name.resize(maxLength); + auto name = std::vector(maxLength); for (GLint index = 0; index < numActiveUniforms; ++index) { GLsizei actualLength = 0; GLint size = 0; diff --git a/src/mbgl/shaders/mtl/background.cpp b/src/mbgl/shaders/mtl/background.cpp index a542af6afc4..e6a9d6e28e8 100644 --- a/src/mbgl/shaders/mtl/background.cpp +++ b/src/mbgl/shaders/mtl/background.cpp @@ -8,8 +8,8 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{1, true, false, sizeof(BackgroundDrawableUBO), "BackgroundDrawableUBO"}, - UniformBlockInfo{2, false, true, sizeof(BackgroundLayerUBO), "BackgroundLayerUBO"}, + UniformBlockInfo{1, true, false, sizeof(BackgroundDrawableUBO), idBackgroundDrawableUBO}, + UniformBlockInfo{2, false, true, sizeof(BackgroundLayerUBO), idBackgroundLayerUBO}, }; const std::array ShaderSource::textures = {}; diff --git a/src/mbgl/shaders/mtl/background_pattern.cpp b/src/mbgl/shaders/mtl/background_pattern.cpp index 44ebf54b794..1cdb89b0deb 100644 --- a/src/mbgl/shaders/mtl/background_pattern.cpp +++ b/src/mbgl/shaders/mtl/background_pattern.cpp @@ -10,8 +10,8 @@ const std::array }; const std::array ShaderSource::uniforms = { - UniformBlockInfo{1, true, false, sizeof(BackgroundDrawableUBO), "BackgroundDrawableUBO"}, - UniformBlockInfo{2, true, true, sizeof(BackgroundPatternLayerUBO), "BackgroundLayerUBO"}, + UniformBlockInfo{1, true, false, sizeof(BackgroundDrawableUBO), idBackgroundDrawableUBO}, + UniformBlockInfo{2, true, true, sizeof(BackgroundPatternLayerUBO), idBackgroundLayerUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}}; diff --git a/src/mbgl/shaders/mtl/circle.cpp b/src/mbgl/shaders/mtl/circle.cpp index 949a05c7b65..426447ad5bd 100644 --- a/src/mbgl/shaders/mtl/circle.cpp +++ b/src/mbgl/shaders/mtl/circle.cpp @@ -14,10 +14,10 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{8, true, false, sizeof(CircleDrawableUBO), "CircleDrawableUBO"}, - UniformBlockInfo{9, true, true, sizeof(CirclePaintParamsUBO), "CirclePaintParamsUBO"}, - UniformBlockInfo{10, true, true, sizeof(CircleEvaluatedPropsUBO), "CircleEvaluatedPropsUBO"}, - UniformBlockInfo{11, true, false, sizeof(CircleInterpolateUBO), "CircleInterpolateUBO"}, + UniformBlockInfo{8, true, false, sizeof(CircleDrawableUBO), idCircleDrawableUBO}, + UniformBlockInfo{9, true, true, sizeof(CirclePaintParamsUBO), idCirclePaintParamsUBO}, + UniformBlockInfo{10, true, true, sizeof(CircleEvaluatedPropsUBO), idCircleEvaluatedPropsUBO}, + UniformBlockInfo{11, true, false, sizeof(CircleInterpolateUBO), idCircleInterpolateUBO}, }; const std::array ShaderSource::textures = {}; diff --git a/src/mbgl/shaders/mtl/clipping_mask.cpp b/src/mbgl/shaders/mtl/clipping_mask.cpp index 28585accc3f..bd2ba6237a2 100644 --- a/src/mbgl/shaders/mtl/clipping_mask.cpp +++ b/src/mbgl/shaders/mtl/clipping_mask.cpp @@ -9,7 +9,7 @@ const std::array ShaderType::attributes = { AttributeInfo{0, gfx::AttributeDataType::Float3, "a_pos"}, }; const std::array ShaderType::uniforms = { - UniformBlockInfo{1, true, false, sizeof(ClipUBO), "ClipUBO"}, + UniformBlockInfo{1, true, false, sizeof(ClipUBO), 0}, }; const std::array ShaderType::textures = {}; diff --git a/src/mbgl/shaders/mtl/collision_box.cpp b/src/mbgl/shaders/mtl/collision_box.cpp index 205f7fe6e9f..c712f76b8b5 100644 --- a/src/mbgl/shaders/mtl/collision_box.cpp +++ b/src/mbgl/shaders/mtl/collision_box.cpp @@ -11,7 +11,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{5, true, true, sizeof(CollisionUBO), "CollisionBoxUBO"}, + UniformBlockInfo{5, true, true, sizeof(CollisionUBO), idCollisionUBO}, }; const std::array ShaderSource::textures = {}; diff --git a/src/mbgl/shaders/mtl/collision_circle.cpp b/src/mbgl/shaders/mtl/collision_circle.cpp index b0c36b07546..2979256b5c6 100644 --- a/src/mbgl/shaders/mtl/collision_circle.cpp +++ b/src/mbgl/shaders/mtl/collision_circle.cpp @@ -12,7 +12,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{4, true, true, sizeof(CollisionUBO), "CollisionCircleUBO"}, + UniformBlockInfo{4, true, true, sizeof(CollisionUBO), idCollisionUBO}, }; const std::array ShaderSource::textures = {}; diff --git a/src/mbgl/shaders/mtl/debug.cpp b/src/mbgl/shaders/mtl/debug.cpp index f47f5ff1ae8..8694c6a21fd 100644 --- a/src/mbgl/shaders/mtl/debug.cpp +++ b/src/mbgl/shaders/mtl/debug.cpp @@ -7,7 +7,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{1, true, true, sizeof(DebugUBO), "DebugUBO"}, + UniformBlockInfo{1, true, true, sizeof(DebugUBO), idDebugUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_overlay"}, diff --git a/src/mbgl/shaders/mtl/fill.cpp b/src/mbgl/shaders/mtl/fill.cpp index 5fcee811486..3b124bbaa84 100644 --- a/src/mbgl/shaders/mtl/fill.cpp +++ b/src/mbgl/shaders/mtl/fill.cpp @@ -9,9 +9,9 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{3, true, false, sizeof(FillDrawableUBO), "FillDrawableUBO"}, - UniformBlockInfo{4, true, true, sizeof(FillEvaluatedPropsUBO), "FillEvaluatedPropsUBO"}, - UniformBlockInfo{5, true, false, sizeof(FillInterpolateUBO), "FillInterpolateUBO"}, + UniformBlockInfo{3, true, false, sizeof(FillDrawableUBO), idFillDrawableUBO}, + UniformBlockInfo{4, true, true, sizeof(FillEvaluatedPropsUBO), idFillEvaluatedPropsUBO}, + UniformBlockInfo{5, true, false, sizeof(FillInterpolateUBO), idFillInterpolateUBO}, }; const std::array ShaderSource::textures = {}; @@ -21,9 +21,9 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{3, true, false, sizeof(FillOutlineDrawableUBO), "FillOutlineDrawableUBO"}, - UniformBlockInfo{4, true, true, sizeof(FillOutlineEvaluatedPropsUBO), "FillOutlineEvaluatedPropsUBO"}, - UniformBlockInfo{5, true, false, sizeof(FillOutlineInterpolateUBO), "FillOutlineInterpolateUBO"}, + UniformBlockInfo{3, true, false, sizeof(FillOutlineDrawableUBO), idFillOutlineDrawableUBO}, + UniformBlockInfo{4, true, true, sizeof(FillOutlineEvaluatedPropsUBO), idFillOutlineEvaluatedPropsUBO}, + UniformBlockInfo{5, true, false, sizeof(FillOutlineInterpolateUBO), idFillOutlineInterpolateUBO}, }; const std::array ShaderSource::textures = {}; @@ -34,10 +34,10 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{4, true, true, sizeof(FillPatternDrawableUBO), "FillPatternDrawableUBO"}, - UniformBlockInfo{5, true, true, sizeof(FillPatternTilePropsUBO), "FillPatternTilePropsUBO"}, - UniformBlockInfo{6, true, true, sizeof(FillPatternEvaluatedPropsUBO), "FillPatternEvaluatedPropsUBO"}, - UniformBlockInfo{7, true, false, sizeof(FillPatternInterpolateUBO), "FillPatternInterpolateUBO"}, + UniformBlockInfo{4, true, true, sizeof(FillPatternDrawableUBO), idFillPatternDrawableUBO}, + UniformBlockInfo{5, true, true, sizeof(FillPatternTilePropsUBO), idFillPatternTilePropsUBO}, + UniformBlockInfo{6, true, true, sizeof(FillPatternEvaluatedPropsUBO), idFillPatternEvaluatedPropsUBO}, + UniformBlockInfo{7, true, false, sizeof(FillPatternInterpolateUBO), idFillPatternInterpolateUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}, @@ -52,10 +52,10 @@ const std::array }; const std::array ShaderSource::uniforms = { - UniformBlockInfo{4, true, true, sizeof(FillOutlinePatternDrawableUBO), "FillOutlinePatternDrawableUBO"}, - UniformBlockInfo{5, true, true, sizeof(FillOutlinePatternTilePropsUBO), "FillOutlinePatternTilePropsUBO"}, - UniformBlockInfo{6, true, true, sizeof(FillOutlinePatternEvaluatedPropsUBO), "FillOutlinePatternEvaluatedPropsUBO"}, - UniformBlockInfo{7, true, false, sizeof(FillOutlinePatternInterpolateUBO), "FillOutlinePatternInterpolateUBO"}, + UniformBlockInfo{4, true, true, sizeof(FillOutlinePatternDrawableUBO), idFillOutlinePatternDrawableUBO}, + UniformBlockInfo{5, true, true, sizeof(FillOutlinePatternTilePropsUBO), idFillOutlinePatternTilePropsUBO}, + UniformBlockInfo{6, true, true, sizeof(FillOutlinePatternEvaluatedPropsUBO), idFillOutlinePatternEvaluatedPropsUBO}, + UniformBlockInfo{7, true, false, sizeof(FillOutlinePatternInterpolateUBO), idFillOutlinePatternInterpolateUBO}, }; const std::array ShaderSource::textures = { diff --git a/src/mbgl/shaders/mtl/fill_extrusion.cpp b/src/mbgl/shaders/mtl/fill_extrusion.cpp index 3b80c261aaa..b3dc1472eed 100644 --- a/src/mbgl/shaders/mtl/fill_extrusion.cpp +++ b/src/mbgl/shaders/mtl/fill_extrusion.cpp @@ -12,9 +12,9 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{5, true, false, sizeof(FillExtrusionDrawableUBO), "FillExtrusionDrawableUBO"}, - UniformBlockInfo{6, true, false, sizeof(FillExtrusionDrawablePropsUBO), "FillExtrusionDrawablePropsUBO"}, - UniformBlockInfo{7, true, false, sizeof(FillExtrusionInterpolateUBO), "FillExtrusionInterpolateUBO"}, + UniformBlockInfo{5, true, false, sizeof(FillExtrusionDrawableUBO), idFillExtrusionDrawableUBO}, + UniformBlockInfo{6, true, false, sizeof(FillExtrusionDrawablePropsUBO), idFillExtrusionDrawablePropsUBO}, + UniformBlockInfo{7, true, false, sizeof(FillExtrusionInterpolateUBO), idFillExtrusionInterpolateUBO}, }; const std::array ShaderSource::textures = {}; diff --git a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp index 6e5a78e2803..7f4b301f40b 100644 --- a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp +++ b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp @@ -15,10 +15,10 @@ const std::array }; const std::array ShaderSource::uniforms = { - UniformBlockInfo{6, true, true, sizeof(FillExtrusionDrawableUBO), "FillExtrusionDrawableUBO"}, - UniformBlockInfo{7, true, true, sizeof(FillExtrusionDrawablePropsUBO), "FillExtrusionDrawablePropsUBO"}, - UniformBlockInfo{8, true, true, sizeof(FillExtrusionDrawableTilePropsUBO), "FillExtrusionDrawableTilePropsUBO"}, - UniformBlockInfo{9, true, false, sizeof(FillExtrusionInterpolateUBO), "FillExtrusionInterpolateUBO"}, + UniformBlockInfo{6, true, true, sizeof(FillExtrusionDrawableUBO), idFillExtrusionDrawableUBO}, + UniformBlockInfo{7, true, true, sizeof(FillExtrusionDrawablePropsUBO), idFillExtrusionDrawablePropsUBO}, + UniformBlockInfo{8, true, true, sizeof(FillExtrusionDrawableTilePropsUBO), idFillExtrusionDrawableTilePropsUBO}, + UniformBlockInfo{9, true, false, sizeof(FillExtrusionInterpolateUBO), idFillExtrusionInterpolateUBO}, }; const std::array ShaderSource::textures = { diff --git a/src/mbgl/shaders/mtl/heatmap.cpp b/src/mbgl/shaders/mtl/heatmap.cpp index 23f179b5ce1..9f3d05853b8 100644 --- a/src/mbgl/shaders/mtl/heatmap.cpp +++ b/src/mbgl/shaders/mtl/heatmap.cpp @@ -9,9 +9,9 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{3, true, false, sizeof(HeatmapDrawableUBO), "HeatmapDrawableUBO"}, - UniformBlockInfo{4, true, true, sizeof(HeatmapEvaluatedPropsUBO), "HeatmapEvaluatedPropsUBO"}, - UniformBlockInfo{5, true, false, sizeof(HeatmapInterpolateUBO), "HeatmapInterpolateUBO"}, + UniformBlockInfo{3, true, false, sizeof(HeatmapDrawableUBO), idHeatmapDrawableUBO}, + UniformBlockInfo{4, true, true, sizeof(HeatmapEvaluatedPropsUBO), idHeatmapEvaluatedPropsUBO}, + UniformBlockInfo{5, true, false, sizeof(HeatmapInterpolateUBO), idHeatmapInterpolateUBO}, }; const std::array ShaderSource::textures = {}; diff --git a/src/mbgl/shaders/mtl/heatmap_texture.cpp b/src/mbgl/shaders/mtl/heatmap_texture.cpp index 689ba08fe07..40a8e99cbf7 100644 --- a/src/mbgl/shaders/mtl/heatmap_texture.cpp +++ b/src/mbgl/shaders/mtl/heatmap_texture.cpp @@ -9,7 +9,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{1, true, true, sizeof(HeatmapTextureDrawableUBO), "HeatmapTextureDrawableUBO"}, + UniformBlockInfo{1, true, true, sizeof(HeatmapTextureDrawableUBO), idHeatmapTextureDrawableUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}, diff --git a/src/mbgl/shaders/mtl/hillshade.cpp b/src/mbgl/shaders/mtl/hillshade.cpp index 88713ea10ac..c4d9b1c7ad5 100644 --- a/src/mbgl/shaders/mtl/hillshade.cpp +++ b/src/mbgl/shaders/mtl/hillshade.cpp @@ -8,8 +8,8 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{2, true, true, sizeof(HillshadeDrawableUBO), "HillshadeDrawableUBO"}, - UniformBlockInfo{3, false, true, sizeof(HillshadeEvaluatedPropsUBO), "HillshadeEvaluatedPropsUBO"}, + UniformBlockInfo{2, true, true, sizeof(HillshadeDrawableUBO), idHillshadeDrawableUBO}, + UniformBlockInfo{3, false, true, sizeof(HillshadeEvaluatedPropsUBO), idHillshadeEvaluatedPropsUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}, diff --git a/src/mbgl/shaders/mtl/hillshade_prepare.cpp b/src/mbgl/shaders/mtl/hillshade_prepare.cpp index d4b6cb301bc..717552aa2c1 100644 --- a/src/mbgl/shaders/mtl/hillshade_prepare.cpp +++ b/src/mbgl/shaders/mtl/hillshade_prepare.cpp @@ -10,7 +10,7 @@ const std::array }; const std::array ShaderSource::uniforms = { - UniformBlockInfo{2, true, true, sizeof(HillshadePrepareDrawableUBO), "HillshadePrepareDrawableUBO"}, + UniformBlockInfo{2, true, true, sizeof(HillshadePrepareDrawableUBO), idHillshadePrepareDrawableUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}, diff --git a/src/mbgl/shaders/mtl/line.cpp b/src/mbgl/shaders/mtl/line.cpp index f99d466fcb2..805007aeed9 100644 --- a/src/mbgl/shaders/mtl/line.cpp +++ b/src/mbgl/shaders/mtl/line.cpp @@ -14,10 +14,10 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{8, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, - UniformBlockInfo{9, true, true, sizeof(LineUBO), "LineUBO"}, - UniformBlockInfo{10, true, true, sizeof(LinePropertiesUBO), "LinePropertiesUBO"}, - UniformBlockInfo{11, true, false, sizeof(LineInterpolationUBO), "LineInterpolationUBO"}, + UniformBlockInfo{8, true, false, sizeof(LineDynamicUBO), idLineDynamicUBO}, + UniformBlockInfo{9, true, true, sizeof(LineUBO), idLineUBO}, + UniformBlockInfo{10, true, true, sizeof(LinePropertiesUBO), idLinePropertiesUBO}, + UniformBlockInfo{11, true, false, sizeof(LineInterpolationUBO), idLineInterpolationUBO}, }; const std::array ShaderSource::textures = {}; @@ -33,11 +33,11 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, - UniformBlockInfo{10, true, true, sizeof(LinePatternUBO), "LinePatternUBO"}, - UniformBlockInfo{11, true, true, sizeof(LinePatternPropertiesUBO), "LinePatternPropertiesUBO"}, - UniformBlockInfo{12, true, false, sizeof(LinePatternInterpolationUBO), "LinePatternInterpolationUBO"}, - UniformBlockInfo{13, true, true, sizeof(LinePatternTilePropertiesUBO), "LinePatternTilePropertiesUBO"}, + UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), idLinePatternDynamicUBO}, + UniformBlockInfo{10, true, true, sizeof(LinePatternUBO), idLinePatternUBO}, + UniformBlockInfo{11, true, true, sizeof(LinePatternPropertiesUBO), idLinePatternPropertiesUBO}, + UniformBlockInfo{12, true, false, sizeof(LinePatternInterpolationUBO), idLinePatternInterpolationUBO}, + UniformBlockInfo{13, true, true, sizeof(LinePatternTilePropertiesUBO), idLinePatternTilePropertiesUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}, @@ -55,10 +55,10 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, - UniformBlockInfo{10, true, true, sizeof(LineSDFUBO), "LineSDFUBO"}, - UniformBlockInfo{11, true, true, sizeof(LineSDFPropertiesUBO), "LineSDFPropertiesUBO"}, - UniformBlockInfo{12, true, false, sizeof(LineSDFInterpolationUBO), "LineSDFInterpolationUBO"}, + UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), idLineSDFDynamicUBO}, + UniformBlockInfo{10, true, true, sizeof(LineSDFUBO), idLineSDFUBO}, + UniformBlockInfo{11, true, true, sizeof(LineSDFPropertiesUBO), idLineSDFPropertiesUBO}, + UniformBlockInfo{12, true, false, sizeof(LineSDFInterpolationUBO), idLineSDFInterpolationUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}, @@ -69,8 +69,8 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{2, true, true, sizeof(LineBasicUBO), "LineBasicUBO"}, - UniformBlockInfo{3, true, true, sizeof(LineBasicPropertiesUBO), "LineBasicPropertiesUBO"}, + UniformBlockInfo{2, true, true, sizeof(LineBasicUBO), idLineBasicUBO}, + UniformBlockInfo{3, true, true, sizeof(LineBasicPropertiesUBO), idLineBasicPropertiesUBO}, }; const std::array ShaderSource::textures = {}; diff --git a/src/mbgl/shaders/mtl/line_gradient.cpp b/src/mbgl/shaders/mtl/line_gradient.cpp index e981fe15719..131a6a1a597 100644 --- a/src/mbgl/shaders/mtl/line_gradient.cpp +++ b/src/mbgl/shaders/mtl/line_gradient.cpp @@ -13,10 +13,10 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{7, true, false, sizeof(LineDynamicUBO), "LineDynamicUBO"}, - UniformBlockInfo{8, true, true, sizeof(LineGradientUBO), "LineGradientUBO"}, - UniformBlockInfo{9, true, true, sizeof(LineGradientPropertiesUBO), "LineGradientPropertiesUBO"}, - UniformBlockInfo{10, true, false, sizeof(LineGradientInterpolationUBO), "LineGradientInterpolationUBO"}, + UniformBlockInfo{7, true, false, sizeof(LineDynamicUBO), idLineGradientDynamicUBO}, + UniformBlockInfo{8, true, true, sizeof(LineGradientUBO), idLineGradientUBO}, + UniformBlockInfo{9, true, true, sizeof(LineGradientPropertiesUBO), idLineGradientPropertiesUBO}, + UniformBlockInfo{10, true, false, sizeof(LineGradientInterpolationUBO), idLineGradientInterpolationUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image"}, diff --git a/src/mbgl/shaders/mtl/raster.cpp b/src/mbgl/shaders/mtl/raster.cpp index 0b74b219509..cd0a21dd23c 100644 --- a/src/mbgl/shaders/mtl/raster.cpp +++ b/src/mbgl/shaders/mtl/raster.cpp @@ -8,7 +8,7 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{2, true, true, sizeof(RasterDrawableUBO), "RasterDrawableUBO"}, + UniformBlockInfo{2, true, true, sizeof(RasterDrawableUBO), idRasterDrawableUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_image0"}, diff --git a/src/mbgl/shaders/mtl/shader_program.cpp b/src/mbgl/shaders/mtl/shader_program.cpp index bcb7df20430..c75ef817ec4 100644 --- a/src/mbgl/shaders/mtl/shader_program.cpp +++ b/src/mbgl/shaders/mtl/shader_program.cpp @@ -31,13 +31,12 @@ shaders::AttributeInfo::AttributeInfo(std::size_t index_, gfx::AttributeDataType nameID(stringIndexer().get(name_)) {} shaders::UniformBlockInfo::UniformBlockInfo( - std::size_t index_, bool vertex_, bool fragment_, std::size_t size_, std::string_view name_) + std::size_t index_, bool vertex_, bool fragment_, std::size_t size_, std::size_t id_) : index(index_), vertex(vertex_), fragment(fragment_), size(size_), - name(name_), - nameID(stringIndexer().get(name_)) {} + id(id_) {} shaders::TextureInfo::TextureInfo(std::size_t index_, std::string_view name_) : index(index_), @@ -202,7 +201,7 @@ void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) { // Indexes must be unique, if there's a conflict check the `attributes` array in the shader vertexAttributes.visitAttributes( [&](auto, const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); - uniformBlocks.visit([&](auto, const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); + uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); #endif vertexAttributes.add(stringIndexer().get(info.name), index, info.dataType, 1); } @@ -213,9 +212,9 @@ void ShaderProgram::initUniformBlock(const shaders::UniformBlockInfo& info) { // Indexes must be unique, if there's a conflict check the `attributes` array in the shader vertexAttributes.visitAttributes( [&](auto, const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); - uniformBlocks.visit([&](auto, const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); + uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); #endif - if (const auto& block_ = uniformBlocks.add(stringIndexer().get(info.name), index, info.size)) { + if (const auto& block_ = uniformBlocks.set(info.id, index, info.size)) { auto& block = static_cast(*block_); block.setBindVertex(info.vertex); block.setBindFragment(info.fragment); diff --git a/src/mbgl/shaders/mtl/symbol_icon.cpp b/src/mbgl/shaders/mtl/symbol_icon.cpp index e1b77655688..cfb82bb8383 100644 --- a/src/mbgl/shaders/mtl/symbol_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_icon.cpp @@ -15,11 +15,11 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{8, true, true, sizeof(SymbolDrawableUBO), "SymbolDrawableUBO"}, - UniformBlockInfo{9, true, false, sizeof(SymbolDynamicUBO), "SymbolDynamicUBO"}, - UniformBlockInfo{10, true, true, sizeof(SymbolDrawablePaintUBO), "SymbolDrawablePaintUBO"}, - UniformBlockInfo{11, true, false, sizeof(SymbolDrawableTilePropsUBO), "SymbolDrawableTilePropsUBO"}, - UniformBlockInfo{12, true, false, sizeof(SymbolDrawableInterpolateUBO), "SymbolDrawableInterpolateUBO"}, + UniformBlockInfo{6, true, true, sizeof(SymbolDrawableUBO), idSymbolDrawableUBO}, + UniformBlockInfo{7, true, false, sizeof(SymbolDynamicUBO), idSymbolDynamicUBO}, + UniformBlockInfo{8, true, true, sizeof(SymbolDrawablePaintUBO), idSymbolDrawablePaintUBO}, + UniformBlockInfo{9, true, false, sizeof(SymbolDrawableTilePropsUBO), idSymbolDrawableTilePropsUBO}, + UniformBlockInfo{10, true, false, sizeof(SymbolDrawableInterpolateUBO), idSymbolDrawableInterpolateUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_texture"}, diff --git a/src/mbgl/shaders/mtl/symbol_sdf.cpp b/src/mbgl/shaders/mtl/symbol_sdf.cpp index 74998b262ea..074d706e45c 100644 --- a/src/mbgl/shaders/mtl/symbol_sdf.cpp +++ b/src/mbgl/shaders/mtl/symbol_sdf.cpp @@ -21,11 +21,11 @@ const std::array ShaderSource ShaderSource::uniforms = { - UniformBlockInfo{10, true, true, sizeof(SymbolDrawableUBO), "SymbolDrawableUBO"}, - UniformBlockInfo{11, true, true, sizeof(SymbolDynamicUBO), "SymbolDynamicUBO"}, - UniformBlockInfo{12, true, true, sizeof(SymbolDrawablePaintUBO), "SymbolDrawablePaintUBO"}, - UniformBlockInfo{13, true, true, sizeof(SymbolDrawableTilePropsUBO), "SymbolDrawableTilePropsUBO"}, - UniformBlockInfo{14, true, false, sizeof(SymbolDrawableInterpolateUBO), "SymbolDrawableInterpolateUBO"}, + UniformBlockInfo{10, true, true, sizeof(SymbolDrawableUBO), idSymbolDrawableUBO}, + UniformBlockInfo{11, true, true, sizeof(SymbolDynamicUBO), idSymbolDynamicUBO}, + UniformBlockInfo{12, true, true, sizeof(SymbolDrawablePaintUBO), idSymbolDrawablePaintUBO}, + UniformBlockInfo{13, true, true, sizeof(SymbolDrawableTilePropsUBO), idSymbolDrawableTilePropsUBO}, + UniformBlockInfo{14, true, false, sizeof(SymbolDrawableInterpolateUBO), idSymbolDrawableInterpolateUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_texture"}, diff --git a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp index 9c6ebdeb20e..afcd477efeb 100644 --- a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp @@ -20,11 +20,11 @@ const std::array }; const std::array ShaderSource::uniforms = { - UniformBlockInfo{9, true, true, sizeof(SymbolDrawableUBO), "SymbolDrawableUBO"}, - UniformBlockInfo{10, true, true, sizeof(SymbolDynamicUBO), "SymbolDynamicUBO"}, - UniformBlockInfo{11, true, true, sizeof(SymbolDrawablePaintUBO), "SymbolDrawablePaintUBO"}, - UniformBlockInfo{12, true, true, sizeof(SymbolDrawableTilePropsUBO), "SymbolDrawableTilePropsUBO"}, - UniformBlockInfo{13, true, false, sizeof(SymbolDrawableInterpolateUBO), "SymbolDrawableInterpolateUBO"}, + UniformBlockInfo{9, true, true, sizeof(SymbolDrawableUBO), idSymbolDrawableUBO}, + UniformBlockInfo{10, true, true, sizeof(SymbolDynamicUBO), idSymbolDynamicUBO}, + UniformBlockInfo{11, true, true, sizeof(SymbolDrawablePaintUBO), idSymbolDrawablePaintUBO}, + UniformBlockInfo{12, true, true, sizeof(SymbolDrawableTilePropsUBO), idSymbolDrawableTilePropsUBO}, + UniformBlockInfo{13, true, false, sizeof(SymbolDrawableInterpolateUBO), idSymbolDrawableInterpolateUBO}, }; const std::array ShaderSource::textures = { TextureInfo{0, "u_texture"}, diff --git a/src/mbgl/style/layers/custom_drawable_layer.cpp b/src/mbgl/style/layers/custom_drawable_layer.cpp index 0b48648ba82..d7ee3b3e2fe 100644 --- a/src/mbgl/style/layers/custom_drawable_layer.cpp +++ b/src/mbgl/style/layers/custom_drawable_layer.cpp @@ -27,6 +27,8 @@ namespace mbgl { namespace style { +using namespace shaders; + namespace { const LayerTypeInfo typeInfoCustomDrawable{"custom-drawable", LayerTypeInfo::Source::NotRequired, @@ -97,20 +99,15 @@ class LineDrawableTweaker : public gfx::DrawableTweaker { const auto matrix = LayerTweaker::getTileMatrix( tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); - static const StringIdentity idLineDynamicUBOName = stringIndexer().get("LineDynamicUBO"); const shaders::LineDynamicUBO dynamicUBO = { /*units_to_pixels = */ {1.0f / parameters.pixelsToGLUnits[0], 1.0f / parameters.pixelsToGLUnits[1]}, 0, 0}; - static const StringIdentity idLineUBOName = stringIndexer().get("LineUBO"); const shaders::LineUBO lineUBO{/*matrix = */ util::cast(matrix), /*ratio = */ 1.0f / tileID.pixelsToTileUnits(1.0f, zoom), 0, 0, 0}; - static const StringIdentity idLinePropertiesUBOName = stringIndexer().get("LinePropertiesUBO"); - - static const StringIdentity idLineInterpolationUBOName = stringIndexer().get("LineInterpolationUBO"); const shaders::LineInterpolationUBO lineInterpolationUBO{/*color_t =*/0.f, /*blur_t =*/0.f, /*opacity_t =*/0.f, @@ -120,10 +117,10 @@ class LineDrawableTweaker : public gfx::DrawableTweaker { 0, 0}; auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idLineDynamicUBOName, &dynamicUBO, parameters.context); - uniforms.createOrUpdate(idLineUBOName, &lineUBO, parameters.context); - uniforms.createOrUpdate(idLinePropertiesUBOName, &linePropertiesUBO, parameters.context); - uniforms.createOrUpdate(idLineInterpolationUBOName, &lineInterpolationUBO, parameters.context); + uniforms.createOrUpdate(idLineDynamicUBO, &dynamicUBO, parameters.context); + uniforms.createOrUpdate(idLineUBO, &lineUBO, parameters.context); + uniforms.createOrUpdate(idLinePropertiesUBO, &linePropertiesUBO, parameters.context); + uniforms.createOrUpdate(idLineInterpolationUBO, &lineInterpolationUBO, parameters.context); }; private: @@ -151,10 +148,8 @@ class FillDrawableTweaker : public gfx::DrawableTweaker { const auto matrix = LayerTweaker::getTileMatrix( tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); - static const StringIdentity idFillDrawableUBOName = stringIndexer().get("FillDrawableUBO"); const shaders::FillDrawableUBO fillUBO{/*matrix = */ util::cast(matrix)}; - static const StringIdentity idFillEvaluatedPropsUBOName = stringIndexer().get("FillEvaluatedPropsUBO"); const shaders::FillEvaluatedPropsUBO fillPropertiesUBO{ /* .color = */ color, /* .opacity = */ opacity, @@ -163,7 +158,6 @@ class FillDrawableTweaker : public gfx::DrawableTweaker { 0, }; - static const StringIdentity idFillInterpolateUBOName = stringIndexer().get("FillInterpolateUBO"); const shaders::FillInterpolateUBO fillInterpolateUBO{ /* .color_t = */ 0.f, /* .opacity_t = */ 0.f, @@ -171,9 +165,9 @@ class FillDrawableTweaker : public gfx::DrawableTweaker { 0, }; auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idFillDrawableUBOName, &fillUBO, parameters.context); - uniforms.createOrUpdate(idFillEvaluatedPropsUBOName, &fillPropertiesUBO, parameters.context); - uniforms.createOrUpdate(idFillInterpolateUBOName, &fillInterpolateUBO, parameters.context); + uniforms.createOrUpdate(idFillDrawableUBO, &fillUBO, parameters.context); + uniforms.createOrUpdate(idFillEvaluatedPropsUBO, &fillPropertiesUBO, parameters.context); + uniforms.createOrUpdate(idFillInterpolateUBO, &fillInterpolateUBO, parameters.context); }; private: From 867e7cc813cc47f57d0e4d140a04e46c69ad7bae Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Wed, 31 Jan 2024 14:48:41 -0800 Subject: [PATCH 53/96] Use `enableDepth` option (#2073) --- include/mbgl/gfx/drawable.hpp | 2 +- include/mbgl/mtl/tile_layer_group.hpp | 7 +++++ src/mbgl/mtl/drawable.cpp | 4 ++- src/mbgl/mtl/tile_layer_group.cpp | 42 ++++++++++++++++++++++++--- src/mbgl/renderer/layer_tweaker.cpp | 5 ++-- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/include/mbgl/gfx/drawable.hpp b/include/mbgl/gfx/drawable.hpp index be68a343e0a..621e16f8f5b 100644 --- a/include/mbgl/gfx/drawable.hpp +++ b/include/mbgl/gfx/drawable.hpp @@ -143,7 +143,7 @@ class Drawable { void setDrawPriority(DrawPriority value) { drawPriority = value; } /// Whether to enable depth testing - bool getEnableDepth() { return enableDepth; } + bool getEnableDepth() const { return enableDepth; } virtual void setEnableDepth(bool value) { enableDepth = value; } /// Determines depth range within the layer for 2D drawables diff --git a/include/mbgl/mtl/tile_layer_group.hpp b/include/mbgl/mtl/tile_layer_group.hpp index d24198d3266..58b15d4e3f1 100644 --- a/include/mbgl/mtl/tile_layer_group.hpp +++ b/include/mbgl/mtl/tile_layer_group.hpp @@ -1,7 +1,12 @@ #pragma once +#include #include +#include + +#include + namespace mbgl { namespace mtl { @@ -17,6 +22,8 @@ class TileLayerGroup : public mbgl::TileLayerGroup { void render(RenderOrchestrator&, PaintParameters&) override; protected: + std::optional stateNone; + std::optional stateDepth; }; } // namespace mtl diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index 79a5caed8f6..d0883842c11 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -235,7 +235,9 @@ void Drawable::draw(PaintParameters& parameters) const { if (enableStencil && !newStencilMode) { newStencilMode = parameters.stencilModeForClipping(tileID->toUnwrapped()); } - const auto depthMode = parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType()); + const auto depthMode = getEnableDepth() + ? parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType()) + : gfx::DepthMode::disabled(); const auto stencilMode = enableStencil ? parameters.stencilModeForClipping(tileID->toUnwrapped()) : gfx::StencilMode::disabled(); impl->depthStencilState = context.makeDepthStencilState(depthMode, stencilMode, renderable); diff --git a/src/mbgl/mtl/tile_layer_group.cpp b/src/mbgl/mtl/tile_layer_group.cpp index adb334d8ed3..678b8953be8 100644 --- a/src/mbgl/mtl/tile_layer_group.cpp +++ b/src/mbgl/mtl/tile_layer_group.cpp @@ -74,15 +74,49 @@ void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { // If we're doing 3D stenciling and have any features to draw, set up the single-value stencil mask. // If we're doing 2D stenciling and have any drawables with tile IDs, render each tile into the stencil buffer with // a different value. - MTLDepthStencilStatePtr stateWithStencil, stateWithoutStencil; + // We can keep the depth-based descriptors, but the stencil-based ones can change + // every time, as a new value is assigned in each call to `stencilModeFor3D`. + std::optional stateStencil, stateDepthStencil; + std::function getDepthStencilState; if (features3d) { + // If we're using group-wide states, build only the ones that actually get used + getDepthStencilState = [&](bool depth, bool stencil) -> const MTLDepthStencilStatePtr& { + if (depth) { + // We assume this doesn't change over the lifetime of a layer group. + const auto depthMode = parameters.depthModeFor3D(); + if (stencil) { + if (!stateDepthStencil.has_value()) { + stateDepthStencil = context.makeDepthStencilState(depthMode, stencilMode3d, renderable); + } + return *stateDepthStencil; + } else { + if (!stateDepth) { + stateDepth = context.makeDepthStencilState(depthMode, gfx::StencilMode::disabled(), renderable); + } + return *stateDepth; + } + } else { + if (stencil) { + if (!stateStencil.has_value()) { + stateStencil = context.makeDepthStencilState( + gfx::DepthMode::disabled(), stencilMode3d, renderable); + } + return *stateStencil; + } else { + if (!stateNone) { + stateNone = context.makeDepthStencilState( + gfx::DepthMode::disabled(), gfx::StencilMode::disabled(), renderable); + } + return *stateNone; + } + } + }; + const auto depthMode = parameters.depthModeFor3D(); if (stencil3d) { stencilMode3d = parameters.stencilModeFor3D(); - stateWithStencil = context.makeDepthStencilState(depthMode, stencilMode3d, renderable); encoder->setStencilReferenceValue(stencilMode3d.ref); } - stateWithoutStencil = context.makeDepthStencilState(depthMode, gfx::StencilMode::disabled(), renderable); } else if (stencilTiles && !stencilTiles->empty()) { parameters.renderTileClippingMasks(stencilTiles); } @@ -100,7 +134,7 @@ void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { // stencil mode for features with stencil enabled or disable stenciling. // 2D drawables will set their own stencil mode within `draw`. if (features3d) { - const auto& state = drawable.getEnableStencil() ? stateWithStencil : stateWithoutStencil; + const auto state = getDepthStencilState(drawable.getEnableDepth(), drawable.getEnableStencil()); renderPass.setDepthStencilState(state); } diff --git a/src/mbgl/renderer/layer_tweaker.cpp b/src/mbgl/renderer/layer_tweaker.cpp index 426fd57b385..b39b25c7586 100644 --- a/src/mbgl/renderer/layer_tweaker.cpp +++ b/src/mbgl/renderer/layer_tweaker.cpp @@ -44,8 +44,9 @@ mat4 LayerTweaker::getTileMatrix(const UnwrappedTileID& tileID, : parameters.transformParams.projMatrix); #if !MLN_RENDER_BACKEND_OPENGL - // Offset the projection matrix NDC depth range for the drawable's layer and sublayer. - if (!drawable.getIs3D()) { + // If this drawable is participating in depth testing, offset the + // projection matrix NDC depth range for the drawable's layer and sublayer. + if (!drawable.getIs3D() && drawable.getEnableDepth()) { projMatrix[14] -= ((1 + drawable.getLayerIndex()) * PaintParameters::numSublayers - drawable.getSubLayerIndex()) * PaintParameters::depthEpsilon; From b683cceb3f4f1199c548af62c6bd4b4130876990 Mon Sep 17 00:00:00 2001 From: Alex Cristici Date: Thu, 1 Feb 2024 20:53:28 +0200 Subject: [PATCH 54/96] iOS moving camera benchmark (#2076) Co-authored-by: Bart Louwers --- .../ios/benchmark/MBXBenchViewController.mm | 40 +++++++------------ platform/ios/benchmark/locations.cpp | 25 +++++++----- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/platform/ios/benchmark/MBXBenchViewController.mm b/platform/ios/benchmark/MBXBenchViewController.mm index 12b167ea369..cc7218e45bf 100644 --- a/platform/ios/benchmark/MBXBenchViewController.mm +++ b/platform/ios/benchmark/MBXBenchViewController.mm @@ -31,7 +31,7 @@ void onDidFinishRenderingFrame(RenderFrameStatus status) override final { //NSLog(@"Frame rendering time: %4.1f ms", status.frameRenderingTime * 1e3); bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; - [mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered + [mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered frameEncodingTime:status.frameEncodingTime frameRenderingTime:status.frameRenderingTime]; } @@ -176,15 +176,14 @@ - (void)redirectLogToDocuments: (NSString*) fileName } size_t idx = 0; -enum class State { None, WaitingForAssets, WarmingUp, Benchmarking } state = State::None; +enum class State { None, WaitingForAssets, Benchmarking } state = State::None; int frames = 0; double totalFrameEncodingTime = 0; double totalFrameRenderingTime = 0; std::chrono::steady_clock::time_point started; std::vector> > result; -static const int warmupDuration = 20; // frames -static const int benchmarkDuration = 200; // frames +static const int benchmarkDuration = 5; // seconds namespace mbgl { extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments; @@ -225,7 +224,9 @@ - (void)startBenchmarkIteration cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude); cameraOptions.zoom = location.zoom; cameraOptions.bearing = location.bearing; - map->jumpTo(cameraOptions); + mbgl::AnimationOptions animationOptions; + animationOptions.duration.emplace(std::chrono::duration_cast(std::chrono::duration(benchmarkDuration))); + map->easeTo(cameraOptions, animationOptions); state = State::WaitingForAssets; NSLog(@"Benchmarking \"%s\"", location.name.c_str()); @@ -293,17 +294,17 @@ - (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered frames++; totalFrameEncodingTime += frameEncodingTime; totalFrameRenderingTime += frameRenderingTime; - if (frames >= benchmarkDuration) + + const auto duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - started).count(); + if (duration >= benchmarkDuration * 1e6) { state = State::None; // Report FPS - const auto duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - started).count(); - const auto wallClockFPS = double(frames * 1e6) / duration; const auto frameEncodingTime = static_cast(totalFrameEncodingTime) / frames; const auto frameRenderingTime = static_cast(totalFrameRenderingTime) / frames; result.emplace_back(mbgl::bench::locations[idx].name, std::make_pair(frameEncodingTime, frameRenderingTime)); - NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%.1f sync FPS)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, wallClockFPS); + NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames); // Start benchmarking the next location. idx++; @@ -315,30 +316,17 @@ - (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered } return; } - else if (state == State::WarmingUp) + else if (state == State::WaitingForAssets) { - frames++; - if (frames >= warmupDuration) + if (map->isFullyLoaded()) { + // Start the benchmarking timer. frames = 0; totalFrameEncodingTime = 0; totalFrameRenderingTime = 0; state = State::Benchmarking; started = std::chrono::steady_clock::now(); - NSLog(@"- Benchmarking for %d frames...", benchmarkDuration); - } - dispatch_async(dispatch_get_main_queue(), ^{ - [self renderFrame]; - }); - return; - } - else if (state == State::WaitingForAssets) - { - if (map->isFullyLoaded()) - { - // Start the benchmarking timer. - state = State::WarmingUp; - NSLog(@"- Warming up for %d frames...", warmupDuration); + NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration); } dispatch_async(dispatch_get_main_queue(), ^{ [self renderFrame]; diff --git a/platform/ios/benchmark/locations.cpp b/platform/ios/benchmark/locations.cpp index a546bdd7fd3..e2ff09ffc9b 100644 --- a/platform/ios/benchmark/locations.cpp +++ b/platform/ios/benchmark/locations.cpp @@ -4,15 +4,22 @@ namespace mbgl { namespace bench { const std::vector locations = { - { "boston", -71.07766, 42.36513, 16, 0.0 }, - { "paris", 2.3411, 48.8664, 11, 0.0 }, - { "paris2", 2.3516, 48.8356, 13, 273.8 }, - { "alps", 10.6107, 46.9599, 6, 0.0 }, - { "us east", -84.3395, 36.9400, 5, 0.0 }, - { "greater la", -117.9529, 34.0259, 9, 0.0 }, - { "sf", -122.4202, 37.7625, 14, 0.0 }, - { "oakland", -122.2328, 37.8267, 12, 0.0 }, - { "germany", 9.2280, 50.9262, 6, 0.0 }, + { "SF zoom in", -122.4194, 37.7749, 14, 0.0 }, + { "SF zoom out", -122.4194, 37.7749, 2, 0.0 }, + { "DC zoom in", -77.0369, 38.9072, 14, 0.0 }, + { "DC zoom out", -77.0369, 38.9072, 2, 0.0 }, + { "AMS zoom in", 4.8952, 52.3702, 14, 0.0 }, + { "AMS zoom out", 4.8952, 52.3702, 2, 0.0 }, + { "HEL zoom in", 24.9384, 60.1699, 14, 0.0 }, + { "HEL zoom out", 24.9384, 60.1699, 2, 0.0 }, + { "AYA zoom in", -74.2236, -13.1639, 14, 0.0 }, + { "AYA zoom out", -74.2236, -13.1639, 2, 0.0 }, + { "BER zoom in", 13.4050, 52.5200, 14, 0.0 }, + { "BER zoom out", 13.4050, 52.5200, 2, 0.0 }, + { "BAN zoom in", 77.5946, 12.9716, 14, 0.0 }, + { "BAN zoom out", 77.5946, 12.9716, 2, 0.0 }, + { "SHA zoom in", 121.4737, 31.2304, 14, 0.0 }, + { "SHA zoom out", 121.4737, 31.2304, 2, 0.0 }, }; } From d2feec269e2fa787ef1473fb2bdeb3e161b63658 Mon Sep 17 00:00:00 2001 From: Alex Cristici Date: Thu, 1 Feb 2024 22:30:56 +0200 Subject: [PATCH 55/96] Android benchmark frame time (#1958) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bart Louwers --- include/mbgl/gfx/renderable.hpp | 6 ++ .../src/cpp/android_renderer_backend.cpp | 12 +++ .../src/cpp/android_renderer_backend.hpp | 6 ++ .../src/cpp/map_renderer.cpp | 7 +- .../src/cpp/map_renderer.hpp | 2 + .../src/cpp/native_map_view.cpp | 10 ++- .../android/maps/MapChangeReceiver.java | 4 +- .../maplibre/android/maps/MapLibreMap.java | 4 + .../org/maplibre/android/maps/MapView.java | 6 +- .../org/maplibre/android/maps/NativeMap.java | 2 + .../maplibre/android/maps/NativeMapView.java | 11 ++- .../android/maps/renderer/MapRenderer.java | 12 ++- .../android/maps/MapChangeReceiverTest.kt | 32 +++---- .../testapp/maps/RemoveUnusedImagesTest.kt | 4 +- .../activity/benchmark/BenchmarkActivity.kt | 89 ++++++++++++++----- .../activity/fragment/MapFragmentActivity.kt | 2 +- .../fragment/SupportMapFragmentActivity.kt | 2 +- .../imagegenerator/SnapshotActivity.kt | 2 +- .../activity/maplayout/MapChangeActivity.kt | 2 +- .../android/testapp/utils/BenchmarkUtils.kt | 31 +++++++ .../include/mbgl/gfx/headless_backend.hpp | 5 -- .../default/src/mbgl/gl/headless_backend.cpp | 2 +- 22 files changed, 191 insertions(+), 62 deletions(-) diff --git a/include/mbgl/gfx/renderable.hpp b/include/mbgl/gfx/renderable.hpp index dd95dc12315..f6c707d6eee 100644 --- a/include/mbgl/gfx/renderable.hpp +++ b/include/mbgl/gfx/renderable.hpp @@ -21,6 +21,12 @@ class RenderableResource { }; class Renderable { +public: + enum class SwapBehaviour { + NoFlush, + Flush + }; + protected: Renderable(const Size size_, std::unique_ptr resource_) : size(size_), diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp index 2d8880a3e64..c20b21ced18 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.cpp @@ -22,6 +22,8 @@ class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource { backend.setViewport(0, 0, backend.getSize()); } + void swap() override { backend.swap(); } + private: AndroidRendererBackend& backend; }; @@ -62,5 +64,15 @@ void AndroidRendererBackend::markContextLost() { } } +void AndroidRendererBackend::setSwapBehavior(SwapBehaviour swapBehaviour_) { + swapBehaviour = swapBehaviour_; +} + +void AndroidRendererBackend::swap() { + if (swapBehaviour == SwapBehaviour::Flush) { + static_cast(getContext()).finish(); + } +} + } // namespace android } // namespace mbgl diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp index cdd9df9511c..9b06a6cca14 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/android_renderer_backend.hpp @@ -19,6 +19,12 @@ class AndroidRendererBackend : public gl::RendererBackend, public mbgl::gfx::Ren void resizeFramebuffer(int width, int height); PremultipliedImage readFramebuffer(); + void setSwapBehavior(SwapBehaviour swapBehaviour); + void swap(); + +private: + SwapBehaviour swapBehaviour = SwapBehaviour::NoFlush; + // mbgl::gfx::RendererBackend implementation public: mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp index 4941e3316f9..3ea039d1fda 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp @@ -236,6 +236,10 @@ void MapRenderer::onSurfaceDestroyed(JNIEnv&) { resetRenderer(); } +void MapRenderer::setSwapBehaviorFlush(JNIEnv&, jboolean flush) { + backend->setSwapBehavior(flush ? gfx::Renderable::SwapBehaviour::Flush : gfx::Renderable::SwapBehaviour::NoFlush); +} + // Static methods // void MapRenderer::registerNative(jni::JNIEnv& env) { @@ -256,7 +260,8 @@ void MapRenderer::registerNative(jni::JNIEnv& env) { METHOD(&MapRenderer::onRendererReset, "nativeReset"), METHOD(&MapRenderer::onSurfaceCreated, "nativeOnSurfaceCreated"), METHOD(&MapRenderer::onSurfaceChanged, "nativeOnSurfaceChanged"), - METHOD(&MapRenderer::onSurfaceDestroyed, "nativeOnSurfaceDestroyed")); + METHOD(&MapRenderer::onSurfaceDestroyed, "nativeOnSurfaceDestroyed"), + METHOD(&MapRenderer::setSwapBehaviorFlush, "nativeSetSwapBehaviorFlush")); } MapRenderer& MapRenderer::getNativePeer(JNIEnv& env, const jni::Object& jObject) { diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp index 41f0259a50d..25ffc60695d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp @@ -107,6 +107,8 @@ class MapRenderer : public Scheduler { // Called on either Main or GL thread // void onRendererReset(JNIEnv&); + void setSwapBehaviorFlush(JNIEnv&, jboolean flush); + private: jni::WeakReference, jni::EnvAttachingDeleter> javaPeer; diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp index 59f0719149e..988eb929a46 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/native_map_view.cpp @@ -189,11 +189,15 @@ void NativeMapView::onDidFinishRenderingFrame(MapObserver::RenderFrameStatus sta android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); - static auto onDidFinishRenderingFrame = javaClass.GetMethod(*_env, "onDidFinishRenderingFrame"); + static auto onDidFinishRenderingFrame = javaClass.GetMethod( + *_env, "onDidFinishRenderingFrame"); auto weakReference = javaPeer.get(*_env); if (weakReference) { - weakReference.Call( - *_env, onDidFinishRenderingFrame, (jboolean)(status.mode != MapObserver::RenderMode::Partial)); + weakReference.Call(*_env, + onDidFinishRenderingFrame, + (jboolean)(status.mode != MapObserver::RenderMode::Partial), + (jdouble)status.frameEncodingTime, + (jdouble)status.frameRenderingTime); } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java index 72d4d3e371d..7807c3d3286 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapChangeReceiver.java @@ -135,11 +135,11 @@ public void onWillStartRenderingFrame() { } @Override - public void onDidFinishRenderingFrame(boolean fully) { + public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { try { if (!onDidFinishRenderingFrameList.isEmpty()) { for (MapView.OnDidFinishRenderingFrameListener listener : onDidFinishRenderingFrameList) { - listener.onDidFinishRenderingFrame(fully); + listener.onDidFinishRenderingFrame(fully, frameEncodingTime, frameRenderingTime); } } } catch (Throwable err) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java index db5873653c8..47748d9bd3b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapLibreMap.java @@ -103,6 +103,10 @@ public void triggerRepaint() { nativeMapView.triggerRepaint(); } + public void setSwapBehaviorFlush(boolean flush) { + nativeMapView.setSwapBehaviorFlush(flush); + } + void initialise(@NonNull Context context, @NonNull MapLibreMapOptions options) { transform.initialise(this, options); uiSettings.initialise(context, options); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java index 8c927970c57..d603b3ab626 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/MapView.java @@ -1012,7 +1012,7 @@ public interface OnDidFinishRenderingFrameListener { * * @param fully true if all frames have been rendered, false if partially rendered */ - void onDidFinishRenderingFrame(boolean fully); + void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime); } /** @@ -1185,7 +1185,7 @@ private class InitialRenderCallback implements OnDidFinishRenderingFrameListener } @Override - public void onDidFinishRenderingFrame(boolean fully) { + public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { if (maplibreMap != null && maplibreMap.getStyle() != null && maplibreMap.getStyle().isFullyLoaded()) { renderCount++; if (renderCount == 3) { @@ -1357,7 +1357,7 @@ public void onDidFailLoadingMap(String errorMessage) { } @Override - public void onDidFinishRenderingFrame(boolean fully) { + public void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { if (maplibreMap != null) { maplibreMap.onUpdateFullyRendered(); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java index 36da6e4184d..48993886b43 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMap.java @@ -239,6 +239,8 @@ List queryRenderedFeatures(@NonNull RectF coordinates, void triggerRepaint(); + void setSwapBehaviorFlush(boolean flush); + // // Deprecated Annotations API // diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java index 5e1088c54b8..9caea12b70c 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/NativeMapView.java @@ -1026,6 +1026,11 @@ public void triggerRepaint() { nativeTriggerRepaint(); } + @Override + public void setSwapBehaviorFlush(boolean flush) { + mapRenderer.setSwapBehaviorFlush(flush); + } + @NonNull @Override public RectF getDensityDependantRectangle(final RectF rectangle) { @@ -1091,9 +1096,9 @@ private void onWillStartRenderingFrame() { } @Keep - private void onDidFinishRenderingFrame(boolean fully) { + private void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime) { if (stateCallback != null) { - stateCallback.onDidFinishRenderingFrame(fully); + stateCallback.onDidFinishRenderingFrame(fully, frameEncodingTime, frameRenderingTime); } } @@ -1581,7 +1586,7 @@ interface StateCallback extends StyleCallback { void onWillStartRenderingFrame(); - void onDidFinishRenderingFrame(boolean fully); + void onDidFinishRenderingFrame(boolean fully, double frameEncodingTime, double frameRenderingTime); void onWillStartRenderingMap(); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java index 0e8fe35b474..7be40232479 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java @@ -102,6 +102,10 @@ protected void onDrawFrame(GL10 gl) { } } + public void setSwapBehaviorFlush(boolean flush) { + nativeSetSwapBehaviorFlush(flush); + } + /** * May be called from any thread. *

@@ -134,12 +138,16 @@ private native void nativeInitialize(MapRenderer self, private native void nativeRender(); + private native void nativeSetSwapBehaviorFlush(boolean flush); + private long timeElapsed; private void updateFps() { long currentTime = System.nanoTime(); - double fps = 1E9 / ((currentTime - timeElapsed)); - onFpsChangedListener.onFpsChanged(fps); + if (timeElapsed > 0) { + double fps = 1E9 / ((currentTime - timeElapsed)); + onFpsChangedListener.onFpsChanged(fps); + } timeElapsed = currentTime; } diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt b/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt index 9a41d4cc513..37d1d285af7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/org/maplibre/android/maps/MapChangeReceiverTest.kt @@ -401,22 +401,22 @@ class MapChangeReceiverTest { mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true, .0, .0) mapChangeEventManager!!.removeOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(true,.0, .0) mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) Logger.setLoggerDefinition(loggerDefinition) val exc: Exception = RuntimeException() Mockito.doThrow(exc).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(true) + ?.onDidFinishRenderingFrame(true, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: RuntimeException) { Mockito.verify(loggerDefinition)?.e( @@ -427,9 +427,9 @@ class MapChangeReceiverTest { } val err: Error = ExecutionError("", Error()) Mockito.doThrow(err).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(true) + ?.onDidFinishRenderingFrame(true, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(true) + mapChangeEventManager!!?.onDidFinishRenderingFrame(true, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: ExecutionError) { Mockito.verify(loggerDefinition)?.e( @@ -445,22 +445,22 @@ class MapChangeReceiverTest { mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false, .0, .0) mapChangeEventManager!!.removeOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) - Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) + Mockito.verify(onDidFinishRenderingFrameListener)?.onDidFinishRenderingFrame(false, .0, .0) mapChangeEventManager!!.addOnDidFinishRenderingFrameListener( onDidFinishRenderingFrameListener ) Logger.setLoggerDefinition(loggerDefinition) val exc: Exception = RuntimeException() Mockito.doThrow(exc).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(false) + ?.onDidFinishRenderingFrame(false, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: RuntimeException) { Mockito.verify(loggerDefinition)?.e( @@ -471,9 +471,9 @@ class MapChangeReceiverTest { } val err: Error = ExecutionError("", Error()) Mockito.doThrow(err).`when`(onDidFinishRenderingFrameListener) - ?.onDidFinishRenderingFrame(false) + ?.onDidFinishRenderingFrame(false, .0, .0) try { - mapChangeEventManager!!?.onDidFinishRenderingFrame(false) + mapChangeEventManager!!?.onDidFinishRenderingFrame(false, .0, .0) Assert.fail("The exception should've been re-thrown.") } catch (throwable: ExecutionError) { Mockito.verify(loggerDefinition)?.e( diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt index 3384f494c8c..40842838b61 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RemoveUnusedImagesTest.kt @@ -60,7 +60,7 @@ class RemoveUnusedImagesTest : AppCenter() { mapView.addOnCanRemoveUnusedStyleImageListener { callbackLatch.countDown() maplibreMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 120.0), 8.0)) - mapView.addOnDidFinishRenderingFrameListener { + mapView.addOnDidFinishRenderingFrameListener{ _, _, _ -> assertNotNull(maplibreMap.style!!.getImage("small")) assertNotNull(maplibreMap.style!!.getImage("large")) latch.countDown() @@ -89,7 +89,7 @@ class RemoveUnusedImagesTest : AppCenter() { maplibreMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 120.0), 8.0)) // Wait for the next frame and check that images were removed from the style. - mapView.addOnDidFinishRenderingFrameListener { + mapView.addOnDidFinishRenderingFrameListener { _, _, _ -> if (maplibreMap.style!!.getImage("small") == null && maplibreMap.style!!.getImage("large") == null) { latch.countDown() } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt index f903d03c2a6..b67532a1027 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/benchmark/BenchmarkActivity.kt @@ -8,7 +8,6 @@ import android.os.Environment import android.os.Handler import android.view.View import android.view.WindowManager -import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -27,12 +26,14 @@ import org.maplibre.android.BuildConfig.GIT_REVISION import org.maplibre.android.camera.CameraUpdateFactory import org.maplibre.android.geometry.LatLng import org.maplibre.android.log.Logger +import org.maplibre.android.log.Logger.INFO import org.maplibre.android.maps.* import org.maplibre.android.maps.MapLibreMap.CancelableCallback import org.maplibre.android.testapp.BuildConfig import org.maplibre.android.testapp.R import org.maplibre.android.testapp.utils.FpsStore import org.maplibre.android.testapp.utils.BenchmarkResults +import org.maplibre.android.testapp.utils.FrameTimeStore import java.io.File import java.util.* @@ -52,13 +53,32 @@ data class BenchmarkInputData( * Prepares JSON payload that is sent to the API that collects benchmark results. * See https://github.com/maplibre/ci-runners */ -fun jsonPayload(results: BenchmarkResults): JsonObject { +@SuppressLint("NewApi") +fun jsonPayload(styleNames: List, fpsResults: BenchmarkResults, encodingTimeResults: BenchmarkResults, renderingTimeResults: BenchmarkResults): JsonObject { return buildJsonObject { putJsonObject("resultsPerStyle") { - for ((styleName, result) in results.resultsPerStyle) { - putJsonObject(styleName) { - put("avgFps", JsonPrimitive(result.map { it.average }.average())) - put("low1p", JsonPrimitive(result.map { it.low1p }.average())) + for (style in styleNames) { + putJsonObject(style) { + fpsResults.resultsPerStyle[style].let { results -> + if (results !== null) { + put("avgFps", JsonPrimitive(results.map { it.average }.average())) + put("low1pFps", JsonPrimitive(results.map { it.low1p }.average())) + } + } + + encodingTimeResults.resultsPerStyle[style].let { results -> + if (results !== null) { + put("avgEncodingTime", JsonPrimitive(results.map { it.average }.average())) + put("low1pEncodingTime", JsonPrimitive(results.map { it.low1p }.average())) + } + } + + renderingTimeResults.resultsPerStyle[style].let { results -> + if (results !== null) { + put("avgRenderingTime", JsonPrimitive(results.map { it.average }.average())) + put("low1pRenderingTime", JsonPrimitive(results.map { it.low1p }.average())) + } + } } } } @@ -77,12 +97,16 @@ class BenchmarkActivity : AppCompatActivity() { private val TAG = "BenchmarkActivity" private lateinit var mapView: MapView - private lateinit var maplibreMap: MapLibreMap private var handler: Handler? = null private var delayed: Runnable? = null private var fpsStore = FpsStore() - private var results = BenchmarkResults() + private var encodingTimeStore = FrameTimeStore() + private var renderingTimeStore = FrameTimeStore() + private var fpsResults = BenchmarkResults() + private var encodingTimeResults = BenchmarkResults() + private var renderingTimeResults = BenchmarkResults() private var runsLeft = 5 + private var measureFrameTime = true // the styles used for the benchmark // can be overridden adding with developer-config.xml @@ -160,6 +184,8 @@ class BenchmarkActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Logger.setVerbosity(INFO) + setContentView(R.layout.activity_benchmark) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -179,10 +205,20 @@ class BenchmarkActivity : AppCompatActivity() { private fun setupMapView(savedInstanceState: Bundle?) { mapView = findViewById(R.id.mapView) as MapView + if (measureFrameTime) { + mapView.addOnDidFinishRenderingFrameListener { fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double -> + encodingTimeStore.add(frameEncodingTime * 1e3) + renderingTimeStore.add(frameRenderingTime * 1e3) + } + } mapView.getMapAsync { maplibreMap: MapLibreMap -> - this@BenchmarkActivity.maplibreMap = maplibreMap maplibreMap.setStyle(inputData.styleURLs[0]) - setFpsView(maplibreMap) + maplibreMap.setSwapBehaviorFlush(measureFrameTime) + if (!measureFrameTime) { + maplibreMap.setOnFpsChangedListener { fps: Double -> + fpsStore.add(fps) + } + } // Start an animation on the map as well flyTo(maplibreMap, 0, 0,14.0) @@ -206,10 +242,29 @@ class BenchmarkActivity : AppCompatActivity() { override fun onFinish() { if (place == PLACES.size - 1) { // done with tour - results.addResult(inputData.styleNames[style], fpsStore) - fpsStore.reset() + if (measureFrameTime) { + encodingTimeResults.addResult( + inputData.styleNames[style], + encodingTimeStore + ) + encodingTimeStore.reset() + + println("Encoding time results $encodingTimeResults") + + renderingTimeResults.addResult( + inputData.styleNames[style], + renderingTimeStore + ) + renderingTimeStore.reset() + + println("Rendering time results $renderingTimeResults") + } else { + fpsResults.addResult(inputData.styleNames[style], fpsStore) + fpsStore.reset() - println("FPS results $results") + println("FPS results $fpsResults") + } + println("Benchmark ${jsonPayload(inputData.styleNames, fpsResults, encodingTimeResults, renderingTimeResults)}") if (style < inputData.styleURLs.size - 1) { // continue with next style maplibreMap.setStyle(inputData.styleURLs[style + 1]) @@ -231,12 +286,6 @@ class BenchmarkActivity : AppCompatActivity() { ) } - private fun setFpsView(maplibreMap: MapLibreMap) { - maplibreMap.setOnFpsChangedListener { fps: Double -> - fpsStore.add(fps) - } - } - override fun onStart() { super.onStart() mapView.onStart() @@ -284,7 +333,7 @@ class BenchmarkActivity : AppCompatActivity() { val client = OkHttpClient() - val payload = jsonPayload(results) + val payload = jsonPayload(inputData.styleNames, fpsResults, encodingTimeResults, renderingTimeResults) Logger.i(TAG, "Sending JSON payload to API: $payload") val request = Request.Builder() diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt index 4a7f7f7a66b..d18b91dc3ad 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/MapFragmentActivity.kt @@ -77,7 +77,7 @@ class MapFragmentActivity : } } - override fun onDidFinishRenderingFrame(fully: Boolean) { + override fun onDidFinishRenderingFrame(fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double) { if (initialCameraAnimation && fully && maplibreMap != null) { maplibreMap.animateCamera( CameraUpdateFactory.newCameraPosition(CameraPosition.Builder().tilt(45.0).build()), diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt index 876617a7b0d..6c1a5f6d8d9 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/SupportMapFragmentActivity.kt @@ -75,7 +75,7 @@ class SupportMapFragmentActivity : mapView.removeOnDidFinishRenderingFrameListener(this) } - override fun onDidFinishRenderingFrame(fully: Boolean) { + override fun onDidFinishRenderingFrame(fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double) { if (initialCameraAnimation && fully && maplibreMap != null) { maplibreMap.animateCamera( CameraUpdateFactory.newCameraPosition(CameraPosition.Builder().tilt(45.0).build()), diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt index cd062541bb5..7462750ce51 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/imagegenerator/SnapshotActivity.kt @@ -20,7 +20,7 @@ class SnapshotActivity : AppCompatActivity(), OnMapReadyCallback { private lateinit var maplibreMap: MapLibreMap private val idleListener = object : MapView.OnDidFinishRenderingFrameListener { - override fun onDidFinishRenderingFrame(fully: Boolean) { + override fun onDidFinishRenderingFrame(fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double) { if (fully) { binding.mapView.removeOnDidFinishRenderingFrameListener(this) Logger.v(TAG, LOG_MESSAGE) diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt index 5f40e1bf81a..85413966838 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/MapChangeActivity.kt @@ -58,7 +58,7 @@ class MapChangeActivity : AppCompatActivity() { mapView.addOnDidFinishLoadingMapListener(OnDidFinishLoadingMapListener { Timber.v("OnDidFinishLoadingMap") }) mapView.addOnDidFinishLoadingStyleListener(OnDidFinishLoadingStyleListener { Timber.v("OnDidFinishLoadingStyle") }) mapView.addOnDidFinishRenderingFrameListener( - OnDidFinishRenderingFrameListener { fully: Boolean -> + OnDidFinishRenderingFrameListener { fully: Boolean, frameEncodingTime: Double, frameRenderingTime: Double -> Timber.v( "OnDidFinishRenderingFrame: fully: %s", fully diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt index ef0b820c1d4..203457d6a8b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/org/maplibre/android/testapp/utils/BenchmarkUtils.kt @@ -23,6 +23,27 @@ class FpsStore { } } +class FrameTimeStore { + private val timeValues = ArrayList(100000) + + fun add(time: Double) { + timeValues.add(time) + } + + fun reset() { + timeValues.clear() + } + + fun low1p(): Double { + timeValues.sort() + return timeValues.slice((99 * timeValues.size / 100)..timeValues.size - 1).average() + } + + fun average(): Double { + return timeValues.average() + } +} + /** * Result of single benchmark run */ @@ -41,4 +62,14 @@ data class BenchmarkResults( ) resultsPerStyle[styleName] = newResults } + + fun addResult(styleName: String, frameTimeStore: FrameTimeStore) { + val newResults = resultsPerStyle.getValue(styleName).plus( + BenchmarkResult( + frameTimeStore.average(), + frameTimeStore.low1p() + ) + ) + resultsPerStyle[styleName] = newResults + } } \ No newline at end of file diff --git a/platform/default/include/mbgl/gfx/headless_backend.hpp b/platform/default/include/mbgl/gfx/headless_backend.hpp index 1dc7755317d..e6a3c9e539c 100644 --- a/platform/default/include/mbgl/gfx/headless_backend.hpp +++ b/platform/default/include/mbgl/gfx/headless_backend.hpp @@ -15,11 +15,6 @@ namespace gfx { // of readStillImage. class HeadlessBackend : public gfx::Renderable { public: - enum class SwapBehaviour { - NoFlush, - Flush - }; - // Factory. static std::unique_ptr Create(const Size size = {256, 256}, SwapBehaviour swapBehavior = SwapBehaviour::NoFlush, diff --git a/platform/default/src/mbgl/gl/headless_backend.cpp b/platform/default/src/mbgl/gl/headless_backend.cpp index 816c38e7078..ccfe6936d0a 100644 --- a/platform/default/src/mbgl/gl/headless_backend.cpp +++ b/platform/default/src/mbgl/gl/headless_backend.cpp @@ -111,7 +111,7 @@ namespace gfx { template <> std::unique_ptr Backend::Create( - const Size size, gfx::HeadlessBackend::SwapBehaviour swapBehavior, const gfx::ContextMode contextMode) { + const Size size, gfx::Renderable::SwapBehaviour swapBehavior, const gfx::ContextMode contextMode) { return std::make_unique(size, swapBehavior, contextMode); } From 14bfb5df7257848ee0d14570e349a07996c9469f Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Thu, 1 Feb 2024 14:23:26 -0800 Subject: [PATCH 56/96] Retain property update flags (#2083) --- .../renderer/layers/fill_layer_tweaker.cpp | 38 ++++++++----------- .../renderer/layers/fill_layer_tweaker.hpp | 6 +++ .../renderer/layers/heatmap_layer_tweaker.cpp | 3 +- .../layers/hillshade_layer_tweaker.cpp | 3 +- .../renderer/layers/line_layer_tweaker.cpp | 12 +++--- .../renderer/layers/line_layer_tweaker.hpp | 5 +++ 6 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp index 63565a87d7f..d8d614add18 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp @@ -43,17 +43,16 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters const auto debugGroup = parameters.encoder->createDebugGroup(label.c_str()); #endif - // Only run each update function once - bool fillUniformBufferUpdated = false; - bool fillOutlineUniformBufferUpdated = false; - bool fillPatternUniformBufferUpdated = false; - bool fillOutlinePatternUniformBufferUpdated = false; - - const auto UpdateFillUniformBuffers = [&]() { - if (fillUniformBufferUpdated) return; + if (propertiesUpdated) { fillUniformBufferUpdated = true; + fillOutlineUniformBufferUpdated = true; + fillPatternUniformBufferUpdated = true; + fillOutlinePatternUniformBufferUpdated = true; + propertiesUpdated = false; + } - if (!fillPropsUniformBuffer || propertiesUpdated) { + const auto UpdateFillUniformBuffers = [&]() { + if (!fillPropsUniformBuffer || fillUniformBufferUpdated) { const FillEvaluatedPropsUBO paramsUBO = { /* .color = */ evaluated.get().constantOr(FillColor::defaultValue()), /* .opacity = */ evaluated.get().constantOr(FillOpacity::defaultValue()), @@ -62,14 +61,12 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, }; context.emplaceOrUpdateUniformBuffer(fillPropsUniformBuffer, ¶msUBO); + fillUniformBufferUpdated = false; } }; const auto UpdateFillOutlineUniformBuffers = [&]() { - if (fillOutlineUniformBufferUpdated) return; - fillOutlineUniformBufferUpdated = true; - - if (!fillOutlinePropsUniformBuffer || propertiesUpdated) { + if (!fillOutlinePropsUniformBuffer || fillOutlineUniformBufferUpdated) { const FillOutlineEvaluatedPropsUBO paramsUBO = { /* .outline_color = */ evaluated.get().constantOr(FillOutlineColor::defaultValue()), /* .opacity = */ evaluated.get().constantOr(FillOpacity::defaultValue()), @@ -78,14 +75,12 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, }; context.emplaceOrUpdateUniformBuffer(fillOutlinePropsUniformBuffer, ¶msUBO); + fillOutlineUniformBufferUpdated = false; } }; const auto UpdateFillPatternUniformBuffers = [&]() { - if (fillPatternUniformBufferUpdated) return; - fillPatternUniformBufferUpdated = true; - - if (!fillPatternPropsUniformBuffer || propertiesUpdated) { + if (!fillPatternPropsUniformBuffer || fillPatternUniformBufferUpdated) { const FillPatternEvaluatedPropsUBO paramsUBO = { /* .opacity = */ evaluated.get().constantOr(FillOpacity::defaultValue()), /* .fade = */ crossfade.t, @@ -93,14 +88,12 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, }; context.emplaceOrUpdateUniformBuffer(fillPatternPropsUniformBuffer, ¶msUBO); + fillPatternUniformBufferUpdated = false; } }; const auto UpdateFillOutlinePatternUniformBuffers = [&]() { - if (fillOutlinePatternUniformBufferUpdated) return; - fillOutlinePatternUniformBufferUpdated = true; - - if (!fillOutlinePatternPropsUniformBuffer || propertiesUpdated) { + if (!fillOutlinePatternPropsUniformBuffer || fillOutlinePatternUniformBufferUpdated) { const FillOutlinePatternEvaluatedPropsUBO paramsUBO = { /* .opacity = */ evaluated.get().constantOr(FillOpacity::defaultValue()), /* .fade = */ crossfade.t, @@ -108,6 +101,7 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters 0, }; context.emplaceOrUpdateUniformBuffer(fillOutlinePatternPropsUniformBuffer, ¶msUBO); + fillOutlinePatternUniformBufferUpdated = false; } }; @@ -214,8 +208,6 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters } } }); - - propertiesUpdated = false; } } // namespace mbgl diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp index 92d98340482..272a7cf40f1 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp @@ -25,6 +25,12 @@ class FillLayerTweaker : public LayerTweaker { gfx::UniformBufferPtr fillOutlinePropsUniformBuffer; gfx::UniformBufferPtr fillPatternPropsUniformBuffer; gfx::UniformBufferPtr fillOutlinePatternPropsUniformBuffer; + + // Only run each update function once per property update + bool fillUniformBufferUpdated = true; + bool fillOutlineUniformBufferUpdated = true; + bool fillPatternUniformBufferUpdated = true; + bool fillOutlinePatternUniformBufferUpdated = true; }; } // namespace mbgl diff --git a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp index a699bed7ec8..983aaab7313 100644 --- a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp @@ -42,6 +42,7 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet /* .intensity = */ evaluated.get(), /* .padding = */ 0}; parameters.context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &evaluatedPropsUBO); + propertiesUpdated = false; } return evaluatedPropsUniformBuffer; }; @@ -67,8 +68,6 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamet uniforms.createOrUpdate(idHeatmapDrawableUBO, &drawableUBO, context); }); - - propertiesUpdated = false; } } // namespace mbgl diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp index 5a38e2aaebd..ebffbb0627b 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp @@ -51,6 +51,7 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam /* .shadow = */ evaluated.get(), /* .accent = */ evaluated.get()}; parameters.context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &evaluatedPropsUBO); + propertiesUpdated = false; } return evaluatedPropsUniformBuffer; }; @@ -72,8 +73,6 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam /* .light = */ getLight(parameters, evaluated)}; uniforms.createOrUpdate(idHillshadeDrawableUBO, &drawableUBO, parameters.context); }); - - propertiesUpdated = false; } } // namespace mbgl diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.cpp b/src/mbgl/renderer/layers/line_layer_tweaker.cpp index d3256c706bd..8f2fc7d4bdd 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.cpp @@ -34,11 +34,13 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters const auto& crossfade = static_cast(*evaluatedProperties).crossfade; // Each property UBO is updated at most once if new evaluated properties were set - bool simplePropertiesUpdated = propertiesUpdated; - bool gradientPropertiesUpdated = propertiesUpdated; - bool patternPropertiesUpdated = propertiesUpdated; - bool sdfPropertiesUpdated = propertiesUpdated; - propertiesUpdated = false; + if (propertiesUpdated) { + simplePropertiesUpdated = true; + gradientPropertiesUpdated = true; + patternPropertiesUpdated = true; + sdfPropertiesUpdated = true; + propertiesUpdated = false; + } const auto getLinePropsBuffer = [&]() { if (!linePropertiesBuffer || simplePropertiesUpdated) { diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.hpp b/src/mbgl/renderer/layers/line_layer_tweaker.hpp index 724cc88f128..ef156ae737a 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.hpp @@ -41,6 +41,11 @@ class LineLayerTweaker : public LayerTweaker { gfx::UniformBufferPtr permutationUniformBuffer; gfx::UniformBufferPtr expressionUniformBuffer; #endif // MLN_RENDER_BACKEND_METAL + + bool simplePropertiesUpdated = true; + bool gradientPropertiesUpdated = true; + bool patternPropertiesUpdated = true; + bool sdfPropertiesUpdated = true; }; } // namespace mbgl From f9c6c9c2be3a283baaac73bdb5fbf8c2bd9766ec Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Tue, 6 Feb 2024 07:13:13 -0800 Subject: [PATCH 57/96] Use optional values in place of zero placeholders (#2089) --- include/mbgl/gfx/drawable_atlases_tweaker.hpp | 12 +++++++----- src/mbgl/gfx/drawable_atlases_tweaker.cpp | 11 +++++++++-- .../renderer/layers/render_fill_extrusion_layer.cpp | 2 +- src/mbgl/renderer/layers/render_fill_layer.cpp | 2 +- src/mbgl/renderer/layers/render_line_layer.cpp | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/include/mbgl/gfx/drawable_atlases_tweaker.hpp b/include/mbgl/gfx/drawable_atlases_tweaker.hpp index 277622b4efb..f13a30470c5 100644 --- a/include/mbgl/gfx/drawable_atlases_tweaker.hpp +++ b/include/mbgl/gfx/drawable_atlases_tweaker.hpp @@ -23,8 +23,8 @@ class Drawable; class DrawableAtlasesTweaker : public gfx::DrawableTweaker { public: DrawableAtlasesTweaker(TileAtlasTexturesPtr atlases_, - const StringIdentity iconNameId_, - const StringIdentity glyphNameId_, + const std::optional iconNameId_, + const std::optional glyphNameId_, bool isText_, const bool sdfIcons_, const style::AlignmentType rotationAlignment_, @@ -37,7 +37,9 @@ class DrawableAtlasesTweaker : public gfx::DrawableTweaker { sdfIcons(sdfIcons_), rotationAlignment(rotationAlignment_), iconScaled(iconScaled_), - textSizeIsZoomConstant(textSizeIsZoomConstant_) {} + textSizeIsZoomConstant(textSizeIsZoomConstant_) { + assert(iconNameId_ != glyphNameId_); + } ~DrawableAtlasesTweaker() override = default; void init(Drawable&) override; @@ -48,8 +50,8 @@ class DrawableAtlasesTweaker : public gfx::DrawableTweaker { void setupTextures(Drawable&, const bool); TileAtlasTexturesPtr atlases; - StringIdentity iconNameId; - StringIdentity glyphNameId; + std::optional iconNameId; + std::optional glyphNameId; bool isText; const bool sdfIcons; diff --git a/src/mbgl/gfx/drawable_atlases_tweaker.cpp b/src/mbgl/gfx/drawable_atlases_tweaker.cpp index 1b816afb711..120c37ed15e 100644 --- a/src/mbgl/gfx/drawable_atlases_tweaker.cpp +++ b/src/mbgl/gfx/drawable_atlases_tweaker.cpp @@ -8,9 +8,16 @@ namespace mbgl { namespace gfx { +namespace { +std::optional getSamplerLocation(const gfx::ShaderProgramBasePtr& shader, + const std::optional& nameId) { + return nameId ? shader->getSamplerLocation(*nameId) : std::nullopt; +} +} // namespace + void DrawableAtlasesTweaker::setupTextures(gfx::Drawable& drawable, const bool linearFilterForIcons) { if (const auto& shader = drawable.getShader()) { - if (const auto samplerLocation = shader->getSamplerLocation(glyphNameId)) { + if (const auto samplerLocation = getSamplerLocation(shader, glyphNameId)) { if (atlases) { atlases->glyph->setSamplerConfiguration( {TextureFilterType::Linear, TextureWrapType::Clamp, TextureWrapType::Clamp}); @@ -19,7 +26,7 @@ void DrawableAtlasesTweaker::setupTextures(gfx::Drawable& drawable, const bool l TextureWrapType::Clamp, TextureWrapType::Clamp}); } - if (const auto iconSamplerLocation = shader->getSamplerLocation(iconNameId)) { + if (const auto iconSamplerLocation = getSamplerLocation(shader, iconNameId)) { assert(*samplerLocation != *iconSamplerLocation); drawable.setTexture(atlases ? atlases->glyph : nullptr, *samplerLocation); drawable.setTexture(atlases ? atlases->icon : nullptr, *iconSamplerLocation); diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp index f6bf877ce39..269ba752d03 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -459,7 +459,7 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, if (hasPattern && !tweaker) { if (const auto& atlases = tile.getAtlasTextures()) { tweaker = std::make_shared(atlases, - 0, + std::nullopt, idIconTextureName, /*isText=*/false, false, diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index 7ade9731d37..58367aac241 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -599,7 +599,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, if (const auto& atlases = tile.getAtlasTextures(); atlases && atlases->icon) { atlasTweaker = std::make_shared( atlases, - 0, + std::nullopt, idIconTextureName, /*isText*/ false, /*sdfIcons*/ true, // to force linear filter diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index a6c90f2e9f3..eecbfe52c5b 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -680,7 +680,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, if (!iconTweaker) { iconTweaker = std::make_shared( atlases, - 0, + std::nullopt, idLineImageUniformName, /*isText*/ false, /*sdfIcons*/ true, // to force linear filter From 5cd2f73d779debefe3ba8272031a77ab20aa3e92 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Wed, 7 Feb 2024 13:29:06 -0500 Subject: [PATCH 58/96] [bazel] Run nodejs inside of bazel for genrules (#1948) --- .../npm_translate_lock_LTE4Nzc1MDcwNjU= | 6 + .bazelignore | 3 + .bazelrc | 3 + .github/workflows/ios-ci.yml | 19 - BUILD.bazel | 89 +- MODULE.bazel | 14 + platform/BUILD.bazel | 62 +- platform/darwin/BUILD.bazel | 67 +- platform/darwin/bazel/files.bzl | 3 +- .../darwin/scripts/check-public-symbols.sh | 3 - .../darwin/scripts/generate-style-code.js | 56 +- .../darwin/src/MLNCustomDrawableStyleLayer.h | 1 + platform/ios/BUILD.bazel | 19 +- platform/ios/bazel/files.bzl | 32 +- platform/ios/src/MLNCalloutView.h | 1 + platform/ios/src/MLNMapView+IBAdditions.h | 3 +- platform/ios/src/Mapbox.template.h | 2 +- platform/macos/BUILD.bazel | 18 +- platform/macos/src/MLNMapViewDelegate.h | 6 +- .../scripts/check-public-symbols.js | 0 pnpm-lock.yaml | 2634 +++++++++++++++++ scripts/generate-style-code.js | 28 +- scripts/style-code.js | 9 + shaders/generate_shader_code.js | 9 +- 24 files changed, 2909 insertions(+), 178 deletions(-) create mode 100755 .aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= create mode 100644 .bazelignore delete mode 100755 platform/darwin/scripts/check-public-symbols.sh rename platform/{darwin => }/scripts/check-public-symbols.js (100%) create mode 100644 pnpm-lock.yaml diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= b/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= new file mode 100755 index 00000000000..30308cadd66 --- /dev/null +++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_LTE4Nzc1MDcwNjU= @@ -0,0 +1,6 @@ +# @generated +# Input hashes for repository rule npm_translate_lock(name = "npm", pnpm_lock = "//:pnpm-lock.yaml"). +# This file should be checked into version control along with the pnpm-lock.yaml file. +pnpm-lock.yaml=-214709846 +package-lock.json=479094047 +package.json=1266280293 diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 00000000000..46c2539b481 --- /dev/null +++ b/.bazelignore @@ -0,0 +1,3 @@ +bazel-bin +bazel-out +bazel-testlogs diff --git a/.bazelrc b/.bazelrc index bca2641901b..320763927b3 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,6 @@ +# https://github.com/aspect-build/rules_js/issues/1408 +startup --host_jvm_args=-DBAZEL_TRACK_SOURCE_DIRECTORIES=1 + # TODO: remove with bazel 7.x common --enable_bzlmod diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 17832fa97e2..85efe537469 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -68,18 +68,6 @@ jobs: submodules: recursive fetch-depth: 0 - - name: Cache node modules - uses: actions/cache@v4 - env: - cache-name: cache-node-modules - with: - path: /user/local/lib/node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - name: Cache Bazel uses: actions/cache@v4 with: @@ -88,13 +76,6 @@ jobs: ${{ runner.os }}-bazel- path: ~/.cache/bazel - - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: npm install - run: npm ci --ignore-scripts - - run: cp bazel/example_config.bzl bazel/config.bzl - name: Check debug symbols diff --git a/BUILD.bazel b/BUILD.bazel index d0d8df9c205..56e744c46db 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,6 @@ +load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_library", "js_run_binary") load("@bazel_skylib//rules:common_settings.bzl", "string_flag") +load("@npm//:defs.bzl", "npm_link_all_packages") load( "//bazel:core.bzl", "MLN_CORE_HEADERS", @@ -20,62 +22,51 @@ load( load("//bazel:flags.bzl", "CPP_FLAGS", "MAPLIBRE_FLAGS") # Generate code required by the core -filegroup( - name = "style_data", - srcs = glob(["**/*.ejs"]), -) -genrule( +js_run_binary( name = "generated_style_code", - srcs = ["style_data"], + srcs = glob( + ["**/*.ejs"], + allow_empty = False, + ), outs = MLN_PUBLIC_GENERATED_STYLE_HEADERS + MLN_PRIVATE_GENERATED_STYLE_HEADERS + MLN_GENERATED_STYLE_SOURCE, - cmd = "node $(execpath scripts/generate-style-code.js) --root $(RULEDIR)", - tools = ["scripts/generate-style-code.js"], -) - -filegroup( - name = "shader_data", - srcs = ["shaders/manifest.json"] + glob(["shaders/*.glsl"]), + tool = ":generate-style-code-script", ) -genrule( +js_run_binary( name = "generated_shaders", - srcs = ["shader_data"], + srcs = ["shaders/manifest.json"] + glob( + ["shaders/*.glsl"], + allow_empty = False, + ), outs = MLN_GENERATED_SHADER_HEADERS + MLN_GENERATED_OPENGL_SHADER_HEADERS, - cmd = "node $(execpath shaders/generate_shader_code.js) --root $(RULEDIR)", - tools = ["shaders/generate_shader_code.js"], + tool = ":generate-shader-code-script", ) # This header only target strips the __generated__ prefix for the compiler # search paths, making the location of generated code transparent to the build cc_library( name = "mbgl-core-generated-public-artifacts", - srcs = [], - hdrs = MLN_PUBLIC_GENERATED_STYLE_HEADERS + + hdrs = [ + ":generated_shaders", + ":generated_style_code", + ] + MLN_PUBLIC_GENERATED_STYLE_HEADERS + MLN_GENERATED_SHADER_HEADERS + select({ "//:metal_renderer": [], "//conditions:default": MLN_GENERATED_OPENGL_SHADER_HEADERS, }), visibility = ["//visibility:public"], - deps = [ - ":generated_shaders", - ":generated_style_code", - ], ) # ditto, but for private headers (under the src/ path) cc_library( name = "mbgl-core-generated-private-artifacts", - srcs = [], - hdrs = MLN_PRIVATE_GENERATED_STYLE_HEADERS, + hdrs = [":generated_style_code"] + MLN_PRIVATE_GENERATED_STYLE_HEADERS, visibility = ["//visibility:private"], - deps = [ - ":generated_style_code", - ], ) # Generated source is inserted directly into the core target, no need to remove @@ -207,3 +198,45 @@ exports_files( ], visibility = ["//visibility:public"], ) + +npm_link_all_packages( + name = "node_modules", +) + +js_binary( + name = "generate-style-code-script", + data = [ + ":node_modules/argparse", + ":node_modules/csscolorparser", + ":style-code", + ":style-spec", + ], + entry_point = "scripts/generate-style-code.js", +) + +js_binary( + name = "generate-shader-code-script", + data = [ + ":node_modules/argparse", + ], + entry_point = "shaders/generate_shader_code.js", +) + +js_library( + name = "style-spec", + srcs = ["scripts/style-spec.js"], + data = glob([ + "scripts/style-spec-reference/*.js", + "scripts/style-spec-reference/*.json", + ]), + visibility = ["//visibility:public"], +) + +js_library( + name = "style-code", + srcs = ["scripts/style-code.js"], + visibility = ["//visibility:public"], + deps = [ + ":node_modules/ejs", + ], +) diff --git a/MODULE.bazel b/MODULE.bazel index ba62316896f..15e66665694 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,10 +1,24 @@ module(name = "maplibre") bazel_dep(name = "apple_support", version = "1.11.1", repo_name = "build_bazel_apple_support") +bazel_dep(name = "aspect_rules_js", version = "1.34.1") bazel_dep(name = "bazel_skylib", version = "1.5.0") bazel_dep(name = "rules_apple", version = "3.1.1", repo_name = "build_bazel_rules_apple") bazel_dep(name = "rules_swift", version = "1.13.0", repo_name = "build_bazel_rules_swift") bazel_dep(name = "rules_xcodeproj", version = "1.13.0") +npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm") +npm.npm_translate_lock( + name = "npm", + data = ["package.json"], + npm_package_lock = "//:package-lock.json", + pnpm_lock = "//:pnpm-lock.yaml", + update_pnpm_lock = True, +) +use_repo(npm, "npm") + +pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") +use_repo(pnpm, "pnpm") + provisioning_profile_repository = use_extension("@build_bazel_rules_apple//apple:apple.bzl", "provisioning_profile_repository_extension") provisioning_profile_repository.setup() diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index f5896879a78..4052b441e27 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -1,3 +1,4 @@ +load("@aspect_rules_js//js:defs.bzl", "js_binary") load("//bazel:flags.bzl", "CPP_FLAGS", "MAPLIBRE_FLAGS", "WARNING_FLAGS") # Objcpp files for the iOS source @@ -41,11 +42,11 @@ objc_library( "QuartzCore", ] + select({ "//:metal_renderer": [ - "MetalKit" + "MetalKit", ], "//conditions:default": [ "OpenGLES", - "GLKit" + "GLKit", ], }), deps = [ @@ -169,8 +170,8 @@ objc_library( objc_library( name = "app_custom_drawable_layer_objcpp_srcs", srcs = [ - "//platform/darwin:app/ExampleCustomDrawableStyleLayer.h", - "//platform/darwin:app/ExampleCustomDrawableStyleLayer.mm", + "//platform/darwin:app/ExampleCustomDrawableStyleLayer.h", + "//platform/darwin:app/ExampleCustomDrawableStyleLayer.mm", ], copts = CPP_FLAGS + MAPLIBRE_FLAGS + WARNING_FLAGS["ios"] + [ "-fcxx-modules", @@ -183,8 +184,8 @@ objc_library( ], visibility = ["//visibility:public"], deps = [ - "//:mbgl-core", ":ios-sdk", + "//:mbgl-core", ], ) @@ -205,13 +206,14 @@ objc_library( "darwin/src", ], sdk_frameworks = [ - ] + select({ + ] + select({ "//:metal_renderer": [ - "MetalKit" + "MetalKit", ], "//conditions:default": [ "GLKit", # needed for LimeGreenStyleLayer - ]}), + ], + }), visibility = ["//visibility:public"], deps = [ ":ios-sdk", @@ -225,9 +227,9 @@ objc_library( objc_library( name = "ios-benchapp", - copts = CPP_FLAGS + MAPLIBRE_FLAGS, srcs = ["//platform/ios/benchmark:ios_benchmark_srcs"], hdrs = ["//platform/ios/benchmark:ios_benchmark_hdrs"], + copts = CPP_FLAGS + MAPLIBRE_FLAGS, includes = [ "darwin/app", "darwin/src", @@ -240,39 +242,17 @@ objc_library( ], ) -sh_binary( +js_binary( name = "check-public-symbols", - srcs = ["//platform/darwin:scripts/check-public-symbols.sh"], + args = [ + "macOS", + "iOS", + ], data = [ - "//platform/darwin:darwin_objc_hdrs", - "//platform/darwin:darwin_objc_srcs", - "//platform/darwin:darwin_objcpp_hdrs", - "//platform/darwin:darwin_objcpp_srcs", - "//platform/darwin:darwin_private_hdrs", - "//platform/darwin:generated_style_hdrs", - "//platform/darwin:generated_style_srcs", - "//platform/darwin:scripts/check-public-symbols.js", - "//platform/ios:ios_objc_srcs", - "//platform/ios:ios_objcpp_srcs", - "//platform/ios:ios_private_hdrs", - "//platform/ios:ios_public_hdrs", - "//platform/ios:ios_sdk_hdrs", - "//platform/macos:macos_objc_srcs", - "//platform/macos:macos_objcpp_srcs", - "//platform/macos:macos_private_hdrs", - "//platform/macos:macos_public_hdrs", - ] + select({ - "//:metal_renderer": [], - "//conditions:default": [ - "//platform/darwin:darwin_objcpp_opengl_srcs", - ], - }) + select({ - "//:legacy_renderer": [], - "//conditions:default": [ - "//platform/darwin:darwin_objcpp_custom_drawable_srcs", - ], - }), - deps = [ - "//platform/darwin:generated_code", + "//:node_modules/lodash", + "//platform/darwin:symbols-to-check", + "//platform/ios:symbols-to-check", + "//platform/macos:symbols-to-check", ], + entry_point = "scripts/check-public-symbols.js", ) diff --git a/platform/darwin/BUILD.bazel b/platform/darwin/BUILD.bazel index 7eeea973641..5fa5997bf65 100644 --- a/platform/darwin/BUILD.bazel +++ b/platform/darwin/BUILD.bazel @@ -1,3 +1,8 @@ +load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_library", "js_run_binary") +load( + "@build_bazel_rules_swift//swift:swift.bzl", + "swift_library", +) load("//bazel:flags.bzl", "CPP_FLAGS", "MAPLIBRE_FLAGS", "WARNING_FLAGS") load( "bazel/files.bzl", @@ -13,10 +18,6 @@ load( "MLN_GENERATED_DARWIN_STYLE_SOURCE", "MLN_GENERATED_DARWIN_TEST_CODE", ) -load( - "@build_bazel_rules_swift//swift:swift.bzl", - "swift_library", -) filegroup( name = "darwin_private_hdrs", @@ -32,7 +33,10 @@ filegroup( filegroup( name = "darwin_objcpp_hdrs", - srcs = MLN_DARWIN_OBJCPP_HEADERS, + srcs = MLN_DARWIN_OBJCPP_HEADERS + select({ + "//:metal_renderer": [], + "//conditions:default": ["src/MLNOpenGLStyleLayer.h"], + }), visibility = ["//visibility:public"], ) @@ -115,17 +119,16 @@ filegroup( ], ) -genrule( +js_run_binary( name = "generated_code", srcs = [ + ":docs_ejs_templates", ":generator_data", ":test_ejs_templates", - ":docs_ejs_templates", ], outs = MLN_GENERATED_DARWIN_STYLE_SOURCE + MLN_GENERATED_DARWIN_STYLE_HEADERS + MLN_GENERATED_DARWIN_TEST_CODE, - cmd = "node $(execpath scripts/generate-style-code.js) --root $(RULEDIR)/../../", - tools = ["scripts/generate-style-code.js"], + tool = ":generate-style-code-script", visibility = ["//visibility:public"], ) @@ -206,15 +209,12 @@ cc_library( ":darwin_objc_hdrs", ":darwin_objcpp_hdrs", ":darwin_private_hdrs", - ":generated_style_hdrs", + ":generated_code", ], hdrs = [ ":generated_style_hdrs", ], visibility = ["//visibility:public"], - deps = [ - ":generated_code", - ], ) # Objc files for the iOS source @@ -324,8 +324,6 @@ exports_files( "app/ExampleCustomDrawableStyleLayer.h", "app/ExampleCustomDrawableStyleLayer.mm", "include/mbgl/util/image+MLNAdditions.hpp", - "scripts/check-public-symbols.js", - "scripts/check-public-symbols.sh", ] + MLN_DARWIN_PUBLIC_OBJC_SOURCE + MLN_DARWIN_PUBLIC_OBJCPP_SOURCE + MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE + @@ -334,3 +332,42 @@ exports_files( MLN_DARWIN_OBJC_HEADERS + MLN_DARWIN_OBJCPP_HEADERS, ) + +js_binary( + name = "generate-style-code-script", + data = [ + "scripts/style-spec-cocoa-conventions-v8.json", + "scripts/style-spec-overrides-v8.json", + "//:node_modules/argparse", + "//:node_modules/csscolorparser", + "//:node_modules/lodash", + "//:style-code", + "//:style-spec", + ], + entry_point = "scripts/generate-style-code.js", +) + +js_library( + name = "symbols-to-check", + srcs = [ + ":darwin_objc_hdrs", + ":darwin_objc_srcs", + ":darwin_objcpp_hdrs", + ":darwin_objcpp_srcs", + ":darwin_private_hdrs", + ":generated_code", + ":generated_style_hdrs", + ":generated_style_srcs", + ] + select({ + "//:metal_renderer": [], + "//conditions:default": [ + ":darwin_objcpp_opengl_srcs", + ], + }) + select({ + "//:legacy_renderer": [], + "//conditions:default": [ + ":darwin_objcpp_custom_drawable_srcs", + ], + }), + visibility = ["//platform:__pkg__"], +) diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index df83c141247..a7cee65be70 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -58,6 +58,7 @@ MLN_DARWIN_OBJC_HEADERS = [ "src/MLNCompassDirectionFormatter.h", "src/MLNComputedShapeSource.h", "src/MLNCoordinateFormatter.h", + "src/MLNCustomDrawableStyleLayer.h", "src/MLNDefaultStyle.h", "src/MLNDistanceFormatter.h", "src/MLNFeature.h", @@ -74,6 +75,7 @@ MLN_DARWIN_OBJC_HEADERS = [ "src/MLNOfflinePack.h", "src/MLNOfflineRegion.h", "src/MLNOfflineStorage.h", + "src/MLNOpenGLStyleLayer.h", "src/MLNOverlay.h", "src/MLNPointAnnotation.h", "src/MLNPointCollection.h", @@ -210,7 +212,6 @@ MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE = [ ] MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE = [ "src/MLNCustomDrawableStyleLayer_Private.h", - "src/MLNCustomDrawableStyleLayer.h", "src/MLNCustomDrawableStyleLayer.mm", ] MLN_DARWIN_PUBLIC_OBJC_SOURCE = [ diff --git a/platform/darwin/scripts/check-public-symbols.sh b/platform/darwin/scripts/check-public-symbols.sh deleted file mode 100755 index 36b0c052e93..00000000000 --- a/platform/darwin/scripts/check-public-symbols.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -node platform/darwin/scripts/check-public-symbols.js macOS iOS diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index 33ee6a820a2..fad212c1667 100644 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -3,7 +3,7 @@ const { ArgumentParser } = require("argparse"); const fs = require('fs'); -const ejs = require('ejs'); +const path = require('path'); const _ = require('lodash'); const colorParser = require('csscolorparser'); const assert = require('assert'); @@ -21,7 +21,7 @@ const args = (() => { }); return parser.parse_args(); })(); - + const cocoaConventions = require('./style-spec-cocoa-conventions-v8.json'); const prefix = 'MLN'; const suffix = 'StyleLayer'; @@ -51,7 +51,7 @@ _.forOwn(cocoaConventions, function (properties, kind) { _.forOwn(properties, function (newConvention, oldName) { let conventionOverride = new ConventionOverride(newConvention); let property = spec[kind][oldName]; - + if (property) { if (conventionOverride.name.startsWith('is-')) { property.getter = conventionOverride.name; @@ -787,23 +787,25 @@ const lightProperties = Object.keys(spec['light']).reduce((memo, name) => { const lightDoc = spec['light-cocoa-doc']; const lightType = 'light'; -const layerH = ejs.compile(fs.readFileSync('platform/darwin/src/MLNStyleLayer.h.ejs', 'utf8'), { strict: true }); -const layerPrivateH = ejs.compile(fs.readFileSync('platform/darwin/src/MLNStyleLayer_Private.h.ejs', 'utf8'), { strict: true }); -const layerM = ejs.compile(fs.readFileSync('platform/darwin/src/MLNStyleLayer.mm.ejs', 'utf8'), { strict: true}); -const testLayers = ejs.compile(fs.readFileSync('platform/darwin/test/MLNStyleLayerTests.mm.ejs', 'utf8'), { strict: true}); -const forStyleAuthorsMD = ejs.compile(fs.readFileSync('platform/darwin/docs/guides/For_Style_Authors.md.ejs', 'utf8'), { strict: true }); -const ddsGuideMD = ejs.compile(fs.readFileSync('platform/darwin/docs/guides/Migrating_to_Expressions.md.ejs', 'utf8'), { strict: true }); -const templatesMD = ejs.compile(fs.readFileSync('platform/darwin/docs/guides/Tile_URL_Templates.md.ejs', 'utf8'), { strict: true }); - -const lightH = ejs.compile(fs.readFileSync('platform/darwin/src/MLNLight.h.ejs', 'utf8'), {strict: true}); -const lightM = ejs.compile(fs.readFileSync('platform/darwin/src/MLNLight.mm.ejs', 'utf8'), {strict: true}); -const testLight = ejs.compile(fs.readFileSync('platform/darwin/test/MLNLightTest.mm.ejs', 'utf8'), { strict: true}); +const root = args.root ? args.root : path.dirname(path.dirname(path.dirname(__dirname))); + +const layerH = readAndCompile('platform/darwin/src/MLNStyleLayer.h.ejs', root); +const layerPrivateH = readAndCompile('platform/darwin/src/MLNStyleLayer_Private.h.ejs', root); +const layerM = readAndCompile('platform/darwin/src/MLNStyleLayer.mm.ejs', root); +const testLayers = readAndCompile('platform/darwin/test/MLNStyleLayerTests.mm.ejs', root); +const forStyleAuthorsMD = readAndCompile('platform/darwin/docs/guides/For_Style_Authors.md.ejs', root); +const ddsGuideMD = readAndCompile('platform/darwin/docs/guides/Migrating_to_Expressions.md.ejs', root); +const templatesMD = readAndCompile('platform/darwin/docs/guides/Tile_URL_Templates.md.ejs', root); + +const lightH = readAndCompile('platform/darwin/src/MLNLight.h.ejs', root); +const lightM = readAndCompile('platform/darwin/src/MLNLight.mm.ejs', root); +const testLight = readAndCompile('platform/darwin/test/MLNLightTest.mm.ejs', root); writeIfModified(`platform/darwin/src/MLNLight.h`, duplicatePlatformDecls( - lightH({ properties: lightProperties, doc: lightDoc, type: lightType })), args.root); + lightH({ properties: lightProperties, doc: lightDoc, type: lightType })), root); writeIfModified(`platform/darwin/src/MLNLight.mm`, - lightM({ properties: lightProperties, doc: lightDoc, type: lightType }), args.root); + lightM({ properties: lightProperties, doc: lightDoc, type: lightType }), root); writeIfModified(`platform/darwin/test/MLNLightTest.mm`, - testLight({ properties: lightProperties, doc: lightDoc, type: lightType }), args.root); + testLight({ properties: lightProperties, doc: lightDoc, type: lightType }), root); const layers = _(spec.layer.type.values).map((value, layerType) => { const layoutProperties = Object.keys(spec[`layout_${layerType}`]).reduce((memo, name) => { @@ -877,13 +879,13 @@ for (var layer of layers) { } writeIfModified(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.h`, - duplicatePlatformDecls(layerH(layer)), args.root); + duplicatePlatformDecls(layerH(layer)), root); writeIfModified(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}_Private.h`, - duplicatePlatformDecls(layerPrivateH(layer)), args.root); + duplicatePlatformDecls(layerPrivateH(layer)), root); writeIfModified(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.mm`, - layerM(layer), args.root); + layerM(layer), root); writeIfModified(`platform/darwin/test/${prefix}${camelize(layer.type)}${suffix}Tests.mm`, - testLayers(layer), args.root); + testLayers(layer), root); } // Extract examples for guides from unit tests. @@ -925,21 +927,21 @@ writeIfModified(`platform/ios/docs/guides/For Style Authors.md`, forStyleAuthors os: 'iOS', renamedProperties: renamedPropertiesByLayerType, layers: layers, -}), args.root); +}), root); writeIfModified(`platform/macos/docs/guides/For Style Authors.md`, forStyleAuthorsMD({ os: 'macOS', renamedProperties: renamedPropertiesByLayerType, layers: layers, -}), args.root); +}), root); writeIfModified(`platform/ios/docs/guides/Migrating to Expressions.md`, ddsGuideMD({ os: 'iOS', -}), args.root); +}), root); writeIfModified(`platform/macos/docs/guides/Migrating to Expressions.md`, ddsGuideMD({ os: 'macOS', -}), args.root); +}), root); writeIfModified(`platform/ios/docs/guides/Tile URL Templates.md`, templatesMD({ os: 'iOS', -}), args.root); +}), root); writeIfModified(`platform/macos/docs/guides/Tile URL Templates.md`, templatesMD({ os: 'macOS', -}), args.root);*/ \ No newline at end of file +}), root);*/ diff --git a/platform/darwin/src/MLNCustomDrawableStyleLayer.h b/platform/darwin/src/MLNCustomDrawableStyleLayer.h index bd84e13aee3..b32d63a462e 100644 --- a/platform/darwin/src/MLNCustomDrawableStyleLayer.h +++ b/platform/darwin/src/MLNCustomDrawableStyleLayer.h @@ -10,6 +10,7 @@ #include #endif +MLN_EXPORT @interface MLNCustomDrawableStyleLayer : MLNStyleLayer #ifdef __cplusplus diff --git a/platform/ios/BUILD.bazel b/platform/ios/BUILD.bazel index 146f3ea3289..15a7fe0d15b 100644 --- a/platform/ios/BUILD.bazel +++ b/platform/ios/BUILD.bazel @@ -1,3 +1,4 @@ +load("@aspect_rules_js//js:defs.bzl", "js_library") load("@build_bazel_rules_apple//apple:apple.bzl", "apple_static_xcframework", "apple_xcframework") load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_framework") load("@build_bazel_rules_apple//apple:resources.bzl", "apple_resource_bundle") @@ -96,11 +97,9 @@ info_plist( ) public_hdrs = [ - ":umbrella_header", ":ios_public_hdrs", ":ios_sdk_hdrs", "//platform/darwin:darwin_objc_hdrs", - "//platform/darwin:darwin_objcpp_hdrs", "//platform/darwin:generated_style_public_hdrs", ] @@ -116,7 +115,6 @@ apple_static_xcframework( }, minimum_os_versions = {"ios": "12.0"}, public_hdrs = public_hdrs, - umbrella_header = "umbrella_header", visibility = ["//visibility:public"], deps = ["//platform:ios-sdk"], ) @@ -150,7 +148,6 @@ apple_xcframework( }, minimum_os_versions = {"ios": "12.0"}, public_hdrs = public_hdrs, - umbrella_header = "umbrella_header", version = ":maplibre_ios_version", visibility = ["//visibility:public"], deps = ["//platform:ios-sdk-dynamic"], @@ -158,11 +155,13 @@ apple_xcframework( ios_framework( name = "MapLibre.link", + testonly = True, bundle_id = "com.maplibre.link", families = ["iphone"], infoplists = ["info_plist"], linkopts = [""], minimum_os_version = "12.0", + visibility = ["//visibility:public"], deps = ["//platform:ios-sdk"], ) @@ -333,3 +332,15 @@ exports_files( MLN_IOS_PUBLIC_OBJC_SOURCE + MLN_PUBLIC_IOS_APP_SOPURCE, ) + +js_library( + name = "symbols-to-check", + srcs = [ + ":ios_objc_srcs", + ":ios_objcpp_srcs", + ":ios_private_hdrs", + ":ios_public_hdrs", + ":ios_sdk_hdrs", + ], + visibility = ["//platform:__pkg__"], +) diff --git a/platform/ios/bazel/files.bzl b/platform/ios/bazel/files.bzl index 0a868521601..fb9ad10976e 100644 --- a/platform/ios/bazel/files.bzl +++ b/platform/ios/bazel/files.bzl @@ -14,26 +14,22 @@ MLN_IOS_SDK_HEADERS = [ ] MLN_IOS_PUBLIC_HEADERS = [ - "src/MLNAnnotationContainerView.h", - "src/MLNCompactCalloutView.h", - "src/MLNFaux3DUserLocationAnnotationView.h", - "src/MLNMapAccessibilityElement.h", - "src/MLNMapView+Impl.h", - "src/MLNMapView+Metal.h", - "src/MLNMapView+OpenGL.h", - "src/MLNScaleBar.h", - "src/MLNUserLocationHeadingArrowLayer.h", - "src/MLNUserLocationHeadingBeamLayer.h", - "src/MLNUserLocationHeadingIndicator.h", +] + +MLN_IOS_PRIVATE_HEADERS = [ "src/NSOrthography+MLNAdditions.h", - "src/UIColor+MLNAdditions.h", "src/UIDevice+MLNAdditions.h", "src/UIImage+MLNAdditions.h", - "src/UIView+MLNAdditions.h", + "src/MLNUserLocationHeadingArrowLayer.h", + "src/MLNUserLocationHeadingIndicator.h", + "src/UIColor+MLNAdditions.h", + "src/MLNMapAccessibilityElement.h", "src/UIViewController+MLNAdditions.h", -] - -MLN_IOS_PRIVATE_HEADERS = [ + "src/UIView+MLNAdditions.h", + "src/MLNScaleBar.h", + "src/MLNFaux3DUserLocationAnnotationView.h", + "src/MLNUserLocationHeadingBeamLayer.h", + "src/MLNAnnotationContainerView.h", "src/MLNAnnotationContainerView_Private.h", "src/MLNAnnotationImage_Private.h", "src/MLNAnnotationView_Private.h", @@ -41,6 +37,10 @@ MLN_IOS_PRIVATE_HEADERS = [ "src/MLNMapView_Private.h", "src/MLNUserLocationAnnotationView_Private.h", "src/MLNUserLocation_Private.h", + "src/MLNCompactCalloutView.h", + "src/MLNMapView+Impl.h", + "src/MLNMapView+Metal.h", + "src/MLNMapView+OpenGL.h", ] MLN_IOS_PUBLIC_OBJC_SOURCE = [ diff --git a/platform/ios/src/MLNCalloutView.h b/platform/ios/src/MLNCalloutView.h index f93fa44902a..e1c69e287f5 100644 --- a/platform/ios/src/MLNCalloutView.h +++ b/platform/ios/src/MLNCalloutView.h @@ -1,4 +1,5 @@ #import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/platform/ios/src/MLNMapView+IBAdditions.h b/platform/ios/src/MLNMapView+IBAdditions.h index c99af1bf608..f726c3efca4 100644 --- a/platform/ios/src/MLNMapView+IBAdditions.h +++ b/platform/ios/src/MLNMapView+IBAdditions.h @@ -1,6 +1,5 @@ #import - -@class MLNMapView; +#import "MLNMapView.h" NS_ASSUME_NONNULL_BEGIN diff --git a/platform/ios/src/Mapbox.template.h b/platform/ios/src/Mapbox.template.h index 43ac29b78b1..bd74eb2ceca 100644 --- a/platform/ios/src/Mapbox.template.h +++ b/platform/ios/src/Mapbox.template.h @@ -77,6 +77,6 @@ FOUNDATION_EXPORT MLN_EXPORT const unsigned char MapboxVersionString[]; #import "NSPredicate+MLNAdditions.h" #import "NSValue+MLNAdditions.h" #import "MLNUserLocationAnnotationViewStyle.h" -#if MLN_DRAWABLE_RENDERER +#if defined(MLN_DRAWABLE_RENDERER) || defined(MLN_RENDER_BACKEND_METAL) #import "MLNCustomDrawableStyleLayer.h" #endif diff --git a/platform/macos/BUILD.bazel b/platform/macos/BUILD.bazel index 66b7858e306..813880dcbb4 100644 --- a/platform/macos/BUILD.bazel +++ b/platform/macos/BUILD.bazel @@ -1,10 +1,11 @@ +load("@aspect_rules_js//js:defs.bzl", "js_library") load( "bazel/files.bzl", "MLN_MACOS_PRIVATE_HEADERS", "MLN_MACOS_PUBLIC_HEADERS", - "MLN_MACOS_PUBLIC_OBJCPP_SOURCE", - "MLN_MACOS_PUBLIC_OBJCPP_OPENGL_SOURCE", "MLN_MACOS_PUBLIC_OBJCPP_METAL_SOURCE", + "MLN_MACOS_PUBLIC_OBJCPP_OPENGL_SOURCE", + "MLN_MACOS_PUBLIC_OBJCPP_SOURCE", "MLN_MACOS_PUBLIC_OBJC_SOURCE", ) @@ -40,5 +41,16 @@ exports_files( MLN_MACOS_PUBLIC_HEADERS + MLN_MACOS_PUBLIC_OBJCPP_SOURCE + MLN_MACOS_PUBLIC_OBJCPP_METAL_SOURCE + - MLN_MACOS_PUBLIC_OBJC_SOURCE + MLN_MACOS_PUBLIC_OBJC_SOURCE, +) + +js_library( + name = "symbols-to-check", + srcs = [ + ":macos_objc_srcs", + ":macos_objcpp_srcs", + ":macos_private_hdrs", + ":macos_public_hdrs", + ], + visibility = ["//platform:__pkg__"], ) diff --git a/platform/macos/src/MLNMapViewDelegate.h b/platform/macos/src/MLNMapViewDelegate.h index c1f1e40615f..e0ddac33059 100644 --- a/platform/macos/src/MLNMapViewDelegate.h +++ b/platform/macos/src/MLNMapViewDelegate.h @@ -1,12 +1,16 @@ +#import #import NS_ASSUME_NONNULL_BEGIN -@class MLNMapView; @class MLNAnnotationImage; +@class MLNMapCamera; +@class MLNMapView; @class MLNPolygon; @class MLNPolyline; @class MLNShape; +@class MLNStyle; +@protocol MLNAnnotation; /** The `MLNMapViewDelegate` protocol defines a set of optional methods that you diff --git a/platform/darwin/scripts/check-public-symbols.js b/platform/scripts/check-public-symbols.js similarity index 100% rename from platform/darwin/scripts/check-public-symbols.js rename to platform/scripts/check-public-symbols.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000000..7b18975eb53 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2634 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@acalcutt/node-pre-gyp': + specifier: ^1.0.14 + version: 1.0.14 + '@acalcutt/node-pre-gyp-github': + specifier: 1.4.8 + version: 1.4.8 + minimatch: + specifier: ^7.2.0 + version: 7.2.0 + npm-run-all: + specifier: ^4.0.2 + version: 4.1.5 + +devDependencies: + '@mapbox/flow-remove-types': + specifier: ^2.0.0 + version: 2.0.0 + '@mapbox/mvt-fixtures': + specifier: 3.10.0 + version: 3.10.0 + '@octokit/plugin-retry': + specifier: ^4.1.2 + version: 4.1.2(@octokit/core@4.1.0) + '@octokit/rest': + specifier: ^19.0.7 + version: 19.0.7 + argparse: + specifier: ^2.0.1 + version: 2.0.1 + aws-sdk: + specifier: ^2.1318.0 + version: 2.1318.0 + csscolorparser: + specifier: ~1.0.2 + version: 1.0.3 + d3-queue: + specifier: 3.0.7 + version: 3.0.7 + diff: + specifier: 5.1.0 + version: 5.1.0 + ejs: + specifier: ^3.1.8 + version: 3.1.8 + esm: + specifier: ~3.2.25 + version: 3.2.25 + express: + specifier: ^4.18.2 + version: 4.18.2 + json-stringify-pretty-compact: + specifier: ^4.0.0 + version: 4.0.0 + jsonwebtoken: + specifier: ^9.0.0 + version: 9.0.0 + lodash: + specifier: ^4.16.4 + version: 4.17.21 + lodash.template: + specifier: 4.5.0 + version: 4.5.0 + mapbox-gl-styles: + specifier: 2.0.2 + version: 2.0.2 + pixelmatch: + specifier: ^5.3.0 + version: 5.3.0 + pngjs: + specifier: ^6.0.0 + version: 6.0.0 + pretty-bytes: + specifier: ^6.1.0 + version: 6.1.0 + request: + specifier: ^2.88.0 + version: 2.88.2 + semver: + specifier: ^7.5.2 + version: 7.5.2 + shuffle-seed: + specifier: 1.1.6 + version: 1.1.6 + st: + specifier: 3.0.0 + version: 3.0.0 + tape: + specifier: ^5.6.3 + version: 5.6.3 + xcode: + specifier: ^3.0.1 + version: 3.0.1 + +packages: + + /@acalcutt/node-pre-gyp-github@1.4.8: + resolution: {integrity: sha512-GmtxEU5YdBUbeKw0Jy0qUFCy7/seA34iRj3buTlmY4L9ecaV2FKmhvgndhrJ3k7mYo2xQylGjNgpAQXY511oPw==} + hasBin: true + dependencies: + '@octokit/rest': 18.12.0 + commander: 7.2.0 + transitivePeerDependencies: + - encoding + dev: false + + /@acalcutt/node-pre-gyp@1.0.14: + resolution: {integrity: sha512-P+xIiJefMa2ylrqPDwCw1S4Xr6ULKvF5TqmbZKifPSzId9jiSgLoplKfTmoP/y3Mq2kWts/rZDX1N9wMaSl6ZA==} + hasBin: true + dependencies: + detect-libc: 2.0.1 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.6.7 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.5.2 + tar: 6.1.11 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /@babel/helper-string-parser@7.23.4: + resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/parser@7.19.0: + resolution: {integrity: sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/types@7.23.9: + resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.23.4 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + dev: true + + /@mapbox/flow-remove-types@2.0.0: + resolution: {integrity: sha512-FZIrEhfDJ4YUsedDrRJUptVg5gs1PJECdv8PzFEjgqNXn/dQBg0gXIvcsqNweuptybyGM+/Dl2E1QIvarxaMSw==} + hasBin: true + dependencies: + '@babel/parser': 7.19.0 + node-modules-regexp: 1.0.0 + pirates: 4.0.5 + vlq: 1.0.1 + dev: true + + /@mapbox/mvt-fixtures@3.10.0: + resolution: {integrity: sha512-HpObcr5eu7MOcxWqjj81fWjQ/VNUaAWKoK/rjxnd6NeEgN3uknrq6aGrkhC5vvZ20T2G6sWkikyITJ8mgPUa8g==} + dependencies: + '@mapbox/sphericalmercator': 1.2.0 + '@mapbox/vector-tile': 1.3.1 + d3-queue: 3.0.7 + pbf: 3.2.1 + protocol-buffers-schema: 3.6.0 + dev: true + + /@mapbox/point-geometry@0.1.0: + resolution: {integrity: sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==} + dev: true + + /@mapbox/sphericalmercator@1.2.0: + resolution: {integrity: sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ==} + hasBin: true + dev: true + + /@mapbox/vector-tile@1.3.1: + resolution: {integrity: sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==} + dependencies: + '@mapbox/point-geometry': 0.1.0 + dev: true + + /@octokit/auth-token@2.5.0: + resolution: {integrity: sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==} + dependencies: + '@octokit/types': 6.41.0 + dev: false + + /@octokit/auth-token@3.0.0: + resolution: {integrity: sha512-MDNFUBcJIptB9At7HiV7VCvU3NcL4GnfCQaP8C5lrxWrRPMJBnemYtehaKSOlaM7AYxeRyj9etenu8LVpSpVaQ==} + engines: {node: '>= 14'} + dependencies: + '@octokit/types': 6.41.0 + dev: true + + /@octokit/core@3.6.0: + resolution: {integrity: sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==} + dependencies: + '@octokit/auth-token': 2.5.0 + '@octokit/graphql': 4.8.0 + '@octokit/request': 5.6.3 + '@octokit/request-error': 2.1.0 + '@octokit/types': 6.41.0 + before-after-hook: 2.2.2 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: false + + /@octokit/core@4.1.0: + resolution: {integrity: sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==} + engines: {node: '>= 14'} + dependencies: + '@octokit/auth-token': 3.0.0 + '@octokit/graphql': 5.0.0 + '@octokit/request': 6.2.0 + '@octokit/request-error': 3.0.0 + '@octokit/types': 8.0.0 + before-after-hook: 2.2.2 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/endpoint@6.0.12: + resolution: {integrity: sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==} + dependencies: + '@octokit/types': 6.41.0 + is-plain-object: 5.0.0 + universal-user-agent: 6.0.0 + dev: false + + /@octokit/endpoint@7.0.0: + resolution: {integrity: sha512-Kz/mIkOTjs9rV50hf/JK9pIDl4aGwAtT8pry6Rpy+hVXkAPhXanNQRxMoq6AeRgDCZR6t/A1zKniY2V1YhrzlQ==} + engines: {node: '>= 14'} + dependencies: + '@octokit/types': 6.41.0 + is-plain-object: 5.0.0 + universal-user-agent: 6.0.0 + dev: true + + /@octokit/graphql@4.8.0: + resolution: {integrity: sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==} + dependencies: + '@octokit/request': 5.6.3 + '@octokit/types': 6.41.0 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: false + + /@octokit/graphql@5.0.0: + resolution: {integrity: sha512-1ZZ8tX4lUEcLPvHagfIVu5S2xpHYXAmgN0+95eAOPoaVPzCfUXJtA5vASafcpWcO86ze0Pzn30TAx72aB2aguQ==} + engines: {node: '>= 14'} + dependencies: + '@octokit/request': 6.2.0 + '@octokit/types': 6.41.0 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/openapi-types@12.11.0: + resolution: {integrity: sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==} + + /@octokit/openapi-types@14.0.0: + resolution: {integrity: sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==} + dev: true + + /@octokit/openapi-types@16.0.0: + resolution: {integrity: sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==} + dev: true + + /@octokit/plugin-paginate-rest@2.21.3(@octokit/core@3.6.0): + resolution: {integrity: sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==} + peerDependencies: + '@octokit/core': '>=2' + dependencies: + '@octokit/core': 3.6.0 + '@octokit/types': 6.41.0 + dev: false + + /@octokit/plugin-paginate-rest@6.0.0(@octokit/core@4.1.0): + resolution: {integrity: sha512-Sq5VU1PfT6/JyuXPyt04KZNVsFOSBaYOAq2QRZUwzVlI10KFvcbUo8lR258AAQL1Et60b0WuVik+zOWKLuDZxw==} + engines: {node: '>= 14'} + peerDependencies: + '@octokit/core': '>=4' + dependencies: + '@octokit/core': 4.1.0 + '@octokit/types': 9.0.0 + dev: true + + /@octokit/plugin-request-log@1.0.4(@octokit/core@3.6.0): + resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==} + peerDependencies: + '@octokit/core': '>=3' + dependencies: + '@octokit/core': 3.6.0 + dev: false + + /@octokit/plugin-request-log@1.0.4(@octokit/core@4.1.0): + resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==} + peerDependencies: + '@octokit/core': '>=3' + dependencies: + '@octokit/core': 4.1.0 + dev: true + + /@octokit/plugin-rest-endpoint-methods@5.16.2(@octokit/core@3.6.0): + resolution: {integrity: sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==} + peerDependencies: + '@octokit/core': '>=3' + dependencies: + '@octokit/core': 3.6.0 + '@octokit/types': 6.41.0 + deprecation: 2.3.1 + dev: false + + /@octokit/plugin-rest-endpoint-methods@7.0.1(@octokit/core@4.1.0): + resolution: {integrity: sha512-pnCaLwZBudK5xCdrR823xHGNgqOzRnJ/mpC/76YPpNP7DybdsJtP7mdOwh+wYZxK5jqeQuhu59ogMI4NRlBUvA==} + engines: {node: '>= 14'} + peerDependencies: + '@octokit/core': '>=3' + dependencies: + '@octokit/core': 4.1.0 + '@octokit/types': 9.0.0 + deprecation: 2.3.1 + dev: true + + /@octokit/plugin-retry@4.1.2(@octokit/core@4.1.0): + resolution: {integrity: sha512-hscf7p/6DIQ8xbfDrMl9IflxugED6sFQvAUbSi75R6h/6hcNQgrb2vpfPTmyYKkdAEeTkUsEpzpQFdTAhSITOw==} + engines: {node: '>= 14'} + peerDependencies: + '@octokit/core': '>=3' + dependencies: + '@octokit/core': 4.1.0 + '@octokit/types': 9.0.0 + bottleneck: 2.19.5 + dev: true + + /@octokit/request-error@2.1.0: + resolution: {integrity: sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==} + dependencies: + '@octokit/types': 6.41.0 + deprecation: 2.3.1 + once: 1.4.0 + dev: false + + /@octokit/request-error@3.0.0: + resolution: {integrity: sha512-WBtpzm9lR8z4IHIMtOqr6XwfkGvMOOILNLxsWvDwtzm/n7f5AWuqJTXQXdDtOvPfTDrH4TPhEvW2qMlR4JFA2w==} + engines: {node: '>= 14'} + dependencies: + '@octokit/types': 6.41.0 + deprecation: 2.3.1 + once: 1.4.0 + dev: true + + /@octokit/request@5.6.3: + resolution: {integrity: sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==} + dependencies: + '@octokit/endpoint': 6.0.12 + '@octokit/request-error': 2.1.0 + '@octokit/types': 6.41.0 + is-plain-object: 5.0.0 + node-fetch: 2.6.7 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: false + + /@octokit/request@6.2.0: + resolution: {integrity: sha512-7IAmHnaezZrgUqtRShMlByJK33MT9ZDnMRgZjnRrRV9a/jzzFwKGz0vxhFU6i7VMLraYcQ1qmcAOin37Kryq+Q==} + engines: {node: '>= 14'} + dependencies: + '@octokit/endpoint': 7.0.0 + '@octokit/request-error': 3.0.0 + '@octokit/types': 6.41.0 + is-plain-object: 5.0.0 + node-fetch: 2.6.7 + universal-user-agent: 6.0.0 + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/rest@18.12.0: + resolution: {integrity: sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==} + dependencies: + '@octokit/core': 3.6.0 + '@octokit/plugin-paginate-rest': 2.21.3(@octokit/core@3.6.0) + '@octokit/plugin-request-log': 1.0.4(@octokit/core@3.6.0) + '@octokit/plugin-rest-endpoint-methods': 5.16.2(@octokit/core@3.6.0) + transitivePeerDependencies: + - encoding + dev: false + + /@octokit/rest@19.0.7: + resolution: {integrity: sha512-HRtSfjrWmWVNp2uAkEpQnuGMJsu/+dBr47dRc5QVgsCbnIc1+GFEaoKBWkYG+zjrsHpSqcAElMio+n10c0b5JA==} + engines: {node: '>= 14'} + dependencies: + '@octokit/core': 4.1.0 + '@octokit/plugin-paginate-rest': 6.0.0(@octokit/core@4.1.0) + '@octokit/plugin-request-log': 1.0.4(@octokit/core@4.1.0) + '@octokit/plugin-rest-endpoint-methods': 7.0.1(@octokit/core@4.1.0) + transitivePeerDependencies: + - encoding + dev: true + + /@octokit/types@6.41.0: + resolution: {integrity: sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==} + dependencies: + '@octokit/openapi-types': 12.11.0 + + /@octokit/types@8.0.0: + resolution: {integrity: sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==} + dependencies: + '@octokit/openapi-types': 14.0.0 + dev: true + + /@octokit/types@9.0.0: + resolution: {integrity: sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==} + dependencies: + '@octokit/openapi-types': 16.0.0 + dev: true + + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: false + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: false + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: false + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: false + + /are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.0 + dev: false + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: true + + /array.prototype.every@1.1.4: + resolution: {integrity: sha512-Aui35iRZk1HHLRAyF7QP0KAnOnduaQ6fo6k1NVWfRc0xTs2AZ70ytlXvOmkC6Di4JmUs2Wv3DYzGtCQFSk5uGg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + is-string: 1.0.7 + dev: true + + /asn1@0.2.4: + resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + dev: true + + /async-cache@1.1.0: + resolution: {integrity: sha512-YDQc4vBn5NFhY6g6HhVshyi3Fy9+SQ5ePnE7JLDJn1DoL+i7ER+vMwtTNOYk9leZkYMnOwpBCWqyLDPw8Aig8g==} + deprecated: No longer maintained. Use [lru-cache](http://npm.im/lru-cache) version 7.6 or higher, and provide an asynchronous `fetchMethod` option. + dependencies: + lru-cache: 4.1.5 + dev: true + + /async@3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: true + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + + /aws-sdk@2.1318.0: + resolution: {integrity: sha512-xRCKqx4XWXUIpjDCVHmdOSINEVCIC5+yhmgUGR9A6VfxfPs59HbxKyd5LB+CmXhVbwVUM4SRWG5O+agQj+w7Eg==} + engines: {node: '>= 10.0.0'} + dependencies: + buffer: 4.9.2 + events: 1.1.1 + ieee754: 1.1.13 + jmespath: 0.16.0 + querystring: 0.2.0 + sax: 1.2.1 + url: 0.10.3 + util: 0.12.4 + uuid: 8.0.0 + xml2js: 0.4.19 + dev: true + + /aws-sign2@0.7.0: + resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} + dev: true + + /aws4@1.11.0: + resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==} + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + dependencies: + tweetnacl: 0.14.5 + dev: true + + /before-after-hook@2.2.2: + resolution: {integrity: sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==} + + /big-integer@1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + dev: true + + /bl@5.0.0: + resolution: {integrity: sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==} + dependencies: + buffer: 6.0.3 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: true + + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + dev: true + + /bplist-creator@0.1.0: + resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} + dependencies: + stream-buffers: 2.2.0 + dev: true + + /bplist-parser@0.3.1: + resolution: {integrity: sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==} + engines: {node: '>= 5.10.0'} + dependencies: + big-integer: 1.6.51 + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: true + + /buffer@4.9.2: + resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + isarray: 1.0.0 + dev: true + + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: true + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.3 + + /caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: false + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + dev: false + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: false + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: false + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: true + + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: false + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: false + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /content-type@1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + dev: true + + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: true + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: true + + /core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + dev: true + + /cross-spawn@6.0.5: + resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} + engines: {node: '>=4.8'} + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.1 + shebang-command: 1.2.0 + which: 1.3.1 + dev: false + + /csscolorparser@1.0.3: + resolution: {integrity: sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==} + dev: true + + /d3-queue@3.0.7: + resolution: {integrity: sha512-2rs+6pNFKkrJhqe1rg5znw7dKJ7KZr62j9aLZfhondkrnz6U7VRmJj1UGcbD8MRc46c7H8m4SWhab8EalBQrkw==} + dev: true + + /dashdash@1.14.1: + resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} + engines: {node: '>=0.10'} + dependencies: + assert-plus: 1.0.0 + dev: true + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false + + /deep-equal@2.2.0: + resolution: {integrity: sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==} + dependencies: + call-bind: 1.0.2 + es-get-iterator: 1.1.3 + get-intrinsic: 1.1.3 + is-arguments: 1.1.1 + is-array-buffer: 3.0.1 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + isarray: 2.0.5 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.9 + dev: true + + /define-properties@1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + + /defined@1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + dev: true + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true + + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: false + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: true + + /deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: true + + /detect-libc@2.0.1: + resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} + engines: {node: '>=8'} + dev: false + + /diff@5.1.0: + resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + engines: {node: '>=0.3.1'} + dev: true + + /dotignore@0.1.2: + resolution: {integrity: sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==} + hasBin: true + dependencies: + minimatch: 3.1.2 + dev: true + + /ecc-jsbn@0.1.2: + resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + dev: true + + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: true + + /ejs@3.1.8: + resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + jake: 10.8.5 + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: false + + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: true + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: false + + /es-abstract@1.21.1: + resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.1.3 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.4 + is-array-buffer: 3.0.1 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + safe-regex-test: 1.0.0 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.9 + + /es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + dev: true + + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.3 + has: 1.0.3 + has-tostringtag: 1.0.0 + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: false + + /esm@3.2.25: + resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} + engines: {node: '>=6'} + dev: true + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: true + + /events@1.1.1: + resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} + engines: {node: '>=0.4.x'} + dev: true + + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.4 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: true + + /extsprintf@1.3.0: + resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} + engines: {'0': node >=0.6.0} + dev: true + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fd@0.0.3: + resolution: {integrity: sha512-iAHrIslQb3U68OcMSP0kkNWabp7sSN6d2TBSb2JO3gcLJVDd4owr/hKM4SFJovFOUeeXeItjYgouEDTMWiVAnA==} + dev: true + + /filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + dependencies: + minimatch: 5.1.6 + dev: true + + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + + /forever-agent@0.6.1: + resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} + dev: true + + /form-data@2.3.3: + resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} + engines: {node: '>= 0.12'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: true + + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: true + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: true + + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.4 + dev: false + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /function.prototype.name@1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + functions-have-names: 1.2.3 + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + /gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.5 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + dev: false + + /get-intrinsic@1.1.3: + resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + + /getpass@0.1.7: + resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + dependencies: + assert-plus: 1.0.0 + dev: true + + /glob@5.0.15: + resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} + dependencies: + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.1.4 + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.1.3 + + /graceful-fs@4.2.8: + resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==} + + /har-schema@2.0.0: + resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} + engines: {node: '>=4'} + dev: true + + /har-validator@5.1.5: + resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} + engines: {node: '>=6'} + deprecated: this library is no longer supported + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + dev: true + + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + /has-dynamic-import@2.0.1: + resolution: {integrity: sha512-X3fbtsZmwb6W7fJGR9o7x65fZoodygCrZ3TVycvghP62yYQfS0t4RS0Qcz+j5tQYUKeSWS09tHkWW6WhFV3XhQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: false + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.1.3 + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + dev: false + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: false + + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: true + + /http-signature@1.2.0: + resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} + engines: {node: '>=0.8', npm: '>=1.3.7'} + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.16.1 + dev: true + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /ieee754@1.1.13: + resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==} + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /internal-slot@1.0.4: + resolution: {integrity: sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.3 + has: 1.0.3 + side-channel: 1.0.4 + + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: true + + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-array-buffer@3.0.1: + resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + is-typed-array: 1.1.10 + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: false + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + /is-core-module@2.10.0: + resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} + dependencies: + has: 1.0.3 + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: false + + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + + /is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /is-typed-array@1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + + /is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + dev: true + + /is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + + /is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + dev: true + + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: true + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: false + + /isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + dev: true + + /jake@10.8.5: + resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + async: 3.2.4 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + dev: true + + /jmespath@0.16.0: + resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} + engines: {node: '>= 0.6.0'} + dev: true + + /jsbn@0.1.1: + resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} + dev: true + + /json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + dev: false + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: true + + /json-stringify-pretty-compact@4.0.0: + resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} + dev: true + + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + + /jsonwebtoken@9.0.0: + resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash: 4.17.21 + ms: 2.1.3 + semver: 7.5.2 + dev: true + + /jsprim@1.4.2: + resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} + engines: {node: '>=0.6.0'} + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + dev: true + + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: true + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: true + + /load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + dependencies: + graceful-fs: 4.2.8 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: false + + /lodash._reinterpolate@3.0.0: + resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} + dev: true + + /lodash.template@4.5.0: + resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} + dependencies: + lodash._reinterpolate: 3.0.0 + lodash.templatesettings: 4.2.0 + dev: true + + /lodash.templatesettings@4.2.0: + resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} + dependencies: + lodash._reinterpolate: 3.0.0 + dev: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true + + /lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.0 + dev: false + + /mapbox-gl-styles@2.0.2: + resolution: {integrity: sha512-Uf9Pd37vnKK+7iVs5PHvVr1k0cCdHu+k+twYx0woFf12y6nq340qJUb2HtBFu8vzYjx4OFBvLES8GQZ5ei5sLw==} + deprecated: This package has moved to the @mapbox namespace. All new version are available via @mapbox/mapbox-gl-styles + dependencies: + glob: 5.0.15 + dev: true + + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: true + + /memorystream@0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + dev: false + + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: true + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: true + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: true + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: true + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@7.2.0: + resolution: {integrity: sha512-rMRHmwySzopAQjmWW6TkAKCEDKNaY/HuV/c2YkWWuWnfkTwApt0V4hnYzzPnZ/5Gcd2+8MPncSyuOGPl3xPvcg==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimist@1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: true + + /minipass@3.3.4: + resolution: {integrity: sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==} + engines: {node: '>=8'} + dependencies: + yallist: 4.0.0 + dev: false + + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.4 + yallist: 4.0.0 + dev: false + + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: false + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: true + + /nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + dev: false + + /node-fetch@2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + + /node-modules-regexp@1.0.0: + resolution: {integrity: sha512-JMaRS9L4wSRIR+6PTVEikTrq/lMGEZR43a48ETeilY0Q0iMwVnccMFrUM1k+tNzmYuIU0Vh710bCUqHX+/+ctQ==} + engines: {node: '>=0.10.0'} + dev: true + + /nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: false + + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.20.0 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: false + + /npm-run-all@4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.5 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.7.3 + string.prototype.padend: 3.1.3 + dev: false + + /npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + dev: false + + /oauth-sign@0.9.0: + resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} + dev: true + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: false + + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + /path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + dev: false + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: true + + /path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + dependencies: + pify: 3.0.0 + dev: false + + /pbf@3.2.1: + resolution: {integrity: sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==} + hasBin: true + dependencies: + ieee754: 1.2.1 + resolve-protobuf-schema: 2.1.0 + dev: true + + /performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + dev: true + + /pidtree@0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + dev: false + + /pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: false + + /pirates@4.0.5: + resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} + engines: {node: '>= 6'} + dev: true + + /pixelmatch@5.3.0: + resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} + hasBin: true + dependencies: + pngjs: 6.0.0 + dev: true + + /plist@3.0.6: + resolution: {integrity: sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==} + engines: {node: '>=6'} + dependencies: + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + dev: true + + /pngjs@6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + dev: true + + /pretty-bytes@6.1.0: + resolution: {integrity: sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==} + engines: {node: ^14.13.1 || >=16.0.0} + dev: true + + /protocol-buffers-schema@3.6.0: + resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==} + dev: true + + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: true + + /pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + dev: true + + /psl@1.8.0: + resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} + dev: true + + /punycode@1.3.2: + resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} + dev: true + + /punycode@2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + dev: true + + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: true + + /qs@6.5.3: + resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} + engines: {node: '>=0.6'} + dev: true + + /querystring@0.2.0: + resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} + engines: {node: '>=0.4.x'} + deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. + dev: true + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: true + + /raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: true + + /read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: false + + /readable-stream@3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + /regexp.prototype.flags@1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 + + /request@2.88.2: + resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + dependencies: + aws-sign2: 0.7.0 + aws4: 1.11.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.3.2 + dev: true + + /resolve-protobuf-schema@2.1.0: + resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} + dependencies: + protocol-buffers-schema: 3.6.0 + dev: true + + /resolve@1.20.0: + resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==} + dependencies: + is-core-module: 2.10.0 + path-parse: 1.0.7 + dev: false + + /resolve@2.0.0-next.4: + resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} + hasBin: true + dependencies: + is-core-module: 2.10.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /resumer@0.0.0: + resolution: {integrity: sha512-Fn9X8rX8yYF4m81rZCK/5VmrmsSbqS/i3rDLl6ZZHAXgC2nTAx3dhwG8q8odP/RmdLa2YrybDJaAMg+X1ajY3w==} + dependencies: + through: 2.3.8 + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: false + + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + is-regex: 1.1.4 + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: true + + /sax@1.2.1: + resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} + dev: true + + /sax@1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + dev: true + + /seedrandom@2.4.4: + resolution: {integrity: sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==} + dev: true + + /semver@5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: false + + /semver@6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + dev: false + + /semver@7.5.2: + resolution: {integrity: sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: true + + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: false + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: true + + /shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: false + + /shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: false + + /shell-quote@1.7.3: + resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} + dev: false + + /shuffle-seed@1.1.6: + resolution: {integrity: sha512-Vr9wlwMKJVUeFNGyc4aNbrzkI568gkve7ykyJ+1/cz78j3yRlJODWU0CuJ/fmk3MCjvAClpnqlycd/Y53UG3UA==} + dependencies: + seedrandom: 2.4.4 + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + object-inspect: 1.12.3 + + /signal-exit@3.0.5: + resolution: {integrity: sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==} + dev: false + + /simple-plist@1.3.1: + resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} + dependencies: + bplist-creator: 0.1.0 + bplist-parser: 0.3.1 + plist: 3.0.6 + dev: true + + /spdx-correct@3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.10 + dev: false + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: false + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.10 + dev: false + + /spdx-license-ids@3.0.10: + resolution: {integrity: sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==} + dev: false + + /sshpk@1.16.1: + resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + asn1: 0.2.4 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + dev: true + + /st@3.0.0: + resolution: {integrity: sha512-UEUi8P8Y5GOewlJbE5vrhsaQRwmbNVMUr6PLxRZHH4Cwz8CkHhnBqlqGtE3egXQd+ceUwNxdOVjsC/IsgN2Pww==} + hasBin: true + dependencies: + async-cache: 1.1.0 + bl: 5.0.0 + fd: 0.0.3 + mime: 2.6.0 + negotiator: 0.6.3 + optionalDependencies: + graceful-fs: 4.2.8 + dev: true + + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: true + + /stop-iteration-iterator@1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + dependencies: + internal-slot: 1.0.4 + dev: true + + /stream-buffers@2.2.0: + resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} + engines: {node: '>= 0.10.0'} + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: false + + /string.prototype.padend@3.1.3: + resolution: {integrity: sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: false + + /string.prototype.trim@1.2.7: + resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /string.prototype.trimend@1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + + /string.prototype.trimstart@1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: false + + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: false + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: false + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /tape@5.6.3: + resolution: {integrity: sha512-cUDDGSbyoSIpdUAqbqLI/r7i/S4BHuCB9M5j7E/LrLs/x/i4zeAJ798aqo+FGo+kr9seBZwr8AkZW6rjceyAMQ==} + hasBin: true + dependencies: + array.prototype.every: 1.1.4 + call-bind: 1.0.2 + deep-equal: 2.2.0 + defined: 1.0.1 + dotignore: 0.1.2 + for-each: 0.3.3 + get-package-type: 0.1.0 + glob: 7.2.3 + has: 1.0.3 + has-dynamic-import: 2.0.1 + inherits: 2.0.4 + is-regex: 1.1.4 + minimist: 1.2.7 + object-inspect: 1.12.3 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.4 + resolve: 2.0.0-next.4 + resumer: 0.0.0 + string.prototype.trim: 1.2.7 + through: 2.3.8 + dev: true + + /tar@6.1.11: + resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} + engines: {node: '>= 10'} + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 3.3.4 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + dev: false + + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: true + + /tough-cookie@2.5.0: + resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} + engines: {node: '>=0.8'} + dependencies: + psl: 1.8.0 + punycode: 2.1.1 + dev: true + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + dev: true + + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: true + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.10 + + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + /universal-user-agent@6.0.0: + resolution: {integrity: sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==} + + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + dev: true + + /url@0.10.3: + resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==} + dependencies: + punycode: 1.3.2 + querystring: 0.2.0 + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /util@0.12.4: + resolution: {integrity: sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==} + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.10 + safe-buffer: 5.2.1 + which-typed-array: 1.1.9 + dev: true + + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: true + + /uuid@3.3.2: + resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: true + + /uuid@7.0.3: + resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + hasBin: true + dev: true + + /uuid@8.0.0: + resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==} + hasBin: true + dev: true + + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: false + + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: true + + /verror@1.10.0: + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} + engines: {'0': node >=0.6.0} + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + dev: true + + /vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + dev: true + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + /which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + + /which-typed-array@1.1.9: + resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + is-typed-array: 1.1.10 + + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: false + + /wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + dependencies: + string-width: 4.2.3 + dev: false + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /xcode@3.0.1: + resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} + engines: {node: '>=10.0.0'} + dependencies: + simple-plist: 1.3.1 + uuid: 7.0.3 + dev: true + + /xml2js@0.4.19: + resolution: {integrity: sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==} + dependencies: + sax: 1.2.4 + xmlbuilder: 9.0.7 + dev: true + + /xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + dev: true + + /xmlbuilder@9.0.7: + resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==} + engines: {node: '>=4.0'} + dev: true + + /yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index d2a23f60de5..3c9f67351c9 100755 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -4,7 +4,6 @@ const { ArgumentParser } = require("argparse"); const path = require('path'); const fs = require('fs'); -const ejs = require('ejs'); const spec = require('./style-spec'); const colorParser = require('csscolorparser'); @@ -222,10 +221,13 @@ global.defaultValue = function (property) { } }; -const layerHpp = ejs.compile(fs.readFileSync(`include/mbgl/style/layers/layer.hpp.ejs`, 'utf8'), {strict: true}); -const layerCpp = ejs.compile(fs.readFileSync(`src/mbgl/style/layers/layer.cpp.ejs`, 'utf8'), {strict: true}); -const propertiesHpp = ejs.compile(fs.readFileSync(`src/mbgl/style/layers/layer_properties.hpp.ejs`, 'utf8'), {strict: true}); -const propertiesCpp = ejs.compile(fs.readFileSync(`src/mbgl/style/layers/layer_properties.cpp.ejs`, 'utf8'), {strict: true}); +console.log("Generating style code..."); +const root = args.root ? args.root : path.dirname(__dirname); + +const layerHpp = readAndCompile(`include/mbgl/style/layers/layer.hpp.ejs`, root); +const layerCpp = readAndCompile(`src/mbgl/style/layers/layer.cpp.ejs`, root); +const propertiesHpp = readAndCompile(`src/mbgl/style/layers/layer_properties.hpp.ejs`, root); +const propertiesCpp = readAndCompile(`src/mbgl/style/layers/layer_properties.cpp.ejs`, root); const collator = new Intl.Collator("en-US"); @@ -273,16 +275,16 @@ const layers = Object.keys(spec.layer.type.values).map((type) => { for (const layer of layers) { const layerFileName = layer.type.replace('-', '_'); - writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.hpp`, propertiesHpp(layer), args.root); - writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.cpp`, propertiesCpp(layer), args.root); + writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.hpp`, propertiesHpp(layer), root); + writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.cpp`, propertiesCpp(layer), root); // Remove our fake property for the external interace. if (layer.type === 'line') { layer.paintProperties = layer.paintProperties.filter(property => property.name !== 'line-floor-width'); } - writeIfModified(`include/mbgl/style/layers/${layerFileName}_layer.hpp`, layerHpp(layer), args.root); - writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer.cpp`, layerCpp(layer), args.root); + writeIfModified(`include/mbgl/style/layers/${layerFileName}_layer.hpp`, layerHpp(layer), root); + writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer.cpp`, layerCpp(layer), root); } // Light @@ -298,7 +300,7 @@ const lightProperties = Object.keys(spec[`light`]).reduce((memo, name) => { // to get a deterministic order. lightProperties.sort((a, b) => collator.compare(a.name, b.name)); -const lightHpp = ejs.compile(fs.readFileSync(`include/mbgl/style/light.hpp.ejs`, 'utf8'), {strict: true}); -const lightCpp = ejs.compile(fs.readFileSync(`src/mbgl/style/light.cpp.ejs`, 'utf8'), {strict: true}); -writeIfModified(`include/mbgl/style/light.hpp`, lightHpp({properties: lightProperties}), args.root); -writeIfModified(`src/mbgl/style/light.cpp`, lightCpp({properties: lightProperties}), args.root); +const lightHpp = readAndCompile(`include/mbgl/style/light.hpp.ejs`, root); +const lightCpp = readAndCompile(`src/mbgl/style/light.cpp.ejs`, root); +writeIfModified(`include/mbgl/style/light.hpp`, lightHpp({properties: lightProperties}), root); +writeIfModified(`src/mbgl/style/light.cpp`, lightCpp({properties: lightProperties}), root); diff --git a/scripts/style-code.js b/scripts/style-code.js index fd6bf024b1a..c3c92dc0df6 100644 --- a/scripts/style-code.js +++ b/scripts/style-code.js @@ -1,5 +1,6 @@ // Global functions // +const ejs = require('ejs'); const fs = require('fs'); const path = require('path'); @@ -59,3 +60,11 @@ global.writeIfModified = function(filename, newContent, output) { } console.warn(`* Updating outdated file '${filename}'`); }; + +global.readAndCompile = function(filename, root) { + if (root) { + filename = path.resolve(path.join(root, filename)); + } + + return ejs.compile(fs.readFileSync(filename, 'utf8'), {strict: true}); +} diff --git a/shaders/generate_shader_code.js b/shaders/generate_shader_code.js index cee954bae9d..3b7d3914ba8 100644 --- a/shaders/generate_shader_code.js +++ b/shaders/generate_shader_code.js @@ -204,7 +204,7 @@ const args = (() => { }); parser.add_argument("--root", "--r", { help: "Directory root to place generated code", - required: true + required: false }); parser.add_argument("--compress", "--c", { help: "Compress shader text with zlib and output byte arrays instead of strings", @@ -220,8 +220,9 @@ const args = (() => { // Generate shader source headers -const shaderRoot = "shaders/"; -const outputRoot = path.join((args.root ? args.root : ""), "include/mbgl/shaders"); +const root = args.root ? args.root : path.dirname(__dirname); +const shaderRoot = path.join(root, "shaders"); +const outputRoot = path.join(root, "include/mbgl/shaders"); let generatedHeaders = []; let shaderNames = []; @@ -319,4 +320,4 @@ struct ShaderSource { } // namespace mbgl `); -console.log("Shaders generated!"); \ No newline at end of file +console.log("Shaders generated!"); From 6cd4a976d64238ec34c38472f8237b1cc6c48c0b Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Thu, 8 Feb 2024 15:40:16 -0500 Subject: [PATCH 59/96] [bazel] Add missing platforms dep (#2096) --- MODULE.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/MODULE.bazel b/MODULE.bazel index 15e66665694..cbb45f2ca57 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,6 +3,7 @@ module(name = "maplibre") bazel_dep(name = "apple_support", version = "1.11.1", repo_name = "build_bazel_apple_support") bazel_dep(name = "aspect_rules_js", version = "1.34.1") bazel_dep(name = "bazel_skylib", version = "1.5.0") +bazel_dep(name = "platforms", version = "0.0.8") bazel_dep(name = "rules_apple", version = "3.1.1", repo_name = "build_bazel_rules_apple") bazel_dep(name = "rules_swift", version = "1.13.0", repo_name = "build_bazel_rules_swift") bazel_dep(name = "rules_xcodeproj", version = "1.13.0") From 8df3d33d8c448d14a1df7f283b8284c34ace840a Mon Sep 17 00:00:00 2001 From: JannikGM <72194488+JannikGM@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:07:41 +0100 Subject: [PATCH 60/96] Run platform CI on platform branches (#2095) --- .github/workflows/android-ci.yml | 1 + .github/workflows/ios-ci.yml | 1 + .github/workflows/linux-ci.yml | 3 +++ .github/workflows/macos-ci.yml | 1 + .github/workflows/node-ci.yml | 1 + .github/workflows/qt-ci.yml | 1 + 6 files changed, 8 insertions(+) diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index fc6837c8830..e8b1e9a2ec2 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - android-*.*.x tags: - "android-*" diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 85efe537469..f0591df1b3d 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -14,6 +14,7 @@ on: push: branches: - main + - ios-*.*.x tags: - 'ios-*' diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 69269a943ea..c09363d297a 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -5,6 +5,9 @@ on: push: branches: - main + - linux-*.*.x + tags: + - linux-* pull_request: branches: diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 4651ce8e736..f942d35d9c1 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -5,6 +5,7 @@ on: push: branches: - opengl-2 + - macos-*.*.x tags: - 'macos-*' paths: diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 665dc4fe033..167c1be9786 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -6,6 +6,7 @@ on: branches: - main - topic/drawable + - node-*.*.x tags: - "node-*" paths: diff --git a/.github/workflows/qt-ci.yml b/.github/workflows/qt-ci.yml index e812c19b11f..284b0e558fb 100644 --- a/.github/workflows/qt-ci.yml +++ b/.github/workflows/qt-ci.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - qt-*.*.x tags: - "qt-**" paths: From 9ec8afff9de1cd17b1540edba99963f7aba48bab Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 12 Feb 2024 12:14:33 +0100 Subject: [PATCH 61/96] Upload new external data package for every benchmark run (#2100) --- .github/workflows/android-device-test.yml | 16 ++++--- .../upload-external-data.sh | 43 +++++++++++++++++++ 2 files changed, 52 insertions(+), 7 deletions(-) create mode 100755 .github/workflows/android-device-test/upload-external-data.sh diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index 0967a6cff5b..2df097dbff5 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -26,12 +26,6 @@ jobs: appFile: "MapboxGLAndroidSDKTestApp-drawable-release.apk", name: "Android Benchmark", testFilter: "org.maplibre.android.benchmark.Benchmark", - # echo '{"styleNames": [...], "styleURLs": [...], "resultsAPI: "..." }' > benchmark-input.json - # zip benchmark-input.zip benchmark-input.json - # aws devicefarm create-upload --project-arn --type EXTERNAL_DATA --name benchmark-input.zip - # curl -T benchmark-input.zip - # aws devicefarm get-upload - externalData: "arn:aws:devicefarm:us-west-2:373521797162:upload:20687d72-0e46-403e-8f03-0941850665bc/c27174c2-63f4-4cdb-9af9-68957d75ebed", # top devices, query with `aws list-device-pools --arn ` devicePool: "arn:aws:devicefarm:us-west-2::devicepool:082d10e5-d7d7-48a5-ba5c-b33d66efa1f5", # benchmark-android.yaml @@ -83,6 +77,14 @@ jobs: with: files: "${{ matrix.test.testFile }}, ${{ matrix.test.appFile }}" + - name: Upload external data for benchmark + if: matrix.test.name == 'Android Benchmark' + run: | + export RESULTS_API=${{ secrets.MLN_RESULTS_API }} + export AWS_DEVICE_FARM_PROJECT_ARN=${{ vars.AWS_DEVICE_FARM_PROJECT_ARN }} + upload_arn="$(.github/workflows/android-device-test/upload-external-data.sh)" + echo external_data_arn="$upload_arn" >> "$GITHUB_ENV" + - uses: ./.github/actions/aws-device-farm-run if: steps.check_files.outputs.files_exists == 'true' && (matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id) with: @@ -98,7 +100,7 @@ jobs: AWS_ROLE_TO_ASSUME: ${{ vars.AWS_ROLE_TO_ASSUME }} AWS_DEVICE_FARM_PROJECT_ARN: ${{ vars.AWS_DEVICE_FARM_PROJECT_ARN }} AWS_DEVICE_FARM_DEVICE_POOL_ARN: ${{ matrix.test.devicePool }} - externalData: ${{ matrix.test.externalData }} + externalData: ${{ env.external_data_arn }} testSpecArn: ${{ matrix.test.testSpecArn }} - uses: LouisBrunner/checks-action@v2.0.0 diff --git a/.github/workflows/android-device-test/upload-external-data.sh b/.github/workflows/android-device-test/upload-external-data.sh new file mode 100755 index 00000000000..5610e623c6c --- /dev/null +++ b/.github/workflows/android-device-test/upload-external-data.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Check if the required variables are defined +if [ -z "$RESULTS_API" ] || [ -z "$AWS_DEVICE_FARM_PROJECT_ARN" ]; then + echo "Error: Missing required variables." + usage +fi + +cd "$(mktemp -d)" + +cat << EOF > benchmark-input.json +{ + "styleNames": ["Facebook Light", "MapTiler Basic", "Americana"], + "styleURLs": [ + "https://external.xx.fbcdn.net/maps/vt/style/canterbury_1_0/?locale=en_US", + "https://api.maptiler.com/maps/basic-v2/style.json?key=get_your_own_OpIi9ZULNHzrESv6T2vL", + "https://zelonewolf.github.io/openstreetmap-americana/style.json" + ], + "resultsAPI": "$RESULTS_API" +} +EOF + +>&2 zip benchmark-input.zip benchmark-input.json +upload_json="$(aws devicefarm create-upload --project-arn "$AWS_DEVICE_FARM_PROJECT_ARN" --type EXTERNAL_DATA --name benchmark-input.zip --output json)" +upload_url="$(echo "$upload_json" | jq -r '.upload.url')" +upload_arn="$(echo "$upload_json" | jq -r '.upload.arn')" +curl -T benchmark-input.zip "$upload_url" + +retries=5 +while true; do + upload_status="$(aws devicefarm get-upload --arn "$upload_arn" --output text --query upload.status)" + >&2 echo "Upload $upload_status" + sleep 1 + if [[ "$upload_status" == "SUCCEEDED" ]]; then + break + fi + + if (( --retries == 0 )); then + exit 1 + fi +done + +echo "$upload_arn" From 1e21e1bcb78c0daca1124c20be9a12cb5f8fd877 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 12 Feb 2024 13:03:28 +0100 Subject: [PATCH 62/96] Configure AWS Credentials android-device-test (#2102) --- .github/workflows/android-device-test.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index 2df097dbff5..de3668b206b 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -77,6 +77,17 @@ jobs: with: files: "${{ matrix.test.testFile }}, ${{ matrix.test.appFile }}" + - name: Configure AWS Credentials + if: matrix.test.name == 'Android Benchmark' + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-west-2 + role-to-assume: ${{ vars.AWS_ROLE_TO_ASSUME }} + role-duration-seconds: 180 + role-session-name: MySessionName + - name: Upload external data for benchmark if: matrix.test.name == 'Android Benchmark' run: | From 6cb88bc57a8ced78ad500fd6310d373a51972ab0 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 12 Feb 2024 15:50:30 +0100 Subject: [PATCH 63/96] Fix role duration (#2103) --- .github/workflows/android-device-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index de3668b206b..7c06614c86b 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -85,7 +85,7 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-west-2 role-to-assume: ${{ vars.AWS_ROLE_TO_ASSUME }} - role-duration-seconds: 180 + role-duration-seconds: 21600 role-session-name: MySessionName - name: Upload external data for benchmark From b4d55ecc8ae94e75b710ec2080d78b2193b621b2 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 12 Feb 2024 17:18:14 +0100 Subject: [PATCH 64/96] Build DocC documentation for iOS (#2104) --- .github/workflows/ios-ci.yml | 13 ++++ platform/ios/.gitignore | 1 + platform/ios/MapLibre.docc/MapLibre.md | 83 ++++++++++++++++++++++++++ platform/ios/scripts/docc.sh | 59 ++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 platform/ios/MapLibre.docc/MapLibre.md create mode 100755 platform/ios/scripts/docc.sh diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index f0591df1b3d..b0c008c22ff 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -183,6 +183,19 @@ jobs: - if: github.event_name == 'pull_request' uses: ./.github/actions/save-pr-number + - name: Build DocC documentation main branch + if: github.ref == 'refs/heads/main' + run: | + scripts/docc.sh + + - name: Deploy DocC documentation (main) 🚀 + if: github.ref == 'refs/heads/main' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + branch: gh-pages + folder: platform/ios/build/docs + target-folder: ios/latest/ + # Make Metal XCFramework release - name: Should make release? if: github.event.inputs.release == 'full' || github.event.inputs.release == 'pre' diff --git a/platform/ios/.gitignore b/platform/ios/.gitignore index b2d1f64c9fc..14aef746c1e 100644 --- a/platform/ios/.gitignore +++ b/platform/ios/.gitignore @@ -38,6 +38,7 @@ test/fixtures/storage/assets.zip buck-out .buckd MapLibre.xcodeproj +.docc-build # Visual Studio Code .vscode diff --git a/platform/ios/MapLibre.docc/MapLibre.md b/platform/ios/MapLibre.docc/MapLibre.md new file mode 100644 index 00000000000..fee157ba388 --- /dev/null +++ b/platform/ios/MapLibre.docc/MapLibre.md @@ -0,0 +1,83 @@ +# ``MapLibre`` + +@Metadata { + @Available(iOS, introduced: "12.0") +} + +Powerful, free and open-source mapping toolkit with full control over data sources and styling. + +## Overview + +[MapLibre Native](https://github.com/maplibre/maplibre-native) is a map rendering toolkit with support for iOS. It can be used as an alternative to MapKit. You have full control over the data sources used for rendering the map, as well as the styling. You can even participate in the development as MapLibre Native is free and open-source project. +> Note: For information on creating and modifying map styles, see the [MapLibre Style Spec documentation](https://maplibre.org/maplibre-style-spec/). + +## Topics + +### Map + +- ``MLNMapView`` + +### Style Layers + +- ``MLNBackgroundStyleLayer`` +- ``MLNCircleStyleLayer`` +- ``MLNFillExtrusionStyleLayer`` +- ``MLNFillStyleLayer`` +- ``MLNForegroundStyleLayer`` +- ``MLNHeatmapStyleLayer`` +- ``MLNHillshadeStyleLayer`` +- ``MLNLineStyleLayer`` +- ``MLNRasterStyleLayer`` +- ``MLNStyleLayer`` +- ``MLNSymbolStyleLayer`` +- ``MLNVectorStyleLayer`` + +### Sources + +- ``MLNComputedShapeSource`` +- ``MLNImageSource`` +- ``MLNRasterDEMSource`` +- ``MLNRasterTileSource`` +- ``MLNShapeSource`` +- ``MLNSource`` +- ``MLNTileSource`` + +### Shapes + +- ``MLNEmptyFeature`` +- ``MLNMultiPoint`` +- ``MLNMultiPolygon`` +- ``MLNMultiPolygonFeature`` +- ``MLNMultiPolyline`` +- ``MLNMultiPolylineFeature`` +- ``MLNMultiPolylineFeature`` +- ``MLNPointAnnotation`` +- ``MLNPointCollection`` +- ``MLNPointCollectionFeature`` +- ``MLNPolygon`` +- ``MLNPolyline`` +- ``MLNPolylineFeature`` +- ``MLNShape`` +- ``MLNShapeCollection`` +- ``MLNShapeCollectionFeature`` + +### Snapshotter + +- ``MLNMapSnapshot`` +- ``MLNMapSnapshotOptions`` +- ``MLNMapSnapshotter`` + +### Offline support + +- ``MLNOfflinePack`` +- ``MLNOfflineRegion`` +- ``MLNOfflineStorage`` +- ``MLNShapeOfflineRegion`` +- ``MLNTilePyramidOfflineRegion`` + +### Annotations + +- ``MLNAnnotationImage`` +- ``MLNAnnotationView`` +- ``MLNPointFeature`` +- ``MLNPointFeatureCluster`` diff --git a/platform/ios/scripts/docc.sh b/platform/ios/scripts/docc.sh new file mode 100755 index 00000000000..9a1da58cc8d --- /dev/null +++ b/platform/ios/scripts/docc.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# This script is a bit of a hack to generate DocC documentation until Bazel has support for it. +# https://github.com/bazelbuild/rules_apple/issues/2241 +# To use this script, make sure the XCFramework is built with Bazel (see ios-ci.yml). +# Then to start a local preview, run: +# $ scripts/docc.sh bazel +# You can also build the documentation locally +# $ scripts/docc.sh +# Then go to build/ios and run +# $ python3 -m http.server +# Go to http://localhost:8000/documentation/maplibre/ + +cmd="convert" +if [[ "$1" == "preview" ]]; then + cmd="preview" +fi + +SDK_PATH=$(xcrun -sdk iphoneos --show-sdk-path) + +build_dir=build + +rm -rf "$build_dir"/symbol-graphs +rm -rf "$build_dir"/headers/MapLibre +rm -rf "$build_dir"/MapLibre.xcframework + +mkdir "$build_dir"/symbol-graphs +mkdir -p "$build_dir"/headers/MapLibre + +# unzip built XCFramework in build dir +unzip ../../bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip -d "$build_dir" + +# copy all public headers from XCFramework +cp "$build_dir"/MapLibre.xcframework/ios-arm64/MapLibre.framework/Headers/*.h "$build_dir"/headers/MapLibre + +xcrun --toolchain swift clang \ + -extract-api \ + --product-name=MapLibre \ + -isysroot $SDK_PATH \ + -F "$SDK_PATH"/System/Library/Frameworks \ + -I "$PWD" \ + -I "$(realpath ../darwin/src)" \ + -I "$build_dir"/headers \ + -x objective-c-header \ + -o "$build_dir"/symbol-graphs/MapLibre.symbols.json \ + "$build_dir"/headers/MapLibre/*.h + +export DOCC_HTML_DIR=$(dirname $(xcrun --toolchain swift --find docc))/../share/docc/render +$(xcrun --find docc) "$cmd" MapLibre.docc \ + --fallback-display-name "MapLibre Native for iOS" \ + --fallback-bundle-identifier org.swift.MyProject \ + --fallback-bundle-version 0.0.1 \ + --additional-symbol-graph-dir "$build_dir"/symbol-graphs \ + --output-path "$build_dir"/MapLibre.doccarchive + +if [[ "$cmd" == "convert" ]]; then + rm -rf build/docs + $(xcrun --find docc) process-archive transform-for-static-hosting "$build_dir"/MapLibre.doccarchive --output-path build/docs +fi \ No newline at end of file From 2cf9f75f33ef5b2b727ecbaaafeb2492bfd40a60 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 12 Feb 2024 20:47:47 +0100 Subject: [PATCH 65/96] iOS CI Fixes - Release & Docs (#2105) --- .github/workflows/ios-ci.yml | 10 +++++++--- platform/ios/scripts/docc.sh | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index b0c008c22ff..0425ac13589 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -210,7 +210,7 @@ jobs: if: github.event.inputs.release == 'full' run: | echo version="$(head VERSION)" >> "$GITHUB_ENV" - echo changelog_version_heading="## {{ env.version }}" >> "$GITHUB_ENV" + echo changelog_version_heading="## ${{ env.version }}" >> "$GITHUB_ENV" - name: Get version (pre-release) if: github.event.inputs.release == 'pre' @@ -221,9 +221,13 @@ jobs: - name: Extract changelog for version if: env.make_release run: | - awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "{{ env.changelog_version_heading }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md + awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "${{ env.changelog_version_heading }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md cat changelog_for_version.md + - name: Upload changelog to S3 + if: env.make_release + run: aws s3 cp changelog_for_version.md s3://maplibre-native/changelogs/ios-${{ env.version }}.md + - name: Create tag if: env.make_release run: | @@ -271,7 +275,7 @@ jobs: -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ -d '{"ref":"main","inputs":{ \ - "changelog": "'"$(cat changelog_for_version.md)"'", \ + "changelog_url": "https://maplibre-native.s3.eu-central-1.amazonaws.com/changelogs/ios-${{ env.version }}.md", \ "version":"${{ env.version }}", \ "download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' diff --git a/platform/ios/scripts/docc.sh b/platform/ios/scripts/docc.sh index 9a1da58cc8d..c89a74728c3 100755 --- a/platform/ios/scripts/docc.sh +++ b/platform/ios/scripts/docc.sh @@ -24,7 +24,7 @@ rm -rf "$build_dir"/symbol-graphs rm -rf "$build_dir"/headers/MapLibre rm -rf "$build_dir"/MapLibre.xcframework -mkdir "$build_dir"/symbol-graphs +mkdir -p "$build_dir"/symbol-graphs mkdir -p "$build_dir"/headers/MapLibre # unzip built XCFramework in build dir From 0ec638f226c4dfe8ad22877d2012f04bf38ec2ef Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 12 Feb 2024 23:28:44 +0100 Subject: [PATCH 66/96] Pass --hosting-base-path to docc (#2106) --- platform/ios/scripts/docc.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/ios/scripts/docc.sh b/platform/ios/scripts/docc.sh index c89a74728c3..ae9a8b389ab 100755 --- a/platform/ios/scripts/docc.sh +++ b/platform/ios/scripts/docc.sh @@ -55,5 +55,7 @@ $(xcrun --find docc) "$cmd" MapLibre.docc \ if [[ "$cmd" == "convert" ]]; then rm -rf build/docs - $(xcrun --find docc) process-archive transform-for-static-hosting "$build_dir"/MapLibre.doccarchive --output-path build/docs + $(xcrun --find docc) process-archive transform-for-static-hosting "$build_dir"/MapLibre.doccarchive \ + --hosting-base-path maplibre-native/ios/latest \ # remove for local builds + --output-path build/docs fi \ No newline at end of file From 8e5fc4c198cf2c5f11718c8c0dc0bdc98b58883f Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Tue, 13 Feb 2024 07:40:25 -0800 Subject: [PATCH 67/96] Minor optimizations (#2091) --- src/mbgl/gfx/vertex_vector.hpp | 2 ++ src/mbgl/layout/symbol_projection.cpp | 8 +++++++- src/mbgl/renderer/layers/render_background_layer.cpp | 2 -- src/mbgl/renderer/layers/render_circle_layer.cpp | 2 -- src/mbgl/renderer/layers/render_custom_drawable_layer.cpp | 2 -- src/mbgl/renderer/layers/render_custom_layer.cpp | 2 -- src/mbgl/renderer/layers/render_fill_layer.cpp | 2 -- src/mbgl/renderer/layers/render_heatmap_layer.cpp | 2 -- src/mbgl/renderer/layers/render_hillshade_layer.cpp | 2 -- src/mbgl/renderer/layers/render_line_layer.cpp | 2 -- src/mbgl/renderer/layers/render_raster_layer.cpp | 2 -- src/mbgl/renderer/render_layer.hpp | 2 -- 12 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/mbgl/gfx/vertex_vector.hpp b/src/mbgl/gfx/vertex_vector.hpp index 94d26a13c6c..3be534149c4 100644 --- a/src/mbgl/gfx/vertex_vector.hpp +++ b/src/mbgl/gfx/vertex_vector.hpp @@ -98,6 +98,8 @@ class VertexVector final : public VertexVectorBase { v.clear(); } + void reserve(std::size_t count) { v.reserve(count); } + /// Indicate that this shared vertex vector instance will no longer be updated. void release() { #if MLN_DRAWABLE_RENDERER diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp index c8b6816f7b2..57ab0924278 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -146,6 +146,9 @@ void addDynamicAttributes(const Point& anchorPoint, void hideGlyphs(size_t numGlyphs, gfx::VertexVector>& dynamicVertexArray) { const Point offscreenPoint = {-INFINITY, -INFINITY}; + if (dynamicVertexArray.empty()) { + dynamicVertexArray.reserve(4 * numGlyphs); + } for (size_t i = 0; i < numGlyphs; i++) { addDynamicAttributes(offscreenPoint, 0, dynamicVertexArray); } @@ -270,7 +273,6 @@ std::optional> placeFirstAndLastGlyph(const const float firstGlyphOffset = symbol.glyphOffsets.front(); const float lastGlyphOffset = symbol.glyphOffsets.back(); - ; std::optional firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, @@ -365,6 +367,7 @@ PlacementResult placeGlyphsAlongLine(const PlacedSymbol& symbol, } } + placedGlyphs.reserve(symbol.glyphOffsets.size()); placedGlyphs.push_back(firstAndLastGlyph->first); for (size_t glyphIndex = 1; glyphIndex < symbol.glyphOffsets.size() - 1; glyphIndex++) { const float glyphOffsetX = symbol.glyphOffsets[glyphIndex]; @@ -429,6 +432,9 @@ PlacementResult placeGlyphsAlongLine(const PlacedSymbol& symbol, // The number of placedGlyphs must equal the number of glyphOffsets, which // must correspond to the number of glyph vertices There may be 0 glyphs // here, if a label consists entirely of glyphs that have 0x0 dimensions + if (dynamicVertexArray.empty()) { + dynamicVertexArray.reserve(4 * placedGlyphs.size()); + } for (auto& placedGlyph : placedGlyphs) { addDynamicAttributes(placedGlyph.point, placedGlyph.angle, dynamicVertexArray); } diff --git a/src/mbgl/renderer/layers/render_background_layer.cpp b/src/mbgl/renderer/layers/render_background_layer.cpp index 437c92dcefb..661a9e186c8 100644 --- a/src/mbgl/renderer/layers/render_background_layer.cpp +++ b/src/mbgl/renderer/layers/render_background_layer.cpp @@ -215,8 +215,6 @@ void RenderBackgroundLayer::update(gfx::ShaderRegistry& shaders, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - const auto zoom = state.getIntegerZoom(); const auto tileCover = util::tileCover(state, zoom); diff --git a/src/mbgl/renderer/layers/render_circle_layer.cpp b/src/mbgl/renderer/layers/render_circle_layer.cpp index 9a11ca91993..fe10a003d9a 100644 --- a/src/mbgl/renderer/layers/render_circle_layer.cpp +++ b/src/mbgl/renderer/layers/render_circle_layer.cpp @@ -276,8 +276,6 @@ void RenderCircleLayer::update(gfx::ShaderRegistry& shaders, [[maybe_unused]] const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - if (!renderTiles || renderTiles->empty()) { removeAllDrawables(); return; diff --git a/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp b/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp index c6f22075692..4b111e90308 100644 --- a/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp +++ b/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp @@ -65,8 +65,6 @@ void RenderCustomDrawableLayer::update(gfx::ShaderRegistry& shaders, const std::shared_ptr& updateParameters, const RenderTree& renderTree, UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - // check if host changed and update bool hostChanged = (host != impl(baseImpl).host); if (hostChanged) { diff --git a/src/mbgl/renderer/layers/render_custom_layer.cpp b/src/mbgl/renderer/layers/render_custom_layer.cpp index 16cba64f05c..39647a08ff7 100644 --- a/src/mbgl/renderer/layers/render_custom_layer.cpp +++ b/src/mbgl/renderer/layers/render_custom_layer.cpp @@ -125,8 +125,6 @@ void RenderCustomLayer::update([[maybe_unused]] gfx::ShaderRegistry& shaders, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - // create layer group if (!layerGroup) { if (auto layerGroup_ = context.createLayerGroup(layerIndex, /*initialCapacity=*/1, getID())) { diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index 58367aac241..315f3ac401b 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -400,8 +400,6 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - if (!renderTiles || renderTiles->empty()) { removeAllDrawables(); return; diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp index 20413c70886..0541bd67a6e 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.cpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -292,8 +292,6 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - if (!renderTiles || renderTiles->empty()) { removeAllDrawables(); return; diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index 5d3151e7ed6..2eb12988083 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -304,8 +304,6 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - if (!renderTiles || renderTiles->empty()) { removeAllDrawables(); return; diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index eecbfe52c5b..6d21a77f21b 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -353,8 +353,6 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - if (!renderTiles || renderTiles->empty()) { removeAllDrawables(); return; diff --git a/src/mbgl/renderer/layers/render_raster_layer.cpp b/src/mbgl/renderer/layers/render_raster_layer.cpp index 51f1920aa37..58d043c70be 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.cpp +++ b/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -256,8 +256,6 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, const std::shared_ptr&, [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] UniqueChangeRequestVec& changes) { - std::unique_lock guard(mutex); - if ((!renderTiles || renderTiles->empty()) && !imageData) { if (layerGroup) { stats.drawablesRemoved += layerGroup->clearDrawables(); diff --git a/src/mbgl/renderer/render_layer.hpp b/src/mbgl/renderer/render_layer.hpp index ba1e06cdc41..84fb70d2257 100644 --- a/src/mbgl/renderer/render_layer.hpp +++ b/src/mbgl/renderer/render_layer.hpp @@ -302,8 +302,6 @@ class RenderLayer { // Current renderable status as specified by the markLayerRenderable event bool isRenderable{false}; - std::mutex mutex; - struct Stats { size_t propertyEvaluations = 0; size_t drawablesAdded = 0; From 17e6e7cdcb47c5f384aa181d79481f9084cab879 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 14 Feb 2024 11:09:22 +0100 Subject: [PATCH 68/96] DocC Improvements (#2111) --- .github/workflows/ios-ci.yml | 8 +- platform/ios/BUILD.bazel | 16 +-- .../ios/Documentation.docc/Documentation.md | 5 - platform/ios/MapLibre.docc/MapLibre.md | 4 + .../ios/MapLibre.docc/theme-settings.json | 20 ++++ platform/ios/bazel/files.bzl | 1 + platform/ios/scripts/docc.sh | 53 ++++++--- platform/ios/src/MLNMapView.h | 103 ++++-------------- .../ios/src/{Mapbox.template.h => Mapbox.h} | 5 - 9 files changed, 94 insertions(+), 121 deletions(-) delete mode 100644 platform/ios/Documentation.docc/Documentation.md create mode 100644 platform/ios/MapLibre.docc/theme-settings.json rename platform/ios/src/{Mapbox.template.h => Mapbox.h} (94%) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 0425ac13589..73fc3991f13 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -183,17 +183,17 @@ jobs: - if: github.event_name == 'pull_request' uses: ./.github/actions/save-pr-number - - name: Build DocC documentation main branch - if: github.ref == 'refs/heads/main' + - name: Build DocC documentation + working-directory: . run: | - scripts/docc.sh + HOSTING_BASE_PATH="maplibre-native/ios/latest" platform/ios/scripts/docc.sh - name: Deploy DocC documentation (main) 🚀 if: github.ref == 'refs/heads/main' uses: JamesIves/github-pages-deploy-action@v4.5.0 with: branch: gh-pages - folder: platform/ios/build/docs + folder: build/docs target-folder: ios/latest/ # Make Metal XCFramework release diff --git a/platform/ios/BUILD.bazel b/platform/ios/BUILD.bazel index 15a7fe0d15b..b0e7f8b9f49 100644 --- a/platform/ios/BUILD.bazel +++ b/platform/ios/BUILD.bazel @@ -31,23 +31,9 @@ filegroup( visibility = ["//visibility:public"], ) -# this can be removed once the legacy renderer is removed completely for iOS -genrule( - name = "umbrella_header", - srcs = ["src/Mapbox.template.h"], - outs = ["src/Mapbox.h"], - cmd = select({ - "//:metal_renderer": """ - echo "#define MLN_RENDER_BACKEND_METAL 1" > $@; - cat $(location src/Mapbox.template.h) >> $@; - """, - "//conditions:default": "cat $(location src/Mapbox.template.h) >> $@", - }), -) - filegroup( name = "ios_public_hdrs", - srcs = MLN_IOS_PUBLIC_HEADERS + [":umbrella_header"], + srcs = MLN_IOS_PUBLIC_HEADERS, visibility = ["//visibility:public"], ) diff --git a/platform/ios/Documentation.docc/Documentation.md b/platform/ios/Documentation.docc/Documentation.md deleted file mode 100644 index 89692a214b4..00000000000 --- a/platform/ios/Documentation.docc/Documentation.md +++ /dev/null @@ -1,5 +0,0 @@ -# ``Mapbox`` - -@Metadata { - @DisplayName("MapLibre Native") -} diff --git a/platform/ios/MapLibre.docc/MapLibre.md b/platform/ios/MapLibre.docc/MapLibre.md index fee157ba388..3be22bc0a3c 100644 --- a/platform/ios/MapLibre.docc/MapLibre.md +++ b/platform/ios/MapLibre.docc/MapLibre.md @@ -15,7 +15,11 @@ Powerful, free and open-source mapping toolkit with full control over data sourc ### Map +- ``MLNSettings`` +- ``MLNMapCamera`` +- ``MLNMapViewDelegate`` - ``MLNMapView`` +- ``MLNUserTrackingMode`` ### Style Layers diff --git a/platform/ios/MapLibre.docc/theme-settings.json b/platform/ios/MapLibre.docc/theme-settings.json new file mode 100644 index 00000000000..29f7ee944a8 --- /dev/null +++ b/platform/ios/MapLibre.docc/theme-settings.json @@ -0,0 +1,20 @@ +{ + "theme": { + "color": { + "documentation-intro-fill": "#111725", + "documentation-intro-accent": "#1058c0", + "link": { + "light": "#1058c0", + "dark": "#82b4fe" + }, + "button-background": { + "light": "#1058c0", + "dark": "#82b4fe" + }, + "fill-blue": { + "light": "#1058c0", + "dark": "#82b4fe" + } + } + } +} diff --git a/platform/ios/bazel/files.bzl b/platform/ios/bazel/files.bzl index fb9ad10976e..3e88047b478 100644 --- a/platform/ios/bazel/files.bzl +++ b/platform/ios/bazel/files.bzl @@ -14,6 +14,7 @@ MLN_IOS_SDK_HEADERS = [ ] MLN_IOS_PUBLIC_HEADERS = [ + "src/Mapbox.h", ] MLN_IOS_PRIVATE_HEADERS = [ diff --git a/platform/ios/scripts/docc.sh b/platform/ios/scripts/docc.sh index ae9a8b389ab..4c8ef3dc8f7 100755 --- a/platform/ios/scripts/docc.sh +++ b/platform/ios/scripts/docc.sh @@ -11,6 +11,9 @@ # $ python3 -m http.server # Go to http://localhost:8000/documentation/maplibre/ +set -e +shopt -s extglob + cmd="convert" if [[ "$1" == "preview" ]]; then cmd="preview" @@ -21,41 +24,65 @@ SDK_PATH=$(xcrun -sdk iphoneos --show-sdk-path) build_dir=build rm -rf "$build_dir"/symbol-graphs -rm -rf "$build_dir"/headers/MapLibre -rm -rf "$build_dir"/MapLibre.xcframework +rm -rf "$build_dir"/headers mkdir -p "$build_dir"/symbol-graphs -mkdir -p "$build_dir"/headers/MapLibre +mkdir -p "$build_dir"/headers + +bazel build --//:renderer=metal //platform/darwin:generated_style_public_hdrs + +public_headers=$(bazel query 'kind("source file", deps(//platform:ios-sdk, 2))' --output location | grep ".h$" | sed -r 's#.*/([^:]+).*#\1#') +style_headers=$(bazel cquery --//:renderer=metal //platform/darwin:generated_style_public_hdrs --output=files) + +cp $style_headers "$build_dir"/headers + +filter_filenames() { + local prefix="$1" + local filenames="$2" + local filtered_filenames="" + + for filename in $filenames; do + local prefixed_filename="$prefix/$filename" + + if [ -f "$prefixed_filename" ]; then + filtered_filenames="$filtered_filenames $prefixed_filename" + fi + done -# unzip built XCFramework in build dir -unzip ../../bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip -d "$build_dir" + echo "$filtered_filenames" +} -# copy all public headers from XCFramework -cp "$build_dir"/MapLibre.xcframework/ios-arm64/MapLibre.framework/Headers/*.h "$build_dir"/headers/MapLibre +ios_headers=$(filter_filenames "platform/ios/src" "$public_headers") +darwin_headers=$(filter_filenames "platform/darwin/src" "$public_headers") -xcrun --toolchain swift clang \ +for header in $ios_headers $darwin_headers $style_headers; do + xcrun --toolchain swift clang \ -extract-api \ --product-name=MapLibre \ -isysroot $SDK_PATH \ -F "$SDK_PATH"/System/Library/Frameworks \ -I "$PWD" \ - -I "$(realpath ../darwin/src)" \ -I "$build_dir"/headers \ + -I platform/darwin/src \ -x objective-c-header \ - -o "$build_dir"/symbol-graphs/MapLibre.symbols.json \ - "$build_dir"/headers/MapLibre/*.h + -o "$build_dir"/symbol-graphs/$(basename $header).symbols.json \ + $header +done export DOCC_HTML_DIR=$(dirname $(xcrun --toolchain swift --find docc))/../share/docc/render -$(xcrun --find docc) "$cmd" MapLibre.docc \ +$(xcrun --find docc) "$cmd" platform/ios/MapLibre.docc \ --fallback-display-name "MapLibre Native for iOS" \ --fallback-bundle-identifier org.swift.MyProject \ --fallback-bundle-version 0.0.1 \ --additional-symbol-graph-dir "$build_dir"/symbol-graphs \ + --source-service github \ + --source-service-base-url https://github.com/maplibre/maplibre-native/blob/main \ + --checkout-path $(realpath .) \ --output-path "$build_dir"/MapLibre.doccarchive if [[ "$cmd" == "convert" ]]; then rm -rf build/docs $(xcrun --find docc) process-archive transform-for-static-hosting "$build_dir"/MapLibre.doccarchive \ - --hosting-base-path maplibre-native/ios/latest \ # remove for local builds + ${HOSTING_BASE_PATH:+--hosting-base-path "$HOSTING_BASE_PATH"} \ --output-path build/docs fi \ No newline at end of file diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index d0daffcca28..292cc6e2edc 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -72,8 +72,7 @@ typedef NS_ENUM(NSUInteger, MLNOrnamentPosition) { `MLNMapView.userTrackingMode`. #### Related examples - See the - Switch between user tracking modes example to learn how to toggle modes and + - TODO: Switch between user tracking modes example to learn how to toggle modes and how each mode behaves. */ typedef NS_ENUM(NSUInteger, MLNUserTrackingMode) { @@ -145,22 +144,9 @@ FOUNDATION_EXTERN MLN_EXPORT MLNExceptionName const MLNUserLocationAnnotationTyp Mapbox Vector Tile Specification. It styles them with a style that conforms to the MapLibre Style Spec. - Such styles can be designed in - Mapbox Studio and hosted on - mapbox.com. - - A collection of Mapbox-hosted styles is available through the `MLNStyle` - class. These basic styles use - Mapbox Streets - or Mapbox Satellite data - sources, but you can specify a custom style that makes use of your own data. - - Mapbox-hosted vector tiles and styles require an API access token, which you - can obtain from the - Mapbox account page. - Access tokens associate requests to Mapbox’s vector tile and style APIs with - your Mapbox account. They also deter other developers from using your styles - without your permission. + Such styles can be designed with + Maputnik. + Because `MLNMapView` loads asynchronously, several delegate methods are available for receiving map-related updates. These methods can be used to ensure that certain operations @@ -183,10 +169,6 @@ FOUNDATION_EXTERN MLN_EXPORT MLNExceptionName const MLNUserLocationAnnotationTyp @note You are responsible for getting permission to use the map data and for ensuring that your use adheres to the relevant terms of use. - - #### Related examples - See the - Simple map view example to learn how to initialize a basic `MLNMapView`. */ MLN_EXPORT @interface MLNMapView : UIView @@ -213,15 +195,9 @@ MLN_EXPORT @return An initialized map view. #### Related examples - See the - Apply a style designed in Mapbox Studio example to learn how to - initialize an `MLNMapView` with a custom style. See the - Apply a - style designed in Mapbox Studio Classic example to learn how to intialize - an `MLNMapView` with a Studio Classic style _or_ a custom style JSON. See the - Use - third-party vector tiles example to learn how to initialize an - `MLNMapView` with a third-party tile source. + + - TODO: initialize an `MLNMapView` with a custom style + - TODO: how to initialize an `MLNMapView` with a third-party tile source */ - (instancetype)initWithFrame:(CGRect)frame styleURL:(nullable NSURL *)styleURL; @@ -251,13 +227,6 @@ MLN_EXPORT `-[MLNMapViewDelegate mapView:didFinishLoadingStyle:]` or `-[MLNMapViewDelegate mapViewDidFinishLoadingMap:]` method. It is not possible to manipulate the style before it has finished loading. - - @note The default styles provided by Mapbox contain sources and layers with - identifiers that will change over time. Applications that use APIs that - manipulate a style’s sources and layers must first set the style URL to an - explicitly versioned style using a convenience method like - `+[MLNStyle outdoorsStyleURLWithVersion:]`, `MLNMapView`’s “Style URL” - inspectable in Interface Builder, or a manually constructed `NSURL`. */ @property (nonatomic, readonly, nullable) MLNStyle *style; @@ -274,9 +243,7 @@ MLN_EXPORT you want to introspect individual style attributes, use the `style` property. #### Related examples - See the - Switch between map styles example to learn how to change the style of - a map at runtime. + - TODO: change the style of a map at runtime. */ @property (nonatomic, null_resettable) NSURL *styleURL; @@ -284,9 +251,7 @@ MLN_EXPORT Reloads the style. You do not normally need to call this method. The map view automatically - responds to changes in network connectivity by reloading the style. You may - need to call this method if you change the access token after a style has - loaded but before loading a style associated with a different Mapbox account. + responds to changes in network connectivity by reloading the style. This method does not bust the cache. Even if the style has recently changed on the server, calling this method does not necessarily ensure that the map view @@ -364,13 +329,8 @@ MLN_EXPORT @property (nonatomic, assign) CGPoint compassViewMargins; /** - The Mapbox wordmark, positioned in the lower-left corner. - - @note The Mapbox terms of service, which governs the use of Mapbox-hosted - vector tiles and styles, - requires most Mapbox - customers to display the Mapbox wordmark. If this applies to you, do not - hide this view or change its contents. + A logo, the MapLibre logo by default, positioned in the lower-left corner. + You are not required to display this, but some vector-sources may require attribution. */ @property (nonatomic, readonly) UIImageView *logoView; @@ -392,11 +352,8 @@ MLN_EXPORT If you choose to reimplement this view, assign the `-showAttribution:` method as the action for your view to present the default notices and settings. - @note The Mapbox terms of service, which governs the use of Mapbox-hosted - vector tiles and styles, - requires these - copyright notices to accompany any map that features Mapbox-designed styles, - OpenStreetMap data, or other Mapbox data such as satellite or terrain + @note Attribution is often required for many vector sources, + OpenStreetMap data, or other data such as satellite or terrain data. If that applies to this map view, do not hide this view or remove any notices from it. @@ -514,8 +471,7 @@ MLN_EXPORT `-setUserTrackingMode:animated:` method instead. #### Related examples - See the - Customize the user location annotation to learn how to customize the + - TODO: Customize the user location annotation and learn how to customize the default user location annotation shown by `MLNUserTrackingMode`. */ @property (nonatomic, assign) MLNUserTrackingMode userTrackingMode; @@ -1143,9 +1099,7 @@ MLN_EXPORT immediately. #### Related examples - See the - Camera animation example to learn how to trigger an animation that - rotates around a central point. + - TODO: Camera animation: learn how to trigger an animation that rotates around a central point. */ - (void)setCamera:(MLNMapCamera *)camera animated:(BOOL)animated; @@ -1163,8 +1117,7 @@ MLN_EXPORT is `0`, this parameter is ignored. #### Related examples - See the - Camera animation example to learn how to create a timed animation that + - TODO: Camera animation: learn how to create a timed animation that rotates around a central point for a specific duration. */ - (void)setCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function; @@ -1483,9 +1436,7 @@ MLN_EXPORT @return The geographic coordinate at the given point. #### Related examples - See the - Point conversion example to learn how to convert a `CGPoint` to a map - coordinate. + - TODO: Point conversion example to learn how to convert a `CGPoint` to a map coordinate. */ - (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view; @@ -1502,9 +1453,7 @@ MLN_EXPORT corresponding to the given geographic coordinate. #### Related examples - See the - Point conversion example to learn how to convert a map coordinate to a - `CGPoint` object. + - TODO: Point conversion: learn how to convert a map coordinate to a `CGPoint` object. */ - (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable UIView *)view; @@ -1583,10 +1532,8 @@ MLN_EXPORT annotation object. #### Related examples - See the - Annotation models and - Add a line annotation from GeoJSON examples to learn how to add an - annotation to an `MLNMapView` object. + - TODO: add a line annotation from GeoJSON. + - TODO: add an annotation to an `MLNMapView` object. */ - (void)addAnnotation:(id )annotation; @@ -1654,8 +1601,7 @@ MLN_EXPORT such object exists in the reuse queue. #### Related examples - See the - Add annotation views and images example learn how to most efficiently + - TODO: Add annotation views and images: learn how to most efficiently reuse an `MLNAnnotationImage`. */ - (nullable __kindof MLNAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier; @@ -1847,8 +1793,7 @@ MLN_EXPORT represent features in the sources used by the current style. #### Related examples - See the - Select a feature within a layer example to learn how to query an + - TODO: Select a feature within a layer: to learn how to query an `MLNMapView` object for visible `MLNFeature` objects. */ - (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point NS_SWIFT_NAME(visibleFeatures(at:)); @@ -1909,7 +1854,7 @@ MLN_EXPORT point, even if the road extends into other tiles. To find out the layer names in a particular style, view the style in - Mapbox Studio. + Maputnik. Only visible features are returned. To obtain features regardless of visibility, use the @@ -2012,7 +1957,7 @@ MLN_EXPORT the road within each map tile is included individually. To find out the layer names in a particular style, view the style in - Mapbox Studio. + Maputnik. Only visible features are returned. To obtain features regardless of visibility, use the diff --git a/platform/ios/src/Mapbox.template.h b/platform/ios/src/Mapbox.h similarity index 94% rename from platform/ios/src/Mapbox.template.h rename to platform/ios/src/Mapbox.h index bd74eb2ceca..076b7f6f95d 100644 --- a/platform/ios/src/Mapbox.template.h +++ b/platform/ios/src/Mapbox.h @@ -46,9 +46,6 @@ FOUNDATION_EXPORT MLN_EXPORT const unsigned char MapboxVersionString[]; #import "MLNOfflinePack.h" #import "MLNOfflineRegion.h" #import "MLNOfflineStorage.h" -#if !MLN_RENDER_BACKEND_METAL -#import "MLNOpenGLStyleLayer.h" -#endif #import "MLNOverlay.h" #import "MLNPointAnnotation.h" #import "MLNPointCollection.h" @@ -77,6 +74,4 @@ FOUNDATION_EXPORT MLN_EXPORT const unsigned char MapboxVersionString[]; #import "NSPredicate+MLNAdditions.h" #import "NSValue+MLNAdditions.h" #import "MLNUserLocationAnnotationViewStyle.h" -#if defined(MLN_DRAWABLE_RENDERER) || defined(MLN_RENDER_BACKEND_METAL) #import "MLNCustomDrawableStyleLayer.h" -#endif From 1d00e5b9eafd3f52052549190ba17634f03507ed Mon Sep 17 00:00:00 2001 From: Stefan Karschti Date: Thu, 15 Feb 2024 18:43:40 +0200 Subject: [PATCH 69/96] MLNCustomStyleLayer + Metal example (#2041) --- CMakeLists.txt | 3 + bazel/core.bzl | 4 + include/mbgl/gfx/drawable_atlases_tweaker.hpp | 3 +- .../drawable_custom_layer_host_tweaker.hpp | 2 + include/mbgl/gl/drawable_gl.hpp | 1 + include/mbgl/gl/layer_group_gl.hpp | 3 + include/mbgl/mtl/drawable.hpp | 1 + include/mbgl/mtl/layer_group.hpp | 3 + include/mbgl/mtl/tile_layer_group.hpp | 3 + include/mbgl/style/layers/custom_layer.hpp | 31 +-- .../layers/custom_layer_render_parameters.hpp | 31 +++ .../mtl/custom_layer_render_parameters.hpp | 30 +++ platform/BUILD.bazel | 17 +- platform/android/src/example_custom_layer.cpp | 1 + platform/darwin/BUILD.bazel | 22 +- platform/darwin/app/CustomStyleLayerExample.h | 5 + platform/darwin/app/CustomStyleLayerExample.m | 188 ++++++++++++++++++ platform/darwin/app/LimeGreenStyleLayer.h | 5 - platform/darwin/app/LimeGreenStyleLayer.m | 58 ------ platform/darwin/bazel/files.bzl | 10 +- platform/darwin/src/MLNBackendResource.h | 19 ++ ...enGLStyleLayer.h => MLNCustomStyleLayer.h} | 10 +- ...GLStyleLayer.mm => MLNCustomStyleLayer.mm} | 68 ++++--- ...rivate.h => MLNCustomStyleLayer_Private.h} | 2 +- platform/darwin/src/MLNStyle.mm | 8 +- platform/darwin/src/MLNStyleLayerManager.mm | 9 +- platform/darwin/src/MLNStyle_Private.h | 4 +- platform/darwin/test/MLNStyleTests.mm | 4 +- .../Integration_Tests/MBGLIntegrationTests.mm | 22 +- platform/ios/app/MBXViewController.m | 26 +-- platform/ios/scripts/bazel-package.sh | 0 platform/ios/sdk-files.json | 6 +- platform/ios/src/MLNMapView+Impl.h | 4 + platform/ios/src/MLNMapView+Metal.h | 1 + platform/ios/src/MLNMapView+Metal.mm | 17 +- platform/ios/src/MLNMapView+OpenGL.h | 1 + platform/ios/src/MLNMapView+OpenGL.mm | 4 + platform/ios/src/MLNMapView.h | 3 + platform/ios/src/MLNMapView.mm | 4 + platform/ios/src/Mapbox.h | 1 + platform/macos/app/MapDocument.m | 4 +- platform/macos/sdk-files.json | 6 +- platform/macos/src/Mapbox.h | 2 +- .../drawable_custom_layer_host_tweaker.cpp | 27 ++- src/mbgl/gl/drawable_gl.cpp | 5 + src/mbgl/mtl/drawable.cpp | 3 + src/mbgl/mtl/render_pass.cpp | 4 +- src/mbgl/mtl/upload_pass.cpp | 4 +- .../renderer/layers/render_custom_layer.cpp | 18 +- .../renderer/layers/render_fill_layer.cpp | 2 +- src/mbgl/renderer/render_tree.hpp | 2 +- src/mbgl/renderer/renderer_impl.cpp | 1 + .../style/layers/custom_drawable_layer.cpp | 1 + .../layers/custom_layer_render_parameters.cpp | 24 +++ .../mtl/custom_layer_render_parameters.cpp | 20 ++ test/api/custom_layer.test.cpp | 2 + 56 files changed, 513 insertions(+), 246 deletions(-) create mode 100644 include/mbgl/style/layers/custom_layer_render_parameters.hpp create mode 100644 include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp create mode 100644 platform/darwin/app/CustomStyleLayerExample.h create mode 100644 platform/darwin/app/CustomStyleLayerExample.m delete mode 100644 platform/darwin/app/LimeGreenStyleLayer.h delete mode 100644 platform/darwin/app/LimeGreenStyleLayer.m create mode 100644 platform/darwin/src/MLNBackendResource.h rename platform/darwin/src/{MLNOpenGLStyleLayer.h => MLNCustomStyleLayer.h} (84%) rename platform/darwin/src/{MLNOpenGLStyleLayer.mm => MLNCustomStyleLayer.mm} (73%) rename platform/darwin/src/{MLNOpenGLStyleLayer_Private.h => MLNCustomStyleLayer_Private.h} (84%) mode change 100644 => 100755 platform/ios/scripts/bazel-package.sh create mode 100644 src/mbgl/style/layers/custom_layer_render_parameters.cpp create mode 100644 src/mbgl/style/layers/mtl/custom_layer_render_parameters.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f99fb436d2d..944ea282370 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1000,6 +1000,8 @@ if(MLN_WITH_OPENGL) INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/backend.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer_render_parameters.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/custom_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderable_resource.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderer_backend.hpp @@ -1072,6 +1074,7 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/src/mbgl/gl/fence.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/fence.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_render_parameters.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/custom_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_impl.hpp diff --git a/bazel/core.bzl b/bazel/core.bzl index 34e514003c9..9a756fa84ff 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -449,6 +449,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/layers/circle_layer_impl.cpp", "src/mbgl/style/layers/circle_layer_impl.hpp", "src/mbgl/style/layers/custom_layer.cpp", + "src/mbgl/style/layers/custom_layer_render_parameters.cpp", "src/mbgl/style/layers/custom_layer_impl.cpp", "src/mbgl/style/layers/custom_layer_impl.hpp", "src/mbgl/style/layers/fill_extrusion_layer_impl.cpp", @@ -774,6 +775,7 @@ MLN_CORE_HEADERS = [ "include/mbgl/style/layer.hpp", "include/mbgl/style/layer_properties.hpp", "include/mbgl/style/layers/custom_layer.hpp", + "include/mbgl/style/layers/custom_layer_render_parameters.hpp", "include/mbgl/style/position.hpp", "include/mbgl/style/property_expression.hpp", "include/mbgl/style/property_value.hpp", @@ -1066,6 +1068,7 @@ MLN_DRAWABLES_MTL_SOURCE = [ "src/mbgl/shaders/mtl/symbol_icon.cpp", "src/mbgl/shaders/mtl/symbol_sdf.cpp", "src/mbgl/shaders/mtl/symbol_text_and_icon.cpp", + "src/mbgl/style/layers/mtl/custom_layer_render_parameters.cpp", ] MLN_DRAWABLES_MTL_HEADERS = [ @@ -1111,4 +1114,5 @@ MLN_DRAWABLES_MTL_HEADERS = [ "include/mbgl/shaders/mtl/symbol_icon.hpp", "include/mbgl/shaders/mtl/symbol_sdf.hpp", "include/mbgl/shaders/mtl/symbol_text_and_icon.hpp", + "include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp", ] diff --git a/include/mbgl/gfx/drawable_atlases_tweaker.hpp b/include/mbgl/gfx/drawable_atlases_tweaker.hpp index f13a30470c5..63e123845ba 100644 --- a/include/mbgl/gfx/drawable_atlases_tweaker.hpp +++ b/include/mbgl/gfx/drawable_atlases_tweaker.hpp @@ -13,8 +13,9 @@ namespace mbgl { class TileAtlasTextures; using TileAtlasTexturesPtr = std::shared_ptr; -namespace gfx { +class PaintParameters; +namespace gfx { class Drawable; /** diff --git a/include/mbgl/gfx/drawable_custom_layer_host_tweaker.hpp b/include/mbgl/gfx/drawable_custom_layer_host_tweaker.hpp index 1a6ca0bdd93..0e6341e4460 100644 --- a/include/mbgl/gfx/drawable_custom_layer_host_tweaker.hpp +++ b/include/mbgl/gfx/drawable_custom_layer_host_tweaker.hpp @@ -8,6 +8,8 @@ namespace mbgl { +class PaintParameters; + namespace gfx { class Drawable; diff --git a/include/mbgl/gl/drawable_gl.hpp b/include/mbgl/gl/drawable_gl.hpp index f7184a88b25..7da9372ba8a 100644 --- a/include/mbgl/gl/drawable_gl.hpp +++ b/include/mbgl/gl/drawable_gl.hpp @@ -13,6 +13,7 @@ namespace mbgl { template class Segment; +class PaintParameters; namespace gfx { diff --git a/include/mbgl/gl/layer_group_gl.hpp b/include/mbgl/gl/layer_group_gl.hpp index 6b3a83fa616..dc8173f193a 100644 --- a/include/mbgl/gl/layer_group_gl.hpp +++ b/include/mbgl/gl/layer_group_gl.hpp @@ -3,6 +3,9 @@ #include namespace mbgl { + +class PaintParameters; + namespace gl { /** diff --git a/include/mbgl/mtl/drawable.hpp b/include/mbgl/mtl/drawable.hpp index c4bb7251934..ddeaccb1486 100644 --- a/include/mbgl/mtl/drawable.hpp +++ b/include/mbgl/mtl/drawable.hpp @@ -11,6 +11,7 @@ namespace mbgl { template class Segment; +class PaintParameters; namespace gfx { diff --git a/include/mbgl/mtl/layer_group.hpp b/include/mbgl/mtl/layer_group.hpp index 586b8096f18..aba0a0d4293 100644 --- a/include/mbgl/mtl/layer_group.hpp +++ b/include/mbgl/mtl/layer_group.hpp @@ -3,6 +3,9 @@ #include namespace mbgl { + +class PaintParameters; + namespace mtl { /** diff --git a/include/mbgl/mtl/tile_layer_group.hpp b/include/mbgl/mtl/tile_layer_group.hpp index 58b15d4e3f1..069906168c0 100644 --- a/include/mbgl/mtl/tile_layer_group.hpp +++ b/include/mbgl/mtl/tile_layer_group.hpp @@ -8,6 +8,9 @@ #include namespace mbgl { + +class PaintParameters; + namespace mtl { /** diff --git a/include/mbgl/style/layers/custom_layer.hpp b/include/mbgl/style/layers/custom_layer.hpp index c957b6dc5d5..2ef7aa39c9c 100644 --- a/include/mbgl/style/layers/custom_layer.hpp +++ b/include/mbgl/style/layers/custom_layer.hpp @@ -1,33 +1,20 @@ #pragma once #include +#include #include +#include namespace mbgl { -namespace style { -/** - * Parameters that define the current camera position for a - * `CustomLayerHost::render()` function. - */ -struct CustomLayerRenderParameters { - double width; - double height; - double latitude; - double longitude; - double zoom; - double bearing; - double pitch; - double fieldOfView; - std::array projectionMatrix; -}; +namespace style { class CustomLayerHost { public: virtual ~CustomLayerHost() = default; /** - * Initialize any GL state needed by the custom layer. This method is called + * Initialize any GL/Metal state needed by the custom layer. This method is called * once, from the main thread, at a point when the GL context is active but * before rendering for the first time. * @@ -38,7 +25,7 @@ class CustomLayerHost { /** * Render the layer. This method is called once per frame. The - * implementation should not make any assumptions about the GL state (other + * implementation should not make any assumptions about the GL/Metal state (other * than that the correct context is active). It may make changes to the * state, and is not required to reset values such as the depth mask, * stencil mask, and corresponding test flags to their original values. Make @@ -46,10 +33,10 @@ class CustomLayerHost { * advantage of the opaque fragment culling in case there are opaque layers * above your custom layer. */ - virtual void render(const CustomLayerRenderParameters&) = 0; + virtual void render(const mbgl::style::CustomLayerRenderParameters&) = 0; /** - * Called when the system has destroyed the underlying GL context. The + * Called when the system has destroyed the underlying GL/Metal context. The * `deinitialize` function will not be called in this case, however * `initialize` will be called instead to prepare for a new render. * @@ -57,9 +44,9 @@ class CustomLayerHost { virtual void contextLost() = 0; /** - * Destroy any GL state needed by the custom layer, and deallocate context, + * Destroy any GL/Metal state needed by the custom layer, and deallocate context, * if necessary. This method is called once, from the main thread, at a - * point when the GL context is active. + * point when the GL/Metal context is active. * * Note that it may be called even when the `initialize` function has not * been called. diff --git a/include/mbgl/style/layers/custom_layer_render_parameters.hpp b/include/mbgl/style/layers/custom_layer_render_parameters.hpp new file mode 100644 index 00000000000..54eb7d90d41 --- /dev/null +++ b/include/mbgl/style/layers/custom_layer_render_parameters.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +namespace mbgl { + +class PaintParameters; + +namespace style { + +/** + * Parameters that define the current camera position for a + * `CustomLayerHost::render()` function. + */ +struct CustomLayerRenderParameters { + double width; + double height; + double latitude; + double longitude; + double zoom; + double bearing; + double pitch; + double fieldOfView; + std::array projectionMatrix; + + CustomLayerRenderParameters(const PaintParameters&); +}; + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp b/include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp new file mode 100644 index 00000000000..1b6e094b7d1 --- /dev/null +++ b/include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +namespace mbgl { + +class PaintParameters; + +namespace style { + +namespace mtl { + +/** + * Metal subclass of CustomLayerRenderParameters + */ +struct CustomLayerRenderParameters : mbgl::style::CustomLayerRenderParameters { + mbgl::mtl::MTLRenderCommandEncoderPtr encoder; + + CustomLayerRenderParameters(const PaintParameters&); +}; + +} // namespace mtl +} // namespace style +} // namespace mbgl diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index 4052b441e27..b381a1f62a4 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -12,11 +12,6 @@ objc_library( "//platform/ios:ios_public_hdrs", "//platform/ios:ios_sdk_hdrs", ] + select({ - "//:metal_renderer": [], - "//conditions:default": [ - "//platform/darwin:darwin_objcpp_opengl_srcs", - ], - }) + select({ "//:legacy_renderer": [], "//conditions:default": [ "//platform/darwin:darwin_objcpp_custom_drawable_srcs", @@ -193,13 +188,9 @@ objc_library( name = "iosapp", srcs = [ "//platform/ios:ios_app_srcs", - ] + select({ - "//:metal_renderer": [], - "//conditions:default": [ - "//platform/darwin:app/LimeGreenStyleLayer.h", - "//platform/darwin:app/LimeGreenStyleLayer.m", - ], - }), + "//platform/darwin:app/CustomStyleLayerExample.h", + "//platform/darwin:app/CustomStyleLayerExample.m", + ], defines = ["GLES_SILENCE_DEPRECATION"], includes = [ "darwin/app", @@ -211,7 +202,7 @@ objc_library( "MetalKit", ], "//conditions:default": [ - "GLKit", # needed for LimeGreenStyleLayer + "GLKit", # needed for CustomStyleLayerExample ], }), visibility = ["//visibility:public"], diff --git a/platform/android/src/example_custom_layer.cpp b/platform/android/src/example_custom_layer.cpp index caa44c9831b..535c7c39b87 100644 --- a/platform/android/src/example_custom_layer.cpp +++ b/platform/android/src/example_custom_layer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include // DEBUGGING diff --git a/platform/darwin/BUILD.bazel b/platform/darwin/BUILD.bazel index 5fa5997bf65..331fa679133 100644 --- a/platform/darwin/BUILD.bazel +++ b/platform/darwin/BUILD.bazel @@ -10,7 +10,6 @@ load( "MLN_DARWIN_OBJC_HEADERS", "MLN_DARWIN_PRIVATE_HEADERS", "MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE", - "MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE", "MLN_DARWIN_PUBLIC_OBJCPP_SOURCE", "MLN_DARWIN_PUBLIC_OBJC_SOURCE", "MLN_GENERATED_DARWIN_STYLE_HEADERS", @@ -33,10 +32,7 @@ filegroup( filegroup( name = "darwin_objcpp_hdrs", - srcs = MLN_DARWIN_OBJCPP_HEADERS + select({ - "//:metal_renderer": [], - "//conditions:default": ["src/MLNOpenGLStyleLayer.h"], - }), + srcs = MLN_DARWIN_OBJCPP_HEADERS, visibility = ["//visibility:public"], ) @@ -46,12 +42,6 @@ filegroup( visibility = ["//visibility:public"], ) -filegroup( - name = "darwin_objcpp_opengl_srcs", - srcs = MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE, - visibility = ["//visibility:public"], -) - filegroup( name = "darwin_objcpp_custom_drawable_srcs", srcs = MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE, @@ -319,14 +309,13 @@ exports_files( [ "test/amsterdam.geojson", "test/MLNSDKTestHelpers.swift", - "app/LimeGreenStyleLayer.h", - "app/LimeGreenStyleLayer.m", + "app/CustomStyleLayerExample.h", + "app/CustomStyleLayerExample.m", "app/ExampleCustomDrawableStyleLayer.h", "app/ExampleCustomDrawableStyleLayer.mm", "include/mbgl/util/image+MLNAdditions.hpp", ] + MLN_DARWIN_PUBLIC_OBJC_SOURCE + MLN_DARWIN_PUBLIC_OBJCPP_SOURCE + - MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE + MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE + MLN_DARWIN_PRIVATE_HEADERS + MLN_DARWIN_OBJC_HEADERS + @@ -359,11 +348,6 @@ js_library( ":generated_style_hdrs", ":generated_style_srcs", ] + select({ - "//:metal_renderer": [], - "//conditions:default": [ - ":darwin_objcpp_opengl_srcs", - ], - }) + select({ "//:legacy_renderer": [], "//conditions:default": [ ":darwin_objcpp_custom_drawable_srcs", diff --git a/platform/darwin/app/CustomStyleLayerExample.h b/platform/darwin/app/CustomStyleLayerExample.h new file mode 100644 index 00000000000..69736db6533 --- /dev/null +++ b/platform/darwin/app/CustomStyleLayerExample.h @@ -0,0 +1,5 @@ +#import "Mapbox.h" + +@interface CustomStyleLayerExample : MLNCustomStyleLayer + +@end diff --git a/platform/darwin/app/CustomStyleLayerExample.m b/platform/darwin/app/CustomStyleLayerExample.m new file mode 100644 index 00000000000..c2f69589d92 --- /dev/null +++ b/platform/darwin/app/CustomStyleLayerExample.m @@ -0,0 +1,188 @@ +#if !MLN_RENDER_BACKEND_METAL + +/* OPENGL Custom Layer example implementation */ +#import "CustomStyleLayerExample.h" +#import + +@implementation CustomStyleLayerExample { + GLuint _program; + GLuint _vertexShader; + GLuint _fragmentShader; + GLuint _buffer; + GLuint _aPos; +} + +- (void)didMoveToMapView:(MLNMapView *)mapView { + static const GLchar *vertexShaderSource = "#version 300 es\nlayout (location = 0) in vec2 a_pos; void main() { gl_Position = vec4(a_pos, 1, 1); }"; + static const GLchar *fragmentShaderSource = "#version 300 es\nout highp vec4 fragColor; void main() { fragColor = vec4(0, 0.5, 0, 0.5); }"; + + _program = glCreateProgram(); + _vertexShader = glCreateShader(GL_VERTEX_SHADER); + _fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + + glShaderSource(_vertexShader, 1, &vertexShaderSource, NULL); + glCompileShader(_vertexShader); + glAttachShader(_program, _vertexShader); + glShaderSource(_fragmentShader, 1, &fragmentShaderSource, NULL); + glCompileShader(_fragmentShader); + glAttachShader(_program, _fragmentShader); + glLinkProgram(_program); + _aPos = glGetAttribLocation(_program, "a_pos"); + + GLfloat triangle[] = { 0, 0.5, 0.5, -0.5, -0.5, -0.5 }; + glGenBuffers(1, &_buffer); + glBindBuffer(GL_ARRAY_BUFFER, _buffer); + glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(GLfloat), triangle, GL_STATIC_DRAW); +} + +- (void)drawInMapView:(MLNMapView *)mapView withContext:(MLNStyleLayerDrawingContext)context { + glUseProgram(_program); + glBindBuffer(GL_ARRAY_BUFFER, _buffer); + glEnableVertexAttribArray(_aPos); + glVertexAttribPointer(_aPos, 2, GL_FLOAT, GL_FALSE, 0, NULL); + glDisable(GL_STENCIL_TEST); + glDisable(GL_DEPTH_TEST); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); +} + +- (void)willMoveFromMapView:(MLNMapView *)mapView { + if (!_program) { + return; + } + + glDeleteBuffers(1, &_buffer); + glDetachShader(_program, _vertexShader); + glDetachShader(_program, _fragmentShader); + glDeleteShader(_vertexShader); + glDeleteShader(_fragmentShader); + glDeleteProgram(_program); +} + +@end + +#else // MLN_RENDER_BACKEND_METAL: + +/* Metal Custom Layer example implementation */ +#import "CustomStyleLayerExample.h" + +@implementation CustomStyleLayerExample { + // The render pipeline state + id _pipelineState; + id _depthStencilStateWithoutStencil; +} + +- (void)didMoveToMapView:(MLNMapView *)mapView { + MLNBackendResource resource = [mapView backendResource]; + + NSString *shaderSource = @ +" #include \n" +" using namespace metal;\n" +" typedef struct\n" +" {\n" +" vector_float2 position;\n" +" vector_float4 color;\n" +" } Vertex;\n" +" struct RasterizerData\n" +" {\n" +" float4 position [[position]];\n" +" float4 color;\n" +" };\n" +" vertex RasterizerData\n" +" vertexShader(uint vertexID [[vertex_id]],\n" +" constant Vertex *vertices [[buffer(0)]],\n" +" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n" +" {\n" +" RasterizerData out;\n" +" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n" +" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n" +" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n" +" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n" +" out.color = vertices[vertexID].color;\n" +" return out;\n" +" }\n" +" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n" +" {\n" +" return in.color;\n" +" }\n"; + + + NSError *error = nil; + id _device = resource.device; + id library = [_device newLibraryWithSource:shaderSource options:nil error:&error]; + NSAssert(library, @"Error compiling shaders: %@", error); + id vertexFunction = [library newFunctionWithName:@"vertexShader"]; + id fragmentFunction = [library newFunctionWithName:@"fragmentShader"]; + + // Configure a pipeline descriptor that is used to create a pipeline state. + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"Simple Pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat; + pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + + _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor + error:&error]; + NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error); + + // Notice that we don't configure the stencilTest property, leaving stencil testing disabled + MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; + depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed + depthStencilDescriptor.depthWriteEnabled = NO; + + _depthStencilStateWithoutStencil = [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; + +} + +- (void)drawInMapView:(MLNMapView *)mapView withContext:(MLNStyleLayerDrawingContext)context { + // Use the supplied render command encoder to encode commands + id renderEncoder = self.renderEncoder; + if(renderEncoder != nil) + { + MLNBackendResource resource = [mapView backendResource]; + + vector_uint2 _viewportSize; + _viewportSize.x = resource.mtkView.drawableSize.width; + _viewportSize.y = resource.mtkView.drawableSize.height; + + typedef struct + { + vector_float2 position; + vector_float4 color; + } Vertex; + + static const Vertex triangleVertices[] = + { + // 2D positions, RGBA colors + { { 250, -250 }, { 1, 0, 0, 1 } }, + { { -250, -250 }, { 0, 1, 0, 1 } }, + { { 0, 250 }, { 0, 0, 1, 1 } }, + }; + + [renderEncoder setRenderPipelineState:_pipelineState]; + [renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil]; + + // Pass in the parameter data. + [renderEncoder setVertexBytes:triangleVertices + length:sizeof(triangleVertices) + atIndex:0]; + + [renderEncoder setVertexBytes:&_viewportSize + length:sizeof(_viewportSize) + atIndex:1]; + + // Draw the triangle. + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle + vertexStart:0 + vertexCount:3]; + } +} + +- (void)willMoveFromMapView:(MLNMapView *)mapView { + // Clean up +} + +@end + +#endif diff --git a/platform/darwin/app/LimeGreenStyleLayer.h b/platform/darwin/app/LimeGreenStyleLayer.h deleted file mode 100644 index 10d359422d7..00000000000 --- a/platform/darwin/app/LimeGreenStyleLayer.h +++ /dev/null @@ -1,5 +0,0 @@ -#import "Mapbox.h" - -@interface LimeGreenStyleLayer : MLNOpenGLStyleLayer - -@end diff --git a/platform/darwin/app/LimeGreenStyleLayer.m b/platform/darwin/app/LimeGreenStyleLayer.m deleted file mode 100644 index c920a736f92..00000000000 --- a/platform/darwin/app/LimeGreenStyleLayer.m +++ /dev/null @@ -1,58 +0,0 @@ -#import "LimeGreenStyleLayer.h" -#import - -@implementation LimeGreenStyleLayer { - GLuint _program; - GLuint _vertexShader; - GLuint _fragmentShader; - GLuint _buffer; - GLuint _aPos; -} - -- (void)didMoveToMapView:(MLNMapView *)mapView { - static const GLchar *vertexShaderSource = "#version 300 es\nlayout (location = 0) in vec2 a_pos; void main() { gl_Position = vec4(a_pos, 1, 1); }"; - static const GLchar *fragmentShaderSource = "#version 300 es\nout highp vec4 fragColor; void main() { fragColor = vec4(0, 0.5, 0, 0.5); }"; - - _program = glCreateProgram(); - _vertexShader = glCreateShader(GL_VERTEX_SHADER); - _fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - - glShaderSource(_vertexShader, 1, &vertexShaderSource, NULL); - glCompileShader(_vertexShader); - glAttachShader(_program, _vertexShader); - glShaderSource(_fragmentShader, 1, &fragmentShaderSource, NULL); - glCompileShader(_fragmentShader); - glAttachShader(_program, _fragmentShader); - glLinkProgram(_program); - _aPos = glGetAttribLocation(_program, "a_pos"); - - GLfloat triangle[] = { 0, 0.5, 0.5, -0.5, -0.5, -0.5 }; - glGenBuffers(1, &_buffer); - glBindBuffer(GL_ARRAY_BUFFER, _buffer); - glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(GLfloat), triangle, GL_STATIC_DRAW); -} - -- (void)drawInMapView:(MLNMapView *)mapView withContext:(MLNStyleLayerDrawingContext)context { - glUseProgram(_program); - glBindBuffer(GL_ARRAY_BUFFER, _buffer); - glEnableVertexAttribArray(_aPos); - glVertexAttribPointer(_aPos, 2, GL_FLOAT, GL_FALSE, 0, NULL); - glDisable(GL_STENCIL_TEST); - glDisable(GL_DEPTH_TEST); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); -} - -- (void)willMoveFromMapView:(MLNMapView *)mapView { - if (!_program) { - return; - } - - glDeleteBuffers(1, &_buffer); - glDetachShader(_program, _vertexShader); - glDetachShader(_program, _fragmentShader); - glDeleteShader(_vertexShader); - glDeleteShader(_fragmentShader); - glDeleteProgram(_program); -} - -@end diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index a7cee65be70..fd344cb28af 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -53,11 +53,13 @@ MLN_DARWIN_OBJC_HEADERS = [ "src/MLNAnnotation.h", "src/MLNAttributedExpression.h", "src/MLNAttributionInfo.h", + "src/MLNBackendResource.h", "src/MLNClockDirectionFormatter.h", "src/MLNCluster.h", "src/MLNCompassDirectionFormatter.h", "src/MLNComputedShapeSource.h", "src/MLNCoordinateFormatter.h", + "src/MLNCustomStyleLayer.h", "src/MLNCustomDrawableStyleLayer.h", "src/MLNDefaultStyle.h", "src/MLNDistanceFormatter.h", @@ -75,7 +77,6 @@ MLN_DARWIN_OBJC_HEADERS = [ "src/MLNOfflinePack.h", "src/MLNOfflineRegion.h", "src/MLNOfflineStorage.h", - "src/MLNOpenGLStyleLayer.h", "src/MLNOverlay.h", "src/MLNPointAnnotation.h", "src/MLNPointCollection.h", @@ -126,6 +127,7 @@ MLN_DARWIN_OBJCPP_HEADERS = [ MLN_DARWIN_PRIVATE_HEADERS = [ "src/MLNAttributionInfo_Private.h", "src/MLNComputedShapeSource_Private.h", + "src/MLNCustomStyleLayer_Private.h", "src/MLNFeature_Private.h", "src/MLNFoundation_Private.h", "src/MLNGeometry_Private.h", @@ -160,6 +162,7 @@ MLN_DARWIN_PRIVATE_HEADERS = [ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ "src/MLNAttributionInfo.mm", "src/MLNComputedShapeSource.mm", + "src/MLNCustomStyleLayer.mm", "src/MLNDefaultStyle.mm", "src/MLNFeature.mm", "src/MLNForegroundStyleLayer.mm", @@ -205,11 +208,6 @@ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ "src/NSPredicate+MLNAdditions.mm", "src/NSValue+MLNStyleAttributeAdditions.mm", ] -MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE = [ - "src/MLNOpenGLStyleLayer_Private.h", - "src/MLNOpenGLStyleLayer.h", - "src/MLNOpenGLStyleLayer.mm", -] MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE = [ "src/MLNCustomDrawableStyleLayer_Private.h", "src/MLNCustomDrawableStyleLayer.mm", diff --git a/platform/darwin/src/MLNBackendResource.h b/platform/darwin/src/MLNBackendResource.h new file mode 100644 index 00000000000..97f9c9f630f --- /dev/null +++ b/platform/darwin/src/MLNBackendResource.h @@ -0,0 +1,19 @@ +#if MLN_RENDER_BACKEND_METAL + +#import + +typedef struct +{ + MTKView *mtkView; + id device; + MTLRenderPassDescriptor *renderPassDescriptor; + id commandBuffer; +} MLNBackendResource; + +#else + +typedef struct +{ +} MLNBackendResource; + +#endif diff --git a/platform/darwin/src/MLNOpenGLStyleLayer.h b/platform/darwin/src/MLNCustomStyleLayer.h similarity index 84% rename from platform/darwin/src/MLNOpenGLStyleLayer.h rename to platform/darwin/src/MLNCustomStyleLayer.h index 8231f763efa..135f3923dff 100644 --- a/platform/darwin/src/MLNOpenGLStyleLayer.h +++ b/platform/darwin/src/MLNCustomStyleLayer.h @@ -7,6 +7,10 @@ #import "MLNStyleLayer.h" #import "MLNGeometry.h" +#if MLN_RENDER_BACKEND_METAL +#import +#endif + NS_ASSUME_NONNULL_BEGIN @class MLNMapView; @@ -23,7 +27,7 @@ typedef struct MLNStyleLayerDrawingContext { } MLNStyleLayerDrawingContext; MLN_EXPORT -@interface MLNOpenGLStyleLayer : MLNStyleLayer +@interface MLNCustomStyleLayer : MLNStyleLayer @property (nonatomic, weak, readonly) MLNStyle *style; @@ -36,6 +40,10 @@ MLN_EXPORT #endif #pragma clang diagnostic pop +#if MLN_RENDER_BACKEND_METAL +@property (nonatomic, weak) id renderEncoder; +#endif + - (instancetype)initWithIdentifier:(NSString *)identifier; - (void)didMoveToMapView:(MLNMapView *)mapView; diff --git a/platform/darwin/src/MLNOpenGLStyleLayer.mm b/platform/darwin/src/MLNCustomStyleLayer.mm similarity index 73% rename from platform/darwin/src/MLNOpenGLStyleLayer.mm rename to platform/darwin/src/MLNCustomStyleLayer.mm index dc868979553..7bcb69dab93 100644 --- a/platform/darwin/src/MLNOpenGLStyleLayer.mm +++ b/platform/darwin/src/MLNCustomStyleLayer.mm @@ -1,35 +1,43 @@ -#import "MLNOpenGLStyleLayer.h" -#import "MLNOpenGLStyleLayer_Private.h" +#import "MLNCustomStyleLayer.h" +#import "MLNCustomStyleLayer_Private.h" #import "MLNMapView_Private.h" #import "MLNStyle_Private.h" #import "MLNStyleLayer_Private.h" #import "MLNGeometry_Private.h" +#if MLN_RENDER_BACKEND_METAL +#import +#endif + #include #include -class MLNOpenGLLayerHost; +#if MLN_RENDER_BACKEND_METAL +#include +#endif + +class MLNCustomLayerHost; /** - An `MLNOpenGLStyleLayer` is a style layer that is rendered by OpenGL code that + An `MLNCustomStyleLayer` is a style layer that is rendered by OpenGL / Metal code that you provide. By default, this class does nothing. You can subclass this class to provide - custom OpenGL drawing code that is run on each frame of the map. Your subclass + custom OpenGL or Metal drawing code that is run on each frame of the map. Your subclass should override the `-didMoveToMapView:`, `-willMoveFromMapView:`, and `-drawInMapView:withContext:` methods. - You can access an existing OpenGL style layer using the + You can access an existing MLNCustomStyleLayer using the `-[MLNStyle layerWithIdentifier:]` method if you know its identifier; otherwise, find it using the `MLNStyle.layers` property. You can also create a - new OpenGL style layer and add it to the style using a method such as + new MLNCustomStyleLayer and add it to the style using a method such as `-[MLNStyle addLayer:]`. @warning This API is undocumented and therefore unsupported. It may change at any time without notice. */ -@interface MLNOpenGLStyleLayer () +@interface MLNCustomStyleLayer () @property (nonatomic, readonly) mbgl::style::CustomLayer *rawLayer; @@ -45,10 +53,10 @@ @interface MLNOpenGLStyleLayer () @end -@implementation MLNOpenGLStyleLayer +@implementation MLNCustomStyleLayer /** - Returns an OpenGL style layer object initialized with the given identifier. + Returns an MLNCustomStyleLayer style layer object initialized with the given identifier. After initializing and configuring the style layer, add it to a map view’s style using the `-[MLNStyle addLayer:]` or @@ -60,7 +68,7 @@ @implementation MLNOpenGLStyleLayer */ - (instancetype)initWithIdentifier:(NSString *)identifier { auto layer = std::make_unique(identifier.UTF8String, - std::make_unique(self)); + std::make_unique(self)); return self = [super initWithPendingLayer:std::move(layer)]; } @@ -88,13 +96,13 @@ - (CGLContextObj)context { // MARK: - Adding to and removing from a map view - (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer { self.style = style; - self.style.openGLLayers[self.identifier] = self; + self.style.customLayers[self.identifier] = self; [super addToStyle:style belowLayer:otherLayer]; } - (void)removeFromStyle:(MLNStyle *)style { [super removeFromStyle:style]; - self.style.openGLLayers[self.identifier] = nil; + self.style.customLayers[self.identifier] = nil; self.style = nil; } @@ -164,9 +172,9 @@ - (void)setNeedsDisplay { @end -class MLNOpenGLLayerHost : public mbgl::style::CustomLayerHost { +class MLNCustomLayerHost : public mbgl::style::CustomLayerHost { public: - MLNOpenGLLayerHost(MLNOpenGLStyleLayer *styleLayer) { + MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) { layerRef = styleLayer; layer = nil; } @@ -180,17 +188,23 @@ void initialize() { } } - void render(const mbgl::style::CustomLayerRenderParameters ¶ms) { + void render(const mbgl::style::CustomLayerRenderParameters& parameters) { if(!layer) return; +#if MLN_RENDER_BACKEND_METAL + MTL::RenderCommandEncoder* ptr = static_cast(parameters).encoder.get(); + id encoder = (__bridge id)ptr; + layer.renderEncoder = encoder; +#endif + MLNStyleLayerDrawingContext drawingContext = { - .size = CGSizeMake(params.width, params.height), - .centerCoordinate = CLLocationCoordinate2DMake(params.latitude, params.longitude), - .zoomLevel = params.zoom, - .direction = mbgl::util::wrap(params.bearing, 0., 360.), - .pitch = static_cast(params.pitch), - .fieldOfView = static_cast(params.fieldOfView), - .projectionMatrix = MLNMatrix4Make(params.projectionMatrix) + .size = CGSizeMake(parameters.width, parameters.height), + .centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude), + .zoomLevel = parameters.zoom, + .direction = mbgl::util::wrap(parameters.bearing, 0., 360.), + .pitch = static_cast(parameters.pitch), + .fieldOfView = static_cast(parameters.fieldOfView), + .projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix) }; if (layer.mapView) { [layer drawInMapView:layer.mapView withContext:drawingContext]; @@ -209,14 +223,14 @@ void deinitialize() { layer = nil; } private: - __weak MLNOpenGLStyleLayer * layerRef; - MLNOpenGLStyleLayer * layer = nil; + __weak MLNCustomStyleLayer * layerRef; + MLNCustomStyleLayer * layer = nil; }; namespace mbgl { -MLNStyleLayer* OpenGLStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) { - return [[MLNOpenGLStyleLayer alloc] initWithRawLayer:rawLayer]; +MLNStyleLayer* CustomStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) { + return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer]; } } // namespace mbgl diff --git a/platform/darwin/src/MLNOpenGLStyleLayer_Private.h b/platform/darwin/src/MLNCustomStyleLayer_Private.h similarity index 84% rename from platform/darwin/src/MLNOpenGLStyleLayer_Private.h rename to platform/darwin/src/MLNCustomStyleLayer_Private.h index 07f59d7bfaa..39ce4d33988 100644 --- a/platform/darwin/src/MLNOpenGLStyleLayer_Private.h +++ b/platform/darwin/src/MLNCustomStyleLayer_Private.h @@ -6,7 +6,7 @@ namespace mbgl { -class OpenGLStyleLayerPeerFactory : public LayerPeerFactory, public mbgl::CustomLayerFactory { +class CustomStyleLayerPeerFactory : public LayerPeerFactory, public mbgl::CustomLayerFactory { // LayerPeerFactory overrides. LayerFactory* getCoreLayerFactory() final { return this; } virtual MLNStyleLayer* createPeer(style::Layer*) final; diff --git a/platform/darwin/src/MLNStyle.mm b/platform/darwin/src/MLNStyle.mm index 7b885c6af8a..d8101b56113 100644 --- a/platform/darwin/src/MLNStyle.mm +++ b/platform/darwin/src/MLNStyle.mm @@ -39,9 +39,7 @@ #import "NSDate+MLNAdditions.h" -#if !MLN_RENDER_BACKEND_METAL - #import "MLNOpenGLStyleLayer.h" -#endif +#import "MLNCustomStyleLayer.h" #if TARGET_OS_IPHONE #import "UIImage+MLNAdditions.h" @@ -82,7 +80,7 @@ @interface MLNStyle() @property (nonatomic, readonly, weak) id stylable; @property (nonatomic, readonly) mbgl::style::Style *rawStyle; @property (readonly, copy, nullable) NSURL *URL; -@property (nonatomic, readwrite, strong) NSMutableDictionary *openGLLayers; +@property (nonatomic, readwrite, strong) NSMutableDictionary *customLayers; @property (nonatomic) NSMutableDictionary *> *localizedLayersByIdentifier; @end @@ -125,7 +123,7 @@ - (instancetype)initWithRawStyle:(mbgl::style::Style *)rawStyle stylable:(id ()); -#elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL) && !MLN_RENDER_BACKEND_METAL - addLayerType(std::make_unique()); +#elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL) + addLayerType(std::make_unique()); #endif #if MLN_DRAWABLE_RENDERER diff --git a/platform/darwin/src/MLNStyle_Private.h b/platform/darwin/src/MLNStyle_Private.h index def44f5c3c8..8300081bf7d 100644 --- a/platform/darwin/src/MLNStyle_Private.h +++ b/platform/darwin/src/MLNStyle_Private.h @@ -13,7 +13,7 @@ namespace mbgl { @class MLNAttributionInfo; @class MLNMapView; -@class MLNOpenGLStyleLayer; +@class MLNCustomStyleLayer; @class MLNVectorTileSource; @class MLNVectorStyleLayer; @@ -25,7 +25,7 @@ namespace mbgl { @property (nonatomic, readonly) mbgl::style::Style *rawStyle; - (nullable NSArray *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor; -@property (nonatomic, readonly, strong) NSMutableDictionary *openGLLayers; +@property (nonatomic, readonly, strong) NSMutableDictionary *customLayers; - (void)setStyleClasses:(NSArray *)appliedClasses transitionDuration:(NSTimeInterval)transitionDuration; @end diff --git a/platform/darwin/test/MLNStyleTests.mm b/platform/darwin/test/MLNStyleTests.mm index 101c38ab3cd..4c93719979e 100644 --- a/platform/darwin/test/MLNStyleTests.mm +++ b/platform/darwin/test/MLNStyleTests.mm @@ -232,9 +232,7 @@ - (void)testAddingLayersWithDuplicateIdentifiers { XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] belowLayer:initial],NSException, @"MLNRedundantLayerIdentifierException"); XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] aboveLayer:initial], NSException, @"MLNRedundantLayerIdentifierException"); XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException"); -#if !MLN_RENDER_BACKEND_METAL - XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"my-layer"] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException"); -#endif + XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException"); } - (void)testRemovingLayerBeforeAddingSameLayer { diff --git a/platform/ios/Integration_Tests/MBGLIntegrationTests.mm b/platform/ios/Integration_Tests/MBGLIntegrationTests.mm index 4c57907a980..846103a1953 100644 --- a/platform/ios/Integration_Tests/MBGLIntegrationTests.mm +++ b/platform/ios/Integration_Tests/MBGLIntegrationTests.mm @@ -17,7 +17,7 @@ - (void)waitForMapViewToBeRendered { // This test does not strictly need to be in this test file/target. Including here for convenience. - (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle { - MLNOpenGLStyleLayer *layer = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; __weak id weakLayer = layer; layer = nil; @@ -31,7 +31,7 @@ - (void)testAddingRemovingOpenGLLayerWithoutRendering { __weak id weakLayer = nil; @autoreleasepool { - MLNOpenGLStyleLayer *layer = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; [self.style insertLayer:layer atIndex:0]; weakLayer = layer; @@ -49,15 +49,15 @@ - (void)testAddingRemovingOpenGLLayerWithoutRendering { } - (void)testReusingOpenGLLayerIdentifier { - __weak MLNOpenGLStyleLayer *weakLayer2; + __weak MLNCustomStyleLayer *weakLayer2; @autoreleasepool { - MLNOpenGLStyleLayer *layer1 = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; [self.style insertLayer:layer1 atIndex:0]; [self waitForMapViewToBeRendered]; [self.style removeLayer:layer1]; - MLNOpenGLStyleLayer *layer2 = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; weakLayer2 = layer2; XCTAssertNotNil(layer2); @@ -88,7 +88,7 @@ - (void)testAddingRemovingOpenGLLayer { __weak id retrievedLayer = nil; @autoreleasepool { - MLNOpenGLStyleLayer *layer = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; [self.style insertLayer:layer atIndex:0]; layer = nil; @@ -110,7 +110,7 @@ - (void)testAddingRemovingOpenGLLayer { } - (void)testReusingOpenGLLayer { - MLNOpenGLStyleLayer *layer = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; [self.style insertLayer:layer atIndex:0]; [self waitForMapViewToBeRendered]; @@ -127,7 +127,7 @@ - (void)testReusingOpenGLLayer { - (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle { __weak id weakLayer; @autoreleasepool { - MLNOpenGLStyleLayer *layer = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; weakLayer = layer; [self.style insertLayer:layer atIndex:0]; layer = nil; @@ -146,11 +146,11 @@ - (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle { } - (void)testOpenGLLayerDoesNotLeakWhenStyleChanged { - __weak MLNOpenGLStyleLayer *weakLayer; + __weak MLNCustomStyleLayer *weakLayer; @autoreleasepool { { - MLNOpenGLStyleLayer *layer = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; weakLayer = layer; [self.style insertLayer:layer atIndex:0]; layer = nil; @@ -192,7 +192,7 @@ - (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs { self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; [self waitForExpectationsWithTimeout:10 handler:nil]; - MLNOpenGLStyleLayer *layer = [[MLNOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; weakLayer = layer; [mapView2.style insertLayer:layer atIndex:0]; layer = nil; diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index fa94621cc7a..d724e248d33 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -13,9 +13,7 @@ #import "MBXState.h" #import "MLNSettings.h" -#if !MLN_RENDER_BACKEND_METAL -#import "LimeGreenStyleLayer.h" -#endif +#import "CustomStyleLayerExample.h" #if MLN_DRAWABLE_RENDERER #import "ExampleCustomDrawableStyleLayer.h" @@ -101,9 +99,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingRasterTileSource, MBXSettingsRuntimeStylingImageSource, MBXSettingsRuntimeStylingRouteLine, -#if !MLN_RENDER_BACKEND_METAL - MBXSettingsRuntimeStylingAddLimeGreenTriangleLayer, -#endif + MBXSettingsRuntimeStylingAddCustomTriangleLayer, MBXSettingsRuntimeStylingDDSPolygon, MBXSettingsRuntimeStylingCustomLatLonGrid, MBXSettingsRuntimeStylingLineGradient, @@ -435,8 +431,10 @@ - (void)dismissSettings:(__unused id)sender @"Style Raster Tile Source", @"Style Image Source", @"Add Route Line", -#if !MLN_RENDER_BACKEND_METAL - @"Add Lime Green Triangle Layer", +#if MLN_RENDER_BACKEND_METAL + @"Add Custom Triangle Layer (Metal)", +#else + @"Add Custom Triangle Layer (OpenGL)", #endif @"Dynamically Style Polygon", @"Add Custom Lat/Lon Grid", @@ -654,11 +652,9 @@ - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath case MBXSettingsRuntimeStylingRouteLine: [self styleRouteLine]; break; -#if !MLN_RENDER_BACKEND_METAL - case MBXSettingsRuntimeStylingAddLimeGreenTriangleLayer: - [self styleAddLimeGreenTriangleLayer]; + case MBXSettingsRuntimeStylingAddCustomTriangleLayer: + [self styleAddCustomTriangleLayer]; break; -#endif case MBXSettingsRuntimeStylingDDSPolygon: [self stylePolygonWithDDS]; break; @@ -1604,13 +1600,11 @@ - (void)styleRouteLine [self.mapView.style addLayer:routeLayer]; } -#if !MLN_RENDER_BACKEND_METAL -- (void)styleAddLimeGreenTriangleLayer +- (void)styleAddCustomTriangleLayer { - LimeGreenStyleLayer *layer = [[LimeGreenStyleLayer alloc] initWithIdentifier:@"mbx-custom"]; + CustomStyleLayerExample *layer = [[CustomStyleLayerExample alloc] initWithIdentifier:@"mbx-custom"]; [self.mapView.style addLayer:layer]; } -#endif - (void)stylePolygonWithDDS { CLLocationCoordinate2D leftCoords[] = { diff --git a/platform/ios/scripts/bazel-package.sh b/platform/ios/scripts/bazel-package.sh old mode 100644 new mode 100755 diff --git a/platform/ios/sdk-files.json b/platform/ios/sdk-files.json index 5874ee4cdf8..bdf4b9c32dc 100644 --- a/platform/ios/sdk-files.json +++ b/platform/ios/sdk-files.json @@ -113,7 +113,7 @@ "platform/darwin/src/MLNPointCollection.mm", "platform/darwin/src/MLNLineStyleLayer.mm", "platform/ios/src/MLNMapAccessibilityElement.mm", - "platform/darwin/src/MLNOpenGLStyleLayer.mm", + "platform/darwin/src/MLNCustomStyleLayer.mm", "platform/darwin/src/MLNSettings.mm", "platform/darwin/src/NSCompoundPredicate+MLNAdditions.mm", "platform/ios/src/MLNTelemetryConfig.m", @@ -185,7 +185,7 @@ "MLNOfflineRegion.h": "platform/darwin/src/MLNOfflineRegion.h", "MLNMapViewDelegate.h": "platform/ios/src/MLNMapViewDelegate.h", "MLNDistanceFormatter.h": "platform/darwin/src/MLNDistanceFormatter.h", - "MLNOpenGLStyleLayer.h": "platform/darwin/src/MLNOpenGLStyleLayer.h", + "MLNCustomStyleLayer.h": "platform/darwin/src/MLNCustomStyleLayer.h", "MLNTileSource.h": "platform/darwin/src/MLNTileSource.h", "MLNTilePyramidOfflineRegion.h": "platform/darwin/src/MLNTilePyramidOfflineRegion.h", "MLNVectorTileSource.h": "platform/darwin/src/MLNVectorTileSource.h", @@ -277,7 +277,7 @@ "MLNGeometry_Private.h": "platform/darwin/src/MLNGeometry_Private.h", "NSCompoundPredicate+MLNAdditions.h": "platform/darwin/src/NSCompoundPredicate+MLNAdditions.h", "NSExpression+MLNPrivateAdditions.h": "platform/darwin/src/NSExpression+MLNPrivateAdditions.h", - "MLNOpenGLStyleLayer_Private.h": "platform/darwin/src/MLNOpenGLStyleLayer_Private.h", + "MLNCustomStyleLayer_Private.h": "platform/darwin/src/MLNCustomStyleLayer_Private.h", "MLNTileSource_Private.h": "platform/darwin/src/MLNTileSource_Private.h", "MLNFaux3DUserLocationAnnotationView.h": "platform/ios/src/MLNFaux3DUserLocationAnnotationView.h", "MLNUserLocationAnnotationView_Private.h": "platform/ios/src/MLNUserLocationAnnotationView_Private.h", diff --git a/platform/ios/src/MLNMapView+Impl.h b/platform/ios/src/MLNMapView+Impl.h index 73d6e45361b..04a9919773f 100644 --- a/platform/ios/src/MLNMapView+Impl.h +++ b/platform/ios/src/MLNMapView+Impl.h @@ -2,6 +2,8 @@ #import #import +#import "MLNBackendResource.h" + #import #import #import @@ -54,6 +56,8 @@ class MLNMapViewImpl : public mbgl::MapObserver { // Called by the view delegate when it's time to render. void render(); + virtual MLNBackendResource getObject() = 0; + // mbgl::MapObserver implementation void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) override; void onCameraIsChanging() override; diff --git a/platform/ios/src/MLNMapView+Metal.h b/platform/ios/src/MLNMapView+Metal.h index e78e833730f..17b26146ca3 100644 --- a/platform/ios/src/MLNMapView+Metal.h +++ b/platform/ios/src/MLNMapView+Metal.h @@ -47,6 +47,7 @@ class MLNMapViewMetalImpl final : public MLNMapViewImpl, void deleteView() override; UIImage* snapshot() override; void layoutChanged() override; + MLNBackendResource getObject() override; // End implementation of MLNMapViewImpl private: diff --git a/platform/ios/src/MLNMapView+Metal.mm b/platform/ios/src/MLNMapView+Metal.mm index c9635b6df00..eb9ff5b3893 100644 --- a/platform/ios/src/MLNMapView+Metal.mm +++ b/platform/ios/src/MLNMapView+Metal.mm @@ -52,8 +52,10 @@ void bind() override { commandQueue = [mtlView.device newCommandQueue]; } - commandBuffer = [commandQueue commandBuffer]; - commandBufferPtr = NS::RetainPtr((__bridge MTL::CommandBuffer*)commandBuffer); + if (!commandBuffer) { + commandBuffer = [commandQueue commandBuffer]; + commandBufferPtr = NS::RetainPtr((__bridge MTL::CommandBuffer*)commandBuffer); + } } const mbgl::mtl::RendererBackend& getBackend() const override { return backend; } @@ -219,3 +221,14 @@ void swap() override { size = { static_cast(mapView.bounds.size.width * scaleFactor), static_cast(mapView.bounds.size.height * scaleFactor) }; } + +MLNBackendResource MLNMapViewMetalImpl::getObject() { + auto& resource = getResource(); + auto renderPassDescriptor = resource.getRenderPassDescriptor().get(); + return { + resource.mtlView, + resource.mtlView.device, + [MTLRenderPassDescriptor renderPassDescriptor], + resource.commandBuffer + }; +} diff --git a/platform/ios/src/MLNMapView+OpenGL.h b/platform/ios/src/MLNMapView+OpenGL.h index 555393a8afe..3e455f87d7c 100644 --- a/platform/ios/src/MLNMapView+OpenGL.h +++ b/platform/ios/src/MLNMapView+OpenGL.h @@ -56,5 +56,6 @@ class MLNMapViewOpenGLImpl final : public MLNMapViewImpl, void deleteView() override; UIImage* snapshot() override; void layoutChanged() override; + MLNBackendResource getObject() override; // End implementation of MLNMapViewImpl }; diff --git a/platform/ios/src/MLNMapView+OpenGL.mm b/platform/ios/src/MLNMapView+OpenGL.mm index b8c4d008ec0..3e3cc000628 100644 --- a/platform/ios/src/MLNMapView+OpenGL.mm +++ b/platform/ios/src/MLNMapView+OpenGL.mm @@ -252,3 +252,7 @@ void bind() override { auto& resource = getResource(); return resource.context; } + +MLNBackendResource MLNMapViewOpenGLImpl::getObject() { + return MLNBackendResource(); +} diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index 292cc6e2edc..6ac72f50c97 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -6,6 +6,7 @@ #import "MLNMapCamera.h" #import "MLNTypes.h" #import "MLNStyle.h" +#import "MLNBackendResource.h" NS_ASSUME_NONNULL_BEGIN @@ -2000,6 +2001,8 @@ MLN_EXPORT */ @property (nonatomic) MLNMapDebugMaskOptions debugMask; + +- (MLNBackendResource)backendResource; @end NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 196284ad7d6..4f0657bd61a 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -7327,6 +7327,10 @@ - (void)prepareForInterfaceBuilder return _annotationViewReuseQueueByIdentifier[identifier]; } +- (MLNBackendResource)backendResource { + return _mbglView->getObject(); +} + @end // MARK: - IBAdditions methods diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index 076b7f6f95d..a926ad438dd 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -46,6 +46,7 @@ FOUNDATION_EXPORT MLN_EXPORT const unsigned char MapboxVersionString[]; #import "MLNOfflinePack.h" #import "MLNOfflineRegion.h" #import "MLNOfflineStorage.h" +#import "MLNCustomStyleLayer.h" #import "MLNOverlay.h" #import "MLNPointAnnotation.h" #import "MLNPointCollection.h" diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index e543287f6b2..4cafdc46e17 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -1,7 +1,7 @@ #import "MapDocument.h" #import "AppDelegate.h" -#import "LimeGreenStyleLayer.h" +#import "CustomStyleLayerExample.h" #import "DroppedPinAnnotation.h" #import "MLNMapsnapshotter.h" @@ -787,7 +787,7 @@ - (IBAction)insertCustomStyleLayer:(id)sender { [self.undoManager setActionName:@"Add Lime Green Layer"]; } - LimeGreenStyleLayer *layer = [[LimeGreenStyleLayer alloc] initWithIdentifier:@"mbx-custom"]; + CustomStyleLayerExample *layer = [[CustomStyleLayerExample alloc] initWithIdentifier:@"mbx-custom"]; MLNStyleLayer *houseNumberLayer = [self.mapView.style layerWithIdentifier:@"housenum-label"]; if (houseNumberLayer) { [self.mapView.style insertLayer:layer belowLayer:houseNumberLayer]; diff --git a/platform/macos/sdk-files.json b/platform/macos/sdk-files.json index 609902500f6..91329e6ddf0 100644 --- a/platform/macos/sdk-files.json +++ b/platform/macos/sdk-files.json @@ -19,7 +19,7 @@ "platform/macos/src/MLNAnnotationImage.m", "platform/darwin/src/NSExpression+MLNAdditions.mm", "platform/darwin/src/MLNFeature.mm", - "platform/darwin/src/MLNOpenGLStyleLayer.mm", + "platform/darwin/src/MLNCustomStyleLayer.mm", "platform/macos/src/NSColor+MLNAdditions.mm", "platform/macos/src/MLNAttributionButton.mm", "platform/darwin/src/MLNFillStyleLayer.mm", @@ -108,7 +108,7 @@ "MLNBackgroundStyleLayer.h": "platform/darwin/src/MLNBackgroundStyleLayer.h", "MLNPointCollection.h": "platform/darwin/src/MLNPointCollection.h", "MLNShape.h": "platform/darwin/src/MLNShape.h", - "MLNOpenGLStyleLayer.h": "platform/darwin/src/MLNOpenGLStyleLayer.h", + "MLNCustomStyleLayer.h": "platform/darwin/src/MLNCustomStyleLayer.h", "MLNSource.h": "platform/darwin/src/MLNSource.h", "MLNPolygon.h": "platform/darwin/src/MLNPolygon.h", "MLNClockDirectionFormatter.h": "platform/darwin/src/MLNClockDirectionFormatter.h", @@ -202,7 +202,7 @@ "NSDate+MLNAdditions.h": "platform/darwin/src/NSDate+MLNAdditions.h", "NSColor+MLNAdditions.h": "platform/macos/src/NSColor+MLNAdditions.h", "MLNMultiPoint_Private.h": "platform/darwin/src/MLNMultiPoint_Private.h", - "MLNOpenGLStyleLayer_Private.h": "platform/darwin/src/MLNOpenGLStyleLayer_Private.h", + "MLNCustomStyleLayer_Private.h": "platform/darwin/src/MLNCustomStyleLayer_Private.h", "MLNPolygon_Private.h": "platform/darwin/src/MLNPolygon_Private.h", "MLNShape_Private.h": "platform/darwin/src/MLNShape_Private.h", "MLNTilePyramidOfflineRegion_Private.h": "platform/darwin/src/MLNTilePyramidOfflineRegion_Private.h", diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 0ae1f1de2fc..d8aeab39129 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -47,7 +47,7 @@ FOUNDATION_EXPORT MLN_EXPORT const unsigned char MapboxVersionString[]; #import "MLNBackgroundStyleLayer.h" #import "MLNHeatmapStyleLayer.h" #import "MLNHillshadeStyleLayer.h" -#import "MLNOpenGLStyleLayer.h" +#import "MLNCustomStyleLayer.h" #import "MLNSource.h" #import "MLNTileSource.h" #import "MLNVectorTileSource.h" diff --git a/src/mbgl/gfx/drawable_custom_layer_host_tweaker.cpp b/src/mbgl/gfx/drawable_custom_layer_host_tweaker.cpp index 6a19ac676f0..6bf00fb45ae 100644 --- a/src/mbgl/gfx/drawable_custom_layer_host_tweaker.cpp +++ b/src/mbgl/gfx/drawable_custom_layer_host_tweaker.cpp @@ -6,30 +6,27 @@ #include #include +#if MLN_RENDER_BACKEND_METAL +#include +#endif + +#include + namespace mbgl { namespace gfx { void DrawableCustomLayerHostTweaker::execute([[maybe_unused]] gfx::Drawable& drawable, - const PaintParameters& paintParameters) { + const mbgl::PaintParameters& paintParameters) { // custom drawing auto& context = paintParameters.context; - const TransformState& state = paintParameters.state; context.resetState(paintParameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly), paintParameters.colorModeForRenderPass()); - style::CustomLayerRenderParameters parameters; - - parameters.width = state.getSize().width; - parameters.height = state.getSize().height; - parameters.latitude = state.getLatLng().latitude(); - parameters.longitude = state.getLatLng().longitude(); - parameters.zoom = state.getZoom(); - parameters.bearing = util::rad2deg(-state.getBearing()); - parameters.pitch = state.getPitch(); - parameters.fieldOfView = state.getFieldOfView(); - mat4 projMatrix; - state.getProjMatrix(projMatrix); - parameters.projectionMatrix = projMatrix; +#if MLN_RENDER_BACKEND_METAL + style::mtl::CustomLayerRenderParameters parameters(paintParameters); +#else + style::CustomLayerRenderParameters parameters(paintParameters); +#endif host->render(parameters); diff --git a/src/mbgl/gl/drawable_gl.cpp b/src/mbgl/gl/drawable_gl.cpp index 4560f46651c..33499312cab 100644 --- a/src/mbgl/gl/drawable_gl.cpp +++ b/src/mbgl/gl/drawable_gl.cpp @@ -145,7 +145,12 @@ struct IndexBufferGL : public gfx::IndexBufferBase { }; void DrawableGL::upload(gfx::UploadPass& uploadPass) { + if (isCustom) { + return; + } if (!shader) { + Log::Warning(Event::General, "Missing shader for drawable " + util::toString(getID()) + "/" + getName()); + assert(false); return; } diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index d0883842c11..865a22b893b 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -479,6 +479,9 @@ MTL::VertexFormat mtlVertexTypeOf(gfx::AttributeDataType type) noexcept { } // namespace void Drawable::upload(gfx::UploadPass& uploadPass_) { + if (isCustom) { + return; + } if (!shader) { Log::Warning(Event::General, "Missing shader for drawable " + util::toString(getID()) + "/" + getName()); assert(false); diff --git a/src/mbgl/mtl/render_pass.cpp b/src/mbgl/mtl/render_pass.cpp index f09feb4654e..0841768a2cf 100644 --- a/src/mbgl/mtl/render_pass.cpp +++ b/src/mbgl/mtl/render_pass.cpp @@ -15,9 +15,7 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const commandEncoder(commandEncoder_) { auto& resource = descriptor.renderable.getResource(); - if (!resource.getCommandBuffer()) { - resource.bind(); - } + resource.bind(); if (const auto& buffer = resource.getCommandBuffer()) { if (auto rpd = resource.getRenderPassDescriptor()) { diff --git a/src/mbgl/mtl/upload_pass.cpp b/src/mbgl/mtl/upload_pass.cpp index cf1ef9cf604..b8e6ab7cab2 100644 --- a/src/mbgl/mtl/upload_pass.cpp +++ b/src/mbgl/mtl/upload_pass.cpp @@ -19,9 +19,7 @@ UploadPass::UploadPass(gfx::Renderable& renderable, CommandEncoder& commandEncod : commandEncoder(commandEncoder_) { auto& resource = renderable.getResource(); - if (!resource.getCommandBuffer()) { - resource.bind(); - } + resource.bind(); if (const auto& buffer_ = resource.getCommandBuffer()) { buffer = buffer_; diff --git a/src/mbgl/renderer/layers/render_custom_layer.cpp b/src/mbgl/renderer/layers/render_custom_layer.cpp index 39647a08ff7..86168a1c562 100644 --- a/src/mbgl/renderer/layers/render_custom_layer.cpp +++ b/src/mbgl/renderer/layers/render_custom_layer.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #endif #if MLN_DRAWABLE_RENDERER @@ -86,7 +87,6 @@ void RenderCustomLayer::render(PaintParameters& paintParameters) { // TODO: remove cast auto& glContext = static_cast(paintParameters.context); - const TransformState& state = paintParameters.state; // Reset GL state to a known state so the CustomLayer always has a clean slate. glContext.bindVertexArray = 0; @@ -95,21 +95,7 @@ void RenderCustomLayer::render(PaintParameters& paintParameters) { glContext.setColorMode(paintParameters.colorModeForRenderPass()); glContext.setCullFaceMode(gfx::CullFaceMode::disabled()); - CustomLayerRenderParameters parameters; - - parameters.width = state.getSize().width; - parameters.height = state.getSize().height; - parameters.latitude = state.getLatLng().latitude(); - parameters.longitude = state.getLatLng().longitude(); - parameters.zoom = state.getZoom(); - parameters.bearing = util::rad2deg(-state.getBearing()); - parameters.pitch = state.getPitch(); - parameters.fieldOfView = state.getFieldOfView(); - mat4 projMatrix; - state.getProjMatrix(projMatrix); - parameters.projectionMatrix = projMatrix; - - MBGL_CHECK_ERROR(host->render(parameters)); + MBGL_CHECK_ERROR(host->render(CustomLayerRenderParameters(paintParameters))); // Reset the view back to our original one, just in case the CustomLayer // changed the viewport or Framebuffer. diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index 315f3ac401b..3e115f6ccea 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -350,7 +350,7 @@ class OutlineDrawableTweaker : public gfx::DrawableTweaker { void init(gfx::Drawable&) override{}; - void execute(gfx::Drawable& drawable, const PaintParameters& parameters) override { + virtual void execute(gfx::Drawable& drawable, const PaintParameters& parameters) override { if (!drawable.getTileID().has_value()) { return; } diff --git a/src/mbgl/renderer/render_tree.hpp b/src/mbgl/renderer/render_tree.hpp index f6bf888dbb5..bf106e5e72e 100644 --- a/src/mbgl/renderer/render_tree.hpp +++ b/src/mbgl/renderer/render_tree.hpp @@ -13,8 +13,8 @@ namespace mbgl { -class PaintParameters; class PatternAtlas; +class PaintParameters; namespace gfx { class UploadPass; diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 0546d96afad..56dc856e1e3 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mbgl/style/layers/custom_drawable_layer.cpp b/src/mbgl/style/layers/custom_drawable_layer.cpp index d7ee3b3e2fe..9b8525ec075 100644 --- a/src/mbgl/style/layers/custom_drawable_layer.cpp +++ b/src/mbgl/style/layers/custom_drawable_layer.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mbgl/style/layers/custom_layer_render_parameters.cpp b/src/mbgl/style/layers/custom_layer_render_parameters.cpp new file mode 100644 index 00000000000..24a869657c3 --- /dev/null +++ b/src/mbgl/style/layers/custom_layer_render_parameters.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +namespace mbgl { +namespace style { + +CustomLayerRenderParameters::CustomLayerRenderParameters(const mbgl::PaintParameters& paintParameters) { + const TransformState& state = paintParameters.state; + width = state.getSize().width; + height = state.getSize().height; + latitude = state.getLatLng().latitude(); + longitude = state.getLatLng().longitude(); + zoom = state.getZoom(); + bearing = util::rad2deg(-state.getBearing()); + pitch = state.getPitch(); + fieldOfView = state.getFieldOfView(); + mat4 projMatrix; + state.getProjMatrix(projMatrix); + projectionMatrix = projMatrix; +} + +} // namespace style +} // namespace mbgl \ No newline at end of file diff --git a/src/mbgl/style/layers/mtl/custom_layer_render_parameters.cpp b/src/mbgl/style/layers/mtl/custom_layer_render_parameters.cpp new file mode 100644 index 00000000000..660234e6459 --- /dev/null +++ b/src/mbgl/style/layers/mtl/custom_layer_render_parameters.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace style { +namespace mtl { + +CustomLayerRenderParameters::CustomLayerRenderParameters(const mbgl::PaintParameters& paintParameters) + : mbgl::style::CustomLayerRenderParameters(paintParameters) { + const mbgl::mtl::RenderPass& renderPass = static_cast(*paintParameters.renderPass); + encoder = renderPass.getMetalEncoder(); +} + +} // namespace mtl +} // namespace style +} // namespace mbgl diff --git a/test/api/custom_layer.test.cpp b/test/api/custom_layer.test.cpp index 885d8c2cc59..20cdf3a2bc1 100644 --- a/test/api/custom_layer.test.cpp +++ b/test/api/custom_layer.test.cpp @@ -15,6 +15,8 @@ #include #include +#include + using namespace mbgl; using namespace mbgl::style; using namespace mbgl::platform; From 612b256821d379d86e47bd90fc29ccf922b57173 Mon Sep 17 00:00:00 2001 From: Alex Cristici Date: Thu, 15 Feb 2024 21:28:20 +0200 Subject: [PATCH 70/96] Texture by index instead of map (#2107) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CMakeLists.txt | 2 +- bazel/core.bzl | 2 +- include/mbgl/gfx/drawable.hpp | 22 ++--- include/mbgl/gfx/drawable_atlases_tweaker.hpp | 14 +-- include/mbgl/gfx/drawable_builder.hpp | 12 +-- include/mbgl/gfx/uniform_block.hpp | 2 +- include/mbgl/gfx/uniform_buffer.hpp | 3 +- include/mbgl/mtl/drawable.hpp | 1 + include/mbgl/shaders/gl/shader_group_gl.hpp | 2 +- include/mbgl/shaders/gl/shader_info.hpp | 57 +++++++++--- include/mbgl/shaders/gl/shader_program_gl.hpp | 10 +-- include/mbgl/shaders/mtl/shader_program.hpp | 9 +- .../{ubo_max_count.hpp => shader_defines.hpp} | 68 ++++++++++++++ include/mbgl/shaders/shader_program_base.hpp | 2 +- src/mbgl/gfx/drawable.cpp | 17 ++-- src/mbgl/gfx/drawable_atlases_tweaker.cpp | 19 ++-- src/mbgl/gfx/drawable_builder.cpp | 14 ++- src/mbgl/gfx/uniform_block.cpp | 3 +- src/mbgl/gfx/uniform_buffer.cpp | 3 +- src/mbgl/gl/drawable_gl.cpp | 23 ++--- src/mbgl/gl/drawable_gl_builder.cpp | 2 +- src/mbgl/mtl/drawable.cpp | 26 +++--- src/mbgl/mtl/drawable_builder.cpp | 2 +- .../layers/background_layer_tweaker.cpp | 24 ++--- .../layers/fill_extrusion_layer_tweaker.cpp | 14 +-- .../renderer/layers/fill_layer_tweaker.cpp | 11 +-- .../renderer/layers/line_layer_tweaker.cpp | 21 ++--- .../layers/render_fill_extrusion_layer.cpp | 4 +- .../renderer/layers/render_fill_layer.cpp | 3 +- .../renderer/layers/render_heatmap_layer.cpp | 21 ++--- .../layers/render_hillshade_layer.cpp | 29 ++---- .../layers/render_hillshade_layer.hpp | 1 - .../renderer/layers/render_line_layer.cpp | 48 +++++----- .../renderer/layers/render_raster_layer.cpp | 16 ++-- .../renderer/layers/render_raster_layer.hpp | 2 - .../renderer/layers/render_symbol_layer.cpp | 10 +-- .../renderer/layers/symbol_layer_tweaker.cpp | 17 ++-- .../renderer/sources/render_tile_source.cpp | 7 +- src/mbgl/shaders/gl/shader_info.cpp | 89 ++++++++++++++++++- src/mbgl/shaders/gl/shader_program_gl.cpp | 53 +++-------- src/mbgl/shaders/mtl/background_pattern.cpp | 2 +- src/mbgl/shaders/mtl/debug.cpp | 2 +- src/mbgl/shaders/mtl/fill.cpp | 4 +- .../shaders/mtl/fill_extrusion_pattern.cpp | 2 +- src/mbgl/shaders/mtl/heatmap_texture.cpp | 4 +- src/mbgl/shaders/mtl/hillshade.cpp | 2 +- src/mbgl/shaders/mtl/hillshade_prepare.cpp | 2 +- src/mbgl/shaders/mtl/line.cpp | 4 +- src/mbgl/shaders/mtl/line_gradient.cpp | 2 +- src/mbgl/shaders/mtl/raster.cpp | 4 +- src/mbgl/shaders/mtl/shader_program.cpp | 18 ++-- src/mbgl/shaders/mtl/symbol_icon.cpp | 2 +- src/mbgl/shaders/mtl/symbol_sdf.cpp | 2 +- src/mbgl/shaders/mtl/symbol_text_and_icon.cpp | 4 +- 54 files changed, 404 insertions(+), 335 deletions(-) rename include/mbgl/shaders/{ubo_max_count.hpp => shader_defines.hpp} (59%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 944ea282370..8bba574fe4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1143,8 +1143,8 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/line_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/raster_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_defines.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/symbol_layer_ubo.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/ubo_max_count.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl_builder.hpp diff --git a/bazel/core.bzl b/bazel/core.bzl index 9a756fa84ff..b09873d2549 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -990,9 +990,9 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/layer_ubo.hpp", "include/mbgl/shaders/line_layer_ubo.hpp", "include/mbgl/shaders/raster_layer_ubo.hpp", + "include/mbgl/shaders/shader_defines.hpp", "include/mbgl/shaders/shader_program_base.hpp", "include/mbgl/shaders/symbol_layer_ubo.hpp", - "include/mbgl/shaders/ubo_max_count.hpp", "include/mbgl/util/identity.hpp", "include/mbgl/util/suppress_copies.hpp", "include/mbgl/style/layers/custom_drawable_layer.hpp", diff --git a/include/mbgl/gfx/drawable.hpp b/include/mbgl/gfx/drawable.hpp index 621e16f8f5b..42143d84e94 100644 --- a/include/mbgl/gfx/drawable.hpp +++ b/include/mbgl/gfx/drawable.hpp @@ -44,8 +44,8 @@ using VertexAttributeArrayPtr = std::shared_ptr; class Drawable { public: - /// @brief Map from sampler location to texture info - using Textures = mbgl::unordered_map; + /// @brief Array of textures to bind + using Textures = std::array; protected: Drawable(std::string name); @@ -99,26 +99,18 @@ class Drawable { /// Set line width void setLineWidth(int32_t value) { lineWidth = value; } - /// @brief Remove an attached texture from this drawable at the given sampler location - /// @param location Texture sampler location - void removeTexture(int32_t location); - - /// @brief Return the textures attached to this drawable - /// @return Texture and sampler location pairs - const Textures& getTextures() const { return textures; }; - - /// @brief Get the texture at the given sampler location. - const gfx::Texture2DPtr& getTexture(int32_t location) const; + /// @brief Get the texture at the given internal ID. + const gfx::Texture2DPtr& getTexture(size_t id) const; /// @brief Set the collection of textures bound to this drawable /// @param textures_ A Textures collection to set void setTextures(const Textures& textures_) noexcept { textures = textures_; } void setTextures(Textures&& textures_) noexcept { textures = std::move(textures_); } - /// @brief Attach the given texture to this drawable at the given sampler location. + /// @brief Attach the given texture to this drawable at the given internal ID. /// @param texture Texture2D instance - /// @param location A sampler location in the shader being used with this drawable. - void setTexture(gfx::Texture2DPtr texture, int32_t location); + /// @param id Internal ID of the texture. + void setTexture(gfx::Texture2DPtr texture, size_t id); /// Whether the drawble should be drawn bool getEnabled() const { return enabled; } diff --git a/include/mbgl/gfx/drawable_atlases_tweaker.hpp b/include/mbgl/gfx/drawable_atlases_tweaker.hpp index 63e123845ba..5d74eb45bd7 100644 --- a/include/mbgl/gfx/drawable_atlases_tweaker.hpp +++ b/include/mbgl/gfx/drawable_atlases_tweaker.hpp @@ -24,22 +24,22 @@ class Drawable; class DrawableAtlasesTweaker : public gfx::DrawableTweaker { public: DrawableAtlasesTweaker(TileAtlasTexturesPtr atlases_, - const std::optional iconNameId_, - const std::optional glyphNameId_, + const std::optional iconTextureId_, + const std::optional glyphTextureId_, bool isText_, const bool sdfIcons_, const style::AlignmentType rotationAlignment_, const bool iconScaled_, const bool textSizeIsZoomConstant_) : atlases(std::move(atlases_)), - iconNameId(iconNameId_), - glyphNameId(glyphNameId_), + iconTextureId(iconTextureId_), + glyphTextureId(glyphTextureId_), isText(isText_), sdfIcons(sdfIcons_), rotationAlignment(rotationAlignment_), iconScaled(iconScaled_), textSizeIsZoomConstant(textSizeIsZoomConstant_) { - assert(iconNameId_ != glyphNameId_); + assert(iconTextureId_ != glyphTextureId_); } ~DrawableAtlasesTweaker() override = default; @@ -51,8 +51,8 @@ class DrawableAtlasesTweaker : public gfx::DrawableTweaker { void setupTextures(Drawable&, const bool); TileAtlasTexturesPtr atlases; - std::optional iconNameId; - std::optional glyphNameId; + std::optional iconTextureId; + std::optional glyphTextureId; bool isText; const bool sdfIcons; diff --git a/include/mbgl/gfx/drawable_builder.hpp b/include/mbgl/gfx/drawable_builder.hpp index 6ce94f852cb..3ec548416ad 100644 --- a/include/mbgl/gfx/drawable_builder.hpp +++ b/include/mbgl/gfx/drawable_builder.hpp @@ -140,13 +140,13 @@ class DrawableBuilder { /// The attribute names for vertex/position attributes void setVertexAttrNameId(const StringIdentity id) { vertexAttrNameId = id; } - /// @brief Attach the given texture at the given sampler location. - /// @param texture Texture2D instance - /// @param location A sampler location in the shader being used. - void setTexture(const gfx::Texture2DPtr&, int32_t location); + /// @brief Get the texture at the given internal ID. + const gfx::Texture2DPtr& getTexture(size_t id) const; - /// Direct access to the current texture set - const gfx::Drawable::Textures& getTextures() const { return textures; } + /// @brief Attach the given texture at the given internal ID. + /// @param texture Texture2D instance + /// @param id Internal ID of the texture. + void setTexture(const gfx::Texture2DPtr&, size_t id); /// Add a tweaker to emitted drawable void addTweaker(DrawableTweakerPtr value) { tweakers.emplace_back(std::move(value)); } diff --git a/include/mbgl/gfx/uniform_block.hpp b/include/mbgl/gfx/uniform_block.hpp index d93d49acf5f..70f267a6c4b 100644 --- a/include/mbgl/gfx/uniform_block.hpp +++ b/include/mbgl/gfx/uniform_block.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace mbgl { namespace gfx { diff --git a/include/mbgl/gfx/uniform_buffer.hpp b/include/mbgl/gfx/uniform_buffer.hpp index 8b21ee69e02..5ae27356362 100644 --- a/include/mbgl/gfx/uniform_buffer.hpp +++ b/include/mbgl/gfx/uniform_buffer.hpp @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include namespace mbgl { namespace gfx { diff --git a/include/mbgl/mtl/drawable.hpp b/include/mbgl/mtl/drawable.hpp index ddeaccb1486..83370b782a5 100644 --- a/include/mbgl/mtl/drawable.hpp +++ b/include/mbgl/mtl/drawable.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include diff --git a/include/mbgl/shaders/gl/shader_group_gl.hpp b/include/mbgl/shaders/gl/shader_group_gl.hpp index 1c64db0c3f0..10677538e0f 100644 --- a/include/mbgl/shaders/gl/shader_group_gl.hpp +++ b/include/mbgl/shaders/gl/shader_group_gl.hpp @@ -55,9 +55,9 @@ class ShaderGroupGL final : public gfx::ShaderGroup { auto& glContext = static_cast(context); shader = ShaderProgramGL::create(glContext, programParameters, - shaderName, firstAttribName, shaders::ShaderInfo::uniformBlocks, + shaders::ShaderInfo::textures, vert, frag, additionalDefines); diff --git a/include/mbgl/shaders/gl/shader_info.hpp b/include/mbgl/shaders/gl/shader_info.hpp index 980e4faa15d..7baba27c9cf 100644 --- a/include/mbgl/shaders/gl/shader_info.hpp +++ b/include/mbgl/shaders/gl/shader_info.hpp @@ -15,132 +15,163 @@ struct UniformBlockInfo { std::size_t binding; }; +struct TextureInfo { + TextureInfo(std::string_view name, std::size_t id); + std::string_view name; + std::size_t id; +}; + template struct ShaderInfo; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> -struct ShaderInfo { +struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; template <> struct ShaderInfo { static const std::vector uniformBlocks; + static const std::vector textures; }; } // namespace shaders diff --git a/include/mbgl/shaders/gl/shader_program_gl.hpp b/include/mbgl/shaders/gl/shader_program_gl.hpp index 4836a730e25..f1739f2d335 100644 --- a/include/mbgl/shaders/gl/shader_program_gl.hpp +++ b/include/mbgl/shaders/gl/shader_program_gl.hpp @@ -17,13 +17,13 @@ namespace gl { class ShaderProgramGL final : public gfx::ShaderProgramBase { public: - using SamplerLocationMap = std::unordered_map; + using SamplerLocationArray = std::array, shaders::maxTextureCountPerShader>; ShaderProgramGL(UniqueProgram&& glProgram_); ShaderProgramGL(UniqueProgram&&, UniformBlockArrayGL&& uniformBlocks, VertexAttributeArrayGL&& attributes, - SamplerLocationMap&& samplerLocations); + SamplerLocationArray&& samplerLocations); ShaderProgramGL(ShaderProgramGL&& other); ~ShaderProgramGL() noexcept override = default; @@ -32,14 +32,14 @@ class ShaderProgramGL final : public gfx::ShaderProgramBase { static std::shared_ptr create(Context&, const ProgramParameters& programParameters, - const std::string& name, const std::string_view firstAttribName, const std::vector& uniformBlocksInfo, + const std::vector& texturesInfo, const std::string& vertexSource, const std::string& fragmentSource, const std::string& additionalDefines = "") noexcept(false); - std::optional getSamplerLocation(const StringIdentity id) const override; + std::optional getSamplerLocation(const size_t id) const override; const gfx::UniformBlockArray& getUniformBlocks() const override { return uniformBlocks; } @@ -55,7 +55,7 @@ class ShaderProgramGL final : public gfx::ShaderProgramBase { UniformBlockArrayGL uniformBlocks; VertexAttributeArrayGL vertexAttributes; - SamplerLocationMap samplerLocations; + SamplerLocationArray samplerLocations; }; } // namespace gl diff --git a/include/mbgl/shaders/mtl/shader_program.hpp b/include/mbgl/shaders/mtl/shader_program.hpp index bea1380c957..e11d477796a 100644 --- a/include/mbgl/shaders/mtl/shader_program.hpp +++ b/include/mbgl/shaders/mtl/shader_program.hpp @@ -29,10 +29,9 @@ struct UniformBlockInfo { std::size_t id; }; struct TextureInfo { - TextureInfo(std::size_t index, std::string_view name); + TextureInfo(std::size_t index, std::size_t id); std::size_t index; - std::string_view name; - StringIdentity nameID; + std::size_t id; }; } // namespace shaders namespace mtl { @@ -56,7 +55,7 @@ class ShaderProgram final : public gfx::ShaderProgramBase { const MTLVertexDescriptorPtr&, const gfx::ColorMode& colorMode) const; - std::optional getSamplerLocation(const StringIdentity id) const override; + std::optional getSamplerLocation(const size_t id) const override; const gfx::VertexAttributeArray& getVertexAttributes() const override { return vertexAttributes; } @@ -74,7 +73,7 @@ class ShaderProgram final : public gfx::ShaderProgramBase { MTLFunctionPtr fragmentFunction; UniformBlockArray uniformBlocks; VertexAttributeArray vertexAttributes; - std::unordered_map textureBindings; + std::array, shaders::maxTextureCountPerShader> textureBindings; }; } // namespace mtl diff --git a/include/mbgl/shaders/ubo_max_count.hpp b/include/mbgl/shaders/shader_defines.hpp similarity index 59% rename from include/mbgl/shaders/ubo_max_count.hpp rename to include/mbgl/shaders/shader_defines.hpp index 32f2620978f..9ee9fc0a988 100644 --- a/include/mbgl/shaders/ubo_max_count.hpp +++ b/include/mbgl/shaders/shader_defines.hpp @@ -40,5 +40,73 @@ static constexpr auto maxUBOCountPerShader = std::max({static_cast(backg static_cast(rasterUBOCount), static_cast(symbolUBOCount)}); +enum { + idBackgroundImageTexture, + backgroundTextureCount +}; + +enum { + circleTextureCount +}; + +enum { + collisionTextureCount +}; + +enum { + idDebugOverlayTexture, + debugTextureCount +}; + +enum { + idFillExtrusionImageTexture, + fillExtrusionTextureCount +}; + +enum { + idFillImageTexture, + fillTextureCount +}; + +enum { + idHeatmapImageTexture, + idHeatmapColorRampTexture, + heatmapTextureCount +}; + +enum { + idHillshadeImageTexture, + hillshadeTextureCount +}; + +enum { + idLineImageTexture, + lineTextureCount +}; + +enum { + idRasterImage0Texture, + idRasterImage1Texture, + rasterTextureCount +}; + +enum { + idSymbolImageTexture, + idSymbolImageIconTexture, + symbolTextureCount +}; + +static constexpr auto maxTextureCountPerShader = std::max({static_cast(backgroundTextureCount), + static_cast(circleTextureCount), + static_cast(collisionTextureCount), + static_cast(debugTextureCount), + static_cast(fillTextureCount), + static_cast(fillExtrusionTextureCount), + static_cast(heatmapTextureCount), + static_cast(hillshadeTextureCount), + static_cast(lineTextureCount), + static_cast(rasterTextureCount), + static_cast(symbolTextureCount)}); + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/shader_program_base.hpp b/include/mbgl/shaders/shader_program_base.hpp index acdb80127a9..ff7ff22ddb4 100644 --- a/include/mbgl/shaders/shader_program_base.hpp +++ b/include/mbgl/shaders/shader_program_base.hpp @@ -33,7 +33,7 @@ class ShaderProgramBase : public gfx::Shader { /// @brief Gets the sampler location /// @param name uniform name - virtual std::optional getSamplerLocation(const StringIdentity) const = 0; + virtual std::optional getSamplerLocation(const size_t) const = 0; /// Get the available uniform blocks attached to this shader virtual const gfx::UniformBlockArray& getUniformBlocks() const = 0; diff --git a/src/mbgl/gfx/drawable.cpp b/src/mbgl/gfx/drawable.cpp index fd9441549f5..69dd9802217 100644 --- a/src/mbgl/gfx/drawable.cpp +++ b/src/mbgl/gfx/drawable.cpp @@ -45,17 +45,16 @@ void Drawable::setIndexData(std::vector indexes, std::vectorsecond : noTexture; +const gfx::Texture2DPtr& Drawable::getTexture(size_t id) const { + return (id < textures.size()) ? textures[id] : noTexture; } -void Drawable::setTexture(std::shared_ptr texture, int32_t location) { - textures.insert(std::make_pair(location, gfx::Texture2DPtr{})).first->second = std::move(texture); -} - -void Drawable::removeTexture(int32_t location) { - textures.erase(location); +void Drawable::setTexture(std::shared_ptr texture, size_t id) { + assert(id < textures.size()); + if (id >= textures.size()) { + return; + } + textures[id] = std::move(texture); } } // namespace gfx diff --git a/src/mbgl/gfx/drawable_atlases_tweaker.cpp b/src/mbgl/gfx/drawable_atlases_tweaker.cpp index 120c37ed15e..cba50660b68 100644 --- a/src/mbgl/gfx/drawable_atlases_tweaker.cpp +++ b/src/mbgl/gfx/drawable_atlases_tweaker.cpp @@ -8,16 +8,9 @@ namespace mbgl { namespace gfx { -namespace { -std::optional getSamplerLocation(const gfx::ShaderProgramBasePtr& shader, - const std::optional& nameId) { - return nameId ? shader->getSamplerLocation(*nameId) : std::nullopt; -} -} // namespace - void DrawableAtlasesTweaker::setupTextures(gfx::Drawable& drawable, const bool linearFilterForIcons) { if (const auto& shader = drawable.getShader()) { - if (const auto samplerLocation = getSamplerLocation(shader, glyphNameId)) { + if (glyphTextureId) { if (atlases) { atlases->glyph->setSamplerConfiguration( {TextureFilterType::Linear, TextureWrapType::Clamp, TextureWrapType::Clamp}); @@ -26,12 +19,12 @@ void DrawableAtlasesTweaker::setupTextures(gfx::Drawable& drawable, const bool l TextureWrapType::Clamp, TextureWrapType::Clamp}); } - if (const auto iconSamplerLocation = getSamplerLocation(shader, iconNameId)) { - assert(*samplerLocation != *iconSamplerLocation); - drawable.setTexture(atlases ? atlases->glyph : nullptr, *samplerLocation); - drawable.setTexture(atlases ? atlases->icon : nullptr, *iconSamplerLocation); + if (iconTextureId && shader->getSamplerLocation(*iconTextureId)) { + assert(*glyphTextureId != *iconTextureId); + drawable.setTexture(atlases ? atlases->glyph : nullptr, *glyphTextureId); + drawable.setTexture(atlases ? atlases->icon : nullptr, *iconTextureId); } else { - drawable.setTexture(atlases ? (isText ? atlases->glyph : atlases->icon) : nullptr, *samplerLocation); + drawable.setTexture(atlases ? (isText ? atlases->glyph : atlases->icon) : nullptr, *glyphTextureId); } } } diff --git a/src/mbgl/gfx/drawable_builder.cpp b/src/mbgl/gfx/drawable_builder.cpp index e34dafcafeb..15fcc0bcf9b 100644 --- a/src/mbgl/gfx/drawable_builder.cpp +++ b/src/mbgl/gfx/drawable_builder.cpp @@ -102,8 +102,18 @@ void DrawableBuilder::resetDrawPriority(DrawPriority value) { } } -void DrawableBuilder::setTexture(const std::shared_ptr& texture, int32_t location) { - textures.insert(std::make_pair(location, gfx::Texture2DPtr{})).first->second = std::move(texture); +static const gfx::Texture2DPtr noTexture; + +const gfx::Texture2DPtr& DrawableBuilder::getTexture(size_t id) const { + return (id < textures.size()) ? textures[id] : noTexture; +} + +void DrawableBuilder::setTexture(const std::shared_ptr& texture, size_t id) { + assert(id < textures.size()); + if (id >= textures.size()) { + return; + } + textures[id] = std::move(texture); } void DrawableBuilder::addTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2) { diff --git a/src/mbgl/gfx/uniform_block.cpp b/src/mbgl/gfx/uniform_block.cpp index c8023071148..472d2dbfa0e 100644 --- a/src/mbgl/gfx/uniform_block.cpp +++ b/src/mbgl/gfx/uniform_block.cpp @@ -21,8 +21,7 @@ UniformBlockArray& UniformBlockArray::operator=(const UniformBlockArray& other) } const std::unique_ptr& UniformBlockArray::get(const size_t id) const { - const auto& result = (id < uniformBlockVector.size()) ? uniformBlockVector[id] : nullref; - return (result != nullptr) ? result : nullref; + return (id < uniformBlockVector.size()) ? uniformBlockVector[id] : nullref; } const std::unique_ptr& UniformBlockArray::set(const size_t id, const size_t index, std::size_t size) { diff --git a/src/mbgl/gfx/uniform_buffer.cpp b/src/mbgl/gfx/uniform_buffer.cpp index 8370eee4c24..5f357b99944 100644 --- a/src/mbgl/gfx/uniform_buffer.cpp +++ b/src/mbgl/gfx/uniform_buffer.cpp @@ -23,8 +23,7 @@ UniformBufferArray& UniformBufferArray::operator=(const UniformBufferArray& othe } const std::shared_ptr& UniformBufferArray::get(const size_t id) const { - const auto& result = (id < uniformBufferVector.size()) ? uniformBufferVector[id] : nullref; - return (result != nullptr) ? result : nullref; + return (id < uniformBufferVector.size()) ? uniformBufferVector[id] : nullref; } const std::shared_ptr& UniformBufferArray::set(const size_t id, diff --git a/src/mbgl/gl/drawable_gl.cpp b/src/mbgl/gl/drawable_gl.cpp index 33499312cab..5b3ce61d43c 100644 --- a/src/mbgl/gl/drawable_gl.cpp +++ b/src/mbgl/gl/drawable_gl.cpp @@ -223,7 +223,7 @@ void DrawableGL::upload(gfx::UploadPass& uploadPass) { } const bool texturesNeedUpload = std::any_of( - textures.begin(), textures.end(), [](const auto& pair) { return pair.second && pair.second->needsUpload(); }); + textures.begin(), textures.end(), [](const auto& texture) { return texture && texture->needsUpload(); }); if (texturesNeedUpload) { uploadTextures(); @@ -245,27 +245,28 @@ gfx::StencilMode DrawableGL::makeStencilMode(PaintParameters& parameters) const } void DrawableGL::uploadTextures() const { - for (const auto& pair : textures) { - if (const auto& tex = pair.second) { - std::static_pointer_cast(tex)->upload(); + for (const auto& texture : textures) { + if (texture) { + texture->upload(); } } } void DrawableGL::bindTextures() const { int32_t unit = 0; - for (const auto& pair : textures) { - if (const auto& tex = pair.second) { - const auto& location = pair.first; - std::static_pointer_cast(tex)->bind(location, unit++); + for (size_t id = 0; id < textures.size(); id++) { + if (const auto& texture = textures[id]) { + if (const auto& location = shader->getSamplerLocation(id)) { + static_cast(*texture).bind(static_cast(*location), unit++); + } } } } void DrawableGL::unbindTextures() const { - for (const auto& pair : textures) { - if (const auto& tex = pair.second) { - std::static_pointer_cast(tex)->unbind(); + for (const auto& texture : textures) { + if (texture) { + static_cast(*texture).unbind(); } } } diff --git a/src/mbgl/gl/drawable_gl_builder.cpp b/src/mbgl/gl/drawable_gl_builder.cpp index f1269e9e363..1b43506c982 100644 --- a/src/mbgl/gl/drawable_gl_builder.cpp +++ b/src/mbgl/gl/drawable_gl_builder.cpp @@ -43,7 +43,7 @@ void DrawableGLBuilder::init() { drawableGL.setIndexData(std::move(impl->sharedIndexes), std::move(impl->segments)); impl->clear(); - textures.clear(); + textures.fill(nullptr); } } // namespace gl diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index 865a22b893b..e3380b4723b 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -386,27 +386,29 @@ void Drawable::bindUniformBuffers(RenderPass& renderPass) const noexcept { } void Drawable::bindTextures(RenderPass& renderPass) const noexcept { - for (const auto& pair : textures) { - if (const auto& tex = pair.second) { - const auto& location = pair.first; - static_cast(*tex).bind(renderPass, location); + for (size_t id = 0; id < textures.size(); id++) { + if (const auto& texture = textures[id]) { + if (const auto& location = shader->getSamplerLocation(id)) { + static_cast(*texture).bind(renderPass, static_cast(*location)); + } } } } void Drawable::unbindTextures(RenderPass& renderPass) const noexcept { - for (const auto& pair : textures) { - if (const auto& tex = pair.second) { - const auto& location = pair.first; - static_cast(*tex).unbind(renderPass, location); + for (size_t id = 0; id < textures.size(); id++) { + if (const auto& texture = textures[id]) { + if (const auto& location = shader->getSamplerLocation(id)) { + static_cast(*texture).unbind(renderPass, static_cast(*location)); + } } } } void Drawable::uploadTextures(UploadPass&) const noexcept { - for (const auto& pair : textures) { - if (const auto& tex = pair.second) { - static_cast(*tex).upload(); + for (const auto& texture : textures) { + if (texture) { + texture->upload(); } } } @@ -584,7 +586,7 @@ void Drawable::upload(gfx::UploadPass& uploadPass_) { } const bool texturesNeedUpload = std::any_of( - textures.begin(), textures.end(), [](const auto& pair) { return pair.second && pair.second->needsUpload(); }); + textures.begin(), textures.end(), [](const auto& texture) { return texture && texture->needsUpload(); }); if (texturesNeedUpload) { uploadTextures(uploadPass); diff --git a/src/mbgl/mtl/drawable_builder.cpp b/src/mbgl/mtl/drawable_builder.cpp index 00d52f55b43..632c6063dad 100644 --- a/src/mbgl/mtl/drawable_builder.cpp +++ b/src/mbgl/mtl/drawable_builder.cpp @@ -42,7 +42,7 @@ void DrawableBuilder::init() { drawable.setIndexData(std::move(impl->sharedIndexes), std::move(impl->segments)); impl->clear(); - textures.clear(); + textures.fill(nullptr); } } // namespace mtl diff --git a/src/mbgl/renderer/layers/background_layer_tweaker.cpp b/src/mbgl/renderer/layers/background_layer_tweaker.cpp index 1fb49074f7e..a7eb61f930e 100644 --- a/src/mbgl/renderer/layers/background_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/background_layer_tweaker.cpp @@ -9,7 +9,6 @@ #include #include #include -#include namespace mbgl { @@ -20,8 +19,6 @@ using namespace shaders; constexpr auto BackgroundPatternShaderName = "BackgroundPatternShader"; #endif -static const StringIdentity idTexUniformName = stringIndexer().get("u_image"); - void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { const auto& state = parameters.state; auto& context = parameters.context; @@ -53,7 +50,6 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara // properties are re-evaluated every time propertiesUpdated = false; - std::optional samplerLocation{}; visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { assert(drawable.getTileID()); if (!drawable.getTileID() || !checkTweakDrawable(drawable)) { @@ -61,9 +57,8 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara } // We assume that drawables don't change between pattern and non-pattern. - const auto& shader = drawable.getShader(); - assert(hasPattern == - (shader == context.getGenericShader(parameters.shaders, std::string(BackgroundPatternShaderName)))); + assert(hasPattern == (drawable.getShader() == + context.getGenericShader(parameters.shaders, std::string(BackgroundPatternShaderName)))); const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); const auto matrix = getTileMatrix( @@ -75,17 +70,10 @@ void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintPara uniforms.createOrUpdate(idBackgroundDrawableUBO, &drawableUBO, context); if (hasPattern) { - if (!samplerLocation.has_value()) { - samplerLocation = shader->getSamplerLocation(idTexUniformName); - if (const auto& tex = parameters.patternAtlas.texture()) { - tex->setSamplerConfiguration( - {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - } - } - if (samplerLocation.has_value()) { - if (const auto& tex = parameters.patternAtlas.texture()) { - drawable.setTexture(tex, samplerLocation.value()); - } + if (const auto& tex = parameters.patternAtlas.texture()) { + tex->setSamplerConfiguration( + {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + drawable.setTexture(tex, idBackgroundImageTexture); } // from BackgroundPatternProgram::layoutUniformValues diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp index b65cb907276..f93da13d0ad 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #if MLN_RENDER_BACKEND_METAL #include @@ -26,11 +25,6 @@ namespace mbgl { using namespace shaders; using namespace style; -namespace { -const StringIdentity idTexImageName = stringIndexer().get("u_image"); - -} // namespace - void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& props = static_cast(*evaluatedProperties); @@ -95,12 +89,8 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintP const auto heightFactor = static_cast(-numTiles / util::tileSize_D / 8.0); Size textureSize = {0, 0}; - if (const auto shader = drawable.getShader()) { - if (const auto index = shader->getSamplerLocation(idTexImageName)) { - if (const auto& tex = drawable.getTexture(*index)) { - textureSize = tex->getSize(); - } - } + if (const auto& tex = drawable.getTexture(idFillExtrusionImageTexture)) { + textureSize = tex->getSize(); } const FillExtrusionDrawableUBO drawableUBO = { diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp index d8d614add18..bfb7020b223 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #if MLN_RENDER_BACKEND_METAL #include @@ -24,8 +23,6 @@ namespace mbgl { using namespace style; - -static const StringIdentity idTexImageName = stringIndexer().get("u_image"); using namespace shaders; void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { @@ -135,12 +132,8 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters const int32_t pixelY = tileSizeAtNearestZoom * tileID.canonical.y; Size textureSize = {0, 0}; - if (const auto shader = drawable.getShader()) { - if (const auto index = shader->getSamplerLocation(idTexImageName)) { - if (const auto& tex = drawable.getTexture(*index)) { - textureSize = tex->getSize(); - } - } + if (const auto& tex = drawable.getTexture(idFillImageTexture)) { + textureSize = tex->getSize(); } auto& uniforms = drawable.mutableUniformBuffers(); diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.cpp b/src/mbgl/renderer/layers/line_layer_tweaker.cpp index 8f2fc7d4bdd..0ebde50af53 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -25,8 +24,6 @@ namespace mbgl { using namespace style; using namespace shaders; -static const StringIdentity idTexImageName = stringIndexer().get("u_image"); - void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto zoom = parameters.state.getZoom(); @@ -164,10 +161,8 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters case LineType::Pattern: { Size textureSize{0, 0}; - if (const auto index = shader->getSamplerLocation(idTexImageName)) { - if (const auto& texture = drawable.getTexture(index.value())) { - textureSize = texture->getSize(); - } + if (const auto& texture = drawable.getTexture(idLineImageTexture)) { + textureSize = texture->getSize(); } const LinePatternUBO linePatternUBO{ /*matrix =*/util::cast(matrix), @@ -198,13 +193,11 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters lineData.linePatternCap); // texture - if (const auto index = shader->getSamplerLocation(idTexImageName)) { - if (!drawable.getTexture(index.value())) { - const auto& texture = dashPatternTexture.getTexture(); - drawable.setEnabled(!!texture); - if (texture) { - drawable.setTexture(texture, index.value()); - } + if (!drawable.getTexture(idLineImageTexture)) { + const auto& texture = dashPatternTexture.getTexture(); + drawable.setEnabled(!!texture); + if (texture) { + drawable.setTexture(texture, idLineImageTexture); } } diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp index 269ba752d03..84db4916c2e 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -42,8 +42,6 @@ namespace { const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); const StringIdentity idNormAttribName = stringIndexer().get("a_normal_ed"); -const StringIdentity idIconTextureName = stringIndexer().get("u_image"); - #endif // MLN_DRAWABLE_RENDERER inline const FillExtrusionLayer::Impl& impl_cast(const Immutable& impl) { @@ -460,7 +458,7 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, if (const auto& atlases = tile.getAtlasTextures()) { tweaker = std::make_shared(atlases, std::nullopt, - idIconTextureName, + idFillExtrusionImageTexture, /*isText=*/false, false, style::AlignmentType::Auto, diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index 3e115f6ccea..361d183ab7a 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -51,7 +51,6 @@ constexpr auto FillPatternShaderName = "FillPatternShader"; constexpr auto FillOutlinePatternShaderName = "FillOutlinePatternShader"; const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); -const StringIdentity idIconTextureName = stringIndexer().get("u_image"); #endif // MLN_DRAWABLE_RENDERER inline const FillLayer::Impl& impl_cast(const Immutable& impl) { @@ -598,7 +597,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, atlasTweaker = std::make_shared( atlases, std::nullopt, - idIconTextureName, + idFillImageTexture, /*isText*/ false, /*sdfIcons*/ true, // to force linear filter /*rotationAlignment_*/ AlignmentType::Auto, diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp index 0541bd67a6e..9b7ecabc0f2 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.cpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -279,8 +279,6 @@ constexpr auto HeatmapShaderGroupName = "HeatmapShader"; constexpr auto HeatmapTextureShaderGroupName = "HeatmapTextureShader"; const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); -const StringIdentity idTexImageName = stringIndexer().get("u_image"); -const StringIdentity idTexColorRampName = stringIndexer().get("u_color_ramp"); } // namespace @@ -529,18 +527,13 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, heatmapTextureBuilder->setSegments( gfx::Triangles(), RenderStaticData::quadTriangleIndices().vector(), segments.data(), segments.size()); - auto imageLocation = heatmapTextureShader->getSamplerLocation(idTexImageName); - if (imageLocation.has_value()) { - heatmapTextureBuilder->setTexture(renderTarget->getTexture(), imageLocation.value()); - } - auto colorRampLocation = heatmapTextureShader->getSamplerLocation(idTexColorRampName); - if (colorRampLocation.has_value()) { - std::shared_ptr texture = context.createTexture2D(); - texture->setImage(colorRamp); - texture->setSamplerConfiguration( - {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - heatmapTextureBuilder->setTexture(std::move(texture), colorRampLocation.value()); - } + heatmapTextureBuilder->setTexture(renderTarget->getTexture(), idHeatmapImageTexture); + + std::shared_ptr texture = context.createTexture2D(); + texture->setImage(colorRamp); + texture->setSamplerConfiguration( + {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + heatmapTextureBuilder->setTexture(std::move(texture), idHeatmapColorRampTexture); heatmapTextureBuilder->flush(context); diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index 2eb12988083..f98e3f4acae 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -30,6 +30,7 @@ namespace mbgl { using namespace style; +using namespace shaders; namespace { @@ -296,7 +297,6 @@ static const std::string HillshadeShaderGroupName = "HillshadeShader"; static const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); static const StringIdentity idTexturePosAttribName = stringIndexer().get("a_texture_pos"); -static const StringIdentity idTexImageName = stringIndexer().get("u_image"); void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, @@ -336,10 +336,6 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, return; } - if (!hillshadeImageLocation) { - hillshadeImageLocation = hillshadeShader->getSamplerLocation(idTexImageName); - } - auto renderPass = RenderPass::Translucent; if (!(mbgl::underlying_type(renderPass) & evaluatedProperties->renderPasses)) { return; @@ -433,14 +429,11 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, hillshadePrepareBuilder->setSegments( gfx::Triangles(), staticDataIndices.vector(), staticDataSegments.data(), staticDataSegments.size()); - auto imageLocation = hillshadePrepareShader->getSamplerLocation(idTexImageName); - if (imageLocation.has_value()) { - std::shared_ptr texture = context.createTexture2D(); - texture->setImage(bucket.getDEMData().getImagePtr()); - texture->setSamplerConfiguration( - {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - hillshadePrepareBuilder->setTexture(texture, imageLocation.value()); - } + std::shared_ptr texture = context.createTexture2D(); + texture->setImage(bucket.getDEMData().getImagePtr()); + texture->setSamplerConfiguration( + {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + hillshadePrepareBuilder->setTexture(texture, idHillshadeImageTexture); hillshadePrepareBuilder->flush(context); @@ -515,10 +508,7 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, drawSegments.emplace_back(hillshadeBuilder->createSegment(gfx::Triangles(), std::move(segCopy))); } drawable.setIndexData(indices->vector(), std::move(drawSegments)); - - if (hillshadeImageLocation) { - drawable.setTexture(bucket.renderTarget->getTexture(), *hillshadeImageLocation); - } + drawable.setTexture(bucket.renderTarget->getTexture(), idHillshadeImageTexture); return true; }; if (updateTile(renderPass, tileID, std::move(updateExisting))) { @@ -533,10 +523,7 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, hillshadeBuilder->setVertexAttributes(buildVertexAttributes()); hillshadeBuilder->setRawVertices({}, vertices->elements(), gfx::AttributeDataType::Short2); hillshadeBuilder->setSegments(gfx::Triangles(), indices->vector(), segments->data(), segments->size()); - - if (hillshadeImageLocation) { - hillshadeBuilder->setTexture(bucket.renderTarget->getTexture(), *hillshadeImageLocation); - } + hillshadeBuilder->setTexture(bucket.renderTarget->getTexture(), idHillshadeImageTexture); hillshadeBuilder->flush(context); diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.hpp b/src/mbgl/renderer/layers/render_hillshade_layer.hpp index 83fa357ed7b..59390317ea3 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.hpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.hpp @@ -75,7 +75,6 @@ class RenderHillshadeLayer : public RenderLayer { #if MLN_DRAWABLE_RENDERER gfx::ShaderProgramBasePtr hillshadePrepareShader; gfx::ShaderProgramBasePtr hillshadeShader; - std::optional hillshadeImageLocation; std::vector activatedRenderTargets; using HillshadeVertexVector = gfx::VertexVector; diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index 6d21a77f21b..918a4782f00 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -345,8 +345,6 @@ float RenderLineLayer::getLineWidth(const GeometryTileFeature& feature, } #if MLN_DRAWABLE_RENDERER -static const StringIdentity idLineImageUniformName = stringIndexer().get("u_image"); - void RenderLineLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, const TransformState& state, @@ -679,7 +677,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, iconTweaker = std::make_shared( atlases, std::nullopt, - idLineImageUniformName, + idLineImageTexture, /*isText*/ false, /*sdfIcons*/ true, // to force linear filter /*rotationAlignment_*/ AlignmentType::Auto, @@ -727,33 +725,31 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, addAttributes(*builder, bucket, std::move(vertexAttrs)); // texture - if (const auto samplerLocation = builder->getShader()->getSamplerLocation(idLineImageUniformName)) { - if (!colorRampTexture2D && colorRamp->valid()) { - // create texture. to be reused for all the tiles of the layer - colorRampTexture2D = context.createTexture2D(); - colorRampTexture2D->setImage(colorRamp); - colorRampTexture2D->setSamplerConfiguration( - {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - } + if (!colorRampTexture2D && colorRamp->valid()) { + // create texture. to be reused for all the tiles of the layer + colorRampTexture2D = context.createTexture2D(); + colorRampTexture2D->setImage(colorRamp); + colorRampTexture2D->setSamplerConfiguration( + {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + } - if (colorRampTexture2D) { - builder->setTexture(colorRampTexture2D, samplerLocation.value()); + if (colorRampTexture2D) { + builder->setTexture(colorRampTexture2D, idLineImageTexture); - // segments - setSegments(builder, bucket); + // segments + setSegments(builder, bucket); - // finish - builder->flush(context); - for (auto& drawable : builder->clearDrawables()) { - drawable->setType(mbgl::underlying_type(LineLayerTweaker::LineType::Gradient)); - drawable->setTileID(tileID); - drawable->setLayerTweaker(layerTweaker); - drawable->mutableUniformBuffers().createOrUpdate( - idLineGradientInterpolationUBO, &getLineGradientInterpolationUBO(), context); + // finish + builder->flush(context); + for (auto& drawable : builder->clearDrawables()) { + drawable->setType(mbgl::underlying_type(LineLayerTweaker::LineType::Gradient)); + drawable->setTileID(tileID); + drawable->setLayerTweaker(layerTweaker); + drawable->mutableUniformBuffers().createOrUpdate( + idLineGradientInterpolationUBO, &getLineGradientInterpolationUBO(), context); - tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); - ++stats.drawablesAdded; - } + tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); + ++stats.drawablesAdded; } } } else { diff --git a/src/mbgl/renderer/layers/render_raster_layer.cpp b/src/mbgl/renderer/layers/render_raster_layer.cpp index 58d043c70be..e495030cbf8 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.cpp +++ b/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -26,6 +26,7 @@ namespace mbgl { using namespace style; +using namespace shaders; namespace { @@ -273,8 +274,6 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, if (!rasterShader) { return; } - rasterSampler0 = rasterShader->getSamplerLocation(idTexImage0Name); - rasterSampler1 = rasterShader->getSamplerLocation(idTexImage1Name); } if (!layerTweaker) { @@ -311,7 +310,7 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, }; const auto setTextures = [&](gfx::UniqueDrawableBuilder& builder, RasterBucket& bucket) { - if (bucket.image && (rasterSampler0 || rasterSampler1)) { + if (bucket.image) { if (!bucket.texture2d) { if (auto tex = context.createTexture2D()) { tex->setImage(bucket.image); @@ -327,12 +326,8 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, bucket.texture2d->setSamplerConfiguration( {filter, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - if (rasterSampler0) { - builder->setTexture(bucket.texture2d, *rasterSampler0); - } - if (rasterSampler1) { - builder->setTexture(bucket.texture2d, *rasterSampler1); - } + builder->setTexture(bucket.texture2d, idRasterImage0Texture); + builder->setTexture(bucket.texture2d, idRasterImage1Texture); } } }; @@ -498,7 +493,8 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, builder = createBuilder(); } - if (bucket.image && builder->getTextures().size() == 0) { + if (bucket.image && !builder->getTexture(idRasterImage0Texture) && + !builder->getTexture(idRasterImage1Texture)) { setTextures(builder, bucket); }; diff --git a/src/mbgl/renderer/layers/render_raster_layer.hpp b/src/mbgl/renderer/layers/render_raster_layer.hpp index 361392d8224..105407f85ae 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.hpp +++ b/src/mbgl/renderer/layers/render_raster_layer.hpp @@ -66,8 +66,6 @@ class RenderRasterLayer final : public RenderLayer { #if MLN_DRAWABLE_RENDERER gfx::ShaderProgramBasePtr rasterShader; - std::optional rasterSampler0; - std::optional rasterSampler1; LayerGroupPtr imageLayerGroup; using RasterVertexVector = gfx::VertexVector; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index f35b745d0dd..ac3535b3de8 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -766,8 +766,6 @@ const auto idPosOffsetAttribName = stringIndexer().get(posOffsetAttribName); const auto idPixOffsetAttribName = stringIndexer().get("a_pixeloffset"); const auto idProjPosAttribName = stringIndexer().get("a_projected_pos"); const auto idFadeOpacityAttribName = stringIndexer().get("a_fade_opacity"); -const auto idTexUniformName = stringIndexer().get("u_texture"); -const auto idTexIconUniformName = stringIndexer().get("u_texture_icon"); void updateTileAttributes(const SymbolBucket::Buffer& buffer, const bool isText, @@ -1293,8 +1291,8 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, const bool textSizeIsZoomConstant = bucket.textSizeBinder->evaluateForZoom(static_cast(state.getZoom())).isZoomConstant; tileInfo.textTweaker = std::make_shared(atlases, - idTexIconUniformName, - idTexUniformName, + idSymbolImageIconTexture, + idSymbolImageTexture, isText, false, values.rotationAlignment, @@ -1304,8 +1302,8 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, if (!isText && !tileInfo.iconTweaker) { const bool iconScaled = layout.get().constantOr(1.0) != 1.0 || bucket.iconsNeedLinear; tileInfo.iconTweaker = std::make_shared(atlases, - idTexIconUniformName, - idTexUniformName, + idSymbolImageIconTexture, + idSymbolImageTexture, isText, sdfIcons, values.rotationAlignment, diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp index 120e9c20b54..fd0d1b25d92 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp @@ -29,13 +29,9 @@ using namespace shaders; namespace { -Size getTexSize(const gfx::Drawable& drawable, const StringIdentity nameId) { - if (const auto& shader = drawable.getShader()) { - if (const auto index = shader->getSamplerLocation(nameId)) { - if (const auto& tex = drawable.getTexture(*index)) { - return tex->getSize(); - } - } +Size getTexSize(const gfx::Drawable& drawable, const size_t texId) { + if (const auto& tex = drawable.getTexture(texId)) { + return tex->getSize(); } return {0, 0}; } @@ -44,9 +40,6 @@ std::array toArray(const Size& s) { return util::cast(std::array{s.width, s.height}); } -const StringIdentity idTexUniformName = stringIndexer().get("u_texture"); -const StringIdentity idTexIconUniformName = stringIndexer().get("u_texture_icon"); - SymbolDrawablePaintUBO buildPaintUBO(bool isText, const SymbolPaintProperties::PossiblyEvaluated& evaluated) { return { /*.fill_color=*/isText ? constOrDefault(evaluated) : constOrDefault(evaluated), @@ -152,8 +145,8 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete /*.label_plane_matrix=*/util::cast(labelPlaneMatrix), /*.coord_matrix=*/util::cast(glCoordMatrix), - /*.texsize=*/toArray(getTexSize(drawable, idTexUniformName)), - /*.texsize_icon=*/toArray(getTexSize(drawable, idTexIconUniformName)), + /*.texsize=*/toArray(getTexSize(drawable, idSymbolImageTexture)), + /*.texsize_icon=*/toArray(getTexSize(drawable, idSymbolImageIconTexture)), /*.gamma_scale=*/gammaScale, /*.rotate_symbol=*/rotateInShader, diff --git a/src/mbgl/renderer/sources/render_tile_source.cpp b/src/mbgl/renderer/sources/render_tile_source.cpp index 959dbff4330..a88e6741c20 100644 --- a/src/mbgl/renderer/sources/render_tile_source.cpp +++ b/src/mbgl/renderer/sources/render_tile_source.cpp @@ -131,18 +131,13 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr // create texture. to be reused for all the tiles of the debug layers auto texture = context.createTexture2D(); - std::optional samplerLocation; { std::array data{{0, 0, 0, 0}}; auto emptyImage = std::make_shared(Size(1, 1), data.data(), data.size()); texture->setImage(emptyImage); texture->setSamplerConfiguration( {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - static const StringIdentity idDebugOverlayUniformName = stringIndexer().get("u_overlay"); - samplerLocation = debugShader->getSamplerLocation(idDebugOverlayUniformName); } - assert(samplerLocation.has_value()); - if (!samplerLocation.has_value()) return; // function to update existing tile drawables with UBO value. return number of updated drawables const auto updateDrawables = @@ -172,7 +167,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr debugBuilder->addVertices(verts, 0, verts.size()); debugBuilder->setSegments(mode, indexes, segments.data(), segments.size()); // texture - debugBuilder->setTexture(texture, samplerLocation.value()); + debugBuilder->setTexture(texture, idDebugOverlayTexture); // finish debugBuilder->flush(context); diff --git a/src/mbgl/shaders/gl/shader_info.cpp b/src/mbgl/shaders/gl/shader_info.cpp index 5d90eb2fc41..637c26db1c1 100644 --- a/src/mbgl/shaders/gl/shader_info.cpp +++ b/src/mbgl/shaders/gl/shader_info.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace mbgl { namespace shaders { @@ -10,51 +10,76 @@ UniformBlockInfo::UniformBlockInfo(std::string_view name_, std::size_t id_) id(id_), binding(id_) {} +TextureInfo::TextureInfo(std::string_view name_, std::size_t id_) + : name(name_), + id(id_) {} + +// Background const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"BackgroundDrawableUBO", idBackgroundDrawableUBO}, UniformBlockInfo{"BackgroundLayerUBO", idBackgroundLayerUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Background Pattern const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"BackgroundDrawableUBO", idBackgroundDrawableUBO}, UniformBlockInfo{"BackgroundLayerUBO", idBackgroundLayerUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image", idBackgroundImageTexture}, +}; +// Circle const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CircleDrawableUBO", idCircleDrawableUBO}, UniformBlockInfo{"CirclePaintParamsUBO", idCirclePaintParamsUBO}, UniformBlockInfo{"CircleEvaluatedPropsUBO", idCircleEvaluatedPropsUBO}, UniformBlockInfo{"CircleInterpolateUBO", idCircleInterpolateUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Collision Box const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CollisionBoxUBO", idCollisionUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Collision Circle const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CollisionCircleUBO", idCollisionUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Debug const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"DebugUBO", idDebugUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_overlay", idDebugOverlayTexture}, +}; +// Fill const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillDrawableUBO", idFillDrawableUBO}, UniformBlockInfo{"FillEvaluatedPropsUBO", idFillEvaluatedPropsUBO}, UniformBlockInfo{"FillInterpolateUBO", idFillInterpolateUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Fill Outline const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillOutlineDrawableUBO", idFillOutlineDrawableUBO}, UniformBlockInfo{"FillOutlineEvaluatedPropsUBO", idFillOutlineEvaluatedPropsUBO}, UniformBlockInfo{"FillOutlineInterpolateUBO", idFillOutlineInterpolateUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Fill Pattern const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillPatternDrawableUBO", idFillPatternDrawableUBO}, @@ -62,7 +87,11 @@ const std::vector ShaderInfo ShaderInfo::textures = { + TextureInfo{"u_image", idFillImageTexture}, +}; +// Fill Outline Pattern const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillOutlinePatternDrawableUBO", idFillOutlinePatternDrawableUBO}, @@ -70,7 +99,11 @@ const std::vector UniformBlockInfo{"FillOutlinePatternEvaluatedPropsUBO", idFillOutlinePatternEvaluatedPropsUBO}, UniformBlockInfo{"FillOutlinePatternInterpolateUBO", idFillOutlinePatternInterpolateUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image", idFillImageTexture}, +}; +// Fill Extrusion const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillExtrusionDrawableUBO", idFillExtrusionDrawableUBO}, @@ -78,7 +111,9 @@ const std::vector UniformBlockInfo{"FillExtrusionDrawableTilePropsUBO", idFillExtrusionDrawableTilePropsUBO}, UniformBlockInfo{"FillExtrusionInterpolateUBO", idFillExtrusionInterpolateUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Fill Extrusion Pattern const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillExtrusionDrawableUBO", idFillExtrusionDrawableUBO}, @@ -86,28 +121,47 @@ const std::vector UniformBlockInfo{"FillExtrusionDrawableTilePropsUBO", idFillExtrusionDrawableTilePropsUBO}, UniformBlockInfo{"FillExtrusionInterpolateUBO", idFillExtrusionInterpolateUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image", idFillExtrusionImageTexture}, +}; +// Heatmap const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HeatmapDrawableUBO", idHeatmapDrawableUBO}, UniformBlockInfo{"HeatmapEvaluatedPropsUBO", idHeatmapEvaluatedPropsUBO}, UniformBlockInfo{"HeatmapInterpolateUBO", idHeatmapInterpolateUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Heatmap Texture const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HeatmapTextureDrawableUBO", idHeatmapTextureDrawableUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image", idHeatmapImageTexture}, + TextureInfo{"u_color_ramp", idHeatmapColorRampTexture}, +}; +// Hillshade Prepare const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HillshadePrepareDrawableUBO", idHillshadePrepareDrawableUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image", idHillshadeImageTexture}, +}; +// Hillshade const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HillshadeDrawableUBO", idHillshadeDrawableUBO}, UniformBlockInfo{"HillshadeEvaluatedPropsUBO", idHillshadeEvaluatedPropsUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image", idHillshadeImageTexture}, +}; +// Line Gradient const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, @@ -115,7 +169,11 @@ const std::vector ShaderInfo ShaderInfo::textures = { + TextureInfo{"u_image", idLineImageTexture}, +}; +// Line Pattern const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, @@ -124,30 +182,47 @@ const std::vector ShaderInfo ShaderInfo::textures = { + TextureInfo{"u_image", idLineImageTexture}, +}; +// Line SDF const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, UniformBlockInfo{"LineSDFUBO", idLineSDFUBO}, UniformBlockInfo{"LineSDFPropertiesUBO", idLineSDFPropertiesUBO}, UniformBlockInfo{"LineSDFInterpolationUBO", idLineSDFInterpolationUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image", idLineImageTexture}, +}; +// Line const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, UniformBlockInfo{"LineUBO", idLineUBO}, UniformBlockInfo{"LinePropertiesUBO", idLinePropertiesUBO}, UniformBlockInfo{"LineInterpolationUBO", idLineInterpolationUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Line Basic const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineBasicUBO", idLineBasicUBO}, UniformBlockInfo{"LineBasicPropertiesUBO", idLineBasicPropertiesUBO}, }; +const std::vector ShaderInfo::textures = {}; +// Raster const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"RasterDrawableUBO", idRasterDrawableUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_image0", idRasterImage0Texture}, + TextureInfo{"u_image1", idRasterImage0Texture}, +}; +// Symbol Icon const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, UniformBlockInfo{"SymbolDynamicUBO", idSymbolDynamicUBO}, @@ -155,7 +230,11 @@ const std::vector ShaderInfo ShaderInfo::textures = { + TextureInfo{"u_texture", idSymbolImageTexture}, +}; +// Symbol SDF const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, @@ -164,7 +243,11 @@ const std::vector UniformBlockInfo{"SymbolDrawableTilePropsUBO", idSymbolDrawableTilePropsUBO}, UniformBlockInfo{"SymbolDrawableInterpolateUBO", idSymbolDrawableInterpolateUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_texture", idSymbolImageTexture}, +}; +// Symbol Text & Icon const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, @@ -173,6 +256,10 @@ const std::vector UniformBlockInfo{"SymbolDrawableTilePropsUBO", idSymbolDrawableTilePropsUBO}, UniformBlockInfo{"SymbolDrawableInterpolateUBO", idSymbolDrawableInterpolateUBO}, }; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_texture", idSymbolImageTexture}, + TextureInfo{"u_texture_icon", idSymbolImageIconTexture}, +}; } // namespace shaders } // namespace mbgl diff --git a/src/mbgl/shaders/gl/shader_program_gl.cpp b/src/mbgl/shaders/gl/shader_program_gl.cpp index 0e3ce2f41ab..2183bf91bc0 100644 --- a/src/mbgl/shaders/gl/shader_program_gl.cpp +++ b/src/mbgl/shaders/gl/shader_program_gl.cpp @@ -91,7 +91,7 @@ ShaderProgramGL::ShaderProgramGL(UniqueProgram&& glProgram_) ShaderProgramGL::ShaderProgramGL(UniqueProgram&& program, UniformBlockArrayGL&& uniformBlocks_, VertexAttributeArrayGL&& attributes_, - SamplerLocationMap&& samplerLocations_) + SamplerLocationArray&& samplerLocations_) : ShaderProgramBase(), glProgram(std::move(program)), uniformBlocks(std::move(uniformBlocks_)), @@ -105,20 +105,16 @@ ShaderProgramGL::ShaderProgramGL(ShaderProgramGL&& other) vertexAttributes(std::move(other.vertexAttributes)), samplerLocations(std::move(other.samplerLocations)) {} -std::optional ShaderProgramGL::getSamplerLocation(const StringIdentity id) const { - std::optional result{}; - if (auto it = samplerLocations.find(id); it != samplerLocations.end()) { - result = it->second; - } - return result; +std::optional ShaderProgramGL::getSamplerLocation(const size_t id) const { + return (id < samplerLocations.size()) ? samplerLocations[id] : std::nullopt; } std::shared_ptr ShaderProgramGL::create( Context& context, const ProgramParameters& programParameters, - const std::string& /*name*/, const std::string_view firstAttribName, const std::vector& uniformBlocksInfo, + const std::vector& texturesInfo, const std::string& vertexSource, const std::string& fragmentSource, const std::string& additionalDefines) noexcept(false) { @@ -140,17 +136,7 @@ std::shared_ptr ShaderProgramGL::create( fragmentSource.c_str()}); auto program = context.createProgram(vertProg, fragProg, firstAttribName.data()); - // GLES3.1 - // GLint numAttribs; - // glGetProgramInterfaceiv(program, GL_PROGRAM_INPUT, GL_ACTIVE_RESOURCES, &numAttribs); - UniformBlockArrayGL uniformBlocks; - - GLint count = 0; - GLint maxLength = 0; - MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, &count)); - MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &maxLength)); - for (const auto& blockInfo : uniformBlocksInfo) { GLint index = MBGL_CHECK_ERROR(glGetUniformBlockIndex(program, blockInfo.name.data())); GLint size = 0; @@ -161,34 +147,21 @@ std::shared_ptr ShaderProgramGL::create( uniformBlocks.set(blockInfo.id, binding, size); } - SamplerLocationMap samplerLocations; - GLint numActiveUniforms = 0; - MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numActiveUniforms)); - MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLength)); - auto name = std::vector(maxLength); - for (GLint index = 0; index < numActiveUniforms; ++index) { - GLsizei actualLength = 0; - GLint size = 0; - GLenum type = GL_ZERO; - - MBGL_CHECK_ERROR(glGetActiveUniform(program, index, maxLength, &actualLength, &size, &type, name.data())); - - if (type == GL_SAMPLER_2D) { - // This uniform is a texture sampler - GLint location = MBGL_CHECK_ERROR(glGetUniformLocation(program, name.data())); - assert(location != -1); - if (location != -1) { - samplerLocations[stringIndexer().get(name.data())] = location; - } + SamplerLocationArray samplerLocations; + for (const auto& textureInfo : texturesInfo) { + GLint location = MBGL_CHECK_ERROR(glGetUniformLocation(program, textureInfo.name.data())); + assert(location != -1); + if (location != -1) { + samplerLocations[textureInfo.id] = location; } } VertexAttributeArrayGL attrs; - - count = 0; + GLint count = 0; + GLint maxLength = 0; MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &count)); MBGL_CHECK_ERROR(glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLength)); - name.resize(maxLength); + auto name = std::vector(maxLength); for (GLint index = 0; index < count; ++index) { GLsizei length = 0; // "number of characters actually written in name (excluding the null terminator)" GLint size = 0; // "size of the attribute variable, in units of the type returned in type" diff --git a/src/mbgl/shaders/mtl/background_pattern.cpp b/src/mbgl/shaders/mtl/background_pattern.cpp index 1cdb89b0deb..aa6a7c70424 100644 --- a/src/mbgl/shaders/mtl/background_pattern.cpp +++ b/src/mbgl/shaders/mtl/background_pattern.cpp @@ -14,7 +14,7 @@ const std::array UniformBlockInfo{2, true, true, sizeof(BackgroundPatternLayerUBO), idBackgroundLayerUBO}, }; const std::array ShaderSource::textures = { - TextureInfo{0, "u_image"}}; + TextureInfo{0, idBackgroundImageTexture}}; } // namespace shaders } // namespace mbgl diff --git a/src/mbgl/shaders/mtl/debug.cpp b/src/mbgl/shaders/mtl/debug.cpp index 8694c6a21fd..017c0edb148 100644 --- a/src/mbgl/shaders/mtl/debug.cpp +++ b/src/mbgl/shaders/mtl/debug.cpp @@ -10,7 +10,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_overlay"}, + TextureInfo{0, idDebugOverlayTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/fill.cpp b/src/mbgl/shaders/mtl/fill.cpp index 3b124bbaa84..05ad0f24ae1 100644 --- a/src/mbgl/shaders/mtl/fill.cpp +++ b/src/mbgl/shaders/mtl/fill.cpp @@ -40,7 +40,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idFillImageTexture}, }; const std::array @@ -59,7 +59,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idFillImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp index 7f4b301f40b..3bd415ae756 100644 --- a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp +++ b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp @@ -22,7 +22,7 @@ const std::array }; const std::array ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idFillExtrusionImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/heatmap_texture.cpp b/src/mbgl/shaders/mtl/heatmap_texture.cpp index 40a8e99cbf7..61f2067502b 100644 --- a/src/mbgl/shaders/mtl/heatmap_texture.cpp +++ b/src/mbgl/shaders/mtl/heatmap_texture.cpp @@ -12,8 +12,8 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image"}, - TextureInfo{1, "u_color_ramp"}, + TextureInfo{0, idHeatmapImageTexture}, + TextureInfo{1, idHeatmapColorRampTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/hillshade.cpp b/src/mbgl/shaders/mtl/hillshade.cpp index c4d9b1c7ad5..3bc21b853c1 100644 --- a/src/mbgl/shaders/mtl/hillshade.cpp +++ b/src/mbgl/shaders/mtl/hillshade.cpp @@ -12,7 +12,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idHillshadeImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/hillshade_prepare.cpp b/src/mbgl/shaders/mtl/hillshade_prepare.cpp index 717552aa2c1..5aa84fa9345 100644 --- a/src/mbgl/shaders/mtl/hillshade_prepare.cpp +++ b/src/mbgl/shaders/mtl/hillshade_prepare.cpp @@ -13,7 +13,7 @@ const std::array UniformBlockInfo{2, true, true, sizeof(HillshadePrepareDrawableUBO), idHillshadePrepareDrawableUBO}, }; const std::array ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idHillshadeImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/line.cpp b/src/mbgl/shaders/mtl/line.cpp index 805007aeed9..fed8f6dd5ba 100644 --- a/src/mbgl/shaders/mtl/line.cpp +++ b/src/mbgl/shaders/mtl/line.cpp @@ -40,7 +40,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idLineImageTexture}, }; const std::array ShaderSource::attributes = { @@ -61,7 +61,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idLineImageTexture}, }; const std::array ShaderSource::attributes = { diff --git a/src/mbgl/shaders/mtl/line_gradient.cpp b/src/mbgl/shaders/mtl/line_gradient.cpp index 131a6a1a597..a3a4a1aa77b 100644 --- a/src/mbgl/shaders/mtl/line_gradient.cpp +++ b/src/mbgl/shaders/mtl/line_gradient.cpp @@ -19,7 +19,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image"}, + TextureInfo{0, idLineImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/raster.cpp b/src/mbgl/shaders/mtl/raster.cpp index cd0a21dd23c..0bb98a6150c 100644 --- a/src/mbgl/shaders/mtl/raster.cpp +++ b/src/mbgl/shaders/mtl/raster.cpp @@ -11,8 +11,8 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_image0"}, - TextureInfo{1, "u_image1"}, + TextureInfo{0, idRasterImage0Texture}, + TextureInfo{1, idRasterImage1Texture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/shader_program.cpp b/src/mbgl/shaders/mtl/shader_program.cpp index c75ef817ec4..ca13c4acdae 100644 --- a/src/mbgl/shaders/mtl/shader_program.cpp +++ b/src/mbgl/shaders/mtl/shader_program.cpp @@ -38,10 +38,9 @@ shaders::UniformBlockInfo::UniformBlockInfo( size(size_), id(id_) {} -shaders::TextureInfo::TextureInfo(std::size_t index_, std::string_view name_) +shaders::TextureInfo::TextureInfo(std::size_t index_, std::size_t id_) : index(index_), - name(name_), - nameID(stringIndexer().get(name_)) {} + id(id_) {} namespace mtl { namespace { @@ -188,11 +187,8 @@ MTLRenderPipelineStatePtr ShaderProgram::getRenderPipelineState(const gfx::Rende return rps; } -std::optional ShaderProgram::getSamplerLocation(const StringIdentity id) const { - if (auto it = textureBindings.find(id); it != textureBindings.end()) { - return it->second; - } - return std::nullopt; +std::optional ShaderProgram::getSamplerLocation(const size_t id) const { + return (id < textureBindings.size()) ? textureBindings[id] : std::nullopt; } void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) { @@ -222,7 +218,11 @@ void ShaderProgram::initUniformBlock(const shaders::UniformBlockInfo& info) { } void ShaderProgram::initTexture(const shaders::TextureInfo& info) { - textureBindings[stringIndexer().get(info.name.data())] = info.index; + assert(info.id < textureBindings.size()); + if (info.id >= textureBindings.size()) { + return; + } + textureBindings[info.id] = info.index; } } // namespace mtl diff --git a/src/mbgl/shaders/mtl/symbol_icon.cpp b/src/mbgl/shaders/mtl/symbol_icon.cpp index cfb82bb8383..556a29aebdc 100644 --- a/src/mbgl/shaders/mtl/symbol_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_icon.cpp @@ -22,7 +22,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_texture"}, + TextureInfo{0, idSymbolImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/symbol_sdf.cpp b/src/mbgl/shaders/mtl/symbol_sdf.cpp index 074d706e45c..f064ad62060 100644 --- a/src/mbgl/shaders/mtl/symbol_sdf.cpp +++ b/src/mbgl/shaders/mtl/symbol_sdf.cpp @@ -28,7 +28,7 @@ const std::array ShaderSource ShaderSource::textures = { - TextureInfo{0, "u_texture"}, + TextureInfo{0, idSymbolImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp index afcd477efeb..2cc4523eab2 100644 --- a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp @@ -27,8 +27,8 @@ const std::array UniformBlockInfo{13, true, false, sizeof(SymbolDrawableInterpolateUBO), idSymbolDrawableInterpolateUBO}, }; const std::array ShaderSource::textures = { - TextureInfo{0, "u_texture"}, - TextureInfo{1, "u_texture_icon"}, + TextureInfo{0, idSymbolImageTexture}, + TextureInfo{1, idSymbolImageIconTexture}, }; } // namespace shaders From d6db72e8ba8a7c44487392e2cef987d271d5b7d6 Mon Sep 17 00:00:00 2001 From: Stefan Karschti Date: Fri, 16 Feb 2024 17:39:47 +0200 Subject: [PATCH 71/96] Custom Drawable Layer: Symbol Icon (#1908) --- CMakeLists.txt | 5 +- bazel/core.bzl | 6 +- {src => include}/mbgl/gfx/context.hpp | 0 .../shaders/custom_drawable_layer_ubo.hpp | 36 ++++ .../gl/drawable_custom_symbol_icon.hpp | 92 ++++++++++ include/mbgl/shaders/gl/shader_info.hpp | 6 + .../mbgl/shaders/mtl/custom_symbol_icon.hpp | 105 +++++++++++ include/mbgl/shaders/shader_defines.hpp | 8 + include/mbgl/shaders/shader_manifest.hpp | 1 + include/mbgl/shaders/shader_source.hpp | 1 + .../style/layers/custom_drawable_layer.hpp | 40 ++++- .../app/ExampleCustomDrawableStyleLayer.mm | 35 +++- .../ios/app/Assets.xcassets/Contents.json | 6 +- .../pin.imageset/Contents.json | 21 +++ .../app/Assets.xcassets/pin.imageset/pin1.png | Bin 0 -> 21574 bytes .../drawable.custom.symbol_icon.fragment.glsl | 7 + .../drawable.custom.symbol_icon.vertex.glsl | 69 ++++++++ shaders/manifest.json | 7 + src/mbgl/gfx/index_vector.hpp | 2 +- src/mbgl/gfx/vertex_vector.hpp | 6 +- src/mbgl/gl/renderer_backend.cpp | 1 + src/mbgl/mtl/renderer_backend.cpp | 2 + src/mbgl/shaders/gl/shader_info.cpp | 9 + src/mbgl/shaders/mtl/custom_symbol_icon.cpp | 22 +++ .../style/layers/custom_drawable_layer.cpp | 164 +++++++++++++++++- test/api/custom_drawable_layer.test.cpp | 141 +++++++++++++-- test/fixtures/api/simple.json | 25 +++ .../custom_drawable_layer/basic/expected.png | Bin 30225 -> 0 bytes .../custom_drawable_layer/fill/expected.png | Bin 0 -> 7742 bytes .../custom_drawable_layer/line/expected.png | Bin 0 -> 35055 bytes .../symbol_icon/expected.png | Bin 0 -> 11379 bytes .../symbol_icon/pin1.png | Bin 0 -> 21574 bytes 32 files changed, 788 insertions(+), 29 deletions(-) rename {src => include}/mbgl/gfx/context.hpp (100%) create mode 100644 include/mbgl/shaders/custom_drawable_layer_ubo.hpp create mode 100644 include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp create mode 100644 include/mbgl/shaders/mtl/custom_symbol_icon.hpp create mode 100644 platform/ios/app/Assets.xcassets/pin.imageset/Contents.json create mode 100644 platform/ios/app/Assets.xcassets/pin.imageset/pin1.png create mode 100644 shaders/drawable.custom.symbol_icon.fragment.glsl create mode 100644 shaders/drawable.custom.symbol_icon.vertex.glsl create mode 100644 src/mbgl/shaders/mtl/custom_symbol_icon.cpp create mode 100644 test/fixtures/api/simple.json delete mode 100644 test/fixtures/custom_drawable_layer/basic/expected.png create mode 100644 test/fixtures/custom_drawable_layer/fill/expected.png create mode 100644 test/fixtures/custom_drawable_layer/line/expected.png create mode 100644 test/fixtures/custom_drawable_layer/symbol_icon/expected.png create mode 100644 test/fixtures/custom_drawable_layer/symbol_icon/pin1.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bba574fe4e..eadad187612 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ endif() if(MLN_DRAWABLE_RENDERER) list(APPEND INCLUDE_FILES + ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/context.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/drawable.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/drawable_data.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/drawable_impl.hpp @@ -465,7 +466,6 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/attribute.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/color_mode.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/command_encoder.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/context.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/cull_face_mode.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/debug_group.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/depth_mode.hpp @@ -1142,6 +1142,7 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/line_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/custom_drawable_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/raster_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_defines.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/symbol_layer_ubo.hpp @@ -1211,6 +1212,7 @@ if(MBGL_WITH_METAL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/common.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/collision_box.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/collision_circle.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/custom_symbol_icon.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/debug.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill_extrusion.hpp @@ -1256,6 +1258,7 @@ if(MBGL_WITH_METAL) ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/collision_box.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/collision_circle.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/clipping_mask.cpp + ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/custom_symbol_icon.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/debug.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/fill.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/fill_extrusion.cpp diff --git a/bazel/core.bzl b/bazel/core.bzl index b09873d2549..6c22564d922 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -67,6 +67,7 @@ MLN_GENERATED_OPENGL_SHADER_HEADERS = [ "include/mbgl/shaders/gl/drawable_circle.hpp", "include/mbgl/shaders/gl/drawable_collision_box.hpp", "include/mbgl/shaders/gl/drawable_collision_circle.hpp", + "include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp", "include/mbgl/shaders/gl/drawable_debug.hpp", "include/mbgl/shaders/gl/drawable_fill.hpp", "include/mbgl/shaders/gl/drawable_fill_outline.hpp", @@ -143,7 +144,6 @@ MLN_CORE_SOURCE = [ "include/mbgl/gfx/backend.hpp", "src/mbgl/gfx/color_mode.hpp", "src/mbgl/gfx/command_encoder.hpp", - "src/mbgl/gfx/context.hpp", "src/mbgl/gfx/cull_face_mode.hpp", "src/mbgl/gfx/debug_group.hpp", "src/mbgl/gfx/depth_mode.hpp", @@ -648,6 +648,7 @@ MLN_CORE_SOURCE = [ ] MLN_CORE_HEADERS = [ + "include/mbgl/gfx/context.hpp", "include/mbgl/actor/actor.hpp", "include/mbgl/actor/actor_ref.hpp", "include/mbgl/actor/aspiring_actor.hpp", @@ -980,6 +981,7 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/background_layer_ubo.hpp", "include/mbgl/shaders/circle_layer_ubo.hpp", "include/mbgl/shaders/collision_layer_ubo.hpp", + "include/mbgl/shaders/custom_drawable_layer_ubo.hpp", "include/mbgl/shaders/debug_layer_ubo.hpp", "include/mbgl/shaders/fill_layer_ubo.hpp", "include/mbgl/shaders/fill_extrusion_layer_ubo.hpp", @@ -1054,6 +1056,7 @@ MLN_DRAWABLES_MTL_SOURCE = [ "src/mbgl/shaders/mtl/collision_box.cpp", "src/mbgl/shaders/mtl/collision_circle.cpp", "src/mbgl/shaders/mtl/clipping_mask.cpp", + "src/mbgl/shaders/mtl/custom_symbol_icon.cpp", "src/mbgl/shaders/mtl/debug.cpp", "src/mbgl/shaders/mtl/fill.cpp", "src/mbgl/shaders/mtl/fill_extrusion.cpp", @@ -1098,6 +1101,7 @@ MLN_DRAWABLES_MTL_HEADERS = [ "include/mbgl/shaders/mtl/collision_box.hpp", "include/mbgl/shaders/mtl/collision_circle.hpp", "include/mbgl/shaders/mtl/common.hpp", + "include/mbgl/shaders/mtl/custom_symbol_icon.hpp", "include/mbgl/shaders/mtl/debug.hpp", "include/mbgl/shaders/mtl/fill.hpp", "include/mbgl/shaders/mtl/fill_extrusion.hpp", diff --git a/src/mbgl/gfx/context.hpp b/include/mbgl/gfx/context.hpp similarity index 100% rename from src/mbgl/gfx/context.hpp rename to include/mbgl/gfx/context.hpp diff --git a/include/mbgl/shaders/custom_drawable_layer_ubo.hpp b/include/mbgl/shaders/custom_drawable_layer_ubo.hpp new file mode 100644 index 00000000000..36259ea819a --- /dev/null +++ b/include/mbgl/shaders/custom_drawable_layer_ubo.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +namespace mbgl { +namespace shaders { + +/// Custom Symbol Icon matrix +struct alignas(16) CustomSymbolIconDrawableUBO { + /* 0 */ std::array matrix; + /* 64 */ +}; +static_assert(sizeof(CustomSymbolIconDrawableUBO) == 4 * 16); + +/// Custom Symbol Icon Parameters +struct alignas(16) CustomSymbolIconParametersUBO { + /* 0 */ std::array extrude_scale; + /* 8 */ std::array anchor; + /* 16 */ float angle_degrees; + /* 20 */ uint32_t scale_with_map; + /* 24 */ uint32_t pitch_with_map; + /* 28 */ float camera_to_center_distance; + /* 32 */ float aspect_ratio; + /* 36 */ float pad0, pad1, pad2; + /* 48 */ +}; +static_assert(sizeof(CustomSymbolIconParametersUBO) == 3 * 16); + +enum { + idCustomSymbolIconDrawableUBO, + idCustomSymbolIconParametersUBO, + customDrawableUBOCount +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp b/include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp new file mode 100644 index 00000000000..77b4a76b1bc --- /dev/null +++ b/include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp @@ -0,0 +1,92 @@ +// Generated code, do not modify this file! +#pragma once +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "CustomSymbolIconShader"; + static constexpr const char* vertex = R"(layout(location = 0) in vec2 a_pos; +layout(location = 1) in vec2 a_tex; + +layout(std140) uniform CustomSymbolIconDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform CustomSymbolIconParametersUBO { + highp vec2 u_extrude_scale; + highp vec2 u_anchor; + highp float u_angle_degrees; + bool u_scale_with_map; + bool u_pitch_with_map; + highp float u_camera_to_center_distance; + highp float u_aspect_ratio; + highp float pad0, pad1, pad3; +}; + +out vec2 v_tex; + +vec2 rotateVec2(vec2 v, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + return vec2(v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA); +} + +vec2 ellipseRotateVec2(vec2 v, float angle, float radiusRatio /* A/B */) { + float cosA = cos(angle); + float sinA = sin(angle); + float invRatio = 1.0 / radiusRatio; + return vec2(v.x * cosA - radiusRatio * v.y * sinA, invRatio * v.x * sinA + v.y * cosA); +} + +void main() { + // decode the extrusion vector (-1, -1) to (1, 1) + vec2 extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0); + + // make anchor relative to (0.5, 0.5) and corners in range (-1, -1) to (1, 1) + vec2 anchor = (u_anchor - vec2(0.5, 0.5)) * 2.0; + + // decode center + vec2 center = floor(a_pos * 0.5); + + // rotate extrusion around anchor + float angle = radians(-u_angle_degrees); + vec2 corner = extrude - anchor; + + // compute + if (u_pitch_with_map) { + if (u_scale_with_map) { + corner *= u_extrude_scale; + } else { + vec4 projected_center = u_matrix * vec4(center, 0, 1); + corner *= u_extrude_scale * (projected_center.w / u_camera_to_center_distance); + } + corner = center + rotateVec2(corner, angle); + gl_Position = u_matrix * vec4(corner, 0, 1); + } else { + gl_Position = u_matrix * vec4(center, 0, 1); + if (u_scale_with_map) { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * u_camera_to_center_distance, angle, u_aspect_ratio); + } else { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * gl_Position.w, angle, u_aspect_ratio); + } + } + + // texture coordinates + v_tex = a_tex; +} +)"; + static constexpr const char* fragment = R"(uniform sampler2D u_texture; + +in vec2 v_tex; + +void main() { + fragColor = texture(u_texture, v_tex); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/gl/shader_info.hpp b/include/mbgl/shaders/gl/shader_info.hpp index 7baba27c9cf..0ce68f3b6c5 100644 --- a/include/mbgl/shaders/gl/shader_info.hpp +++ b/include/mbgl/shaders/gl/shader_info.hpp @@ -54,6 +54,12 @@ struct ShaderInfo { static const std::vector textures; }; +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; + static const std::vector textures; +}; + template <> struct ShaderInfo { static const std::vector uniformBlocks; diff --git a/include/mbgl/shaders/mtl/custom_symbol_icon.hpp b/include/mbgl/shaders/mtl/custom_symbol_icon.hpp new file mode 100644 index 00000000000..229a7b5f217 --- /dev/null +++ b/include/mbgl/shaders/mtl/custom_symbol_icon.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr auto name = "CustomSymbolIconShader"; + static constexpr auto vertexMainFunction = "vertexMain"; + static constexpr auto fragmentMainFunction = "fragmentMain"; + + static const std::array attributes; + static const std::array uniforms; + static const std::array textures; + + static constexpr auto source = R"( +struct alignas(16) CustomSymbolIconDrawableUBO { + float4x4 matrix; +}; + +struct alignas(16) CustomSymbolIconParametersUBO { + float2 extrude_scale; + float2 anchor; + float angle_degrees; + int scale_with_map; + int pitch_with_map; + float camera_to_center_distance; + float aspect_ratio; + float pad0, pad1, pad3; +}; + +struct VertexStage { + float2 a_pos [[attribute(0)]]; + float2 a_tex [[attribute(1)]]; +}; + +struct FragmentStage { + float4 position [[position, invariant]]; + half2 tex; +}; + +float2 rotateVec2(float2 v, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + return float2(v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA); +} + +float2 ellipseRotateVec2(float2 v, float angle, float radiusRatio /* A/B */) { + float cosA = cos(angle); + float sinA = sin(angle); + float invRatio = 1.0 / radiusRatio; + return float2(v.x * cosA - radiusRatio * v.y * sinA, invRatio * v.x * sinA + v.y * cosA); +} + +FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], + device const CustomSymbolIconDrawableUBO& drawable [[buffer(2)]], + device const CustomSymbolIconParametersUBO& parameters [[buffer(3)]]) { + + const float2 extrude = glMod(float2(vertx.a_pos), 2.0) * 2.0 - 1.0; + const float2 anchor = (parameters.anchor - float2(0.5, 0.5)) * 2.0; + const float2 center = floor(float2(vertx.a_pos) * 0.5); + const float angle = radians(-parameters.angle_degrees); + float2 corner = extrude - anchor; + + float4 position; + if (parameters.pitch_with_map) { + if (parameters.scale_with_map) { + corner *= parameters.extrude_scale; + } else { + float4 projected_center = drawable.matrix * float4(center, 0, 1); + corner *= parameters.extrude_scale * (projected_center.w / parameters.camera_to_center_distance); + } + corner = center + rotateVec2(corner, angle); + position = drawable.matrix * float4(corner, 0, 1); + } else { + position = drawable.matrix * float4(center, 0, 1); + const float factor = parameters.scale_with_map ? parameters.camera_to_center_distance : position.w; + position.xy += ellipseRotateVec2(corner * parameters.extrude_scale * factor, angle, parameters.aspect_ratio); + } + + return { + .position = position, + .tex = half2(vertx.a_tex) + }; +} + +half4 fragment fragmentMain(FragmentStage in [[stage_in]], + texture2d image [[texture(0)]], + sampler image_sampler [[sampler(0)]]) { +#if defined(OVERDRAW_INSPECTOR) + return half4(1.0); +#endif + + return half4(image.sample(image_sampler, float2(in.tex))); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/shader_defines.hpp b/include/mbgl/shaders/shader_defines.hpp index 9ee9fc0a988..36e73c87737 100644 --- a/include/mbgl/shaders/shader_defines.hpp +++ b/include/mbgl/shaders/shader_defines.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ namespace shaders { static constexpr auto maxUBOCountPerShader = std::max({static_cast(backgroundUBOCount), static_cast(circleUBOCount), static_cast(collisionUBOCount), + static_cast(customDrawableUBOCount), static_cast(debugUBOCount), static_cast(fillUBOCount), static_cast(fillOutlineUBOCount), @@ -53,6 +55,11 @@ enum { collisionTextureCount }; +enum { + idCustomSymbolIconTexture, + customSymbolIconTextureCount +}; + enum { idDebugOverlayTexture, debugTextureCount @@ -99,6 +106,7 @@ enum { static constexpr auto maxTextureCountPerShader = std::max({static_cast(backgroundTextureCount), static_cast(circleTextureCount), static_cast(collisionTextureCount), + static_cast(customSymbolIconTextureCount), static_cast(debugTextureCount), static_cast(fillTextureCount), static_cast(fillExtrusionTextureCount), diff --git a/include/mbgl/shaders/shader_manifest.hpp b/include/mbgl/shaders/shader_manifest.hpp index d769192e296..44882c401d4 100644 --- a/include/mbgl/shaders/shader_manifest.hpp +++ b/include/mbgl/shaders/shader_manifest.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/include/mbgl/shaders/shader_source.hpp b/include/mbgl/shaders/shader_source.hpp index 8f44c1eec88..7facb567cc0 100644 --- a/include/mbgl/shaders/shader_source.hpp +++ b/include/mbgl/shaders/shader_source.hpp @@ -34,6 +34,7 @@ enum class BuiltIn { SymbolIconShader, SymbolSDFIconShader, SymbolTextAndIconShader, + CustomSymbolIconShader, Prelude, BackgroundProgram, BackgroundPatternProgram, diff --git a/include/mbgl/style/layers/custom_drawable_layer.hpp b/include/mbgl/style/layers/custom_drawable_layer.hpp index 7deb77057a4..c2ffdea1e97 100644 --- a/include/mbgl/style/layers/custom_drawable_layer.hpp +++ b/include/mbgl/style/layers/custom_drawable_layer.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -46,6 +48,16 @@ class CustomDrawableLayerHost::Interface { float opacity = 1.f; }; + struct SymbolOptions { + Size size; + gfx::Texture2DPtr texture; + std::array anchor{0.5f, 0.5f}; + std::array, 2> textureCoordinates{{{0, 0}, {1, 1}}}; + float angleDegrees{.0f}; + bool scaleWithMap{false}; + bool pitchWithMap{false}; + }; + public: /// @brief Construct a new Interface object (internal core use only) Interface(RenderLayer& layer, @@ -84,6 +96,13 @@ class CustomDrawableLayerHost::Interface { */ void setFillOptions(const FillOptions& options); + /** + * @brief Set the Symbol options + * + * @param options + */ + void setSymbolOptions(const SymbolOptions& options); + /** * @brief Add a polyline * @@ -92,8 +111,20 @@ class CustomDrawableLayerHost::Interface { */ void addPolyline(const GeometryCoordinates& coordinates); + /** + * @brief Add a multipolygon area fill + * + * @param geometry a collection of rings with optional holes + */ void addFill(const GeometryCollection& geometry); + /** + * @brief Add a symbol + * + * @param point + */ + void addSymbol(const GeometryCoordinate& point); + /** * @brief Finish the current drawable building session * @@ -113,15 +144,20 @@ class CustomDrawableLayerHost::Interface { private: gfx::ShaderPtr lineShaderDefault() const; gfx::ShaderPtr fillShaderDefault() const; + gfx::ShaderPtr symbolShaderDefault() const; std::unique_ptr createBuilder(const std::string& name, gfx::ShaderPtr shader) const; - gfx::ShaderPtr lineShader; - gfx::ShaderPtr fillShader; std::unique_ptr builder; std::optional tileID; + + gfx::ShaderPtr lineShader; + gfx::ShaderPtr fillShader; + gfx::ShaderPtr symbolShader; + LineOptions lineOptions; FillOptions fillOptions; + SymbolOptions symbolOptions; }; class CustomDrawableLayer final : public Layer { diff --git a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm index dce804d73eb..49f0e341226 100644 --- a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm +++ b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm @@ -2,6 +2,9 @@ #import "MLNStyleLayer.h" #import "MLNCustomDrawableStyleLayer.h" +#import + +#include #include #include #include @@ -81,7 +84,7 @@ void update(Interface& interface) override { interface.addPolyline(polyline); } } - + // add fill polygon { using namespace mbgl; @@ -111,6 +114,36 @@ void update(Interface& interface) override { // add fill interface.addFill(geometry); } + + // add symbol + { + using namespace mbgl; + GeometryCoordinate position {static_cast(extent* 0.5f), static_cast(extent* 0.5f)}; + + // load image + UIImage *assetImage = [UIImage imageNamed:@"pin"]; + assert(assetImage.CGImage != NULL); + std::shared_ptr image = std::make_shared(MLNPremultipliedImageFromCGImage(assetImage.CGImage)); + + // set symbol options + Interface::SymbolOptions options; + options.texture = interface.context.createTexture2D(); + options.texture->setImage(image); + options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + options.textureCoordinates = {{{0.0f, 0.08f}, {1.0f, 0.9f}}}; + const float xspan = options.textureCoordinates[1][0] - options.textureCoordinates[0][0]; + const float yspan = options.textureCoordinates[1][1] - options.textureCoordinates[0][1]; + assert(xspan > 0.0f && yspan > 0.0f); + options.size = {static_cast(image->size.width * xspan), static_cast(image->size.height * yspan)}; + options.anchor = {0.5f, 0.95f}; + options.angleDegrees = 45.0f; + options.scaleWithMap = true; + options.pitchWithMap = false; + interface.setSymbolOptions(options); + + // add symbol + interface.addSymbol(position); + } // finish interface.finish(); diff --git a/platform/ios/app/Assets.xcassets/Contents.json b/platform/ios/app/Assets.xcassets/Contents.json index da4a164c918..73c00596a7f 100644 --- a/platform/ios/app/Assets.xcassets/Contents.json +++ b/platform/ios/app/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/platform/ios/app/Assets.xcassets/pin.imageset/Contents.json b/platform/ios/app/Assets.xcassets/pin.imageset/Contents.json new file mode 100644 index 00000000000..102c27746c5 --- /dev/null +++ b/platform/ios/app/Assets.xcassets/pin.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pin1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/app/Assets.xcassets/pin.imageset/pin1.png b/platform/ios/app/Assets.xcassets/pin.imageset/pin1.png new file mode 100644 index 0000000000000000000000000000000000000000..c4cc5d2f5f0f7a0ada4ffc4887b957a84f97f72d GIT binary patch literal 21574 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 literal 0 HcmV?d00001 diff --git a/shaders/drawable.custom.symbol_icon.fragment.glsl b/shaders/drawable.custom.symbol_icon.fragment.glsl new file mode 100644 index 00000000000..0f8a298e350 --- /dev/null +++ b/shaders/drawable.custom.symbol_icon.fragment.glsl @@ -0,0 +1,7 @@ +uniform sampler2D u_texture; + +in vec2 v_tex; + +void main() { + fragColor = texture(u_texture, v_tex); +} diff --git a/shaders/drawable.custom.symbol_icon.vertex.glsl b/shaders/drawable.custom.symbol_icon.vertex.glsl new file mode 100644 index 00000000000..b912f515e68 --- /dev/null +++ b/shaders/drawable.custom.symbol_icon.vertex.glsl @@ -0,0 +1,69 @@ +layout(location = 0) in vec2 a_pos; +layout(location = 1) in vec2 a_tex; + +layout(std140) uniform CustomSymbolIconDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform CustomSymbolIconParametersUBO { + highp vec2 u_extrude_scale; + highp vec2 u_anchor; + highp float u_angle_degrees; + bool u_scale_with_map; + bool u_pitch_with_map; + highp float u_camera_to_center_distance; + highp float u_aspect_ratio; + highp float pad0, pad1, pad3; +}; + +out vec2 v_tex; + +vec2 rotateVec2(vec2 v, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + return vec2(v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA); +} + +vec2 ellipseRotateVec2(vec2 v, float angle, float radiusRatio /* A/B */) { + float cosA = cos(angle); + float sinA = sin(angle); + float invRatio = 1.0 / radiusRatio; + return vec2(v.x * cosA - radiusRatio * v.y * sinA, invRatio * v.x * sinA + v.y * cosA); +} + +void main() { + // decode the extrusion vector (-1, -1) to (1, 1) + vec2 extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0); + + // make anchor relative to (0.5, 0.5) and corners in range (-1, -1) to (1, 1) + vec2 anchor = (u_anchor - vec2(0.5, 0.5)) * 2.0; + + // decode center + vec2 center = floor(a_pos * 0.5); + + // rotate extrusion around anchor + float angle = radians(-u_angle_degrees); + vec2 corner = extrude - anchor; + + // compute + if (u_pitch_with_map) { + if (u_scale_with_map) { + corner *= u_extrude_scale; + } else { + vec4 projected_center = u_matrix * vec4(center, 0, 1); + corner *= u_extrude_scale * (projected_center.w / u_camera_to_center_distance); + } + corner = center + rotateVec2(corner, angle); + gl_Position = u_matrix * vec4(corner, 0, 1); + } else { + gl_Position = u_matrix * vec4(center, 0, 1); + if (u_scale_with_map) { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * u_camera_to_center_distance, angle, u_aspect_ratio); + } else { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * gl_Position.w, angle, u_aspect_ratio); + } + } + + // texture coordinates + v_tex = a_tex; +} diff --git a/shaders/manifest.json b/shaders/manifest.json index 331f3eac248..0280508b020 100644 --- a/shaders/manifest.json +++ b/shaders/manifest.json @@ -174,6 +174,13 @@ "glsl_frag": "drawable.symbol_text_and_icon.fragment.glsl", "uses_ubos": true }, + { + "name": "CustomSymbolIconShader", + "header": "drawable_custom_symbol_icon", + "glsl_vert": "drawable.custom.symbol_icon.vertex.glsl", + "glsl_frag": "drawable.custom.symbol_icon.fragment.glsl", + "uses_ubos": true + }, { "name": "Prelude", diff --git a/src/mbgl/gfx/index_vector.hpp b/src/mbgl/gfx/index_vector.hpp index b42b63321a0..4f67ab053cb 100644 --- a/src/mbgl/gfx/index_vector.hpp +++ b/src/mbgl/gfx/index_vector.hpp @@ -108,7 +108,7 @@ class IndexVector final : public IndexVectorBase { template void emplace_back(Args&&... args) { - static_assert(sizeof...(args) == groupSize, "wrong buffer element count"); + static_assert(sizeof...(args) % groupSize == 0, "wrong buffer element count"); assert(!released); util::ignore({(v.emplace_back(std::forward(args)), 0)...}); } diff --git a/src/mbgl/gfx/vertex_vector.hpp b/src/mbgl/gfx/vertex_vector.hpp index 3be534149c4..c46069a3734 100644 --- a/src/mbgl/gfx/vertex_vector.hpp +++ b/src/mbgl/gfx/vertex_vector.hpp @@ -63,10 +63,10 @@ class VertexVector final : public VertexVectorBase { v(std::move(other.v)) {} ~VertexVector() override = default; - template - void emplace_back(Arg&& vertex) { + template + void emplace_back(Args&&... args) { assert(!released); - v.emplace_back(std::forward(vertex)); + util::ignore({(v.emplace_back(std::forward(args)), 0)...}); dirty = true; } diff --git a/src/mbgl/gl/renderer_backend.cpp b/src/mbgl/gl/renderer_backend.cpp index f84d0021218..1e88e1684ac 100644 --- a/src/mbgl/gl/renderer_backend.cpp +++ b/src/mbgl/gl/renderer_backend.cpp @@ -98,6 +98,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar shaders::BuiltIn::CircleShader, shaders::BuiltIn::CollisionBoxShader, shaders::BuiltIn::CollisionCircleShader, + shaders::BuiltIn::CustomSymbolIconShader, shaders::BuiltIn::DebugShader, shaders::BuiltIn::FillShader, shaders::BuiltIn::FillOutlineShader, diff --git a/src/mbgl/mtl/renderer_backend.cpp b/src/mbgl/mtl/renderer_backend.cpp index e65bcf0795b..a7351175f42 100644 --- a/src/mbgl/mtl/renderer_backend.cpp +++ b/src/mbgl/mtl/renderer_backend.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -129,6 +130,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar shaders::BuiltIn::ClippingMaskProgram, shaders::BuiltIn::CollisionBoxShader, shaders::BuiltIn::CollisionCircleShader, + shaders::BuiltIn::CustomSymbolIconShader, shaders::BuiltIn::DebugShader, shaders::BuiltIn::FillShader, shaders::BuiltIn::FillOutlineShader, diff --git a/src/mbgl/shaders/gl/shader_info.cpp b/src/mbgl/shaders/gl/shader_info.cpp index 637c26db1c1..704e91585d1 100644 --- a/src/mbgl/shaders/gl/shader_info.cpp +++ b/src/mbgl/shaders/gl/shader_info.cpp @@ -48,6 +48,15 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Collision Circle +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"CustomSymbolIconDrawableUBO", idCustomSymbolIconDrawableUBO}, + UniformBlockInfo{"CustomSymbolIconParametersUBO", idCustomSymbolIconParametersUBO}, +}; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_texture", idSymbolImageTexture}, +}; + const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CollisionCircleUBO", idCollisionUBO}, diff --git a/src/mbgl/shaders/mtl/custom_symbol_icon.cpp b/src/mbgl/shaders/mtl/custom_symbol_icon.cpp new file mode 100644 index 00000000000..4c1afaf791e --- /dev/null +++ b/src/mbgl/shaders/mtl/custom_symbol_icon.cpp @@ -0,0 +1,22 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array + ShaderSource::attributes = { + // always attributes + AttributeInfo{0, gfx::AttributeDataType::Float2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Float2, "a_tex"}, +}; +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{2, true, false, sizeof(CustomSymbolIconDrawableUBO), idCustomSymbolIconDrawableUBO}, + UniformBlockInfo{3, true, false, sizeof(CustomSymbolIconParametersUBO), idCustomSymbolIconParametersUBO}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idCustomSymbolIconTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/style/layers/custom_drawable_layer.cpp b/src/mbgl/style/layers/custom_drawable_layer.cpp index 9b8525ec075..8e3554326b4 100644 --- a/src/mbgl/style/layers/custom_drawable_layer.cpp +++ b/src/mbgl/style/layers/custom_drawable_layer.cpp @@ -21,8 +21,10 @@ #include #include #include - #include +#include + +#include namespace mbgl { @@ -176,6 +178,59 @@ class FillDrawableTweaker : public gfx::DrawableTweaker { float opacity; }; +class SymbolDrawableTweaker : public gfx::DrawableTweaker { +public: + SymbolDrawableTweaker(const CustomDrawableLayerHost::Interface::SymbolOptions& options_) + : options(options_) {} + ~SymbolDrawableTweaker() override = default; + + void init(gfx::Drawable&) override{}; + + void execute(gfx::Drawable& drawable, const PaintParameters& parameters) override { + if (!drawable.getTileID().has_value()) { + return; + } + + const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); + mat4 tileMatrix; + parameters.state.matrixFor(/*out*/ tileMatrix, tileID); + + const auto matrix = LayerTweaker::getTileMatrix( + tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); + + const shaders::CustomSymbolIconDrawableUBO drawableUBO{/*matrix = */ util::cast(matrix)}; + + const auto pixelsToTileUnits = tileID.pixelsToTileUnits( + 1.0f, options.scaleWithMap ? tileID.canonical.z : parameters.state.getZoom()); + const float factor = options.scaleWithMap + ? static_cast(std::pow(2.f, parameters.state.getZoom() - tileID.canonical.z)) + : 1.0f; + const auto extrudeScale = options.pitchWithMap ? std::array{pixelsToTileUnits, pixelsToTileUnits} + : std::array{parameters.pixelsToGLUnits[0] * factor, + parameters.pixelsToGLUnits[1] * factor}; + + const shaders::CustomSymbolIconParametersUBO parametersUBO{ + /*extrude_scale*/ {extrudeScale[0] * options.size.width, extrudeScale[1] * options.size.height}, + /*anchor*/ options.anchor, + /*angle_degrees*/ options.angleDegrees, + /*scale_with_map*/ options.scaleWithMap, + /*pitch_with_map*/ options.pitchWithMap, + /*camera_to_center_distance*/ parameters.state.getCameraToCenterDistance(), + /*aspect_ratio*/ parameters.pixelsToGLUnits[0] / parameters.pixelsToGLUnits[1], + 0, + 0, + 0}; + + // set UBOs + auto& uniforms = drawable.mutableUniformBuffers(); + uniforms.createOrUpdate(idCustomSymbolIconDrawableUBO, &drawableUBO, parameters.context); + uniforms.createOrUpdate(idCustomSymbolIconParametersUBO, ¶metersUBO, parameters.context); + }; + +private: + CustomDrawableLayerHost::Interface::SymbolOptions options; +}; + CustomDrawableLayerHost::Interface::Interface(RenderLayer& layer_, LayerGroupBasePtr& layerGroup_, gfx::ShaderRegistry& shaders_, @@ -220,10 +275,16 @@ void CustomDrawableLayerHost::Interface::setFillOptions(const FillOptions& optio fillOptions = options; } +void CustomDrawableLayerHost::Interface::setSymbolOptions(const SymbolOptions& options) { + finish(); + symbolOptions = options; +} + void CustomDrawableLayerHost::Interface::addPolyline(const GeometryCoordinates& coordinates) { if (!lineShader) lineShader = lineShaderDefault(); assert(lineShader); if (!builder || builder->getShader() != lineShader) { + finish(); builder = createBuilder("lines", lineShader); } assert(builder); @@ -235,6 +296,7 @@ void CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geome if (!fillShader) fillShader = fillShaderDefault(); assert(fillShader); if (!builder || builder->getShader() != fillShader) { + finish(); builder = createBuilder("fill", fillShader); } assert(builder); @@ -274,15 +336,94 @@ void CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geome builder->flush(context); } +void CustomDrawableLayerHost::Interface::addSymbol(const GeometryCoordinate& point) { + if (!symbolShader) symbolShader = symbolShaderDefault(); + assert(symbolShader); + if (!builder || builder->getShader() != symbolShader) { + finish(); + builder = createBuilder("symbol", symbolShader); + } + assert(builder); + assert(builder->getShader() == symbolShader); + + // temporary: buffers + struct CustomSymbolIcon { + std::array a_pos; + std::array a_tex; + }; + + // vertices + using VertexVector = gfx::VertexVector; + const std::shared_ptr sharedVertices = std::make_shared(); + VertexVector& vertices = *sharedVertices; + + // encode center and extrude direction into vertices + for (int y = 0; y <= 1; ++y) { + for (int x = 0; x <= 1; ++x) { + vertices.emplace_back( + CustomSymbolIcon{{static_cast(point.x * 2 + x), static_cast(point.y * 2 + y)}, + {symbolOptions.textureCoordinates[x][0], symbolOptions.textureCoordinates[y][1]}}); + } + } + + // indexes + using TriangleIndexVector = gfx::IndexVector; + const std::shared_ptr sharedTriangles = std::make_shared(); + TriangleIndexVector& triangles = *sharedTriangles; + + triangles.emplace_back(0, 1, 2, 1, 2, 3); + + SegmentVector triangleSegments; + triangleSegments.emplace_back(Segment{0, 0, 4, 6}); + + // add to builder + static const StringIdentity idPositionAttribName = stringIndexer().get("a_pos"); + static const StringIdentity idTextureAttribName = stringIndexer().get("a_tex"); + builder->setVertexAttrNameId(idPositionAttribName); + + auto attrs = context.createVertexAttributeArray(); + if (const auto& attr = attrs->add(idPositionAttribName)) { + attr->setSharedRawData(sharedVertices, + offsetof(CustomSymbolIcon, a_pos), + /*vertexOffset=*/0, + sizeof(CustomSymbolIcon), + gfx::AttributeDataType::Float2); + } + if (const auto& attr = attrs->add(idTextureAttribName)) { + attr->setSharedRawData(sharedVertices, + offsetof(CustomSymbolIcon, a_tex), + /*vertexOffset=*/0, + sizeof(CustomSymbolIcon), + gfx::AttributeDataType::Float2); + } + builder->setVertexAttributes(std::move(attrs)); + builder->setRawVertices({}, vertices.elements(), gfx::AttributeDataType::Float2); + builder->setSegments(gfx::Triangles(), sharedTriangles, triangleSegments.data(), triangleSegments.size()); + + // texture + if (symbolOptions.texture) { + builder->setTexture(symbolOptions.texture, idCustomSymbolIconTexture); + } + + // create fill tweaker + auto tweaker = std::make_shared(symbolOptions); + builder->addTweaker(tweaker); + + // flush current builder drawable + builder->flush(context); +} + void CustomDrawableLayerHost::Interface::finish() { if (builder && !builder->empty()) { // finish - const auto finish_ = [this](auto& tweaker) { + const auto finish_ = [this](gfx::DrawableTweakerPtr tweaker) { builder->flush(context); for (auto& drawable : builder->clearDrawables()) { assert(tileID.has_value()); drawable->setTileID(tileID.value()); - drawable->addTweaker(tweaker); + if (tweaker) { + drawable->addTweaker(tweaker); + } TileLayerGroup* tileLayerGroup = static_cast(layerGroup.get()); tileLayerGroup->addDrawable(RenderPass::Translucent, tileID.value(), std::move(drawable)); @@ -314,12 +455,17 @@ void CustomDrawableLayerHost::Interface::finish() { // finish drawables finish_(tweaker); + } else if (builder->getShader() == symbolShader) { + // finish building symbols + + // finish drawables + finish_(nullptr); } } } gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const { - gfx::ShaderGroupPtr lineShaderGroup = shaders.getShaderGroup("LineShader"); + gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("LineShader"); const mbgl::unordered_set propertiesAsUniforms{ stringIndexer().get("a_color"), @@ -330,18 +476,22 @@ gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const { stringIndexer().get("a_width"), }; - return lineShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + return shaderGroup->getOrCreateShader(context, propertiesAsUniforms); } gfx::ShaderPtr CustomDrawableLayerHost::Interface::fillShaderDefault() const { - gfx::ShaderGroupPtr fillShaderGroup = shaders.getShaderGroup("FillShader"); + gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("FillShader"); const mbgl::unordered_set propertiesAsUniforms{ stringIndexer().get("a_color"), stringIndexer().get("a_opacity"), }; - return fillShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + return shaderGroup->getOrCreateShader(context, propertiesAsUniforms); +} + +gfx::ShaderPtr CustomDrawableLayerHost::Interface::symbolShaderDefault() const { + return context.getGenericShader(shaders, "CustomSymbolIconShader"); } std::unique_ptr CustomDrawableLayerHost::Interface::createBuilder(const std::string& name, diff --git a/test/api/custom_drawable_layer.test.cpp b/test/api/custom_drawable_layer.test.cpp index c9a2599af01..e3c27f58c69 100644 --- a/test/api/custom_drawable_layer.test.cpp +++ b/test/api/custom_drawable_layer.test.cpp @@ -14,11 +14,14 @@ #include #include +#include +#include +#include #include #include -class TestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { +class LineTestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { public: void initialize() override {} @@ -104,6 +107,24 @@ class TestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { } } + // finish + interface.finish(); + } + + void deinitialize() override {} +}; + +class FillTestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { +public: + void initialize() override {} + + void update(Interface& interface) override { + // if we have built our drawable(s) already, either update or skip + if (interface.getDrawableCount()) return; + + // set tile + interface.setTileID({11, 327, 791}); + // add fill polygon { using namespace mbgl; @@ -141,7 +162,65 @@ class TestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { void deinitialize() override {} }; -TEST(CustomDrawableLayer, Basic) { +class SymbolIconTestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { +public: + void initialize() override {} + + void update(Interface& interface) override { + // if we have built our drawable(s) already, either update or skip + if (interface.getDrawableCount()) return; + + // set tile + interface.setTileID({11, 327, 791}); + + // add symbol icon + { + using namespace mbgl; + GeometryCoordinate position{static_cast(util::EXTENT * 0.5f), + static_cast(util::EXTENT * 0.5f)}; + + // load image + std::string imageData; // doh! + constexpr auto imagePath = "test/fixtures/custom_drawable_layer/symbol_icon/pin1.png"; + try { + imageData = util::read_file(imagePath); + } catch (std::exception& ex) { + using namespace std::string_literals; + Log::Error(Event::Setup, "Failed to load expected image "s + imagePath + ": " + ex.what()); + throw; + } + std::shared_ptr image = std::make_shared(decodeImage(imageData)); + + // set symbol options + Interface::SymbolOptions options; + options.texture = interface.context.createTexture2D(); + options.texture->setImage(image); + options.texture->setSamplerConfiguration( + {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + options.textureCoordinates = {{{0.0f, 0.08f}, {1.0f, 0.9f}}}; + const float xspan = options.textureCoordinates[1][0] - options.textureCoordinates[0][0]; + const float yspan = options.textureCoordinates[1][1] - options.textureCoordinates[0][1]; + assert(xspan > 0.0f && yspan > 0.0f); + options.size = {static_cast(image->size.width * 0.2f * xspan), + static_cast(image->size.height * 0.2f * yspan)}; + options.anchor = {0.5f, 0.95f}; + options.angleDegrees = 45.0f; + options.scaleWithMap = false; + options.pitchWithMap = false; + interface.setSymbolOptions(options); + + // add symbol + interface.addSymbol(position); + } + + // finish + interface.finish(); + } + + void deinitialize() override {} +}; + +TEST(CustomDrawableLayer, Line) { using namespace mbgl; using namespace mbgl::style; @@ -154,21 +233,63 @@ TEST(CustomDrawableLayer, Basic) { ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets")); // load style - map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); + map.getStyle().loadJSON(util::read_file("test/fixtures/api/simple.json")); map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.4426032}).withZoom(10.0)); - // add fill layer - auto layer = std::make_unique("landcover", "mapbox"); - layer->setSourceLayer("landcover"); - layer->setFillColor(Color{1.0, 1.0, 0.0, 1.0}); - map.getStyle().addLayer(std::move(layer)); + // add custom drawable layer + map.getStyle().addLayer( + std::make_unique("custom-drawable", std::make_unique())); + + // render and test + test::checkImage("test/fixtures/custom_drawable_layer/line", frontend.render(map).image, 0.000657, 0.1); +} + +TEST(CustomDrawableLayer, Fill) { + using namespace mbgl; + using namespace mbgl::style; + + util::RunLoop loop; + + HeadlessFrontend frontend{1}; + Map map(frontend, + MapObserver::nullObserver(), + MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize()), + ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets")); + + // load style + map.getStyle().loadJSON(util::read_file("test/fixtures/api/simple.json")); + map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.4426032}).withZoom(10.0)); + + // add custom drawable layer + map.getStyle().addLayer( + std::make_unique("custom-drawable", std::make_unique())); + + // render and test + test::checkImage("test/fixtures/custom_drawable_layer/fill", frontend.render(map).image, 0.000657, 0.1); +} + +TEST(CustomDrawableLayer, SymbolIcon) { + using namespace mbgl; + using namespace mbgl::style; + + util::RunLoop loop; + + HeadlessFrontend frontend{1}; + Map map(frontend, + MapObserver::nullObserver(), + MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize()), + ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets")); + + // load style + map.getStyle().loadJSON(util::read_file("test/fixtures/api/simple.json")); + map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.4426032}).withZoom(10.0)); // add custom drawable layer map.getStyle().addLayer( - std::make_unique("custom-drawable", std::make_unique())); + std::make_unique("custom-drawable", std::make_unique())); // render and test - test::checkImage("test/fixtures/custom_drawable_layer/basic", frontend.render(map).image, 0.000657, 0.1); + test::checkImage("test/fixtures/custom_drawable_layer/symbol_icon", frontend.render(map).image, 0.000657, 0.1); } #endif // MLN_DRAWABLE_RENDERER diff --git a/test/fixtures/api/simple.json b/test/fixtures/api/simple.json new file mode 100644 index 00000000000..d8827381676 --- /dev/null +++ b/test/fixtures/api/simple.json @@ -0,0 +1,25 @@ +{ + "version": 8, + "name": "Water", + "sources": { + "mapbox": { + "type": "vector", + "tiles": ["asset://streets/{z}-{x}-{y}.vector.pbf"] + } + }, + "layers": [{ + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "#3440eb" + } + }] +} diff --git a/test/fixtures/custom_drawable_layer/basic/expected.png b/test/fixtures/custom_drawable_layer/basic/expected.png deleted file mode 100644 index 10572d2719d907ddd59b9b5278bce5d7df403a2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30225 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� 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 diff --git a/test/fixtures/custom_drawable_layer/fill/expected.png b/test/fixtures/custom_drawable_layer/fill/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..26a6fa1c2fee964405695d469965518351e55911 GIT binary patch literal 7742 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 UNaR8>&xHYFgA4kVy2Ob81DX^TL;wH) literal 0 HcmV?d00001 diff --git a/test/fixtures/custom_drawable_layer/line/expected.png b/test/fixtures/custom_drawable_layer/line/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..217b3e170bcea1482b4c2081819e8b2388598659 GIT binary patch literal 35055 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 T%;q8X00000NkvXXu0mjfGppjY literal 0 HcmV?d00001 diff --git a/test/fixtures/custom_drawable_layer/symbol_icon/expected.png b/test/fixtures/custom_drawable_layer/symbol_icon/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..7e2cecc29c1271e29474593c841f8443a36ca842 GIT binary patch literal 11379 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

  • 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 literal 0 HcmV?d00001 From 7387bb8c02ca6adb678c24ad604a15a35e80d384 Mon Sep 17 00:00:00 2001 From: Alex Cristici Date: Fri, 16 Feb 2024 20:29:53 +0200 Subject: [PATCH 72/96] Vertex attribute by index instead of map (#2116) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- include/mbgl/gfx/drawable_atlases_tweaker.hpp | 1 - include/mbgl/gfx/drawable_builder.hpp | 7 +- include/mbgl/gfx/shader_group.hpp | 15 +- include/mbgl/gfx/vertex_attribute.hpp | 105 ++++----- include/mbgl/gl/drawable_gl.hpp | 3 +- include/mbgl/mtl/drawable.hpp | 3 +- include/mbgl/renderer/layer_tweaker.hpp | 1 - .../shaders/custom_drawable_layer_ubo.hpp | 6 +- include/mbgl/shaders/gl/shader_group_gl.hpp | 10 +- include/mbgl/shaders/gl/shader_info.hpp | 32 +++ include/mbgl/shaders/gl/shader_program_gl.hpp | 1 + include/mbgl/shaders/mtl/clipping_mask.hpp | 2 - include/mbgl/shaders/mtl/shader_group.hpp | 14 +- include/mbgl/shaders/mtl/shader_program.hpp | 5 +- include/mbgl/shaders/shader_defines.hpp | 174 +++++++++++++- include/mbgl/shaders/shader_program_base.hpp | 3 +- src/mbgl/gfx/drawable_builder.cpp | 3 +- src/mbgl/gfx/drawable_builder_impl.cpp | 16 +- src/mbgl/gfx/symbol_drawable_data.hpp | 3 - src/mbgl/gfx/vertex_attribute.cpp | 54 ++--- src/mbgl/gl/drawable_gl.cpp | 7 +- src/mbgl/gl/drawable_gl_builder.cpp | 2 +- src/mbgl/gl/drawable_gl_impl.hpp | 3 +- src/mbgl/gl/upload_pass.cpp | 6 +- src/mbgl/gl/vertex_attribute_gl.cpp | 8 +- src/mbgl/mtl/drawable.cpp | 13 +- src/mbgl/mtl/drawable_builder.cpp | 2 +- src/mbgl/mtl/drawable_impl.hpp | 3 +- src/mbgl/mtl/upload_pass.cpp | 4 +- src/mbgl/mtl/vertex_attribute.cpp | 8 +- .../renderer/layers/circle_layer_tweaker.cpp | 1 - .../layers/collision_layer_tweaker.hpp | 1 - .../layers/fill_extrusion_layer_tweaker.hpp | 1 - .../renderer/layers/fill_layer_tweaker.hpp | 1 - .../renderer/layers/heatmap_layer_tweaker.cpp | 1 - .../layers/heatmap_texture_layer_tweaker.cpp | 1 - .../layers/hillshade_layer_tweaker.cpp | 1 - .../hillshade_prepare_layer_tweaker.cpp | 1 - .../renderer/layers/raster_layer_tweaker.cpp | 1 - .../layers/render_background_layer.cpp | 2 + .../renderer/layers/render_circle_layer.cpp | 15 +- .../layers/render_fill_extrusion_layer.cpp | 18 +- .../renderer/layers/render_fill_layer.cpp | 30 +-- .../renderer/layers/render_heatmap_layer.cpp | 16 +- .../layers/render_hillshade_layer.cpp | 11 +- .../renderer/layers/render_line_layer.cpp | 110 ++++----- .../renderer/layers/render_raster_layer.cpp | 10 +- .../renderer/layers/render_symbol_layer.cpp | 57 ++--- .../renderer/layers/symbol_layer_tweaker.hpp | 1 - .../renderer/sources/render_tile_source.cpp | 23 +- src/mbgl/shaders/gl/shader_info.cpp | 212 ++++++++++++++++-- src/mbgl/shaders/gl/shader_program_gl.cpp | 9 +- src/mbgl/shaders/mtl/background.cpp | 2 +- src/mbgl/shaders/mtl/background_pattern.cpp | 2 +- src/mbgl/shaders/mtl/circle.cpp | 16 +- src/mbgl/shaders/mtl/clipping_mask.cpp | 4 +- src/mbgl/shaders/mtl/collision_box.cpp | 10 +- src/mbgl/shaders/mtl/collision_circle.cpp | 8 +- src/mbgl/shaders/mtl/custom_symbol_icon.cpp | 10 +- src/mbgl/shaders/mtl/debug.cpp | 2 +- src/mbgl/shaders/mtl/fill.cpp | 28 +-- src/mbgl/shaders/mtl/fill_extrusion.cpp | 10 +- .../shaders/mtl/fill_extrusion_pattern.cpp | 12 +- src/mbgl/shaders/mtl/heatmap.cpp | 6 +- src/mbgl/shaders/mtl/heatmap_texture.cpp | 2 +- src/mbgl/shaders/mtl/hillshade.cpp | 4 +- src/mbgl/shaders/mtl/hillshade_prepare.cpp | 4 +- src/mbgl/shaders/mtl/line.cpp | 56 ++--- src/mbgl/shaders/mtl/line_gradient.cpp | 14 +- src/mbgl/shaders/mtl/raster.cpp | 4 +- src/mbgl/shaders/mtl/shader_program.cpp | 14 +- src/mbgl/shaders/mtl/symbol_icon.cpp | 12 +- src/mbgl/shaders/mtl/symbol_sdf.cpp | 20 +- src/mbgl/shaders/mtl/symbol_text_and_icon.cpp | 18 +- .../style/layers/custom_drawable_layer.cpp | 41 ++-- src/mbgl/style/paint_property.hpp | 12 - test/util/hash.test.cpp | 6 +- 77 files changed, 780 insertions(+), 574 deletions(-) diff --git a/include/mbgl/gfx/drawable_atlases_tweaker.hpp b/include/mbgl/gfx/drawable_atlases_tweaker.hpp index 5d74eb45bd7..f988c697efe 100644 --- a/include/mbgl/gfx/drawable_atlases_tweaker.hpp +++ b/include/mbgl/gfx/drawable_atlases_tweaker.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include diff --git a/include/mbgl/gfx/drawable_builder.hpp b/include/mbgl/gfx/drawable_builder.hpp index 3ec548416ad..5ec1f7c64bc 100644 --- a/include/mbgl/gfx/drawable_builder.hpp +++ b/include/mbgl/gfx/drawable_builder.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -137,8 +136,8 @@ class DrawableBuilder { /// Set the name given to new drawables void setDrawableName(std::string value) { drawableName = std::move(value); } - /// The attribute names for vertex/position attributes - void setVertexAttrNameId(const StringIdentity id) { vertexAttrNameId = id; } + /// The attribute id for vertex/position attribute + void setVertexAttrId(const size_t id) { vertexAttrId = id; } /// @brief Get the texture at the given internal ID. const gfx::Texture2DPtr& getTexture(size_t id) const; @@ -216,7 +215,7 @@ class DrawableBuilder { protected: std::string name; std::string drawableName; - StringIdentity vertexAttrNameId; + std::size_t vertexAttrId; mbgl::RenderPass renderPass; bool enabled = true; bool enableColor = true; diff --git a/include/mbgl/gfx/shader_group.hpp b/include/mbgl/gfx/shader_group.hpp index a271c1b9bde..89fc2cfc33a 100644 --- a/include/mbgl/gfx/shader_group.hpp +++ b/include/mbgl/gfx/shader_group.hpp @@ -13,7 +13,7 @@ namespace mbgl { -using StringIdentity = std::size_t; +using StringIDSetsPair = std::pair, unordered_set>; namespace gfx { @@ -147,10 +147,9 @@ class ShaderGroup { /// @param propertiesAsUniforms Set of data driven properties as uniforms. /// @param firstAttribName Name of the first attribute /// @return A `gfx::ShaderPtr` - virtual gfx::ShaderPtr getOrCreateShader( - gfx::Context&, - [[maybe_unused]] const mbgl::unordered_set& propertiesAsUniforms, - [[maybe_unused]] std::string_view firstAttribName = "a_pos") { + virtual gfx::ShaderPtr getOrCreateShader(gfx::Context&, + [[maybe_unused]] const StringIDSetsPair& propertiesAsUniforms, + [[maybe_unused]] std::string_view firstAttribName = "a_pos") { return {}; } @@ -162,9 +161,9 @@ class ShaderGroup { } /// Generate a map key for the specified combination of properties - PropertyHashType propertyHash(const mbgl::unordered_set& propertiesAsUniforms) { - const auto beg = propertiesAsUniforms.cbegin(); - const auto end = propertiesAsUniforms.cend(); + PropertyHashType propertyHash(const StringIDSetsPair& propertiesAsUniforms) { + const auto beg = propertiesAsUniforms.second.cbegin(); + const auto end = propertiesAsUniforms.second.cend(); return util::order_independent_hash(beg, end); } diff --git a/include/mbgl/gfx/vertex_attribute.hpp b/include/mbgl/gfx/vertex_attribute.hpp index dae588c77a0..4cb09d969b3 100644 --- a/include/mbgl/gfx/vertex_attribute.hpp +++ b/include/mbgl/gfx/vertex_attribute.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -33,6 +32,7 @@ class VertexAttributeArray; class VertexVectorBase; using UniqueVertexAttribute = std::unique_ptr; +using StringIDSetsPair = std::pair, unordered_set>; class VertexAttribute { public: @@ -279,15 +279,14 @@ class VertexAttribute { /// Stores a collection of vertex attributes by name class VertexAttributeArray { public: - using AttributeMap = mbgl::unordered_map>; - + using AttributeVector = std::array, shaders::maxVertexAttributeCountPerShader>; VertexAttributeArray() = default; VertexAttributeArray(VertexAttributeArray&&); VertexAttributeArray(const VertexAttributeArray&) = delete; // Would need to use the virtual assignment operator virtual ~VertexAttributeArray() = default; - /// Number of elements - std::size_t size() const { return attrs.size(); } + /// Number of maximum allocated elements + std::size_t allocatedSize() const { return attrs.size(); } /// Sum of element strides, and the total size of a vertex in the buffer std::size_t getTotalSize() const; @@ -295,64 +294,46 @@ class VertexAttributeArray { /// Get the largest count value of the attribute elements std::size_t getMaxCount() const; - /// Add a new attribute element. - /// Returns a pointer to the new element on success, or null if the attribute already exists. - /// The result is valid only until the next non-const method call on this class. - const std::unique_ptr& get(const StringIdentity id) const; + /// Get a attribute element. + /// Returns a pointer to the element on success, or null if the attribute doesn't exists. + const std::unique_ptr& get(const size_t id) const; - /// Add a new attribute element. + /// Set a new attribute element or replace the existing one. /// Returns a pointer to the new element on success, or null if the attribute already exists. /// The result is valid only until the next non-const method call on this class. + /// @param index index to match, or -1 for any + /// @param type type to match, or `Invalid` for any /// @param count Number of items, zero for shared data - const std::unique_ptr& add(const StringIdentity id, + const std::unique_ptr& set(const size_t id, int index = -1, AttributeDataType type = AttributeDataType::Invalid, std::size_t count = 0); - /// Add a new attribute element if it doesn't already exist. - /// Returns a pointer to the new element on success, or null if the type or count conflict with an existing entry. - /// The result is valid only until the next non-const method call on this class. - /// @param index index to match, or -1 for any - /// @param type type to match, or `Invalid` for any - /// @param count Number of items, zero for shared data - const std::unique_ptr& getOrAdd(const StringIdentity id, - int index = -1, - AttributeDataType type = AttributeDataType::Invalid, - std::size_t count = 0); - - // Set a value if the element is present - template - bool set(const StringIdentity id, std::size_t i, T value) { - if (const auto& item = get(id)) { - return item->set(i, value); - } - return false; - } - /// Indicates whether any values have changed virtual bool isDirty() const { - return std::any_of( - attrs.begin(), attrs.end(), [](const auto& kv) { return kv.second && kv.second->isDirty(); }); + return std::any_of(attrs.begin(), attrs.end(), [](const auto& attr) { return attr && attr->isDirty(); }); } /// Clear the collection void clear(); /// Do something with each attribute - template + template void visitAttributes(Func f) { - std::for_each(attrs.begin(), attrs.end(), [&](const auto& kv) { - if (kv.second) { - f(kv.first, *kv.second); + std::for_each(attrs.begin(), attrs.end(), [&](const auto& attr) { + if (attr) { + f(*attr); } }); } /// Call the provided delegate with each value, providing the override if one exists. - template &) */> + template &) */> void resolve(const VertexAttributeArray& overrides, Func delegate) const { - for (auto& kv : attrs) { - delegate(kv.first, *kv.second, overrides.get(kv.first)); + for (size_t id = 0; id < attrs.size(); id++) { + if (const auto& attr = attrs[id]) { + delegate(id, *attr, overrides.get(id)); + } } } @@ -368,14 +349,18 @@ class VertexAttributeArray { template void readDataDrivenPaintProperties(const Binders& binders, const Evaluated& evaluated, - mbgl::unordered_set* propertiesAsUniforms) { + StringIDSetsPair* propertiesAsUniforms, + const size_t firstDataDrivenAttrId) { // Read each property in the type pack if (propertiesAsUniforms) { - propertiesAsUniforms->reserve(sizeof...(DataDrivenPaintProperty)); + propertiesAsUniforms->first.reserve(sizeof...(DataDrivenPaintProperty)); + propertiesAsUniforms->second.reserve(sizeof...(DataDrivenPaintProperty)); } + size_t dataDrivenAttrId = firstDataDrivenAttrId; (readDataDrivenPaintProperty(binders.template get(), isConstant(evaluated), - propertiesAsUniforms), + propertiesAsUniforms, + dataDrivenAttrId), ...); } @@ -388,12 +373,17 @@ class VertexAttributeArray { template void readDataDrivenPaintProperties(const Binders& binders, const Evaluated& evaluated, - mbgl::unordered_set& propertiesAsUniforms) { + StringIDSetsPair& propertiesAsUniforms, + const size_t firstDataDrivenAttrId) { // Read each property in the type pack - propertiesAsUniforms.reserve(sizeof...(DataDrivenPaintProperty)); + propertiesAsUniforms.first.reserve(sizeof...(DataDrivenPaintProperty)); + propertiesAsUniforms.second.reserve(sizeof...(DataDrivenPaintProperty)); + + size_t dataDrivenAttrId = firstDataDrivenAttrId; (readDataDrivenPaintProperty(binders.template get(), isConstant(evaluated), - &propertiesAsUniforms), + &propertiesAsUniforms, + dataDrivenAttrId), ...); } @@ -407,28 +397,27 @@ class VertexAttributeArray { template void readDataDrivenPaintProperty(const Binder& binder, const bool isConstant, - mbgl::unordered_set* propertiesAsUniforms) { + StringIDSetsPair* propertiesAsUniforms, + size_t& dataDrivenAttrId) { if (!binder) { return; } // Consider each attribute name in the attribute (e.g., pattern_from, pattern_to) for (std::size_t attrIndex = 0; attrIndex < DataDrivenPaintProperty::AttributeNames.size(); ++attrIndex) { - auto& attributeNameID = DataDrivenPaintProperty::AttributeNameIDs[attrIndex]; - if (!attributeNameID) { - const auto& attributeName = DataDrivenPaintProperty::AttributeNames[attrIndex]; - attributeNameID = stringIndexer().get(attributePrefix + attributeName.data()); - } + const auto& attributeName = DataDrivenPaintProperty::AttributeNames[attrIndex]; // Apply the property, or add it to the uniforms collection if it's constant. if (!isConstant && binder->getVertexCount() > 0) { using Attribute = typename DataDrivenPaintProperty::Attribute; - const auto& attr = getOrAdd( - *attributeNameID, /*index=*/-1, /*type=*/gfx::AttributeDataType::Invalid, /*count=*/0); - applyPaintProperty(attrIndex, attr, binder); + if (const auto& attr = set(dataDrivenAttrId)) { + applyPaintProperty(attrIndex, attr, binder); + } } else if (propertiesAsUniforms) { - propertiesAsUniforms->emplace(*attributeNameID); + propertiesAsUniforms->first.emplace(attributeName); + propertiesAsUniforms->second.emplace(dataDrivenAttrId); } + dataDrivenAttrId++; } } @@ -459,8 +448,6 @@ class VertexAttributeArray { } } - const UniqueVertexAttribute& add(const StringIdentity id, std::unique_ptr&&); - virtual UniqueVertexAttribute create(int index, AttributeDataType dataType, std::size_t count) const { return std::make_unique(index, dataType, count); } @@ -470,7 +457,7 @@ class VertexAttributeArray { } protected: - AttributeMap attrs; + AttributeVector attrs; static const std::unique_ptr nullref; static const std::string attributePrefix; }; diff --git a/include/mbgl/gl/drawable_gl.hpp b/include/mbgl/gl/drawable_gl.hpp index 7da9372ba8a..6746939c22d 100644 --- a/include/mbgl/gl/drawable_gl.hpp +++ b/include/mbgl/gl/drawable_gl.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include @@ -45,7 +44,7 @@ class DrawableGL : public gfx::Drawable { const gfx::UniformBufferArray& getUniformBuffers() const override; gfx::UniformBufferArray& mutableUniformBuffers() override; - void setVertexAttrNameId(const StringIdentity id); + void setVertexAttrId(const size_t id); void upload(gfx::UploadPass&); diff --git a/include/mbgl/mtl/drawable.hpp b/include/mbgl/mtl/drawable.hpp index 83370b782a5..a307b0b520c 100644 --- a/include/mbgl/mtl/drawable.hpp +++ b/include/mbgl/mtl/drawable.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -45,7 +44,7 @@ class Drawable : public gfx::Drawable { const gfx::UniformBufferArray& getUniformBuffers() const override; gfx::UniformBufferArray& mutableUniformBuffers() override; - void setVertexAttrNameId(const StringIdentity); + void setVertexAttrId(const size_t); void upload(gfx::UploadPass&); diff --git a/include/mbgl/renderer/layer_tweaker.hpp b/include/mbgl/renderer/layer_tweaker.hpp index 347580466e2..ac64c2ff5fa 100644 --- a/include/mbgl/renderer/layer_tweaker.hpp +++ b/include/mbgl/renderer/layer_tweaker.hpp @@ -29,7 +29,6 @@ class RenderTree; class UnwrappedTileID; using mat4 = std::array; -using StringIdentity = std::size_t; /** Base class for layer tweakers, which manipulate layer group per frame diff --git a/include/mbgl/shaders/custom_drawable_layer_ubo.hpp b/include/mbgl/shaders/custom_drawable_layer_ubo.hpp index 36259ea819a..8742c4bbb5a 100644 --- a/include/mbgl/shaders/custom_drawable_layer_ubo.hpp +++ b/include/mbgl/shaders/custom_drawable_layer_ubo.hpp @@ -27,9 +27,9 @@ struct alignas(16) CustomSymbolIconParametersUBO { static_assert(sizeof(CustomSymbolIconParametersUBO) == 3 * 16); enum { - idCustomSymbolIconDrawableUBO, - idCustomSymbolIconParametersUBO, - customDrawableUBOCount + idCustomSymbolDrawableUBO, + idCustomSymbolParametersUBO, + customSymbolUBOCount }; } // namespace shaders diff --git a/include/mbgl/shaders/gl/shader_group_gl.hpp b/include/mbgl/shaders/gl/shader_group_gl.hpp index 10677538e0f..15e4022279d 100644 --- a/include/mbgl/shaders/gl/shader_group_gl.hpp +++ b/include/mbgl/shaders/gl/shader_group_gl.hpp @@ -18,7 +18,7 @@ class ShaderGroupGL final : public gfx::ShaderGroup { ~ShaderGroupGL() noexcept override = default; gfx::ShaderPtr getOrCreateShader(gfx::Context& context, - const mbgl::unordered_set& propertiesAsUniforms, + const StringIDSetsPair& propertiesAsUniforms, std::string_view firstAttribName) override { constexpr auto& name = shaders::ShaderSource::name; constexpr auto& vert = shaders::ShaderSource::vertex; @@ -38,11 +38,10 @@ class ShaderGroupGL final : public gfx::ShaderGroup { // No match, we need to create the shader. std::string additionalDefines; - additionalDefines.reserve(propertiesAsUniforms.size() * 48); - for (const auto nameID : propertiesAsUniforms) { + additionalDefines.reserve(propertiesAsUniforms.first.size() * 48); + for (const auto propertyName : propertiesAsUniforms.first) { // We expect the names to be prefixed by "a_", but we need just the base here. - const auto prefixedAttrName = stringIndexer().get(nameID); - const auto* prefix = prefixedAttrName.data(); + const auto* prefix = propertyName.data(); if (prefix[0] == 'a' && prefix[1] == '_') { prefix += 2; } @@ -58,6 +57,7 @@ class ShaderGroupGL final : public gfx::ShaderGroup { firstAttribName, shaders::ShaderInfo::uniformBlocks, shaders::ShaderInfo::textures, + shaders::ShaderInfo::attributes, vert, frag, additionalDefines); diff --git a/include/mbgl/shaders/gl/shader_info.hpp b/include/mbgl/shaders/gl/shader_info.hpp index 0ce68f3b6c5..e4317498052 100644 --- a/include/mbgl/shaders/gl/shader_info.hpp +++ b/include/mbgl/shaders/gl/shader_info.hpp @@ -8,6 +8,12 @@ namespace mbgl { namespace shaders { +struct AttributeInfo { + AttributeInfo(std::string_view name, std::size_t id); + std::string_view name; + std::size_t id; +}; + struct UniformBlockInfo { UniformBlockInfo(std::string_view name, std::size_t id); std::string_view name; @@ -26,156 +32,182 @@ struct ShaderInfo; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; template <> struct ShaderInfo { + static const std::vector attributes; static const std::vector uniformBlocks; static const std::vector textures; }; diff --git a/include/mbgl/shaders/gl/shader_program_gl.hpp b/include/mbgl/shaders/gl/shader_program_gl.hpp index f1739f2d335..222049a1778 100644 --- a/include/mbgl/shaders/gl/shader_program_gl.hpp +++ b/include/mbgl/shaders/gl/shader_program_gl.hpp @@ -35,6 +35,7 @@ class ShaderProgramGL final : public gfx::ShaderProgramBase { const std::string_view firstAttribName, const std::vector& uniformBlocksInfo, const std::vector& texturesInfo, + const std::vector& attributesInfo, const std::string& vertexSource, const std::string& fragmentSource, const std::string& additionalDefines = "") noexcept(false); diff --git a/include/mbgl/shaders/mtl/clipping_mask.hpp b/include/mbgl/shaders/mtl/clipping_mask.hpp index 197f5c4f4b8..1ba32e2821d 100644 --- a/include/mbgl/shaders/mtl/clipping_mask.hpp +++ b/include/mbgl/shaders/mtl/clipping_mask.hpp @@ -1,8 +1,6 @@ #pragma once #include -#include -#include #include namespace mbgl { diff --git a/include/mbgl/shaders/mtl/shader_group.hpp b/include/mbgl/shaders/mtl/shader_group.hpp index a3b8e990423..f27abb256ab 100644 --- a/include/mbgl/shaders/mtl/shader_group.hpp +++ b/include/mbgl/shaders/mtl/shader_group.hpp @@ -16,19 +16,15 @@ namespace mbgl { namespace mtl { class ShaderGroupBase : public gfx::ShaderGroup { -public: - using StringIDSet = mbgl::unordered_set; - protected: ShaderGroupBase(const ProgramParameters& parameters_) : programParameters(parameters_) {} using DefinesMap = mbgl::unordered_map; - void addAdditionalDefines(const StringIDSet& propertiesAsUniforms, DefinesMap& additionalDefines) { - additionalDefines.reserve(propertiesAsUniforms.size()); - for (const auto nameID : propertiesAsUniforms) { + void addAdditionalDefines(const StringIDSetsPair& propertiesAsUniforms, DefinesMap& additionalDefines) { + additionalDefines.reserve(propertiesAsUniforms.first.size()); + for (const auto name : propertiesAsUniforms.first) { // We expect the names to be prefixed by "a_", but we need just the base here. - const auto name = stringIndexer().get(nameID); const auto* base = (name[0] == 'a' && name[1] == '_') ? &name[2] : name.data(); additionalDefines.insert(std::make_pair(std::string(uniformPrefix) + base, std::string())); } @@ -48,7 +44,7 @@ class ShaderGroup final : public ShaderGroupBase { ~ShaderGroup() noexcept override = default; gfx::ShaderPtr getOrCreateShader(gfx::Context& gfxContext, - const mbgl::unordered_set& propertiesAsUniforms, + const StringIDSetsPair& propertiesAsUniforms, std::string_view /*firstAttribName*/) override { using ShaderSource = shaders::ShaderSource; constexpr auto& name = ShaderSource::name; @@ -78,7 +74,7 @@ class ShaderGroup final : public ShaderGroupBase { using ShaderClass = shaders::ShaderSource; for (const auto& attrib : ShaderClass::attributes) { - if (!propertiesAsUniforms.count(attrib.nameID)) { + if (!propertiesAsUniforms.second.count(attrib.id)) { shader->initAttribute(attrib); } } diff --git a/include/mbgl/shaders/mtl/shader_program.hpp b/include/mbgl/shaders/mtl/shader_program.hpp index e11d477796a..e40253f1c73 100644 --- a/include/mbgl/shaders/mtl/shader_program.hpp +++ b/include/mbgl/shaders/mtl/shader_program.hpp @@ -14,11 +14,10 @@ namespace mbgl { namespace shaders { struct AttributeInfo { - AttributeInfo(std::size_t index, gfx::AttributeDataType dataType, std::string_view name); + AttributeInfo(std::size_t index, gfx::AttributeDataType dataType, std::size_t id); std::size_t index; gfx::AttributeDataType dataType; - std::string_view name; - StringIdentity nameID; + std::size_t id; }; struct UniformBlockInfo { UniformBlockInfo(std::size_t index, bool vertex, bool fragment, std::size_t size, std::size_t id); diff --git a/include/mbgl/shaders/shader_defines.hpp b/include/mbgl/shaders/shader_defines.hpp index 36e73c87737..e033c0ea3e5 100644 --- a/include/mbgl/shaders/shader_defines.hpp +++ b/include/mbgl/shaders/shader_defines.hpp @@ -20,10 +20,17 @@ namespace mbgl { namespace shaders { +// UBO defines +enum { + idClippingMaskUBO, + clippingMaskUBOCount +}; + static constexpr auto maxUBOCountPerShader = std::max({static_cast(backgroundUBOCount), static_cast(circleUBOCount), + static_cast(clippingMaskUBOCount), static_cast(collisionUBOCount), - static_cast(customDrawableUBOCount), + static_cast(customSymbolUBOCount), static_cast(debugUBOCount), static_cast(fillUBOCount), static_cast(fillOutlineUBOCount), @@ -42,6 +49,7 @@ static constexpr auto maxUBOCountPerShader = std::max({static_cast(backg static_cast(rasterUBOCount), static_cast(symbolUBOCount)}); +// Texture defines enum { idBackgroundImageTexture, backgroundTextureCount @@ -51,13 +59,17 @@ enum { circleTextureCount }; +enum { + clippingMaskTextureCount +}; + enum { collisionTextureCount }; enum { - idCustomSymbolIconTexture, - customSymbolIconTextureCount + idCustomSymbolImageTexture, + customSymbolTextureCount }; enum { @@ -66,13 +78,13 @@ enum { }; enum { - idFillExtrusionImageTexture, - fillExtrusionTextureCount + idFillImageTexture, + fillTextureCount }; enum { - idFillImageTexture, - fillTextureCount + idFillExtrusionImageTexture, + fillExtrusionTextureCount }; enum { @@ -105,8 +117,9 @@ enum { static constexpr auto maxTextureCountPerShader = std::max({static_cast(backgroundTextureCount), static_cast(circleTextureCount), + static_cast(clippingMaskTextureCount), static_cast(collisionTextureCount), - static_cast(customSymbolIconTextureCount), + static_cast(customSymbolTextureCount), static_cast(debugTextureCount), static_cast(fillTextureCount), static_cast(fillExtrusionTextureCount), @@ -116,5 +129,150 @@ static constexpr auto maxTextureCountPerShader = std::max({static_cast(b static_cast(rasterTextureCount), static_cast(symbolTextureCount)}); +// Vertex attribute defines +enum { + idBackgroundPosVertexAttribute, + backgroundVertexAttributeCount +}; + +enum { + idCirclePosVertexAttribute, + + // Data driven + idCircleColorVertexAttribute, + idCircleRadiusVertexAttribute, + idCircleBlurVertexAttribute, + idCircleOpacityVertexAttribute, + idCircleStrokeColorVertexAttribute, + idCircleStrokeWidthVertexAttribute, + idCircleStrokeOpacityVertexAttribute, + + circleVertexAttributeCount +}; + +enum { + idClippingMaskPosVertexAttribute, + clippingMaskVertexAttributeCount +}; + +enum { + idCollisionPosVertexAttribute, + idCollisionAnchorPosVertexAttribute, + idCollisionExtrudeVertexAttribute, + idCollisionPlacedVertexAttribute, + idCollisionShiftVertexAttribute, + collisionVertexAttributeCount +}; + +enum { + idCustomSymbolPosVertexAttribute, + idCustomSymbolTexVertexAttribute, + customSymbolVertexAttributeCount +}; + +enum { + idDebugPosVertexAttribute, + debugVertexAttributeCount +}; + +enum { + idFillPosVertexAttribute, + + // Data driven + idFillColorVertexAttribute, + idFillOpacityVertexAttribute, + idFillOutlineColorVertexAttribute, + idFillPatternFromVertexAttribute, + idFillPatternToVertexAttribute, + + fillVertexAttributeCount +}; + +enum { + idFillExtrusionPosVertexAttribute, + idFillExtrusionNormalEdVertexAttribute, + + // Data driven + idFillExtrusionBaseVertexAttribute, + idFillExtrusionColorVertexAttribute, + idFillExtrusionHeightVertexAttribute, + idFillExtrusionPatternFromVertexAttribute, + idFillExtrusionPatternToVertexAttribute, + + fillExtrusionVertexAttributeCount +}; + +enum { + idHeatmapPosVertexAttribute, + + // Data driven + idHeatmapWeightVertexAttribute, + idHeatmapRadiusVertexAttribute, + + heatmapVertexAttributeCount +}; + +enum { + idHillshadePosVertexAttribute, + idHillshadeTexturePosVertexAttribute, + hillshadeVertexAttributeCount +}; + +enum { + idLinePosNormalVertexAttribute, + idLineDataVertexAttribute, + + // Data driven + idLineColorVertexAttribute, + idLineBlurVertexAttribute, + idLineOpacityVertexAttribute, + idLineGapWidthVertexAttribute, + idLineOffsetVertexAttribute, + idLineWidthVertexAttribute, + idLineFloorWidthVertexAttribute, + idLinePatternFromVertexAttribute, + idLinePatternToVertexAttribute, + + lineVertexAttributeCount +}; + +enum { + idRasterPosVertexAttribute, + idRasterTexturePosVertexAttribute, + rasterVertexAttributeCount +}; + +enum { + idSymbolPosOffsetVertexAttribute, + idSymbolDataVertexAttribute, + idSymbolPixelOffsetVertexAttribute, + idSymbolProjectedPosVertexAttribute, + idSymbolFadeOpacityVertexAttribute, + + // Data driven + idSymbolOpacityVertexAttribute, + idSymbolColorVertexAttribute, + idSymbolHaloColorVertexAttribute, + idSymbolHaloWidthVertexAttribute, + idSymbolHaloBlurVertexAttribute, + + symbolVertexAttributeCount +}; + +static constexpr auto maxVertexAttributeCountPerShader = std::max( + {static_cast(backgroundVertexAttributeCount), + static_cast(circleVertexAttributeCount), + static_cast(clippingMaskVertexAttributeCount), + static_cast(collisionVertexAttributeCount), + static_cast(customSymbolVertexAttributeCount), + static_cast(debugVertexAttributeCount), + static_cast(fillVertexAttributeCount), + static_cast(fillExtrusionVertexAttributeCount), + static_cast(heatmapVertexAttributeCount), + static_cast(hillshadeVertexAttributeCount), + static_cast(lineVertexAttributeCount), + static_cast(rasterVertexAttributeCount), + static_cast(symbolVertexAttributeCount)}); + } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/shader_program_base.hpp b/include/mbgl/shaders/shader_program_base.hpp index ff7ff22ddb4..9bd778dfdc6 100644 --- a/include/mbgl/shaders/shader_program_base.hpp +++ b/include/mbgl/shaders/shader_program_base.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -19,7 +18,7 @@ class ShaderProgramBase : public gfx::Shader { ~ShaderProgramBase() noexcept override = default; template - bool set(gfx::VertexAttributeArray& attrs, const StringIdentity id, std::size_t i, T value) { + bool set(gfx::VertexAttributeArray& attrs, const size_t id, std::size_t i, T value) { const auto& item = attrs.get(id); if (item && i < item->getCount()) { item->set(i, value); diff --git a/src/mbgl/gfx/drawable_builder.cpp b/src/mbgl/gfx/drawable_builder.cpp index 15fcc0bcf9b..72bff502367 100644 --- a/src/mbgl/gfx/drawable_builder.cpp +++ b/src/mbgl/gfx/drawable_builder.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include namespace mbgl { @@ -13,7 +12,7 @@ namespace gfx { DrawableBuilder::DrawableBuilder(std::string name_) : name(std::move(name_)), - vertexAttrNameId(stringIndexer().get("a_pos")), + vertexAttrId(0), renderPass(mbgl::RenderPass::Opaque), impl(std::make_unique()) {} diff --git a/src/mbgl/gfx/drawable_builder_impl.cpp b/src/mbgl/gfx/drawable_builder_impl.cpp index b1498a80641..a8e88a66cbc 100644 --- a/src/mbgl/gfx/drawable_builder_impl.cpp +++ b/src/mbgl/gfx/drawable_builder_impl.cpp @@ -1,16 +1,18 @@ #include #include -#include -#include #include +#include #include +#include #include namespace mbgl { namespace gfx { +using namespace shaders; + DrawableBuilder::Impl::LineLayoutVertex DrawableBuilder::Impl::layoutVertex( Point p, Point e, bool round, bool up, int8_t dir, int32_t linesofar /*= 0*/) { /* @@ -61,18 +63,14 @@ void DrawableBuilder::Impl::addPolyline(gfx::DrawableBuilder& builder, void DrawableBuilder::Impl::setupForPolylines(gfx::Context& context, gfx::DrawableBuilder& builder) { // setup vertex attributes - static const StringIdentity idVertexAttribName = stringIndexer().get("a_pos_normal"); - static const StringIdentity idDataAttribName = stringIndexer().get("a_data"); - - builder.setVertexAttrNameId(idVertexAttribName); - + builder.setVertexAttrId(idLinePosNormalVertexAttribute); builder.setRawVertices({}, polylineVertices.elements(), gfx::AttributeDataType::Short2); auto attrs = context.createVertexAttributeArray(); using VertexVector = gfx::VertexVector; std::shared_ptr verts = std::make_shared(std::move(polylineVertices)); - if (const auto& attr = attrs->add(idVertexAttribName)) { + if (const auto& attr = attrs->set(idLinePosNormalVertexAttribute)) { attr->setSharedRawData(verts, offsetof(LineLayoutVertex, a1), /*vertexOffset=*/0, @@ -80,7 +78,7 @@ void DrawableBuilder::Impl::setupForPolylines(gfx::Context& context, gfx::Drawab gfx::AttributeDataType::Short2); } - if (const auto& attr = attrs->add(idDataAttribName)) { + if (const auto& attr = attrs->set(idLineDataVertexAttribute)) { attr->setSharedRawData(verts, offsetof(LineLayoutVertex, a2), /*vertexOffset=*/0, diff --git a/src/mbgl/gfx/symbol_drawable_data.hpp b/src/mbgl/gfx/symbol_drawable_data.hpp index 2231064328f..6688b39ecaa 100644 --- a/src/mbgl/gfx/symbol_drawable_data.hpp +++ b/src/mbgl/gfx/symbol_drawable_data.hpp @@ -13,9 +13,6 @@ enum class SymbolType : uint8_t; namespace gfx { struct SymbolDrawableData : public DrawableData { - constexpr static std::size_t LinearThreshold = 10; - using PropertyMapType = util::TinyUnorderedMap; - SymbolDrawableData(const bool isHalo_, const bool bucketVariablePlacement_, const style::SymbolType symbolType_, diff --git a/src/mbgl/gfx/vertex_attribute.cpp b/src/mbgl/gfx/vertex_attribute.cpp index 111e3e0b424..c69b4135f14 100644 --- a/src/mbgl/gfx/vertex_attribute.cpp +++ b/src/mbgl/gfx/vertex_attribute.cpp @@ -93,65 +93,37 @@ VertexAttributeArray& VertexAttributeArray::operator=(VertexAttributeArray&& oth return *this; } -const std::unique_ptr& VertexAttributeArray::get(const StringIdentity id) const { - const auto result = attrs.find(id); - return (result != attrs.end()) ? result->second : nullref; +const std::unique_ptr& VertexAttributeArray::get(const size_t id) const { + return (id < attrs.size()) ? attrs[id] : nullref; } -const std::unique_ptr& VertexAttributeArray::add(const StringIdentity id, +const std::unique_ptr& VertexAttributeArray::set(const size_t id, int index, AttributeDataType dataType, std::size_t count) { - const auto result = attrs.insert(std::make_pair(id, std::unique_ptr())); - if (result.second) { - result.first->second = create(index, dataType, count); - return result.first->second; - } else { + assert(id < attrs.size()); + if (id >= attrs.size()) { return nullref; } -} - -const std::unique_ptr& VertexAttributeArray::getOrAdd(const StringIdentity id, - int index, - AttributeDataType dataType, - std::size_t count) { - const auto result = attrs.insert(std::make_pair(id, std::unique_ptr())); - auto& attr = result.first->second; - if (result.second) { - // inserted - attr = create(index, dataType, count); - } else { - // already present - assert(dataType == AttributeDataType::Invalid || attr->getDataType() == dataType); - assert((index < 0 || attr->getIndex() == index) && (count == 0 || attr->getCount() == count)); - } - return attr; + attrs[id] = create(index, dataType, count); + return attrs[id]; } std::size_t VertexAttributeArray::getTotalSize() const { - return std::accumulate(attrs.begin(), attrs.end(), std::size_t(0), [](const auto acc, const auto& kv) { - return acc + kv.second->getStride(); + return std::accumulate(attrs.begin(), attrs.end(), std::size_t(0), [](const auto acc, const auto& attr) { + return acc + (attr ? attr->getStride() : 0); }); } std::size_t VertexAttributeArray::getMaxCount() const { - return std::accumulate(attrs.begin(), attrs.end(), std::size_t(0), [](const auto acc, const auto& kv) { - return std::max(acc, kv.second->getCount()); + return std::accumulate(attrs.begin(), attrs.end(), std::size_t(0), [](const auto acc, const auto& attr) { + return std::max(acc, (attr ? attr->getCount() : 0)); }); } void VertexAttributeArray::clear() { - attrs.clear(); -} - -const UniqueVertexAttribute& VertexAttributeArray::add(const StringIdentity id, - std::unique_ptr&& attr) { - const auto result = attrs.insert(std::make_pair(id, std::unique_ptr())); - if (result.second) { - result.first->second = std::move(attr); - return result.first->second; - } else { - return nullref; + for (auto& attr : attrs) { + attr = nullptr; } } diff --git a/src/mbgl/gl/drawable_gl.cpp b/src/mbgl/gl/drawable_gl.cpp index 5b3ce61d43c..f90d353262b 100644 --- a/src/mbgl/gl/drawable_gl.cpp +++ b/src/mbgl/gl/drawable_gl.cpp @@ -8,7 +8,6 @@ #include #include #include -#include namespace mbgl { namespace gl { @@ -98,8 +97,8 @@ gfx::UniformBufferArray& DrawableGL::mutableUniformBuffers() { return impl->uniformBuffers; } -void DrawableGL::setVertexAttrNameId(const StringIdentity id) { - impl->idVertexAttrName = id; +void DrawableGL::setVertexAttrId(const size_t id) { + impl->vertexAttrId = id; } void DrawableGL::bindUniformBuffers() const { @@ -169,7 +168,7 @@ void DrawableGL::upload(gfx::UploadPass& uploadPass) { const auto& defaults = shader->getVertexAttributes(); const auto& overrides = *vertexAttributes; - const auto& indexAttribute = defaults.get(impl->idVertexAttrName); + const auto& indexAttribute = defaults.get(impl->vertexAttrId); const auto vertexAttributeIndex = static_cast(indexAttribute ? indexAttribute->getIndex() : -1); std::vector> vertexBuffers; diff --git a/src/mbgl/gl/drawable_gl_builder.cpp b/src/mbgl/gl/drawable_gl_builder.cpp index 1b43506c982..78547195c4a 100644 --- a/src/mbgl/gl/drawable_gl_builder.cpp +++ b/src/mbgl/gl/drawable_gl_builder.cpp @@ -23,7 +23,7 @@ std::unique_ptr DrawableGLBuilder::createSegment(gfx void DrawableGLBuilder::init() { auto& drawableGL = static_cast(*currentDrawable); - drawableGL.setVertexAttrNameId(vertexAttrNameId); + drawableGL.setVertexAttrId(vertexAttrId); if (impl->rawVerticesCount) { auto raw = impl->rawVertices; diff --git a/src/mbgl/gl/drawable_gl_impl.hpp b/src/mbgl/gl/drawable_gl_impl.hpp index f92e82be2a8..d72aea8a0c4 100644 --- a/src/mbgl/gl/drawable_gl_impl.hpp +++ b/src/mbgl/gl/drawable_gl_impl.hpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -49,7 +48,7 @@ class DrawableGL::Impl final { gfx::CullFaceMode cullFaceMode; GLfloat pointSize = 0.0f; - StringIdentity idVertexAttrName = stringIndexer().get("a_pos"); + size_t vertexAttrId = 0; }; struct DrawableGL::DrawSegmentGL final : public gfx::Drawable::DrawSegment { diff --git a/src/mbgl/gl/upload_pass.cpp b/src/mbgl/gl/upload_pass.cpp index 0a7cb2a3654..e0b71b6b91c 100644 --- a/src/mbgl/gl/upload_pass.cpp +++ b/src/mbgl/gl/upload_pass.cpp @@ -192,7 +192,7 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( const gfx::BufferUsageType usage, /*out*/ std::vector>& outBuffers) { AttributeBindingArray bindings; - bindings.resize(defaults.size()); + bindings.resize(defaults.allocatedSize()); constexpr std::size_t align = 16; constexpr std::uint8_t padding = 0; @@ -216,7 +216,7 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( } // For each attribute in the program, with the corresponding default and optional override... - const auto resolveAttr = [&](const StringIdentity id, auto& defaultAttr, auto& overrideAttr) -> void { + const auto resolveAttr = [&](const size_t id, auto& defaultAttr, auto& overrideAttr) -> void { auto& effectiveAttr = overrideAttr ? *overrideAttr : defaultAttr; const auto& defaultGL = static_cast(defaultAttr); const auto stride = defaultAttr.getStride(); @@ -262,7 +262,7 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( // something else, the binding is invalid // TODO: throw? Log::Warning(Event::General, - "Got " + util::toString(rawData.size()) + " bytes for attribute '" + stringIndexer().get(id) + + "Got " + util::toString(rawData.size()) + " bytes for attribute '" + util::toString(id) + "' (" + util::toString(defaultGL.getIndex()) + "), expected " + util::toString(stride) + " or " + util::toString(stride * vertexCount)); return; diff --git a/src/mbgl/gl/vertex_attribute_gl.cpp b/src/mbgl/gl/vertex_attribute_gl.cpp index 2f859fea3cc..386831a2e9d 100644 --- a/src/mbgl/gl/vertex_attribute_gl.cpp +++ b/src/mbgl/gl/vertex_attribute_gl.cpp @@ -183,15 +183,15 @@ const std::vector& VertexAttributeGL::getRaw(gfx::VertexAttribute& } bool VertexAttributeArrayGL::isDirty() const { - return std::any_of(attrs.begin(), attrs.end(), [](const auto& kv) { - if (kv.second) { + return std::any_of(attrs.begin(), attrs.end(), [](const auto& attr) { + if (attr) { // If we have shared data, the dirty flag from that overrides ours - const auto& glAttrib = static_cast(*kv.second); + const auto& glAttrib = static_cast(*attr); if (const auto& shared = glAttrib.getSharedRawData()) { return shared->getDirty(); } } - return kv.second && kv.second->isDirty(); + return attr && attr->isDirty(); }); } diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index e3380b4723b..6e5a85a3a0e 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -317,14 +317,13 @@ void Drawable::setVertices(std::vector&& data, std::size_t count, gfx:: if (!vertexAttributes) { vertexAttributes = std::make_shared(); } - if (auto& attrib = vertexAttributes->getOrAdd(impl->idVertexAttrName, /*index=*/-1, type)) { + if (auto& attrib = vertexAttributes->set(impl->vertexAttrId, /*index=*/-1, type)) { attrib->setRawData(std::move(data)); attrib->setStride(VertexAttribute::getStrideOf(type)); } else { using namespace std::string_literals; - Log::Warning( - Event::General, - "Vertex attribute type mismatch: "s + name + " / " + stringIndexer().get(impl->idVertexAttrName)); + Log::Warning(Event::General, + "Vertex attribute type mismatch: "s + name + " / " + util::toString(impl->vertexAttrId)); assert(false); } } @@ -338,8 +337,8 @@ gfx::UniformBufferArray& Drawable::mutableUniformBuffers() { return impl->uniformBuffers; } -void Drawable::setVertexAttrNameId(const StringIdentity value) { - impl->idVertexAttrName = value; +void Drawable::setVertexAttrId(const size_t id) { + impl->vertexAttrId = id; } void Drawable::bindAttributes(RenderPass& renderPass) const noexcept { @@ -540,7 +539,7 @@ void Drawable::upload(gfx::UploadPass& uploadPass_) { vertexBuffers); impl->attributeBuffers = std::move(vertexBuffers); - vertexAttributes->visitAttributes([](const auto&, gfx::VertexAttribute& attrib) { attrib.setDirty(false); }); + vertexAttributes->visitAttributes([](gfx::VertexAttribute& attrib) { attrib.setDirty(false); }); if (impl->attributeBindings != attributeBindings_) { impl->attributeBindings = std::move(attributeBindings_); diff --git a/src/mbgl/mtl/drawable_builder.cpp b/src/mbgl/mtl/drawable_builder.cpp index 632c6063dad..f10186361b0 100644 --- a/src/mbgl/mtl/drawable_builder.cpp +++ b/src/mbgl/mtl/drawable_builder.cpp @@ -22,7 +22,7 @@ std::unique_ptr DrawableBuilder::createSegment(gfx:: void DrawableBuilder::init() { auto& drawable = static_cast(*currentDrawable); - drawable.setVertexAttrNameId(vertexAttrNameId); + drawable.setVertexAttrId(vertexAttrId); if (impl->rawVerticesCount) { auto raw = impl->rawVertices; diff --git a/src/mbgl/mtl/drawable_impl.hpp b/src/mbgl/mtl/drawable_impl.hpp index 88768b6f015..b9d826e921e 100644 --- a/src/mbgl/mtl/drawable_impl.hpp +++ b/src/mbgl/mtl/drawable_impl.hpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -53,7 +52,7 @@ class Drawable::Impl final { gfx::StencilMode stencilMode; gfx::CullFaceMode cullFaceMode; // GLfloat pointSize = 0.0f; - StringIdentity idVertexAttrName = stringIndexer().get("a_pos"); + std::size_t vertexAttrId = 0; VertexBufferResource* noBindingBuffer = nullptr; diff --git a/src/mbgl/mtl/upload_pass.cpp b/src/mbgl/mtl/upload_pass.cpp index b8e6ab7cab2..1c6d4b82983 100644 --- a/src/mbgl/mtl/upload_pass.cpp +++ b/src/mbgl/mtl/upload_pass.cpp @@ -158,7 +158,7 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( const gfx::BufferUsageType usage, /*out*/ std::vector>& outBuffers) { gfx::AttributeBindingArray bindings; - bindings.resize(defaults.size()); + bindings.resize(defaults.allocatedSize()); constexpr std::size_t align = 16; constexpr std::uint8_t padding = 0; @@ -169,7 +169,7 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( uint32_t vertexStride = 0; // For each attribute in the program, with the corresponding default and optional override... - const auto resolveAttr = [&](const StringIdentity id, auto& default_, auto& override_) -> void { + const auto resolveAttr = [&](const size_t id, auto& default_, auto& override_) -> void { auto& effectiveAttr = override_ ? *override_ : default_; const auto& defaultAttr = static_cast(default_); const auto stride = defaultAttr.getStride(); diff --git a/src/mbgl/mtl/vertex_attribute.cpp b/src/mbgl/mtl/vertex_attribute.cpp index b7647fccd6c..d30feea4b87 100644 --- a/src/mbgl/mtl/vertex_attribute.cpp +++ b/src/mbgl/mtl/vertex_attribute.cpp @@ -34,15 +34,15 @@ const gfx::UniqueVertexBufferResource& VertexAttribute::getBuffer(gfx::VertexAtt } bool VertexAttributeArray::isDirty() const { - return std::any_of(attrs.begin(), attrs.end(), [](const auto& kv) { - if (kv.second) { + return std::any_of(attrs.begin(), attrs.end(), [](const auto& attr) { + if (attr) { // If we have shared data, the dirty flag from that overrides ours - const auto& attrib = static_cast(*kv.second); + const auto& attrib = static_cast(*attr); if (const auto& shared = attrib.getSharedRawData()) { return shared->getDirty(); } } - return kv.second && kv.second->isDirty(); + return attr && attr->isDirty(); }); } diff --git a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp index 0af7eefd837..712b5fa82ae 100644 --- a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #if MLN_RENDER_BACKEND_METAL #include diff --git a/src/mbgl/renderer/layers/collision_layer_tweaker.hpp b/src/mbgl/renderer/layers/collision_layer_tweaker.hpp index 34ade8bf978..f3cc3305e41 100644 --- a/src/mbgl/renderer/layers/collision_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/collision_layer_tweaker.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp index d6fffe21981..6ada998544d 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp index 272a7cf40f1..a75279e0a00 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include diff --git a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp index 983aaab7313..2c3c925da0d 100644 --- a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #if MLN_RENDER_BACKEND_METAL #include diff --git a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp index 436e871be66..f12002f56cc 100644 --- a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp @@ -9,7 +9,6 @@ #include #include #include -#include namespace mbgl { diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp index ebffbb0627b..bc0b1557ca1 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp @@ -8,7 +8,6 @@ #include #include #include -#include namespace mbgl { diff --git a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp index 6ba4d46649e..fea60775dbc 100644 --- a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp @@ -9,7 +9,6 @@ #include #include #include -#include namespace mbgl { diff --git a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp index de19f3914c1..bd0e3c7f790 100644 --- a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp @@ -10,7 +10,6 @@ #include #include #include -#include namespace mbgl { diff --git a/src/mbgl/renderer/layers/render_background_layer.cpp b/src/mbgl/renderer/layers/render_background_layer.cpp index 661a9e186c8..9a83beabccd 100644 --- a/src/mbgl/renderer/layers/render_background_layer.cpp +++ b/src/mbgl/renderer/layers/render_background_layer.cpp @@ -29,6 +29,7 @@ namespace mbgl { using namespace style; +using namespace shaders; namespace { @@ -308,6 +309,7 @@ void RenderBackgroundLayer::update(gfx::ShaderRegistry& shaders, } auto verticesCopy = rawVertices; + builder->setVertexAttrId(idBackgroundPosVertexAttribute); builder->setRawVertices(std::move(verticesCopy), vertexCount, gfx::AttributeDataType::Short2); builder->setSegments(gfx::Triangles(), indexes.vector(), segs.data(), segs.size()); builder->flush(context); diff --git a/src/mbgl/renderer/layers/render_circle_layer.cpp b/src/mbgl/renderer/layers/render_circle_layer.cpp index fe10a003d9a..469e2d5a52c 100644 --- a/src/mbgl/renderer/layers/render_circle_layer.cpp +++ b/src/mbgl/renderer/layers/render_circle_layer.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #endif namespace mbgl { @@ -264,7 +263,6 @@ bool RenderCircleLayer::queryIntersectsFeature(const GeometryCoordinates& queryG namespace { constexpr auto CircleShaderGroupName = "CircleShader"; -const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); } // namespace @@ -314,7 +312,7 @@ void RenderCircleLayer::update(gfx::ShaderRegistry& shaders, [&](gfx::Drawable& drawable) { return drawable.getTileID() && !hasRenderTile(*drawable.getTileID()); }); const auto& evaluated = static_cast(*evaluatedProperties).evaluated; - mbgl::unordered_set propertiesAsUniforms; + StringIDSetsPair propertiesAsUniforms; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); @@ -367,7 +365,9 @@ void RenderCircleLayer::update(gfx::ShaderRegistry& shaders, const auto interpBuffer = context.createUniformBuffer(&interpolateUBO, sizeof(interpolateUBO)); - propertiesAsUniforms.clear(); + propertiesAsUniforms.first.clear(); + propertiesAsUniforms.second.clear(); + auto circleVertexAttrs = context.createVertexAttributeArray(); circleVertexAttrs->readDataDrivenPaintProperties( - paintPropertyBinders, evaluated, propertiesAsUniforms); + paintPropertyBinders, evaluated, propertiesAsUniforms, idCircleColorVertexAttribute); - if (!circleShaderGroup) { - continue; - } const auto circleShader = circleShaderGroup->getOrCreateShader(context, propertiesAsUniforms); if (!circleShader) { continue; } - if (const auto& attr = circleVertexAttrs->add(idVertexAttribName)) { + if (const auto& attr = circleVertexAttrs->set(idCirclePosVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(CircleLayoutVertex, a1), 0, diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp index 84db4916c2e..39325bfd2f3 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -38,12 +38,6 @@ using namespace shaders; namespace { -#if MLN_DRAWABLE_RENDERER -const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); -const StringIdentity idNormAttribName = stringIndexer().get("a_normal_ed"); - -#endif // MLN_DRAWABLE_RENDERER - inline const FillExtrusionLayer::Impl& impl_cast(const Immutable& impl) { assert(impl->getTypeInfo() == FillExtrusionLayer::Impl::staticTypeInfo()); return static_cast(*impl); @@ -338,7 +332,7 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, tileLayerGroup->setStencilTiles(renderTiles); - mbgl::unordered_set propertiesAsUniforms; + StringIDSetsPair propertiesAsUniforms; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); @@ -408,13 +402,15 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, continue; } - propertiesAsUniforms.clear(); + propertiesAsUniforms.first.clear(); + propertiesAsUniforms.second.clear(); auto vertexAttrs = context.createVertexAttributeArray(); vertexAttrs->readDataDrivenPaintProperties(binders, evaluated, propertiesAsUniforms); + FillExtrusionPattern>( + binders, evaluated, propertiesAsUniforms, idFillExtrusionBaseVertexAttribute); const auto shader = std::static_pointer_cast( shaderGroup->getOrCreateShader(context, propertiesAsUniforms)); @@ -473,14 +469,14 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, } } - if (const auto& attr = vertexAttrs->add(idPosAttribName)) { + if (const auto& attr = vertexAttrs->set(idFillExtrusionPosVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(FillExtrusionLayoutVertex, a1), /*vertexOffset=*/0, sizeof(FillExtrusionLayoutVertex), gfx::AttributeDataType::Short2); } - if (const auto& attr = vertexAttrs->add(idNormAttribName)) { + if (const auto& attr = vertexAttrs->set(idFillExtrusionNormalEdVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(FillExtrusionLayoutVertex, a2), /*vertexOffset=*/0, diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index 361d183ab7a..d921ddb6f61 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #endif @@ -49,8 +48,6 @@ constexpr auto FillOutlineTriangulatedShaderName = "LineBasicShader"; constexpr auto FillOutlineShaderName = "FillOutlineShader"; constexpr auto FillPatternShaderName = "FillPatternShader"; constexpr auto FillOutlinePatternShaderName = "FillOutlinePatternShader"; - -const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); #endif // MLN_DRAWABLE_RENDERER inline const FillLayer::Impl& impl_cast(const Immutable& impl) { @@ -479,7 +476,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, fillTileLayerGroup->setStencilTiles(renderTiles); - mbgl::unordered_set propertiesAsUniforms; + StringIDSetsPair propertiesAsUniforms; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); @@ -608,15 +605,16 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, return atlasTweaker; }; - propertiesAsUniforms.clear(); + propertiesAsUniforms.first.clear(); + propertiesAsUniforms.second.clear(); // `Fill*Program` all use `style::FillPaintProperties` auto vertexAttrs = context.createVertexAttributeArray(); vertexAttrs->readDataDrivenPaintProperties( - binders, evaluated, propertiesAsUniforms); + binders, evaluated, propertiesAsUniforms, idFillColorVertexAttribute); const auto vertexCount = bucket.vertices.elements(); - if (const auto& attr = vertexAttrs->add(idPosAttribName)) { + if (const auto& attr = vertexAttrs->set(idFillPosVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(FillLayoutVertex, a1), /*vertexOffset=*/0, @@ -682,11 +680,9 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, #if MLN_TRIANGULATE_FILL_OUTLINES const auto outlineTriangulatedShader = doOutline && !dataDrivenOutline ? [&]() -> auto { - static const mbgl::unordered_set outlinePropertiesAsUniforms{ - stringIndexer().get("a_color"), - stringIndexer().get("a_opacity"), - stringIndexer().get("a_width"), - }; + static const StringIDSetsPair outlinePropertiesAsUniforms{ + {"a_color", "a_opacity", "a_width"}, + {idLineColorVertexAttribute, idLineOpacityVertexAttribute, idLineWidthVertexAttribute}}; return std::static_pointer_cast( outlineTriangulatedShaderGroup->getOrCreateShader(context, outlinePropertiesAsUniforms)); }() @@ -694,21 +690,18 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, auto createOutline = [&](auto& builder, Color color, float opacity) { if (doOutline && builder && bucket.sharedLineIndexes->elements()) { - static const StringIdentity idVertexAttribName = stringIndexer().get("a_pos_normal"); - static const StringIdentity idDataAttribName = stringIndexer().get("a_data"); - builder->setVertexAttrNameId(idVertexAttribName); builder->setShader(outlineTriangulatedShader); builder->setRawVertices({}, bucket.lineVertices.elements(), gfx::AttributeDataType::Short2); auto attrs = context.createVertexAttributeArray(); - if (const auto& attr = attrs->add(idVertexAttribName)) { + if (const auto& attr = attrs->set(idLinePosNormalVertexAttribute)) { attr->setSharedRawData(bucket.sharedLineVertices, offsetof(LineLayoutVertex, a1), /*vertexOffset=*/0, sizeof(LineLayoutVertex), gfx::AttributeDataType::Short2); } - if (const auto& attr = attrs->add(idDataAttribName)) { + if (const auto& attr = attrs->set(idLineDataVertexAttribute)) { attr->setSharedRawData(bucket.sharedLineVertices, offsetof(LineLayoutVertex, a2), /*vertexOffset=*/0, @@ -770,7 +763,6 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, const size_t interpolateUBOId, const auto& interpolateUBO, FillVariant type) { - builder.setVertexAttrNameId(idPosAttribName); builder.flush(context); for (auto& drawable : builder.clearDrawables()) { @@ -897,7 +889,7 @@ void RenderFillLayer::update(gfx::ShaderRegistry& shaders, } const auto finish = [&](gfx::DrawableBuilder& builder, - const StringIdentity interpolateNameId, + const size_t interpolateNameId, const auto& interpolateUBO, const size_t tileUBOId, const auto& tileUBO, diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp index 9b7ecabc0f2..429e93436ed 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.cpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #endif namespace mbgl { @@ -278,8 +277,6 @@ namespace { constexpr auto HeatmapShaderGroupName = "HeatmapShader"; constexpr auto HeatmapTextureShaderGroupName = "HeatmapTextureShader"; -const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); - } // namespace using namespace shaders; @@ -345,9 +342,9 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, [&](gfx::Drawable& drawable) { return drawable.getTileID() && !hasRenderTile(*drawable.getTileID()); }); const auto& evaluated = static_cast(*evaluatedProperties).evaluated; - std::optional> propertiesAsUniforms; + std::optional propertiesAsUniforms; #if !defined(NDEBUG) - std::optional> previousPropertiesAsUniforms; + std::optional previousPropertiesAsUniforms; #endif gfx::ShaderPtr heatmapShader; @@ -411,13 +408,14 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, } #if !defined(NDEBUG) else { - propertiesAsUniforms->clear(); + propertiesAsUniforms->first.clear(); + propertiesAsUniforms->second.clear(); } #endif auto heatmapVertexAttrs = context.createVertexAttributeArray(); heatmapVertexAttrs->readDataDrivenPaintProperties( - paintPropertyBinders, evaluated, *propertiesAsUniforms); + paintPropertyBinders, evaluated, *propertiesAsUniforms, idHeatmapWeightVertexAttribute); #if !defined(NDEBUG) // We assume the properties are the same across tiles. @@ -435,7 +433,7 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, } } - if (const auto& attr = heatmapVertexAttrs->add(idVertexAttribName)) { + if (const auto& attr = heatmapVertexAttrs->set(idHeatmapPosVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(HeatmapLayoutVertex, a1), /*vertexOffset=*/0, @@ -505,7 +503,7 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders, const auto textureVertexCount = sharedTextureVertices->elements(); auto textureVertexAttrs = context.createVertexAttributeArray(); - if (const auto& attr = textureVertexAttrs->add(idVertexAttribName)) { + if (const auto& attr = textureVertexAttrs->set(idHeatmapPosVertexAttribute)) { attr->setSharedRawData(sharedTextureVertices, offsetof(HeatmapLayoutVertex, a1), /*vertexOffset=*/0, diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index f98e3f4acae..b5635cb5aba 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -295,9 +295,6 @@ void RenderHillshadeLayer::removeRenderTargets(UniqueChangeRequestVec& changes) static const std::string HillshadePrepareShaderGroupName = "HillshadePrepareShader"; static const std::string HillshadeShaderGroupName = "HillshadeShader"; -static const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); -static const StringIdentity idTexturePosAttribName = stringIndexer().get("a_texture_pos"); - void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, [[maybe_unused]] const TransformState& state, @@ -358,14 +355,14 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, if (!hillshadePrepareVertexAttrs) { hillshadePrepareVertexAttrs = context.createVertexAttributeArray(); - if (const auto& attr = hillshadePrepareVertexAttrs->add(idPosAttribName)) { + if (const auto& attr = hillshadePrepareVertexAttrs->set(idHillshadePosVertexAttribute)) { attr->setSharedRawData(staticDataSharedVertices, offsetof(HillshadeLayoutVertex, a1), 0, sizeof(HillshadeLayoutVertex), gfx::AttributeDataType::Short2); } - if (const auto& attr = hillshadePrepareVertexAttrs->add(idTexturePosAttribName)) { + if (const auto& attr = hillshadePrepareVertexAttrs->set(idHillshadeTexturePosVertexAttribute)) { attr->setSharedRawData(staticDataSharedVertices, offsetof(HillshadeLayoutVertex, a2), 0, @@ -465,14 +462,14 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, if (!hillshadeVertexAttrs) { hillshadeVertexAttrs = context.createVertexAttributeArray(); - if (const auto& attr = hillshadeVertexAttrs->add(idPosAttribName)) { + if (const auto& attr = hillshadeVertexAttrs->set(idHillshadePosVertexAttribute)) { attr->setSharedRawData(vertices, offsetof(HillshadeLayoutVertex, a1), 0, sizeof(HillshadeLayoutVertex), gfx::AttributeDataType::Short2); } - if (const auto& attr = hillshadeVertexAttrs->getOrAdd(idTexturePosAttribName)) { + if (const auto& attr = hillshadeVertexAttrs->set(idHillshadeTexturePosVertexAttribute)) { attr->setSharedRawData(vertices, offsetof(HillshadeLayoutVertex, a2), 0, diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index 918a4782f00..d31051d7458 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #endif namespace mbgl { @@ -47,8 +46,7 @@ inline const LineLayer::Impl& impl_cast(const Immutable& imp #if MLN_DRAWABLE_RENDERER -const StringIdentity idVertexAttribName = stringIndexer().get("a_pos_normal"); -const StringIdentity idDataAttribName = stringIndexer().get("a_data"); +const auto posNormalAttribName = "a_pos_normal"; #endif // MLN_DRAWABLE_RENDERER @@ -371,19 +369,6 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, layerGroup->addLayerTweaker(layerTweaker); } - if (!lineShaderGroup) { - lineShaderGroup = shaders.getShaderGroup("LineShader"); - } - if (!lineGradientShaderGroup) { - lineGradientShaderGroup = shaders.getShaderGroup("LineGradientShader"); - } - if (!linePatternShaderGroup) { - linePatternShaderGroup = shaders.getShaderGroup("LinePatternShader"); - } - if (!lineSDFShaderGroup) { - lineSDFShaderGroup = shaders.getShaderGroup("LineSDFShader"); - } - const RenderPass renderPass = static_cast(evaluatedProperties->renderPasses & ~mbgl::underlying_type(RenderPass::Opaque)); @@ -407,7 +392,6 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, : gfx::ColorMode::unblended()); builder->setCullFaceMode(gfx::CullFaceMode::disabled()); builder->setEnableStencil(true); - builder->setVertexAttrNameId(idVertexAttribName); return builder; }; @@ -417,7 +401,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, const auto vertexCount = bucket.vertices.elements(); builder.setRawVertices({}, vertexCount, gfx::AttributeDataType::Short4); - if (const auto& attr = vertexAttrs->add(idVertexAttribName)) { + if (const auto& attr = vertexAttrs->set(idLinePosNormalVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(LineLayoutVertex, a1), /*vertexOffset=*/0, @@ -425,7 +409,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, gfx::AttributeDataType::Short2); } - if (const auto& attr = vertexAttrs->add(idDataAttribName)) { + if (const auto& attr = vertexAttrs->set(idLineDataVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(LineLayoutVertex, a2), /*vertexOffset=*/0, @@ -442,7 +426,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, tileLayerGroup->setStencilTiles(renderTiles); - mbgl::unordered_set propertiesAsUniforms; + StringIDSetsPair propertiesAsUniforms; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); @@ -547,6 +531,7 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, const auto& linePatternValue = evaluated.get().constantOr(Faded{"", ""}); const std::optional patternPosA = tile.getPattern(linePatternValue.from.id()); const std::optional patternPosB = tile.getPattern(linePatternValue.to.id()); + paintPropertyBinders.setPatternParameters(patternPosA, patternPosB, crossfade); auto getLinePatternTilePropertiesUBO = [&]() -> const LinePatternTilePropertiesUBO& { if (!linePatternTilePropertiesUBO) { @@ -600,24 +585,30 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, continue; } + propertiesAsUniforms.first.clear(); + propertiesAsUniforms.second.clear(); + + auto vertexAttrs = context.createVertexAttributeArray(); + vertexAttrs->readDataDrivenPaintProperties( + paintPropertyBinders, evaluated, propertiesAsUniforms, idLineColorVertexAttribute); + if (!evaluated.get().from.empty()) { + // dash array line (SDF) if (!lineSDFShaderGroup) { - continue; + lineSDFShaderGroup = shaders.getShaderGroup("LineSDFShader"); + if (!lineSDFShaderGroup) { + continue; + } } - // dash array line (SDF) - propertiesAsUniforms.clear(); - auto vertexAttrs = context.createVertexAttributeArray(); - vertexAttrs->readDataDrivenPaintProperties( - paintPropertyBinders, evaluated, propertiesAsUniforms); - - auto shader = lineSDFShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + auto shader = lineSDFShaderGroup->getOrCreateShader(context, propertiesAsUniforms, posNormalAttribName); if (!shader) { continue; } @@ -644,24 +635,15 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, ++stats.drawablesAdded; } } else if (!unevaluated.get().isUndefined()) { + // pattern line if (!linePatternShaderGroup) { - continue; + linePatternShaderGroup = shaders.getShaderGroup("LinePatternShader"); + if (!linePatternShaderGroup) { + continue; + } } - // pattern line - paintPropertyBinders.setPatternParameters(patternPosA, patternPosB, crossfade); - - propertiesAsUniforms.clear(); - auto vertexAttrs = context.createVertexAttributeArray(); - vertexAttrs->readDataDrivenPaintProperties( - paintPropertyBinders, evaluated, propertiesAsUniforms); - - auto shader = linePatternShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + auto shader = linePatternShaderGroup->getOrCreateShader(context, propertiesAsUniforms, posNormalAttribName); if (!shader) { continue; } @@ -704,17 +686,16 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, } } } else if (!unevaluated.get().getValue().isUndefined()) { + // gradient line if (!lineGradientShaderGroup) { - continue; + lineGradientShaderGroup = shaders.getShaderGroup("LineGradientShader"); + if (!lineGradientShaderGroup) { + continue; + } } - // gradient line - propertiesAsUniforms.clear(); - auto vertexAttrs = context.createVertexAttributeArray(); - vertexAttrs->readDataDrivenPaintProperties( - paintPropertyBinders, evaluated, propertiesAsUniforms); - - auto shader = lineGradientShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + auto shader = lineGradientShaderGroup->getOrCreateShader( + context, propertiesAsUniforms, posNormalAttribName); if (!shader) { continue; } @@ -753,18 +734,15 @@ void RenderLineLayer::update(gfx::ShaderRegistry& shaders, } } } else { + // simple line if (!lineShaderGroup) { - continue; + lineShaderGroup = shaders.getShaderGroup("LineShader"); + if (!lineShaderGroup) { + continue; + } } - // simple line - propertiesAsUniforms.clear(); - auto vertexAttrs = context.createVertexAttributeArray(); - vertexAttrs - ->readDataDrivenPaintProperties( - paintPropertyBinders, evaluated, propertiesAsUniforms); - - auto shader = lineShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + auto shader = lineShaderGroup->getOrCreateShader(context, propertiesAsUniforms, posNormalAttribName); if (!shader) { continue; } diff --git a/src/mbgl/renderer/layers/render_raster_layer.cpp b/src/mbgl/renderer/layers/render_raster_layer.cpp index e495030cbf8..23058ef9d0d 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.cpp +++ b/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -246,11 +246,6 @@ void RenderRasterLayer::layerIndexChanged(int32_t newLayerIndex, UniqueChangeReq changeLayerIndex(imageLayerGroup, newLayerIndex, changes); } -static const StringIdentity idPosAttribName = stringIndexer().get("a_pos"); -static const StringIdentity idTexturePosAttribName = stringIndexer().get("a_texture_pos"); -static const StringIdentity idTexImage0Name = stringIndexer().get("u_image0"); -static const StringIdentity idTexImage1Name = stringIndexer().get("u_image1"); - void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, const TransformState& /*state*/, @@ -305,7 +300,6 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, builder->setDepthType(gfx::DepthMaskType::ReadOnly); builder->setColorMode(gfx::ColorMode::alphaBlended()); builder->setCullFaceMode(gfx::CullFaceMode::disabled()); - builder->setVertexAttrNameId(idPosAttribName); return builder; }; @@ -358,7 +352,7 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, if (!vertexAttrs) { vertexAttrs = context.createVertexAttributeArray(); - if (auto& attr = vertexAttrs->add(idPosAttribName)) { + if (auto& attr = vertexAttrs->set(idRasterPosVertexAttribute)) { attr->setSharedRawData(vertices, offsetof(RasterLayoutVertex, a1), /*vertexOffset=*/0, @@ -366,7 +360,7 @@ void RenderRasterLayer::update(gfx::ShaderRegistry& shaders, gfx::AttributeDataType::Short2); } - if (auto& attr = vertexAttrs->add(idTexturePosAttribName)) { + if (auto& attr = vertexAttrs->set(idRasterTexturePosVertexAttribute)) { attr->setSharedRawData(vertices, offsetof(RasterLayoutVertex, a2), /*vertexOffset=*/0, diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index ac3535b3de8..fbdd688409f 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -760,34 +760,29 @@ SymbolDrawableTilePropsUBO buildTileUBO(const SymbolBucket& bucket, }; } -const auto idDataAttibName = stringIndexer().get("a_data"); const auto posOffsetAttribName = "a_pos_offset"; -const auto idPosOffsetAttribName = stringIndexer().get(posOffsetAttribName); -const auto idPixOffsetAttribName = stringIndexer().get("a_pixeloffset"); -const auto idProjPosAttribName = stringIndexer().get("a_projected_pos"); -const auto idFadeOpacityAttribName = stringIndexer().get("a_fade_opacity"); void updateTileAttributes(const SymbolBucket::Buffer& buffer, const bool isText, const SymbolBucket::PaintProperties& paintProps, const SymbolPaintProperties::PossiblyEvaluated& evaluated, gfx::VertexAttributeArray& attribs, - mbgl::unordered_set* propertiesAsUniforms) { - if (const auto& attr = attribs.getOrAdd(idPosOffsetAttribName)) { + StringIDSetsPair* propertiesAsUniforms) { + if (const auto& attr = attribs.set(idSymbolPosOffsetVertexAttribute)) { attr->setSharedRawData(buffer.sharedVertices, offsetof(SymbolLayoutVertex, a1), /*vertexOffset=*/0, sizeof(SymbolLayoutVertex), gfx::AttributeDataType::Short4); } - if (const auto& attr = attribs.getOrAdd(idDataAttibName)) { + if (const auto& attr = attribs.set(idSymbolDataVertexAttribute)) { attr->setSharedRawData(buffer.sharedVertices, offsetof(SymbolLayoutVertex, a2), /*vertexOffset=*/0, sizeof(SymbolLayoutVertex), gfx::AttributeDataType::UShort4); } - if (const auto& attr = attribs.getOrAdd(idPixOffsetAttribName)) { + if (const auto& attr = attribs.set(idSymbolPixelOffsetVertexAttribute)) { attr->setSharedRawData(buffer.sharedVertices, offsetof(SymbolLayoutVertex, a3), /*vertexOffset=*/0, @@ -795,7 +790,7 @@ void updateTileAttributes(const SymbolBucket::Buffer& buffer, gfx::AttributeDataType::Short4); } - if (const auto& attr = attribs.getOrAdd(idProjPosAttribName)) { + if (const auto& attr = attribs.set(idSymbolProjectedPosVertexAttribute)) { using Vertex = gfx::Vertex; attr->setSharedRawData(buffer.sharedDynamicVertices, offsetof(Vertex, a1), @@ -803,7 +798,7 @@ void updateTileAttributes(const SymbolBucket::Buffer& buffer, sizeof(Vertex), gfx::AttributeDataType::Float3); } - if (const auto& attr = attribs.getOrAdd(idFadeOpacityAttribName)) { + if (const auto& attr = attribs.set(idSymbolFadeOpacityVertexAttribute)) { using Vertex = gfx::Vertex; attr->setSharedRawData(buffer.sharedOpacityVertices, offsetof(Vertex, a1), @@ -814,10 +809,10 @@ void updateTileAttributes(const SymbolBucket::Buffer& buffer, if (isText) { attribs.readDataDrivenPaintProperties( - paintProps.textBinders, evaluated, propertiesAsUniforms); + paintProps.textBinders, evaluated, propertiesAsUniforms, idSymbolOpacityVertexAttribute); } else { attribs.readDataDrivenPaintProperties( - paintProps.iconBinders, evaluated, propertiesAsUniforms); + paintProps.iconBinders, evaluated, propertiesAsUniforms, idSymbolOpacityVertexAttribute); } } @@ -874,32 +869,26 @@ void updateTileDrawable(gfx::Drawable& drawable, } } -const StringIdentity idCollisionPosAttribName = stringIndexer().get("a_pos"); -const StringIdentity idCollisionAnchorPosAttribName = stringIndexer().get("a_anchor_pos"); -const StringIdentity idCollisionExtrudeAttribName = stringIndexer().get("a_extrude"); -const StringIdentity idCollisionPlacedAttribName = stringIndexer().get("a_placed"); -const StringIdentity idCollisionShiftAttribName = stringIndexer().get("a_shift"); - gfx::VertexAttributeArrayPtr getCollisionVertexAttributes(gfx::Context& context, const SymbolBucket::CollisionBuffer& buffer) { auto vertexAttrs = context.createVertexAttributeArray(); using LayoutVertex = gfx::Vertex; - if (const auto& attr = vertexAttrs->add(idCollisionPosAttribName)) { + if (const auto& attr = vertexAttrs->set(idCollisionPosVertexAttribute)) { attr->setSharedRawData(buffer.sharedVertices, offsetof(LayoutVertex, a1), /*vertexOffset=*/0, sizeof(LayoutVertex), gfx::AttributeDataType::Short2); } - if (const auto& attr = vertexAttrs->add(idCollisionAnchorPosAttribName)) { + if (const auto& attr = vertexAttrs->set(idCollisionAnchorPosVertexAttribute)) { attr->setSharedRawData(buffer.sharedVertices, offsetof(LayoutVertex, a2), /*vertexOffset=*/0, sizeof(LayoutVertex), gfx::AttributeDataType::Short2); } - if (const auto& attr = vertexAttrs->add(idCollisionExtrudeAttribName)) { + if (const auto& attr = vertexAttrs->set(idCollisionExtrudeVertexAttribute)) { attr->setSharedRawData(buffer.sharedVertices, offsetof(LayoutVertex, a3), /*vertexOffset=*/0, @@ -909,14 +898,14 @@ gfx::VertexAttributeArrayPtr getCollisionVertexAttributes(gfx::Context& context, using DynamicVertex = gfx::Vertex; - if (const auto& attr = vertexAttrs->add(idCollisionPlacedAttribName)) { + if (const auto& attr = vertexAttrs->set(idCollisionPlacedVertexAttribute)) { attr->setSharedRawData(buffer.sharedDynamicVertices, offsetof(DynamicVertex, a1), /*vertexOffset=*/0, sizeof(DynamicVertex), gfx::AttributeDataType::UShort2); } - if (const auto& attr = vertexAttrs->add(idCollisionShiftAttribName)) { + if (const auto& attr = vertexAttrs->set(idCollisionShiftVertexAttribute)) { attr->setSharedRawData(buffer.sharedDynamicVertices, offsetof(DynamicVertex, a2), /*vertexOffset=*/0, @@ -1061,9 +1050,8 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, collisionBuilder->setRenderPass(passes); collisionBuilder->setCullFaceMode(gfx::CullFaceMode::disabled()); collisionBuilder->setColorMode(gfx::ColorMode::alphaBlended()); - collisionBuilder->setVertexAttrNameId(idCollisionPosAttribName); - mbgl::unordered_set propertiesAsUniforms; + StringIDSetsPair propertiesAsUniforms; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); @@ -1106,8 +1094,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, if (hasCollisionBox) { const auto& collisionBox = isText ? bucket.textCollisionBox : bucket.iconCollisionBox; if (const auto shader = std::static_pointer_cast( - collisionBoxGroup->getOrCreateShader( - context, {}, stringIndexer().get(idCollisionPosAttribName)))) { + collisionBoxGroup->getOrCreateShader(context, {}))) { collisionBuilder->setDrawableName(layerCollisionPrefix + suffix + "box"); collisionBuilder->setShader(shader); addVertices(collisionBox->vertices().vector()); @@ -1123,8 +1110,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, if (hasCollisionCircle) { const auto& collisionCircle = isText ? bucket.textCollisionCircle : bucket.iconCollisionCircle; if (const auto shader = std::static_pointer_cast( - collisionCircleGroup->getOrCreateShader( - context, {}, stringIndexer().get(idCollisionPosAttribName)))) { + collisionCircleGroup->getOrCreateShader(context, {}))) { collisionBuilder->setDrawableName(layerCollisionPrefix + suffix + "circle"); collisionBuilder->setShader(shader); addVertices(collisionCircle->vertices().vector()); @@ -1158,7 +1144,8 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, return false; } - propertiesAsUniforms.clear(); + propertiesAsUniforms.first.clear(); + propertiesAsUniforms.second.clear(); const auto& evaluated = getEvaluated(renderData.layerProperties); updateTileDrawable( @@ -1257,7 +1244,9 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, const auto vertexCount = buffer.vertices().elements(); - propertiesAsUniforms.clear(); + propertiesAsUniforms.first.clear(); + propertiesAsUniforms.second.clear(); + auto attribs = context.createVertexAttributeArray(); updateTileAttributes(buffer, isText, bucketPaintProperties, evaluated, *attribs, &propertiesAsUniforms); @@ -1321,12 +1310,8 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, ((mbgl::underlying_type(passes) & mbgl::underlying_type(RenderPass::Translucent)) != 0) ? gfx::ColorMode::alphaBlended() : gfx::ColorMode::unblended()); - builder->setVertexAttrNameId(idPosOffsetAttribName); } - if (!shaderGroup) { - return; - } const auto shader = std::static_pointer_cast( shaderGroup->getOrCreateShader(context, propertiesAsUniforms, posOffsetAttribName)); if (!shader) { diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp index 341d627efff..ce06420ee9a 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include diff --git a/src/mbgl/renderer/sources/render_tile_source.cpp b/src/mbgl/renderer/sources/render_tile_source.cpp index a88e6741c20..d4eceeccd6c 100644 --- a/src/mbgl/renderer/sources/render_tile_source.cpp +++ b/src/mbgl/renderer/sources/render_tile_source.cpp @@ -13,12 +13,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -65,7 +65,6 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr if (!debugShader) { return; } - static const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); std::unique_ptr debugBuilder = [&]() -> std::unique_ptr { auto builder = context.createDrawableBuilder("debug-builder"); builder->setShader(debugShader); @@ -73,7 +72,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr builder->setEnableDepth(false); builder->setColorMode(gfx::ColorMode::unblended()); builder->setCullFaceMode(gfx::CullFaceMode::disabled()); - builder->setVertexAttrNameId(idVertexAttribName); + builder->setVertexAttrId(idDebugPosVertexAttribute); return builder; }(); @@ -83,14 +82,14 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr gfx::ShaderPtr polylineShader; const auto createPolylineShader = [&]() -> gfx::ShaderPtr { gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("LineShader"); - const mbgl::unordered_set propertiesAsUniforms{ - stringIndexer().get("a_color"), - stringIndexer().get("a_blur"), - stringIndexer().get("a_opacity"), - stringIndexer().get("a_gapwidth"), - stringIndexer().get("a_offset"), - stringIndexer().get("a_width"), - }; + const StringIDSetsPair propertiesAsUniforms{ + {"a_color", "a_blur", "a_opacity", "a_gapwidth", "a_offset", "a_width"}, + {idLineColorVertexAttribute, + idLineBlurVertexAttribute, + idLineOpacityVertexAttribute, + idLineGapWidthVertexAttribute, + idLineOffsetVertexAttribute, + idLineWidthVertexAttribute}}; return shaderGroup->getOrCreateShader(context, propertiesAsUniforms); }; @@ -102,7 +101,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr builder->setEnableDepth(false); builder->setColorMode(gfx::ColorMode::alphaBlended()); builder->setCullFaceMode(gfx::CullFaceMode::disabled()); - builder->setVertexAttrNameId(idVertexAttribName); + builder->setVertexAttrId(idLinePosNormalVertexAttribute); return builder; }; diff --git a/src/mbgl/shaders/gl/shader_info.cpp b/src/mbgl/shaders/gl/shader_info.cpp index 704e91585d1..e3c07bd41f7 100644 --- a/src/mbgl/shaders/gl/shader_info.cpp +++ b/src/mbgl/shaders/gl/shader_info.cpp @@ -5,6 +5,10 @@ namespace mbgl { namespace shaders { +AttributeInfo::AttributeInfo(std::string_view name_, std::size_t id_) + : name(name_), + id(id_) {} + UniformBlockInfo::UniformBlockInfo(std::string_view name_, std::size_t id_) : name(name_), id(id_), @@ -15,6 +19,9 @@ TextureInfo::TextureInfo(std::string_view name_, std::size_t id_) id(id_) {} // Background +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idBackgroundPosVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"BackgroundDrawableUBO", idBackgroundDrawableUBO}, UniformBlockInfo{"BackgroundLayerUBO", idBackgroundLayerUBO}, @@ -22,6 +29,10 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Background Pattern +const std::vector ShaderInfo::attributes = + { + AttributeInfo{"a_pos", idBackgroundPosVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"BackgroundDrawableUBO", idBackgroundDrawableUBO}, @@ -32,6 +43,16 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos", idCirclePosVertexAttribute}, + AttributeInfo{"a_color", idCircleColorVertexAttribute}, + AttributeInfo{"a_radius", idCircleRadiusVertexAttribute}, + AttributeInfo{"a_blur", idCircleBlurVertexAttribute}, + AttributeInfo{"a_opacity", idCircleOpacityVertexAttribute}, + AttributeInfo{"a_stroke_color", idCircleStrokeColorVertexAttribute}, + AttributeInfo{"a_stroke_width", idCircleStrokeWidthVertexAttribute}, + AttributeInfo{"a_stroke_opacity", idCircleStrokeOpacityVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CircleDrawableUBO", idCircleDrawableUBO}, UniformBlockInfo{"CirclePaintParamsUBO", idCirclePaintParamsUBO}, @@ -41,6 +62,13 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Collision Box +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idCollisionPosVertexAttribute}, + AttributeInfo{"a_anchor_pos", idCollisionAnchorPosVertexAttribute}, + AttributeInfo{"a_extrude", idCollisionExtrudeVertexAttribute}, + AttributeInfo{"a_placed", idCollisionPlacedVertexAttribute}, + AttributeInfo{"a_shift", idCollisionShiftVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CollisionBoxUBO", idCollisionUBO}, @@ -48,22 +76,36 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Collision Circle -const std::vector - ShaderInfo::uniformBlocks = { - UniformBlockInfo{"CustomSymbolIconDrawableUBO", idCustomSymbolIconDrawableUBO}, - UniformBlockInfo{"CustomSymbolIconParametersUBO", idCustomSymbolIconParametersUBO}, -}; -const std::vector ShaderInfo::textures = { - TextureInfo{"u_texture", idSymbolImageTexture}, +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idCollisionPosVertexAttribute}, + AttributeInfo{"a_anchor_pos", idCollisionAnchorPosVertexAttribute}, + AttributeInfo{"a_extrude", idCollisionExtrudeVertexAttribute}, + AttributeInfo{"a_placed", idCollisionPlacedVertexAttribute}, }; - const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CollisionCircleUBO", idCollisionUBO}, }; const std::vector ShaderInfo::textures = {}; +// Custom Symbol Icon +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idCustomSymbolPosVertexAttribute}, + AttributeInfo{"a_tex", idCustomSymbolTexVertexAttribute}, +}; +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"CustomSymbolIconDrawableUBO", idCustomSymbolDrawableUBO}, + UniformBlockInfo{"CustomSymbolIconParametersUBO", idCustomSymbolParametersUBO}, +}; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_texture", idCustomSymbolImageTexture}, +}; + // Debug +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idDebugPosVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"DebugUBO", idDebugUBO}, }; @@ -72,6 +114,11 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos", idFillPosVertexAttribute}, + AttributeInfo{"a_color", idFillColorVertexAttribute}, + AttributeInfo{"a_opacity", idFillOpacityVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillDrawableUBO", idFillDrawableUBO}, UniformBlockInfo{"FillEvaluatedPropsUBO", idFillEvaluatedPropsUBO}, @@ -80,6 +127,11 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Fill Outline +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idFillPosVertexAttribute}, + AttributeInfo{"a_outline_color", idFillOutlineColorVertexAttribute}, + AttributeInfo{"a_opacity", idFillOpacityVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillOutlineDrawableUBO", idFillOutlineDrawableUBO}, @@ -89,6 +141,12 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Fill Pattern +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idFillPosVertexAttribute}, + AttributeInfo{"a_opacity", idFillOpacityVertexAttribute}, + AttributeInfo{"a_pattern_from", idFillPatternFromVertexAttribute}, + AttributeInfo{"a_pattern_to", idFillPatternToVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillPatternDrawableUBO", idFillPatternDrawableUBO}, @@ -101,6 +159,13 @@ const std::vector ShaderInfo ShaderInfo::attributes = + { + AttributeInfo{"a_pos", idFillPosVertexAttribute}, + AttributeInfo{"a_opacity", idFillOpacityVertexAttribute}, + AttributeInfo{"a_pattern_from", idFillPatternFromVertexAttribute}, + AttributeInfo{"a_pattern_to", idFillPatternToVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillOutlinePatternDrawableUBO", idFillOutlinePatternDrawableUBO}, @@ -113,6 +178,13 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos", idFillExtrusionPosVertexAttribute}, + AttributeInfo{"a_normal_ed", idFillExtrusionNormalEdVertexAttribute}, + AttributeInfo{"a_base", idFillExtrusionBaseVertexAttribute}, + AttributeInfo{"a_height", idFillExtrusionHeightVertexAttribute}, + AttributeInfo{"a_color", idFillExtrusionColorVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillExtrusionDrawableUBO", idFillExtrusionDrawableUBO}, @@ -123,6 +195,15 @@ const std::vector const std::vector ShaderInfo::textures = {}; // Fill Extrusion Pattern +const std::vector + ShaderInfo::attributes = { + AttributeInfo{"a_pos", idFillExtrusionPosVertexAttribute}, + AttributeInfo{"a_normal_ed", idFillExtrusionNormalEdVertexAttribute}, + AttributeInfo{"a_base", idFillExtrusionBaseVertexAttribute}, + AttributeInfo{"a_height", idFillExtrusionHeightVertexAttribute}, + AttributeInfo{"a_pattern_from", idFillExtrusionPatternFromVertexAttribute}, + AttributeInfo{"a_pattern_to", idFillExtrusionPatternToVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"FillExtrusionDrawableUBO", idFillExtrusionDrawableUBO}, @@ -135,6 +216,11 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos", idHeatmapPosVertexAttribute}, + AttributeInfo{"a_weight", idHeatmapWeightVertexAttribute}, + AttributeInfo{"a_radius", idHeatmapRadiusVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HeatmapDrawableUBO", idHeatmapDrawableUBO}, UniformBlockInfo{"HeatmapEvaluatedPropsUBO", idHeatmapEvaluatedPropsUBO}, @@ -143,6 +229,9 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Heatmap Texture +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idHeatmapPosVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HeatmapTextureDrawableUBO", idHeatmapTextureDrawableUBO}, @@ -153,6 +242,10 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos", idHillshadePosVertexAttribute}, + AttributeInfo{"a_texture_pos", idHillshadeTexturePosVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HillshadePrepareDrawableUBO", idHillshadePrepareDrawableUBO}, @@ -162,6 +255,10 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos", idHillshadePosVertexAttribute}, + AttributeInfo{"a_texture_pos", idHillshadeTexturePosVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"HillshadeDrawableUBO", idHillshadeDrawableUBO}, UniformBlockInfo{"HillshadeEvaluatedPropsUBO", idHillshadeEvaluatedPropsUBO}, @@ -170,7 +267,35 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos_normal", idLinePosNormalVertexAttribute}, + AttributeInfo{"a_data", idLineDataVertexAttribute}, + AttributeInfo{"a_color", idLineColorVertexAttribute}, + AttributeInfo{"a_blur", idLineBlurVertexAttribute}, + AttributeInfo{"a_opacity", idLineOpacityVertexAttribute}, + AttributeInfo{"a_gapwidth", idLineGapWidthVertexAttribute}, + AttributeInfo{"a_offset", idLineOffsetVertexAttribute}, + AttributeInfo{"a_width", idLineWidthVertexAttribute}, +}; +const std::vector ShaderInfo::uniformBlocks = { + UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, + UniformBlockInfo{"LineUBO", idLineUBO}, + UniformBlockInfo{"LinePropertiesUBO", idLinePropertiesUBO}, + UniformBlockInfo{"LineInterpolationUBO", idLineInterpolationUBO}, +}; +const std::vector ShaderInfo::textures = {}; + // Line Gradient +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos_normal", idLinePosNormalVertexAttribute}, + AttributeInfo{"a_data", idLineDataVertexAttribute}, + AttributeInfo{"a_blur", idLineBlurVertexAttribute}, + AttributeInfo{"a_opacity", idLineOpacityVertexAttribute}, + AttributeInfo{"a_gapwidth", idLineGapWidthVertexAttribute}, + AttributeInfo{"a_offset", idLineOffsetVertexAttribute}, + AttributeInfo{"a_width", idLineWidthVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, @@ -183,6 +308,17 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos_normal", idLinePosNormalVertexAttribute}, + AttributeInfo{"a_data", idLineDataVertexAttribute}, + AttributeInfo{"a_blur", idLineBlurVertexAttribute}, + AttributeInfo{"a_opacity", idLineOpacityVertexAttribute}, + AttributeInfo{"a_offset", idLineOffsetVertexAttribute}, + AttributeInfo{"a_gapwidth", idLineGapWidthVertexAttribute}, + AttributeInfo{"a_width", idLineWidthVertexAttribute}, + AttributeInfo{"a_pattern_from", idLinePatternFromVertexAttribute}, + AttributeInfo{"a_pattern_to", idLinePatternToVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, @@ -196,6 +332,17 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos_normal", idLinePosNormalVertexAttribute}, + AttributeInfo{"a_data", idLineDataVertexAttribute}, + AttributeInfo{"a_color", idLineColorVertexAttribute}, + AttributeInfo{"a_blur", idLineBlurVertexAttribute}, + AttributeInfo{"a_opacity", idLineOpacityVertexAttribute}, + AttributeInfo{"a_gapwidth", idLineGapWidthVertexAttribute}, + AttributeInfo{"a_offset", idLineOffsetVertexAttribute}, + AttributeInfo{"a_width", idLineWidthVertexAttribute}, + AttributeInfo{"a_floorwidth", idLineFloorWidthVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, UniformBlockInfo{"LineSDFUBO", idLineSDFUBO}, @@ -206,16 +353,11 @@ const std::vector ShaderInfo ShaderInfo::uniformBlocks = { - UniformBlockInfo{"LineDynamicUBO", idLineDynamicUBO}, - UniformBlockInfo{"LineUBO", idLineUBO}, - UniformBlockInfo{"LinePropertiesUBO", idLinePropertiesUBO}, - UniformBlockInfo{"LineInterpolationUBO", idLineInterpolationUBO}, -}; -const std::vector ShaderInfo::textures = {}; - // Line Basic +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos_normal", idLinePosNormalVertexAttribute}, + AttributeInfo{"a_data", idLineDataVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"LineBasicUBO", idLineBasicUBO}, UniformBlockInfo{"LineBasicPropertiesUBO", idLineBasicPropertiesUBO}, @@ -223,6 +365,10 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Raster +const std::vector ShaderInfo::attributes = { + AttributeInfo{"a_pos", idRasterPosVertexAttribute}, + AttributeInfo{"a_texture_pos", idRasterTexturePosVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"RasterDrawableUBO", idRasterDrawableUBO}, }; @@ -232,6 +378,14 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos_offset", idSymbolPosOffsetVertexAttribute}, + AttributeInfo{"a_data", idSymbolDataVertexAttribute}, + AttributeInfo{"a_pixeloffset", idSymbolPixelOffsetVertexAttribute}, + AttributeInfo{"a_projected_pos", idSymbolProjectedPosVertexAttribute}, + AttributeInfo{"a_fade_opacity", idSymbolFadeOpacityVertexAttribute}, + AttributeInfo{"a_opacity", idSymbolOpacityVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, UniformBlockInfo{"SymbolDynamicUBO", idSymbolDynamicUBO}, @@ -244,6 +398,18 @@ const std::vector ShaderInfo ShaderInfo::attributes = { + AttributeInfo{"a_pos_offset", idSymbolPosOffsetVertexAttribute}, + AttributeInfo{"a_data", idSymbolDataVertexAttribute}, + AttributeInfo{"a_pixeloffset", idSymbolPixelOffsetVertexAttribute}, + AttributeInfo{"a_projected_pos", idSymbolProjectedPosVertexAttribute}, + AttributeInfo{"a_fade_opacity", idSymbolFadeOpacityVertexAttribute}, + AttributeInfo{"a_fill_color", idSymbolColorVertexAttribute}, + AttributeInfo{"a_halo_color", idSymbolHaloColorVertexAttribute}, + AttributeInfo{"a_opacity", idSymbolOpacityVertexAttribute}, + AttributeInfo{"a_halo_width", idSymbolHaloWidthVertexAttribute}, + AttributeInfo{"a_halo_blur", idSymbolHaloBlurVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, @@ -257,6 +423,18 @@ const std::vector ShaderInfo ShaderInfo::attributes = + { + AttributeInfo{"a_pos_offset", idSymbolPosOffsetVertexAttribute}, + AttributeInfo{"a_data", idSymbolDataVertexAttribute}, + AttributeInfo{"a_projected_pos", idSymbolProjectedPosVertexAttribute}, + AttributeInfo{"a_fade_opacity", idSymbolFadeOpacityVertexAttribute}, + AttributeInfo{"a_fill_color", idSymbolColorVertexAttribute}, + AttributeInfo{"a_halo_color", idSymbolHaloColorVertexAttribute}, + AttributeInfo{"a_opacity", idSymbolOpacityVertexAttribute}, + AttributeInfo{"a_halo_width", idSymbolHaloWidthVertexAttribute}, + AttributeInfo{"a_halo_blur", idSymbolHaloBlurVertexAttribute}, +}; const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"SymbolDrawableUBO", idSymbolDrawableUBO}, diff --git a/src/mbgl/shaders/gl/shader_program_gl.cpp b/src/mbgl/shaders/gl/shader_program_gl.cpp index 2183bf91bc0..90ef0a891ff 100644 --- a/src/mbgl/shaders/gl/shader_program_gl.cpp +++ b/src/mbgl/shaders/gl/shader_program_gl.cpp @@ -71,11 +71,10 @@ gfx::AttributeDataType mapType(platform::GLenum attrType) { using namespace platform; -void addAttr( - VertexAttributeArrayGL& attrs, const StringIdentity id, GLint index, GLsizei length, GLint count, GLenum glType) { +void addAttr(VertexAttributeArrayGL& attrs, const size_t id, GLint index, GLsizei length, GLint count, GLenum glType) { const auto elementType = mapType(glType); if (elementType != gfx::AttributeDataType::Invalid && length > 0) { - if (const auto& newAttr = attrs.add(id, index, elementType, count)) { + if (const auto& newAttr = attrs.set(id, index, elementType, count)) { const auto& glAttr = static_cast(newAttr.get()); glAttr->setGLType(glType); } @@ -115,6 +114,7 @@ std::shared_ptr ShaderProgramGL::create( const std::string_view firstAttribName, const std::vector& uniformBlocksInfo, const std::vector& texturesInfo, + const std::vector& attributesInfo, const std::string& vertexSource, const std::string& fragmentSource, const std::string& additionalDefines) noexcept(false) { @@ -171,7 +171,8 @@ std::shared_ptr ShaderProgramGL::create( continue; } const GLint location = MBGL_CHECK_ERROR(glGetAttribLocation(program, name.data())); - addAttr(attrs, stringIndexer().get(name.data()), location, length, size, glType); + assert(attributesInfo[location].name == std::string_view(name.data())); + addAttr(attrs, attributesInfo[location].id, location, length, size, glType); } return std::make_shared( diff --git a/src/mbgl/shaders/mtl/background.cpp b/src/mbgl/shaders/mtl/background.cpp index e6a9d6e28e8..3effa409195 100644 --- a/src/mbgl/shaders/mtl/background.cpp +++ b/src/mbgl/shaders/mtl/background.cpp @@ -5,7 +5,7 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{1, true, false, sizeof(BackgroundDrawableUBO), idBackgroundDrawableUBO}, diff --git a/src/mbgl/shaders/mtl/background_pattern.cpp b/src/mbgl/shaders/mtl/background_pattern.cpp index aa6a7c70424..6d2629ce145 100644 --- a/src/mbgl/shaders/mtl/background_pattern.cpp +++ b/src/mbgl/shaders/mtl/background_pattern.cpp @@ -6,7 +6,7 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/circle.cpp b/src/mbgl/shaders/mtl/circle.cpp index 426447ad5bd..f993b9ade89 100644 --- a/src/mbgl/shaders/mtl/circle.cpp +++ b/src/mbgl/shaders/mtl/circle.cpp @@ -4,14 +4,14 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float4, "a_color"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, "a_radius"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, "a_blur"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, "a_opacity"}, - AttributeInfo{5, gfx::AttributeDataType::Float4, "a_stroke_color"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, "a_stroke_width"}, - AttributeInfo{7, gfx::AttributeDataType::Float2, "a_stroke_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idCirclePosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float4, idCircleColorVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idCircleRadiusVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idCircleBlurVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idCircleOpacityVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float4, idCircleStrokeColorVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idCircleStrokeWidthVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idCircleStrokeOpacityVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{8, true, false, sizeof(CircleDrawableUBO), idCircleDrawableUBO}, diff --git a/src/mbgl/shaders/mtl/clipping_mask.cpp b/src/mbgl/shaders/mtl/clipping_mask.cpp index bd2ba6237a2..e3eacf8688e 100644 --- a/src/mbgl/shaders/mtl/clipping_mask.cpp +++ b/src/mbgl/shaders/mtl/clipping_mask.cpp @@ -6,10 +6,10 @@ namespace shaders { using ShaderType = ShaderSource; const std::array ShaderType::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Float3, idClippingMaskPosVertexAttribute}, }; const std::array ShaderType::uniforms = { - UniformBlockInfo{1, true, false, sizeof(ClipUBO), 0}, + UniformBlockInfo{1, true, false, sizeof(ClipUBO), idClippingMaskUBO}, }; const std::array ShaderType::textures = {}; diff --git a/src/mbgl/shaders/mtl/collision_box.cpp b/src/mbgl/shaders/mtl/collision_box.cpp index c712f76b8b5..ac1eb889944 100644 --- a/src/mbgl/shaders/mtl/collision_box.cpp +++ b/src/mbgl/shaders/mtl/collision_box.cpp @@ -4,11 +4,11 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, "a_anchor_pos"}, - AttributeInfo{2, gfx::AttributeDataType::Short2, "a_extrude"}, - AttributeInfo{3, gfx::AttributeDataType::UShort2, "a_placed"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, "a_shift"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idCollisionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idCollisionAnchorPosVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short2, idCollisionExtrudeVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::UShort2, idCollisionPlacedVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idCollisionShiftVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{5, true, true, sizeof(CollisionUBO), idCollisionUBO}, diff --git a/src/mbgl/shaders/mtl/collision_circle.cpp b/src/mbgl/shaders/mtl/collision_circle.cpp index 2979256b5c6..e62054bbf08 100644 --- a/src/mbgl/shaders/mtl/collision_circle.cpp +++ b/src/mbgl/shaders/mtl/collision_circle.cpp @@ -5,10 +5,10 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, "a_anchor_pos"}, - AttributeInfo{2, gfx::AttributeDataType::Short2, "a_extrude"}, - AttributeInfo{3, gfx::AttributeDataType::UShort2, "a_placed"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idCollisionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idCollisionAnchorPosVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short2, idCollisionExtrudeVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::UShort2, idCollisionPlacedVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/custom_symbol_icon.cpp b/src/mbgl/shaders/mtl/custom_symbol_icon.cpp index 4c1afaf791e..145d2fc2375 100644 --- a/src/mbgl/shaders/mtl/custom_symbol_icon.cpp +++ b/src/mbgl/shaders/mtl/custom_symbol_icon.cpp @@ -6,16 +6,16 @@ namespace shaders { const std::array ShaderSource::attributes = { // always attributes - AttributeInfo{0, gfx::AttributeDataType::Float2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float2, "a_tex"}, + AttributeInfo{0, gfx::AttributeDataType::Float2, idCustomSymbolPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float2, idCustomSymbolTexVertexAttribute}, }; const std::array ShaderSource::uniforms = { - UniformBlockInfo{2, true, false, sizeof(CustomSymbolIconDrawableUBO), idCustomSymbolIconDrawableUBO}, - UniformBlockInfo{3, true, false, sizeof(CustomSymbolIconParametersUBO), idCustomSymbolIconParametersUBO}, + UniformBlockInfo{2, true, false, sizeof(CustomSymbolIconDrawableUBO), idCustomSymbolDrawableUBO}, + UniformBlockInfo{3, true, false, sizeof(CustomSymbolIconParametersUBO), idCustomSymbolParametersUBO}, }; const std::array ShaderSource::textures = { - TextureInfo{0, idCustomSymbolIconTexture}, + TextureInfo{0, idCustomSymbolImageTexture}, }; } // namespace shaders diff --git a/src/mbgl/shaders/mtl/debug.cpp b/src/mbgl/shaders/mtl/debug.cpp index 017c0edb148..d2eac2a00cd 100644 --- a/src/mbgl/shaders/mtl/debug.cpp +++ b/src/mbgl/shaders/mtl/debug.cpp @@ -4,7 +4,7 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idDebugPosVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{1, true, true, sizeof(DebugUBO), idDebugUBO}, diff --git a/src/mbgl/shaders/mtl/fill.cpp b/src/mbgl/shaders/mtl/fill.cpp index 05ad0f24ae1..3bc99120b58 100644 --- a/src/mbgl/shaders/mtl/fill.cpp +++ b/src/mbgl/shaders/mtl/fill.cpp @@ -4,9 +4,9 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float4, "a_color"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float4, idFillColorVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{3, true, false, sizeof(FillDrawableUBO), idFillDrawableUBO}, @@ -16,9 +16,9 @@ const std::array ShaderSource ShaderSource::textures = {}; const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float4, "a_outline_color"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float4, idFillOutlineColorVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{3, true, false, sizeof(FillOutlineDrawableUBO), idFillOutlineDrawableUBO}, @@ -28,10 +28,10 @@ const std::array ShaderSource ShaderSource::textures = {}; const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_pattern_from"}, - AttributeInfo{2, gfx::AttributeDataType::UShort4, "a_pattern_to"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idFillPatternFromVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::UShort4, idFillPatternToVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{4, true, true, sizeof(FillPatternDrawableUBO), idFillPatternDrawableUBO}, @@ -45,10 +45,10 @@ const std::array ShaderSource ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_pattern_from"}, - AttributeInfo{2, gfx::AttributeDataType::UShort4, "a_pattern_to"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idFillPatternFromVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::UShort4, idFillPatternToVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/fill_extrusion.cpp b/src/mbgl/shaders/mtl/fill_extrusion.cpp index b3dc1472eed..e8223697ac5 100644 --- a/src/mbgl/shaders/mtl/fill_extrusion.cpp +++ b/src/mbgl/shaders/mtl/fill_extrusion.cpp @@ -4,11 +4,11 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short4, "a_normal_ed"}, - AttributeInfo{2, gfx::AttributeDataType::Float4, "a_color"}, - AttributeInfo{3, gfx::AttributeDataType::Float, "a_base"}, - AttributeInfo{4, gfx::AttributeDataType::Float, "a_height"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillExtrusionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short4, idFillExtrusionNormalEdVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float4, idFillExtrusionColorVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float, idFillExtrusionBaseVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float, idFillExtrusionHeightVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp index 3bd415ae756..3df13f684f6 100644 --- a/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp +++ b/src/mbgl/shaders/mtl/fill_extrusion_pattern.cpp @@ -6,12 +6,12 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short4, "a_normal_ed"}, - AttributeInfo{2, gfx::AttributeDataType::Float, "a_base"}, - AttributeInfo{3, gfx::AttributeDataType::Float, "a_height"}, - AttributeInfo{4, gfx::AttributeDataType::UShort4, "a_pattern_from"}, - AttributeInfo{5, gfx::AttributeDataType::UShort4, "a_pattern_to"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillExtrusionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short4, idFillExtrusionNormalEdVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float, idFillExtrusionBaseVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float, idFillExtrusionHeightVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::UShort4, idFillExtrusionPatternFromVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::UShort4, idFillExtrusionPatternToVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/heatmap.cpp b/src/mbgl/shaders/mtl/heatmap.cpp index 9f3d05853b8..0ac7738da41 100644 --- a/src/mbgl/shaders/mtl/heatmap.cpp +++ b/src/mbgl/shaders/mtl/heatmap.cpp @@ -4,9 +4,9 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Float2, "a_weight"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, "a_radius"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idHeatmapPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float2, idHeatmapWeightVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idHeatmapRadiusVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{3, true, false, sizeof(HeatmapDrawableUBO), idHeatmapDrawableUBO}, diff --git a/src/mbgl/shaders/mtl/heatmap_texture.cpp b/src/mbgl/shaders/mtl/heatmap_texture.cpp index 61f2067502b..944205d5ed3 100644 --- a/src/mbgl/shaders/mtl/heatmap_texture.cpp +++ b/src/mbgl/shaders/mtl/heatmap_texture.cpp @@ -5,7 +5,7 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idHeatmapPosVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/hillshade.cpp b/src/mbgl/shaders/mtl/hillshade.cpp index 3bc21b853c1..6353df65c78 100644 --- a/src/mbgl/shaders/mtl/hillshade.cpp +++ b/src/mbgl/shaders/mtl/hillshade.cpp @@ -4,8 +4,8 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, "a_texture_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idHillshadePosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idHillshadeTexturePosVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{2, true, true, sizeof(HillshadeDrawableUBO), idHillshadeDrawableUBO}, diff --git a/src/mbgl/shaders/mtl/hillshade_prepare.cpp b/src/mbgl/shaders/mtl/hillshade_prepare.cpp index 5aa84fa9345..a4efc7701bf 100644 --- a/src/mbgl/shaders/mtl/hillshade_prepare.cpp +++ b/src/mbgl/shaders/mtl/hillshade_prepare.cpp @@ -5,8 +5,8 @@ namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, "a_texture_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idHillshadePosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idHillshadeTexturePosVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/line.cpp b/src/mbgl/shaders/mtl/line.cpp index fed8f6dd5ba..72ddc10094c 100644 --- a/src/mbgl/shaders/mtl/line.cpp +++ b/src/mbgl/shaders/mtl/line.cpp @@ -4,14 +4,14 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float4, "a_color"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, "a_blur"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, "a_opacity"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, "a_gapwidth"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, "a_offset"}, - AttributeInfo{7, gfx::AttributeDataType::Float2, "a_width"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float4, idLineColorVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{8, true, false, sizeof(LineDynamicUBO), idLineDynamicUBO}, @@ -22,15 +22,15 @@ const std::array ShaderSource ShaderSource::textures = {}; const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, "a_blur"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, "a_gapwidth"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, "a_offset"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, "a_width"}, - AttributeInfo{7, gfx::AttributeDataType::UShort4, "a_pattern_from"}, - AttributeInfo{8, gfx::AttributeDataType::UShort4, "a_pattern_to"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::UShort4, idLinePatternFromVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::UShort4, idLinePatternToVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), idLinePatternDynamicUBO}, @@ -44,15 +44,15 @@ const std::array ShaderSource ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float4, "a_color"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, "a_blur"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, "a_opacity"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, "a_gapwidth"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, "a_offset"}, - AttributeInfo{7, gfx::AttributeDataType::Float2, "a_width"}, - AttributeInfo{8, gfx::AttributeDataType::Float2, "a_floorwidth"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float4, idLineColorVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::Float2, idLineFloorWidthVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{9, true, false, sizeof(LineDynamicUBO), idLineSDFDynamicUBO}, @@ -65,8 +65,8 @@ const std::array ShaderSource ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{2, true, true, sizeof(LineBasicUBO), idLineBasicUBO}, diff --git a/src/mbgl/shaders/mtl/line_gradient.cpp b/src/mbgl/shaders/mtl/line_gradient.cpp index a3a4a1aa77b..c44177070e0 100644 --- a/src/mbgl/shaders/mtl/line_gradient.cpp +++ b/src/mbgl/shaders/mtl/line_gradient.cpp @@ -4,13 +4,13 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos_normal"}, - AttributeInfo{1, gfx::AttributeDataType::UByte4, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float2, "a_blur"}, - AttributeInfo{3, gfx::AttributeDataType::Float2, "a_opacity"}, - AttributeInfo{4, gfx::AttributeDataType::Float2, "a_gapwidth"}, - AttributeInfo{5, gfx::AttributeDataType::Float2, "a_offset"}, - AttributeInfo{6, gfx::AttributeDataType::Float2, "a_width"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{7, true, false, sizeof(LineDynamicUBO), idLineGradientDynamicUBO}, diff --git a/src/mbgl/shaders/mtl/raster.cpp b/src/mbgl/shaders/mtl/raster.cpp index 0bb98a6150c..ccfd9b82133 100644 --- a/src/mbgl/shaders/mtl/raster.cpp +++ b/src/mbgl/shaders/mtl/raster.cpp @@ -4,8 +4,8 @@ namespace mbgl { namespace shaders { const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Short2, "a_pos"}, - AttributeInfo{1, gfx::AttributeDataType::Short2, "a_texture_pos"}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idRasterPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idRasterTexturePosVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{2, true, true, sizeof(RasterDrawableUBO), idRasterDrawableUBO}, diff --git a/src/mbgl/shaders/mtl/shader_program.cpp b/src/mbgl/shaders/mtl/shader_program.cpp index ca13c4acdae..466854487da 100644 --- a/src/mbgl/shaders/mtl/shader_program.cpp +++ b/src/mbgl/shaders/mtl/shader_program.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -24,11 +23,10 @@ using namespace std::string_literals; namespace mbgl { -shaders::AttributeInfo::AttributeInfo(std::size_t index_, gfx::AttributeDataType dataType_, std::string_view name_) +shaders::AttributeInfo::AttributeInfo(std::size_t index_, gfx::AttributeDataType dataType_, std::size_t id_) : index(index_), dataType(dataType_), - name(name_), - nameID(stringIndexer().get(name_)) {} + id(id_) {} shaders::UniformBlockInfo::UniformBlockInfo( std::size_t index_, bool vertex_, bool fragment_, std::size_t size_, std::size_t id_) @@ -195,19 +193,17 @@ void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) { const auto index = static_cast(info.index); #if !defined(NDEBUG) // Indexes must be unique, if there's a conflict check the `attributes` array in the shader - vertexAttributes.visitAttributes( - [&](auto, const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); + vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); #endif - vertexAttributes.add(stringIndexer().get(info.name), index, info.dataType, 1); + vertexAttributes.set(info.id, index, info.dataType, 1); } void ShaderProgram::initUniformBlock(const shaders::UniformBlockInfo& info) { const auto index = static_cast(info.index); #if !defined(NDEBUG) // Indexes must be unique, if there's a conflict check the `attributes` array in the shader - vertexAttributes.visitAttributes( - [&](auto, const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); + vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); #endif if (const auto& block_ = uniformBlocks.set(info.id, index, info.size)) { diff --git a/src/mbgl/shaders/mtl/symbol_icon.cpp b/src/mbgl/shaders/mtl/symbol_icon.cpp index 556a29aebdc..1681044bde5 100644 --- a/src/mbgl/shaders/mtl/symbol_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_icon.cpp @@ -5,14 +5,14 @@ namespace shaders { const std::array ShaderSource::attributes = { // always attributes - AttributeInfo{0, gfx::AttributeDataType::Short4, "a_pos_offset"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Short4, "a_pixeloffset"}, - AttributeInfo{3, gfx::AttributeDataType::Float3, "a_projected_pos"}, - AttributeInfo{4, gfx::AttributeDataType::Float, "a_fade_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short4, idSymbolPixelOffsetVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float, idSymbolFadeOpacityVertexAttribute}, // sometimes uniforms - AttributeInfo{5, gfx::AttributeDataType::Float, "a_opacity"}, + AttributeInfo{5, gfx::AttributeDataType::Float, idSymbolOpacityVertexAttribute}, }; const std::array ShaderSource::uniforms = { UniformBlockInfo{6, true, true, sizeof(SymbolDrawableUBO), idSymbolDrawableUBO}, diff --git a/src/mbgl/shaders/mtl/symbol_sdf.cpp b/src/mbgl/shaders/mtl/symbol_sdf.cpp index f064ad62060..48a9edb7038 100644 --- a/src/mbgl/shaders/mtl/symbol_sdf.cpp +++ b/src/mbgl/shaders/mtl/symbol_sdf.cpp @@ -6,18 +6,18 @@ namespace shaders { const std::array ShaderSource::attributes = { // always attributes - AttributeInfo{0, gfx::AttributeDataType::Short4, "a_pos_offset"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Short4, "a_pixeloffset"}, - AttributeInfo{3, gfx::AttributeDataType::Float3, "a_projected_pos"}, - AttributeInfo{4, gfx::AttributeDataType::Float, "a_fade_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short4, idSymbolPixelOffsetVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float, idSymbolFadeOpacityVertexAttribute}, // sometimes uniforms - AttributeInfo{5, gfx::AttributeDataType::Float4, "a_fill_color"}, - AttributeInfo{6, gfx::AttributeDataType::Float4, "a_halo_color"}, - AttributeInfo{7, gfx::AttributeDataType::Float, "a_opacity"}, - AttributeInfo{8, gfx::AttributeDataType::Float, "a_halo_width"}, - AttributeInfo{9, gfx::AttributeDataType::Float, "a_halo_blur"}, + AttributeInfo{5, gfx::AttributeDataType::Float4, idSymbolColorVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float4, idSymbolHaloColorVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float, idSymbolOpacityVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::Float, idSymbolHaloWidthVertexAttribute}, + AttributeInfo{9, gfx::AttributeDataType::Float, idSymbolHaloBlurVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp index 2cc4523eab2..83b8d645955 100644 --- a/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp +++ b/src/mbgl/shaders/mtl/symbol_text_and_icon.cpp @@ -6,17 +6,17 @@ namespace shaders { const std::array ShaderSource::attributes = { // always attributes - AttributeInfo{0, gfx::AttributeDataType::Short4, "a_pos_offset"}, - AttributeInfo{1, gfx::AttributeDataType::UShort4, "a_data"}, - AttributeInfo{2, gfx::AttributeDataType::Float3, "a_projected_pos"}, - AttributeInfo{3, gfx::AttributeDataType::Float, "a_fade_opacity"}, + AttributeInfo{0, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float, idSymbolFadeOpacityVertexAttribute}, // sometimes uniforms - AttributeInfo{4, gfx::AttributeDataType::Float4, "a_fill_color"}, - AttributeInfo{5, gfx::AttributeDataType::Float4, "a_halo_color"}, - AttributeInfo{6, gfx::AttributeDataType::Float, "a_opacity"}, - AttributeInfo{7, gfx::AttributeDataType::Float, "a_halo_width"}, - AttributeInfo{8, gfx::AttributeDataType::Float, "a_halo_blur"}, + AttributeInfo{4, gfx::AttributeDataType::Float4, idSymbolColorVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float4, idSymbolHaloColorVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float, idSymbolOpacityVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float, idSymbolHaloWidthVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::Float, idSymbolHaloBlurVertexAttribute}, }; const std::array ShaderSource::uniforms = { diff --git a/src/mbgl/style/layers/custom_drawable_layer.cpp b/src/mbgl/style/layers/custom_drawable_layer.cpp index 8e3554326b4..6f79ba910ce 100644 --- a/src/mbgl/style/layers/custom_drawable_layer.cpp +++ b/src/mbgl/style/layers/custom_drawable_layer.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -223,8 +222,8 @@ class SymbolDrawableTweaker : public gfx::DrawableTweaker { // set UBOs auto& uniforms = drawable.mutableUniformBuffers(); - uniforms.createOrUpdate(idCustomSymbolIconDrawableUBO, &drawableUBO, parameters.context); - uniforms.createOrUpdate(idCustomSymbolIconParametersUBO, ¶metersUBO, parameters.context); + uniforms.createOrUpdate(idCustomSymbolDrawableUBO, &drawableUBO, parameters.context); + uniforms.createOrUpdate(idCustomSymbolParametersUBO, ¶metersUBO, parameters.context); }; private: @@ -317,11 +316,8 @@ void CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geome gfx::generateFillBuffers(geometry, vertices, triangles, triangleSegments); // add to builder - static const StringIdentity idVertexAttribName = stringIndexer().get("a_pos"); - builder->setVertexAttrNameId(idVertexAttribName); - auto attrs = context.createVertexAttributeArray(); - if (const auto& attr = attrs->add(idVertexAttribName)) { + if (const auto& attr = attrs->set(idFillPosVertexAttribute)) { attr->setSharedRawData(sharedVertices, offsetof(FillLayoutVertex, a1), /*vertexOffset=*/0, @@ -377,19 +373,15 @@ void CustomDrawableLayerHost::Interface::addSymbol(const GeometryCoordinate& poi triangleSegments.emplace_back(Segment{0, 0, 4, 6}); // add to builder - static const StringIdentity idPositionAttribName = stringIndexer().get("a_pos"); - static const StringIdentity idTextureAttribName = stringIndexer().get("a_tex"); - builder->setVertexAttrNameId(idPositionAttribName); - auto attrs = context.createVertexAttributeArray(); - if (const auto& attr = attrs->add(idPositionAttribName)) { + if (const auto& attr = attrs->set(idCustomSymbolPosVertexAttribute)) { attr->setSharedRawData(sharedVertices, offsetof(CustomSymbolIcon, a_pos), /*vertexOffset=*/0, sizeof(CustomSymbolIcon), gfx::AttributeDataType::Float2); } - if (const auto& attr = attrs->add(idTextureAttribName)) { + if (const auto& attr = attrs->set(idCustomSymbolTexVertexAttribute)) { attr->setSharedRawData(sharedVertices, offsetof(CustomSymbolIcon, a_tex), /*vertexOffset=*/0, @@ -402,7 +394,7 @@ void CustomDrawableLayerHost::Interface::addSymbol(const GeometryCoordinate& poi // texture if (symbolOptions.texture) { - builder->setTexture(symbolOptions.texture, idCustomSymbolIconTexture); + builder->setTexture(symbolOptions.texture, idCustomSymbolImageTexture); } // create fill tweaker @@ -467,14 +459,13 @@ void CustomDrawableLayerHost::Interface::finish() { gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const { gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("LineShader"); - const mbgl::unordered_set propertiesAsUniforms{ - stringIndexer().get("a_color"), - stringIndexer().get("a_blur"), - stringIndexer().get("a_opacity"), - stringIndexer().get("a_gapwidth"), - stringIndexer().get("a_offset"), - stringIndexer().get("a_width"), - }; + const StringIDSetsPair propertiesAsUniforms{{"a_color", "a_blur", "a_opacity", "a_gapwidth", "a_offset", "a_width"}, + {idLineColorVertexAttribute, + idLineBlurVertexAttribute, + idLineOpacityVertexAttribute, + idLineGapWidthVertexAttribute, + idLineOffsetVertexAttribute, + idLineWidthVertexAttribute}}; return shaderGroup->getOrCreateShader(context, propertiesAsUniforms); } @@ -482,10 +473,8 @@ gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const { gfx::ShaderPtr CustomDrawableLayerHost::Interface::fillShaderDefault() const { gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("FillShader"); - const mbgl::unordered_set propertiesAsUniforms{ - stringIndexer().get("a_color"), - stringIndexer().get("a_opacity"), - }; + const StringIDSetsPair propertiesAsUniforms{{"a_color", "a_opacity"}, + {idFillColorVertexAttribute, idFillOpacityVertexAttribute}}; return shaderGroup->getOrCreateShader(context, propertiesAsUniforms); } diff --git a/src/mbgl/style/paint_property.hpp b/src/mbgl/style/paint_property.hpp index b7ac4ae4fbd..1b6f64c81b9 100644 --- a/src/mbgl/style/paint_property.hpp +++ b/src/mbgl/style/paint_property.hpp @@ -12,9 +12,6 @@ #include namespace mbgl { - -using StringIdentity = std::size_t; - namespace style { template @@ -46,12 +43,8 @@ class DataDrivenPaintProperty { using UniformList = TypeList; static constexpr const std::array AttributeNames = {A::name()}; - static std::array, 1> AttributeNameIDs; }; -template -std::array, 1> DataDrivenPaintProperty::AttributeNameIDs = {std::nullopt}; - template class CrossFadedDataDrivenPaintProperty { public: @@ -69,13 +62,8 @@ class CrossFadedDataDrivenPaintProperty { using UniformList = TypeList; static constexpr const std::array AttributeNames = {A1::name(), A2::name()}; - static std::array, 2> AttributeNameIDs; }; -template -std::array, 2> CrossFadedDataDrivenPaintProperty::AttributeNameIDs = { - std::nullopt, std::nullopt}; - template class CrossFadedPaintProperty { public: diff --git a/test/util/hash.test.cpp b/test/util/hash.test.cpp index b297df7be87..033d2a0f03b 100644 --- a/test/util/hash.test.cpp +++ b/test/util/hash.test.cpp @@ -94,16 +94,16 @@ void each_subset(const TSet& sofar, TIter beg, TIter end, TFunc f) { /// Ensure that no combination of attributes used by a shader definition produce a hash conflict template void checkShaderHashes() { - using AttribSet = mbgl::unordered_set; + using AttribSet = mbgl::unordered_set; AttribSet attributes; for (const auto& attrib : ShaderType::attributes) { - attributes.insert(attrib.nameID); + attributes.insert(attrib.id); } std::set subsetHashes; each_subset(AttribSet{}, attributes.cbegin(), attributes.cend(), [&](auto beg, auto end) { if (beg != end) { - std::vector subset(beg, end); + std::vector subset(beg, end); std::optional firstHash; do { const auto hash = util::order_independent_hash(subset.begin(), subset.end()); From 2d73f9b2600d153d61774a926cbfa2e40c4825c9 Mon Sep 17 00:00:00 2001 From: Tadej Novak Date: Sat, 17 Feb 2024 22:47:07 +0100 Subject: [PATCH 73/96] [qt] Try to stabilise the CI (#2120) Co-authored-by: Bart Louwers --- .github/actionlint.yaml | 1 + .github/workflows/qt-ci.yml | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 2ef1c46f4ab..a5c10e5b851 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -2,6 +2,7 @@ self-hosted-runner: # Labels of self-hosted runner in array of strings. labels: - MapLibre_Native_Linux_16_core + - macos-14 # can be removed once actionlint is updated # Configuration variables in array of strings defined in your repository or # organization. `null` means disabling configuration variables check. # Empty array means no configuration variable is allowed. diff --git a/.github/workflows/qt-ci.yml b/.github/workflows/qt-ci.yml index 284b0e558fb..83f7a3ebc17 100644 --- a/.github/workflows/qt-ci.yml +++ b/.github/workflows/qt-ci.yml @@ -78,7 +78,7 @@ jobs: qt_target: desktop compiler: "gcc-13" - name: macOS - os: macos-12 + os: macos-13 build_type: RelWithDebInfo qt_version: 5.15.2 qt_target: desktop @@ -86,20 +86,20 @@ jobs: deployment_arch: "x86_64" compiler: "" - name: macOS - os: macos-12 + os: macos-14 build_type: RelWithDebInfo - qt_version: 6.5.3 + qt_version: 6.6.2 qt_target: desktop deployment_target: 11.0 deployment_arch: "x86_64;arm64" compiler: "" - name: macOS_LLVM17 - os: macos-13 + os: macos-14 build_type: RelWithDebInfo - qt_version: 6.5.3 + qt_version: 6.6.2 qt_target: desktop deployment_target: 11.0 - deployment_arch: "x86_64" + deployment_arch: "arm64" compiler: "llvm@17" - name: win64_msvc2019 os: windows-2022 @@ -145,6 +145,7 @@ jobs: - name: Install test dependencies if: runner.os == 'Linux' && matrix.compiler != '' run: | + sudo apt-get update sudo apt-get install \ libxkbcommon-x11-0 \ libxcb-cursor0 \ @@ -184,12 +185,12 @@ jobs: # https://github.com/actions/runner-images/issues/8838#issuecomment-1817486924 brew link --overwrite python@3.12 brew install "$MLN_COMPILER" - echo "/usr/local/opt/${MLN_COMPILER}/bin" >> "$GITHUB_PATH" + echo "/opt/homebrew/opt/${MLN_COMPILER}/bin" >> "$GITHUB_PATH" { - echo "CC=/usr/local/opt/${MLN_COMPILER}/bin/clang" - echo "CXX=/usr/local/opt/${MLN_COMPILER}/bin/clang++" - echo "LDFLAGS=\"-L/usr/local/opt/${MLN_COMPILER}/lib\"" - echo "CPPFLAGS=\"-I/usr/local/opt/${MLN_COMPILER}/include\"" + echo "CC=/opt/homebrew/opt/${MLN_COMPILER}/bin/clang" + echo "CXX=/opt/homebrew/opt/${MLN_COMPILER}/bin/clang++" + echo "LDFLAGS=\"-L/opt/homebrew/opt/${MLN_COMPILER}/lib\"" + echo "CPPFLAGS=\"-I/opt/homebrew/opt/${MLN_COMPILER}/include\"" } >> "$GITHUB_ENV" - name: Setup Xcode @@ -217,6 +218,7 @@ jobs: target: ${{ matrix.qt_target }} arch: ${{ matrix.qt_arch }} tools: ${{ matrix.qt_tools }} + extra: --base https://mirrors.ocf.berkeley.edu/qt/ - name: Update ccache if: runner.os == 'Windows' From 10be6c25d838487f2608d0d9314fe59e3535a8e0 Mon Sep 17 00:00:00 2001 From: Oliver Wipfli Date: Tue, 20 Feb 2024 13:18:21 +0300 Subject: [PATCH 74/96] Add MSFT logo (#2123) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 49fdeb50c46..b9b81f024d4 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Silver: Logo Radar +Logo MSFT + Backers and Supporters: [![](https://opencollective.com/maplibre/backers.svg?avatarHeight=50&width=600)](https://opencollective.com/maplibre) From 0fc91b236a7b07750bc3e4d84a2284cba50df3e2 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Tue, 20 Feb 2024 16:36:29 +0100 Subject: [PATCH 75/96] Changelog for iOS 6.1.0 (#2126) --- platform/ios/CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 824732d36c8..52b887cefc5 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,6 +4,19 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C ## main +## 6.1.0 + +- Addition of an MLNCustomStyleLayer that allows running custom Metal shaders and code. Example included in the PR. ([#2041](https://github.com/maplibre/maplibre-native/pull/2006)) +- Various performance optimizations. + - Avoid redundant bindings/states based on Metal profiler feedback. ([#2006](https://github.com/maplibre/maplibre-native/pull/2006)) + - Eliminate the remaining examples of tweakers being re-created on each update, which reduced reuse of uniform buffers. ([#2050](https://github.com/maplibre/maplibre-native/pull/2050)) + - Eliminate an extra vector allocation for many attributes ([#2049](https://github.com/maplibre/maplibre-native/pull/2049)) + - Don't save an extra copy of properties-as-uniforms set with symbol data, or re-build it when updating properties on existing drawables. ([#2054](https://github.com/maplibre/maplibre-native/pull/2054)) + - Combine multiple segments into a drawable when `sortFeaturesByKey` is not used ([#2060](https://github.com/maplibre/maplibre-native/pull/2060)) + - UBO by index instead of map ([#1980](https://github.com/maplibre/maplibre-native/pull/1980)) + - Use `enableDepth` option ([#2073](https://github.com/maplibre/maplibre-native/pull/2073)) + - Minor optimizations ([#2091](https://github.com/maplibre/maplibre-native/pull/2091)) + ## 6.0.0 * This is the first release that uses **Metal** for rendering. This is a graphics API from Apple that replaces OpenGL ES on Apple platforms. From e461ed6174550fe151eb2d9a7f72973d2dc64fc6 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Tue, 20 Feb 2024 17:45:21 +0100 Subject: [PATCH 76/96] Bump iOS version number (#2127) --- platform/ios/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ios/VERSION b/platform/ios/VERSION index f4965a313a2..358e78e6074 100644 --- a/platform/ios/VERSION +++ b/platform/ios/VERSION @@ -1 +1 @@ -6.0.0 \ No newline at end of file +6.1.0 \ No newline at end of file From 86a936cde9f45e2fec7aa3bb1f789ce825a0b0ee Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Tue, 20 Feb 2024 17:47:15 +0100 Subject: [PATCH 77/96] Update README.md (#2125) --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b9b81f024d4..676c992fc84 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,8 @@ Platforms with a ⭐️ are **MapLibre Core Projects** and have a substantial am ![image-metal](https://user-images.githubusercontent.com/53421382/214308933-66cd4efb-b5a5-4de3-b4b4-7ed59045a1c3.png) -MapLibre Native is being actively developed. Our big goal for 2023 is to modularize the OpenGL renderer and implement a Metal graphics backend (https://developer.apple.com/metal/). This will improve the performance and yield lower power consumption on iOS devices. At the same time, the Metal preparations will help us in the implementation of a Vulkan graphics backend. - -Your help in preparing the codebase for the latest graphics backends is more than welcome. Feel free to reach out if you are interested in joining the effort! - -- Check out the [news](https://maplibre.org/news/) on MapLibre's website. -- See the [Design Proposals](https://github.com/maplibre/maplibre-native/tree/main/design-proposals) that have been accepted and are being worked on, the most recent ones being the [Rendering Modularization Design Proposal](design-proposals/2022-10-27-rendering-modularization.md) and the [Metal Port Design Proposal](design-proposals/2022-11-29-metal-port.md). - +MapLibre Native for iOS 6.0.0 with Metal support has been released. See the [news announcement](https://maplibre.org/news/2024-01-19-metal-support-for-maplibre-native-ios-is-here/). + ## Contributing To contribute to MapLibre Native, see [`CONTRIBUTING.md`](CONTRIBUTING.md) and (if applicable) the specific instructions for the platform you want to contribute to. @@ -76,7 +71,7 @@ Silver: Logo Radar -Logo MSFT +Logo Microsoft Backers and Supporters: From f93a646921e344d9955b8a466238f3847abdd200 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:56:14 +0100 Subject: [PATCH 78/96] Bump codecov/codecov-action from 3 to 4 (#2077) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/upload-coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload-coverage.yml b/.github/workflows/upload-coverage.yml index 6cd0836294a..1a5c38d6222 100644 --- a/.github/workflows/upload-coverage.yml +++ b/.github/workflows/upload-coverage.yml @@ -19,7 +19,7 @@ jobs: - name: Upload coverage report if: '!cancelled()' - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: override_commit: ${{ github.event.workflow_run.head_sha }} override_pr: ${{ github.event.workflow_run.pull_requests[0].number }} From 61fd33f5267ce55b68522b152d398fc23222c323 Mon Sep 17 00:00:00 2001 From: Jonas Vautherin Date: Wed, 21 Feb 2024 13:26:19 +0100 Subject: [PATCH 79/96] cmake: enable MLN_WITH_EGL and OPENGL_USE_GLES for Wayland (#2022) --- platform/linux/linux.cmake | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/platform/linux/linux.cmake b/platform/linux/linux.cmake index a83f8407605..f816a4aadba 100644 --- a/platform/linux/linux.cmake +++ b/platform/linux/linux.cmake @@ -15,6 +15,18 @@ find_package(Threads REQUIRED) pkg_search_module(WEBP libwebp REQUIRED) pkg_search_module(LIBUV libuv REQUIRED) +if(MLN_WITH_WAYLAND) + # See https://github.com/maplibre/maplibre-native/pull/2022 + + # MLN_WITH_EGL needs to be set for Wayland, otherwise this CMakeLists will + # call find_package(OpenGL REQUIRED GLX), which is for X11. + set(MLN_WITH_EGL TRUE) + + # OPENGL_USE_GLES2 or OPENGL_USE_GLES3 need to be set, otherwise + # FindOpenGL.cmake will include the GLVND library, which is for X11. + set(OPENGL_USE_GLES3 TRUE) +endif() + target_sources( mbgl-core PRIVATE From 02eeec172d31120d1b8f56e87912ab047a6b1d01 Mon Sep 17 00:00:00 2001 From: Tim Sylvester Date: Wed, 21 Feb 2024 04:53:43 -0800 Subject: [PATCH 80/96] Get unit and render tests running on iOS simulator (#2129) --- src/mbgl/mtl/offscreen_texture.cpp | 4 ++++ src/mbgl/mtl/texture2d.cpp | 31 ++++++++++++++++++++++++++++++ test/include/mbgl/test/util.hpp | 3 ++- test/map/map.test.cpp | 1 - 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/mbgl/mtl/offscreen_texture.cpp b/src/mbgl/mtl/offscreen_texture.cpp index 8226b946d24..b24658d1bc4 100644 --- a/src/mbgl/mtl/offscreen_texture.cpp +++ b/src/mbgl/mtl/offscreen_texture.cpp @@ -35,6 +35,8 @@ class OffscreenTextureResource final : public RenderableResource { ->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget); } + // On iOS simulator, the depth target is PixelFormatDepth32Float_Stencil8 +#if !TARGET_OS_SIMULATOR if (stencil) { stencilTexture = context.createTexture2D(); stencilTexture->setSize(size); @@ -44,6 +46,8 @@ class OffscreenTextureResource final : public RenderableResource { static_cast(stencilTexture.get()) ->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget); } +#endif + context.renderingStats().numFrameBuffers++; } diff --git a/src/mbgl/mtl/texture2d.cpp b/src/mbgl/mtl/texture2d.cpp index 6d10d2d282c..b42d4b69838 100644 --- a/src/mbgl/mtl/texture2d.cpp +++ b/src/mbgl/mtl/texture2d.cpp @@ -82,6 +82,19 @@ size_t Texture2D::numChannels() const noexcept { } MTL::PixelFormat Texture2D::getMetalPixelFormat() const noexcept { + // On iOS simulator, we need to use the combined depth/stencil format. If the depth and stencil + // formats are both set on a render pipeline, they have to be identical or we'll get, e.g.: + // validateWithDevice:4343: failed assertion `Render Pipeline Descriptor Validation + // depthAttachmentPixelFormat (MTLPixelFormatDepth32Float) and + // stencilAttachmentPixelFormat (MTLPixelFormatStencil8) must match. + +#if TARGET_OS_SIMULATOR + if (channelType == gfx::TextureChannelDataType::Float && pixelFormat == gfx::TexturePixelType::Depth && + (usage & MTL::TextureUsageRenderTarget)) { + return MTL::PixelFormatDepth32Float_Stencil8; + } +#endif + switch (channelType) { case gfx::TextureChannelDataType::UnsignedByte: switch (pixelFormat) { @@ -138,6 +151,24 @@ void Texture2D::createMetalTexture() noexcept { if (auto textureDescriptor = NS::RetainPtr( MTL::TextureDescriptor::texture2DDescriptor(format, size.width, size.height, /*mipmapped=*/false))) { textureDescriptor->setUsage(usage); +#if TARGET_OS_SIMULATOR + switch (format) { + case MTL::PixelFormatDepth16Unorm: + case MTL::PixelFormatDepth32Float: + case MTL::PixelFormatStencil8: + case MTL::PixelFormatDepth24Unorm_Stencil8: + case MTL::PixelFormatDepth32Float_Stencil8: + case MTL::PixelFormatX32_Stencil8: + case MTL::PixelFormatX24_Stencil8: + // On iOS simulator, the default shared mode is invalid for depth and stencil textures. + // 'Texture Descriptor Validation MTLTextureDescriptor: Depth, Stencil, DepthStencil + // textures cannot be allocated with MTLStorageModeShared on this device. + textureDescriptor->setStorageMode(MTL::StorageMode::StorageModePrivate); + break; + default: + break; + } +#endif metalTexture = context.createMetalTexture(std::move(textureDescriptor)); } diff --git a/test/include/mbgl/test/util.hpp b/test/include/mbgl/test/util.hpp index cac43570545..c1abe6bad0e 100644 --- a/test/include/mbgl/test/util.hpp +++ b/test/include/mbgl/test/util.hpp @@ -6,7 +6,8 @@ #define TEST_READ_ONLY 0 -#if !ANDROID +// iOS simulator server can work if port 3000 is available +#if !ANDROID && !TARGET_OS_SIMULATOR #ifndef TEST_HAS_SERVER #define TEST_HAS_SERVER 1 #endif diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp index 9b0827164cb..81050434ef0 100644 --- a/test/map/map.test.cpp +++ b/test/map/map.test.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include From 7f6193db022d6cbef6daf519392c76446e37b562 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Fri, 23 Feb 2024 15:08:27 +0100 Subject: [PATCH 81/96] Prepare for Android 11.0.0-pre0 release (#2133) --- .github/workflows/android-release.yml | 2 +- platform/android/CHANGELOG.md | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index c9b9dddfe58..0e4c4de41bd 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -141,7 +141,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: platform/android/MapboxGLAndroidSDK/build/outputs/aar/MapboxGLAndroidSDK-release.aar + asset_path: platform/android/MapboxGLAndroidSDK/build/outputs/aar/MapboxGLAndroidSDK-drawable-release.aar asset_name: MapboxGLAndroidSDK-release.aar asset_content_type: application/zip diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index de075c24ba0..fa9a322392b 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -1,11 +1,18 @@ -# Changelog for the MapLibre Maps SDK for Android - -MapLibre welcomes participation and contributions from everyone. Please read [`Contributing Guide`](https://github.com/maplibre/maplibre-native/blob/main/CONTRIBUTING.md) to get started. +# Changelog MapLibre Native for Android ## main ### ✨ Features and improvements -* Add support for the [`slice` expression](https://maplibre.org/maplibre-style-spec/expressions/#slice) ([#1113](https://github.com/maplibre/maplibre-native/pull/1133)) + +### 🐞 Bug fixes + +## 11.0.0 + +### ✨ Features and improvements + +- Add support for the [`slice` expression](https://maplibre.org/maplibre-style-spec/expressions/#slice) ([#1113](https://github.com/maplibre/maplibre-native/pull/1133)) +- Add support for the [`index-of` expression](https://maplibre.org/maplibre-style-spec/expressions/#index-of) ([#1113](https://github.com/maplibre/maplibre-native/pull/1113)) +- Change to a more natural fling animation and allow setting `flingThreshold` and `flingAnimationBaseTime` in `UiSettings` ([#963](https://github.com/maplibre/maplibre-native/pull/963)) - 💥 Breaking: Change package of all classes from `com.mapbox.mapboxsdk` to `org.maplibre.android` ([#1201](https://github.com/maplibre/maplibre-native/pull/1201)). This means you will need to fix your imports. @@ -29,7 +36,11 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C - Fix "... has unresolved theme attributes" error in BitMapUtils ([#1274](https://github.com/maplibre/maplibre-native/issues/1274)). -### ⛵ Dependencies +## 10.2.0 + +Revert changes of 10.1.0, which was a breaking release by accident. + +This version is identical to 10.0.2. ## 10.1.0 - May 9, 2023 From 94324cfa8af4daf2873eb9607facb47072b1c897 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Fri, 23 Feb 2024 16:34:12 +0100 Subject: [PATCH 82/96] Fix Android release (#2137) --- .github/workflows/android-release.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 0e4c4de41bd..fce79a42f7a 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -109,9 +109,6 @@ jobs: fi shell: bash - - name: Generate javadoc - run: make android-javadoc - # create github release - name: Prepare release id: prepare_release From bfafb724d9b50d72a1b851dbdfdb566d9798cb6d Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Fri, 23 Feb 2024 17:22:06 +0100 Subject: [PATCH 83/96] Try to fix coverage upload (#2136) --- .github/workflows/upload-coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/upload-coverage.yml b/.github/workflows/upload-coverage.yml index 1a5c38d6222..c98818284d0 100644 --- a/.github/workflows/upload-coverage.yml +++ b/.github/workflows/upload-coverage.yml @@ -25,5 +25,6 @@ jobs: override_pr: ${{ github.event.workflow_run.pull_requests[0].number }} token: ${{ secrets.CODECOV_TOKEN }} files: "_coverage_report.dat" + disable_search: true fail_ci_if_error: true verbose: true From b403ac9682de6073e10132343e56b016d27ad003 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Fri, 23 Feb 2024 21:49:05 +0100 Subject: [PATCH 84/96] Free GitHub runners (#2132) --- .github/actionlint.yaml | 1 - .github/workflows/android-ci.yml | 50 ++++++++++++++++++++++---------- .github/workflows/ios-ci.yml | 8 ++--- .github/workflows/linux-ci.yml | 2 +- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index a5c10e5b851..36ae1928dc7 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,7 +1,6 @@ self-hosted-runner: # Labels of self-hosted runner in array of strings. labels: - - MapLibre_Native_Linux_16_core - macos-14 # can be removed once actionlint is updated # Configuration variables in array of strings defined in your repository or # organization. `null` means disabling configuration variables check. diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index e8b1e9a2ec2..269b766158b 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -33,7 +33,7 @@ jobs: files_yaml_from_source_file: .github/changed-files.yml android-build: - runs-on: ${{ github.event.pull_request && !github.event.pull_request.draft && 'MapLibre_Native_Linux_16_core' || 'ubuntu-22.04' }} + runs-on: ubuntu-22.04 needs: - pre_job if: needs.pre_job.outputs.should_skip != 'true' @@ -193,7 +193,39 @@ jobs: name: uploadsEnv path: uploads.env - # render test + - name: Store debug artifacts + uses: actions/upload-artifact@v4 + with: + name: debug-artifacts + path: | + MapboxGLAndroidSDKTestApp/build/outputs/apk/debug + MapboxGLAndroidSDK/build/reports/lint-results.html + MapboxGLAndroidSDK/lint-baseline.xml + MapboxGLAndroidSDKTestApp/build/reports/lint-results.html + MapboxGLAndroidSDKTestApp/build/reports/lint-results.xml + MapboxGLAndroidSDKTestApp/lint-baseline.xml + MapboxGLAndroidSDK/build/intermediates/cmake/debug/obj + + android-build-render-test: + runs-on: ubuntu-latest + needs: + - pre_job + if: needs.pre_job.outputs.should_skip != 'true' + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }} + + - uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + - name: Build Render Test App run: | ./gradlew assemble assembleAndroidTest @@ -210,19 +242,6 @@ jobs: ./render-test/android/RenderTestsApp.apk ./render-test/android/RenderTests.apk - - name: Store debug artifacts - uses: actions/upload-artifact@v4 - with: - name: debug-artifacts - path: | - MapboxGLAndroidSDKTestApp/build/outputs/apk/debug - MapboxGLAndroidSDK/build/reports/lint-results.html - MapboxGLAndroidSDK/lint-baseline.xml - MapboxGLAndroidSDKTestApp/build/reports/lint-results.html - MapboxGLAndroidSDKTestApp/build/reports/lint-results.xml - MapboxGLAndroidSDKTestApp/lint-baseline.xml - MapboxGLAndroidSDK/build/intermediates/cmake/debug/obj - android-instrumentation-test: needs: android-build @@ -265,6 +284,7 @@ jobs: needs: - pre_job - android-build + - android-build-render-test steps: - name: Mark result as failed if: needs.android-build.result != 'success' diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 73fc3991f13..8a3b0e72edf 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -210,7 +210,7 @@ jobs: if: github.event.inputs.release == 'full' run: | echo version="$(head VERSION)" >> "$GITHUB_ENV" - echo changelog_version_heading="## ${{ env.version }}" >> "$GITHUB_ENV" + echo changelog_version_heading="## $(head VERSION)" >> "$GITHUB_ENV" - name: Get version (pre-release) if: github.event.inputs.release == 'pre' @@ -274,9 +274,9 @@ jobs: -H "Authorization: token ${{ steps.generate_token.outputs.token }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ - -d '{"ref":"main","inputs":{ \ - "changelog_url": "https://maplibre-native.s3.eu-central-1.amazonaws.com/changelogs/ios-${{ env.version }}.md", \ - "version":"${{ env.version }}", \ + -d '{"ref":"main","inputs":{ + "changelog_url": "https://maplibre-native.s3.eu-central-1.amazonaws.com/changelogs/ios-${{ env.version }}.md", + "version":"${{ env.version }}", "download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' - name: Release (CocoaPods) diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index c09363d297a..d5cfd857e71 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -51,7 +51,7 @@ jobs: fail-fast: true matrix: renderer: [legacy, drawable] - runs-on: ${{ github.event.pull_request && !github.event.pull_request.draft && 'MapLibre_Native_Linux_16_core' || 'ubuntu-22.04' }} + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: From 3eb3a233eb85093aff8b77c06b1f160bfb63d50b Mon Sep 17 00:00:00 2001 From: Theodore Calmes Date: Sun, 25 Feb 2024 06:11:57 -0800 Subject: [PATCH 85/96] [darwin] Tighten camera equality requirements (#2139) --- platform/darwin/src/MLNMapCamera.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/darwin/src/MLNMapCamera.mm b/platform/darwin/src/MLNMapCamera.mm index a3d64b1d91f..758b5e9bfbe 100644 --- a/platform/darwin/src/MLNMapCamera.mm +++ b/platform/darwin/src/MLNMapCamera.mm @@ -180,11 +180,11 @@ - (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera return YES; } - return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude, otherCamera.centerCoordinate.latitude, 1e-6) - && MLNEqualFloatWithAccuracy(_centerCoordinate.longitude, otherCamera.centerCoordinate.longitude, 1e-6) + return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude, otherCamera.centerCoordinate.latitude, 1e-8) + && MLNEqualFloatWithAccuracy(_centerCoordinate.longitude, otherCamera.centerCoordinate.longitude, 1e-8) && MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6) - && MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1) - && MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1)); + && MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2) + && MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2)); } - (NSUInteger)hash From 84838209e452dc6e91665dc26c83f8ff83c862b4 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Sun, 25 Feb 2024 16:24:34 +0100 Subject: [PATCH 86/96] Prepare for iOS 6.1.1 release (#2143) --- .github/workflows/ios-ci.yml | 12 ++++++++++-- platform/ios/CHANGELOG.md | 6 +++++- platform/ios/VERSION | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 8a3b0e72edf..6373c7008ed 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -196,9 +196,17 @@ jobs: folder: build/docs target-folder: ios/latest/ + - name: VERSION file changed + id: version-file-ios-changed + uses: tj-actions/changed-files@v42 + with: + files: platform/ios/VERSION + # Make Metal XCFramework release - name: Should make release? - if: github.event.inputs.release == 'full' || github.event.inputs.release == 'pre' + if: | + github.event.inputs.release == 'full' || github.event.inputs.release == 'pre' || + (github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.version-file-ios-changed.outputs.any_changed == 'true') run: echo make_release=true >> "$GITHUB_ENV" - name: Build XCFramework @@ -207,7 +215,7 @@ jobs: echo xcframework="$(bazel info execution_root)"/"$(bazel cquery --output=files --compilation_mode=opt --//:renderer=metal //platform/ios:MapLibre.dynamic)" >> "$GITHUB_ENV" - name: Get version (release) - if: github.event.inputs.release == 'full' + if: github.event.inputs.release == 'full' || steps.version-file-ios-changed.outputs.any_changed == 'true' run: | echo version="$(head VERSION)" >> "$GITHUB_ENV" echo changelog_version_heading="## $(head VERSION)" >> "$GITHUB_ENV" diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 52b887cefc5..603710e0819 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,9 +4,13 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C ## main +## 6.1.1 + +- Tighten camera equality requirements ([#2139](https://github.com/maplibre/maplibre-native/pull/2139)). + ## 6.1.0 -- Addition of an MLNCustomStyleLayer that allows running custom Metal shaders and code. Example included in the PR. ([#2041](https://github.com/maplibre/maplibre-native/pull/2006)) +- Addition of an MLNCustomStyleLayer that allows running custom Metal shaders and code. Example included in the PR. ([#2006](https://github.com/maplibre/maplibre-native/pull/2006)) - Various performance optimizations. - Avoid redundant bindings/states based on Metal profiler feedback. ([#2006](https://github.com/maplibre/maplibre-native/pull/2006)) - Eliminate the remaining examples of tweakers being re-created on each update, which reduced reuse of uniform buffers. ([#2050](https://github.com/maplibre/maplibre-native/pull/2050)) diff --git a/platform/ios/VERSION b/platform/ios/VERSION index 358e78e6074..132c6def58c 100644 --- a/platform/ios/VERSION +++ b/platform/ios/VERSION @@ -1 +1 @@ -6.1.0 \ No newline at end of file +6.1.1 \ No newline at end of file From 0261b454a016188a74a101d6a14d0a78770bb5a7 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Sun, 25 Feb 2024 19:21:27 +0100 Subject: [PATCH 87/96] Roll back codecov/codecov-action@v4 (#2144) --- .github/workflows/upload-coverage.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/upload-coverage.yml b/.github/workflows/upload-coverage.yml index c98818284d0..6cd0836294a 100644 --- a/.github/workflows/upload-coverage.yml +++ b/.github/workflows/upload-coverage.yml @@ -19,12 +19,11 @@ jobs: - name: Upload coverage report if: '!cancelled()' - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 with: override_commit: ${{ github.event.workflow_run.head_sha }} override_pr: ${{ github.event.workflow_run.pull_requests[0].number }} token: ${{ secrets.CODECOV_TOKEN }} files: "_coverage_report.dat" - disable_search: true fail_ci_if_error: true verbose: true From 61a4790c0dfa8696384e991aad5a37573262a110 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 26 Feb 2024 06:59:45 +0100 Subject: [PATCH 88/96] Refactor PR workflows to not fail (#2145) --- .github/actions/get-pr-number/action.yml | 5 +- .github/workflows/android-device-test.yml | 64 +++++++++++++------ .github/workflows/pr-bloaty-ios.yml | 16 ++++- .../{pr-tests.yml => pr-linux-tests.yml} | 18 +++++- .github/workflows/pr-render-test-result.yml | 60 ----------------- .github/workflows/upload-coverage.yml | 13 ++++ 6 files changed, 92 insertions(+), 84 deletions(-) rename .github/workflows/{pr-tests.yml => pr-linux-tests.yml} (90%) delete mode 100644 .github/workflows/pr-render-test-result.yml diff --git a/.github/actions/get-pr-number/action.yml b/.github/actions/get-pr-number/action.yml index 84f360ca406..47225256ac4 100644 --- a/.github/actions/get-pr-number/action.yml +++ b/.github/actions/get-pr-number/action.yml @@ -2,7 +2,7 @@ name: 'get-pr-number' description: 'Gets the PR number from an artifact' outputs: pr-number: - description: "PR number" + description: "PR number or empty string" value: ${{ steps.cat.outputs.pr-number }} runs: using: "composite" @@ -10,7 +10,8 @@ runs: - uses: ./.github/actions/download-workflow-run-artifact with: artifact-name: pr-number - expect-files: "./pr_number" + + - run: touch ./pr_number - id: cat run: echo pr-number="$(cat ./pr_number)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index 7c06614c86b..e7e9c881623 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -7,7 +7,21 @@ on: - completed jobs: - create-check: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.check_skip.outputs.should_skip }} + steps: + # if android-build in android-ci was skipped, the android-device-test job can be skipped entirely + - id: check_skip + run: | + conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "android-build").conclusion') + should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") + echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" + + android-device-test: + needs: pre_job + if: needs.pre_job.outputs.should_skip == 'false' strategy: max-parallel: 2 matrix: @@ -34,29 +48,48 @@ jobs: } ] runs-on: ubuntu-latest - if: github.event.workflow_run.event == 'pull_request' steps: - uses: actions/checkout@v4 + - name: Sanity check, test files should exist + uses: andstor/file-existence-action@v2.0.0 + with: + files: "${{ matrix.test.testFile }}, ${{ matrix.test.appFile }}" + fail: true + + # get comment from PR + + - uses: ./.github/actions/get-pr-number + id: get-pr-number + - name: Generate token + if: matrix.test.name == 'Android Benchmark' && steps.get-pr-number.outputs.pr-number id: generate_token uses: tibdex/github-app-token@v2 with: app_id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} private_key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} - - uses: ./.github/actions/get-pr-number - id: get-pr-number - - name: Check if comment on PR contains '!benchmark android' + if: matrix.test.name == 'Android Benchmark' && steps.get-pr-number.outputs.pr-number uses: peter-evans/find-comment@v3 - id: fc + id: benchmark_comment with: issue-number: ${{ steps.get-pr-number.outputs.pr-number }} body-regex: '^!benchmark.*android.*$' + - name: Should we run this device test? + # always run when something was merged into main + # run benchmark when comment with '!benchmark android' exists in PR + if: | + (github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.event == 'push') || + matrix.test.name == 'Android Benchmark' && steps.benchmark_comment.outputs.comment-id || + matrix.test.name != 'Android Benchmark' + run: + echo "run_device_test=true" >> "$GITHUB_ENV" + - uses: LouisBrunner/checks-action@v2.0.0 - if: matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id + if: env.run_device_test == 'true' id: create_check with: token: ${{ steps.generate_token.outputs.token }} @@ -66,19 +99,12 @@ jobs: sha: ${{ github.event.workflow_run.head_sha }} - uses: ./.github/actions/download-workflow-run-artifact - if: matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id + if: env.run_device_test == 'true' with: artifact-name: ${{ matrix.test.artifactName }} - - name: Check if test files exist (otherwise the parent workflow was skipped) - if: matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id - id: check_files - uses: andstor/file-existence-action@v2.0.0 - with: - files: "${{ matrix.test.testFile }}, ${{ matrix.test.appFile }}" - - name: Configure AWS Credentials - if: matrix.test.name == 'Android Benchmark' + if: env.run_device_test == 'true' && matrix.test.name == 'Android Benchmark' uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -89,7 +115,7 @@ jobs: role-session-name: MySessionName - name: Upload external data for benchmark - if: matrix.test.name == 'Android Benchmark' + if: env.run_device_test == 'true' && matrix.test.name == 'Android Benchmark' run: | export RESULTS_API=${{ secrets.MLN_RESULTS_API }} export AWS_DEVICE_FARM_PROJECT_ARN=${{ vars.AWS_DEVICE_FARM_PROJECT_ARN }} @@ -97,7 +123,7 @@ jobs: echo external_data_arn="$upload_arn" >> "$GITHUB_ENV" - uses: ./.github/actions/aws-device-farm-run - if: steps.check_files.outputs.files_exists == 'true' && (matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id) + if: env.run_device_test == 'true' with: name: ${{ matrix.test.name }} appType: ANDROID_APP @@ -115,7 +141,7 @@ jobs: testSpecArn: ${{ matrix.test.testSpecArn }} - uses: LouisBrunner/checks-action@v2.0.0 - if: always() && (matrix.test.name != 'Android Benchmark' || steps.fc.outputs.comment-id) + if: always() && env.run_device_test == 'true' with: token: ${{ steps.generate_token.outputs.token }} check_id: ${{ steps.create_check.outputs.check_id }} diff --git a/.github/workflows/pr-bloaty-ios.yml b/.github/workflows/pr-bloaty-ios.yml index bbc4f14eeef..ec731132fda 100644 --- a/.github/workflows/pr-bloaty-ios.yml +++ b/.github/workflows/pr-bloaty-ios.yml @@ -13,8 +13,22 @@ permissions: id-token: write # This is required for requesting the AWS JWT jobs: - pr-bloaty-ios: + pre_job: if: github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.check_skip.outputs.should_skip }} + steps: + # if ios-build in ios-ci was skipped, the rest of the workflow can be skipped + - id: check_skip + run: | + conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "ios-build").conclusion') + should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") + echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" + + pr-bloaty-ios: + needs: pre_job + if: needs.pre_job.outputs.should_skip == 'false' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-linux-tests.yml similarity index 90% rename from .github/workflows/pr-tests.yml rename to .github/workflows/pr-linux-tests.yml index c77e1fe4c87..25610ce8a57 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-linux-tests.yml @@ -1,5 +1,5 @@ # Runs Bloaty (size test) and Google Benchmark and reports results on PR -name: pr-tests +name: pr-linux-tests on: workflow_run: @@ -16,8 +16,22 @@ permissions: id-token: write # This is required for requesting the AWS JWT jobs: - pr-bloaty: + pre_job: if: github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.check_skip.outputs.should_skip }} + steps: + # if linux-build-and-test in linux-ci was skipped, the rest of the workflow can be skipped + - id: check_skip + run: | + conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "linux-build-and-test").conclusion') + should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") + echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" + + pr-bloaty: + needs: pre_job + if: needs.pre_job.outputs.should_skip == 'false' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr-render-test-result.yml b/.github/workflows/pr-render-test-result.yml deleted file mode 100644 index 5461753ee70..00000000000 --- a/.github/workflows/pr-render-test-result.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: pr-upload-render-test-result - -permissions: - pull-requests: write # This is required to leave a comment on the PR - id-token: write # This is required for requesting the AWS JWT - -on: - workflow_run: - workflows: [linux-ci] - types: - - completed - -jobs: - upload-render-test-result: - runs-on: ubuntu-22.04 - if: github.event.workflow_run.event == 'pull_request' - env: - html_filename: "linux-drawable.html" - steps: - - uses: actions/checkout@v4 - - - uses: ./.github/actions/get-pr-number - id: get-pr-number - - - uses: ./.github/actions/download-workflow-run-artifact - with: - artifact-name: render-test-result - - # when there are no results, the render test succeeded - # in this case the subsequent steps can be skipped - - name: "Check existence render test results HTML" - id: render_test_results - uses: andstor/file-existence-action@v2.0.0 - with: - files: ${{ env.html_filename }} - - - name: Configure AWS Credentials - if: steps.render_test_results.outputs.files_exists - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-region: us-west-2 - role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} - role-session-name: ${{ github.run_id }} - - - name: Upload render test results to S3 - if: steps.render_test_results.outputs.files_exists - id: upload_render_test_results - run: | - aws s3 cp metrics/${{ env.html_filename }} \ - s3://maplibre-native-test-artifacts/${{ github.run_id }}-${{ env.html_filename }} \ - --expires "$(date -d '+30 days' --utc +'%Y-%m-%dT%H:%M:%SZ')" - - - name: 'Leave comment on PR with test results' - if: steps.render_test_results.outputs.files_exists - uses: marocchino/sticky-pull-request-comment@v2 - with: - header: render-test-result - number: ${{ steps.get-pr-number.outputs.pr-number }} - message: | - Render test results at https://maplibre-native-test-artifacts.s3.eu-central-1.amazonaws.com/${{ github.run_id }}-${{ env.html_filename }} diff --git a/.github/workflows/upload-coverage.yml b/.github/workflows/upload-coverage.yml index 6cd0836294a..046b2fdb843 100644 --- a/.github/workflows/upload-coverage.yml +++ b/.github/workflows/upload-coverage.yml @@ -7,7 +7,20 @@ on: - completed jobs: + pre_job: + runs-on: ubuntu-latest + outputs: + should_skip: ${{ steps.check_skip.outputs.should_skip }} + steps: + - id: check_skip + run: | + conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "linux-coverage").conclusion') + should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") + echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" + upload-coverage: + needs: pre_job + if: needs.pre_job.outputs.should_skip == 'false' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 9e8837b0fbe5eeb75720f11b31ee604a5829b79e Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Mon, 26 Feb 2024 13:34:01 +0100 Subject: [PATCH 89/96] Debug Android release (#2147) --- .github/actions/get-pr-number/action.yml | 1 + platform/android/Makefile | 2 +- platform/android/gradle/gradle-publish.gradle | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/actions/get-pr-number/action.yml b/.github/actions/get-pr-number/action.yml index 47225256ac4..23da26d09c3 100644 --- a/.github/actions/get-pr-number/action.yml +++ b/.github/actions/get-pr-number/action.yml @@ -12,6 +12,7 @@ runs: artifact-name: pr-number - run: touch ./pr_number + shell: bash - id: cat run: echo pr-number="$(cat ./pr_number)" >> $GITHUB_OUTPUT diff --git a/platform/android/Makefile b/platform/android/Makefile index 652c7963668..81cb2e7187f 100644 --- a/platform/android/Makefile +++ b/platform/android/Makefile @@ -234,7 +234,7 @@ run-android-test-app-center: # Uploads the compiled Android SDK to Maven Central Staging .PHONY: run-android-publish run-android-publish: - $(MLN_ANDROID_GRADLE_SINGLE_JOB) -Pmapbox.abis=all :MapboxGLAndroidSDK:publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository + $(MLN_ANDROID_GRADLE_SINGLE_JOB) --debug -Pmapbox.abis=all :MapboxGLAndroidSDK:publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository # Dump system graphics information for the test app .PHONY: android-gfxinfo diff --git a/platform/android/gradle/gradle-publish.gradle b/platform/android/gradle/gradle-publish.gradle index d3e383e4184..0c915ac923e 100644 --- a/platform/android/gradle/gradle-publish.gradle +++ b/platform/android/gradle/gradle-publish.gradle @@ -44,7 +44,7 @@ afterEvaluate { artifactId project.ext.mapLibreArtifactId version this.version - from components.legacyRelease + from components.drawableRelease artifact androidSourcesJar artifact androidJavadocsJar From f4c6fabbe6784418c64f9731fef2e3f87e7d7962 Mon Sep 17 00:00:00 2001 From: Geolives Date: Tue, 27 Feb 2024 18:42:17 +0100 Subject: [PATCH 90/96] Implementation of multi-sprites in mbgl-core (#1858) Co-authored-by: Christophe Brasseur Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bart Louwers Co-authored-by: Bart Louwers --- .github/workflows/ios-ci.yml | 2 +- CMakeLists.txt | 3 + bazel/core.bzl | 4 + .../sprites/array-default-only/metrics.json | 35 ++++++++ .../sprites/array-multiple/metrics.json | 35 ++++++++ .../sprites/array-default-only/expected.png | Bin 0 -> 1545 bytes .../sprites/array-default-only/style.json | 56 +++++++++++++ .../sprites/array-multiple/expected.png | Bin 0 -> 1834 bytes .../sprites/array-multiple/style.json | 79 ++++++++++++++++++ .../sprites/array-default-only/metrics.json | 35 ++++++++ .../sprites/array-multiple/metrics.json | 35 ++++++++ platform/android/CHANGELOG.md | 1 + .../src/mbgl/storage/offline_download.cpp | 19 +++-- platform/ios/CHANGELOG.md | 4 + platform/ios/VERSION | 2 +- render-test/parser.cpp | 1 + src/mbgl/map/map_impl.cpp | 2 +- src/mbgl/sprite/sprite_loader.cpp | 64 +++++++------- src/mbgl/sprite/sprite_loader.hpp | 8 +- src/mbgl/sprite/sprite_loader_observer.hpp | 5 +- src/mbgl/sprite/sprite_parser.cpp | 22 ++++- src/mbgl/sprite/sprite_parser.hpp | 4 +- src/mbgl/style/conversion/sprite.cpp | 34 ++++++++ src/mbgl/style/conversion/sprite.hpp | 21 +++++ src/mbgl/style/parser.cpp | 42 +++++++++- src/mbgl/style/parser.hpp | 5 +- src/mbgl/style/sprite.cpp | 14 ++++ src/mbgl/style/sprite.hpp | 18 ++++ src/mbgl/style/style_impl.cpp | 48 +++++++++-- src/mbgl/style/style_impl.hpp | 7 +- .../sprites-missing-fields.info.json | 8 ++ .../sprites-missing-fields.style.json | 4 + .../sprites-not-same-ids.info.json | 7 ++ .../sprites-not-same-ids.style.json | 11 +++ test/renderer/image_manager.test.cpp | 3 +- test/renderer/pattern_atlas.test.cpp | 3 +- test/sprite/sprite_loader.test.cpp | 8 +- test/sprite/sprite_parser.test.cpp | 26 +++--- test/style/style_parser.test.cpp | 57 +++++++++++++ 39 files changed, 649 insertions(+), 83 deletions(-) create mode 100644 metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json create mode 100644 metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json create mode 100644 metrics/integration/render-tests/sprites/array-default-only/expected.png create mode 100644 metrics/integration/render-tests/sprites/array-default-only/style.json create mode 100644 metrics/integration/render-tests/sprites/array-multiple/expected.png create mode 100644 metrics/integration/render-tests/sprites/array-multiple/style.json create mode 100644 metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json create mode 100644 metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json create mode 100644 src/mbgl/style/conversion/sprite.cpp create mode 100644 src/mbgl/style/conversion/sprite.hpp create mode 100644 src/mbgl/style/sprite.cpp create mode 100644 src/mbgl/style/sprite.hpp create mode 100644 test/fixtures/style_parser/sprites-missing-fields.info.json create mode 100644 test/fixtures/style_parser/sprites-missing-fields.style.json create mode 100644 test/fixtures/style_parser/sprites-not-same-ids.info.json create mode 100644 test/fixtures/style_parser/sprites-not-same-ids.style.json diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 6373c7008ed..2504e6b3707 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -96,7 +96,7 @@ jobs: - name: Build RenderTest .ipa and .xctest for AWS Device Farm run: | set -e - bazel run --//:renderer=metal //platform/ios:xcodeproj + bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" build_dir="$(mktemp -d)" xcodebuild build-for-testing -scheme RenderTest -project MapLibre.xcodeproj -derivedDataPath "$build_dir" render_test_app_dir="$(dirname "$(find "$build_dir" -name RenderTestApp.app)")" diff --git a/CMakeLists.txt b/CMakeLists.txt index eadad187612..6fa5197b0da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -700,6 +700,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/collection.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/color_ramp_property_value.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/constant.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/sprite.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/coordinate.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/custom_geometry_source_options.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/filter.cpp @@ -759,6 +760,8 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/within.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/filter.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/image.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/sprite.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/sprite.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/image_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/image_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layer.cpp diff --git a/bazel/core.bzl b/bazel/core.bzl index 6c22564d922..7fe2e6fcc77 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -396,6 +396,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/conversion/stringify.hpp", "src/mbgl/style/conversion/tileset.cpp", "src/mbgl/style/conversion/transition_options.cpp", + "src/mbgl/style/conversion/sprite.hpp", + "src/mbgl/style/conversion/sprite.cpp", "src/mbgl/style/custom_tile_loader.cpp", "src/mbgl/style/custom_tile_loader.hpp", "src/mbgl/style/expression/assertion.cpp", @@ -437,6 +439,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/expression/value.cpp", "src/mbgl/style/expression/within.cpp", "src/mbgl/style/filter.cpp", + "src/mbgl/style/sprite.hpp", + "src/mbgl/style/sprite.cpp", "src/mbgl/style/image.cpp", "src/mbgl/style/image_impl.cpp", "src/mbgl/style/image_impl.hpp", diff --git a/metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json b/metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json new file mode 100644 index 00000000000..28c44c7705c --- /dev/null +++ b/metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 2, + 35923 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 1, + 4, + 9, + 1, + [ + 22080, + 22080 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json b/metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json new file mode 100644 index 00000000000..45342fbc3d7 --- /dev/null +++ b/metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 4, + 247582 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 2, + 6, + 13, + 1, + [ + 28480, + 28480 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/metrics/integration/render-tests/sprites/array-default-only/expected.png b/metrics/integration/render-tests/sprites/array-default-only/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2c9a49d0ef5d3a30d9d7dbd38b64778512d46b27 GIT binary patch literal 1545 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 H-lYEmVElB5 literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/sprites/array-multiple/style.json b/metrics/integration/render-tests/sprites/array-multiple/style.json new file mode 100644 index 00000000000..79942a3daa1 --- /dev/null +++ b/metrics/integration/render-tests/sprites/array-multiple/style.json @@ -0,0 +1,79 @@ +{ + "version": 8, + "metadata": { + "test": { + "pixelRatio": 1, + "width": 128, + "height": 64 + } + }, + "center": [ + 0, + 0 + ], + "zoom": 0, + "sources": { + "source-one": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [25, 0] + } + } + ] + } + }, + "source-two": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [-25, 0] + } + } + ] + } + } + }, + "sprite": [ + { + "id": "default", + "url": "local://sprites/emerald" + }, + { + "id": "sprite", + "url": "local://sprites/sprite" + } + ], + "layers": [ + { + "id": "default-sprite-features", + "type": "symbol", + "source": "source-one", + "layout": { + "symbol-placement": "point", + "icon-image": "post_icon" + } + }, + { + "id": "sprite-sprite-features", + "type": "symbol", + "source": "source-two", + "layout": { + "symbol-placement": "point", + "icon-image": "sprite:night-lighthouse-12" + } + } + ] +} \ No newline at end of file diff --git a/metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json b/metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json new file mode 100644 index 00000000000..28c44c7705c --- /dev/null +++ b/metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 2, + 35923 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 1, + 4, + 9, + 1, + [ + 22080, + 22080 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json b/metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json new file mode 100644 index 00000000000..45342fbc3d7 --- /dev/null +++ b/metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 4, + 247582 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 2, + 6, + 13, + 1, + [ + 28480, + 28480 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index fa9a322392b..9f442041cd2 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -12,6 +12,7 @@ - Add support for the [`slice` expression](https://maplibre.org/maplibre-style-spec/expressions/#slice) ([#1113](https://github.com/maplibre/maplibre-native/pull/1133)) - Add support for the [`index-of` expression](https://maplibre.org/maplibre-style-spec/expressions/#index-of) ([#1113](https://github.com/maplibre/maplibre-native/pull/1113)) +- Add support for [multi sprites](https://github.com/maplibre/maplibre-native/pull/1858). More information on this feature can be found in the [Style Spec Documentation](https://maplibre.org/maplibre-style-spec/sprite/#multiple-sprite-sources). - Change to a more natural fling animation and allow setting `flingThreshold` and `flingAnimationBaseTime` in `UiSettings` ([#963](https://github.com/maplibre/maplibre-native/pull/963)) - 💥 Breaking: Change package of all classes from `com.mapbox.mapboxsdk` to `org.maplibre.android` ([#1201](https://github.com/maplibre/maplibre-native/pull/1201)). This means you will need to fix your imports. diff --git a/platform/default/src/mbgl/storage/offline_download.cpp b/platform/default/src/mbgl/storage/offline_download.cpp index d25f7019d8d..105535cef09 100644 --- a/platform/default/src/mbgl/storage/offline_download.cpp +++ b/platform/default/src/mbgl/storage/offline_download.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -223,8 +224,8 @@ OfflineRegionStatus OfflineDownload::getStatus() const { : NON_IDEOGRAPH_GLYPH_RANGES_PER_FONT_STACK); } - if (!parser.spriteURL.empty()) { - result->requiredResourceCount += 4; + if (!parser.sprites.empty()) { + result->requiredResourceCount += 4 * parser.sprites.size(); } return *result; @@ -246,7 +247,6 @@ void OfflineDownload::activateDownload() { parser.parse(*styleResponse.data); auto tileServerOptions = onlineFileSource.getResourceOptions().tileServerOptions(); - parser.spriteURL = util::mapbox::canonicalizeSpriteURL(tileServerOptions, parser.spriteURL); parser.glyphURL = util::mapbox::canonicalizeGlyphURL(tileServerOptions, parser.glyphURL); for (const auto& source : parser.sources) { @@ -343,12 +343,15 @@ void OfflineDownload::activateDownload() { } } - if (!parser.spriteURL.empty()) { + if (!parser.sprites.empty()) { // Always request 1x and @2x sprite images for portability. - queueResource(Resource::spriteImage(parser.spriteURL, 1)); - queueResource(Resource::spriteImage(parser.spriteURL, 2)); - queueResource(Resource::spriteJSON(parser.spriteURL, 1)); - queueResource(Resource::spriteJSON(parser.spriteURL, 2)); + for (const auto& sprite : parser.sprites) { + std::string spriteURL = util::mapbox::canonicalizeSpriteURL(tileServerOptions, sprite.spriteURL); + queueResource(Resource::spriteImage(spriteURL, 1)); + queueResource(Resource::spriteImage(spriteURL, 2)); + queueResource(Resource::spriteJSON(spriteURL, 1)); + queueResource(Resource::spriteJSON(spriteURL, 2)); + } } continueDownload(); diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 603710e0819..d966f0d6091 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,6 +4,10 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C ## main +## 6.2.0 + +- Add support for [multi sprites](https://github.com/maplibre/maplibre-native/pull/1858). More information on this feature can be found in the [Style Spec Documentation](https://maplibre.org/maplibre-style-spec/sprite/#multiple-sprite-sources). + ## 6.1.1 - Tighten camera equality requirements ([#2139](https://github.com/maplibre/maplibre-native/pull/2139)). diff --git a/platform/ios/VERSION b/platform/ios/VERSION index 132c6def58c..f3b5af39e43 100644 --- a/platform/ios/VERSION +++ b/platform/ios/VERSION @@ -1 +1 @@ -6.1.1 \ No newline at end of file +6.1.1 diff --git a/render-test/parser.cpp b/render-test/parser.cpp index 4bc96b9e035..bcc5527c420 100644 --- a/render-test/parser.cpp +++ b/render-test/parser.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mbgl/map/map_impl.cpp b/src/mbgl/map/map_impl.cpp index 789adc8e3b8..d9de1bb755e 100644 --- a/src/mbgl/map/map_impl.cpp +++ b/src/mbgl/map/map_impl.cpp @@ -55,7 +55,7 @@ void Map::Impl::onUpdate() { timePoint, transform.getState(), style->impl->getGlyphURL(), - style->impl->spriteLoaded, + style->impl->areSpritesLoaded(), style->impl->getTransitionOptions(), style->impl->getLight()->impl, style->impl->getImageImpls(), diff --git a/src/mbgl/sprite/sprite_loader.cpp b/src/mbgl/sprite/sprite_loader.cpp index 61f5fb47a07..ec2483f50b7 100644 --- a/src/mbgl/sprite/sprite_loader.cpp +++ b/src/mbgl/sprite/sprite_loader.cpp @@ -33,48 +33,56 @@ SpriteLoader::SpriteLoader(float pixelRatio_) SpriteLoader::~SpriteLoader() = default; -void SpriteLoader::load(const std::string& url, FileSource& fileSource) { - if (url.empty()) { +void SpriteLoader::load(const std::optional sprite, FileSource& fileSource) { + if (!sprite) { // Treat a non-existent sprite as a successfully loaded empty sprite. - observer->onSpriteLoaded({}); + observer->onSpriteLoaded(std::nullopt, {}); return; } - data = std::make_unique(); + std::string id = sprite->id; + std::string url = sprite->spriteURL; - data->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this](Response res) { + dataMap[id] = std::make_unique(); + dataMap[id]->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this, sprite](Response res) { + std::lock_guard lock(dataMapMutex); + Data* data = dataMap[sprite->id].get(); if (res.error) { - observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); + observer->onSpriteError(*sprite, std::make_exception_ptr(std::runtime_error(res.error->message))); } else if (res.notModified) { return; } else if (res.noContent) { data->json = std::make_shared(); - emitSpriteLoadedIfComplete(); + emitSpriteLoadedIfComplete(*sprite); } else { // Only trigger a sprite loaded event we got new data. assert(data->json != res.data); data->json = std::move(res.data); - emitSpriteLoadedIfComplete(); + emitSpriteLoadedIfComplete(*sprite); } }); - data->spriteRequest = fileSource.request(Resource::spriteImage(url, pixelRatio), [this](Response res) { - if (res.error) { - observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); - } else if (res.notModified) { - return; - } else if (res.noContent) { - data->image = std::make_shared(); - emitSpriteLoadedIfComplete(); - } else { - assert(data->image != res.data); - data->image = std::move(res.data); - emitSpriteLoadedIfComplete(); - } - }); + dataMap[id]->spriteRequest = fileSource.request( + Resource::spriteImage(url, pixelRatio), [this, sprite](Response res) { + std::lock_guard lock(dataMapMutex); + Data* data = dataMap[sprite->id].get(); + if (res.error) { + observer->onSpriteError(*sprite, std::make_exception_ptr(std::runtime_error(res.error->message))); + } else if (res.notModified) { + return; + } else if (res.noContent) { + data->image = std::make_shared(); + emitSpriteLoadedIfComplete(*sprite); + } else { + assert(dataMap[sprite->id]->image != res.data); + data->image = std::move(res.data); + emitSpriteLoadedIfComplete(*sprite); + } + }); } -void SpriteLoader::emitSpriteLoadedIfComplete() { +void SpriteLoader::emitSpriteLoadedIfComplete(style::Sprite sprite) { + Data* data = dataMap[sprite.id].get(); assert(data); if (!data->image || !data->json) { return; @@ -85,22 +93,22 @@ void SpriteLoader::emitSpriteLoadedIfComplete() { std::exception_ptr error; }; - auto parseClosure = [image = data->image, json = data->json]() -> ParseResult { + auto parseClosure = [sprite = sprite, image = data->image, json = data->json]() -> ParseResult { try { - return {parseSprite(*image, *json), nullptr}; + return {parseSprite(sprite.id, *image, *json), nullptr}; } catch (...) { return {{}, std::current_exception()}; } }; - auto resultClosure = [this, weak = weakFactory.makeWeakPtr()](ParseResult result) { + auto resultClosure = [this, sprite = sprite, weak = weakFactory.makeWeakPtr()](ParseResult result) { if (!weak) return; // This instance has been deleted. if (result.error) { - observer->onSpriteError(result.error); + observer->onSpriteError(std::optional(sprite), result.error); return; } - observer->onSpriteLoaded(std::move(result.images)); + observer->onSpriteLoaded(std::optional(sprite), std::move(result.images)); }; threadPool->scheduleAndReplyValue(parseClosure, resultClosure); diff --git a/src/mbgl/sprite/sprite_loader.hpp b/src/mbgl/sprite/sprite_loader.hpp index 373331e3b3e..2333d697f37 100644 --- a/src/mbgl/sprite/sprite_loader.hpp +++ b/src/mbgl/sprite/sprite_loader.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -21,12 +22,12 @@ class SpriteLoader { SpriteLoader(float pixelRatio); ~SpriteLoader(); - void load(const std::string& url, FileSource&); + void load(const std::optional sprite, FileSource&); void setObserver(SpriteLoaderObserver*); private: - void emitSpriteLoadedIfComplete(); + void emitSpriteLoadedIfComplete(style::Sprite sprite); // Invoked by SpriteAtlasWorker friend class SpriteLoaderWorker; @@ -34,7 +35,8 @@ class SpriteLoader { const float pixelRatio; struct Data; - std::unique_ptr data; + std::map> dataMap; + std::mutex dataMapMutex; SpriteLoaderObserver* observer = nullptr; std::shared_ptr threadPool; diff --git a/src/mbgl/sprite/sprite_loader_observer.hpp b/src/mbgl/sprite/sprite_loader_observer.hpp index f72f67cbbba..ad7257172d4 100644 --- a/src/mbgl/sprite/sprite_loader_observer.hpp +++ b/src/mbgl/sprite/sprite_loader_observer.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -17,9 +18,9 @@ class SpriteLoaderObserver { virtual ~SpriteLoaderObserver() = default; // NOLINTNEXTLINE(performance-unnecessary-value-param) - virtual void onSpriteLoaded(std::vector>) {} + virtual void onSpriteLoaded(std::optional, std::vector>) {} - virtual void onSpriteError(std::exception_ptr) {} + virtual void onSpriteError(std::optional, std::exception_ptr) {} }; } // namespace mbgl diff --git a/src/mbgl/sprite/sprite_parser.cpp b/src/mbgl/sprite/sprite_parser.cpp index 10629b4d53d..8c7768b5c8b 100644 --- a/src/mbgl/sprite/sprite_parser.cpp +++ b/src/mbgl/sprite/sprite_parser.cpp @@ -152,7 +152,9 @@ std::optional getContent(const JSValue& value, const char* } // namespace -std::vector> parseSprite(const std::string& encodedImage, const std::string& json) { +std::vector> parseSprite(const std::string& id, + const std::string& encodedImage, + const std::string& json) { const PremultipliedImage raster = decodeImage(encodedImage); JSDocument doc; @@ -170,6 +172,11 @@ std::vector> parseSprite(const std::string& encode images.reserve(properties.MemberCount()); for (const auto& property : properties) { const std::string name = {property.name.GetString(), property.name.GetStringLength()}; + std::string completeName = name; + if (id != "default") { + completeName = id + ":"; + completeName += name; + } const JSValue& value = property.value; if (value.IsObject()) { @@ -183,8 +190,17 @@ std::vector> parseSprite(const std::string& encode style::ImageStretches stretchY = getStretches(value, "stretchY", name.c_str()); std::optional content = getContent(value, "content", name.c_str()); - auto image = createStyleImage( - name, raster, x, y, width, height, pixelRatio, sdf, std::move(stretchX), std::move(stretchY), content); + auto image = createStyleImage(completeName, + raster, + x, + y, + width, + height, + pixelRatio, + sdf, + std::move(stretchX), + std::move(stretchY), + content); if (image) { images.push_back(std::move(image->baseImpl)); } diff --git a/src/mbgl/sprite/sprite_parser.hpp b/src/mbgl/sprite/sprite_parser.hpp index 54a0746cbcb..d08d19110cb 100644 --- a/src/mbgl/sprite/sprite_parser.hpp +++ b/src/mbgl/sprite/sprite_parser.hpp @@ -20,6 +20,8 @@ std::unique_ptr createStyleImage(const std::string& id, const std::optional& content = std::nullopt); // Parses an image and an associated JSON file and returns the sprite objects. -std::vector> parseSprite(const std::string& image, const std::string& json); +std::vector> parseSprite(const std::string& id, + const std::string& image, + const std::string& json); } // namespace mbgl diff --git a/src/mbgl/style/conversion/sprite.cpp b/src/mbgl/style/conversion/sprite.cpp new file mode 100644 index 00000000000..60ae104b3a6 --- /dev/null +++ b/src/mbgl/style/conversion/sprite.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +#include + +namespace mbgl { +namespace style { +namespace conversion { + +std::optional Converter::operator()(const Convertible& value, Error& error) const { + std::optional id; + auto idValue = objectMember(value, "id"); + if (!idValue) { + error.message = "id must be defined for sprite object"; + return std::nullopt; + } + id = toString(*idValue); + + std::optional spriteURL; + auto urlValue = objectMember(value, "url"); + if (!urlValue) { + error.message = "url must be defined for sprite object"; + return std::nullopt; + } + spriteURL = toString(*urlValue); + + const Sprite sprite(*id, *spriteURL); + return sprite; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/sprite.hpp b/src/mbgl/style/conversion/sprite.hpp new file mode 100644 index 00000000000..3acea30e1dd --- /dev/null +++ b/src/mbgl/style/conversion/sprite.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter { +public: + std::optional operator()(const Convertible& value, Error& error) const; +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index 461cedc7b91..c57de6ba1cc 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -17,7 +18,9 @@ #include #include +#include #include +#include namespace mbgl { namespace style { @@ -103,10 +106,7 @@ StyleParseResult Parser::parse(const std::string& json) { } if (document.HasMember("sprite")) { - const JSValue& sprite = document["sprite"]; - if (sprite.IsString()) { - spriteURL = {sprite.GetString(), sprite.GetStringLength()}; - } + parseSprites(document["sprite"]); } if (document.HasMember("glyphs")) { @@ -165,6 +165,40 @@ void Parser::parseSources(const JSValue& value) { } } +void Parser::parseSprites(const JSValue& value) { + if (value.IsString()) { + std::string url = {value.GetString(), value.GetStringLength()}; + auto sprite = Sprite("default", url); + sprites.emplace_back(sprite); + } else if (value.IsArray()) { + std::unordered_set spriteIds; + for (auto& spriteValue : value.GetArray()) { + if (!spriteValue.IsObject()) { + Log::Warning(Event::ParseStyle, "sprite child must be an object"); + continue; + } + + conversion::Error error; + std::optional sprite = conversion::convert(spriteValue, error); + if (!sprite) { + Log::Warning(Event::ParseStyle, error.message); + continue; + } + + if (spriteIds.find(sprite->id) != spriteIds.end()) { + Log::Warning(Event::ParseStyle, "sprite ids must be unique"); + continue; + } + + spriteIds.insert(sprite->id); + sprites.emplace_back(*sprite); + } + } else { + Log::Warning(Event::ParseStyle, "sprite must be an object or string"); + return; + } +} + void Parser::parseLayers(const JSValue& value) { std::vector ids; diff --git a/src/mbgl/style/parser.hpp b/src/mbgl/style/parser.hpp index ab9d2df7387..6d5e8794898 100644 --- a/src/mbgl/style/parser.hpp +++ b/src/mbgl/style/parser.hpp @@ -1,9 +1,9 @@ #pragma once #include +#include #include #include - #include #include #include @@ -27,7 +27,7 @@ class Parser { StyleParseResult parse(const std::string&); - std::string spriteURL; + std::vector sprites; std::string glyphURL; std::vector> sources; @@ -49,6 +49,7 @@ class Parser { void parseTransition(const JSValue&); void parseLight(const JSValue&); void parseSources(const JSValue&); + void parseSprites(const JSValue&); void parseLayers(const JSValue&); void parseLayer(const std::string& id, const JSValue&, std::unique_ptr&); diff --git a/src/mbgl/style/sprite.cpp b/src/mbgl/style/sprite.cpp new file mode 100644 index 00000000000..bc0e42ba304 --- /dev/null +++ b/src/mbgl/style/sprite.cpp @@ -0,0 +1,14 @@ +#include + +namespace mbgl { +namespace style { + +Sprite::~Sprite() = default; + +Sprite::Sprite(std::string id_, std::string spriteURL_) { + this->id = id_; + this->spriteURL = spriteURL_; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sprite.hpp b/src/mbgl/style/sprite.hpp new file mode 100644 index 00000000000..c08f1e23ef3 --- /dev/null +++ b/src/mbgl/style/sprite.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace mbgl { +namespace style { + +class Sprite { +public: + Sprite(std::string, std::string); + ~Sprite(); + + std::string id; + std::string spriteURL; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 1b9b8e798f7..4b2119ba0c3 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -118,11 +118,22 @@ void Style::Impl::parse(const std::string& json_) { setLight(std::make_unique(parser.light)); - spriteLoaded = false; if (fileSource) { - spriteLoader->load(parser.spriteURL, *fileSource); + if (parser.sprites.empty()) { + // We identify no sprite with 'default' as string in the sprite loading status. + spritesLoadingStatus["default"] = false; + spriteLoader->load(std::nullopt, *fileSource); + } else { + for (const auto& sprite : parser.sprites) { + spritesLoadingStatus[sprite.id] = false; + spriteLoader->load(std::optional(sprite), *fileSource); + } + } } else { - onSpriteError(std::make_exception_ptr(std::runtime_error("Unable to find resource provider for sprite url."))); + // We identify no sprite with 'default' as string in the sprite loading status. + spritesLoadingStatus["default"] = false; + onSpriteError(std::nullopt, + std::make_exception_ptr(std::runtime_error("Unable to find resource provider for sprite url."))); } glyphURL = parser.glyphURL; @@ -255,12 +266,24 @@ Source* Style::Impl::getSource(const std::string& id) const { return sources.get(id); } +bool Style::Impl::areSpritesLoaded() const { + if (spritesLoadingStatus.empty()) { + return false; // If nothing is stored inside, sprites are not yet loaded. + } + for (const auto& entry : spritesLoadingStatus) { + if (!entry.second) { + return false; + } + } + return true; +} + bool Style::Impl::isLoaded() const { if (!loaded) { return false; } - if (!spriteLoaded) { + if (!areSpritesLoaded()) { return false; } @@ -338,7 +361,8 @@ void Style::Impl::onSourceDescriptionChanged(Source& source) { } } -void Style::Impl::onSpriteLoaded(std::vector> images_) { +void Style::Impl::onSpriteLoaded(std::optional sprite, + std::vector> images_) { auto newImages = makeMutable(*images); assert(std::is_sorted(newImages->begin(), newImages->end())); @@ -358,16 +382,24 @@ void Style::Impl::onSpriteLoaded(std::vector> imag newImages->end(), std::make_move_iterator(images_.begin()), std::make_move_iterator(images_.end())); std::sort(newImages->begin(), newImages->end()); images = std::move(newImages); - spriteLoaded = true; + if (sprite) { + spritesLoadingStatus[sprite->id] = true; + } else { + spritesLoadingStatus["default"] = true; + } observer->onUpdate(); // For *-pattern properties. } -void Style::Impl::onSpriteError(std::exception_ptr error) { +void Style::Impl::onSpriteError(std::optional sprite, std::exception_ptr error) { lastError = error; Log::Error(Event::Style, "Failed to load sprite: " + util::toString(error)); observer->onResourceError(error); + if (sprite) { + spritesLoadingStatus[sprite->id] = true; + } else { + spritesLoadingStatus["default"] = false; + } // Unblock rendering tiles (even though sprite request has failed). - spriteLoaded = true; observer->onUpdate(); } diff --git a/src/mbgl/style/style_impl.hpp b/src/mbgl/style/style_impl.hpp index ed943e24054..63d6b54c1cd 100644 --- a/src/mbgl/style/style_impl.hpp +++ b/src/mbgl/style/style_impl.hpp @@ -86,10 +86,10 @@ class Style::Impl : public SpriteLoaderObserver, Immutable>> getLayerImpls() const; void dumpDebugLogs() const; + bool areSpritesLoaded() const; bool mutated = false; bool loaded = false; - bool spriteLoaded = false; private: void parse(const std::string&); @@ -108,14 +108,15 @@ class Style::Impl : public SpriteLoaderObserver, Collection layers; TransitionOptions transitionOptions; std::unique_ptr light; + std::unordered_map spritesLoadingStatus; // Defaults std::string name; CameraOptions defaultCamera; // SpriteLoaderObserver implementation. - void onSpriteLoaded(std::vector>) override; - void onSpriteError(std::exception_ptr) override; + void onSpriteLoaded(std::optional sprite, std::vector>) override; + void onSpriteError(std::optional sprite, std::exception_ptr) override; // SourceObserver implementation. void onSourceLoaded(Source&) override; diff --git a/test/fixtures/style_parser/sprites-missing-fields.info.json b/test/fixtures/style_parser/sprites-missing-fields.info.json new file mode 100644 index 00000000000..96fb44b3251 --- /dev/null +++ b/test/fixtures/style_parser/sprites-missing-fields.info.json @@ -0,0 +1,8 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "id must be defined for sprite object"], + [1, "WARNING", "ParseStyle", "url must be defined for sprite object"] + ] + } +} diff --git a/test/fixtures/style_parser/sprites-missing-fields.style.json b/test/fixtures/style_parser/sprites-missing-fields.style.json new file mode 100644 index 00000000000..0c813b8cb35 --- /dev/null +++ b/test/fixtures/style_parser/sprites-missing-fields.style.json @@ -0,0 +1,4 @@ +{ + "version": 8, + "sprite": [{"id": "default"},{"url": "https://example.com"}] +} diff --git a/test/fixtures/style_parser/sprites-not-same-ids.info.json b/test/fixtures/style_parser/sprites-not-same-ids.info.json new file mode 100644 index 00000000000..7f07d5196a2 --- /dev/null +++ b/test/fixtures/style_parser/sprites-not-same-ids.info.json @@ -0,0 +1,7 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "sprite ids must be unique"] + ] + } +} diff --git a/test/fixtures/style_parser/sprites-not-same-ids.style.json b/test/fixtures/style_parser/sprites-not-same-ids.style.json new file mode 100644 index 00000000000..75a54eba1d1 --- /dev/null +++ b/test/fixtures/style_parser/sprites-not-same-ids.style.json @@ -0,0 +1,11 @@ +{ + "version": 8, + "sprite": [{ + "id": "default", + "url": "https://example.com" + }, + { + "id": "default", + "url": "https://example2.com" + }] +} diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp index fb359bd7773..7eafee86a86 100644 --- a/test/renderer/image_manager.test.cpp +++ b/test/renderer/image_manager.test.cpp @@ -24,7 +24,8 @@ TEST(ImageManager, Basic) { FixtureLog log; ImageManager imageManager; - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + auto images = parseSprite("default", + util::read_file("test/fixtures/annotations/emerald.png"), util::read_file("test/fixtures/annotations/emerald.json")); for (auto& image : images) { imageManager.addImage(image); diff --git a/test/renderer/pattern_atlas.test.cpp b/test/renderer/pattern_atlas.test.cpp index 0c170175368..399b4ecea7d 100644 --- a/test/renderer/pattern_atlas.test.cpp +++ b/test/renderer/pattern_atlas.test.cpp @@ -18,7 +18,8 @@ TEST(PatternAtlas, Basic) { FixtureLog log; PatternAtlas patternAtlas; - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + auto images = parseSprite("default", + util::read_file("test/fixtures/annotations/emerald.png"), util::read_file("test/fixtures/annotations/emerald.json")); for (auto& image : images) { if (image->id == "metro") { diff --git a/test/sprite/sprite_loader.test.cpp b/test/sprite/sprite_loader.test.cpp index 8b5ad25d9cf..75eaa6c98ec 100644 --- a/test/sprite/sprite_loader.test.cpp +++ b/test/sprite/sprite_loader.test.cpp @@ -17,11 +17,11 @@ using namespace mbgl::style; class StubSpriteLoaderObserver : public SpriteLoaderObserver { public: - void onSpriteLoaded(std::vector> images) override { + void onSpriteLoaded(std::optional, std::vector> images) override { if (spriteLoaded) spriteLoaded(std::move(images)); } - void onSpriteError(std::exception_ptr error) override { + void onSpriteError(std::optional, std::exception_ptr error) override { if (spriteError) spriteError(error); } @@ -43,7 +43,9 @@ class SpriteLoaderTest { Log::setObserver(std::make_unique()); spriteLoader.setObserver(&observer); - spriteLoader.load("test/fixtures/resources/sprite", fileSource); + + Sprite sprite = Sprite("default", "test/fixtures/resources/sprite"); + spriteLoader.load(sprite, fileSource); loop.run(); } diff --git a/test/sprite/sprite_parser.test.cpp b/test/sprite/sprite_parser.test.cpp index 3599c564cb7..57c1026d4c9 100644 --- a/test/sprite/sprite_parser.test.cpp +++ b/test/sprite/sprite_parser.test.cpp @@ -256,7 +256,7 @@ TEST(Sprite, SpriteParsing) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = util::read_file("test/fixtures/annotations/emerald.json"); - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); std::set names; std::transform( @@ -352,7 +352,7 @@ TEST(Sprite, SpriteParsingInvalidJSON) { const auto json_1x = R"JSON({ "image": " })JSON"; try { - parseSprite(image_1x, json_1x); + parseSprite("default", image_1x, json_1x); FAIL() << "Expected exception"; } catch (std::runtime_error& err) { EXPECT_STREQ( @@ -367,7 +367,7 @@ TEST(Sprite, SpriteParsingInvalidStretches) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -385,7 +385,7 @@ TEST(Sprite, SpriteParsingInvalidStretches) { "an array"})); EXPECT_EQ(0u, log.uncheckedCount()); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -403,7 +403,7 @@ TEST(Sprite, SpriteParsingInvalidStretches) { "be an array of two numbers"})); EXPECT_EQ(0u, log.uncheckedCount()); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -427,7 +427,7 @@ TEST(Sprite, SpriteParsingInvalidContent) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -445,7 +445,7 @@ TEST(Sprite, SpriteParsingInvalidContent) { "an array of four numbers"})); EXPECT_EQ(0u, log.uncheckedCount()); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -469,7 +469,7 @@ TEST(Sprite, SpriteParsingStretchAndContent) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); - const auto images = parseSprite(image_1x, R"JSON({ + const auto images = parseSprite("default", image_1x, R"JSON({ "image": { "width": 16, "height": 16, @@ -494,7 +494,7 @@ TEST(Sprite, SpriteParsingEmptyImage) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": {} })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, @@ -513,7 +513,7 @@ TEST(Sprite, SpriteParsingSimpleWidthHeight) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": 32, "height": 32 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(1u, images.size()); } @@ -523,7 +523,7 @@ TEST(Sprite, SpriteParsingWidthTooBig) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": 65536, "height": 32 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, @@ -550,7 +550,7 @@ TEST(Sprite, SpriteParsingNegativeWidth) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": -1, "height": 32 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, @@ -577,7 +577,7 @@ TEST(Sprite, SpriteParsingNullRatio) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": 32, "height": 32, "pixelRatio": 0 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, diff --git a/test/style/style_parser.test.cpp b/test/style/style_parser.test.cpp index 43b04148f56..8b44a70ebbb 100644 --- a/test/style/style_parser.test.cpp +++ b/test/style/style_parser.test.cpp @@ -126,6 +126,63 @@ INSTANTIATE_TEST_SUITE_P(StyleParser, StyleParserTest, ::testing::ValuesIn([] { return names; }())); +TEST(StyleParser, SpriteAsString) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": "https://example.com/default/markers" + })"); + auto result = &parser.sprites; + ASSERT_EQ(1, result->size()); + ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); + ASSERT_EQ("default", result->at(0).id); +} + +TEST(StyleParser, SpriteAsArrayEmpty) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": [] + })"); + auto result = &parser.sprites; + ASSERT_EQ(0, result->size()); +} + +TEST(StyleParser, SpriteAsArraySingle) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": [{ + "id": "default", + "url": "https://example.com/default/markers" + }] + })"); + auto result = &parser.sprites; + ASSERT_EQ(1, result->size()); + ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); + ASSERT_EQ("default", result->at(0).id); +} + +TEST(StyleParser, SpriteAsArrayMultiple) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": [{ + "id": "default", + "url": "https://example.com/default/markers" + },{ + "id": "hiking", + "url": "https://example.com/hiking/markers" + }] + })"); + auto result = &parser.sprites; + ASSERT_EQ(2, result->size()); + ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); + ASSERT_EQ("default", result->at(0).id); + ASSERT_EQ("https://example.com/hiking/markers", result->at(1).spriteURL); + ASSERT_EQ("hiking", result->at(1).id); +} + TEST(StyleParser, FontStacks) { style::Parser parser; parser.parse(util::read_file("test/fixtures/style_parser/font_stacks.json")); From f27848fdaa8bbf0d55201353702457984fb03d10 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Tue, 27 Feb 2024 19:28:05 +0100 Subject: [PATCH 91/96] Always create check for Android render test, Take into account cancelled workflows (#2149) --- .github/workflows/android-device-test.yml | 34 ++++++++--------------- .github/workflows/pr-bloaty-ios.yml | 3 +- .github/workflows/pr-linux-tests.yml | 3 +- .github/workflows/upload-coverage.yml | 2 +- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index e7e9c881623..4c2f003afa0 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -7,21 +7,7 @@ on: - completed jobs: - pre_job: - runs-on: ubuntu-latest - outputs: - should_skip: ${{ steps.check_skip.outputs.should_skip }} - steps: - # if android-build in android-ci was skipped, the android-device-test job can be skipped entirely - - id: check_skip - run: | - conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "android-build").conclusion') - should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") - echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" - android-device-test: - needs: pre_job - if: needs.pre_job.outputs.should_skip == 'false' strategy: max-parallel: 2 matrix: @@ -51,11 +37,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Sanity check, test files should exist - uses: andstor/file-existence-action@v2.0.0 - with: - files: "${{ matrix.test.testFile }}, ${{ matrix.test.appFile }}" - fail: true + - id: parent_workflow + run: | + conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "android-build").conclusion') + was_skipped=$([[ "$conclusion" = "skipped" || "$conclusion" = "cancelled" ]] && echo "true" || echo "false") + echo "was_skipped=$was_skipped" >> "$GITHUB_OUTPUT" # get comment from PR @@ -82,14 +68,15 @@ jobs: # always run when something was merged into main # run benchmark when comment with '!benchmark android' exists in PR if: | - (github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.event == 'push') || + steps.parent_workflow.outputs.was_skipped == 'false' && + ((github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.event == 'push') || matrix.test.name == 'Android Benchmark' && steps.benchmark_comment.outputs.comment-id || - matrix.test.name != 'Android Benchmark' + matrix.test.name != 'Android Benchmark') run: echo "run_device_test=true" >> "$GITHUB_ENV" - uses: LouisBrunner/checks-action@v2.0.0 - if: env.run_device_test == 'true' + if: env.run_device_test == 'true' || matrix.test.name == 'Android Render Tests' id: create_check with: token: ${{ steps.generate_token.outputs.token }} @@ -102,6 +89,7 @@ jobs: if: env.run_device_test == 'true' with: artifact-name: ${{ matrix.test.artifactName }} + expect-files: "${{ matrix.test.testFile }}, ${{ matrix.test.appFile }}" - name: Configure AWS Credentials if: env.run_device_test == 'true' && matrix.test.name == 'Android Benchmark' @@ -141,7 +129,7 @@ jobs: testSpecArn: ${{ matrix.test.testSpecArn }} - uses: LouisBrunner/checks-action@v2.0.0 - if: always() && env.run_device_test == 'true' + if: always() && (env.run_device_test == 'true' || matrix.test.name == 'Android Render Tests') with: token: ${{ steps.generate_token.outputs.token }} check_id: ${{ steps.create_check.outputs.check_id }} diff --git a/.github/workflows/pr-bloaty-ios.yml b/.github/workflows/pr-bloaty-ios.yml index ec731132fda..31d7ff6c065 100644 --- a/.github/workflows/pr-bloaty-ios.yml +++ b/.github/workflows/pr-bloaty-ios.yml @@ -19,11 +19,10 @@ jobs: outputs: should_skip: ${{ steps.check_skip.outputs.should_skip }} steps: - # if ios-build in ios-ci was skipped, the rest of the workflow can be skipped - id: check_skip run: | conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "ios-build").conclusion') - should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") + should_skip=$([[ "$conclusion" = "skipped" || "$conclusion" = "cancelled" ]] && echo "true" || echo "false") echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" pr-bloaty-ios: diff --git a/.github/workflows/pr-linux-tests.yml b/.github/workflows/pr-linux-tests.yml index 25610ce8a57..e65e4824dd2 100644 --- a/.github/workflows/pr-linux-tests.yml +++ b/.github/workflows/pr-linux-tests.yml @@ -22,11 +22,10 @@ jobs: outputs: should_skip: ${{ steps.check_skip.outputs.should_skip }} steps: - # if linux-build-and-test in linux-ci was skipped, the rest of the workflow can be skipped - id: check_skip run: | conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "linux-build-and-test").conclusion') - should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") + should_skip=$([[ "$conclusion" = "skipped" || "$conclusion" = "cancelled" ]] && echo "true" || echo "false") echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" pr-bloaty: diff --git a/.github/workflows/upload-coverage.yml b/.github/workflows/upload-coverage.yml index 046b2fdb843..7f884eeb4e4 100644 --- a/.github/workflows/upload-coverage.yml +++ b/.github/workflows/upload-coverage.yml @@ -15,7 +15,7 @@ jobs: - id: check_skip run: | conclusion=$(curl ${{ github.event.workflow_run.jobs_url }} | jq -r '.jobs[] | select(.name == "linux-coverage").conclusion') - should_skip=$([ "$conclusion" = "skipped" ] && echo "true" || echo "false") + should_skip=$([[ "$conclusion" = "skipped" || "$conclusion" = "cancelled" ]] && echo "true" || echo "false") echo "should_skip=$should_skip" >> "$GITHUB_OUTPUT" upload-coverage: From 003c062c2531a32424afdcb56254a5c192906120 Mon Sep 17 00:00:00 2001 From: mwilsnd <53413200+mwilsnd@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:11:33 -0500 Subject: [PATCH 92/96] Add optional split output location to codegen scripts (#2152) --- .../darwin/scripts/generate-style-code.js | 33 ++++++++++--------- scripts/generate-style-code.js | 19 ++++++----- shaders/generate_shader_code.js | 13 ++++---- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index fad212c1667..a926089c30b 100644 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -15,8 +15,8 @@ const args = (() => { const parser = new ArgumentParser({ description: "MapLibre Shader Tools" }); - parser.add_argument("--root", "--r", { - help: "Directory root to place generated code", + parser.add_argument("--out", "--o", { + help: "Directory root to write generated code.", required: false }); return parser.parse_args(); @@ -787,7 +787,8 @@ const lightProperties = Object.keys(spec['light']).reduce((memo, name) => { const lightDoc = spec['light-cocoa-doc']; const lightType = 'light'; -const root = args.root ? args.root : path.dirname(path.dirname(path.dirname(__dirname))); +const root = path.dirname(path.dirname(path.dirname(__dirname))); +const outLocation = args.out ? args.out : root; const layerH = readAndCompile('platform/darwin/src/MLNStyleLayer.h.ejs', root); const layerPrivateH = readAndCompile('platform/darwin/src/MLNStyleLayer_Private.h.ejs', root); @@ -801,11 +802,11 @@ const lightH = readAndCompile('platform/darwin/src/MLNLight.h.ejs', root); const lightM = readAndCompile('platform/darwin/src/MLNLight.mm.ejs', root); const testLight = readAndCompile('platform/darwin/test/MLNLightTest.mm.ejs', root); writeIfModified(`platform/darwin/src/MLNLight.h`, duplicatePlatformDecls( - lightH({ properties: lightProperties, doc: lightDoc, type: lightType })), root); + lightH({ properties: lightProperties, doc: lightDoc, type: lightType })), outLocation); writeIfModified(`platform/darwin/src/MLNLight.mm`, - lightM({ properties: lightProperties, doc: lightDoc, type: lightType }), root); + lightM({ properties: lightProperties, doc: lightDoc, type: lightType }), outLocation); writeIfModified(`platform/darwin/test/MLNLightTest.mm`, - testLight({ properties: lightProperties, doc: lightDoc, type: lightType }), root); + testLight({ properties: lightProperties, doc: lightDoc, type: lightType }), outLocation); const layers = _(spec.layer.type.values).map((value, layerType) => { const layoutProperties = Object.keys(spec[`layout_${layerType}`]).reduce((memo, name) => { @@ -879,13 +880,13 @@ for (var layer of layers) { } writeIfModified(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.h`, - duplicatePlatformDecls(layerH(layer)), root); + duplicatePlatformDecls(layerH(layer)), outLocation); writeIfModified(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}_Private.h`, - duplicatePlatformDecls(layerPrivateH(layer)), root); + duplicatePlatformDecls(layerPrivateH(layer)), outLocation); writeIfModified(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.mm`, - layerM(layer), root); + layerM(layer), outLocation); writeIfModified(`platform/darwin/test/${prefix}${camelize(layer.type)}${suffix}Tests.mm`, - testLayers(layer), root); + testLayers(layer), outLocation); } // Extract examples for guides from unit tests. @@ -927,21 +928,21 @@ writeIfModified(`platform/ios/docs/guides/For Style Authors.md`, forStyleAuthors os: 'iOS', renamedProperties: renamedPropertiesByLayerType, layers: layers, -}), root); +}), outLocation); writeIfModified(`platform/macos/docs/guides/For Style Authors.md`, forStyleAuthorsMD({ os: 'macOS', renamedProperties: renamedPropertiesByLayerType, layers: layers, -}), root); +}), outLocation); writeIfModified(`platform/ios/docs/guides/Migrating to Expressions.md`, ddsGuideMD({ os: 'iOS', -}), root); +}), outLocation); writeIfModified(`platform/macos/docs/guides/Migrating to Expressions.md`, ddsGuideMD({ os: 'macOS', -}), root); +}), outLocation); writeIfModified(`platform/ios/docs/guides/Tile URL Templates.md`, templatesMD({ os: 'iOS', -}), root); +}), outLocation); writeIfModified(`platform/macos/docs/guides/Tile URL Templates.md`, templatesMD({ os: 'macOS', -}), root);*/ +}), rooutLocationot);*/ diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index 3c9f67351c9..a1c431448d6 100755 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -14,8 +14,8 @@ const args = (() => { const parser = new ArgumentParser({ description: "MapLibre Shader Tools" }); - parser.add_argument("--root", "--r", { - help: "Directory root to place generated code", + parser.add_argument("--out", "--o", { + help: "Directory root to write generated code.", required: false }); return parser.parse_args(); @@ -222,7 +222,8 @@ global.defaultValue = function (property) { }; console.log("Generating style code..."); -const root = args.root ? args.root : path.dirname(__dirname); +const root = path.dirname(__dirname); +const outLocation = args.out ? args.out : root; const layerHpp = readAndCompile(`include/mbgl/style/layers/layer.hpp.ejs`, root); const layerCpp = readAndCompile(`src/mbgl/style/layers/layer.cpp.ejs`, root); @@ -275,16 +276,16 @@ const layers = Object.keys(spec.layer.type.values).map((type) => { for (const layer of layers) { const layerFileName = layer.type.replace('-', '_'); - writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.hpp`, propertiesHpp(layer), root); - writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.cpp`, propertiesCpp(layer), root); + writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.hpp`, propertiesHpp(layer), outLocation); + writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer_properties.cpp`, propertiesCpp(layer), outLocation); // Remove our fake property for the external interace. if (layer.type === 'line') { layer.paintProperties = layer.paintProperties.filter(property => property.name !== 'line-floor-width'); } - writeIfModified(`include/mbgl/style/layers/${layerFileName}_layer.hpp`, layerHpp(layer), root); - writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer.cpp`, layerCpp(layer), root); + writeIfModified(`include/mbgl/style/layers/${layerFileName}_layer.hpp`, layerHpp(layer), outLocation); + writeIfModified(`src/mbgl/style/layers/${layerFileName}_layer.cpp`, layerCpp(layer), outLocation); } // Light @@ -302,5 +303,5 @@ lightProperties.sort((a, b) => collator.compare(a.name, b.name)); const lightHpp = readAndCompile(`include/mbgl/style/light.hpp.ejs`, root); const lightCpp = readAndCompile(`src/mbgl/style/light.cpp.ejs`, root); -writeIfModified(`include/mbgl/style/light.hpp`, lightHpp({properties: lightProperties}), root); -writeIfModified(`src/mbgl/style/light.cpp`, lightCpp({properties: lightProperties}), root); +writeIfModified(`include/mbgl/style/light.hpp`, lightHpp({properties: lightProperties}), outLocation); +writeIfModified(`src/mbgl/style/light.cpp`, lightCpp({properties: lightProperties}), outLocation); diff --git a/shaders/generate_shader_code.js b/shaders/generate_shader_code.js index 3b7d3914ba8..ba15163a6b0 100644 --- a/shaders/generate_shader_code.js +++ b/shaders/generate_shader_code.js @@ -62,7 +62,7 @@ ${precision} ${type} ${name} = u_${name}; return source.replace(re, (match, operation, precision, type, name) => { const attrType = type === 'float' ? 'vec2' : 'vec4'; const unpackType = name.match(/color/) ? 'color' : attrType; - + if (pragmaMap[name]) { if (operation === 'define') { return `#ifndef HAS_UNIFORM_u_${name} @@ -140,7 +140,7 @@ ${precision} ${type} ${name} = u_${name}; return source.replace(re, (match, operation, precision, type, name) => { const attrType = type === 'float' ? 'vec2' : 'vec4'; const unpackType = name.match(/color/) ? 'color' : attrType; - + if (pragmaMap[name]) { if (operation === 'define') { return `#ifndef HAS_UNIFORM_u_${name} @@ -202,8 +202,8 @@ const args = (() => { const parser = new ArgumentParser({ description: "MapLibre Shader Tools" }); - parser.add_argument("--root", "--r", { - help: "Directory root to place generated code", + parser.add_argument("--out", "--o", { + help: "Directory root to write generated code.", required: false }); parser.add_argument("--compress", "--c", { @@ -220,9 +220,10 @@ const args = (() => { // Generate shader source headers -const root = args.root ? args.root : path.dirname(__dirname); +const root = path.dirname(__dirname); +const outLocation = args.out ? args.out : root; const shaderRoot = path.join(root, "shaders"); -const outputRoot = path.join(root, "include/mbgl/shaders"); +const outputRoot = path.join(outLocation, "include/mbgl/shaders"); let generatedHeaders = []; let shaderNames = []; From 649f4a5630dfc718f04c80fc6be39e072227d4cc Mon Sep 17 00:00:00 2001 From: Stefan Karschti Date: Wed, 28 Feb 2024 19:16:03 +0200 Subject: [PATCH 93/96] Fix for C++ header in public Objective-C header (#2156) --- platform/darwin/app/ExampleCustomDrawableStyleLayer.mm | 4 ++++ platform/darwin/src/MLNCustomDrawableStyleLayer.h | 9 --------- platform/darwin/src/MLNCustomDrawableStyleLayer.mm | 6 ++++++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm index 49f0e341226..62cde00f0f6 100644 --- a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm +++ b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm @@ -13,6 +13,10 @@ #include #include +@interface MLNCustomDrawableStyleLayer (Internal) +- (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer; +@end + class ExampleCustomDrawableStyleLayerHost; @implementation ExampleCustomDrawableStyleLayer diff --git a/platform/darwin/src/MLNCustomDrawableStyleLayer.h b/platform/darwin/src/MLNCustomDrawableStyleLayer.h index b32d63a462e..df671349cb2 100644 --- a/platform/darwin/src/MLNCustomDrawableStyleLayer.h +++ b/platform/darwin/src/MLNCustomDrawableStyleLayer.h @@ -6,15 +6,6 @@ #import "MLNStyleLayer.h" #import "MLNGeometry.h" -#ifdef __cplusplus -#include -#endif - MLN_EXPORT @interface MLNCustomDrawableStyleLayer : MLNStyleLayer - -#ifdef __cplusplus -- (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer; -#endif - @end diff --git a/platform/darwin/src/MLNCustomDrawableStyleLayer.mm b/platform/darwin/src/MLNCustomDrawableStyleLayer.mm index b8ddaae46aa..f91fd019023 100644 --- a/platform/darwin/src/MLNCustomDrawableStyleLayer.mm +++ b/platform/darwin/src/MLNCustomDrawableStyleLayer.mm @@ -12,6 +12,12 @@ #include #include +#include + +@interface MLNCustomDrawableStyleLayer (Internal) +- (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer; +@end + @implementation MLNCustomDrawableStyleLayer - (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer { From 0b97ae25e10528458096e23d2232613c75f4fe2a Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 28 Feb 2024 18:50:38 +0100 Subject: [PATCH 94/96] Fix Android release script (#2157) --- platform/android/gradle/gradle-publish.gradle | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/platform/android/gradle/gradle-publish.gradle b/platform/android/gradle/gradle-publish.gradle index 0c915ac923e..aa934e939e6 100644 --- a/platform/android/gradle/gradle-publish.gradle +++ b/platform/android/gradle/gradle-publish.gradle @@ -44,10 +44,7 @@ afterEvaluate { artifactId project.ext.mapLibreArtifactId version this.version - from components.drawableRelease - - artifact androidSourcesJar - artifact androidJavadocsJar + from components.drawableRelease pom { name = project.ext.mapLibreArtifactTitle From e3cbdb63a8dc29efe244b54607ae599d0db8beaa Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Wed, 28 Feb 2024 20:28:00 +0100 Subject: [PATCH 95/96] Release MapLibre Native iOS 6.2.0 (#2158) --- platform/ios/CHANGELOG.md | 1 + platform/ios/VERSION | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index d966f0d6091..a391a194f8c 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -7,6 +7,7 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C ## 6.2.0 - Add support for [multi sprites](https://github.com/maplibre/maplibre-native/pull/1858). More information on this feature can be found in the [Style Spec Documentation](https://maplibre.org/maplibre-style-spec/sprite/#multiple-sprite-sources). +- Fix for C++ header in public Objective-C header ([#2156](https://github.com/maplibre/maplibre-native/pull/q 56)). ## 6.1.1 diff --git a/platform/ios/VERSION b/platform/ios/VERSION index f3b5af39e43..4ac4fded49f 100644 --- a/platform/ios/VERSION +++ b/platform/ios/VERSION @@ -1 +1 @@ -6.1.1 +6.2.0 \ No newline at end of file From 4fff8a5d33852c7bc4c5d8c989092eb42769a71d Mon Sep 17 00:00:00 2001 From: mwilsnd <53413200+mwilsnd@users.noreply.github.com> Date: Wed, 28 Feb 2024 17:18:08 -0500 Subject: [PATCH 96/96] Asynchronous GeometryTile deletion (#2051) Co-authored-by: Tim Sylvester --- CMakeLists.txt | 7 +- bazel/core.bzl | 16 +-- include/mbgl/actor/actor.hpp | 2 +- include/mbgl/actor/mailbox.hpp | 12 +- include/mbgl/actor/scheduler.hpp | 18 ++- include/mbgl/gfx/context.hpp | 6 +- include/mbgl/util/run_loop.hpp | 4 +- include/mbgl/util/scoped.hpp | 21 ++++ include/mbgl/util/string.hpp | 10 +- .../src/cpp/map_renderer.cpp | 19 ++- .../src/cpp/map_renderer.hpp | 6 +- .../android/maps/renderer/MapRenderer.java | 5 + .../maps/renderer/MapRendererScheduler.java | 5 + .../GLSurfaceViewMapRenderer.java | 7 ++ .../glsurfaceview/MapLibreGLSurfaceView.java | 43 +++++++ .../textureview/TextureViewMapRenderer.java | 9 ++ .../textureview/TextureViewRenderThread.java | 33 ++++++ .../android/maps/NativeMapViewTest.kt | 5 + platform/android/src/run_loop.cpp | 39 +++++- platform/android/src/run_loop_impl.hpp | 3 + platform/darwin/src/run_loop.cpp | 25 +++- platform/default/src/mbgl/util/run_loop.cpp | 24 +++- .../default/src/mbgl/util/thread_local.cpp | 4 +- platform/qt/src/mbgl/run_loop.cpp | 20 ++++ platform/qt/src/mbgl/thread_local.cpp | 4 +- platform/qt/src/utils/scheduler.cpp | 26 +++- platform/qt/src/utils/scheduler.hpp | 8 +- platform/windows/src/thread_local.cpp | 5 +- src/mbgl/actor/mailbox.cpp | 39 ++++++ .../annotation/render_annotation_source.cpp | 5 +- .../annotation/render_annotation_source.hpp | 2 +- src/mbgl/gl/context.cpp | 5 + src/mbgl/mtl/context.cpp | 7 +- src/mbgl/renderer/image_manager.cpp | 38 +++++- src/mbgl/renderer/image_manager.hpp | 10 +- src/mbgl/renderer/render_orchestrator.cpp | 16 ++- src/mbgl/renderer/render_orchestrator.hpp | 6 +- src/mbgl/renderer/render_source.cpp | 23 ++-- src/mbgl/renderer/render_source.hpp | 19 +-- .../sources/render_custom_geometry_source.cpp | 5 +- .../sources/render_custom_geometry_source.hpp | 2 +- .../sources/render_geojson_source.cpp | 5 +- .../sources/render_geojson_source.hpp | 2 +- .../sources/render_raster_dem_source.cpp | 5 +- .../sources/render_raster_dem_source.hpp | 2 +- .../renderer/sources/render_raster_source.cpp | 5 +- .../renderer/sources/render_raster_source.hpp | 2 +- .../renderer/sources/render_tile_source.cpp | 7 +- .../renderer/sources/render_tile_source.hpp | 4 +- .../renderer/sources/render_vector_source.cpp | 5 +- .../renderer/sources/render_vector_source.hpp | 2 +- src/mbgl/renderer/tile_parameters.hpp | 4 +- src/mbgl/renderer/tile_pyramid.cpp | 26 ++-- src/mbgl/renderer/tile_pyramid.hpp | 2 +- src/mbgl/text/glyph_manager.cpp | 105 +++++++++-------- src/mbgl/text/glyph_manager.hpp | 3 + src/mbgl/tile/geometry_tile.cpp | 26 +++- src/mbgl/tile/geometry_tile.hpp | 10 +- src/mbgl/tile/geometry_tile_worker.cpp | 5 +- src/mbgl/tile/tile.hpp | 2 +- src/mbgl/tile/tile_cache.cpp | 66 +++++++++-- src/mbgl/tile/tile_cache.hpp | 22 +++- src/mbgl/tile/tile_loader.hpp | 17 +++ src/mbgl/tile/tile_loader_impl.hpp | 77 ++++++++---- src/mbgl/util/thread_local.hpp | 4 +- src/mbgl/util/thread_pool.cpp | 92 +++++++++++++-- src/mbgl/util/thread_pool.hpp | 74 ++++++++++-- test/BUILD.bazel | 4 +- test/actor/actor.test.cpp | 7 +- test/include/mbgl/test/vector_tile_test.hpp | 49 ++++++++ test/map/map.test.cpp | 3 + test/renderer/image_manager.test.cpp | 41 ++++--- test/style/source.test.cpp | 55 +++++---- test/tile/custom_geometry_tile.test.cpp | 4 +- test/tile/geojson_tile.test.cpp | 4 +- test/tile/raster_dem_tile.test.cpp | 4 +- test/tile/raster_tile.test.cpp | 4 +- test/tile/tile_cache.test.cpp | 67 ++++++----- test/tile/vector_tile.test.cpp | 24 +--- test/util/thread.test.cpp | 111 ++++++++++++++++++ 80 files changed, 1180 insertions(+), 334 deletions(-) create mode 100644 include/mbgl/util/scoped.hpp create mode 100644 test/include/mbgl/test/vector_tile_test.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fa5197b0da..8faf667cd32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,7 +158,6 @@ if(MLN_DRAWABLE_RENDERER) ${PROJECT_SOURCE_DIR}/include/mbgl/renderer/layer_tweaker.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/renderer/render_target.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_program_base.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/util/identity.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/suppress_copies.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_info.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp @@ -216,7 +215,6 @@ if(MLN_DRAWABLE_RENDERER) ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/collision_layer_tweaker.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/collision_layer_tweaker.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/shader_program_base.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/util/identity.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_program_gl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/buffer_allocator.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl.cpp @@ -403,6 +401,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/util/geo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/geojson.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/geometry.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/util/identity.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/ignore.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/image.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/immutable.hpp @@ -415,6 +414,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/util/projection.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/range.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/run_loop.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/util/scoped.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/size.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/string.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/string_indexer.hpp @@ -939,6 +939,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/util/http_timeout.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/i18n.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/i18n.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/util/identity.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/interpolate.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/intersection_tests.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/intersection_tests.hpp @@ -1444,4 +1445,4 @@ endif() add_subdirectory(${PROJECT_SOURCE_DIR}/test) add_subdirectory(${PROJECT_SOURCE_DIR}/benchmark) -add_subdirectory(${PROJECT_SOURCE_DIR}/render-test) \ No newline at end of file +add_subdirectory(${PROJECT_SOURCE_DIR}/render-test) diff --git a/bazel/core.bzl b/bazel/core.bzl index 7fe2e6fcc77..06cd361eae3 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -141,7 +141,6 @@ MLN_CORE_SOURCE = [ "src/mbgl/geometry/line_atlas.hpp", "src/mbgl/gfx/attribute.cpp", "src/mbgl/gfx/attribute.hpp", - "include/mbgl/gfx/backend.hpp", "src/mbgl/gfx/color_mode.hpp", "src/mbgl/gfx/command_encoder.hpp", "src/mbgl/gfx/cull_face_mode.hpp", @@ -149,9 +148,11 @@ MLN_CORE_SOURCE = [ "src/mbgl/gfx/depth_mode.hpp", "src/mbgl/gfx/draw_mode.hpp", "src/mbgl/gfx/draw_scope.hpp", + "src/mbgl/gfx/fill_generator.cpp", "src/mbgl/gfx/index_buffer.hpp", "src/mbgl/gfx/index_vector.hpp", "src/mbgl/gfx/offscreen_texture.hpp", + "src/mbgl/gfx/polyline_generator.cpp", "src/mbgl/gfx/program.hpp", "src/mbgl/gfx/render_pass.hpp", "src/mbgl/gfx/renderbuffer.hpp", @@ -598,6 +599,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/util/http_timeout.hpp", "src/mbgl/util/i18n.cpp", "src/mbgl/util/i18n.hpp", + "src/mbgl/util/identity.cpp", "src/mbgl/util/interpolate.cpp", "src/mbgl/util/intersection_tests.cpp", "src/mbgl/util/intersection_tests.hpp", @@ -618,8 +620,6 @@ MLN_CORE_SOURCE = [ "src/mbgl/util/premultiply.cpp", "src/mbgl/util/quaternion.cpp", "src/mbgl/util/quaternion.hpp", - "src/mbgl/gfx/polyline_generator.cpp", - "src/mbgl/gfx/fill_generator.cpp", "src/mbgl/util/rapidjson.cpp", "src/mbgl/util/rapidjson.hpp", "src/mbgl/util/rect.hpp", @@ -661,8 +661,11 @@ MLN_CORE_HEADERS = [ "include/mbgl/actor/message.hpp", "include/mbgl/actor/scheduler.hpp", "include/mbgl/annotation/annotation.hpp", + "include/mbgl/gfx/backend.hpp", "include/mbgl/gfx/backend_scope.hpp", + "include/mbgl/gfx/fill_generator.hpp", "include/mbgl/gfx/gfx_types.hpp", + "include/mbgl/gfx/polyline_generator.hpp", "include/mbgl/gfx/renderable.hpp", "include/mbgl/gfx/renderer_backend.hpp", "include/mbgl/gfx/rendering_stats.hpp", @@ -820,6 +823,7 @@ MLN_CORE_HEADERS = [ "include/mbgl/util/geo.hpp", "include/mbgl/util/geojson.hpp", "include/mbgl/util/geometry.hpp", + "include/mbgl/util/identity.hpp", "include/mbgl/util/ignore.hpp", "include/mbgl/util/image.hpp", "include/mbgl/util/immutable.hpp", @@ -829,12 +833,11 @@ MLN_CORE_HEADERS = [ "include/mbgl/util/monotonic_timer.hpp", "include/mbgl/util/noncopyable.hpp", "include/mbgl/util/platform.hpp", - "include/mbgl/gfx/polyline_generator.hpp", - "include/mbgl/gfx/fill_generator.hpp", "include/mbgl/util/premultiply.hpp", "include/mbgl/util/projection.hpp", "include/mbgl/util/range.hpp", "include/mbgl/util/run_loop.hpp", + "include/mbgl/util/scoped.hpp", "include/mbgl/util/size.hpp", "include/mbgl/util/string.hpp", "include/mbgl/util/string_indexer.hpp", @@ -905,7 +908,6 @@ MLN_OPENGL_SOURCE = [ ] MLN_OPENGL_HEADERS = [ - "include/mbgl/gfx/backend.hpp", "include/mbgl/gl/renderable_resource.hpp", "include/mbgl/gl/renderer_backend.hpp", "include/mbgl/layermanager/location_indicator_layer_factory.hpp", @@ -957,7 +959,6 @@ MLN_DRAWABLES_SOURCE = [ "src/mbgl/renderer/layers/collision_layer_tweaker.cpp", "src/mbgl/renderer/layers/collision_layer_tweaker.hpp", "src/mbgl/shaders/shader_program_base.cpp", - "src/mbgl/util/identity.cpp", "src/mbgl/style/layers/custom_drawable_layer.cpp", "src/mbgl/layermanager/custom_drawable_layer_factory.cpp", "src/mbgl/style/layers/custom_drawable_layer_impl.cpp", @@ -999,7 +1000,6 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/shader_defines.hpp", "include/mbgl/shaders/shader_program_base.hpp", "include/mbgl/shaders/symbol_layer_ubo.hpp", - "include/mbgl/util/identity.hpp", "include/mbgl/util/suppress_copies.hpp", "include/mbgl/style/layers/custom_drawable_layer.hpp", "include/mbgl/layermanager/custom_drawable_layer_factory.hpp", diff --git a/include/mbgl/actor/actor.hpp b/include/mbgl/actor/actor.hpp index 7ecc559d57e..af42bec7b97 100644 --- a/include/mbgl/actor/actor.hpp +++ b/include/mbgl/actor/actor.hpp @@ -71,7 +71,7 @@ class Actor { ActorRef> self() { return parent.self(); } private: - std::shared_ptr retainer; + const std::shared_ptr retainer; AspiringActor parent; EstablishedActor target; }; diff --git a/include/mbgl/actor/mailbox.hpp b/include/mbgl/actor/mailbox.hpp index 782af736608..b558062aeb3 100644 --- a/include/mbgl/actor/mailbox.hpp +++ b/include/mbgl/actor/mailbox.hpp @@ -1,5 +1,5 @@ #pragma once - +#include #include #include #include @@ -28,6 +28,9 @@ class Mailbox : public std::enable_shared_from_this { void open(Scheduler& scheduler_); void close(); + // Indicate this mailbox will no longer be checked for messages + void abandon(); + bool isOpen() const; void push(std::unique_ptr); @@ -37,11 +40,18 @@ class Mailbox : public std::enable_shared_from_this { static std::function makeClosure(std::weak_ptr); private: + enum class State : uint32_t { + Idle = 0, + Processing, + Abandoned + }; + mapbox::base::WeakPtr weakScheduler; std::recursive_mutex receivingMutex; std::mutex pushingMutex; + std::atomic state{State::Idle}; bool closed{false}; std::mutex queueMutex; diff --git a/include/mbgl/actor/scheduler.hpp b/include/mbgl/actor/scheduler.hpp index 8d4fa187d75..3f99e11c4ea 100644 --- a/include/mbgl/actor/scheduler.hpp +++ b/include/mbgl/actor/scheduler.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -36,9 +38,12 @@ class Scheduler { virtual ~Scheduler() = default; /// Enqueues a function for execution. - virtual void schedule(std::function) = 0; + virtual void schedule(std::function&&) = 0; /// Makes a weak pointer to this Scheduler. virtual mapbox::base::WeakPtr makeWeakPtr() = 0; + /// Enqueues a function for execution on the render thread. + virtual void runOnRenderThread(std::function&&){}; + virtual void runRenderJobs() {} /// Returns a closure wrapping the given one. /// @@ -62,6 +67,11 @@ class Scheduler { scheduleAndReplyValue(task, reply, GetCurrent()->makeWeakPtr()); } + /// Wait until there's nothing pending or in process + /// Must not be called from a task provided to this scheduler. + /// @param timeout Time to wait, or zero to wait forever. + virtual std::size_t waitForEmpty(Milliseconds timeout = Milliseconds{0}) = 0; + /// Set/Get the current Scheduler for this thread static Scheduler* GetCurrent(); static void SetCurrent(Scheduler*); @@ -84,6 +94,9 @@ class Scheduler { /// on the same thread-unsafe object. [[nodiscard]] static std::shared_ptr GetSequenced(); + /// Set a function to be called when an exception occurs on a thread controlled by the scheduler + void setExceptionHandler(std::function handler_) { handler = std::move(handler_); } + protected: template void scheduleAndReplyValue(const TaskFn& task, @@ -97,9 +110,10 @@ class Scheduler { }; replyScheduler->schedule(std::move(scheduledReply)); }; - schedule(std::move(scheduled)); } + + std::function handler; }; } // namespace mbgl diff --git a/include/mbgl/gfx/context.hpp b/include/mbgl/gfx/context.hpp index 3ec22e7b766..b6f95fe14f5 100644 --- a/include/mbgl/gfx/context.hpp +++ b/include/mbgl/gfx/context.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -53,7 +54,8 @@ using VertexAttributeArrayPtr = std::shared_ptr; class Context { protected: Context(uint32_t maximumVertexBindingCount_) - : maximumVertexBindingCount(maximumVertexBindingCount_) {} + : maximumVertexBindingCount(maximumVertexBindingCount_), + backgroundScheduler(Scheduler::GetBackground()) {} public: static constexpr const uint32_t minimumRequiredVertexBindingCount = 8; @@ -161,6 +163,8 @@ class Context { virtual std::unique_ptr createDrawScopeResource() = 0; gfx::RenderingStats stats; + + std::shared_ptr backgroundScheduler; }; } // namespace gfx diff --git a/include/mbgl/util/run_loop.hpp b/include/mbgl/util/run_loop.hpp index 7e1d94332a9..841814638ab 100644 --- a/include/mbgl/util/run_loop.hpp +++ b/include/mbgl/util/run_loop.hpp @@ -78,9 +78,11 @@ class RunLoop : public Scheduler, private util::noncopyable { return std::make_unique(task); } - void schedule(std::function fn) override { invoke(std::move(fn)); } + void schedule(std::function&& fn) override { invoke(std::move(fn)); } ::mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } + std::size_t waitForEmpty(Milliseconds timeout) override; + class Impl; private: diff --git a/include/mbgl/util/scoped.hpp b/include/mbgl/util/scoped.hpp new file mode 100644 index 00000000000..40766371986 --- /dev/null +++ b/include/mbgl/util/scoped.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace mbgl { + +/// Run a lambda on scope exit +template +struct Scoped { + Scoped(Func&& fn) + : cb(std::move(fn)){}; + ~Scoped() { cb(); } + + Scoped(const Scoped&) = delete; + Scoped(Scoped&&) noexcept = delete; + Scoped& operator=(const Scoped&) = delete; + Scoped& operator=(Scoped&&) noexcept = delete; + +private: + Func cb; +}; + +} // namespace mbgl diff --git a/include/mbgl/util/string.hpp b/include/mbgl/util/string.hpp index 70e1212f949..a6d4d2e4c5f 100644 --- a/include/mbgl/util/string.hpp +++ b/include/mbgl/util/string.hpp @@ -1,10 +1,12 @@ #pragma once -#include #include #include -#include #include +#include +#include +#include +#include // Polyfill needed by Qt when building for Android with GCC #if defined(__ANDROID__) && defined(__GLIBCXX__) @@ -76,6 +78,10 @@ inline std::string toString(long double t, bool decimal = false) { return toString(static_cast(t), decimal); } +inline std::string toString(std::thread::id threadId) { + return (std::ostringstream() << threadId).str(); +} + std::string toString(const std::exception_ptr &); template diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp index 3ea039d1fda..472b96d14c2 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.cpp @@ -60,7 +60,7 @@ ActorRef MapRenderer::actor() const { return *rendererRef; } -void MapRenderer::schedule(std::function scheduled) { +void MapRenderer::schedule(std::function&& scheduled) { try { // Create a runnable android::UniqueEnv _env = android::AttachEnv(); @@ -84,6 +84,23 @@ void MapRenderer::schedule(std::function scheduled) { } } +std::size_t MapRenderer::waitForEmpty(Milliseconds timeout) { + try { + android::UniqueEnv _env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*_env); + static auto waitForEmpty = javaClass.GetMethod(*_env, "waitForEmpty"); + if (auto weakReference = javaPeer.get(*_env)) { + return weakReference.Call(*_env, waitForEmpty, static_cast(timeout.count())); + } + // If the peer is already cleaned up, there's nothing to wait for + return 0; + } catch (...) { + Log::Error(Event::Android, "MapRenderer::waitForEmpty failed"); + jni::ThrowJavaError(*android::AttachEnv(), std::current_exception()); + return 0; + } +} + void MapRenderer::requestRender() { try { android::UniqueEnv _env = android::AttachEnv(); diff --git a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp index 25ffc60695d..4ecb8f5d94b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp +++ b/platform/android/MapboxGLAndroidSDK/src/cpp/map_renderer.hpp @@ -66,9 +66,13 @@ class MapRenderer : public Scheduler { // From Scheduler. Schedules by using callbacks to the // JVM to process the mailbox on the right thread. - void schedule(std::function scheduled) override; + void schedule(std::function&& scheduled) override; mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } + // Wait for the queue to be empty + // A timeout of zero results in an unbounded wait + std::size_t waitForEmpty(Milliseconds timeout) override; + void requestRender(); // Snapshot - requires a RunLoop on the calling thread diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java index 7be40232479..72c539040de 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java @@ -120,6 +120,11 @@ void queueEvent(MapRendererRunnable runnable) { this.queueEvent((Runnable) runnable); } + /// Wait indefinitely for the queue to become empty + public void waitForEmpty() { + waitForEmpty(0); + } + private native void nativeInitialize(MapRenderer self, float pixelRatio, String localIdeographFontFamily); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java index 145ae6397fc..f1e17e658f7 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java @@ -14,4 +14,9 @@ public interface MapRendererScheduler { @Keep void queueEvent(Runnable runnable); + @Keep + void waitForEmpty(); + + @Keep + long waitForEmpty(long timeoutMillis); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java index ac803068388..a8a3c3fb41c 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java @@ -116,4 +116,11 @@ public void queueEvent(Runnable runnable) { glSurfaceView.queueEvent(runnable); } + /** + * {@inheritDoc} + */ + @Override + public long waitForEmpty(long timeoutMillis) { + return glSurfaceView.waitForEmpty(timeoutMillis); + } } \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java index 111cbc9e5a6..a78a8a907a4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java @@ -317,6 +317,16 @@ public void queueEvent(Runnable r) { glThread.queueEvent(r); } + /** + * Wait for the queue to become empty + * @param timeoutMillis Timeout in milliseconds + * @return Number of queue items remaining + */ + public long waitForEmpty(long timeoutMillis) { + return glThread.waitForEmpty(timeoutMillis); + } + + /** * This method is used as part of the View class and is not normally * called or subclassed by clients of GLSurfaceView. @@ -1023,6 +1033,39 @@ public void queueEvent(@NonNull Runnable r) { } } + /** + * Wait for the queue to become empty + * @param timeoutMillis Timeout in milliseconds, zero for indefinite wait + * @return Number of queue items remaining + */ + public int waitForEmpty(long timeoutMillis) { + final long startTime = System.nanoTime(); + synchronized (glThreadManager) { + // Wait for the queue to be empty + while (!this.eventQueue.isEmpty()) { + if (timeoutMillis > 0) { + final long elapsedMillis = (System.nanoTime() - startTime) / 1000 / 1000; + if (elapsedMillis < timeoutMillis) { + try { + glThreadManager.wait(timeoutMillis - elapsedMillis); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } else { + break; + } + } else { + try { + glThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + return this.eventQueue.size(); + } + } + // Once the thread is started, all accesses to the following member // variables are protected by the sGLThreadManager monitor private boolean shouldExit; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java index 420b23d10a4..ee20aae6089 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java @@ -35,6 +35,7 @@ public TextureViewMapRenderer(@NonNull Context context, super(context, localIdeographFontFamily); this.translucentSurface = translucentSurface; renderThread = new TextureViewRenderThread(textureView, this); + renderThread.setName("TextureViewRenderer"); renderThread.start(); } @@ -86,6 +87,14 @@ public void queueEvent(Runnable runnable) { renderThread.queueEvent(runnable); } + /** + * {@inheritDoc} + */ + @Override + public long waitForEmpty(long timeoutMillis) { + return renderThread.waitForEmpty(timeoutMillis); + } + /** * {@inheritDoc} */ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java index 5f5772af479..51598b967ee 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java @@ -135,6 +135,39 @@ void queueEvent(@NonNull Runnable runnable) { } } + /** + * Wait for the queue to be empty. + * @param timeoutMillis Maximum time to wait, in milliseconds + * @return The number of items remaining in the queue + */ + @UiThread + int waitForEmpty(long timeoutMillis) { + final long startTime = System.nanoTime(); + synchronized (lock) { + // Wait for the queue to be empty + while (!this.eventQueue.isEmpty()) { + if (timeoutMillis > 0) { + final long elapsedMillis = (System.nanoTime() - startTime) / 1000 / 1000; + if (elapsedMillis < timeoutMillis) { + try { + lock.wait(timeoutMillis - elapsedMillis); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } else { + break; + } + } else { + try { + lock.wait(0); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + return this.eventQueue.size(); + } + } @UiThread void onPause() { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt index fafcac7d1a3..307bd4a8611 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt @@ -442,5 +442,10 @@ class NativeMapViewTest : AppCenter() { override fun queueEvent(runnable: Runnable?) { // no-op } + + override fun waitForEmpty(timeoutMillis: Long): Long { + // no-op + return 0 + } } } diff --git a/platform/android/src/run_loop.cpp b/platform/android/src/run_loop.cpp index 8fcf2fb30e8..a26c91ead0d 100644 --- a/platform/android/src/run_loop.cpp +++ b/platform/android/src/run_loop.cpp @@ -1,11 +1,13 @@ #include "run_loop_impl.hpp" +#include +#include +#include +#include #include -#include #include +#include #include -#include -#include #include @@ -18,7 +20,6 @@ #include #include -#include #define PIPE_OUT 0 #define PIPE_IN 1 @@ -167,6 +168,9 @@ void RunLoop::Impl::addRunnable(Runnable* runnable) { void RunLoop::Impl::removeRunnable(Runnable* runnable) { std::lock_guard lock(mutex); runnables.remove(runnable); + if (runnables.empty()) { + cvEmpty.notify_all(); + } } Milliseconds RunLoop::Impl::processRunnables() { @@ -196,6 +200,10 @@ Milliseconds RunLoop::Impl::processRunnables() { runnable->runTask(); } + if (runnables.empty()) { + cvEmpty.notify_all(); + } + if (runnables.empty() || nextDue == TimePoint::max()) { return Milliseconds(-1); } @@ -208,6 +216,25 @@ Milliseconds RunLoop::Impl::processRunnables() { return timeout; } +std::size_t RunLoop::Impl::waitForEmpty(Milliseconds timeout) { + const auto startTime = mbgl::util::MonotonicTimer::now(); + while (true) { + std::size_t remaining; + { + std::lock_guard lock(mutex); + remaining = runnables.size(); + } + + const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; + const auto elapsedMillis = std::chrono::duration_cast(elapsed); + if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) { + return remaining; + } + + runLoop->runOnce(); + } +} + RunLoop* RunLoop::Get() { assert(static_cast(Scheduler::GetCurrent())); return static_cast(Scheduler::GetCurrent()); @@ -230,6 +257,10 @@ void RunLoop::wake() { impl->wake(); } +std::size_t RunLoop::waitForEmpty(std::chrono::milliseconds timeout) { + return impl->waitForEmpty(timeout); +} + void RunLoop::run() { MBGL_VERIFY_THREAD(tid); diff --git a/platform/android/src/run_loop_impl.hpp b/platform/android/src/run_loop_impl.hpp index 8bbf6dfc2e3..ff2420cd02e 100644 --- a/platform/android/src/run_loop_impl.hpp +++ b/platform/android/src/run_loop_impl.hpp @@ -42,6 +42,8 @@ class RunLoop::Impl { Milliseconds processRunnables(); + std::size_t waitForEmpty(Milliseconds timeout); + ALooper* loop = nullptr; RunLoop* runLoop = nullptr; std::atomic running; @@ -57,6 +59,7 @@ class RunLoop::Impl { std::unique_ptr> alarm; std::mutex mutex; + std::condition_variable cvEmpty; std::list runnables; }; diff --git a/platform/darwin/src/run_loop.cpp b/platform/darwin/src/run_loop.cpp index 0e22dc9e337..bf5bf71d0bb 100644 --- a/platform/darwin/src/run_loop.cpp +++ b/platform/darwin/src/run_loop.cpp @@ -1,6 +1,8 @@ -#include -#include #include +#include +#include +#include +#include #include @@ -45,5 +47,24 @@ void RunLoop::stop() { invoke([&] { CFRunLoopStop(CFRunLoopGetCurrent()); }); } +std::size_t RunLoop::waitForEmpty(Milliseconds timeout) { + const auto startTime = mbgl::util::MonotonicTimer::now(); + while (true) { + std::size_t remaining; + { + std::lock_guard lock(mutex); + remaining = defaultQueue.size() + highPriorityQueue.size(); + } + + const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; + const auto elapsedMillis = std::chrono::duration_cast(elapsed); + if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) { + return remaining; + } + + runOnce(); + } +} + } // namespace util } // namespace mbgl diff --git a/platform/default/src/mbgl/util/run_loop.cpp b/platform/default/src/mbgl/util/run_loop.cpp index 1a5939ec14b..39d31d87b74 100644 --- a/platform/default/src/mbgl/util/run_loop.cpp +++ b/platform/default/src/mbgl/util/run_loop.cpp @@ -1,7 +1,8 @@ -#include +#include #include +#include +#include #include -#include #include @@ -148,6 +149,25 @@ void RunLoop::stop() { invoke([&] { uv_unref(impl->holderHandle()); }); } +std::size_t RunLoop::waitForEmpty(Milliseconds timeout) { + const auto startTime = mbgl::util::MonotonicTimer::now(); + while (true) { + std::size_t remaining; + { + std::lock_guard lock(mutex); + remaining = defaultQueue.size() + highPriorityQueue.size(); + } + + const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; + const auto elapsedMillis = std::chrono::duration_cast(elapsed); + if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) { + return remaining; + } + + runOnce(); + } +} + void RunLoop::addWatch(int fd, Event event, std::function&& callback) { MBGL_VERIFY_THREAD(tid); diff --git a/platform/default/src/mbgl/util/thread_local.cpp b/platform/default/src/mbgl/util/thread_local.cpp index e8d9d93715a..3ab51d92349 100644 --- a/platform/default/src/mbgl/util/thread_local.cpp +++ b/platform/default/src/mbgl/util/thread_local.cpp @@ -30,8 +30,8 @@ ThreadLocalBase::~ThreadLocalBase() { } } -void* ThreadLocalBase::get() { - return pthread_getspecific(reinterpret_cast(storage)); +void* ThreadLocalBase::get() const { + return pthread_getspecific(reinterpret_cast(storage)); } void ThreadLocalBase::set(void* ptr) { diff --git a/platform/qt/src/mbgl/run_loop.cpp b/platform/qt/src/mbgl/run_loop.cpp index 6bd9cb035c5..c978bd81ea5 100644 --- a/platform/qt/src/mbgl/run_loop.cpp +++ b/platform/qt/src/mbgl/run_loop.cpp @@ -1,6 +1,7 @@ #include "run_loop_impl.hpp" #include +#include #include @@ -90,6 +91,25 @@ void RunLoop::runOnce() { } } +std::size_t RunLoop::waitForEmpty(std::chrono::milliseconds timeout) { + const auto startTime = mbgl::util::MonotonicTimer::now(); + while (true) { + std::size_t remaining; + { + std::lock_guard lock(mutex); + remaining = defaultQueue.size() + highPriorityQueue.size(); + } + + const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; + const auto elapsedMillis = std::chrono::duration_cast(elapsed); + if (remaining == 0 || timeout <= elapsedMillis) { + return remaining; + } + + runOnce(); + } +} + void RunLoop::addWatch(int fd, Event event, std::function&& cb) { MBGL_VERIFY_THREAD(tid); diff --git a/platform/qt/src/mbgl/thread_local.cpp b/platform/qt/src/mbgl/thread_local.cpp index 1f69a8aac36..88da18079df 100644 --- a/platform/qt/src/mbgl/thread_local.cpp +++ b/platform/qt/src/mbgl/thread_local.cpp @@ -27,8 +27,8 @@ ThreadLocalBase::~ThreadLocalBase() { reinterpret_cast(storage).~QThreadStorage(); } -void* ThreadLocalBase::get() { - return reinterpret_cast(storage).localData()[0]; +void* ThreadLocalBase::get() const { + return reinterpret_cast(storage).localData()[0]; } void ThreadLocalBase::set(void* ptr) { diff --git a/platform/qt/src/utils/scheduler.cpp b/platform/qt/src/utils/scheduler.cpp index 0c450a1bc63..45e83926151 100644 --- a/platform/qt/src/utils/scheduler.cpp +++ b/platform/qt/src/utils/scheduler.cpp @@ -1,5 +1,6 @@ #include "scheduler.hpp" +#include #include #include @@ -12,7 +13,7 @@ Scheduler::~Scheduler() { MBGL_VERIFY_THREAD(tid); } -void Scheduler::schedule(std::function function) { +void Scheduler::schedule(std::function&& function) { const std::lock_guard lock(m_taskQueueMutex); m_taskQueue.push(std::move(function)); @@ -26,6 +27,7 @@ void Scheduler::processEvents() { { const std::unique_lock lock(m_taskQueueMutex); std::swap(taskQueue, m_taskQueue); + pendingItems += taskQueue.size(); } while (!taskQueue.empty()) { @@ -34,7 +36,29 @@ void Scheduler::processEvents() { function(); } taskQueue.pop(); + pendingItems--; } + + cvEmpty.notify_all(); +} + +std::size_t Scheduler::waitForEmpty(std::chrono::milliseconds timeout) { + MBGL_VERIFY_THREAD(tid); + + const auto startTime = mbgl::util::MonotonicTimer::now(); + std::unique_lock lock(m_taskQueueMutex); + const auto isDone = [&] { + return m_taskQueue.empty() && pendingItems == 0; + }; + while (!isDone()) { + const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; + if (timeout <= elapsed || !cvEmpty.wait_for(lock, timeout - elapsed, isDone)) { + assert(isDone()); + break; + } + } + + return m_taskQueue.size() + pendingItems; } } // namespace QMapLibre diff --git a/platform/qt/src/utils/scheduler.hpp b/platform/qt/src/utils/scheduler.hpp index f5dc1802846..56f9fd954d7 100644 --- a/platform/qt/src/utils/scheduler.hpp +++ b/platform/qt/src/utils/scheduler.hpp @@ -5,6 +5,7 @@ #include +#include #include #include #include @@ -19,7 +20,10 @@ class Scheduler : public QObject, public mbgl::Scheduler { ~Scheduler() override; // mbgl::Scheduler implementation. - void schedule(std::function function) final; + void schedule(std::function&& function) final; + + std::size_t waitForEmpty(std::chrono::milliseconds timeout) override; + mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } void processEvents(); @@ -31,6 +35,8 @@ class Scheduler : public QObject, public mbgl::Scheduler { MBGL_STORE_THREAD(tid); std::mutex m_taskQueueMutex; + std::condition_variable cvEmpty; + std::atomic pendingItems; std::queue> m_taskQueue; mapbox::base::WeakPtrFactory weakFactory{this}; }; diff --git a/platform/windows/src/thread_local.cpp b/platform/windows/src/thread_local.cpp index 7e0b8b9c4bf..66c9f1efb6d 100644 --- a/platform/windows/src/thread_local.cpp +++ b/platform/windows/src/thread_local.cpp @@ -10,6 +10,7 @@ #include "thread.h" #define StorageToThreadInfo reinterpret_cast(storage) +#define StorageToConstThreadInfo reinterpret_cast(storage) namespace mbgl { namespace util { @@ -47,8 +48,8 @@ ThreadLocalBase::~ThreadLocalBase() { } } -void* ThreadLocalBase::get() { - return TlsGetValue(StorageToThreadInfo->key); +void* ThreadLocalBase::get() const { + return TlsGetValue(StorageToConstThreadInfo->key); } void ThreadLocalBase::set(void* ptr) { diff --git a/src/mbgl/actor/mailbox.cpp b/src/mbgl/actor/mailbox.cpp index 4c50f74719a..ad63f4bc5d3 100644 --- a/src/mbgl/actor/mailbox.cpp +++ b/src/mbgl/actor/mailbox.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -32,6 +33,8 @@ void Mailbox::open(Scheduler& scheduler_) { } void Mailbox::close() { + abandon(); + // Block until neither receive() nor push() are in progress. Two mutexes are // used because receive() must not block send(). Of the two, the receiving // mutex must be acquired first, because that is the order that an actor @@ -44,14 +47,37 @@ void Mailbox::close() { closed = true; } +void Mailbox::abandon() { + auto idleValue = State::Idle; + while (!state.compare_exchange_strong(idleValue, State::Abandoned)) { + if (state == State::Abandoned) { + break; + } + } +} + bool Mailbox::isOpen() const { return bool(weakScheduler); } void Mailbox::push(std::unique_ptr message) { + auto idleState = State::Idle; + while (!state.compare_exchange_strong(idleState, State::Processing)) { + if (state == State::Abandoned) { + return; + } + } + + Scoped activityFlag{[this]() { + if (state == State::Processing) { + state = State::Idle; + } + }}; + std::lock_guard pushingLock(pushingMutex); if (closed) { + state = State::Abandoned; return; } @@ -65,12 +91,25 @@ void Mailbox::push(std::unique_ptr message) { } void Mailbox::receive() { + auto idleState = State::Idle; + while (!state.compare_exchange_strong(idleState, State::Processing)) { + if (state == State::Abandoned) { + return; + } + } + + Scoped activityFlag{[this]() { + if (state == State::Processing) { + state = State::Idle; + } + }}; std::lock_guard receivingLock(receivingMutex); auto guard = weakScheduler.lock(); assert(weakScheduler); if (closed) { + state = State::Abandoned; return; } diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index 1333c39a11e..ec60eecbe68 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -9,8 +9,9 @@ namespace mbgl { using namespace style; -RenderAnnotationSource::RenderAnnotationSource(Immutable impl_) - : RenderTileSource(std::move(impl_)) { +RenderAnnotationSource::RenderAnnotationSource(Immutable impl_, + std::shared_ptr threadPool_) + : RenderTileSource(std::move(impl_), std::move(threadPool_)) { assert(LayerManager::annotationsEnabled); tilePyramid.setObserver(this); } diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp index 23187b6c31e..d195ea9cb27 100644 --- a/src/mbgl/annotation/render_annotation_source.hpp +++ b/src/mbgl/annotation/render_annotation_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderAnnotationSource final : public RenderTileSource { public: - explicit RenderAnnotationSource(Immutable); + explicit RenderAnnotationSource(Immutable, std::shared_ptr); void update(Immutable, const std::vector>&, diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 3f7b36c526b..87d0dad5e55 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #if MLN_DRAWABLE_RENDERER #include @@ -81,6 +82,8 @@ Context::Context(RendererBackend& backend_) Context::~Context() noexcept { if (cleanupOnDestruction) { + Scheduler::GetBackground()->runRenderJobs(); + reset(); #if !defined(NDEBUG) Log::Debug(Event::General, "Rendering Stats:\n" + stats.toString("\n")); @@ -90,6 +93,8 @@ Context::~Context() noexcept { } void Context::beginFrame() { + Scheduler::GetBackground()->runRenderJobs(); + #if MLN_DRAWABLE_RENDERER frameInFlightFence = std::make_shared(); diff --git a/src/mbgl/mtl/context.cpp b/src/mbgl/mtl/context.cpp index edd8e60e1ff..2b5014debb2 100644 --- a/src/mbgl/mtl/context.cpp +++ b/src/mbgl/mtl/context.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ Context::Context(RendererBackend& backend_) Context::~Context() noexcept { if (cleanupOnDestruction) { + Scheduler::GetBackground()->runRenderJobs(); performCleanup(); emptyVertexBuffer.reset(); @@ -60,7 +62,10 @@ Context::~Context() noexcept { } } -void Context::beginFrame() {} +void Context::beginFrame() { + Scheduler::GetBackground()->runRenderJobs(); +} + void Context::endFrame() {} std::unique_ptr Context::createCommandEncoder() { diff --git a/src/mbgl/renderer/image_manager.cpp b/src/mbgl/renderer/image_manager.cpp index 74d109f0107..af08124b238 100644 --- a/src/mbgl/renderer/image_manager.cpp +++ b/src/mbgl/renderer/image_manager.cpp @@ -21,6 +21,7 @@ void ImageManager::setObserver(ImageManagerObserver* observer_) { } void ImageManager::setLoaded(bool loaded_) { + std::lock_guard readWriteLock(rwLock); if (loaded == loaded_) { return; } @@ -31,6 +32,7 @@ void ImageManager::setLoaded(bool loaded_) { for (const auto& entry : requestors) { checkMissingAndNotify(*entry.first, entry.second); } + requestors.clear(); } } @@ -40,16 +42,21 @@ bool ImageManager::isLoaded() const { } void ImageManager::addImage(Immutable image_) { + std::lock_guard readLock(rwLock); assert(images.find(image_->id) == images.end()); + // Increase cache size if requested image was provided. if (requestedImages.find(image_->id) != requestedImages.end()) { requestedImagesCacheSize += image_->image.bytes(); } + availableImages.emplace(image_->id); images.emplace(image_->id, std::move(image_)); } bool ImageManager::updateImage(Immutable image_) { + std::lock_guard readWriteLock(rwLock); + auto oldImage = images.find(image_->id); assert(oldImage != images.end()); if (oldImage == images.end()) return false; @@ -74,8 +81,10 @@ bool ImageManager::updateImage(Immutable image_) { } void ImageManager::removeImage(const std::string& id) { + std::lock_guard readWriteLock(rwLock); auto it = images.find(id); assert(it != images.end()); + // Reduce cache size for requested images. auto requestedIt = requestedImages.find(it->second->id); if (requestedIt != requestedImages.end()) { @@ -83,12 +92,14 @@ void ImageManager::removeImage(const std::string& id) { requestedImagesCacheSize -= it->second->image.bytes(); requestedImages.erase(requestedIt); } + images.erase(it); availableImages.erase(id); updatedImageVersions.erase(id); } const style::Image::Impl* ImageManager::getImage(const std::string& id) const { + std::lock_guard readWriteLock(rwLock); if (auto* image = getSharedImage(id)) { return image->get(); } @@ -96,6 +107,7 @@ const style::Image::Impl* ImageManager::getImage(const std::string& id) const { } const Immutable* ImageManager::getSharedImage(const std::string& id) const { + std::lock_guard readWriteLock(rwLock); const auto it = images.find(id); if (it != images.end()) { return &(it->second); @@ -107,6 +119,8 @@ void ImageManager::getImages(ImageRequestor& requestor, ImageRequestPair&& pair) // remove previous requests from this tile removeRequestor(requestor); + std::lock_guard readWriteLock(rwLock); + // If all the icon dependencies are already present ((i.e. if they've been addeded via // runtime styling), then notify the requestor immediately. Otherwise, if the // sprite has not loaded, then wait for it. When the sprite has loaded check @@ -133,6 +147,8 @@ void ImageManager::getImages(ImageRequestor& requestor, ImageRequestPair&& pair) } void ImageManager::removeRequestor(ImageRequestor& requestor) { + std::lock_guard readWriteLock(rwLock); + requestors.erase(&requestor); missingImageRequestors.erase(&requestor); for (auto& requestedImage : requestedImages) { @@ -141,6 +157,8 @@ void ImageManager::removeRequestor(ImageRequestor& requestor) { } void ImageManager::notifyIfMissingImageAdded() { + std::lock_guard readWriteLock(rwLock); + for (auto it = missingImageRequestors.begin(); it != missingImageRequestors.end();) { ImageRequestor& requestor = *it->first; if (!requestor.hasPendingRequests()) { @@ -153,6 +171,8 @@ void ImageManager::notifyIfMissingImageAdded() { } void ImageManager::reduceMemoryUse() { + std::lock_guard readLock(rwLock); + std::vector unusedIDs; unusedIDs.reserve(requestedImages.size()); @@ -173,11 +193,18 @@ void ImageManager::reduceMemoryUseIfCacheSizeExceedsLimit() { } } -const std::set& ImageManager::getAvailableImages() const { - return availableImages; +std::set ImageManager::getAvailableImages() const { + std::set copy; + { + std::lock_guard readWriteLock(rwLock); + copy = availableImages; + } + return copy; } void ImageManager::clear() { + std::lock_guard readWriteLock(rwLock); + assert(requestors.empty()); assert(missingImageRequestors.empty()); @@ -229,6 +256,7 @@ void ImageManager::checkMissingAndNotify(ImageRequestor& requestor, const ImageR } auto removePendingRequests = [this, missingImage] { + std::lock_guard readWriteLock(rwLock); auto existingRequest = requestedImages.find(missingImage); if (existingRequest == requestedImages.end()) { return; @@ -278,11 +306,11 @@ void ImageManager::dumpDebugLogs() const { Log::Info(Event::General, ss.str()); } -ImageRequestor::ImageRequestor(ImageManager& imageManager_) - : imageManager(imageManager_) {} +ImageRequestor::ImageRequestor(std::shared_ptr imageManager_) + : imageManager(std::move(imageManager_)) {} ImageRequestor::~ImageRequestor() { - imageManager.removeRequestor(*this); + imageManager->removeRequestor(*this); } } // namespace mbgl diff --git a/src/mbgl/renderer/image_manager.hpp b/src/mbgl/renderer/image_manager.hpp index ad6715fe6e5..8890332daa7 100644 --- a/src/mbgl/renderer/image_manager.hpp +++ b/src/mbgl/renderer/image_manager.hpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace mbgl { @@ -48,7 +49,7 @@ class ImageManager { void notifyIfMissingImageAdded(); void reduceMemoryUse(); void reduceMemoryUseIfCacheSizeExceedsLimit(); - const std::set& getAvailableImages() const; + std::set getAvailableImages() const; ImageVersionMap updatedImageVersions; @@ -57,7 +58,6 @@ class ImageManager { private: void checkMissingAndNotify(ImageRequestor&, const ImageRequestPair&); void notify(ImageRequestor&, const ImageRequestPair&) const; - void removePattern(const std::string&); bool loaded = false; @@ -70,11 +70,13 @@ class ImageManager { std::set availableImages; ImageManagerObserver* observer = nullptr; + + mutable std::recursive_mutex rwLock; }; class ImageRequestor { public: - explicit ImageRequestor(ImageManager&); + explicit ImageRequestor(std::shared_ptr); virtual ~ImageRequestor(); virtual void onImagesAvailable(ImageMap icons, ImageMap patterns, @@ -87,7 +89,7 @@ class ImageRequestor { void removePendingRequest(const std::string& imageId) { pendingRequests.erase(imageId); } private: - ImageManager& imageManager; + std::shared_ptr imageManager; // Pending requests are image requests that are waiting to be dispatched to the client. std::set pendingRequests; diff --git a/src/mbgl/renderer/render_orchestrator.cpp b/src/mbgl/renderer/render_orchestrator.cpp index ecf41d8eccc..990becf88cb 100644 --- a/src/mbgl/renderer/render_orchestrator.cpp +++ b/src/mbgl/renderer/render_orchestrator.cpp @@ -121,7 +121,8 @@ RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_, const std:: sourceImpls(makeMutable>>()), layerImpls(makeMutable>>()), renderLight(makeMutable()), - backgroundLayerAsColor(backgroundLayerAsColor_) { + backgroundLayerAsColor(backgroundLayerAsColor_), + threadPool(Scheduler::GetBackground()) { glyphManager->setObserver(this); imageManager->setObserver(this); } @@ -137,6 +138,13 @@ RenderOrchestrator::~RenderOrchestrator() { layer.markContextDestroyed(); } } + + // Wait for any deferred cleanup tasks to complete before releasing and potentially + // destroying the scheduler. Those cleanup tasks must not hold the final reference + // to the scheduler because it cannot be destroyed from one of its own pool threads. + constexpr auto deferredCleanupTimeout = Milliseconds{1000}; + [[maybe_unused]] const auto remaining = threadPool->waitForEmpty(deferredCleanupTimeout); + assert(remaining == 0); } void RenderOrchestrator::setObserver(RendererObserver* observer_) { @@ -180,8 +188,8 @@ std::unique_ptr RenderOrchestrator::createRenderTree( updateParameters->fileSource, updateParameters->mode, updateParameters->annotationManager, - *imageManager, - *glyphManager, + imageManager, + glyphManager, updateParameters->prefetchZoomDelta}; glyphManager->setURL(updateParameters->glyphURL); @@ -311,7 +319,7 @@ std::unique_ptr RenderOrchestrator::createRenderTree( // Create render sources for newly added sources. for (const auto& entry : sourceDiff.added) { - std::unique_ptr renderSource = RenderSource::create(entry.second); + std::unique_ptr renderSource = RenderSource::create(entry.second, threadPool); renderSource->setObserver(this); renderSources.emplace(entry.first, std::move(renderSource)); } diff --git a/src/mbgl/renderer/render_orchestrator.hpp b/src/mbgl/renderer/render_orchestrator.hpp index 8df4d0b390b..69ff4667e6b 100644 --- a/src/mbgl/renderer/render_orchestrator.hpp +++ b/src/mbgl/renderer/render_orchestrator.hpp @@ -184,8 +184,8 @@ class RenderOrchestrator final : public GlyphManagerObserver, public ImageManage ZoomHistory zoomHistory; TransformState transformState; - std::unique_ptr glyphManager; - std::unique_ptr imageManager; + std::shared_ptr glyphManager; + std::shared_ptr imageManager; std::unique_ptr lineAtlas; std::unique_ptr patternAtlas; @@ -210,6 +210,8 @@ class RenderOrchestrator final : public GlyphManagerObserver, public ImageManage RenderLayerReferences orderedLayers; RenderLayerReferences layersNeedPlacement; + std::shared_ptr threadPool; + #if MLN_DRAWABLE_RENDERER std::vector> pendingChanges; diff --git a/src/mbgl/renderer/render_source.cpp b/src/mbgl/renderer/render_source.cpp index 624d89073e0..2eb5ce900e2 100644 --- a/src/mbgl/renderer/render_source.cpp +++ b/src/mbgl/renderer/render_source.cpp @@ -12,28 +12,36 @@ #include #include + +#include #include namespace mbgl { using namespace style; -std::unique_ptr RenderSource::create(const Immutable& impl) { +std::unique_ptr RenderSource::create(const Immutable& impl, + std::shared_ptr threadPool_) { switch (impl->type) { case SourceType::Vector: - return std::make_unique(staticImmutableCast(impl)); + return std::make_unique(staticImmutableCast(impl), + std::move(threadPool_)); case SourceType::Raster: - return std::make_unique(staticImmutableCast(impl)); + return std::make_unique(staticImmutableCast(impl), + std::move(threadPool_)); case SourceType::RasterDEM: - return std::make_unique(staticImmutableCast(impl)); + return std::make_unique(staticImmutableCast(impl), + std::move(threadPool_)); case SourceType::GeoJSON: - return std::make_unique(staticImmutableCast(impl)); + return std::make_unique(staticImmutableCast(impl), + std::move(threadPool_)); case SourceType::Video: assert(false); return nullptr; case SourceType::Annotations: if (LayerManager::annotationsEnabled) { - return std::make_unique(staticImmutableCast(impl)); + return std::make_unique(staticImmutableCast(impl), + std::move(threadPool_)); } else { assert(false); return nullptr; @@ -41,7 +49,8 @@ std::unique_ptr RenderSource::create(const Immutable case SourceType::Image: return std::make_unique(staticImmutableCast(impl)); case SourceType::CustomVector: - return std::make_unique(staticImmutableCast(impl)); + return std::make_unique(staticImmutableCast(impl), + std::move(threadPool_)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index 9df117c7751..4b46e07756b 100644 --- a/src/mbgl/renderer/render_source.hpp +++ b/src/mbgl/renderer/render_source.hpp @@ -17,20 +17,21 @@ namespace mbgl { +class CollisionIndex; +class ImageManager; +class ImageSourceRenderData; class PaintParameters; -class TransformState; -class RenderTile; -class RenderLayer; class RenderedQueryOptions; +class RenderItem; +class RenderLayer; +class RenderSourceObserver; +class RenderTile; +class Scheduler; class SourceQueryOptions; class Tile; -class RenderSourceObserver; class TileParameters; -class CollisionIndex; class TransformParameters; -class ImageManager; -class ImageSourceRenderData; -class RenderItem; +class TransformState; namespace gfx { class UploadPass; @@ -47,7 +48,7 @@ using RenderTiles = std::shared_ptr create(const Immutable&); + static std::unique_ptr create(const Immutable&, std::shared_ptr); ~RenderSource() override; bool isEnabled() const; diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp index e1e78b6930f..7f054bfa2c2 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -7,8 +7,9 @@ namespace mbgl { using namespace style; -RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable impl_) - : RenderTileSource(std::move(impl_)) { +RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable impl_, + std::shared_ptr threadPool_) + : RenderTileSource(std::move(impl_), std::move(threadPool_)) { tilePyramid.setObserver(this); } diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp index 559039e245b..4c77c6a1973 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderCustomGeometrySource final : public RenderTileSource { public: - explicit RenderCustomGeometrySource(Immutable); + explicit RenderCustomGeometrySource(Immutable, std::shared_ptr); void update(Immutable, const std::vector>&, diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index 024e5f96044..633df708879 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -65,8 +65,9 @@ MAPBOX_ETERNAL_CONSTEXPR const auto extensionGetters = } // namespace -RenderGeoJSONSource::RenderGeoJSONSource(Immutable impl_) - : RenderTileSource(std::move(impl_)) {} +RenderGeoJSONSource::RenderGeoJSONSource(Immutable impl_, + std::shared_ptr threadPool_) + : RenderTileSource(std::move(impl_), std::move(threadPool_)) {} RenderGeoJSONSource::~RenderGeoJSONSource() = default; diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp index ef2b3a1e7cb..f0c41c2ab50 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.hpp +++ b/src/mbgl/renderer/sources/render_geojson_source.hpp @@ -11,7 +11,7 @@ class GeoJSONData; class RenderGeoJSONSource final : public RenderTileSource { public: - explicit RenderGeoJSONSource(Immutable); + explicit RenderGeoJSONSource(Immutable, std::shared_ptr); ~RenderGeoJSONSource() override; void update(Immutable, diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.cpp b/src/mbgl/renderer/sources/render_raster_dem_source.cpp index 041127e6877..6f41426141e 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.cpp @@ -10,8 +10,9 @@ namespace mbgl { using namespace style; -RenderRasterDEMSource::RenderRasterDEMSource(Immutable impl_) - : RenderTileSetSource(std::move(impl_)) {} +RenderRasterDEMSource::RenderRasterDEMSource(Immutable impl_, + std::shared_ptr threadPool_) + : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} const style::RasterSource::Impl& RenderRasterDEMSource::impl() const { return static_cast(*baseImpl); diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.hpp b/src/mbgl/renderer/sources/render_raster_dem_source.hpp index 1da7d6f14a8..1599b76ef5b 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderRasterDEMSource final : public RenderTileSetSource { public: - explicit RenderRasterDEMSource(Immutable); + explicit RenderRasterDEMSource(Immutable, std::shared_ptr); std::unordered_map> queryRenderedFeatures( const ScreenLineString& geometry, diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index 2bfd6a23092..db99831e02c 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -8,8 +8,9 @@ namespace mbgl { using namespace style; -RenderRasterSource::RenderRasterSource(Immutable impl_) - : RenderTileSetSource(std::move(impl_)) {} +RenderRasterSource::RenderRasterSource(Immutable impl_, + std::shared_ptr threadPool_) + : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} inline const style::RasterSource::Impl& RenderRasterSource::impl() const { return static_cast(*baseImpl); diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index 47035e41b54..c827c8a7e0f 100644 --- a/src/mbgl/renderer/sources/render_raster_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderRasterSource final : public RenderTileSetSource { public: - explicit RenderRasterSource(Immutable); + explicit RenderRasterSource(Immutable, std::shared_ptr); private: void prepare(const SourcePrepareParameters&) final; diff --git a/src/mbgl/renderer/sources/render_tile_source.cpp b/src/mbgl/renderer/sources/render_tile_source.cpp index d4eceeccd6c..e3527efc63e 100644 --- a/src/mbgl/renderer/sources/render_tile_source.cpp +++ b/src/mbgl/renderer/sources/render_tile_source.cpp @@ -369,8 +369,9 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr } #endif -RenderTileSource::RenderTileSource(Immutable impl_) +RenderTileSource::RenderTileSource(Immutable impl_, std::shared_ptr threadPool_) : RenderSource(std::move(impl_)), + tilePyramid(std::move(threadPool_)), renderTiles(makeMutable>()) { tilePyramid.setObserver(this); } @@ -489,8 +490,8 @@ void RenderTileSource::dumpDebugLogs() const { // RenderTileSetSource implementation -RenderTileSetSource::RenderTileSetSource(Immutable impl_) - : RenderTileSource(std::move(impl_)) {} +RenderTileSetSource::RenderTileSetSource(Immutable impl_, std::shared_ptr threadPool_) + : RenderTileSource(std::move(impl_), std::move(threadPool_)) {} RenderTileSetSource::~RenderTileSetSource() = default; diff --git a/src/mbgl/renderer/sources/render_tile_source.hpp b/src/mbgl/renderer/sources/render_tile_source.hpp index dd10bd4ce36..160ed9541da 100644 --- a/src/mbgl/renderer/sources/render_tile_source.hpp +++ b/src/mbgl/renderer/sources/render_tile_source.hpp @@ -51,7 +51,7 @@ class RenderTileSource : public RenderSource { void dumpDebugLogs() const override; protected: - RenderTileSource(Immutable); + RenderTileSource(Immutable, std::shared_ptr); TilePyramid tilePyramid; Immutable> renderTiles; mutable RenderTiles filteredRenderTiles; @@ -67,7 +67,7 @@ class RenderTileSource : public RenderSource { */ class RenderTileSetSource : public RenderTileSource { protected: - RenderTileSetSource(Immutable); + RenderTileSetSource(Immutable, std::shared_ptr); ~RenderTileSetSource() override; virtual void updateInternal(const Tileset&, diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index 8134f4cc815..212338db670 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -8,8 +8,9 @@ namespace mbgl { using namespace style; -RenderVectorSource::RenderVectorSource(Immutable impl_) - : RenderTileSetSource(std::move(impl_)) {} +RenderVectorSource::RenderVectorSource(Immutable impl_, + std::shared_ptr threadPool_) + : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} const std::optional& RenderVectorSource::getTileset() const { return static_cast(*baseImpl).tileset; diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp index f826603c8a7..326b392b698 100644 --- a/src/mbgl/renderer/sources/render_vector_source.hpp +++ b/src/mbgl/renderer/sources/render_vector_source.hpp @@ -8,7 +8,7 @@ namespace mbgl { class RenderVectorSource final : public RenderTileSetSource { public: - explicit RenderVectorSource(Immutable); + explicit RenderVectorSource(Immutable, std::shared_ptr); private: void updateInternal(const Tileset&, diff --git a/src/mbgl/renderer/tile_parameters.hpp b/src/mbgl/renderer/tile_parameters.hpp index e76d8d0f836..b7ada67d636 100644 --- a/src/mbgl/renderer/tile_parameters.hpp +++ b/src/mbgl/renderer/tile_parameters.hpp @@ -22,8 +22,8 @@ class TileParameters { std::shared_ptr fileSource; const MapMode mode; mapbox::base::WeakPtr annotationManager; - ImageManager& imageManager; - GlyphManager& glyphManager; + std::shared_ptr imageManager; + std::shared_ptr glyphManager; const uint8_t prefetchZoomDelta; }; diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 350b4dd3fe4..55466aa3ada 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -23,8 +24,9 @@ using namespace style; static TileObserver nullObserver; -TilePyramid::TilePyramid() - : observer(&nullObserver) {} +TilePyramid::TilePyramid(std::shared_ptr threadPool_) + : cache(std::move(threadPool_)), + observer(&nullObserver) {} TilePyramid::~TilePyramid() = default; @@ -65,13 +67,15 @@ void TilePyramid::update(const std::vector>& l // If we're not going to render anything, move our existing tiles into // the cache (if they're not stale) or abandon them, and return. if (!needsRendering) { - if (!needsRelayout) { - for (auto& entry : tiles) { + for (auto& entry : tiles) { + if (!needsRelayout) { // These tiles are invisible, we set optional necessity // for them and thus suppress network requests on // tiles expiration (see `OnlineFileRequest`). entry.second->setNecessity(TileNecessity::Optional); cache.add(entry.first, std::move(entry.second)); + } else { + cache.deferredRelease(std::move(entry.second)); } } @@ -227,11 +231,17 @@ void TilePyramid::update(const std::vector>& l auto retainIt = retain.begin(); while (tilesIt != tiles.end()) { if (retainIt == retain.end() || tilesIt->first < *retainIt) { - if (!needsRelayout) { - tilesIt->second->setNecessity(TileNecessity::Optional); - cache.add(tilesIt->first, std::move(tilesIt->second)); + // Remove the tile from the map. + // If it requires re-layout, discard it asynchronously, otherwise keep it in the cache + const auto key = tilesIt->first; + if (std::unique_ptr tile = std::move(tiles.extract(tilesIt++).mapped())) { + if (needsRelayout) { + cache.deferredRelease(std::move(tile)); + } else { + tile->setNecessity(TileNecessity::Optional); + cache.add(key, std::move(tile)); + } } - tiles.erase(tilesIt++); } else { if (!(*retainIt < tilesIt->first)) { ++tilesIt; diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp index e50a0eee290..bfae55f0952 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -29,7 +29,7 @@ class SourcePrepareParameters; class TilePyramid { public: - TilePyramid(); + TilePyramid(std::shared_ptr threadPool_); ~TilePyramid(); bool isLoaded() const; diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp index f651185b654..b6727d2e891 100644 --- a/src/mbgl/text/glyph_manager.cpp +++ b/src/mbgl/text/glyph_manager.cpp @@ -21,33 +21,36 @@ GlyphManager::~GlyphManager() = default; void GlyphManager::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphDependencies, FileSource& fileSource) { auto dependencies = std::make_shared(std::move(glyphDependencies)); - // Figure out which glyph ranges need to be fetched. For each range that - // does need to be fetched, record an entry mapping the requestor to a - // shared pointer containing the dependencies. When the shared pointer - // becomes unique, we know that all the dependencies for that requestor have - // been fetched, and can notify it of completion. - for (const auto& dependency : *dependencies) { - const FontStack& fontStack = dependency.first; - Entry& entry = entries[fontStack]; - - const GlyphIDs& glyphIDs = dependency.second; - std::unordered_set ranges; - for (const auto& glyphID : glyphIDs) { - if (localGlyphRasterizer->canRasterizeGlyph(fontStack, glyphID)) { - if (entry.glyphs.find(glyphID) == entry.glyphs.end()) { - entry.glyphs.emplace(glyphID, makeMutable(generateLocalSDF(fontStack, glyphID))); + { + std::lock_guard readWriteLock(rwLock); + // Figure out which glyph ranges need to be fetched. For each range that + // does need to be fetched, record an entry mapping the requestor to a + // shared pointer containing the dependencies. When the shared pointer + // becomes unique, we know that all the dependencies for that requestor have + // been fetched, and can notify it of completion. + for (const auto& dependency : *dependencies) { + const FontStack& fontStack = dependency.first; + Entry& entry = entries[fontStack]; + + const GlyphIDs& glyphIDs = dependency.second; + std::unordered_set ranges; + for (const auto& glyphID : glyphIDs) { + if (localGlyphRasterizer->canRasterizeGlyph(fontStack, glyphID)) { + if (entry.glyphs.find(glyphID) == entry.glyphs.end()) { + entry.glyphs.emplace(glyphID, makeMutable(generateLocalSDF(fontStack, glyphID))); + } + } else { + ranges.insert(getGlyphRange(glyphID)); } - } else { - ranges.insert(getGlyphRange(glyphID)); } - } - for (const auto& range : ranges) { - auto it = entry.ranges.find(range); - if (it == entry.ranges.end() || !it->second.parsed) { - GlyphRequest& request = entry.ranges[range]; - request.requestors[&requestor] = dependencies; - requestRange(request, fontStack, range, fileSource); + for (const auto& range : ranges) { + auto it = entry.ranges.find(range); + if (it == entry.ranges.end() || !it->second.parsed) { + GlyphRequest& request = entry.ranges[range]; + request.requestors[&requestor] = dependencies; + requestRange(request, fontStack, range, fileSource); + } } } } @@ -88,39 +91,43 @@ void GlyphManager::processResponse(const Response& res, const FontStack& fontSta return; } - Entry& entry = entries[fontStack]; - GlyphRequest& request = entry.ranges[range]; + { + std::lock_guard readWriteLock(rwLock); - if (!res.noContent) { - std::vector glyphs; + Entry& entry = entries[fontStack]; + GlyphRequest& request = entry.ranges[range]; - try { - glyphs = parseGlyphPBF(range, *res.data); - } catch (...) { - observer->onGlyphsError(fontStack, range, std::current_exception()); - return; - } + if (!res.noContent) { + std::vector glyphs; + + try { + glyphs = parseGlyphPBF(range, *res.data); + } catch (...) { + observer->onGlyphsError(fontStack, range, std::current_exception()); + return; + } - for (auto& glyph : glyphs) { - auto id = glyph.id; - if (!localGlyphRasterizer->canRasterizeGlyph(fontStack, id)) { - entry.glyphs.erase(id); - entry.glyphs.emplace(id, makeMutable(std::move(glyph))); + for (auto& glyph : glyphs) { + auto id = glyph.id; + if (!localGlyphRasterizer->canRasterizeGlyph(fontStack, id)) { + entry.glyphs.erase(id); + entry.glyphs.emplace(id, makeMutable(std::move(glyph))); + } } } - } - request.parsed = true; + request.parsed = true; - for (auto& pair : request.requestors) { - GlyphRequestor& requestor = *pair.first; - const std::shared_ptr& dependencies = pair.second; - if (dependencies.unique()) { - notify(requestor, *dependencies); + for (auto& pair : request.requestors) { + GlyphRequestor& requestor = *pair.first; + const std::shared_ptr& dependencies = pair.second; + if (dependencies.unique()) { + notify(requestor, *dependencies); + } } - } - request.requestors.clear(); + request.requestors.clear(); + } observer->onGlyphsLoaded(fontStack, range); } @@ -153,6 +160,7 @@ void GlyphManager::notify(GlyphRequestor& requestor, const GlyphDependencies& gl } void GlyphManager::removeRequestor(GlyphRequestor& requestor) { + std::lock_guard readWriteLock(rwLock); for (auto& entry : entries) { for (auto& range : entry.second.ranges) { range.second.requestors.erase(&requestor); @@ -161,6 +169,7 @@ void GlyphManager::removeRequestor(GlyphRequestor& requestor) { } void GlyphManager::evict(const std::set& keep) { + std::lock_guard readWriteLock(rwLock); util::erase_if(entries, [&](const auto& entry) { return keep.count(entry.first) == 0; }); } diff --git a/src/mbgl/text/glyph_manager.hpp b/src/mbgl/text/glyph_manager.hpp index b01b630872f..23cb15f753e 100644 --- a/src/mbgl/text/glyph_manager.hpp +++ b/src/mbgl/text/glyph_manager.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -71,6 +72,8 @@ class GlyphManager { GlyphManagerObserver* observer = nullptr; std::unique_ptr localGlyphRasterizer; + + std::recursive_mutex rwLock; }; } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 297792a3180..1649b69f926 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -159,8 +160,9 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, std::string sourceID_, c : Tile(Kind::Geometry, id_), ImageRequestor(parameters.imageManager), sourceID(std::move(sourceID_)), + threadPool(Scheduler::GetBackground()), mailbox(std::make_shared(*Scheduler::GetCurrent())), - worker(Scheduler::GetBackground(), + worker(threadPool, ActorRef(*this, mailbox), id_, sourceID, @@ -175,8 +177,15 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, std::string sourceID_, c showCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision) {} GeometryTile::~GeometryTile() { - glyphManager.removeRequestor(*this); markObsolete(); + + glyphManager->removeRequestor(*this); + imageManager->removeRequestor(*this); + + if (layoutResult) { + threadPool->runOnRenderThread( + [layoutResult_{std::move(layoutResult)}, atlasTextures_{std::move(atlasTextures)}]() {}); + } } void GeometryTile::cancel() { @@ -185,6 +194,7 @@ void GeometryTile::cancel() { void GeometryTile::markObsolete() { obsolete = true; + mailbox->abandon(); } void GeometryTile::setError(std::exception_ptr err) { @@ -193,13 +203,17 @@ void GeometryTile::setError(std::exception_ptr err) { } void GeometryTile::setData(std::unique_ptr data_) { + if (obsolete) { + return; + } + // Mark the tile as pending again if it was complete before to prevent // signaling a complete state despite pending parse operations. pending = true; ++correlationID; worker.self().invoke( - &GeometryTileWorker::setData, std::move(data_), imageManager.getAvailableImages(), correlationID); + &GeometryTileWorker::setData, std::move(data_), imageManager->getAvailableImages(), correlationID); } void GeometryTile::reset() { @@ -238,7 +252,7 @@ void GeometryTile::setLayers(const std::vector>& laye ++correlationID; worker.self().invoke( - &GeometryTileWorker::setLayers, std::move(impls), imageManager.getAvailableImages(), correlationID); + &GeometryTileWorker::setLayers, std::move(impls), imageManager->getAvailableImages(), correlationID); } void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) { @@ -278,7 +292,7 @@ void GeometryTile::onGlyphsAvailable(GlyphMap glyphs) { void GeometryTile::getGlyphs(GlyphDependencies glyphDependencies) { if (fileSource) { - glyphManager.getGlyphs(*this, std::move(glyphDependencies), *fileSource); + glyphManager->getGlyphs(*this, std::move(glyphDependencies), *fileSource); } } @@ -294,7 +308,7 @@ void GeometryTile::onImagesAvailable(ImageMap images, } void GeometryTile::getImages(ImageRequestPair pair) { - imageManager.getImages(*this, std::move(pair)); + imageManager->getImages(*this, std::move(pair)); } std::shared_ptr GeometryTile::getFeatureIndex() const { diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index 23e4686ed2c..31aa70b668f 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -106,12 +106,14 @@ class GeometryTile : public Tile, public GlyphRequestor, public ImageRequestor { // Used to signal the worker that it should abandon parsing this tile as soon as possible. std::atomic obsolete{false}; - std::shared_ptr mailbox; + const std::shared_ptr threadPool; + + const std::shared_ptr mailbox; Actor worker; - std::shared_ptr fileSource; - GlyphManager& glyphManager; - ImageManager& imageManager; + const std::shared_ptr fileSource; + const std::shared_ptr glyphManager; + const std::shared_ptr imageManager; uint64_t correlationID = 0; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 57285f09901..5e3646eebfe 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -44,7 +45,9 @@ GeometryTileWorker::GeometryTileWorker(ActorRef self_, pixelRatio(pixelRatio_), showCollisionBoxes(showCollisionBoxes_) {} -GeometryTileWorker::~GeometryTileWorker() = default; +GeometryTileWorker::~GeometryTileWorker() { + Scheduler::GetBackground()->runOnRenderThread([renderData_{std::move(renderData)}]() {}); +} /* GeometryTileWorker is a state machine. This is its transition diagram. diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 77652e6e18b..c487155d5af 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -76,7 +76,7 @@ class Tile { // render data with the given properties. // // Returns `true` if the corresponding render layer data is present in this - // tile (and i.e. it was succesfully updated); returns `false` otherwise. + // tile (and i.e. it was successfully updated); returns `false` otherwise. virtual bool layerPropertiesUpdated(const Immutable& layerProperties) = 0; virtual void setShowCollisionBoxes(const bool) {} virtual void setLayers(const std::vector>&) {} diff --git a/src/mbgl/tile/tile_cache.cpp b/src/mbgl/tile/tile_cache.cpp index 09bfb928a8c..b4d65990fcf 100644 --- a/src/mbgl/tile/tile_cache.cpp +++ b/src/mbgl/tile/tile_cache.cpp @@ -1,4 +1,5 @@ #include +#include #include namespace mbgl { @@ -7,23 +8,66 @@ void TileCache::setSize(size_t size_) { size = size_; while (orderedKeys.size() > size) { - auto key = orderedKeys.front(); + const auto key = orderedKeys.front(); orderedKeys.remove(key); - tiles.erase(key); + + auto hit = tiles.find(key); + if (hit != tiles.end()) { + auto tile = std::move(hit->second); + tiles.erase(hit); + deferredRelease(std::move(tile)); + } } assert(orderedKeys.size() <= size); } -void TileCache::add(const OverscaledTileID& key, std::unique_ptr tile) { +namespace { + +/// This exists solely to prevent a problem where temporary lambda captures +/// are retained for the duration of the scope instead of being destroyed immediately. +template +struct CaptureWrapper { + CaptureWrapper(std::unique_ptr&& item_) + : item(std::move(item_)) {} + CaptureWrapper(const CaptureWrapper& other) + : item(other.item) {} + std::shared_ptr item; +}; +} // namespace + +void TileCache::deferredRelease(std::unique_ptr&& tile) { + tile->cancel(); + + // The `std::function` must be created in a separate statement from the `schedule` call. + // Creating a `std::function` from a lambda involves a copy, which is why we must use + // `shared_ptr` rather than `unique_ptr` for the capture. As a result, a temporary holds + // a reference until the construction is complete and the lambda is destroyed. + // If this temporary outlives the `schedule` call, and the function is executed immediately + // by a waiting thread and is already complete, that temporary reference ends up being the + // last one and the destruction actually occurs here on this thread. + std::function func{[tile_{CaptureWrapper{std::move(tile)}}]() { + }}; + + threadPool->schedule(std::move(func)); +} + +void TileCache::add(const OverscaledTileID& key, std::unique_ptr&& tile) { if (!tile->isRenderable() || !size) { + deferredRelease(std::move(tile)); return; } - // insert new or query existing tile - if (!tiles.emplace(key, std::move(tile)).second) { - // remove existing tile key + const auto result = tiles.insert(std::make_pair(key, std::unique_ptr{})); + if (result.second) { + // inserted + result.first->second = std::move(tile); + } else { + // already present + // remove existing tile key to move it to the end orderedKeys.remove(key); + // release the newly-provided item + deferredRelease(std::move(tile)); } // (re-)insert tile key as newest @@ -31,7 +75,7 @@ void TileCache::add(const OverscaledTileID& key, std::unique_ptr tile) { // purge oldest key/tile if necessary if (orderedKeys.size() > size) { - pop(orderedKeys.front()); + deferredRelease(pop(orderedKeys.front())); } assert(orderedKeys.size() <= size); @@ -49,10 +93,9 @@ Tile* TileCache::get(const OverscaledTileID& key) { std::unique_ptr TileCache::pop(const OverscaledTileID& key) { std::unique_ptr tile; - auto it = tiles.find(key); + const auto it = tiles.find(key); if (it != tiles.end()) { - tile = std::move(it->second); - tiles.erase(it); + tile = std::move(tiles.extract(it).mapped()); orderedKeys.remove(key); assert(tile->isRenderable()); } @@ -65,6 +108,9 @@ bool TileCache::has(const OverscaledTileID& key) { } void TileCache::clear() { + for (auto& item : tiles) { + deferredRelease(std::move(item.second)); + } orderedKeys.clear(); tiles.clear(); } diff --git a/src/mbgl/tile/tile_cache.hpp b/src/mbgl/tile/tile_cache.hpp index d5b295cd9ed..fe7f41f7b9f 100644 --- a/src/mbgl/tile/tile_cache.hpp +++ b/src/mbgl/tile/tile_cache.hpp @@ -9,22 +9,36 @@ namespace mbgl { +class Scheduler; + class TileCache { public: - TileCache(size_t size_ = 0) - : size(size_) {} + TileCache(std::shared_ptr threadPool_, size_t size_ = 0) + : threadPool(std::move(threadPool_)), + size(size_) {} + /// Change the maximum size of the cache. void setSize(size_t); - size_t getSize() const { return size; }; - void add(const OverscaledTileID& key, std::unique_ptr tile); + + /// Get the maximum size + size_t getMaxSize() const { return size; } + + /// Add a new tile with the given ID. + /// If a tile with the same ID is already present, it will be retained and the new one will be discarded. + void add(const OverscaledTileID& key, std::unique_ptr&& tile); + std::unique_ptr pop(const OverscaledTileID& key); Tile* get(const OverscaledTileID& key); bool has(const OverscaledTileID& key); void clear(); + /// Destroy a tile without blocking + void deferredRelease(std::unique_ptr&&); + private: std::map> tiles; std::list orderedKeys; + std::shared_ptr threadPool; size_t size; }; diff --git a/src/mbgl/tile/tile_loader.hpp b/src/mbgl/tile/tile_loader.hpp index 9709cf77278..6c07af8fd0b 100644 --- a/src/mbgl/tile/tile_loader.hpp +++ b/src/mbgl/tile/tile_loader.hpp @@ -3,6 +3,10 @@ #include #include +#include +#include +#include + namespace mbgl { class FileSource; @@ -48,6 +52,19 @@ class TileLoader { std::shared_ptr fileSource; std::unique_ptr request; TileUpdateParameters updateParameters{Duration::zero(), false}; + + /// @brief It's possible for async requests in flight to mess with the request + /// object at the same time as the loader's destructor. This construct is shared + /// with the request lambdas to ensure more tightly controlled synchronization + /// to prevent this from happening. + struct Shared { + std::shared_mutex requestLock; + std::atomic_bool aborted{false}; + }; + + // Allocated as a share_ptr so either the loader or request can outlive the + // other and still see this. + std::shared_ptr shared; }; } // namespace mbgl diff --git a/src/mbgl/tile/tile_loader_impl.hpp b/src/mbgl/tile/tile_loader_impl.hpp index cc1e50de1ce..68781d378b0 100644 --- a/src/mbgl/tile/tile_loader_impl.hpp +++ b/src/mbgl/tile/tile_loader_impl.hpp @@ -30,6 +30,9 @@ TileLoader::TileLoader(T& tile_, Resource::LoadingMethod::CacheOnly)), fileSource(parameters.fileSource) { assert(!request); + + shared = std::make_shared(); + if (!fileSource) { tile.setError(getCantLoadTileError()); return; @@ -55,7 +58,12 @@ TileLoader::TileLoader(T& tile_, } template -TileLoader::~TileLoader() = default; +TileLoader::~TileLoader() { + std::unique_lock lock(shared->requestLock); + shared->aborted = true; + tile.cancel(); + request.reset(); +}; template void TileLoader::setNecessity(TileNecessity newNecessity) { @@ -90,29 +98,36 @@ void TileLoader::loadFromCache() { } resource.loadingMethod = Resource::LoadingMethod::CacheOnly; - request = fileSource->request(resource, [this](const Response& res) { - request.reset(); - - tile.setTriedCache(); - - if (res.error && res.error->reason == Response::Error::Reason::NotFound) { - // When the cache-only request could not be satisfied, don't treat - // it as an error. A cache lookup could still return data, _and_ an - // error, in particular when we were able to find the data, but it - // is expired and the Cache-Control headers indicated that we aren't - // allowed to use expired responses. In this case, we still get the - // data which we can use in our conditional network request. - resource.priorModified = res.modified; - resource.priorExpires = res.expires; - resource.priorEtag = res.etag; - resource.priorData = res.data; - } else { - loadedData(res); - } - - if (necessity == TileNecessity::Required) { - loadFromNetwork(); - } + request = fileSource->request(resource, [this, shared_{shared}](const Response& res) { + do { + if (shared_->requestLock.try_lock_shared()) { + std::shared_lock lock(shared_->requestLock, std::adopt_lock); + if (shared_->aborted) return; + + request.reset(); + tile.setTriedCache(); + + if (res.error && res.error->reason == Response::Error::Reason::NotFound) { + // When the cache-only request could not be satisfied, don't treat + // it as an error. A cache lookup could still return data, _and_ an + // error, in particular when we were able to find the data, but it + // is expired and the Cache-Control headers indicated that we aren't + // allowed to use expired responses. In this case, we still get the + // data which we can use in our conditional network request. + resource.priorModified = res.modified; + resource.priorExpires = res.expires; + resource.priorEtag = res.etag; + resource.priorData = res.data; + } else { + loadedData(res); + } + + if (necessity == TileNecessity::Required) { + loadFromNetwork(); + } + break; + } + } while (!shared_->aborted); }); } @@ -164,7 +179,19 @@ void TileLoader::loadFromNetwork() { resource.minimumUpdateInterval = updateParameters.minimumUpdateInterval; resource.storagePolicy = updateParameters.isVolatile ? Resource::StoragePolicy::Volatile : Resource::StoragePolicy::Permanent; - request = fileSource->request(resource, [this](const Response& res) { loadedData(res); }); + + request = fileSource->request(resource, [this, shared_{shared}](const Response& res) { + do { + if (shared_->requestLock.try_lock_shared()) { + std::shared_lock lock(shared_->requestLock, std::adopt_lock); + if (shared_->aborted) return; + + request.reset(); + loadedData(res); + break; + } + } while (!shared_->aborted); + }); } } // namespace mbgl diff --git a/src/mbgl/util/thread_local.hpp b/src/mbgl/util/thread_local.hpp index 8b96dd3080c..a1b2002d817 100644 --- a/src/mbgl/util/thread_local.hpp +++ b/src/mbgl/util/thread_local.hpp @@ -11,7 +11,7 @@ class ThreadLocalBase { ThreadLocalBase(); ~ThreadLocalBase(); - void* get(); + void* get() const; void set(void*); private: @@ -27,7 +27,7 @@ class ThreadLocal : public impl::ThreadLocalBase { ThreadLocal(T* val) { set(val); } - T* get() { return reinterpret_cast(impl::ThreadLocalBase::get()); } + T* get() const { return reinterpret_cast(impl::ThreadLocalBase::get()); } void set(T* ptr) { impl::ThreadLocalBase::set(ptr); } }; diff --git a/src/mbgl/util/thread_pool.cpp b/src/mbgl/util/thread_pool.cpp index e1c18a45b58..4db3e322bee 100644 --- a/src/mbgl/util/thread_pool.cpp +++ b/src/mbgl/util/thread_pool.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,11 +11,16 @@ namespace mbgl { ThreadedSchedulerBase::~ThreadedSchedulerBase() = default; void ThreadedSchedulerBase::terminate() { + // Run any leftover render jobs + runRenderJobs(); + { std::lock_guard lock(mutex); terminated = true; } - cv.notify_all(); + + // Wake up all threads so that they shut down + cvAvailable.notify_all(); } std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { @@ -25,13 +31,18 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { platform::setCurrentThreadPriority(*priority); } - platform::setCurrentThreadName(std::string{"Worker "} + util::toString(index + 1)); + platform::setCurrentThreadName("Worker " + util::toString(index + 1)); platform::attachThread(); + owningThreadPool.set(this); + while (true) { std::unique_lock lock(mutex); + if (queue.empty() && !pendingItems) { + cvEmpty.notify_all(); + } - cv.wait(lock, [this] { return !queue.empty() || terminated; }); + cvAvailable.wait(lock, [this] { return !queue.empty() || terminated; }); if (terminated) { platform::detachThread(); @@ -40,20 +51,83 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { auto function = std::move(queue.front()); queue.pop(); + + if (function) { + pendingItems++; + } + lock.unlock(); - if (function) function(); + + if (function) { + const auto cleanup = [&] { + // destroy the function and release its captures before unblocking `waitForEmpty` + function = {}; + pendingItems--; + if (queue.empty() && !pendingItems) { + cvEmpty.notify_all(); + } + }; + try { + function(); + cleanup(); + } catch (...) { + lock.lock(); + if (handler) { + handler(std::current_exception()); + } + cleanup(); + if (handler) { + continue; + } + throw; + } + } } }); } -void ThreadedSchedulerBase::schedule(std::function fn) { +void ThreadedSchedulerBase::schedule(std::function&& fn) { assert(fn); - { - std::lock_guard lock(mutex); - queue.push(std::move(fn)); + if (fn) { + { + // We need to block if adding adding a new task from a thread not controlled by this + // pool. Tasks are added by other tasks, so we must not block a thread we do control + // or `waitForEmpty` will deadlock. + std::unique_lock addLock(addMutex, std::defer_lock); + if (!thisThreadIsOwned()) { + addLock.lock(); + } + std::lock_guard lock(mutex); + queue.push(std::move(fn)); + } + cvAvailable.notify_one(); } +} - cv.notify_one(); +std::size_t ThreadedSchedulerBase::waitForEmpty(Milliseconds timeout) { + // Must not be called from a thread in our pool, or we would deadlock + assert(!thisThreadIsOwned()); + if (!thisThreadIsOwned()) { + const auto startTime = util::MonotonicTimer::now(); + const auto isDone = [&] { + return queue.empty() && pendingItems == 0; + }; + // Block any other threads from adding new items + std::scoped_lock addLock(addMutex); + std::unique_lock lock(mutex); + while (!isDone()) { + if (timeout > Milliseconds::zero()) { + const auto elapsed = util::MonotonicTimer::now() - startTime; + if (timeout <= elapsed || !cvEmpty.wait_for(lock, timeout - elapsed, isDone)) { + break; + } + } else { + cvEmpty.wait(lock, isDone); + } + } + return queue.size() + pendingItems; + } + return 0; } } // namespace mbgl diff --git a/src/mbgl/util/thread_pool.hpp b/src/mbgl/util/thread_pool.hpp index 9791ff94601..ee0735082c9 100644 --- a/src/mbgl/util/thread_pool.hpp +++ b/src/mbgl/util/thread_pool.hpp @@ -2,18 +2,20 @@ #include #include +#include -#include +#include #include #include #include #include +#include namespace mbgl { class ThreadedSchedulerBase : public Scheduler { public: - void schedule(std::function) override; + void schedule(std::function&&) override; protected: ThreadedSchedulerBase() = default; @@ -22,9 +24,27 @@ class ThreadedSchedulerBase : public Scheduler { void terminate(); std::thread makeSchedulerThread(size_t index); + /// Wait until there's nothing pending or in process + /// Must not be called from a task provided to this scheduler. + /// @param timeout Time to wait, or zero to wait forever. + std::size_t waitForEmpty(Milliseconds timeout) override; + + /// Returns true if called from a thread managed by the scheduler + bool thisThreadIsOwned() const { return owningThreadPool.get() == this; } + std::queue> queue; + // protects `queue` std::mutex mutex; - std::condition_variable cv; + // Used to block addition of new items while waiting + std::mutex addMutex; + // Signal when an item is added to the queue + std::condition_variable cvAvailable; + // Signal when the queue becomes empty + std::condition_variable cvEmpty; + // Count of functions removed from the queue but still executing + std::atomic pendingItems{0}; + // Points to the owning pool in owned threads + util::ThreadLocal owningThreadPool; bool terminated{false}; }; @@ -36,16 +56,17 @@ class ThreadedSchedulerBase : public Scheduler { * Note: If N == 1 all scheduled tasks are guaranteed to execute consequently; * otherwise, some of the scheduled tasks might be executed in parallel. */ -template class ThreadedScheduler : public ThreadedSchedulerBase { public: - ThreadedScheduler() { - for (std::size_t i = 0u; i < N; ++i) { + ThreadedScheduler(std::size_t n) + : threads(n) { + for (std::size_t i = 0u; i < threads.size(); ++i) { threads[i] = makeSchedulerThread(i); } } ~ThreadedScheduler() override { + assert(!thisThreadIsOwned()); terminate(); for (auto& thread : threads) { assert(std::this_thread::get_id() != thread.get_id()); @@ -53,19 +74,48 @@ class ThreadedScheduler : public ThreadedSchedulerBase { } } + void runOnRenderThread(std::function&& fn) override { + std::lock_guard lock(renderMutex); + renderThreadQueue.push(std::move(fn)); + } + + void runRenderJobs() override { + std::lock_guard lock(renderMutex); + while (renderThreadQueue.size()) { + auto fn = std::move(renderThreadQueue.front()); + renderThreadQueue.pop(); + if (fn) { + fn(); + } + } + } + mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } private: - std::array threads; + std::vector threads; mapbox::base::WeakPtrFactory weakFactory{this}; - static_assert(N > 0, "Thread count must be more than zero."); + + std::queue> renderThreadQueue; + std::mutex renderMutex; }; -class SequencedScheduler : public ThreadedScheduler<1> {}; +class SequencedScheduler : public ThreadedScheduler { +public: + SequencedScheduler() + : ThreadedScheduler(1) {} +}; -template -using ParallelScheduler = ThreadedScheduler<1 + extra>; +class ParallelScheduler : public ThreadedScheduler { +public: + ParallelScheduler(std::size_t extra) + : ThreadedScheduler(1 + extra) {} +}; -class ThreadPool : public ParallelScheduler<3> {}; +class ThreadPool : public ParallelScheduler { +public: + ThreadPool() + : ParallelScheduler(3) {} +}; } // namespace mbgl diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 8c3c3046b26..2848fce98cf 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -2,9 +2,7 @@ load("//bazel:flags.bzl", "CPP_FLAGS", "MAPLIBRE_FLAGS") cc_library( name = "testutils", - hdrs = [ - "include/mbgl/test/util.hpp", - ], + hdrs = glob(["include/mbgl/test/*.hpp"]), strip_include_prefix = "include", deps = [ "//vendor/googletest:gtest", diff --git a/test/actor/actor.test.cpp b/test/actor/actor.test.cpp index 033a29e86e2..70e5f70481c 100644 --- a/test/actor/actor.test.cpp +++ b/test/actor/actor.test.cpp @@ -91,7 +91,12 @@ TEST(Actor, DestructionBlocksOnSend) { ~TestScheduler() override { EXPECT_TRUE(waited.load()); } - void schedule(std::function) final { + std::size_t waitForEmpty(Milliseconds) override { + assert(false); + return 0; + } + + void schedule(std::function&&) final { promise.set_value(); future.wait(); std::this_thread::sleep_for(1ms); diff --git a/test/include/mbgl/test/vector_tile_test.hpp b/test/include/mbgl/test/vector_tile_test.hpp new file mode 100644 index 00000000000..63b45aba639 --- /dev/null +++ b/test/include/mbgl/test/vector_tile_test.hpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace mbgl { + +class VectorTileTest { +public: + std::shared_ptr fileSource = std::make_shared(); + TransformState transformState; + util::RunLoop loop; + style::Style style{fileSource, 1}; + AnnotationManager annotationManager{style}; + + const std::shared_ptr imageManager = std::make_shared(); + const std::shared_ptr glyphManager = std::make_shared(); + + Tileset tileset{{"https://example.com"}, {0, 22}, "none"}; + + const std::shared_ptr threadPool = Scheduler::GetBackground(); + + TileParameters tileParameters{1.0, + MapDebugOptions(), + transformState, + fileSource, + MapMode::Continuous, + annotationManager.makeWeakPtr(), + imageManager, + glyphManager, + 0}; + + ~VectorTileTest() { + // Ensure that deferred releases are complete before cleaning up + EXPECT_EQ(0, loop.waitForEmpty(Milliseconds::zero())); + EXPECT_EQ(0, threadPool->waitForEmpty()); + } +}; + +} // namespace mbgl diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp index 81050434ef0..856fd2999e6 100644 --- a/test/map/map.test.cpp +++ b/test/map/map.test.cpp @@ -1451,11 +1451,14 @@ TEST(Map, KeepRenderData) { test.map.getStyle().loadURL("maptiler://maps/streets"); const int iterations = 3; const int resourcesCount = 4 /*tiles*/; + + requestsCount = 0; // Keep render data. for (int i = 1; i <= iterations; ++i) { test.frontend.render(test.map); EXPECT_EQ(resourcesCount, requestsCount); } + requestsCount = 0; // Clear render data. for (int i = 1; i <= iterations; ++i) { diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp index 7eafee86a86..c5d82527cc9 100644 --- a/test/renderer/image_manager.test.cpp +++ b/test/renderer/image_manager.test.cpp @@ -33,6 +33,8 @@ TEST(ImageManager, Basic) { ASSERT_TRUE(stored); EXPECT_EQ(image->image.size, stored->image.size); } + + imageManager.dumpDebugLogs(); } TEST(ImageManager, AddRemove) { @@ -79,7 +81,7 @@ TEST(ImageManager, RemoveReleasesBinPackRect) { class StubImageRequestor : public ImageRequestor { public: - StubImageRequestor(ImageManager& imageManager_) + StubImageRequestor(std::shared_ptr imageManager_) : ImageRequestor(imageManager_) {} void onImagesAvailable(ImageMap icons, @@ -95,8 +97,9 @@ class StubImageRequestor : public ImageRequestor { TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) { util::RunLoop runLoop; - ImageManager imageManager; - StubImageRequestor requestor(imageManager); + auto imageManagerPtr = std::make_shared(); + auto& imageManager = *imageManagerPtr; + StubImageRequestor requestor(imageManagerPtr); bool notified = false; ImageManagerObserver observer; @@ -123,8 +126,9 @@ TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) { } TEST(ImageManager, NotifiesRequestorImmediatelyIfDependenciesAreSatisfied) { - ImageManager imageManager; - StubImageRequestor requestor(imageManager); + auto imageManagerPtr = std::make_shared(); + auto& imageManager = *imageManagerPtr; + StubImageRequestor requestor(imageManagerPtr); bool notified = false; requestor.imagesAvailable = [&](ImageMap, ImageMap, std::unordered_map) { @@ -160,8 +164,9 @@ class StubImageManagerObserver : public ImageManagerObserver { TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) { util::RunLoop runLoop; - ImageManager imageManager; - StubImageRequestor requestor(imageManager); + auto imageManagerPtr = std::make_shared(); + auto& imageManager = *imageManagerPtr; + StubImageRequestor requestor(imageManagerPtr); StubImageManagerObserver observer; imageManager.setObserver(&observer); @@ -203,7 +208,7 @@ TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) { ASSERT_FALSE(requestor.hasPendingRequests()); // Another requestor shall not have pending requests for already obtained images. - StubImageRequestor anotherRequestor(imageManager); + StubImageRequestor anotherRequestor(imageManagerPtr); imageManager.getImages(anotherRequestor, std::make_pair(dependencies, ++imageCorrelationID)); ASSERT_FALSE(anotherRequestor.hasPendingRequests()); @@ -215,8 +220,9 @@ TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) { TEST(ImageManager, OnStyleImageMissingAfterSpriteLoaded) { util::RunLoop runLoop; - ImageManager imageManager; - StubImageRequestor requestor(imageManager); + auto imageManagerPtr = std::make_shared(); + auto& imageManager = *imageManagerPtr; + StubImageRequestor requestor(imageManagerPtr); StubImageManagerObserver observer; imageManager.setObserver(&observer); @@ -251,7 +257,8 @@ TEST(ImageManager, OnStyleImageMissingAfterSpriteLoaded) { TEST(ImageManager, RemoveUnusedStyleImages) { util::RunLoop runLoop; - ImageManager imageManager; + auto imageManagerPtr = std::make_shared(); + auto& imageManager = *imageManagerPtr; StubImageManagerObserver observer; imageManager.setObserver(&observer); imageManager.setLoaded(true); @@ -276,7 +283,7 @@ TEST(ImageManager, RemoveUnusedStyleImages) { // Single requestor { - std::unique_ptr requestor = std::make_unique(imageManager); + std::unique_ptr requestor = std::make_unique(imageManagerPtr); imageManager.getImages(*requestor, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull)); runLoop.runOnce(); EXPECT_EQ(observer.count, 1); @@ -296,7 +303,7 @@ TEST(ImageManager, RemoveUnusedStyleImages) { // Single requestor, exceed cache size limit. { - std::unique_ptr requestor = std::make_unique(imageManager); + std::unique_ptr requestor = std::make_unique(imageManagerPtr); imageManager.getImages(*requestor, std::make_pair(ImageDependencies{{"1024px", ImageType::Icon}}, 0ull)); runLoop.runOnce(); EXPECT_EQ(observer.count, 2); @@ -311,8 +318,8 @@ TEST(ImageManager, RemoveUnusedStyleImages) { // Multiple requestors { - std::unique_ptr requestor1 = std::make_unique(imageManager); - std::unique_ptr requestor2 = std::make_unique(imageManager); + std::unique_ptr requestor1 = std::make_unique(imageManagerPtr); + std::unique_ptr requestor2 = std::make_unique(imageManagerPtr); imageManager.getImages(*requestor1, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull)); imageManager.getImages(*requestor2, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 1ull)); runLoop.runOnce(); @@ -330,9 +337,9 @@ TEST(ImageManager, RemoveUnusedStyleImages) { // Multiple requestors, check that image resource is not destroyed if there // is at least 1 requestor that uses it. - std::unique_ptr requestor = std::make_unique(imageManager); + std::unique_ptr requestor = std::make_unique(imageManagerPtr); { - std::unique_ptr requestor1 = std::make_unique(imageManager); + std::unique_ptr requestor1 = std::make_unique(imageManagerPtr); imageManager.getImages( *requestor, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}, {"1024px", ImageType::Icon}}, 0ull)); diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index b14f53af199..0a2a52b5afd 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -62,8 +62,9 @@ class SourceTest { TransformState transformState; Style style{fileSource, 1}; AnnotationManager annotationManager{style}; - ImageManager imageManager; - GlyphManager glyphManager; + std::shared_ptr imageManager = std::make_shared(); + std::shared_ptr glyphManager = std::make_shared(); + std::shared_ptr threadPool = Scheduler::GetBackground(); TileParameters tileParameters(MapMode mapMode = MapMode::Continuous) { return {1.0, @@ -87,6 +88,8 @@ class SourceTest { transformState = transform.getState(); } + ~SourceTest() { threadPool->waitForEmpty(); } + void run() { loop.run(); } void end() { loop.stop(); } @@ -167,7 +170,7 @@ TEST(Source, RasterTileEmpty) { FAIL() << "Should never be called"; }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -203,7 +206,7 @@ TEST(Source, RasterDEMTileEmpty) { FAIL() << "Should never be called"; }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -241,7 +244,7 @@ TEST(Source, VectorTileEmpty) { FAIL() << "Should never be called"; }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -276,7 +279,7 @@ TEST(Source, RasterTileFail) { test.end(); }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -311,7 +314,7 @@ TEST(Source, RasterDEMTileFail) { test.end(); }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -348,7 +351,7 @@ TEST(Source, VectorTileFail) { test.end(); }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -384,7 +387,7 @@ TEST(Source, RasterTileCorrupt) { test.end(); }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -421,7 +424,7 @@ TEST(Source, RasterDEMTileCorrupt) { test.end(); }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -458,7 +461,7 @@ TEST(Source, VectorTileCorrupt) { test.end(); }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -492,7 +495,7 @@ TEST(Source, RasterTileCancel) { FAIL() << "Should never be called"; }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -526,7 +529,7 @@ TEST(Source, RasterDEMTileCancel) { FAIL() << "Should never be called"; }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -562,7 +565,7 @@ TEST(Source, VectorTileCancel) { FAIL() << "Should never be called"; }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -607,7 +610,7 @@ TEST(Source, RasterTileAttribution) { source.setObserver(&test.styleObserver); source.loadDescription(*test.fileSource); - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); test.run(); @@ -648,7 +651,7 @@ TEST(Source, RasterDEMTileAttribution) { source.setObserver(&test.styleObserver); source.loadDescription(*test.fileSource); - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); test.run(); @@ -741,7 +744,7 @@ TEST(Source, CustomGeometrySourceSetTileData) { FAIL() << "Should never be called"; }; - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); @@ -778,8 +781,8 @@ class FakeTileSource : public RenderTileSetSource { MOCK_METHOD1(tileSetNecessity, void(TileNecessity)); MOCK_METHOD1(tileSetMinimumUpdateInterval, void(Duration)); - explicit FakeTileSource(Immutable impl_) - : RenderTileSetSource(std::move(impl_)) {} + explicit FakeTileSource(Immutable impl_, std::shared_ptr threadPool_) + : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} void updateInternal(const Tileset& tileset, const std::vector>& layers, const bool needsRendering, @@ -816,7 +819,7 @@ TEST(Source, InvisibleSourcesTileNecessity) { VectorSource initialized("source", Tileset{{"tiles"}}); initialized.loadDescription(*test.fileSource); - FakeTileSource renderTilesetSource{initialized.baseImpl}; + FakeTileSource renderTilesetSource{initialized.baseImpl, test.threadPool}; RenderSource* renderSource = &renderTilesetSource; LineLayer layer("id", "source"); Immutable layerProperties = makeMutable( @@ -839,7 +842,7 @@ TEST(Source, SourceMinimumUpdateInterval) { VectorSource initialized("source", Tileset{{"tiles"}}); initialized.loadDescription(*test.fileSource); - FakeTileSource renderTilesetSource{initialized.baseImpl}; + FakeTileSource renderTilesetSource{initialized.baseImpl, test.threadPool}; RenderSource* renderSource = &renderTilesetSource; LineLayer layer("id", "source"); Immutable layerProperties = makeMutable( @@ -882,8 +885,8 @@ TEST(Source, RenderTileSetSourceUpdate) { class FakeRenderTileSetSource : public RenderTileSetSource { public: - explicit FakeRenderTileSetSource(Immutable impl_) - : RenderTileSetSource(std::move(impl_)) {} + explicit FakeRenderTileSetSource(Immutable impl_, std::shared_ptr threadPool_) + : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} MOCK_METHOD0(mockedUpdateInternal, void()); @@ -903,7 +906,7 @@ TEST(Source, RenderTileSetSourceUpdate) { VectorSource initialized("source", Tileset{{"tiles"}}); initialized.loadDescription(*test.fileSource); - FakeRenderTileSetSource renderTilesetSource{initialized.baseImpl}; + FakeRenderTileSetSource renderTilesetSource{initialized.baseImpl, test.threadPool}; LineLayer layer("id", "source"); Immutable layerProperties = makeMutable( @@ -969,7 +972,7 @@ TEST(Source, GeoJSONSourceTilesAfterDataReset) { auto geoJSONData = GeoJSONData::create(mapbox::geojson::parse( R"({"geometry": {"type": "Point", "coordinates": [1.1, 1.1]}, "type": "Feature", "properties": {}})")); source.setGeoJSONData(geoJSONData); - RenderGeoJSONSource renderSource{staticImmutableCast(source.baseImpl)}; + RenderGeoJSONSource renderSource{staticImmutableCast(source.baseImpl), test.threadPool}; CircleLayer layer("id", "source"); Immutable layerProperties = makeMutable( @@ -1026,7 +1029,7 @@ TEST(Source, SetMaxParentOverscaleFactor) { ASSERT_EQ(3, *source.getMaxOverscaleFactorForParentTiles()); source.loadDescription(*test.fileSource); - auto renderSource = RenderSource::create(source.baseImpl); + auto renderSource = RenderSource::create(source.baseImpl, test.threadPool); renderSource->setObserver(&test.renderSourceObserver); renderSource->update(source.baseImpl, layers, true, true, test.tileParameters()); diff --git a/test/tile/custom_geometry_tile.test.cpp b/test/tile/custom_geometry_tile.test.cpp index ae48d9aaa15..d80e4cd878b 100644 --- a/test/tile/custom_geometry_tile.test.cpp +++ b/test/tile/custom_geometry_tile.test.cpp @@ -27,8 +27,8 @@ class CustomTileTest { util::RunLoop loop; style::Style style{fileSource, 1}; AnnotationManager annotationManager{style}; - ImageManager imageManager; - GlyphManager glyphManager; + std::shared_ptr imageManager = std::make_shared(); + std::shared_ptr glyphManager = std::make_shared(); TileParameters tileParameters{1.0, MapDebugOptions(), diff --git a/test/tile/geojson_tile.test.cpp b/test/tile/geojson_tile.test.cpp index ad2eb682f93..2db82f01e50 100644 --- a/test/tile/geojson_tile.test.cpp +++ b/test/tile/geojson_tile.test.cpp @@ -27,8 +27,8 @@ class GeoJSONTileTest { util::RunLoop loop; style::Style style{fileSource, 1}; AnnotationManager annotationManager{style}; - ImageManager imageManager; - GlyphManager glyphManager; + std::shared_ptr imageManager = std::make_shared(); + std::shared_ptr glyphManager = std::make_shared(); Tileset tileset{{"https://example.com"}, {0, 22}, "none"}; TileParameters tileParameters{1.0, diff --git a/test/tile/raster_dem_tile.test.cpp b/test/tile/raster_dem_tile.test.cpp index 90803d97ffe..d727d3c777c 100644 --- a/test/tile/raster_dem_tile.test.cpp +++ b/test/tile/raster_dem_tile.test.cpp @@ -21,8 +21,8 @@ class RasterDEMTileTest { util::RunLoop loop; style::Style style{fileSource, 1}; AnnotationManager annotationManager{style}; - ImageManager imageManager; - GlyphManager glyphManager; + std::shared_ptr imageManager = std::make_shared(); + std::shared_ptr glyphManager = std::make_shared(); Tileset tileset{{"https://example.com"}, {0, 22}, "none"}; TileParameters tileParameters{1.0, diff --git a/test/tile/raster_tile.test.cpp b/test/tile/raster_tile.test.cpp index 34aba76f175..213801d20d1 100644 --- a/test/tile/raster_tile.test.cpp +++ b/test/tile/raster_tile.test.cpp @@ -21,8 +21,8 @@ class RasterTileTest { util::RunLoop loop; style::Style style{fileSource, 1}; AnnotationManager annotationManager{style}; - ImageManager imageManager; - GlyphManager glyphManager; + std::shared_ptr imageManager = std::make_shared(); + std::shared_ptr glyphManager = std::make_shared(); Tileset tileset{{"https://example.com"}, {0, 22}, "none"}; TileParameters tileParameters{1.0, diff --git a/test/tile/tile_cache.test.cpp b/test/tile/tile_cache.test.cpp index 64444f91cf6..51c0c92ef8f 100644 --- a/test/tile/tile_cache.test.cpp +++ b/test/tile/tile_cache.test.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -16,34 +17,16 @@ #include #include #include +#include #include #include +#include #include using namespace mbgl; -class VectorTileTest { -public: - std::shared_ptr fileSource = std::make_shared(); - TransformState transformState; - util::RunLoop loop; - style::Style style{fileSource, 1}; - AnnotationManager annotationManager{style}; - ImageManager imageManager; - GlyphManager glyphManager; - Tileset tileset{{"https://example.com"}, {0, 22}, "none"}; - - TileParameters tileParameters{1.0, - MapDebugOptions(), - transformState, - fileSource, - MapMode::Continuous, - annotationManager.makeWeakPtr(), - imageManager, - glyphManager, - 0}; -}; +namespace { class VectorTileMock : public VectorTile { public: @@ -51,16 +34,20 @@ class VectorTileMock : public VectorTile { std::string sourceID_, const TileParameters& parameters, const Tileset& tileset) - : VectorTile(id_, sourceID_, parameters, tileset) { + : VectorTile(id_, std::move(sourceID_), parameters, tileset) { renderable = true; } + + util::SimpleIdentity uniqueId; }; +} // namespace + TEST(TileCache, Smoke) { VectorTileTest test; - TileCache cache(1); - OverscaledTileID id(0, 0, 0); - std::unique_ptr tile = std::make_unique(id, "source", test.tileParameters, test.tileset); + TileCache cache(Scheduler::GetBackground(), 1); + const OverscaledTileID id(0, 0, 0); + auto tile = std::make_unique(id, "source", test.tileParameters, test.tileset); cache.add(id, std::move(tile)); EXPECT_TRUE(cache.has(id)); @@ -70,18 +57,36 @@ TEST(TileCache, Smoke) { TEST(TileCache, Issue15926) { VectorTileTest test; - TileCache cache(2); - OverscaledTileID id0(0, 0, 0); - OverscaledTileID id1(1, 0, 0); - std::unique_ptr tile1 = std::make_unique(id0, "source", test.tileParameters, test.tileset); - std::unique_ptr tile2 = std::make_unique(id0, "source", test.tileParameters, test.tileset); - std::unique_ptr tile3 = std::make_unique(id1, "source", test.tileParameters, test.tileset); + TileCache cache(test.threadPool, 2); + const OverscaledTileID id0(0, 0, 0); + const OverscaledTileID id1(1, 0, 0); + auto tile1 = std::make_unique(id0, "source", test.tileParameters, test.tileset); + auto tile2 = std::make_unique(id0, "source", test.tileParameters, test.tileset); + auto tile3 = std::make_unique(id1, "source", test.tileParameters, test.tileset); + auto tile4 = std::make_unique(id0, "source", test.tileParameters, test.tileset); + const auto tile1Id = tile1->uniqueId; + // add cache.add(id0, std::move(tile1)); EXPECT_TRUE(cache.has(id0)); + + // adding a key already present doesn't replace the existing item cache.add(id0, std::move(tile2)); + EXPECT_EQ(tile1Id, static_cast(cache.get(id0))->uniqueId); + + // Evict on add cache.setSize(1); cache.add(id1, std::move(tile3)); EXPECT_FALSE(cache.has(id0)); EXPECT_TRUE(cache.has(id1)); + + // Evict due to size limit change + cache.setSize(2); + cache.add(id0, std::move(tile4)); + EXPECT_TRUE(cache.has(id0)); + EXPECT_TRUE(cache.has(id1)); + cache.setSize(1); + // older item should be evicted + EXPECT_TRUE(cache.has(id0)); + EXPECT_FALSE(cache.has(id1)); } diff --git a/test/tile/vector_tile.test.cpp b/test/tile/vector_tile.test.cpp index 375d6017a6b..0c1a8286bd2 100644 --- a/test/tile/vector_tile.test.cpp +++ b/test/tile/vector_tile.test.cpp @@ -15,35 +15,13 @@ #include #include #include +#include #include #include using namespace mbgl; -class VectorTileTest { -public: - std::shared_ptr fileSource = std::make_shared(ResourceOptions::Default(), - ClientOptions()); - TransformState transformState; - util::RunLoop loop; - style::Style style{fileSource, 1}; - AnnotationManager annotationManager{style}; - ImageManager imageManager; - GlyphManager glyphManager; - Tileset tileset{{"https://example.com"}, {0, 22}, "none"}; - - TileParameters tileParameters{1.0, - MapDebugOptions(), - transformState, - fileSource, - MapMode::Continuous, - annotationManager.makeWeakPtr(), - imageManager, - glyphManager, - 0}; -}; - TEST(VectorTile, setError) { VectorTileTest test; VectorTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, test.tileset); diff --git a/test/util/thread.test.cpp b/test/util/thread.test.cpp index 5ea98b36b6f..01bfce2304b 100644 --- a/test/util/thread.test.cpp +++ b/test/util/thread.test.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -134,6 +135,11 @@ TEST(Thread, Concurrency) { unsigned numMessages = 100000; std::atomic_uint completed(numMessages); + auto& settings = platform::Settings::getInstance(); + if (!settings.get(platform::EXPERIMENTAL_THREAD_PRIORITY_WORKER).getDouble()) { + settings.set(platform::EXPERIMENTAL_THREAD_PRIORITY_WORKER, 0.5); + } + Actor poolWorker(Scheduler::GetBackground()); auto poolWorkerRef = poolWorker.self(); @@ -328,3 +334,108 @@ TEST(Thread, DeleteBeforeChildStarts) { // Should process the queue before destruction. ASSERT_TRUE(flag); } + +TEST(Thread, PoolWait) { + auto pool = Scheduler::GetBackground(); + + constexpr int threadCount = 10; + for (int i = 0; i < threadCount; ++i) { + pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(100)); }); + } + + EXPECT_EQ(0, pool->waitForEmpty()); +} + +TEST(Thread, PoolWaitRecursiveAdd) { + auto pool = Scheduler::GetBackground(); + + pool->schedule([&] { + // Scheduled tasks can add more tasks + pool->schedule([&] { + std::this_thread::sleep_for(Milliseconds(10)); + pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(10)); }); + }); + std::this_thread::sleep_for(Milliseconds(10)); + }); + + EXPECT_EQ(0, pool->waitForEmpty()); +} + +TEST(Thread, PoolWaitAdd) { + auto pool = Scheduler::GetBackground(); + auto seq = Scheduler::GetSequenced(); + + // add new tasks every few milliseconds + std::atomic addActive{true}; + std::atomic added{0}; + std::atomic executed{0}; + seq->schedule([&] { + while (addActive) { + pool->schedule([&] { executed++; }); + added++; + } + }); + + // Wait be sure some are added + while (added < 1) { + std::this_thread::sleep_for(Milliseconds(10)); + } + + // Add an item that should take long enough to be confident that + // more items would be added by the sequential task if not blocked + pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(100)); }); + + EXPECT_EQ(0, pool->waitForEmpty()); + + addActive = false; + EXPECT_EQ(0, pool->waitForEmpty()); +} + +TEST(Thread, PoolWaitTimeout) { + auto pool = Scheduler::GetBackground(); + + std::mutex mutex; + { + std::lock_guard outerLock(mutex); + pool->schedule([&] { std::lock_guard innerLock(mutex); }); + + // should always time out + EXPECT_EQ(1, pool->waitForEmpty(Milliseconds(100))); + } + + EXPECT_EQ(0, pool->waitForEmpty()); +} + +TEST(Thread, PoolWaitException) { + auto pool = Scheduler::GetBackground(); + + std::atomic caught{0}; + pool->setExceptionHandler([&](const auto) { caught++; }); + + constexpr int threadCount = 3; + for (int i = 0; i < threadCount; ++i) { + pool->schedule([=] { + std::this_thread::sleep_for(Milliseconds(i)); + if (i & 1) { + throw std::runtime_error("test"); + } else { + throw 1; + } + }); + } + + // Exceptions shouldn't cause deadlocks by, e.g., abandoning locks. + EXPECT_EQ(0, pool->waitForEmpty()); + EXPECT_EQ(threadCount, caught); +} + +#if defined(NDEBUG) +TEST(Thread, WrongThread) { + auto pool = Scheduler::GetBackground(); + + // Asserts in debug builds, silently ignored in release. + pool->schedule([&] { EXPECT_EQ(0, pool->waitForEmpty()); }); + + EXPECT_EQ(0, pool->waitForEmpty()); +} +#endif