From ea09bdf2b6fff7ddf069d4e018f46eff179915ff Mon Sep 17 00:00:00 2001 From: Adrian Cojocaru Date: Fri, 6 Sep 2024 17:40:57 +0300 Subject: [PATCH] Add Android Vulkan backend (#2711) Co-authored-by: Bart Louwers --- .../actions/aws-device-farm-run/action.yml | 2 +- .github/workflows/android-ci.yml | 39 +- .github/workflows/android-device-test.yml | 16 +- CMakeLists.txt | 28 +- .../gl/drawable_fill_outline_triangulated.hpp | 1 + include/mbgl/shaders/vulkan/background.hpp | 4 +- include/mbgl/shaders/vulkan/circle.hpp | 2 +- include/mbgl/shaders/vulkan/clipping_mask.hpp | 2 +- include/mbgl/shaders/vulkan/collision.hpp | 16 +- include/mbgl/shaders/vulkan/debug.hpp | 15 +- include/mbgl/shaders/vulkan/fill.hpp | 50 +- include/mbgl/shaders/vulkan/heatmap.hpp | 4 +- include/mbgl/shaders/vulkan/hillshade.hpp | 8 +- include/mbgl/shaders/vulkan/line.hpp | 20 +- include/mbgl/shaders/vulkan/raster.hpp | 4 +- include/mbgl/shaders/vulkan/symbol.hpp | 16 +- include/mbgl/shaders/vulkan/widevector.hpp | 173 +++++ include/mbgl/vulkan/buffer_resource.hpp | 15 +- include/mbgl/vulkan/context.hpp | 4 + include/mbgl/vulkan/pipeline.hpp | 4 +- include/mbgl/vulkan/renderable_resource.hpp | 9 +- include/mbgl/vulkan/renderer_backend.hpp | 2 + include/mbgl/vulkan/texture2d.hpp | 12 +- platform/android/MapLibreAndroid/build.gradle | 22 + .../MapLibreAndroid/src/cpp/CMakeLists.txt | 24 + .../src/cpp/android_gl_renderer_backend.cpp | 84 +++ .../src/cpp/android_gl_renderer_backend.hpp | 46 ++ .../src/cpp/android_renderer_backend.cpp | 66 +- .../src/cpp/android_renderer_backend.hpp | 50 +- .../cpp/android_vulkan_renderer_backend.cpp | 79 +++ .../cpp/android_vulkan_renderer_backend.hpp | 42 ++ .../MapLibreAndroid/src/cpp/map_renderer.cpp | 39 +- .../MapLibreAndroid/src/cpp/map_renderer.hpp | 10 +- .../org/maplibre/android/maps/MapView.java | 40 +- .../android/maps/renderer/MapRenderer.java | 45 +- .../GLSurfaceViewMapRenderer.java | 126 ---- .../surfaceview/MapLibreSurfaceView.java | 543 ++++++++++++++++ .../surfaceview/SurfaceViewMapRenderer.java | 115 ++++ .../package-info.java | 0 .../textureview/TextureViewMapRenderer.java | 36 +- .../textureview/TextureViewRenderThread.java | 361 +---------- .../maps/renderer/MapRendererFactory.java | 49 ++ .../maps/renderer/egl/EGLConfigChooser.java | 0 .../maps/renderer/egl/EGLContextFactory.java | 0 .../maps/renderer/egl/EGLLogWrapper.java | 0 .../renderer/egl/EGLWindowSurfaceFactory.java | 0 .../maps/renderer/egl/package-info.java | 0 .../surfaceview/GLSurfaceViewMapRenderer.java | 26 + .../surfaceview}/MapLibreGLSurfaceView.java | 605 ++---------------- .../GLTextureViewRenderThread.java | 344 ++++++++++ .../maps/renderer/MapRendererFactory.java | 49 ++ .../MapLibreVulkanSurfaceView.java | 183 ++++++ .../VulkanSurfaceViewMapRenderer.java | 16 + .../VulkanTextureViewRenderThread.java | 114 ++++ .../MapLibreAndroidTestApp/build.gradle | 3 + .../android/maps/NativeMapViewTest.kt | 5 + .../testapp/maps/RenderViewGetterTest.kt | 6 +- platform/android/android.cmake | 21 +- .../gradle/download-vulkan-validation.gradle | 51 ++ .../include/mbgl/vulkan/headless_backend.hpp | 2 - .../src/mbgl/vulkan/headless_backend.cpp | 9 +- platform/glfw/glfw_vulkan_backend.cpp | 3 + platform/windows/windows.cmake | 15 +- render-test/android/app/build.gradle | 16 + ...able.fill_outline_triangulated.vertex.glsl | 1 + .../render_location_indicator_layer.cpp | 42 +- .../render_location_indicator_layer.hpp | 3 + .../renderer/sources/render_tile_source.cpp | 13 +- src/mbgl/shaders/vulkan/background.cpp | 4 +- src/mbgl/shaders/vulkan/shader_program.cpp | 2 +- src/mbgl/shaders/vulkan/widevector.cpp | 22 + src/mbgl/vulkan/buffer_resource.cpp | 14 +- src/mbgl/vulkan/command_encoder.cpp | 4 +- src/mbgl/vulkan/context.cpp | 72 ++- src/mbgl/vulkan/drawable.cpp | 2 +- src/mbgl/vulkan/pipeline.cpp | 40 +- src/mbgl/vulkan/renderable_resource.cpp | 31 +- src/mbgl/vulkan/renderer_backend.cpp | 32 +- src/mbgl/vulkan/texture2d.cpp | 7 +- 79 files changed, 2580 insertions(+), 1400 deletions(-) create mode 100644 include/mbgl/shaders/vulkan/widevector.hpp create mode 100644 platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp create mode 100644 platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp create mode 100644 platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp create mode 100644 platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.hpp delete mode 100644 platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java create mode 100644 platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java create mode 100644 platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java rename platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/{glsurfaceview => surfaceview}/package-info.java (100%) create mode 100644 platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java rename platform/android/MapLibreAndroid/src/{main => opengl}/java/org/maplibre/android/maps/renderer/egl/EGLConfigChooser.java (100%) rename platform/android/MapLibreAndroid/src/{main => opengl}/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java (100%) rename platform/android/MapLibreAndroid/src/{main => opengl}/java/org/maplibre/android/maps/renderer/egl/EGLLogWrapper.java (100%) rename platform/android/MapLibreAndroid/src/{main => opengl}/java/org/maplibre/android/maps/renderer/egl/EGLWindowSurfaceFactory.java (100%) rename platform/android/MapLibreAndroid/src/{main => opengl}/java/org/maplibre/android/maps/renderer/egl/package-info.java (100%) create mode 100644 platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java rename platform/android/MapLibreAndroid/src/{main/java/org/maplibre/android/maps/renderer/glsurfaceview => opengl/java/org/maplibre/android/maps/renderer/surfaceview}/MapLibreGLSurfaceView.java (50%) create mode 100644 platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/textureview/GLTextureViewRenderThread.java create mode 100644 platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java create mode 100644 platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreVulkanSurfaceView.java create mode 100644 platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/VulkanSurfaceViewMapRenderer.java create mode 100644 platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/textureview/VulkanTextureViewRenderThread.java create mode 100644 platform/android/gradle/download-vulkan-validation.gradle create mode 100644 src/mbgl/shaders/vulkan/widevector.cpp diff --git a/.github/actions/aws-device-farm-run/action.yml b/.github/actions/aws-device-farm-run/action.yml index 9480e1eef..de6157ce9 100644 --- a/.github/actions/aws-device-farm-run/action.yml +++ b/.github/actions/aws-device-farm-run/action.yml @@ -114,7 +114,7 @@ runs: run: | arn="$(aws devicefarm schedule-run \ --project-arn ${{ inputs.AWS_DEVICE_FARM_PROJECT_ARN }} \ - --name "MapLibre Native ${{ matrix.test.name }}" \ + --name "MapLibre Native ${{ inputs.name }}" \ --app-arn ${{ env.app_arn }} \ --device-pool-arn ${{ inputs.AWS_DEVICE_FARM_DEVICE_POOL_ARN }} \ --test type=${{ inputs.testType }},testPackageArn=${{ env.test_package_arn }}${{ inputs.testFilter && ',filter=' }}${{ inputs.testFilter }}${{ inputs.testSpecArn && ',testSpecArn=' }}${{ inputs.testSpecArn }} \ diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index 609e258d6..73cfa5276 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -227,11 +227,27 @@ jobs: ./test/android/app-release-androidTest.apk android-build-render-test: + strategy: + fail-fast: false + matrix: + flavor: [opengl, vulkan] runs-on: ubuntu-latest needs: - pre_job if: needs.pre_job.outputs.should_skip != 'true' steps: + - name: Free Disk Space (Ubuntu) + if: startsWith(runner.name, 'GitHub Actions') + uses: jlumbroso/free-disk-space@main + with: + tool-cache: false + android: false + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: false + - uses: actions/checkout@v4 with: submodules: recursive @@ -248,12 +264,29 @@ jobs: - name: Build Render Test App run: | - ./gradlew assemble assembleAndroidTest - cp app/build/outputs/apk/release/app-release.apk RenderTestsApp.apk - cp app/build/outputs/apk/androidTest/release/app-release-androidTest.apk RenderTests.apk + ./gradlew assemble${{ matrix.flavor }} assemble${{ matrix.flavor }}AndroidTest + cp app/build/outputs/apk/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release.apk RenderTestsApp-${{ matrix.flavor }}.apk + cp app/build/outputs/apk/androidTest/${{ matrix.flavor }}/release/app-${{ matrix.flavor }}-release-androidTest.apk RenderTests-${{ matrix.flavor }}.apk working-directory: ./render-test/android - name: Store Render Test .apk files + uses: actions/upload-artifact@v4 + with: + name: android-render-tests-${{ matrix.flavor }} + if-no-files-found: error + path: | + ./render-test/android/RenderTestsApp-${{ matrix.flavor }}.apk + ./render-test/android/RenderTests-${{ matrix.flavor }}.apk + + # these two can be (and should be) removed once merged into main + - name: Copy render tests and render test app + if: matrix.flavor == 'opengl' + run: | + cp ./render-test/android/RenderTestsApp-opengl.apk ./render-test/android/RenderTestsApp.apk + cp ./render-test/android/RenderTests-opengl.apk ./render-test/android/RenderTests.apk + + - name: Store Render Test .apk files + if: matrix.flavor == 'opengl' uses: actions/upload-artifact@v4 with: name: android-render-tests diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index 14c036902..022815c26 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -16,10 +16,18 @@ jobs: matrix: test: [ { - artifactName: android-render-tests, - testFile: RenderTests.apk, - appFile: RenderTestsApp.apk, - name: "Android Render Tests", + artifactName: android-render-tests-opengl, + testFile: RenderTests-opengl.apk, + appFile: RenderTestsApp-opengl.apk, + name: "Android Render Tests (OpenGL)", + # Google Pixel 7 Pro + devicePool: "arn:aws:devicefarm:us-west-2:373521797162:devicepool:20687d72-0e46-403e-8f03-0941850665bc/9692fe7f-86a9-4ecc-908f-175600968564" + }, + { + artifactName: android-render-tests-vulkan, + testFile: RenderTests-vulkan.apk, + appFile: RenderTestsApp-vulkan.apk, + name: "Android Render Tests (Vulkan)", # Google Pixel 7 Pro devicePool: "arn:aws:devicefarm:us-west-2:373521797162:devicepool:20687d72-0e46-403e-8f03-0941850665bc/9692fe7f-86a9-4ecc-908f-175600968564" }, diff --git a/CMakeLists.txt b/CMakeLists.txt index 60a9791d7..f65d76915 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ if (MLN_WITH_QT AND NOT CMAKE_OSX_DEPLOYMENT_TARGET) set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0) endif() -if(WIN32 AND NOT MLN_WITH_QT) +if(WIN32 AND NOT MLN_WITH_QT AND NOT CMAKE_SYSTEM_NAME STREQUAL Android) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/platform/windows/custom-toolchain.cmake) endif() @@ -252,6 +252,8 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/layer_manager.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/line_layer_factory.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/location_indicator_layer.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/location_indicator_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/raster_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/symbol_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/map/bound_options.hpp @@ -503,6 +505,14 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/line_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/raster_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/symbol_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/location_indicator_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_location_indicator_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_location_indicator_layer.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_impl.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_impl.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_properties.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_properties.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/layout/circle_layout.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/layout/clip_lines.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layout/clip_lines.hpp @@ -856,6 +866,8 @@ list(APPEND SRC_FILES ${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 + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_layer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/bidi.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/check_max_angle.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/text/check_max_angle.hpp @@ -1006,9 +1018,7 @@ if(MLN_WITH_OPENGL) INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderable_resource.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderer_backend.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/location_indicator_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/platform/gl_functions.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/location_indicator_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_source.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_manifest.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_group_gl.hpp @@ -1090,8 +1100,6 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/src/mbgl/gl/offscreen_texture.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/offscreen_texture.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/program.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_layer.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_layer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/render_pass.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/render_pass.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/renderbuffer_resource.cpp @@ -1117,15 +1125,7 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_array.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_buffer_resource.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_buffer_resource.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/location_indicator_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/platform/gl_functions.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_location_indicator_layer.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_location_indicator_layer.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_impl.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_impl.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_properties.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/location_indicator_layer_properties.hpp ) if(MLN_DRAWABLE_RENDERER) @@ -1321,6 +1321,7 @@ if(MLN_WITH_VULKAN) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/line.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/raster.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/symbol.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/widevector.hpp ) list(APPEND @@ -1355,6 +1356,7 @@ if(MLN_WITH_VULKAN) ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/line.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/raster.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/symbol.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/widevector.cpp ) endif() diff --git a/include/mbgl/shaders/gl/drawable_fill_outline_triangulated.hpp b/include/mbgl/shaders/gl/drawable_fill_outline_triangulated.hpp index 73c26f195..3a19b2019 100644 --- a/include/mbgl/shaders/gl/drawable_fill_outline_triangulated.hpp +++ b/include/mbgl/shaders/gl/drawable_fill_outline_triangulated.hpp @@ -68,6 +68,7 @@ void main() { // these transformations used to be applied in the JS and native code bases. // moved them into the shader for clarity and simplicity. + float u_width = 1.0; float halfwidth = u_width / 2.0; float outset = halfwidth + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); diff --git a/include/mbgl/shaders/vulkan/background.hpp b/include/mbgl/shaders/vulkan/background.hpp index 7066891e4..1ca862ebf 100644 --- a/include/mbgl/shaders/vulkan/background.hpp +++ b/include/mbgl/shaders/vulkan/background.hpp @@ -17,7 +17,7 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; +layout(location = 0) in ivec2 in_position; layout(set = 0, binding = 1) uniform BackgroundDrawableUBO { mat4 matrix; @@ -61,7 +61,7 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; +layout(location = 0) in ivec2 in_position; #if !defined(HAS_UNIFORM_u_color) layout(location = 1) in vec4 in_color; diff --git a/include/mbgl/shaders/vulkan/clipping_mask.hpp b/include/mbgl/shaders/vulkan/clipping_mask.hpp index feab7b024..dd3497750 100644 --- a/include/mbgl/shaders/vulkan/clipping_mask.hpp +++ b/include/mbgl/shaders/vulkan/clipping_mask.hpp @@ -22,7 +22,7 @@ struct ShaderSource { static constexpr std::array textures{}; static constexpr auto vertex = R"( - layout(location = 0) in vec2 position; + layout(location = 0) in ivec2 position; layout(push_constant) uniform constants { mat4 matrix; diff --git a/include/mbgl/shaders/vulkan/collision.hpp b/include/mbgl/shaders/vulkan/collision.hpp index 4cc2f8d2e..0d42a1381 100644 --- a/include/mbgl/shaders/vulkan/collision.hpp +++ b/include/mbgl/shaders/vulkan/collision.hpp @@ -17,10 +17,10 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec2 in_anchor_position; -layout(location = 2) in vec2 in_extrude; -layout(location = 3) in vec2 in_placed; +layout(location = 0) in ivec2 in_position; +layout(location = 1) in ivec2 in_anchor_position; +layout(location = 2) in ivec2 in_extrude; +layout(location = 3) in uvec2 in_placed; layout(location = 4) in vec2 in_shift; layout(set = 0, binding = 1) uniform CollisionBoxUBO { @@ -98,10 +98,10 @@ struct ShaderSource static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec2 in_anchor_position; -layout(location = 2) in vec2 in_extrude; -layout(location = 3) in vec2 in_placed; +layout(location = 0) in ivec2 in_position; +layout(location = 1) in ivec2 in_anchor_position; +layout(location = 2) in ivec2 in_extrude; +layout(location = 3) in uvec2 in_placed; layout(set = 0, binding = 1) uniform CollisionCircleUBO { mat4 matrix; diff --git a/include/mbgl/shaders/vulkan/debug.hpp b/include/mbgl/shaders/vulkan/debug.hpp index 0e2759e14..5cb608e29 100644 --- a/include/mbgl/shaders/vulkan/debug.hpp +++ b/include/mbgl/shaders/vulkan/debug.hpp @@ -17,7 +17,7 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; +layout(location = 0) in ivec2 in_position; layout(set = 0, binding = 1) uniform DebugUBO { mat4 matrix; @@ -59,18 +59,5 @@ void main() { )"; }; -template <> -struct ShaderSource { - static constexpr const char* name = "WideVectorShader"; - - static constexpr std::array uniforms{}; - static constexpr std::array attributes{}; - static constexpr std::array instanceAttributes{}; - static constexpr std::array textures{}; - - static constexpr auto vertex = R"()"; - static constexpr auto fragment = R"()"; -}; - } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/fill.hpp b/include/mbgl/shaders/vulkan/fill.hpp index 4ba9dc3f9..df20aa097 100644 --- a/include/mbgl/shaders/vulkan/fill.hpp +++ b/include/mbgl/shaders/vulkan/fill.hpp @@ -17,7 +17,7 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; +layout(location = 0) in ivec2 in_position; #if !defined(HAS_UNIFORM_u_color) layout(location = 1) in vec4 in_color; @@ -110,7 +110,7 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; +layout(location = 0) in ivec2 in_position; #if !defined(HAS_UNIFORM_u_outline_color) layout(location = 1) in vec4 in_color; @@ -216,14 +216,14 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; +layout(location = 0) in ivec2 in_position; #if !defined(HAS_UNIFORM_u_pattern_from) -layout(location = 1) in mediump vec4 in_pattern_from; +layout(location = 1) in mediump uvec4 in_pattern_from; #endif #if !defined(HAS_UNIFORM_u_pattern_to) -layout(location = 2) in mediump vec4 in_pattern_to; +layout(location = 2) in mediump uvec4 in_pattern_to; #endif #if !defined(HAS_UNIFORM_u_opacity) @@ -411,14 +411,14 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec4 in_normal_ed; +layout(location = 0) in ivec2 in_position; +layout(location = 1) in ivec4 in_normal_ed; #if !defined(HAS_UNIFORM_u_color) layout(location = 2) in vec4 in_color; @@ -856,8 +856,8 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; +layout(location = 0) in ivec2 in_position; layout(set = 0, binding = 1) uniform HeatmapTexturePropsUBO { mat4 matrix; diff --git a/include/mbgl/shaders/vulkan/hillshade.hpp b/include/mbgl/shaders/vulkan/hillshade.hpp index e583bf0c6..3e2b5b9c3 100644 --- a/include/mbgl/shaders/vulkan/hillshade.hpp +++ b/include/mbgl/shaders/vulkan/hillshade.hpp @@ -17,8 +17,8 @@ struct ShaderSource static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec2 in_texture_position; +layout(location = 0) in ivec2 in_position; +layout(location = 1) in ivec2 in_texture_position; layout(set = 0, binding = 1) uniform HillshadePrepareDrawableUBO { mat4 matrix; @@ -135,8 +135,8 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec2 in_texture_position; +layout(location = 0) in ivec2 in_position; +layout(location = 1) in ivec2 in_texture_position; layout(set = 0, binding = 1) uniform HillshadeDrawableUBO { mat4 matrix; diff --git a/include/mbgl/shaders/vulkan/line.hpp b/include/mbgl/shaders/vulkan/line.hpp index a0672c49d..8069dc417 100644 --- a/include/mbgl/shaders/vulkan/line.hpp +++ b/include/mbgl/shaders/vulkan/line.hpp @@ -17,8 +17,8 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_pos_normal; -layout(location = 1) in vec4 in_data; +layout(location = 0) in ivec2 in_pos_normal; +layout(location = 1) in uvec4 in_data; #if !defined(HAS_UNIFORM_u_color) layout(location = 2) in vec4 in_color; @@ -248,8 +248,8 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_pos_normal; -layout(location = 1) in vec4 in_data; +layout(location = 0) in ivec2 in_pos_normal; +layout(location = 1) in uvec4 in_data; #if !defined(HAS_UNIFORM_u_blur) layout(location = 2) in vec2 in_blur; @@ -466,8 +466,8 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_pos_normal; -layout(location = 1) in vec4 in_data; +layout(location = 0) in ivec2 in_pos_normal; +layout(location = 1) in uvec4 in_data; #if !defined(HAS_UNIFORM_u_blur) layout(location = 2) in vec2 in_blur; @@ -490,11 +490,11 @@ layout(location = 6) in vec2 in_width; #endif #if !defined(HAS_UNIFORM_u_pattern_from) -layout(location = 7) in vec4 in_pattern_from; +layout(location = 7) in uvec4 in_pattern_from; #endif #if !defined(HAS_UNIFORM_u_pattern_to) -layout(location = 8) in vec4 in_pattern_to; +layout(location = 8) in uvec4 in_pattern_to; #endif layout(set = 0, binding = 1) uniform LineDrawableUBO { @@ -777,8 +777,8 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_pos_normal; -layout(location = 1) in vec4 in_data; +layout(location = 0) in ivec2 in_pos_normal; +layout(location = 1) in uvec4 in_data; #if !defined(HAS_UNIFORM_u_color) layout(location = 2) in vec4 in_color; diff --git a/include/mbgl/shaders/vulkan/raster.hpp b/include/mbgl/shaders/vulkan/raster.hpp index cffeb7073..6e166cd3e 100644 --- a/include/mbgl/shaders/vulkan/raster.hpp +++ b/include/mbgl/shaders/vulkan/raster.hpp @@ -17,8 +17,8 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec2 in_position; -layout(location = 1) in vec2 in_texture_position; +layout(location = 0) in ivec2 in_position; +layout(location = 1) in ivec2 in_texture_position; layout(set = 0, binding = 1) uniform RasterDrawableUBO { mat4 matrix; diff --git a/include/mbgl/shaders/vulkan/symbol.hpp b/include/mbgl/shaders/vulkan/symbol.hpp index 39a16995a..5d4d62308 100644 --- a/include/mbgl/shaders/vulkan/symbol.hpp +++ b/include/mbgl/shaders/vulkan/symbol.hpp @@ -17,9 +17,9 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec4 in_pos_offset; -layout(location = 1) in vec4 in_data; -layout(location = 2) in vec4 in_pixeloffset; +layout(location = 0) in ivec4 in_pos_offset; +layout(location = 1) in uvec4 in_data; +layout(location = 2) in ivec4 in_pixeloffset; layout(location = 3) in vec3 in_projected_pos; layout(location = 4) in float in_fade_opacity; @@ -196,9 +196,9 @@ struct ShaderSource { static constexpr auto vertex = R"( -layout(location = 0) in vec4 in_pos_offset; -layout(location = 1) in vec4 in_data; -layout(location = 2) in vec4 in_pixeloffset; +layout(location = 0) in ivec4 in_pos_offset; +layout(location = 1) in uvec4 in_data; +layout(location = 2) in ivec4 in_pixeloffset; layout(location = 3) in vec3 in_projected_pos; layout(location = 4) in float in_fade_opacity; @@ -496,8 +496,8 @@ struct ShaderSource +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "WideVectorShader"; + + static const std::array uniforms; + static const std::array attributes; + static const std::array instanceAttributes; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +/** Expressions are used to change values like width and opacity over zoom levels. **/ +#define WKSExpStops 8 + +// Line Joins +// These are assumed to match WideVectorLineJoinType +#define WKSVertexLineJoinMiter 0 +#define WKSVertexLineJoinBevel 1 +#define WKSVertexLineJoinRound 2 +#define WKSVertexLineJoinMiterClip 3 +#define WKSVertexLineJoinMiterSimple 4 +#define WKSVertexLineJoinNone 5 + +// Line Caps +// These are assumed to match WideVectorLineCapType +#define WKSVertexLineCapRound 0 +#define WKSVertexLineCapButt 1 +#define WKSVertexLineCapSquare 2 + +layout(set = 0, binding = 1) uniform WideVectorUniformsUBO { + mat4 mvpMatrix; + mat4 mvpMatrixDiff; + mat4 mvMatrix; + mat4 mvMatrixDiff; + mat4 pMatrix; + mat4 pMatrixDiff; + vec2 frameSize; +} uniforms; + +layout(set = 0, binding = 2) uniform WideVectorUniformWideVecUBO { + vec4 color; + float w2; + float offset; + float edge; + float texRepeat; + vec4 texOffset; + float miterLimit; + int join; + int cap; + int hasExp; + float interClipLimit; +} wideVec; + +struct IntersectInfo { + bool valid; + vec2 interPt; + float c; + float ta, tb; +}; + +// wedge product (2D cross product) +// if A ^ B > 0, A is to the right of B +float wedge(vec2 a, vec2 b) { + return a.x * b.y - a.y * b.x; +} + +// Intersect two lines +IntersectInfo intersectLines(vec2 a0, vec2 a1, vec2 b0, vec2 b1) +{ + const vec2 dA = a0 - a1; + const vec2 dB = b0 - b1; + + // Solve the system of equations formed by equating the lines to get their common point. + const float denom = wedge(dA, dB); + if (denom == 0.0) { + // If the denominator comes out zero, the lines are parallel and do not intersect + return { .valid = false }; + } + + const float tA = wedge(a0, a1); + const float tB = wedge(b0, b1); + const vec2 inter = vec2((tA * dB.x - dA.x * tB), (tA * dB.y - dA.y * tB)) / denom; + + return { + .valid = true, + .interPt = inter, + .ta = 0.0, + .tb = 0.0, + }; +} + +// Intersect two offset lines +IntersectInfo intersectWideLines(vec2 p0,vec2 p1,vec2 p2, vec2 n0,vec2 n1) +{ + return intersectLines(p0 + n0, p1 + n0, p1 + n1, p2 + n1); +} + +vec3 viewPos(const mat4 &mat, vec3 vec) { + const vec4 p = mat * vec4(vec, 1.0); + return p.xyz; // / p.w; ? +} + +float2 screenPos_MVP(const Uniforms &u, float3 viewPos) { + const float4 p4 = float4(viewPos, 1.0); + + // Use the MVP matrix + const float4 s = u.mvpMatrix * p4; + + return s.xy / s.w; +} + +layout(location = 0) in vec3 in_position; +layout(location = 1) in vec4 in_color; +layout(location = 2) in int in_index; + +layout(location = 3) in vec3 in_instance_center; +layout(location = 4) in vec4 in_instance_color; +layout(location = 5) in int in_instance_prev; +layout(location = 6) in int in_instance_next; + +layout(location = 0) out vec2 frag_screenPos; // un-transformed vertex position +layout(location = 1) out vec2 frag_centerPos; // un-transformed circle center +layout(location = 2) out vec2 frag_midDir; // Turn direction +layout(location = 3) out vec4 frag_color; +layout(location = 4) out vec2 frag_texCoord; +layout(location = 5) out float frag_w2; +layout(location = 6) out float frag_edge; +layout(location = 7) out uvec2 frag_maskIDs; +layout(location = 8) out bool frag_roundJoin; + +const float wideVecMinTurnThreshold = 1e-5; +const float wideVecMaxTurnThreshold = 0.99999998476; // sin(89.99 deg) +const int WideVecPolyStartGeom = 0; +const int WideVecPolyBodyGeom = 1; +const int WideVecPolyEndGeom = 2; +const vec4 discardPt = vec4(0,0,-1e6,NAN); + +void main() { + + +} +)"; + + static constexpr auto fragment = R"( +layout(location = 0) in vec2 frag_uv; +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform DebugUBO { + mat4 matrix; + vec4 color; + float overlay_scale; + float pad1, pad2, pad3; +} debug; + +layout(set = 1, binding = 0) uniform sampler2D image_sampler; + +void main() { + vec4 overlay_color = texture(image_sampler, frag_uv); + out_color = mix(debug.color, overlay_color, overlay_color.a); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/vulkan/buffer_resource.hpp b/include/mbgl/vulkan/buffer_resource.hpp index 58cdc3006..2d59abff4 100644 --- a/include/mbgl/vulkan/buffer_resource.hpp +++ b/include/mbgl/vulkan/buffer_resource.hpp @@ -14,26 +14,17 @@ class Context; struct BufferAllocation { const VmaAllocator& allocator; VmaAllocation allocation{}; - VkBuffer buffer{}; + vk::Buffer buffer{}; void* mappedBuffer{}; BufferAllocation() = delete; BufferAllocation(BufferAllocation&) = delete; BufferAllocation& operator=(const BufferAllocation& other) = delete; + BufferAllocation(BufferAllocation&& other) = default; BufferAllocation(const VmaAllocator& allocator_) : allocator(allocator_) {} - BufferAllocation(BufferAllocation&& other) noexcept - : allocator(other.allocator), - allocation(other.allocation), - buffer(other.buffer), - mappedBuffer(other.mappedBuffer) { - other.buffer = nullptr; - other.allocation = nullptr; - other.mappedBuffer = nullptr; - } - ~BufferAllocation() { destroy(); } bool create(const VmaAllocationCreateInfo& allocInfo, const vk::BufferCreateInfo& bufferInfo); @@ -63,7 +54,7 @@ public: const void* contents() const noexcept { return (raw.empty() ? nullptr : raw.data()); } Context& getContext() const noexcept { return context; } - const VkBuffer& getVulkanBuffer() const noexcept { return bufferAllocation->buffer; } + const vk::Buffer& getVulkanBuffer() const noexcept { return bufferAllocation->buffer; } std::size_t getVulkanBufferOffset() const noexcept; std::size_t getVulkanBufferSize() const noexcept; diff --git a/include/mbgl/vulkan/context.hpp b/include/mbgl/vulkan/context.hpp index 93669b6e1..621c93946 100644 --- a/include/mbgl/vulkan/context.hpp +++ b/include/mbgl/vulkan/context.hpp @@ -54,6 +54,7 @@ public: void beginFrame() override; void endFrame() override; + void submitFrame(); void waitFrame() const; std::unique_ptr createCommandEncoder() override; @@ -145,6 +146,8 @@ public: void enqueueDeletion(std::function&& function); void submitOneTimeCommand(const std::function& function); + void requestSurfaceUpdate() { surfaceUpdateRequested = true; } + private: struct FrameResources { vk::UniqueCommandBuffer commandBuffer; @@ -189,6 +192,7 @@ private: uint8_t frameResourceIndex = 0; std::vector frameResources; + bool surfaceUpdateRequested{false}; struct { gfx::ShaderProgramBasePtr shader; diff --git a/include/mbgl/vulkan/pipeline.hpp b/include/mbgl/vulkan/pipeline.hpp index 00be06fa3..2c7af7fdf 100644 --- a/include/mbgl/vulkan/pipeline.hpp +++ b/include/mbgl/vulkan/pipeline.hpp @@ -82,8 +82,8 @@ public: void updateVertexInputHash(); std::size_t hash() const; - void setDynamicValues(const vk::UniqueCommandBuffer& buffer) const; - std::vector getDynamicStates() const; + void setDynamicValues(const RendererBackend& backend, const vk::UniqueCommandBuffer& buffer) const; + std::vector getDynamicStates(const RendererBackend& backend) const; protected: std::size_t vertexInputHash{0}; diff --git a/include/mbgl/vulkan/renderable_resource.hpp b/include/mbgl/vulkan/renderable_resource.hpp index 7a2bbf983..4c0bbe062 100644 --- a/include/mbgl/vulkan/renderable_resource.hpp +++ b/include/mbgl/vulkan/renderable_resource.hpp @@ -16,6 +16,11 @@ protected: ~RenderableResource() override = default; public: + virtual void swap() { + // Renderable resources that require a swap function to be called + // explicitly can override this method. + } + const vk::Extent2D& getExtent() const { return extent; } const vk::UniqueRenderPass& getRenderPass() const { return renderPass; } virtual const vk::UniqueFramebuffer& getFramebuffer() const = 0; @@ -34,10 +39,12 @@ protected: ~SurfaceRenderableResource() override; void initColor(uint32_t w, uint32_t h); - void initSwapchain(uint32_t w, uint32_t h); + void initSwapchain(uint32_t w, uint32_t h, vk::PresentModeKHR presentMode = vk::PresentModeKHR::eFifo); void initDepthStencil(); + void swap() override; + public: virtual void createPlatformSurface() = 0; virtual std::vector getDeviceExtensions() { return {}; } diff --git a/include/mbgl/vulkan/renderer_backend.hpp b/include/mbgl/vulkan/renderer_backend.hpp index 6c696573a..f17d5c864 100644 --- a/include/mbgl/vulkan/renderer_backend.hpp +++ b/include/mbgl/vulkan/renderer_backend.hpp @@ -47,6 +47,7 @@ public: uint32_t getMaxFrames() const { return maxFrames; } const VmaAllocator& getAllocator() const { return allocator; } const vk::PhysicalDeviceProperties& getDeviceProperties() const { return physicalDeviceProperties; } + const vk::PhysicalDeviceFeatures& getDeviceFeatures() const { return physicalDeviceFeatures; } int32_t getGraphicsQueueIndex() const { return graphicsQueueIndex; } int32_t getPresentQueueIndex() const { return presentQueueIndex; } @@ -97,6 +98,7 @@ protected: vk::PhysicalDevice physicalDevice; vk::UniqueDevice device; vk::PhysicalDeviceProperties physicalDeviceProperties; + vk::PhysicalDeviceFeatures physicalDeviceFeatures; int32_t graphicsQueueIndex = -1; int32_t presentQueueIndex = -1; diff --git a/include/mbgl/vulkan/texture2d.hpp b/include/mbgl/vulkan/texture2d.hpp index f238a3818..ae7456c88 100644 --- a/include/mbgl/vulkan/texture2d.hpp +++ b/include/mbgl/vulkan/texture2d.hpp @@ -15,25 +15,17 @@ class RenderPass; struct ImageAllocation { const VmaAllocator& allocator; VmaAllocation allocation{}; - VkImage image{}; + vk::Image image{}; vk::UniqueImageView imageView{}; ImageAllocation() = delete; ImageAllocation(ImageAllocation&) = delete; ImageAllocation& operator=(const ImageAllocation& other) = delete; + ImageAllocation(ImageAllocation&& other) = default; ImageAllocation(const VmaAllocator& allocator_) : allocator(allocator_) {} - ImageAllocation(ImageAllocation&& other) noexcept - : allocator(other.allocator), - allocation(other.allocation), - image(other.image), - imageView(std::move(other.imageView)) { - other.image = nullptr; - other.allocation = nullptr; - } - ~ImageAllocation() { destroy(); } bool create(const VmaAllocationCreateInfo& allocInfo, const vk::ImageCreateInfo& imageInfo); diff --git a/platform/android/MapLibreAndroid/build.gradle b/platform/android/MapLibreAndroid/build.gradle index 9e8fa0888..1966bcee0 100644 --- a/platform/android/MapLibreAndroid/build.gradle +++ b/platform/android/MapLibreAndroid/build.gradle @@ -78,6 +78,24 @@ android { } } } + create("vulkan") { + dimension = "renderer" + externalNativeBuild { + cmake { + arguments "-DMLN_LEGACY_RENDERER=OFF", "-DMLN_DRAWABLE_RENDERER=ON" + arguments "-DMLN_WITH_OPENGL=OFF", "-DMLN_WITH_VULKAN=ON" + } + } + } + } + + sourceSets { + legacy { + java.srcDirs += ['src/opengl/java/'] + } + drawable { + java.srcDirs += ['src/opengl/java/'] + } } // build native libraries @@ -156,6 +174,10 @@ configurations { all*.exclude group: 'commons-collections', module: 'commons-collections' } +// Download validation layers from +// https://github.com/KhronosGroup/Vulkan-ValidationLayers +apply from: "${rootDir}/gradle/download-vulkan-validation.gradle" + apply from: "${rootDir}/gradle/gradle-checkstyle.gradle" apply from: "${rootDir}/gradle/gradle-dependencies-graph.gradle" apply from: "${rootDir}/gradle/android-nitpick.gradle" diff --git a/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt b/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt index b5161bf2a..05902547e 100644 --- a/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt +++ b/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt @@ -195,6 +195,30 @@ add_library(maplibre SHARED ${PROJECT_SOURCE_DIR}/util/default_style.hpp ${PROJECT_SOURCE_DIR}/util/tile_server_options.hpp) +if(MLN_WITH_OPENGL) + target_sources( + maplibre PRIVATE + ${PROJECT_SOURCE_DIR}/android_gl_renderer_backend.cpp + ${PROJECT_SOURCE_DIR}/android_gl_renderer_backend.hpp + ) + target_compile_definitions( + maplibre + PRIVATE MLN_RENDER_BACKEND_OPENGL=1 + ) +endif() + +if(MLN_WITH_VULKAN) + target_sources( + maplibre PRIVATE + ${PROJECT_SOURCE_DIR}/android_vulkan_renderer_backend.cpp + ${PROJECT_SOURCE_DIR}/android_vulkan_renderer_backend.hpp + ) + target_compile_definitions( + maplibre + PRIVATE MLN_RENDER_BACKEND_VULKAN=1 + ) +endif() + target_include_directories( maplibre PRIVATE ${PROJECT_SOURCE_DIR} diff --git a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp new file mode 100644 index 000000000..ebf5df788 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.cpp @@ -0,0 +1,84 @@ +#include "android_gl_renderer_backend.hpp" + +#include +#include +#include + +#include + +#include + +namespace mbgl { +namespace android { + +class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource { +public: + AndroidGLRenderableResource(AndroidGLRendererBackend& backend_) + : backend(backend_) {} + + void bind() override { + assert(gfx::BackendScope::exists()); + backend.setFramebufferBinding(0); + backend.setViewport(0, 0, backend.getSize()); + } + + void swap() override { + const auto& swapBehaviour = static_cast(backend).getSwapBehavior(); + if (swapBehaviour == gfx::Renderable::SwapBehaviour::Flush) { + static_cast(backend.getContext()).finish(); + } + } + +private: + AndroidGLRendererBackend& backend; +}; + +AndroidGLRendererBackend::AndroidGLRendererBackend() + : gl::RendererBackend(gfx::ContextMode::Unique), + mbgl::gfx::Renderable({64, 64}, std::make_unique(*this)) {} + +AndroidGLRendererBackend::~AndroidGLRendererBackend() = default; + +gl::ProcAddress AndroidGLRendererBackend::getExtensionFunctionPointer(const char* name) { + assert(gfx::BackendScope::exists()); + return eglGetProcAddress(name); +} + +void AndroidGLRendererBackend::updateViewPort() { + assert(gfx::BackendScope::exists()); + setViewport(0, 0, size); +} + +void AndroidGLRendererBackend::resizeFramebuffer(int width, int height) { + size = {static_cast(width), static_cast(height)}; +} + +PremultipliedImage AndroidGLRendererBackend::readFramebuffer() { + assert(gfx::BackendScope::exists()); + return gl::RendererBackend::readFramebuffer(size); +} + +void AndroidGLRendererBackend::updateAssumedState() { + assumeFramebufferBinding(0); + assumeViewport(0, 0, size); +} + +void AndroidGLRendererBackend::markContextLost() { + if (context) { + getContext().setCleanupOnDestruction(false); + } +} + +} // namespace android +} // namespace mbgl + +namespace mbgl { +namespace gfx { + +template <> +std::unique_ptr Backend::Create(ANativeWindow*) { + return std::make_unique(); +} + +} // namespace gfx +} // namespace mbgl \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp new file mode 100644 index 000000000..c27755cc7 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/android_gl_renderer_backend.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include "android_renderer_backend.hpp" + +namespace mbgl { +namespace android { + +class AndroidGLRendererBackend : public AndroidRendererBackend, + public gl::RendererBackend, + public mbgl::gfx::Renderable { +public: + AndroidGLRendererBackend(); + ~AndroidGLRendererBackend() override; + + mbgl::gfx::RendererBackend& getImpl() override { return *this; } + + void updateViewPort() override; + + // Ensures the current context is not cleaned up when destroyed + void markContextLost() override; + + void resizeFramebuffer(int width, int height) override; + PremultipliedImage readFramebuffer() override; + + // mbgl::gfx::RendererBackend implementation +public: + mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } + +protected: + void activate() override { + // no-op + } + void deactivate() override { + // no-op + } + + // mbgl::gl::RendererBackend implementation +protected: + mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override; + void updateAssumedState() override; +}; + +} // namespace android +} // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.cpp b/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.cpp index 2eb099de0..06b9c4e4d 100644 --- a/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.cpp @@ -1,78 +1,22 @@ #include "android_renderer_backend.hpp" - -#include -#include -#include - -#include - #include namespace mbgl { namespace android { -class AndroidGLRenderableResource final : public mbgl::gl::RenderableResource { -public: - AndroidGLRenderableResource(AndroidRendererBackend& backend_) - : backend(backend_) {} +void AndroidRendererBackend::updateViewPort() {} - void bind() override { - assert(gfx::BackendScope::exists()); - backend.setFramebufferBinding(0); - backend.setViewport(0, 0, backend.getSize()); - } - - void swap() override { backend.swap(); } - -private: - AndroidRendererBackend& backend; -}; - -AndroidRendererBackend::AndroidRendererBackend(const TaggedScheduler& threadPool) - : gl::RendererBackend(gfx::ContextMode::Unique), - mbgl::gfx::Renderable({64, 64}, std::make_unique(*this)) {} - -AndroidRendererBackend::~AndroidRendererBackend() = default; - -gl::ProcAddress AndroidRendererBackend::getExtensionFunctionPointer(const char* name) { - assert(gfx::BackendScope::exists()); - return eglGetProcAddress(name); -} - -void AndroidRendererBackend::updateViewPort() { - assert(gfx::BackendScope::exists()); - setViewport(0, 0, size); -} - -void AndroidRendererBackend::resizeFramebuffer(int width, int height) { - size = {static_cast(width), static_cast(height)}; -} +void AndroidRendererBackend::resizeFramebuffer(int width, int height) {} PremultipliedImage AndroidRendererBackend::readFramebuffer() { - assert(gfx::BackendScope::exists()); - return gl::RendererBackend::readFramebuffer(size); + return PremultipliedImage(); } -void AndroidRendererBackend::updateAssumedState() { - assumeFramebufferBinding(0); - assumeViewport(0, 0, size); -} +void AndroidRendererBackend::markContextLost() {} -void AndroidRendererBackend::markContextLost() { - if (context) { - getContext().setCleanupOnDestruction(false); - } -} - -void AndroidRendererBackend::setSwapBehavior(SwapBehaviour swapBehaviour_) { +void AndroidRendererBackend::setSwapBehavior(gfx::Renderable::SwapBehaviour swapBehaviour_) { swapBehaviour = swapBehaviour_; } -void AndroidRendererBackend::swap() { - if (swapBehaviour == SwapBehaviour::Flush) { - static_cast(getContext()).finish(); - } -} - } // namespace android } // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp b/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp index 2e93bc6ab..3f538c100 100644 --- a/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/android_renderer_backend.hpp @@ -1,46 +1,40 @@ #pragma once +#include +#include #include -#include +#include + +#include namespace mbgl { namespace android { -class AndroidRendererBackend : public gl::RendererBackend, public mbgl::gfx::Renderable { +class AndroidRendererBackend { public: - AndroidRendererBackend(const TaggedScheduler& threadPool); - ~AndroidRendererBackend() override; + AndroidRendererBackend() = default; + AndroidRendererBackend(const AndroidRendererBackend&) = delete; + AndroidRendererBackend& operator=(const AndroidRendererBackend&) = delete; + virtual ~AndroidRendererBackend() = default; - void updateViewPort(); + static std::unique_ptr Create(ANativeWindow* window) { + return mbgl::gfx::Backend::Create(window); + } + virtual mbgl::gfx::RendererBackend& getImpl() = 0; + + virtual void updateViewPort(); // Ensures the current context is not cleaned up when destroyed - void markContextLost(); + virtual void markContextLost(); - void resizeFramebuffer(int width, int height); - PremultipliedImage readFramebuffer(); + virtual void resizeFramebuffer(int width, int height); + virtual 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; } + gfx::Renderable::SwapBehaviour getSwapBehavior() const { return swapBehaviour; } + virtual void setSwapBehavior(gfx::Renderable::SwapBehaviour swapBehaviour); protected: - void activate() override { - // no-op - } - void deactivate() override { - // no-op - } - - // mbgl::gl::RendererBackend implementation -protected: - mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override; - void updateAssumedState() override; + gfx::Renderable::SwapBehaviour swapBehaviour = gfx::Renderable::SwapBehaviour::NoFlush; }; } // namespace android diff --git a/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp b/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp new file mode 100644 index 000000000..b9c1ebd6d --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.cpp @@ -0,0 +1,79 @@ +#include "android_vulkan_renderer_backend.hpp" + +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace android { + +class AndroidVulkanRenderableResource final : public mbgl::vulkan::SurfaceRenderableResource { +public: + AndroidVulkanRenderableResource(AndroidVulkanRendererBackend& backend_) + : SurfaceRenderableResource(backend_) {} + + std::vector getDeviceExtensions() override { + return { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; + } + + void createPlatformSurface() override { + auto& backendImpl = static_cast(backend); + const vk::AndroidSurfaceCreateInfoKHR createInfo({}, backendImpl.getWindow()); + surface = backendImpl.getInstance()->createAndroidSurfaceKHRUnique(createInfo); + } + + void bind() override {} + void swap() override { + vulkan::SurfaceRenderableResource::swap(); + + const auto& swapBehaviour = static_cast(backend).getSwapBehavior(); + if (swapBehaviour == gfx::Renderable::SwapBehaviour::Flush) { + static_cast(backend.getContext()).waitFrame(); + } + } + +private: +}; + +AndroidVulkanRendererBackend::AndroidVulkanRendererBackend(ANativeWindow* window_) + : vulkan::RendererBackend(gfx::ContextMode::Unique), + mbgl::gfx::Renderable({64, 64}, std::make_unique(*this)), + window(window_) { + init(); +} + +AndroidVulkanRendererBackend::~AndroidVulkanRendererBackend() = default; + +std::vector AndroidVulkanRendererBackend::getInstanceExtensions() { + auto extensions = mbgl::vulkan::RendererBackend::getInstanceExtensions(); + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); + return extensions; +} + +void AndroidVulkanRendererBackend::resizeFramebuffer(int width, int height) { + size = {static_cast(width), static_cast(height)}; + if (context) { + static_cast(*context).requestSurfaceUpdate(); + } +} + +} // namespace android +} // namespace mbgl + +namespace mbgl { +namespace gfx { + +template <> +std::unique_ptr Backend::Create( + ANativeWindow* window) { + return std::make_unique(window); +} + +} // namespace gfx +} // namespace mbgl \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.hpp b/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.hpp new file mode 100644 index 000000000..80c7382b7 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/android_vulkan_renderer_backend.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include "android_renderer_backend.hpp" +#include + +namespace mbgl { +namespace android { + +class AndroidVulkanRendererBackend : public AndroidRendererBackend, + public vulkan::RendererBackend, + public mbgl::gfx::Renderable { +public: + AndroidVulkanRendererBackend(ANativeWindow*); + ~AndroidVulkanRendererBackend() override; + + ANativeWindow* getWindow() { return window; } + mbgl::gfx::RendererBackend& getImpl() override { return *this; } + + std::vector getInstanceExtensions() override; + + void resizeFramebuffer(int width, int height) override; + + // mbgl::gfx::RendererBackend implementation +public: + mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } + +protected: + void activate() override { + // no-op + } + void deactivate() override { + // no-op + } + +protected: + ANativeWindow* window; +}; + +} // namespace android +} // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp index 6fa7ae17e..fc235a67f 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "attach_env.hpp" #include "android_renderer_backend.hpp" @@ -24,8 +25,7 @@ MapRenderer::MapRenderer(jni::JNIEnv& _env, localIdeographFontFamily(localIdeographFontFamily_ ? jni::Make(_env, localIdeographFontFamily_) : std::optional{}), threadPool(Scheduler::GetBackground(), {}), - mailboxData(this), - backend(std::make_unique(threadPool)) {} + mailboxData(this) {} MapRenderer::MailboxData::MailboxData(Scheduler* scheduler_) : scheduler(scheduler_) { @@ -163,7 +163,7 @@ void MapRenderer::requestSnapshot(SnapshotCallback callback) { void MapRenderer::resetRenderer() { renderer.reset(); - backend = std::make_unique(threadPool); + swapBehaviorFlush = false; } void MapRenderer::scheduleSnapshot(std::unique_ptr callback) { @@ -186,7 +186,7 @@ void MapRenderer::render(JNIEnv&) { // Activate the backend assert(backend); - gfx::BackendScope backendGuard{*backend}; + gfx::BackendScope backendGuard{backend->getImpl()}; // Ensure that the "current" scheduler on the render thread is // this scheduler. @@ -206,14 +206,10 @@ void MapRenderer::render(JNIEnv&) { } } -void MapRenderer::onSurfaceCreated(JNIEnv&) { +void MapRenderer::onSurfaceCreated(JNIEnv& env, const jni::Object& surface) { // Lock as the initialization can come from the main thread or the GL thread first std::lock_guard lock(initialisationMutex); - // The GL context is already active if get a new surface. - assert(backend); - gfx::BackendScope backendGuard{*backend, gfx::BackendScope::ScopeType::Implicit}; - // The android system will have already destroyed the underlying // GL resources if this is not the first initialization and an // attempt to clean them up will fail @@ -223,12 +219,22 @@ void MapRenderer::onSurfaceCreated(JNIEnv&) { // Reset in opposite order renderer.reset(); backend.reset(); + window.reset(); + + if (surface) { + window = std::unique_ptr>( + ANativeWindow_fromSurface(&env, reinterpret_cast(surface.get())), + [](ANativeWindow* window_) { ANativeWindow_release(window_); }); + } // Create the new backend and renderer - backend = std::make_unique(threadPool); - renderer = std::make_unique(*backend, pixelRatio, localIdeographFontFamily); + backend = AndroidRendererBackend::Create(window.get()); + renderer = std::make_unique(backend->getImpl(), pixelRatio, localIdeographFontFamily); rendererRef = std::make_unique>(*renderer, mailboxData.getMailbox()); + backend->setSwapBehavior(swapBehaviorFlush ? gfx::Renderable::SwapBehaviour::Flush + : gfx::Renderable::SwapBehaviour::NoFlush); + // Set the observer on the new Renderer implementation if (rendererObserver) { renderer->setObserver(rendererObserver.get()); @@ -238,7 +244,8 @@ void MapRenderer::onSurfaceCreated(JNIEnv&) { void MapRenderer::onSurfaceChanged(JNIEnv& env, jint width, jint height) { if (!renderer) { // In case the surface has been destroyed (due to app back-grounding) - onSurfaceCreated(env); + jni::jobject* nullObj = nullptr; + onSurfaceCreated(env, jni::Object(nullObj)); } backend->resizeFramebuffer(width, height); @@ -255,10 +262,16 @@ void MapRenderer::onRendererReset(JNIEnv&) { // needs to be called on GL thread void MapRenderer::onSurfaceDestroyed(JNIEnv&) { resetRenderer(); + window.reset(); } void MapRenderer::setSwapBehaviorFlush(JNIEnv&, jboolean flush) { - backend->setSwapBehavior(flush ? gfx::Renderable::SwapBehaviour::Flush : gfx::Renderable::SwapBehaviour::NoFlush); + swapBehaviorFlush = flush; + + if (backend) { + backend->setSwapBehavior(flush ? gfx::Renderable::SwapBehaviour::Flush + : gfx::Renderable::SwapBehaviour::NoFlush); + } } // Static methods // diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp index d7ad24589..ae72911fe 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp @@ -10,6 +10,7 @@ #include #include +#include namespace mbgl { @@ -26,6 +27,11 @@ namespace android { class AndroidRendererBackend; +class AndroidSurface { +public: + static constexpr auto Name() { return "android/view/Surface"; }; +}; + /** * The MapRenderer is a peer class that encapsulates the actions * performed on the GL Thread. @@ -105,7 +111,7 @@ private: // Renders a frame. void render(JNIEnv&); - void onSurfaceCreated(JNIEnv&); + void onSurfaceCreated(JNIEnv&, const jni::Object& surface); void onSurfaceChanged(JNIEnv&, jint width, jint height); @@ -132,12 +138,14 @@ private: std::unique_ptr backend; std::unique_ptr renderer; std::unique_ptr> rendererRef; + std::unique_ptr> window; std::shared_ptr updateParameters; std::mutex updateMutex; bool framebufferSizeChanged = false; std::atomic destroyed{false}; + bool swapBehaviorFlush{true}; std::unique_ptr snapshotCallback; mapbox::base::WeakPtrFactory weakFactory{this}; diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java index bedfb0577..d041ad762 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java @@ -8,7 +8,6 @@ import android.os.Bundle; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.TextureView; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; @@ -30,9 +29,6 @@ import org.maplibre.android.constants.MapLibreConstants; import org.maplibre.android.exceptions.MapLibreConfigurationException; import org.maplibre.android.location.LocationComponent; import org.maplibre.android.maps.renderer.MapRenderer; -import org.maplibre.android.maps.renderer.glsurfaceview.GLSurfaceViewMapRenderer; -import org.maplibre.android.maps.renderer.glsurfaceview.MapLibreGLSurfaceView; -import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer; import org.maplibre.android.maps.widgets.CompassView; import org.maplibre.android.net.ConnectivityReceiver; import org.maplibre.android.storage.FileSource; @@ -43,9 +39,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - import static org.maplibre.android.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION; import static org.maplibre.android.maps.widgets.CompassView.TIME_WAIT_IDLE; @@ -313,37 +306,12 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { } private void initialiseDrawingSurface(MapLibreMapOptions options) { - String localFontFamily = options.getLocalIdeographFontFamily(); - if (options.getTextureMode()) { - TextureView textureView = new TextureView(getContext()); - boolean translucentSurface = options.getTranslucentTextureSurface(); - mapRenderer = new TextureViewMapRenderer(getContext(), - textureView, localFontFamily, translucentSurface) { - @Override - protected void onSurfaceCreated(GL10 gl, EGLConfig config) { - MapView.this.onSurfaceCreated(); - super.onSurfaceCreated(gl, config); - } - }; + mapRenderer = MapRenderer.create(options, getContext(), () -> MapView.this.onSurfaceCreated()); + renderView = mapRenderer.getView(); - addView(textureView, 0); - renderView = textureView; - } else { - MapLibreGLSurfaceView glSurfaceView = new MapLibreGLSurfaceView(getContext()); - glSurfaceView.setZOrderMediaOverlay(maplibreMapOptions.getRenderSurfaceOnTop()); - mapRenderer = new GLSurfaceViewMapRenderer(getContext(), glSurfaceView, localFontFamily) { - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - MapView.this.onSurfaceCreated(); - super.onSurfaceCreated(gl, config); - } - }; + addView(renderView, 0); - addView(glSurfaceView, 0); - renderView = glSurfaceView; - } - - boolean crossSourceCollisions = maplibreMapOptions.getCrossSourceCollisions(); + boolean crossSourceCollisions = options.getCrossSourceCollisions(); nativeMapView = new NativeMapView( getContext(), getPixelRatio(), crossSourceCollisions, this, mapChangeReceiver, mapRenderer ); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java index 7be402324..992e07b6d 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java @@ -1,6 +1,9 @@ package org.maplibre.android.maps.renderer; import android.content.Context; +import android.view.Surface; +import android.view.TextureView; +import android.view.View; import androidx.annotation.CallSuper; import androidx.annotation.Keep; @@ -9,16 +12,14 @@ import androidx.annotation.NonNull; import org.maplibre.android.LibraryLoader; import org.maplibre.android.log.Logger; import org.maplibre.android.maps.MapLibreMap; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; +import org.maplibre.android.maps.MapLibreMapOptions; /** - * The {@link MapRenderer} encapsulates the GL thread. + * The {@link MapRenderer} encapsulates the render thread. *

- * Performs actions on the GL thread to manage the GL resources and + * Performs actions on the render thread to manage the resources and * render on the one end and acts as a scheduler to request work to - * be performed on the GL thread on the other. + * be performed on the render thread on the other. */ @Keep public abstract class MapRenderer implements MapRendererScheduler { @@ -34,6 +35,25 @@ public abstract class MapRenderer implements MapRendererScheduler { private double expectedRenderTime = 0; private MapLibreMap.OnFpsChangedListener onFpsChangedListener; + public static MapRenderer create(MapLibreMapOptions options, @NonNull Context context, Runnable initCallback) { + + MapRenderer renderer = null; + String localFontFamily = options.getLocalIdeographFontFamily(); + + if (options.getTextureMode()) { + TextureView textureView = new TextureView(context); + boolean translucentSurface = options.getTranslucentTextureSurface(); + renderer = MapRendererFactory.newTextureViewMapRenderer(context, textureView, localFontFamily, + translucentSurface, initCallback); + } else { + boolean renderSurfaceOnTop = options.getRenderSurfaceOnTop(); + renderer = MapRendererFactory.newSurfaceViewMapRenderer(context, localFontFamily, + renderSurfaceOnTop, initCallback); + } + + return renderer; + } + public MapRenderer(@NonNull Context context, String localIdeographFontFamily) { float pixelRatio = context.getResources().getDisplayMetrics().density; @@ -41,6 +61,8 @@ public abstract class MapRenderer implements MapRendererScheduler { nativeInitialize(this, pixelRatio, localIdeographFontFamily); } + public abstract View getView(); + public void onStart() { // Implement if needed } @@ -66,13 +88,12 @@ public abstract class MapRenderer implements MapRendererScheduler { } @CallSuper - protected void onSurfaceCreated(GL10 gl, EGLConfig config) { - nativeOnSurfaceCreated(); + protected void onSurfaceCreated(Surface surface) { + nativeOnSurfaceCreated(surface); } @CallSuper - protected void onSurfaceChanged(@NonNull GL10 gl, int width, int height) { - gl.glViewport(0, 0, width, height); + protected void onSurfaceChanged(int width, int height) { nativeOnSurfaceChanged(width, height); } @@ -82,7 +103,7 @@ public abstract class MapRenderer implements MapRendererScheduler { } @CallSuper - protected void onDrawFrame(GL10 gl) { + protected void onDrawFrame() { long startTime = System.nanoTime(); try { nativeRender(); @@ -128,7 +149,7 @@ public abstract class MapRenderer implements MapRendererScheduler { @Override protected native void finalize() throws Throwable; - private native void nativeOnSurfaceCreated(); + private native void nativeOnSurfaceCreated(Surface surface); private native void nativeOnSurfaceChanged(int width, int height); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java deleted file mode 100644 index b47fa38e3..000000000 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.maplibre.android.maps.renderer.glsurfaceview; - -import android.content.Context; -import android.opengl.GLSurfaceView; - -import androidx.annotation.NonNull; - -import org.maplibre.android.maps.renderer.MapRenderer; -import org.maplibre.android.maps.renderer.egl.EGLConfigChooser; -import org.maplibre.android.maps.renderer.egl.EGLContextFactory; -import org.maplibre.android.maps.renderer.egl.EGLWindowSurfaceFactory; - -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - -import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; - -/** - * The {@link GLSurfaceViewMapRenderer} encapsulates the GL thread and - * {@link GLSurfaceView} specifics to render the map. - * - * @see MapRenderer - */ -public class GLSurfaceViewMapRenderer extends MapRenderer implements GLSurfaceView.Renderer { - - @NonNull - private final MapLibreGLSurfaceView glSurfaceView; - - public GLSurfaceViewMapRenderer(Context context, - MapLibreGLSurfaceView glSurfaceView, - String localIdeographFontFamily) { - super(context, localIdeographFontFamily); - this.glSurfaceView = glSurfaceView; - glSurfaceView.setEGLContextFactory(new EGLContextFactory()); - glSurfaceView.setEGLWindowSurfaceFactory(new EGLWindowSurfaceFactory()); - glSurfaceView.setEGLConfigChooser(new EGLConfigChooser()); - glSurfaceView.setRenderer(this); - glSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY); - glSurfaceView.setPreserveEGLContextOnPause(true); - glSurfaceView.setDetachedListener(new MapLibreGLSurfaceView.OnGLSurfaceViewDetachedListener() { - @Override - public void onGLSurfaceViewDetached() { - // because the GL thread is destroyed when the view is detached from window, - // we need to ensure releasing the native renderer as well. - // This avoids releasing it only when the view is being recreated, which is already on a new GL thread, - // and leads to JNI crashes like https://github.com/mapbox/mapbox-gl-native/issues/14618 - nativeReset(); - } - }); - } - - @Override - public void onStop() { - glSurfaceView.onPause(); - } - - @Override - public void onPause() { - super.onPause(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - @Override - public void onStart() { - glSurfaceView.onResume(); - } - - @Override - public void onResume() { - super.onResume(); - } - - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - super.onSurfaceCreated(gl, config); - } - - @Override - protected void onSurfaceDestroyed() { - super.onSurfaceDestroyed(); - } - - @Override - public void onSurfaceChanged(GL10 gl, int width, int height) { - super.onSurfaceChanged(gl, width, height); - } - - @Override - public void onDrawFrame(GL10 gl) { - super.onDrawFrame(gl); - } - - /** - * May be called from any thread. - *

- * Called from the renderer frontend to schedule a render. - */ - @Override - public void requestRender() { - glSurfaceView.requestRender(); - } - - /** - * May be called from any thread. - *

- * Schedules work to be performed on the MapRenderer thread. - * - * @param runnable the runnable to execute - */ - @Override - public void queueEvent(Runnable runnable) { - glSurfaceView.queueEvent(runnable); - } - - /** - * {@inheritDoc} - */ - @Override - public void waitForEmpty() { - glSurfaceView.waitForEmpty(); - } -} \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java new file mode 100644 index 000000000..d69baa0b1 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreSurfaceView.java @@ -0,0 +1,543 @@ +package org.maplibre.android.maps.renderer.surfaceview; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.util.AttributeSet; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; + +public abstract class MapLibreSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 { + + protected static final String TAG = "MapLibreSurfaceView"; + protected static final RenderThreadManager renderThreadManager = new RenderThreadManager(); + + protected SurfaceViewMapRenderer renderer; + protected RenderThread renderThread; + protected OnSurfaceViewDetachedListener detachedListener; + + protected boolean detached; + + /** + * The renderer only renders + * when the surface is created, or when {@link #requestRender} is called. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + * @see #requestRender() + */ + public static final int RENDERMODE_WHEN_DIRTY = 0; + /** + * The renderer is called + * continuously to re-render the scene. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + */ + public static final int RENDERMODE_CONTINUOUSLY = 1; + + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public MapLibreSurfaceView(Context context) { + super(context); + init(); + } + + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public MapLibreSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + // Install a SurfaceHolder.Callback so we get notified when the + // underlying surface is created and destroyed + SurfaceHolder holder = getHolder(); + holder.setFormat(PixelFormat.TRANSPARENT); + holder.addCallback(this); + } + + @Override + protected void finalize() throws Throwable { + try { + if (renderThread != null) { + // thread may still be running if this view was never + // attached to a window. + renderThread.requestExitAndWait(); + } + } finally { + super.finalize(); + } + } + + /** + * Set a listener that gets notified when the view is detached from window. + * + * @param detachedListener listener + */ + public void setDetachedListener(@NonNull OnSurfaceViewDetachedListener detachedListener) { + if (this.detachedListener != null) { + throw new IllegalArgumentException("Detached from window listener has been already set."); + } + this.detachedListener = detachedListener; + } + + /** + /** + * Set the renderer associated with this view. Also starts the thread that + * will call the renderer, which in turn causes the rendering to start. + *

This method should be called once and only once in the life-cycle of + * a SurfaceView. + * The following SurfaceView methods can only be called after + * setRenderer is called: + *

    + *
  • {@link #getRenderMode()} + *
  • {@link #onPause()} + *
  • {@link #onResume()} + *
  • {@link #queueEvent(Runnable)} + *
  • {@link #requestRender()} + *
  • {@link #setRenderMode(int)} + *
+ * + * @param renderer the renderer to use to perform drawing. + */ + public void setRenderer(SurfaceViewMapRenderer renderer) { + checkRenderThreadState(); + this.renderer = renderer; + + createRenderThread(); + renderThread.start(); + } + + /** + * Set the rendering mode. When renderMode is + * RENDERMODE_CONTINUOUSLY, the renderer is called + * repeatedly to re-render the scene. When renderMode + * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface + * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. + *

+ * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance + * by allowing the GPU and CPU to idle when the view does not need to be updated. + *

+ * This method can only be called after {@link #setRenderer(SurfaceViewMapRenderer)} + * + * @param renderMode one of the RENDERMODE_X constants + */ + public void setRenderMode(int renderMode) { + renderThread.setRenderMode(renderMode); + } + + /** + * Get the current rendering mode. May be called + * from any thread. Must not be called before a renderer has been set. + * + * @return the current rendering mode. + */ + public int getRenderMode() { + return renderThread.getRenderMode(); + } + + /** + * Request that the renderer render a frame. + * This method is typically used when the render mode has been set to + * RENDERMODE_WHEN_DIRTY, so that frames are only rendered on demand. + * May be called + * from any thread. Must not be called before a renderer has been set. + */ + public void requestRender() { + renderThread.requestRender(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of MapLibreSurfaceView. + */ + public void surfaceCreated(SurfaceHolder holder) { + renderThread.surfaceCreated(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of MapLibreSurfaceView. + */ + public void surfaceDestroyed(SurfaceHolder holder) { + // Surface will be destroyed when we return + renderThread.surfaceDestroyed(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of MapLibreSurfaceView. + */ + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + renderThread.onWindowResize(w, h); + } + + /** + * This method is part of the SurfaceHolder.Callback2 interface, and is + * not normally called or subclassed by clients of MapLibreSurfaceView. + */ + @Override + public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) { + if (renderThread != null) { + renderThread.requestRenderAndNotify(finishDrawing); + } + } + + /** + * This method is part of the SurfaceHolder.Callback2 interface, and is + * not normally called or subclassed by clients of MapLibreSurfaceView. + */ + @Deprecated + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + // Since we are part of the framework we know only surfaceRedrawNeededAsync + // will be called. + } + + /** + * Pause the rendering thread. + *

+ * Must not be called before a renderer has been set. + */ + public void onPause() { + renderThread.onPause(); + } + + /** + * Resumes the rendering thread. It + * is the counterpart to {@link #onPause()}. + *

+ * Must not be called before a renderer has been set. + */ + public void onResume() { + renderThread.onResume(); + } + + /** + * Queue a runnable to be run on the rendering thread. This can be used + * to communicate with the Renderer on the rendering thread. + * Must not be called before a renderer has been set. + * + * @param r the runnable to be run on the rendering thread. + */ + public void queueEvent(Runnable r) { + renderThread.queueEvent(r); + } + + /** + * Wait for the queue to become empty + */ + public void waitForEmpty() { + renderThread.waitForEmpty(); + } + + + /** + * This method is used as part of the View class and is not normally + * called or subclassed by clients of MapLibreSurfaceView. + */ + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (detached && (renderer != null)) { + int renderMode = RENDERMODE_CONTINUOUSLY; + if (renderThread != null) { + renderMode = renderThread.getRenderMode(); + } + createRenderThread(); + if (renderMode != RENDERMODE_CONTINUOUSLY) { + renderThread.setRenderMode(renderMode); + } + renderThread.start(); + } + detached = false; + } + + @Override + protected void onDetachedFromWindow() { + if (detachedListener != null) { + detachedListener.onSurfaceViewDetached(); + } + if (renderThread != null) { + renderThread.requestExitAndWait(); + } + detached = true; + super.onDetachedFromWindow(); + } + + /** + /** + * A generic render Thread. Delegates + * to a Renderer instance to do the actual drawing. Can be configured to + * render continuously or on request. + *

+ * All potentially blocking synchronization is done through the + * sRenderThreadManager object. This avoids multiple-lock ordering issues. + */ + abstract static class RenderThread extends Thread { + RenderThread() { + super(); + width = 0; + height = 0; + requestRender = true; + renderMode = RENDERMODE_CONTINUOUSLY; + wantRenderNotification = false; + } + + @Override + public void run() { + setName("RenderThread " + getId()); + + try { + guardedRun(); + } catch (InterruptedException exception) { + // fall thru and exit normally + } finally { + renderThreadManager.threadExiting(this); + } + } + + protected abstract void guardedRun() throws InterruptedException; + + protected boolean readyToDraw() { + return (!paused) && hasSurface + && (width > 0) && (height > 0) + && (requestRender || (renderMode == RENDERMODE_CONTINUOUSLY)); + } + + public boolean ableToDraw() { + return readyToDraw(); + } + + public void setRenderMode(int renderMode) { + if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { + throw new IllegalArgumentException("renderMode"); + } + synchronized (renderThreadManager) { + this.renderMode = renderMode; + renderThreadManager.notifyAll(); + } + } + + public int getRenderMode() { + synchronized (renderThreadManager) { + return renderMode; + } + } + + public void requestRender() { + synchronized (renderThreadManager) { + requestRender = true; + renderThreadManager.notifyAll(); + } + } + + public void requestRenderAndNotify(Runnable finishDrawing) { + synchronized (renderThreadManager) { + // If we are already on the render thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We will return to the client rendering code, so here we don't need to + // do anything. + if (Thread.currentThread() == this) { + return; + } + + wantRenderNotification = true; + requestRender = true; + renderComplete = false; + finishDrawingRunnable = finishDrawing; + + renderThreadManager.notifyAll(); + } + } + + public void surfaceCreated() { + synchronized (renderThreadManager) { + hasSurface = true; + renderThreadManager.notifyAll(); + while (!exited && waitingForSurface) { + try { + renderThreadManager.wait(); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void surfaceDestroyed() { + synchronized (renderThreadManager) { + hasSurface = false; + renderThreadManager.notifyAll(); + while (!exited && !waitingForSurface) { + try { + renderThreadManager.wait(); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void onPause() { + synchronized (renderThreadManager) { + requestPaused = true; + renderThreadManager.notifyAll(); + while ((!exited) && (!paused)) { + try { + renderThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void onResume() { + synchronized (renderThreadManager) { + requestPaused = false; + requestRender = true; + renderComplete = false; + renderThreadManager.notifyAll(); + while ((!exited) && paused && (!renderComplete)) { + try { + renderThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void onWindowResize(int w, int h) { + synchronized (renderThreadManager) { + width = w; + height = h; + sizeChanged = true; + requestRender = true; + renderComplete = false; + + // If we are already on the render thread, this means a client callback + // has caused reentrancy, for example via updating the SurfaceView parameters. + // We need to process the size change eventually though and update our surface. + // So we set the parameters and return so they can be processed on our + // next iteration. + if (Thread.currentThread() == this) { + return; + } + + renderThreadManager.notifyAll(); + + // Wait for thread to react to resize and render a frame + while (!exited && !paused && !renderComplete + && ableToDraw()) { + try { + renderThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void requestExitAndWait() { + // don't call this from renderThread thread or it is a guaranteed + // deadlock! + synchronized (renderThreadManager) { + shouldExit = true; + renderThreadManager.notifyAll(); + while (!exited) { + try { + renderThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + /** + * Queue an "event" to be run on the rendering thread. + * + * @param r the runnable to be run on the rendering thread. + */ + public void queueEvent(@NonNull Runnable r) { + synchronized (renderThreadManager) { + eventQueue.add(r); + renderThreadManager.notifyAll(); + } + } + + /** + * Wait for the queue to become empty + */ + public void waitForEmpty() { + synchronized (renderThreadManager) { + // Wait for the queue to be empty + while (!this.eventQueue.isEmpty()) { + try { + renderThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + // Once the thread is started, all accesses to the following member + // variables are protected by the sRenderThreadManager monitor + protected boolean shouldExit; + protected boolean exited; + protected boolean requestPaused; + protected boolean paused; + protected boolean hasSurface; + protected boolean waitingForSurface; + protected int width; + protected int height; + protected int renderMode; + protected boolean requestRender; + protected boolean wantRenderNotification; + protected boolean renderComplete; + protected ArrayList eventQueue = new ArrayList<>(); + protected boolean sizeChanged = true; + protected Runnable finishDrawingRunnable = null; + // End of member variables protected by the sRenderThreadManager monitor. + + } + + protected abstract void createRenderThread(); + + protected void checkRenderThreadState() { + if (renderThread != null) { + throw new IllegalStateException("setRenderer has already been called for this instance."); + } + } + + protected static class RenderThreadManager { + + synchronized void threadExiting(RenderThread thread) { + thread.exited = true; + notifyAll(); + } + } + + /** + * Listener interface that notifies when a {@link MapLibreSurfaceView} is detached from window. + */ + public interface OnSurfaceViewDetachedListener { + + /** + * Called when a {@link MapLibreSurfaceView} is detached from window. + */ + void onSurfaceViewDetached(); + } +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java new file mode 100644 index 000000000..468f7f6cd --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/SurfaceViewMapRenderer.java @@ -0,0 +1,115 @@ +package org.maplibre.android.maps.renderer.surfaceview; + +import android.content.Context; +import android.view.Surface; +import android.view.View; + +import androidx.annotation.NonNull; + +import org.maplibre.android.maps.renderer.MapRenderer; + +/** + * The {@link SurfaceViewMapRenderer} encapsulates the render thread and + * {@link MapLibreSurfaceView} specifics to render the map. + * + * @see MapRenderer + */ +public class SurfaceViewMapRenderer extends MapRenderer { + + @NonNull + protected final MapLibreSurfaceView surfaceView; + + public SurfaceViewMapRenderer(Context context, + MapLibreSurfaceView surfaceView, + String localIdeographFontFamily) { + super(context, localIdeographFontFamily); + this.surfaceView = surfaceView; + + surfaceView.setDetachedListener(new MapLibreSurfaceView.OnSurfaceViewDetachedListener() { + @Override + public void onSurfaceViewDetached() { + // because the GL thread is destroyed when the view is detached from window, + // we need to ensure releasing the native renderer as well. + // This avoids releasing it only when the view is being recreated, which is already on a new GL thread, + // and leads to JNI crashes like https://github.com/mapbox/mapbox-gl-native/issues/14618 + nativeReset(); + } + }); + } + + @Override + public View getView() { + return this.surfaceView; + } + + @Override + public void onStop() { + surfaceView.onPause(); + } + + @Override + public void onPause() { + super.onPause(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onStart() { + surfaceView.onResume(); + } + + @Override + public void onResume() { + super.onResume(); + } + + public void onSurfaceCreated(Surface surface) { + super.onSurfaceCreated(surface); + } + + public void onSurfaceDestroyed() { + super.onSurfaceDestroyed(); + } + + public void onSurfaceChanged(int width, int height) { + super.onSurfaceChanged(width, height); + } + + public void onDrawFrame() { + super.onDrawFrame(); + } + + /** + * May be called from any thread. + *

+ * Called from the renderer frontend to schedule a render. + */ + @Override + public void requestRender() { + surfaceView.requestRender(); + } + + /** + * May be called from any thread. + *

+ * Schedules work to be performed on the MapRenderer thread. + * + * @param runnable the runnable to execute + */ + @Override + public void queueEvent(Runnable runnable) { + surfaceView.queueEvent(runnable); + } + + /** + * {@inheritDoc} + */ + @Override + public void waitForEmpty() { + surfaceView.waitForEmpty(); + } +} \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/package-info.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/package-info.java similarity index 100% rename from platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/package-info.java rename to platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/surfaceview/package-info.java diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java index 22a9415a6..1ec1b0b9f 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java @@ -1,15 +1,14 @@ package org.maplibre.android.maps.renderer.textureview; import android.content.Context; +import android.view.Surface; import android.view.TextureView; +import android.view.View; import androidx.annotation.NonNull; import org.maplibre.android.maps.renderer.MapRenderer; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - /** * The {@link TextureViewMapRenderer} encapsulates the GL thread and * {@link TextureView} specifics to render the map. @@ -19,6 +18,7 @@ import javax.microedition.khronos.opengles.GL10; public class TextureViewMapRenderer extends MapRenderer { private TextureViewRenderThread renderThread; private boolean translucentSurface; + private TextureView textureView; /** * Create a {@link MapRenderer} for the given {@link TextureView} @@ -33,26 +33,36 @@ public class TextureViewMapRenderer extends MapRenderer { String localIdeographFontFamily, boolean translucentSurface) { super(context, localIdeographFontFamily); + this.textureView = textureView; this.translucentSurface = translucentSurface; - renderThread = new TextureViewRenderThread(textureView, this); + + } + + public void setRenderThread(TextureViewRenderThread thread) { + renderThread = thread; renderThread.setName("TextureViewRenderer"); renderThread.start(); } - /** - * Overridden to provide package access - */ @Override - protected void onSurfaceCreated(GL10 gl, EGLConfig config) { - super.onSurfaceCreated(gl, config); + public View getView() { + return this.textureView; } /** * Overridden to provide package access */ @Override - protected void onSurfaceChanged(GL10 gl, int width, int height) { - super.onSurfaceChanged(gl, width, height); + protected void onSurfaceCreated(Surface surface) { + super.onSurfaceCreated(surface); + } + + /** + * Overridden to provide package access + */ + @Override + protected void onSurfaceChanged(int width, int height) { + super.onSurfaceChanged(width, height); } /** @@ -67,8 +77,8 @@ public class TextureViewMapRenderer extends MapRenderer { * Overridden to provide package access */ @Override - protected void onDrawFrame(GL10 gl) { - super.onDrawFrame(gl); + protected void onDrawFrame() { + super.onDrawFrame(); } /** diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java index 11d8a8d91..f26b8196d 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java @@ -7,50 +7,35 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import org.maplibre.android.log.Logger; -import org.maplibre.android.maps.renderer.egl.EGLConfigChooser; - -import java.lang.ref.WeakReference; import java.util.ArrayList; -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGL11; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL10; - /** * The render thread is responsible for managing the communication between the - * ui thread and the render thread it creates. Also, the EGL and GL contexts - * are managed from here. + * ui thread and the render thread it creates */ -class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextureListener { +abstract class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextureListener { - private static final String TAG = "Mbgl-TextureViewRenderThread"; + protected static final String TAG = "Mbgl-TextureViewRenderThread"; @NonNull - private final TextureViewMapRenderer mapRenderer; - @NonNull - private final EGLHolder eglHolder; + protected final TextureViewMapRenderer mapRenderer; // Lock used for synchronization - private final Object lock = new Object(); + protected final Object lock = new Object(); // Guarded by lock - private final ArrayList eventQueue = new ArrayList<>(); + protected final ArrayList eventQueue = new ArrayList<>(); @Nullable - private SurfaceTexture surface; - private int width; - private int height; - private boolean requestRender; - private boolean sizeChanged; - private boolean paused; - private boolean destroyContext; - private boolean destroySurface; - private boolean shouldExit; - private boolean exited; + protected SurfaceTexture surfaceTexture; + protected boolean hasNativeSurface; + protected int width; + protected int height; + protected boolean requestRender; + protected boolean sizeChanged; + protected boolean paused; + protected boolean destroySurface; + protected boolean shouldExit; + protected boolean exited; /** * Create a render thread for the given TextureView / Maprenderer combination. @@ -63,16 +48,15 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu textureView.setOpaque(!mapRenderer.isTranslucentSurface()); textureView.setSurfaceTextureListener(this); this.mapRenderer = mapRenderer; - this.eglHolder = new EGLHolder(new WeakReference<>(textureView), mapRenderer.isTranslucentSurface()); } // SurfaceTextureListener methods @UiThread @Override - public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) { + public void onSurfaceTextureAvailable(final SurfaceTexture surfaceTexture, final int width, final int height) { synchronized (lock) { - this.surface = surface; + this.surfaceTexture = surfaceTexture; this.width = width; this.height = height; this.requestRender = true; @@ -82,7 +66,7 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu @Override @UiThread - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, final int width, final int height) { + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, final int width, final int height) { synchronized (lock) { this.width = width; this.height = height; @@ -94,9 +78,9 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu @Override @UiThread - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { synchronized (lock) { - this.surface = null; + this.surfaceTexture = null; this.destroySurface = true; this.requestRender = false; lock.notifyAll(); @@ -106,7 +90,7 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu @Override @UiThread - public void onSurfaceTextureUpdated(SurfaceTexture surface) { + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { // Ignored } @@ -137,7 +121,6 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu /** * 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 @@ -170,7 +153,6 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu } } - @UiThread void onDestroy() { synchronized (lock) { @@ -187,303 +169,4 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu } } } - - // Thread implementation - - @Override - public void run() { - try { - - while (true) { - Runnable event = null; - boolean initializeEGL = false; - boolean recreateSurface = false; - int w = -1; - int h = -1; - - // Guarded block - synchronized (lock) { - while (true) { - - if (shouldExit) { - return; - } - - // If any events are scheduled, pop one for processing - if (!eventQueue.isEmpty()) { - event = eventQueue.remove(0); - break; - } - - if (destroySurface) { - eglHolder.destroySurface(); - destroySurface = false; - break; - } - - if (destroyContext) { - eglHolder.destroyContext(); - destroyContext = false; - break; - } - - if (surface != null && !paused && requestRender) { - - w = width; - h = height; - - // Initialize EGL if needed - if (eglHolder.eglContext == EGL10.EGL_NO_CONTEXT) { - initializeEGL = true; - break; - } - - // (re-)Initialize EGL Surface if needed - if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) { - recreateSurface = true; - break; - } - - // Reset the request render flag now, so we can catch new requests - // while rendering - requestRender = false; - - // Break the guarded loop and continue to process - break; - } - - - // Wait until needed - lock.wait(); - - } // end guarded while loop - - } // end guarded block - - // Run event, if any - if (event != null) { - event.run(); - continue; - } - - GL10 gl = eglHolder.createGL(); - - // Initialize EGL - if (initializeEGL) { - eglHolder.prepare(); - synchronized (lock) { - if (!eglHolder.createSurface()) { - // Cleanup the surface if one could not be created - // and wait for another to be ready. - destroySurface = true; - continue; - } - } - mapRenderer.onSurfaceCreated(gl, eglHolder.eglConfig); - mapRenderer.onSurfaceChanged(gl, w, h); - continue; - } - - // If the surface size has changed inform the map renderer. - if (recreateSurface) { - synchronized (lock) { - eglHolder.createSurface(); - } - mapRenderer.onSurfaceChanged(gl, w, h); - continue; - } - - if (sizeChanged) { - mapRenderer.onSurfaceChanged(gl, w, h); - sizeChanged = false; - continue; - } - - // Don't continue without a surface - if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) { - continue; - } - - // Time to render a frame - mapRenderer.onDrawFrame(gl); - - // Swap and check the result - int swapError = eglHolder.swap(); - switch (swapError) { - case EGL10.EGL_SUCCESS: - break; - case EGL11.EGL_CONTEXT_LOST: - Logger.w(TAG, "Context lost. Waiting for re-aquire"); - synchronized (lock) { - surface = null; - destroySurface = true; - destroyContext = true; - } - break; - default: - Logger.w(TAG, String.format("eglSwapBuffer error: %s. Waiting or new surface", swapError)); - // Probably lost the surface. Clear the current one and - // wait for a new one to be set - synchronized (lock) { - surface = null; - destroySurface = true; - } - } - - } - - } catch (InterruptedException err) { - // To be expected - } finally { - // Cleanup - eglHolder.cleanup(); - - // Signal we're done - synchronized (lock) { - this.exited = true; - lock.notifyAll(); - } - } - } - - /** - * Holds the EGL state and offers methods to mutate it. - */ - private static class EGLHolder { - private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - private final WeakReference textureViewWeakRef; - private boolean translucentSurface; - - private EGL10 egl; - @Nullable - private EGLConfig eglConfig; - private EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY; - private EGLContext eglContext = EGL10.EGL_NO_CONTEXT; - private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE; - - EGLHolder(WeakReference textureViewWeakRef, boolean translucentSurface) { - this.textureViewWeakRef = textureViewWeakRef; - this.translucentSurface = translucentSurface; - } - - void prepare() { - this.egl = (EGL10) EGLContext.getEGL(); - - // Only re-initialize display when needed - if (eglDisplay == EGL10.EGL_NO_DISPLAY) { - this.eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - if (eglDisplay == EGL10.EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed"); - } - - int[] version = new int[2]; - if (!egl.eglInitialize(eglDisplay, version)) { - throw new RuntimeException("eglInitialize failed"); - } - } - - if (textureViewWeakRef == null) { - // No texture view present - eglConfig = null; - eglContext = EGL10.EGL_NO_CONTEXT; - } else if (eglContext == EGL10.EGL_NO_CONTEXT) { - eglConfig = new EGLConfigChooser(translucentSurface).chooseConfig(egl, eglDisplay); - int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; - eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); - } - - if (eglContext == EGL10.EGL_NO_CONTEXT) { - throw new RuntimeException("createContext"); - } - } - - @NonNull - GL10 createGL() { - return (GL10) eglContext.getGL(); - } - - boolean createSurface() { - // The window size has changed, so we need to create a new surface. - destroySurface(); - - // Create an EGL surface we can render into. - TextureView view = textureViewWeakRef.get(); - if (view != null && view.getSurfaceTexture() != null) { - int[] surfaceAttribs = {EGL10.EGL_NONE}; - eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, view.getSurfaceTexture(), surfaceAttribs); - } else { - eglSurface = EGL10.EGL_NO_SURFACE; - } - - if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { - int error = egl.eglGetError(); - if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { - Logger.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); - } - return false; - } - - return makeCurrent(); - } - - boolean makeCurrent() { - if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { - // Could not make the context current, probably because the underlying - // SurfaceView surface has been destroyed. - Logger.w(TAG, String.format("eglMakeCurrent: %s", egl.eglGetError())); - return false; - } - - return true; - } - - int swap() { - if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) { - return egl.eglGetError(); - } - return EGL10.EGL_SUCCESS; - } - - private void destroySurface() { - if (eglSurface == EGL10.EGL_NO_SURFACE) { - return; - } - - if (!egl.eglDestroySurface(eglDisplay, eglSurface)) { - Logger.w(TAG, String.format("Could not destroy egl surface. Display %s, Surface %s", eglDisplay, eglSurface)); - } - - eglSurface = EGL10.EGL_NO_SURFACE; - } - - private void destroyContext() { - if (eglContext == EGL10.EGL_NO_CONTEXT) { - return; - } - - if (!egl.eglDestroyContext(eglDisplay, eglContext)) { - Logger.w(TAG, String.format("Could not destroy egl context. Display %s, Context %s", eglDisplay, eglContext)); - } - - eglContext = EGL10.EGL_NO_CONTEXT; - } - - private void terminate() { - if (eglDisplay == EGL10.EGL_NO_DISPLAY) { - return; - } - - if (!egl.eglTerminate(eglDisplay)) { - Logger.w(TAG, String.format("Could not terminate egl. Display %s", eglDisplay)); - } - eglDisplay = EGL10.EGL_NO_DISPLAY; - } - - void cleanup() { - destroySurface(); - destroyContext(); - terminate(); - } - } } diff --git a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java new file mode 100644 index 000000000..64320ecd3 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/MapRendererFactory.java @@ -0,0 +1,49 @@ +package org.maplibre.android.maps.renderer; + +import android.content.Context; +import android.view.Surface; +import android.view.TextureView; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +import org.maplibre.android.maps.renderer.surfaceview.GLSurfaceViewMapRenderer; +import org.maplibre.android.maps.renderer.surfaceview.MapLibreGLSurfaceView; +import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer; +import org.maplibre.android.maps.renderer.textureview.GLTextureViewRenderThread; +import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer; + +@Keep +public class MapRendererFactory { + public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView, + String localFontFamily, boolean translucentSurface, + Runnable initCallback) { + + TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView, + localFontFamily, translucentSurface) { + @Override + protected void onSurfaceCreated(Surface surface) { + initCallback.run(); + super.onSurfaceCreated(surface); + } + }; + + mapRenderer.setRenderThread(new GLTextureViewRenderThread(textureView, mapRenderer)); + return mapRenderer; + } + + public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, String localFontFamily, + boolean renderSurfaceOnTop, Runnable initCallback) { + + MapLibreGLSurfaceView surfaceView = new MapLibreGLSurfaceView(context); + surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop); + + return new GLSurfaceViewMapRenderer(context, surfaceView, localFontFamily) { + @Override + public void onSurfaceCreated(Surface surface) { + initCallback.run(); + super.onSurfaceCreated(surface); + } + }; + } +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLConfigChooser.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLConfigChooser.java similarity index 100% rename from platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLConfigChooser.java rename to platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLConfigChooser.java diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java similarity index 100% rename from platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java rename to platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLContextFactory.java diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLLogWrapper.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLLogWrapper.java similarity index 100% rename from platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLLogWrapper.java rename to platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLLogWrapper.java diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLWindowSurfaceFactory.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLWindowSurfaceFactory.java similarity index 100% rename from platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/EGLWindowSurfaceFactory.java rename to platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/EGLWindowSurfaceFactory.java diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/package-info.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/package-info.java similarity index 100% rename from platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/egl/package-info.java rename to platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/egl/package-info.java diff --git a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java new file mode 100644 index 000000000..0a8f1a0d7 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/GLSurfaceViewMapRenderer.java @@ -0,0 +1,26 @@ +package org.maplibre.android.maps.renderer.surfaceview; + +import android.content.Context; +import androidx.annotation.NonNull; + +import org.maplibre.android.maps.renderer.egl.EGLConfigChooser; +import org.maplibre.android.maps.renderer.egl.EGLContextFactory; +import org.maplibre.android.maps.renderer.egl.EGLWindowSurfaceFactory; + +import static android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY; + +public class GLSurfaceViewMapRenderer extends SurfaceViewMapRenderer { + + public GLSurfaceViewMapRenderer(Context context, + @NonNull MapLibreGLSurfaceView surfaceView, + String localIdeographFontFamily) { + super(context, surfaceView, localIdeographFontFamily); + + surfaceView.setEGLContextFactory(new EGLContextFactory()); + surfaceView.setEGLWindowSurfaceFactory(new EGLWindowSurfaceFactory()); + surfaceView.setEGLConfigChooser(new EGLConfigChooser()); + surfaceView.setRenderer(this); + surfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY); + surfaceView.setPreserveEGLContextOnPause(true); + } +} \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreGLSurfaceView.java similarity index 50% rename from platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java rename to platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreGLSurfaceView.java index a58ba0113..a14d203fd 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreGLSurfaceView.java @@ -1,19 +1,13 @@ -package org.maplibre.android.maps.renderer.glsurfaceview; +package org.maplibre.android.maps.renderer.surfaceview; import android.content.Context; import android.opengl.GLSurfaceView; import android.util.AttributeSet; import android.util.Log; -import android.view.SurfaceHolder; -import android.view.SurfaceView; - -import androidx.annotation.NonNull; import org.maplibre.android.maps.renderer.egl.EGLLogWrapper; -import java.io.Writer; import java.lang.ref.WeakReference; -import java.util.ArrayList; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; @@ -24,76 +18,22 @@ import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL10; -import static android.opengl.GLSurfaceView.RENDERMODE_CONTINUOUSLY; +public class MapLibreGLSurfaceView extends MapLibreSurfaceView { -/** - * {@link GLSurfaceView} extension that notifies a listener when the view is detached from window, - * which is the point of destruction of the GL thread. - */ -public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 { + protected final WeakReference viewWeakReference = new WeakReference<>(this); - private static final String TAG = "GLSurfaceView"; - private static final GLThreadManager glThreadManager = new GLThreadManager(); - - private final WeakReference viewWeakReference = new WeakReference<>(this); - private GLThread glThread; - private GLSurfaceView.Renderer renderer; private GLSurfaceView.EGLConfigChooser eglConfigChooser; private GLSurfaceView.EGLContextFactory eglContextFactory; private GLSurfaceView.EGLWindowSurfaceFactory eglWindowSurfaceFactory; - private OnGLSurfaceViewDetachedListener detachedListener; private boolean preserveEGLContextOnPause; - private boolean detached; - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ public MapLibreGLSurfaceView(Context context) { super(context); - init(); } - /** - * Standard View constructor. In order to render something, you - * must call {@link #setRenderer} to register a renderer. - */ public MapLibreGLSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); - init(); - } - - private void init() { - // Install a SurfaceHolder.Callback so we get notified when the - // underlying surface is created and destroyed - SurfaceHolder holder = getHolder(); - holder.addCallback(this); - } - - @Override - protected void finalize() throws Throwable { - try { - if (glThread != null) { - // GLThread may still be running if this view was never - // attached to a window. - glThread.requestExitAndWait(); - } - } finally { - super.finalize(); - } - } - - /** - * Set a listener that gets notified when the view is detached from window. - * - * @param detachedListener listener - */ - public void setDetachedListener(@NonNull OnGLSurfaceViewDetachedListener detachedListener) { - if (this.detachedListener != null) { - throw new IllegalArgumentException("Detached from window listener has been already set."); - } - this.detachedListener = detachedListener; } /** @@ -127,32 +67,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. return preserveEGLContextOnPause; } - /** - * Set the renderer associated with this view. Also starts the thread that - * will call the renderer, which in turn causes the rendering to start. - *

This method should be called once and only once in the life-cycle of - * a GLSurfaceView. - *

The following GLSurfaceView methods can only be called before - * setRenderer is called: - *

    - *
  • {@link #setEGLConfigChooser(GLSurfaceView.EGLConfigChooser)} - *
- *

- * The following GLSurfaceView methods can only be called after - * setRenderer is called: - *

    - *
  • {@link #getRenderMode()} - *
  • {@link #onPause()} - *
  • {@link #onResume()} - *
  • {@link #queueEvent(Runnable)} - *
  • {@link #requestRender()} - *
  • {@link #setRenderMode(int)} - *
- * - * @param renderer the renderer to use to perform OpenGL drawing. - */ - public void setRenderer(GLSurfaceView.Renderer renderer) { - checkRenderThreadState(); + public void setRenderer(SurfaceViewMapRenderer renderer) { if (eglConfigChooser == null) { throw new IllegalStateException("No eglConfigChooser provided"); } @@ -162,9 +77,8 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. if (eglWindowSurfaceFactory == null) { throw new IllegalStateException("No eglWindowSurfaceFactory provided"); } - this.renderer = renderer; - glThread = new GLThread(viewWeakReference); - glThread.start(); + + super.setRenderer(renderer); } /** @@ -200,164 +114,9 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. eglConfigChooser = configChooser; } - /** - * Set the rendering mode. When renderMode is - * RENDERMODE_CONTINUOUSLY, the renderer is called - * repeatedly to re-render the scene. When renderMode - * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface - * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. - *

- * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance - * by allowing the GPU and CPU to idle when the view does not need to be updated. - *

- * This method can only be called after {@link #setRenderer(GLSurfaceView.Renderer)} - * - * @param renderMode one of the RENDERMODE_X constants - */ - public void setRenderMode(int renderMode) { - glThread.setRenderMode(renderMode); - } - - /** - * Get the current rendering mode. May be called - * from any thread. Must not be called before a renderer has been set. - * - * @return the current rendering mode. - */ - public int getRenderMode() { - return glThread.getRenderMode(); - } - - /** - * Request that the renderer render a frame. - * This method is typically used when the render mode has been set to - * RENDERMODE_WHEN_DIRTY, so that frames are only rendered on demand. - * May be called - * from any thread. Must not be called before a renderer has been set. - */ - public void requestRender() { - glThread.requestRender(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceCreated(SurfaceHolder holder) { - glThread.surfaceCreated(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceDestroyed(SurfaceHolder holder) { - // Surface will be destroyed when we return - glThread.surfaceDestroyed(); - } - - /** - * This method is part of the SurfaceHolder.Callback interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - glThread.onWindowResize(w, h); - } - - /** - * This method is part of the SurfaceHolder.Callback2 interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ @Override - public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) { - if (glThread != null) { - glThread.requestRenderAndNotify(finishDrawing); - } - } - - /** - * This method is part of the SurfaceHolder.Callback2 interface, and is - * not normally called or subclassed by clients of GLSurfaceView. - */ - @Deprecated - @Override - public void surfaceRedrawNeeded(SurfaceHolder holder) { - // Since we are part of the framework we know only surfaceRedrawNeededAsync - // will be called. - } - - /** - * Pause the rendering thread, optionally tearing down the EGL context - * depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}. - *

- * Must not be called before a renderer has been set. - */ - public void onPause() { - glThread.onPause(); - } - - /** - * Resumes the rendering thread, re-creating the OpenGL context if necessary. It - * is the counterpart to {@link #onPause()}. - *

- * Must not be called before a renderer has been set. - */ - public void onResume() { - glThread.onResume(); - } - - /** - * Queue a runnable to be run on the GL rendering thread. This can be used - * to communicate with the Renderer on the rendering thread. - * Must not be called before a renderer has been set. - * - * @param r the runnable to be run on the GL rendering thread. - */ - 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 void waitForEmpty() { - glThread.waitForEmpty(); - } - - - /** - * This method is used as part of the View class and is not normally - * called or subclassed by clients of GLSurfaceView. - */ - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (detached && (renderer != null)) { - int renderMode = RENDERMODE_CONTINUOUSLY; - if (glThread != null) { - renderMode = glThread.getRenderMode(); - } - glThread = new GLThread(viewWeakReference); - if (renderMode != RENDERMODE_CONTINUOUSLY) { - glThread.setRenderMode(renderMode); - } - glThread.start(); - } - detached = false; - } - - @Override - protected void onDetachedFromWindow() { - if (detachedListener != null) { - detachedListener.onGLSurfaceViewDetached(); - } - if (glThread != null) { - glThread.requestExitAndWait(); - } - detached = true; - super.onDetachedFromWindow(); + protected void createRenderThread() { + renderThread = new GLThread(viewWeakReference); } /** @@ -563,28 +322,11 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. * All potentially blocking synchronization is done through the * sGLThreadManager object. This avoids multiple-lock ordering issues. */ - static class GLThread extends Thread { - GLThread(WeakReference glSurfaceViewWeakRef) { + static class GLThread extends MapLibreSurfaceView.RenderThread { + GLThread(WeakReference surfaceViewWeakRef) { super(); - width = 0; - height = 0; - requestRender = true; - renderMode = RENDERMODE_CONTINUOUSLY; - wantRenderNotification = false; - mGLSurfaceViewWeakRef = glSurfaceViewWeakRef; - } - @Override - public void run() { - setName("GLThread " + getId()); - - try { - guardedRun(); - } catch (InterruptedException exception) { - // fall thru and exit normally - } finally { - glThreadManager.threadExiting(this); - } + mSurfaceViewWeakRef = surfaceViewWeakRef; } /* @@ -606,12 +348,13 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. if (haveEglContext) { eglHelper.finish(); haveEglContext = false; - glThreadManager.releaseEglContextLocked(this); + renderThreadManager.notifyAll(); } } - private void guardedRun() throws InterruptedException { - eglHelper = new EglHelper(mGLSurfaceViewWeakRef); + @Override + protected void guardedRun() throws InterruptedException { + eglHelper = new EglHelper(mSurfaceViewWeakRef); haveEglContext = false; haveEglSurface = false; wantRenderNotification = false; @@ -632,7 +375,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. Runnable finishDrawingRunnable = null; while (true) { - synchronized (glThreadManager) { + synchronized (renderThreadManager) { while (true) { if (shouldExit) { return; @@ -648,7 +391,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. if (paused != requestPaused) { pausing = requestPaused; paused = requestPaused; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } // Do we need to give up the EGL context? @@ -673,7 +416,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. // When pausing, optionally release the EGL Context: if (pausing && haveEglContext) { - MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get(); + MapLibreGLSurfaceView view = mSurfaceViewWeakRef.get(); boolean preserveEglContextOnPause = view != null && view.preserveEGLContextOnPause; if (!preserveEglContextOnPause) { stopEglContextLocked(); @@ -687,20 +430,20 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. } waitingForSurface = true; surfaceIsBad = false; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } // Have we acquired the surface view surface? if (hasSurface && waitingForSurface) { waitingForSurface = false; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } if (doRenderNotification) { this.wantRenderNotification = false; doRenderNotification = false; renderComplete = true; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } if (this.finishDrawingRunnable != null) { @@ -719,13 +462,13 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. try { eglHelper.start(); } catch (RuntimeException exception) { - glThreadManager.releaseEglContextLocked(this); + renderThreadManager.notifyAll(); return; } haveEglContext = true; createEglContext = true; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } } @@ -749,7 +492,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. this.sizeChanged = false; } requestRender = false; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); if (this.wantRenderNotification) { wantRenderNotification = true; } @@ -764,7 +507,7 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. } } // By design, this is the only place in a GLThread thread where we wait(). - glThreadManager.wait(); + renderThreadManager.wait(); } } // end of synchronized(sGLThreadManager) @@ -776,15 +519,15 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. if (createEglSurface) { if (eglHelper.createSurface()) { - synchronized (glThreadManager) { + synchronized (renderThreadManager) { finishedCreatingEglSurface = true; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } } else { - synchronized (glThreadManager) { + synchronized (renderThreadManager) { finishedCreatingEglSurface = true; surfaceIsBad = true; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } continue; } @@ -798,24 +541,24 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. } if (createEglContext) { - MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get(); + MapLibreSurfaceView view = mSurfaceViewWeakRef.get(); if (view != null) { - view.renderer.onSurfaceCreated(gl, eglHelper.mEglConfig); + view.renderer.onSurfaceCreated(null); } createEglContext = false; } if (sizeChanged) { - MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get(); + MapLibreSurfaceView view = mSurfaceViewWeakRef.get(); if (view != null) { - view.renderer.onSurfaceChanged(gl, w, h); + view.renderer.onSurfaceChanged(w, h); } sizeChanged = false; } - MapLibreGLSurfaceView view = mGLSurfaceViewWeakRef.get(); + MapLibreSurfaceView view = mSurfaceViewWeakRef.get(); if (view != null) { - view.renderer.onDrawFrame(gl); + view.renderer.onDrawFrame(); if (finishDrawingRunnable != null) { finishDrawingRunnable.run(); finishDrawingRunnable = null; @@ -835,9 +578,9 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. // Log the error to help developers understand why rendering stopped. EglHelper.logEglErrorAsWarning(TAG, "eglSwapBuffers", swapError); - synchronized (glThreadManager) { + synchronized (renderThreadManager) { surfaceIsBad = true; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } break; } @@ -852,72 +595,33 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. /* * clean-up everything... */ - synchronized (glThreadManager) { + synchronized (renderThreadManager) { stopEglSurfaceLocked(); stopEglContextLocked(); } } } + @Override + protected boolean readyToDraw() { + return super.readyToDraw() && !surfaceIsBad; + } + + @Override public boolean ableToDraw() { return haveEglContext && haveEglSurface && readyToDraw(); } - private boolean readyToDraw() { - return (!paused) && hasSurface && (!surfaceIsBad) - && (width > 0) && (height > 0) - && (requestRender || (renderMode == RENDERMODE_CONTINUOUSLY)); - } - - public void setRenderMode(int renderMode) { - synchronized (glThreadManager) { - this.renderMode = renderMode; - glThreadManager.notifyAll(); - } - } - - public int getRenderMode() { - synchronized (glThreadManager) { - return renderMode; - } - } - - public void requestRender() { - synchronized (glThreadManager) { - requestRender = true; - glThreadManager.notifyAll(); - } - } - - public void requestRenderAndNotify(Runnable finishDrawing) { - synchronized (glThreadManager) { - // If we are already on the GL thread, this means a client callback - // has caused reentrancy, for example via updating the SurfaceView parameters. - // We will return to the client rendering code, so here we don't need to - // do anything. - if (Thread.currentThread() == this) { - return; - } - - wantRenderNotification = true; - requestRender = true; - renderComplete = false; - finishDrawingRunnable = finishDrawing; - - glThreadManager.notifyAll(); - } - } - public void surfaceCreated() { - synchronized (glThreadManager) { + synchronized (renderThreadManager) { hasSurface = true; finishedCreatingEglSurface = false; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); while (waitingForSurface && !finishedCreatingEglSurface && !exited) { try { - glThreadManager.wait(); + renderThreadManager.wait(); } catch (InterruptedException exception) { Thread.currentThread().interrupt(); } @@ -925,231 +629,24 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder. } } - public void surfaceDestroyed() { - synchronized (glThreadManager) { - hasSurface = false; - glThreadManager.notifyAll(); - while ((!waitingForSurface) && (!exited)) { - try { - glThreadManager.wait(); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void onPause() { - synchronized (glThreadManager) { - requestPaused = true; - glThreadManager.notifyAll(); - while ((!exited) && (!paused)) { - try { - glThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void onResume() { - synchronized (glThreadManager) { - requestPaused = false; - requestRender = true; - renderComplete = false; - glThreadManager.notifyAll(); - while ((!exited) && paused && (!renderComplete)) { - try { - glThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void onWindowResize(int w, int h) { - synchronized (glThreadManager) { - width = w; - height = h; - sizeChanged = true; - requestRender = true; - renderComplete = false; - - // If we are already on the GL thread, this means a client callback - // has caused reentrancy, for example via updating the SurfaceView parameters. - // We need to process the size change eventually though and update our EGLSurface. - // So we set the parameters and return so they can be processed on our - // next iteration. - if (Thread.currentThread() == this) { - return; - } - - glThreadManager.notifyAll(); - - // Wait for thread to react to resize and render a frame - while (!exited && !paused && !renderComplete - && ableToDraw()) { - try { - glThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - public void requestExitAndWait() { - // don't call this from GLThread thread or it is a guaranteed - // deadlock! - synchronized (glThreadManager) { - shouldExit = true; - glThreadManager.notifyAll(); - while (!exited) { - try { - glThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - public void requestReleaseEglContextLocked() { shouldReleaseEglContext = true; - glThreadManager.notifyAll(); + renderThreadManager.notifyAll(); } - /** - * Queue an "event" to be run on the GL rendering thread. - * - * @param r the runnable to be run on the GL rendering thread. - */ - public void queueEvent(@NonNull Runnable r) { - synchronized (glThreadManager) { - eventQueue.add(r); - glThreadManager.notifyAll(); - } - } - - /** - * Wait for the queue to become empty - * @param timeoutMillis Timeout in milliseconds, zero for indefinite wait - * @return Number of queue items remaining - */ - public void waitForEmpty() { - synchronized (glThreadManager) { - // Wait for the queue to be empty - while (!this.eventQueue.isEmpty()) { - try { - glThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } - } - - // Once the thread is started, all accesses to the following member - // variables are protected by the sGLThreadManager monitor - private boolean shouldExit; - private boolean exited; - private boolean requestPaused; - private boolean paused; - private boolean hasSurface; private boolean surfaceIsBad; - private boolean waitingForSurface; private boolean haveEglContext; private boolean haveEglSurface; private boolean finishedCreatingEglSurface; private boolean shouldReleaseEglContext; - private int width; - private int height; - private int renderMode; - private boolean requestRender; - private boolean wantRenderNotification; - private boolean renderComplete; - private ArrayList eventQueue = new ArrayList<>(); - private boolean sizeChanged = true; - private Runnable finishDrawingRunnable = null; - // End of member variables protected by the sGLThreadManager monitor. private EglHelper eglHelper; /** * Set once at thread construction time, nulled out when the parent view is garbage - * called. This weak reference allows the GLSurfaceView to be garbage collected while - * the GLThread is still alive. + * called. This weak reference allows the SurfaceView to be garbage collected while + * the RenderThread is still alive. */ - private WeakReference mGLSurfaceViewWeakRef; - - } - - static class LogWriter extends Writer { - - @Override - public void close() { - flushBuilder(); - } - - @Override - public void flush() { - flushBuilder(); - } - - @Override - public void write(char[] buf, int offset, int count) { - for (int i = 0; i < count; i++) { - char c = buf[offset + i]; - if (c == '\n') { - flushBuilder(); - } else { - mBuilder.append(c); - } - } - } - - private void flushBuilder() { - if (mBuilder.length() > 0) { - Log.v("GLSurfaceView", mBuilder.toString()); - mBuilder.delete(0, mBuilder.length()); - } - } - - private StringBuilder mBuilder = new StringBuilder(); - } - - private void checkRenderThreadState() { - if (glThread != null) { - throw new IllegalStateException("setRenderer has already been called for this instance."); - } - } - - private static class GLThreadManager { - - synchronized void threadExiting(GLThread thread) { - thread.exited = true; - notifyAll(); - } - - /* - * Releases the EGL context. Requires that we are already in the - * sGLThreadManager monitor when this is called. - */ - void releaseEglContextLocked(GLThread thread) { - notifyAll(); - } - } - - /** - * Listener interface that notifies when a {@link MapLibreGLSurfaceView} is detached from window. - */ - public interface OnGLSurfaceViewDetachedListener { - - /** - * Called when a {@link MapLibreGLSurfaceView} is detached from window. - */ - void onGLSurfaceViewDetached(); + protected WeakReference mSurfaceViewWeakRef; } } diff --git a/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/textureview/GLTextureViewRenderThread.java b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/textureview/GLTextureViewRenderThread.java new file mode 100644 index 000000000..ecaa3b15d --- /dev/null +++ b/platform/android/MapLibreAndroid/src/opengl/java/org/maplibre/android/maps/renderer/textureview/GLTextureViewRenderThread.java @@ -0,0 +1,344 @@ +package org.maplibre.android.maps.renderer.textureview; + +import android.view.TextureView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + +import org.maplibre.android.log.Logger; +import org.maplibre.android.maps.renderer.egl.EGLConfigChooser; + +import java.lang.ref.WeakReference; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL10; + +/** + * The render thread is responsible for managing the communication between the + * ui thread and the render thread it creates. Also, the EGL and GL contexts + * are managed from here. + */ +public class GLTextureViewRenderThread extends TextureViewRenderThread { + + @NonNull + private final EGLHolder eglHolder; + + private boolean destroyContext; + + /** + * Create a render thread for the given TextureView / Maprenderer combination. + * + * @param textureView the TextureView + * @param mapRenderer the MapRenderer + */ + @UiThread + public GLTextureViewRenderThread(@NonNull TextureView textureView, @NonNull TextureViewMapRenderer mapRenderer) { + super(textureView, mapRenderer); + this.eglHolder = new EGLHolder(new WeakReference<>(textureView), mapRenderer.isTranslucentSurface()); + } + + // Thread implementation + + @Override + public void run() { + try { + + while (true) { + Runnable event = null; + boolean initializeEGL = false; + boolean recreateSurface = false; + int w = -1; + int h = -1; + + // Guarded block + synchronized (lock) { + while (true) { + + if (shouldExit) { + return; + } + + // If any events are scheduled, pop one for processing + if (!eventQueue.isEmpty()) { + event = eventQueue.remove(0); + break; + } + + if (destroySurface) { + eglHolder.destroySurface(); + destroySurface = false; + break; + } + + if (destroyContext) { + eglHolder.destroyContext(); + destroyContext = false; + break; + } + + if (surfaceTexture != null && !paused && requestRender) { + + w = width; + h = height; + + // Initialize EGL if needed + if (eglHolder.eglContext == EGL10.EGL_NO_CONTEXT) { + initializeEGL = true; + break; + } + + // (re-)Initialize EGL Surface if needed + if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) { + recreateSurface = true; + break; + } + + // Reset the request render flag now, so we can catch new requests + // while rendering + requestRender = false; + + // Break the guarded loop and continue to process + break; + } + + + // Wait until needed + lock.wait(); + + } // end guarded while loop + + } // end guarded block + + // Run event, if any + if (event != null) { + event.run(); + continue; + } + + GL10 gl = eglHolder.createGL(); + + // Initialize EGL + if (initializeEGL) { + eglHolder.prepare(); + synchronized (lock) { + if (!eglHolder.createSurface()) { + // Cleanup the surface if one could not be created + // and wait for another to be ready. + destroySurface = true; + continue; + } + } + mapRenderer.onSurfaceCreated(null); + mapRenderer.onSurfaceChanged(w, h); + continue; + } + + // If the surface size has changed inform the map renderer. + if (recreateSurface) { + synchronized (lock) { + eglHolder.createSurface(); + } + mapRenderer.onSurfaceChanged(w, h); + continue; + } + + if (sizeChanged) { + mapRenderer.onSurfaceChanged(w, h); + sizeChanged = false; + continue; + } + + // Don't continue without a surface + if (eglHolder.eglSurface == EGL10.EGL_NO_SURFACE) { + continue; + } + + // Time to render a frame + mapRenderer.onDrawFrame(); + + // Swap and check the result + int swapError = eglHolder.swap(); + switch (swapError) { + case EGL10.EGL_SUCCESS: + break; + case EGL11.EGL_CONTEXT_LOST: + Logger.w(TAG, "Context lost. Waiting for re-aquire"); + synchronized (lock) { + surfaceTexture = null; + destroySurface = true; + destroyContext = true; + } + break; + default: + Logger.w(TAG, String.format("eglSwapBuffer error: %s. Waiting or new surface", swapError)); + // Probably lost the surface. Clear the current one and + // wait for a new one to be set + synchronized (lock) { + surfaceTexture = null; + destroySurface = true; + } + } + + } + + } catch (InterruptedException err) { + // To be expected + } finally { + // Cleanup + eglHolder.cleanup(); + + // Signal we're done + synchronized (lock) { + this.exited = true; + lock.notifyAll(); + } + } + } + + /** + * Holds the EGL state and offers methods to mutate it. + */ + private static class EGLHolder { + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private final WeakReference textureViewWeakRef; + private boolean translucentSurface; + + private EGL10 egl; + @Nullable + private EGLConfig eglConfig; + private EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY; + private EGLContext eglContext = EGL10.EGL_NO_CONTEXT; + private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE; + + EGLHolder(WeakReference textureViewWeakRef, boolean translucentSurface) { + this.textureViewWeakRef = textureViewWeakRef; + this.translucentSurface = translucentSurface; + } + + void prepare() { + this.egl = (EGL10) EGLContext.getEGL(); + + // Only re-initialize display when needed + if (eglDisplay == EGL10.EGL_NO_DISPLAY) { + this.eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + if (eglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + + int[] version = new int[2]; + if (!egl.eglInitialize(eglDisplay, version)) { + throw new RuntimeException("eglInitialize failed"); + } + } + + if (textureViewWeakRef == null) { + // No texture view present + eglConfig = null; + eglContext = EGL10.EGL_NO_CONTEXT; + } else if (eglContext == EGL10.EGL_NO_CONTEXT) { + eglConfig = new EGLConfigChooser(translucentSurface).chooseConfig(egl, eglDisplay); + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; + eglContext = egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); + } + + if (eglContext == EGL10.EGL_NO_CONTEXT) { + throw new RuntimeException("createContext"); + } + } + + @NonNull + GL10 createGL() { + return (GL10) eglContext.getGL(); + } + + boolean createSurface() { + // The window size has changed, so we need to create a new surface. + destroySurface(); + + // Create an EGL surface we can render into. + TextureView view = textureViewWeakRef.get(); + if (view != null && view.getSurfaceTexture() != null) { + int[] surfaceAttribs = {EGL10.EGL_NONE}; + eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, view.getSurfaceTexture(), surfaceAttribs); + } else { + eglSurface = EGL10.EGL_NO_SURFACE; + } + + if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { + int error = egl.eglGetError(); + if (error == EGL10.EGL_BAD_NATIVE_WINDOW) { + Logger.e(TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); + } + return false; + } + + return makeCurrent(); + } + + boolean makeCurrent() { + if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { + // Could not make the context current, probably because the underlying + // SurfaceView surface has been destroyed. + Logger.w(TAG, String.format("eglMakeCurrent: %s", egl.eglGetError())); + return false; + } + + return true; + } + + int swap() { + if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) { + return egl.eglGetError(); + } + return EGL10.EGL_SUCCESS; + } + + private void destroySurface() { + if (eglSurface == EGL10.EGL_NO_SURFACE) { + return; + } + + if (!egl.eglDestroySurface(eglDisplay, eglSurface)) { + Logger.w(TAG, String.format("Could not destroy egl surface. Display %s, Surface %s", eglDisplay, eglSurface)); + } + + eglSurface = EGL10.EGL_NO_SURFACE; + } + + private void destroyContext() { + if (eglContext == EGL10.EGL_NO_CONTEXT) { + return; + } + + if (!egl.eglDestroyContext(eglDisplay, eglContext)) { + Logger.w(TAG, String.format("Could not destroy egl context. Display %s, Context %s", eglDisplay, eglContext)); + } + + eglContext = EGL10.EGL_NO_CONTEXT; + } + + private void terminate() { + if (eglDisplay == EGL10.EGL_NO_DISPLAY) { + return; + } + + if (!egl.eglTerminate(eglDisplay)) { + Logger.w(TAG, String.format("Could not terminate egl. Display %s", eglDisplay)); + } + eglDisplay = EGL10.EGL_NO_DISPLAY; + } + + void cleanup() { + destroySurface(); + destroyContext(); + terminate(); + } + } +} diff --git a/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java new file mode 100644 index 000000000..2ac787831 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/MapRendererFactory.java @@ -0,0 +1,49 @@ +package org.maplibre.android.maps.renderer; + +import android.content.Context; +import android.view.Surface; +import android.view.TextureView; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +import org.maplibre.android.maps.renderer.surfaceview.MapLibreVulkanSurfaceView; +import org.maplibre.android.maps.renderer.surfaceview.SurfaceViewMapRenderer; +import org.maplibre.android.maps.renderer.surfaceview.VulkanSurfaceViewMapRenderer; +import org.maplibre.android.maps.renderer.textureview.TextureViewMapRenderer; +import org.maplibre.android.maps.renderer.textureview.VulkanTextureViewRenderThread; + +@Keep +public class MapRendererFactory { + public static TextureViewMapRenderer newTextureViewMapRenderer(@NonNull Context context, TextureView textureView, + String localFontFamily, boolean translucentSurface, + Runnable initCallback) { + + TextureViewMapRenderer mapRenderer = new TextureViewMapRenderer(context, textureView, + localFontFamily, translucentSurface) { + @Override + protected void onSurfaceCreated(Surface surface) { + initCallback.run(); + super.onSurfaceCreated(surface); + } + }; + + mapRenderer.setRenderThread(new VulkanTextureViewRenderThread(textureView, mapRenderer)); + return mapRenderer; + } + + public static SurfaceViewMapRenderer newSurfaceViewMapRenderer(@NonNull Context context, String localFontFamily, + boolean renderSurfaceOnTop, Runnable initCallback) { + + MapLibreVulkanSurfaceView surfaceView = new MapLibreVulkanSurfaceView(context); + surfaceView.setZOrderMediaOverlay(renderSurfaceOnTop); + + return new VulkanSurfaceViewMapRenderer(context, surfaceView, localFontFamily) { + @Override + public void onSurfaceCreated(Surface surface) { + initCallback.run(); + super.onSurfaceCreated(surface); + } + }; + } +} diff --git a/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreVulkanSurfaceView.java b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreVulkanSurfaceView.java new file mode 100644 index 000000000..06c780bdc --- /dev/null +++ b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/MapLibreVulkanSurfaceView.java @@ -0,0 +1,183 @@ +package org.maplibre.android.maps.renderer.surfaceview; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; + +import java.lang.ref.WeakReference; + +public class MapLibreVulkanSurfaceView extends MapLibreSurfaceView { + + protected final WeakReference viewWeakReference = new WeakReference<>(this); + + public MapLibreVulkanSurfaceView(Context context) { + super(context); + } + + public MapLibreVulkanSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void createRenderThread() { + renderThread = new VulkanThread(viewWeakReference); + } + + static class VulkanThread extends MapLibreSurfaceView.RenderThread { + VulkanThread(WeakReference surfaceViewWeakRef) { + super(); + mSurfaceViewWeakRef = surfaceViewWeakRef; + } + + @Override + protected void guardedRun() throws InterruptedException { + wantRenderNotification = false; + + boolean sizeChanged = false; + boolean initSurface = false; + boolean destroySurface = false; + boolean wantRenderNotification = false; + boolean doRenderNotification = false; + int w = 0; + int h = 0; + Runnable event = null; + Runnable finishDrawingRunnable = null; + + while (true) { + synchronized (renderThreadManager) { + while (true) { + if (shouldExit) { + return; + } + + if (!eventQueue.isEmpty()) { + event = eventQueue.remove(0); + break; + } + + // Update the pause state. + if (paused != requestPaused) { + paused = requestPaused; + renderThreadManager.notifyAll(); + } + + if (paused && graphicsSurfaceCreated) { + MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get(); + if (view != null) { + destroySurface = true; + graphicsSurfaceCreated = false; + } + renderThreadManager.notifyAll(); + } + + // lost surface + if (!hasSurface && !waitingForSurface) { + MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get(); + if (view != null) { + destroySurface = true; + graphicsSurfaceCreated = false; + } + waitingForSurface = true; + renderThreadManager.notifyAll(); + } + + // acquired surface + if (hasSurface && waitingForSurface) { + MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get(); + if (view != null) { + initSurface = true; + graphicsSurfaceCreated = true; + } + waitingForSurface = false; + renderThreadManager.notifyAll(); + } + + if (doRenderNotification) { + this.wantRenderNotification = false; + doRenderNotification = false; + renderComplete = true; + renderThreadManager.notifyAll(); + } + + if (this.finishDrawingRunnable != null) { + finishDrawingRunnable = this.finishDrawingRunnable; + this.finishDrawingRunnable = null; + } + + // Ready to draw? + if (readyToDraw()) { + if (this.sizeChanged) { + sizeChanged = true; + w = width; + h = height; + this.wantRenderNotification = true; + this.sizeChanged = false; + } + requestRender = false; + renderThreadManager.notifyAll(); + if (this.wantRenderNotification) { + wantRenderNotification = true; + } + break; + } else { + if (finishDrawingRunnable != null) { + Log.w(TAG, "Warning, !readyToDraw() but waiting for " + + "draw finished! Early reporting draw finished."); + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + } + // By design, this is the only place in a RenderThread thread where we wait(). + renderThreadManager.wait(); + } + } // end of synchronized(sRenderThreadManager) + + if (event != null) { + event.run(); + event = null; + continue; + } + + MapLibreVulkanSurfaceView view = mSurfaceViewWeakRef.get(); + + if (destroySurface) { + if (view != null) { + view.renderer.onSurfaceDestroyed(); + destroySurface = false; + } + } + + if (initSurface) { + if (view != null) { + view.renderer.onSurfaceCreated(view.getHolder().getSurface()); + initSurface = false; + } + } + + if (sizeChanged) { + if (view != null) { + view.renderer.onSurfaceChanged(w, h); + sizeChanged = false; + } + } + + if (view != null) { + view.renderer.onDrawFrame(); + if (finishDrawingRunnable != null) { + finishDrawingRunnable.run(); + finishDrawingRunnable = null; + } + } + + if (wantRenderNotification) { + doRenderNotification = true; + wantRenderNotification = false; + } + } + } + + private boolean graphicsSurfaceCreated; + + private WeakReference mSurfaceViewWeakRef; + } +} diff --git a/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/VulkanSurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/VulkanSurfaceViewMapRenderer.java new file mode 100644 index 000000000..40e91b830 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/surfaceview/VulkanSurfaceViewMapRenderer.java @@ -0,0 +1,16 @@ +package org.maplibre.android.maps.renderer.surfaceview; + +import android.content.Context; +import androidx.annotation.NonNull; + +public class VulkanSurfaceViewMapRenderer extends SurfaceViewMapRenderer { + + public VulkanSurfaceViewMapRenderer(Context context, + @NonNull MapLibreVulkanSurfaceView surfaceView, + String localIdeographFontFamily) { + super(context, surfaceView, localIdeographFontFamily); + + this.surfaceView.setRenderer(this); + } + +} \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/textureview/VulkanTextureViewRenderThread.java b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/textureview/VulkanTextureViewRenderThread.java new file mode 100644 index 000000000..be04ae5c6 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/vulkan/java/org/maplibre/android/maps/renderer/textureview/VulkanTextureViewRenderThread.java @@ -0,0 +1,114 @@ +package org.maplibre.android.maps.renderer.textureview; + +import android.view.Surface; +import android.view.TextureView; + +import androidx.annotation.NonNull; +import androidx.annotation.UiThread; + +public class VulkanTextureViewRenderThread extends TextureViewRenderThread { + + private Surface surface; + + @UiThread + public VulkanTextureViewRenderThread(@NonNull TextureView textureView, @NonNull TextureViewMapRenderer mapRenderer) { + super(textureView, mapRenderer); + } + + // Thread implementation + + @Override + public void run() { + try { + + while (true) { + Runnable event = null; + boolean initialize = false; + int w = -1; + int h = -1; + + // Guarded block + synchronized (lock) { + while (true) { + + if (shouldExit) { + return; + } + + // If any events are scheduled, pop one for processing + if (!eventQueue.isEmpty()) { + event = eventQueue.remove(0); + break; + } + + if (destroySurface) { + destroySurface = false; + mapRenderer.onSurfaceDestroyed(); + surface = null; + break; + } + + if (surfaceTexture != null && !paused && requestRender && surface == null) { + + w = width; + h = height; + surface = new Surface(surfaceTexture); + + initialize = true; + + // Reset the request render flag now, so we can catch new requests + // while rendering + requestRender = false; + + // Break the guarded loop and continue to process + break; + } + + if (requestRender && !paused) { + break; + } + + // Wait until needed + lock.wait(); + + } // end guarded while loop + + } // end guarded block + + // Run event, if any + if (event != null) { + event.run(); + continue; + } + + if (initialize) { + mapRenderer.onSurfaceCreated(surface); + mapRenderer.onSurfaceChanged( w, h); + + initialize = false; + continue; + } + + // If the surface size has changed inform the map renderer. + if (sizeChanged) { + mapRenderer.onSurfaceChanged(w, h); + sizeChanged = false; + continue; + } + + // Time to render a frame + mapRenderer.onDrawFrame(); + } + + } catch (InterruptedException err) { + // To be expected + } finally { + + // Signal we're done + synchronized (lock) { + this.exited = true; + lock.notifyAll(); + } + } + } +} diff --git a/platform/android/MapLibreAndroidTestApp/build.gradle b/platform/android/MapLibreAndroidTestApp/build.gradle index 28bace323..ab7596f8a 100644 --- a/platform/android/MapLibreAndroidTestApp/build.gradle +++ b/platform/android/MapLibreAndroidTestApp/build.gradle @@ -65,6 +65,9 @@ android { create("drawable") { dimension = "renderer" } + create("vulkan") { + dimension = "renderer" + } } buildFeatures { diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt index 974d4093e..c84c6b215 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt @@ -2,6 +2,7 @@ package org.maplibre.android.maps import android.content.Context import android.graphics.PointF +import android.view.View import androidx.test.annotation.UiThreadTest import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import androidx.test.platform.app.InstrumentationRegistry @@ -447,5 +448,9 @@ class NativeMapViewTest : AppCenter() { override fun waitForEmpty() { // no-op } + + override fun getView(): View? { + return null; + } } } diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RenderViewGetterTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RenderViewGetterTest.kt index 50e62dc72..c5dda49fb 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RenderViewGetterTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/maps/RenderViewGetterTest.kt @@ -1,5 +1,6 @@ package org.maplibre.android.testapp.maps +import android.view.SurfaceView import android.view.TextureView import android.view.ViewGroup import androidx.test.annotation.UiThreadTest @@ -8,7 +9,6 @@ import androidx.test.rule.ActivityTestRule import org.maplibre.android.AppCenter import org.maplibre.android.maps.MapView import org.maplibre.android.maps.MapLibreMapOptions -import org.maplibre.android.maps.renderer.glsurfaceview.MapLibreGLSurfaceView import org.maplibre.android.testapp.activity.FeatureOverviewActivity import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue @@ -30,11 +30,11 @@ class RenderViewGetterTest : AppCenter() { @Test @UiThreadTest - fun testGLSurfaceView() { + fun testSurfaceView() { rootView = rule.activity.findViewById(android.R.id.content) mapView = MapView(rule.activity) assertNotNull(mapView.renderView) - assertTrue(mapView.renderView is MapLibreGLSurfaceView) + assertTrue(mapView.renderView is SurfaceView) } @Test diff --git a/platform/android/android.cmake b/platform/android/android.cmake index b28104d3a..083c59b21 100644 --- a/platform/android/android.cmake +++ b/platform/android/android.cmake @@ -37,7 +37,6 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/android/src/bitmap.hpp ${PROJECT_SOURCE_DIR}/platform/android/src/bitmap_factory.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/bitmap_factory.hpp - ${PROJECT_SOURCE_DIR}/platform/android/src/gl_functions.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/image.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/jni.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/jni.hpp @@ -48,7 +47,6 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/android/src/timer.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_backend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_frontend.cpp - ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gl/headless_backend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/map/map_snapshotter.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/platform/time.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/asset_file_source.cpp @@ -72,9 +70,26 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/thread_local.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/utf.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/layermanager/layer_manager.cpp - ${PROJECT_SOURCE_DIR}/platform/linux/src/headless_backend_egl.cpp ) +if(MLN_WITH_OPENGL) + target_sources( + mbgl-core + PRIVATE + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gl/headless_backend.cpp + ${PROJECT_SOURCE_DIR}/platform/linux/src/headless_backend_egl.cpp + ${PROJECT_SOURCE_DIR}/platform/android/src/gl_functions.cpp + ) +endif() + +if(MLN_WITH_VULKAN) + target_sources( + mbgl-core + PRIVATE + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/vulkan/headless_backend.cpp + ) +endif() + target_include_directories( mbgl-core PRIVATE ${PROJECT_SOURCE_DIR}/platform/default/include diff --git a/platform/android/gradle/download-vulkan-validation.gradle b/platform/android/gradle/download-vulkan-validation.gradle new file mode 100644 index 000000000..d707c245a --- /dev/null +++ b/platform/android/gradle/download-vulkan-validation.gradle @@ -0,0 +1,51 @@ +//apply plugin: 'com.android.application' +/** + * Download validation layer binary release zip file from Khronos github repo + * https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases + * + * To use this script, add the following to your module's build.gradle: + * ext.vvl_version='your-new-version' + * apply from: "${PATH-TO-THIS}/download_vvl.gradle" + * To update to a new version: + * - change the ext.vvl_version to a new version string. + * - delete directory pointed by ${VVL_JNILIB_DIR}. + * - sync gradle script in IDE and rebuild project. + * + * Note: binary release can also be manually downloaded and put it into + * the default jniLibs directory at app/src/main/jniLibs. + */ + +// get the validation layer version. +def VVL_VER = "1.3.290.0" +if (ext.has("vvl_version")) { + VVL_VER = ext.vvl_version +} + +// declare local variables shared between downloading and unzipping. +def VVL_SITE ="https://github.com/KhronosGroup/Vulkan-ValidationLayers" +def VVL_LIB_ROOT= "$projectDir" //rootDir.absolutePath.toString() + "" +def VVL_JNILIB_DIR="${VVL_LIB_ROOT}/src/vulkanDebug/jniLibs" +def VVL_SO_NAME = "libVkLayer_khronos_validation.so" + +// download the release zip file to ${VVL_LIB_ROOT}/ +task download { + def VVL_ZIP_NAME = "releases/download/vulkan-sdk-${VVL_VER}/android-binaries-${VVL_VER}.zip" + mkdir "${VVL_LIB_ROOT}" + def f = new File("${VVL_LIB_ROOT}/android-binaries-${VVL_VER}.zip") + new URL("${VVL_SITE}/${VVL_ZIP_NAME}") + .withInputStream { i -> f.withOutputStream { it << i } } +} + +// unzip the downloaded VVL zip archive to the ${VVL_JNILIB_DIR} for APK packaging. +task unzip(dependsOn: download, type: Copy) { + from zipTree(file("${VVL_LIB_ROOT}/android-binaries-${VVL_VER}.zip")) + into file("${VVL_JNILIB_DIR}") +} + +//android.sourceSets.vulkan.jniLibs { +// srcDirs += ["${VVL_JNILIB_DIR}"] +//} + +android.sourceSets.vulkan.jniLibs { + srcDir(tasks.unzip) +} \ No newline at end of file diff --git a/platform/default/include/mbgl/vulkan/headless_backend.hpp b/platform/default/include/mbgl/vulkan/headless_backend.hpp index 72fecf6b3..97319c32a 100644 --- a/platform/default/include/mbgl/vulkan/headless_backend.hpp +++ b/platform/default/include/mbgl/vulkan/headless_backend.hpp @@ -20,8 +20,6 @@ public: PremultipliedImage readStillImage() override; RendererBackend* getRendererBackend() override; - void swap(); - class Impl { public: virtual ~Impl() = default; diff --git a/platform/default/src/mbgl/vulkan/headless_backend.cpp b/platform/default/src/mbgl/vulkan/headless_backend.cpp index 3dc2cec25..f862acf91 100644 --- a/platform/default/src/mbgl/vulkan/headless_backend.cpp +++ b/platform/default/src/mbgl/vulkan/headless_backend.cpp @@ -24,6 +24,11 @@ public: void createPlatformSurface() override {} void bind() override {} + + void swap() override { + SurfaceRenderableResource::swap(); + static_cast(backend.getContext()).waitFrame(); + } }; HeadlessBackend::HeadlessBackend(const Size size_, @@ -69,10 +74,6 @@ gfx::Renderable& HeadlessBackend::getDefaultRenderable() { return *this; } -void HeadlessBackend::swap() { - static_cast(*context).waitFrame(); -} - PremultipliedImage HeadlessBackend::readStillImage() { auto& contextImpl = static_cast(*context); auto& resourceImpl = static_cast(*resource); diff --git a/platform/glfw/glfw_vulkan_backend.cpp b/platform/glfw/glfw_vulkan_backend.cpp index 75a71eb14..a5f5d9144 100644 --- a/platform/glfw/glfw_vulkan_backend.cpp +++ b/platform/glfw/glfw_vulkan_backend.cpp @@ -58,6 +58,9 @@ mbgl::Size GLFWVulkanBackend::getSize() const { void GLFWVulkanBackend::setSize(const mbgl::Size newSize) { size = newSize; + + auto& contextImpl = static_cast(*context); + contextImpl.requestSurfaceUpdate(); } std::vector GLFWVulkanBackend::getInstanceExtensions() { diff --git a/platform/windows/windows.cmake b/platform/windows/windows.cmake index c1c590dd6..964005539 100644 --- a/platform/windows/windows.cmake +++ b/platform/windows/windows.cmake @@ -34,7 +34,6 @@ target_sources( PRIVATE ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_backend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_frontend.cpp - ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gl/headless_backend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/i18n/collator.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/i18n/number_format.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/layermanager/layer_manager.cpp @@ -80,6 +79,14 @@ target_compile_definitions( USE_STD_FILESYSTEM ) +if(MLN_WITH_OPENGL) + target_sources( + mbgl-core + PRIVATE + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gl/headless_backend.cpp + ) +endif() + if(MLN_WITH_EGL) find_package(unofficial-angle CONFIG REQUIRED) target_sources( @@ -123,12 +130,6 @@ elseif(MLN_WITH_OSMESA) OSMesa::libGLESv2 ) elseif(MLN_WITH_VULKAN) - target_include_directories( - mbgl-core - PRIVATE - ${PROJECT_SOURCE_DIR}/vendor/Vulkan-Headers/include - ) - target_sources( mbgl-core PRIVATE diff --git a/render-test/android/app/build.gradle b/render-test/android/app/build.gradle index 60f96ecb5..713da0619 100644 --- a/render-test/android/app/build.gradle +++ b/render-test/android/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +//apply from: "${rootDir}/../../platform/android/gradle/download-vulkan-validation.gradle" android { ndkVersion '27.0.12077973' @@ -52,6 +53,21 @@ android { } } + flavorDimensions += "renderer" + productFlavors { + create("opengl") { + dimension = "renderer" + } + create("vulkan") { + dimension = "renderer" + externalNativeBuild { + cmake { + arguments "-DMLN_WITH_OPENGL=OFF", "-DMLN_WITH_VULKAN=ON" + } + } + } + } + namespace 'android.app' } diff --git a/shaders/drawable.fill_outline_triangulated.vertex.glsl b/shaders/drawable.fill_outline_triangulated.vertex.glsl index ed21764a6..4ce24cda2 100644 --- a/shaders/drawable.fill_outline_triangulated.vertex.glsl +++ b/shaders/drawable.fill_outline_triangulated.vertex.glsl @@ -58,6 +58,7 @@ void main() { // these transformations used to be applied in the JS and native code bases. // moved them into the shader for clarity and simplicity. + float u_width = 1.0; float halfwidth = u_width / 2.0; float outset = halfwidth + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); diff --git a/src/mbgl/renderer/layers/render_location_indicator_layer.cpp b/src/mbgl/renderer/layers/render_location_indicator_layer.cpp index b1c7e11cd..91a8d5e60 100644 --- a/src/mbgl/renderer/layers/render_location_indicator_layer.cpp +++ b/src/mbgl/renderer/layers/render_location_indicator_layer.cpp @@ -15,11 +15,8 @@ #include #include #include -#include -#include #include #include -#include #include #include #include @@ -29,11 +26,25 @@ #include #include +#include + +#if MLN_RENDER_BACKEND_OPENGL + +#include +#include +#include #include #include #include #include -#include + +#else + +#define MBGL_CHECK_ERROR(x) \ + while (0) { \ + } + +#endif #include @@ -75,6 +86,7 @@ struct LocationIndicatorRenderParameters { std::string puckHatImagePath; }; +#if MLN_RENDER_BACKEND_OPENGL class RenderLocationIndicatorImpl { protected: struct vec2 { @@ -756,6 +768,25 @@ public: }; bool RenderLocationIndicatorImpl::anisotropicFilteringAvailable = false; +#else + +class RenderLocationIndicatorImpl { +public: + RenderLocationIndicatorImpl(std::string) {} + + void release() {} + void updateFeature() {} + void updatePuckGeometry([[maybe_unused]] const mbgl::LocationIndicatorRenderParameters& params) {} + +public: + mbgl::LocationIndicatorRenderParameters parameters; + std::shared_ptr feature; + std::shared_ptr> featureEnvelope; + static bool anisotropicFilteringAvailable; +}; + +bool RenderLocationIndicatorImpl::anisotropicFilteringAvailable = false; +#endif using namespace style; namespace { @@ -845,6 +876,7 @@ void RenderLocationIndicatorLayer::prepare(const LayerPrepareParameters& p) { renderImpl->updatePuckGeometry(renderImpl->parameters); } +#if MLN_RENDER_BACKEND_OPENGL void RenderLocationIndicatorLayer::render(PaintParameters& paintParameters) { auto& glContext = static_cast(paintParameters.context); @@ -863,6 +895,8 @@ void RenderLocationIndicatorLayer::render(PaintParameters& paintParameters) { glContext.setDirtyState(); } +#endif + void RenderLocationIndicatorLayer::populateDynamicRenderFeatureIndex(DynamicFeatureIndex& index) const { renderImpl->updateFeature(); if (!renderImpl->featureEnvelope->empty()) index.insert(renderImpl->feature, renderImpl->featureEnvelope); diff --git a/src/mbgl/renderer/layers/render_location_indicator_layer.hpp b/src/mbgl/renderer/layers/render_location_indicator_layer.hpp index 78d120373..dc9f1b282 100644 --- a/src/mbgl/renderer/layers/render_location_indicator_layer.hpp +++ b/src/mbgl/renderer/layers/render_location_indicator_layer.hpp @@ -20,7 +20,10 @@ private: void markContextDestroyed() override; void prepare(const LayerPrepareParameters &) override; +#if MLN_RENDER_BACKEND_OPENGL void render(PaintParameters &) override; +#endif + void populateDynamicRenderFeatureIndex(DynamicFeatureIndex &) const override; bool contextDestroyed = false; diff --git a/src/mbgl/renderer/sources/render_tile_source.cpp b/src/mbgl/renderer/sources/render_tile_source.cpp index d52e67c88..e9831486d 100644 --- a/src/mbgl/renderer/sources/render_tile_source.cpp +++ b/src/mbgl/renderer/sources/render_tile_source.cpp @@ -28,6 +28,13 @@ #include #include + +#if MLN_RENDER_BACKEND_METAL || (MLN_RENDER_BACKEND_VULKAN && defined(__ANDROID__)) +#define MLN_ENABLE_POLYLINE_DRAWABLES 1 +#else +#define MLN_ENABLE_POLYLINE_DRAWABLES 0 +#endif + #endif namespace mbgl { @@ -81,7 +88,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr return builder; }(); -#if MLN_RENDER_BACKEND_METAL +#if MLN_ENABLE_POLYLINE_DRAWABLES // initialize polyline builder gfx::ShaderPtr polylineShader; const auto createPolylineShader = [&]() -> gfx::ShaderPtr { @@ -185,7 +192,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr } }; -#if MLN_RENDER_BACKEND_METAL +#if MLN_ENABLE_POLYLINE_DRAWABLES // function to add polylines drawable const auto addPolylineDrawable = [&](TileLayerGroup* tileLayerGroup, const RenderTile& tile) { class PolylineDrawableTweaker : public gfx::DrawableTweaker { @@ -364,7 +371,7 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr 0, 0}; if (0 == updateDrawables(tileLayerGroup, tileID, debugUBO) && tile.getNeedsRendering()) { -#if MLN_RENDER_BACKEND_METAL +#if MLN_ENABLE_POLYLINE_DRAWABLES addPolylineDrawable(tileLayerGroup, tile); #else addDrawable(tileLayerGroup, diff --git a/src/mbgl/shaders/vulkan/background.cpp b/src/mbgl/shaders/vulkan/background.cpp index e6f4b4bbb..d79f21a7d 100644 --- a/src/mbgl/shaders/vulkan/background.cpp +++ b/src/mbgl/shaders/vulkan/background.cpp @@ -8,7 +8,7 @@ const std::array ShaderSource ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idBackgroundPosVertexAttribute}, }; const std::array @@ -19,7 +19,7 @@ const std::array }; const std::array ShaderSource::attributes = { - AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute}, + AttributeInfo{0, gfx::AttributeDataType::Short2, idBackgroundPosVertexAttribute}, }; const std::array ShaderSource::textures = {TextureInfo{0, idBackgroundImageTexture}}; diff --git a/src/mbgl/shaders/vulkan/shader_program.cpp b/src/mbgl/shaders/vulkan/shader_program.cpp index bfa7aee53..ca5971833 100644 --- a/src/mbgl/shaders/vulkan/shader_program.cpp +++ b/src/mbgl/shaders/vulkan/shader_program.cpp @@ -185,7 +185,7 @@ const vk::UniquePipeline& ShaderProgram::getPipeline(const PipelineInfo& pipelin // vk::DynamicState::eDepthBias, // vk::DynamicState::eDepthBounds, - const auto& dynamicValues = pipelineInfo.getDynamicStates(); + const auto& dynamicValues = pipelineInfo.getDynamicStates(backend); const vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicValues); const auto& device = backend.getDevice(); diff --git a/src/mbgl/shaders/vulkan/widevector.cpp b/src/mbgl/shaders/vulkan/widevector.cpp new file mode 100644 index 000000000..14c009ec8 --- /dev/null +++ b/src/mbgl/shaders/vulkan/widevector.cpp @@ -0,0 +1,22 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(WideVectorUniformsUBO), idWideVectorUniformsUBO}, + UniformBlockInfo{true, false, sizeof(WideVectorUniformWideVecUBO), idWideVectorUniformWideVecUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Float3, idWideVectorScreenPos}, + AttributeInfo{1, gfx::AttributeDataType::Float4, idWideVectorColor}, + AttributeInfo{2, gfx::AttributeDataType::Int, idWideVectorIndex}}; +const std::array + ShaderSource::instanceAttributes = { + AttributeInfo{3, gfx::AttributeDataType::Float3, idWideVectorInstanceCenter}, + AttributeInfo{4, gfx::AttributeDataType::Float4, idWideVectorInstanceColor}, + AttributeInfo{5, gfx::AttributeDataType::Int, idWideVectorInstancePrevious}, + AttributeInfo{6, gfx::AttributeDataType::Int, idWideVectorInstanceNext}}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/vulkan/buffer_resource.cpp b/src/mbgl/vulkan/buffer_resource.cpp index 4b9a9bddb..27557897f 100644 --- a/src/mbgl/vulkan/buffer_resource.cpp +++ b/src/mbgl/vulkan/buffer_resource.cpp @@ -10,19 +10,27 @@ namespace mbgl { namespace vulkan { bool BufferAllocation::create(const VmaAllocationCreateInfo& allocInfo, const vk::BufferCreateInfo& bufferInfo) { - VkResult result = vmaCreateBuffer( - allocator, reinterpret_cast(&bufferInfo), &allocInfo, &buffer, &allocation, nullptr); + VkBuffer buffer_; + VkResult result = vmaCreateBuffer(allocator, + reinterpret_cast(&bufferInfo), + &allocInfo, + &buffer_, + &allocation, + nullptr); if (result != VK_SUCCESS) { return false; } + buffer = vk::Buffer(buffer_); return true; } void BufferAllocation::destroy() { if (mappedBuffer) vmaUnmapMemory(allocator, allocation); - vmaDestroyBuffer(allocator, buffer, allocation); + vmaDestroyBuffer(allocator, VkBuffer(buffer), allocation); + + buffer = nullptr; mappedBuffer = nullptr; } diff --git a/src/mbgl/vulkan/command_encoder.cpp b/src/mbgl/vulkan/command_encoder.cpp index bcb061fb8..a7341f34b 100644 --- a/src/mbgl/vulkan/command_encoder.cpp +++ b/src/mbgl/vulkan/command_encoder.cpp @@ -24,7 +24,9 @@ std::unique_ptr CommandEncoder::createRenderPass(const char* na return std::make_unique(*this, name, descriptor); } -void CommandEncoder::present(gfx::Renderable&) {} +void CommandEncoder::present(gfx::Renderable& renderable) { + renderable.getResource().swap(); +} void CommandEncoder::pushDebugGroup(const char* name) { pushDebugGroup(name, {}); diff --git a/src/mbgl/vulkan/context.cpp b/src/mbgl/vulkan/context.cpp index dd22f4223..25c0d322b 100644 --- a/src/mbgl/vulkan/context.cpp +++ b/src/mbgl/vulkan/context.cpp @@ -135,7 +135,8 @@ void Context::submitOneTimeCommand(const std::functionallocateCommandBuffersUnique(allocateInfo); + const auto& device = backend.getDevice(); + const auto& commandBuffers = device->allocateCommandBuffersUnique(allocateInfo); auto& commandBuffer = commandBuffers.front(); backend.setDebugName(commandBuffer.get(), "OneTimeSubmitCommandBuffer"); @@ -146,8 +147,14 @@ void Context::submitOneTimeCommand(const std::functionwaitIdle(); + const auto& fence = device->createFenceUnique(vk::FenceCreateInfo(vk::FenceCreateFlags())); + backend.getGraphicsQueue().submit(submitInfo, fence.get()); + + constexpr uint64_t timeout = std::numeric_limits::max(); + const vk::Result waitFenceResult = device->waitForFences(1, &fence.get(), VK_TRUE, timeout); + if (waitFenceResult != vk::Result::eSuccess) { + mbgl::Log::Error(mbgl::Event::Render, "OneTimeCommand - Wait fence failed"); + } } void Context::waitFrame() const { @@ -161,10 +168,27 @@ void Context::waitFrame() const { } } void Context::beginFrame() { - backend.startFrameCapture(); - const auto& device = backend.getDevice(); auto& renderableResource = backend.getDefaultRenderable().getResource(); + const auto& platformSurface = renderableResource.getPlatformSurface(); + + if (platformSurface && surfaceUpdateRequested) { + renderableResource.recreateSwapchain(); + + // we wait for an idle device to recreate the swapchain + // so it's a good opportunity to delete all queued items + for (auto& frame : frameResources) { + device->resetDescriptorPool(frame.descriptorPool.get()); + frame.runDeletionQueue(*this); + } + + // sync resources with swapchain + frameResourceIndex = 0; + surfaceUpdateRequested = false; + } + + backend.startFrameCapture(); + auto& frame = frameResources[frameResourceIndex]; constexpr uint64_t timeout = std::numeric_limits::max(); @@ -173,18 +197,26 @@ void Context::beginFrame() { device->resetDescriptorPool(getCurrentDescriptorPool().get()); frame.runDeletionQueue(*this); - if (renderableResource.getPlatformSurface()) { + if (platformSurface) { try { const vk::ResultValue acquireImageResult = device->acquireNextImageKHR( renderableResource.getSwapchain().get(), timeout, frame.surfaceSemaphore.get(), nullptr); - if (acquireImageResult.result == vk::Result::eSuccess) + if (acquireImageResult.result == vk::Result::eSuccess) { renderableResource.setAcquiredImageIndex(acquireImageResult.value); - else if (acquireImageResult.result == vk::Result::eSuboptimalKHR) - renderableResource.recreateSwapchain(); + } else if (acquireImageResult.result == vk::Result::eSuboptimalKHR) { + renderableResource.setAcquiredImageIndex(acquireImageResult.value); + // TODO implement pre-rotation transform for surface orientation + // requestSurfaceUpdate(); + // beginFrame(); + // return; + } } catch (const vk::OutOfDateKHRError& e) { - renderableResource.recreateSwapchain(); + // request an update and restart frame + requestSurfaceUpdate(); + beginFrame(); + return; } } else { renderableResource.setAcquiredImageIndex(frameResourceIndex); @@ -197,18 +229,23 @@ void Context::beginFrame() { } void Context::endFrame() { + frameResourceIndex = (frameResourceIndex + 1) % frameResources.size(); +} + +void Context::submitFrame() { const auto& frame = frameResources[frameResourceIndex]; frame.commandBuffer->end(); const auto& device = backend.getDevice(); const auto& graphicsQueue = backend.getGraphicsQueue(); auto& renderableResource = backend.getDefaultRenderable().getResource(); + const auto& platformSurface = renderableResource.getPlatformSurface(); // submit frame commands const vk::PipelineStageFlags waitStageMask[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; auto submitInfo = vk::SubmitInfo().setCommandBuffers(frame.commandBuffer.get()); - if (renderableResource.getPlatformSurface()) { + if (platformSurface) { submitInfo.setSignalSemaphores(frame.frameSemaphore.get()) .setWaitSemaphores(frame.surfaceSemaphore.get()) .setWaitDstStageMask(waitStageMask); @@ -222,7 +259,7 @@ void Context::endFrame() { graphicsQueue.submit(submitInfo, frame.flightFrameFence.get()); // present rendered frame - if (renderableResource.getPlatformSurface()) { + if (platformSurface) { const auto acquiredImage = renderableResource.getAcquiredImageIndex(); const auto presentInfo = vk::PresentInfoKHR() .setSwapchains(renderableResource.getSwapchain().get()) @@ -232,14 +269,15 @@ void Context::endFrame() { try { const auto& presentQueue = backend.getPresentQueue(); const vk::Result presentResult = presentQueue.presentKHR(presentInfo); - if (presentResult == vk::Result::eSuboptimalKHR) renderableResource.recreateSwapchain(); + if (presentResult == vk::Result::eSuboptimalKHR) { + // TODO implement pre-rotation transform for surface orientation + // requestSurfaceUpdate(); + } } catch (const vk::OutOfDateKHRError& e) { - renderableResource.recreateSwapchain(); + requestSurfaceUpdate(); } } - frameResourceIndex = (frameResourceIndex + 1) % frameResources.size(); - backend.endFrameCapture(); } @@ -419,7 +457,7 @@ bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass, const auto& pipeline = shaderImpl.getPipeline(clipping.pipelineInfo); commandBuffer->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get()); - clipping.pipelineInfo.setDynamicValues(commandBuffer); + clipping.pipelineInfo.setDynamicValues(backend, commandBuffer); const std::array vertexBuffers = {clipping.vertexBuffer->getVulkanBuffer()}; const std::array offset = {0}; diff --git a/src/mbgl/vulkan/drawable.cpp b/src/mbgl/vulkan/drawable.cpp index 2948d13ed..2ca9b4605 100644 --- a/src/mbgl/vulkan/drawable.cpp +++ b/src/mbgl/vulkan/drawable.cpp @@ -247,7 +247,7 @@ void Drawable::draw(PaintParameters& parameters) const { // update pipeline info with per segment modifiers impl->pipelineInfo.setDrawMode(seg->getMode()); - impl->pipelineInfo.setDynamicValues(commandBuffer); + impl->pipelineInfo.setDynamicValues(context.getBackend(), commandBuffer); const auto& pipeline = shaderImpl.getPipeline(impl->pipelineInfo); commandBuffer->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get()); diff --git a/src/mbgl/vulkan/pipeline.cpp b/src/mbgl/vulkan/pipeline.cpp index 2ad334427..29042131d 100644 --- a/src/mbgl/vulkan/pipeline.cpp +++ b/src/mbgl/vulkan/pipeline.cpp @@ -9,37 +9,37 @@ namespace vulkan { vk::Format PipelineInfo::vulkanFormat(const gfx::AttributeDataType& value) { switch (value) { case gfx::AttributeDataType::Byte: - return vk::Format::eR8Sscaled; + return vk::Format::eR8Sint; case gfx::AttributeDataType::Byte2: - return vk::Format::eR8G8Sscaled; + return vk::Format::eR8G8Sint; case gfx::AttributeDataType::Byte3: - return vk::Format::eR8G8B8Sscaled; + return vk::Format::eR8G8B8Sint; case gfx::AttributeDataType::Byte4: - return vk::Format::eR8G8B8A8Sscaled; + return vk::Format::eR8G8B8A8Sint; case gfx::AttributeDataType::UByte: - return vk::Format::eR8Uscaled; + return vk::Format::eR8Uint; case gfx::AttributeDataType::UByte2: - return vk::Format::eR8G8Uscaled; + return vk::Format::eR8G8Uint; case gfx::AttributeDataType::UByte3: - return vk::Format::eR8G8B8Uscaled; + return vk::Format::eR8G8B8Uint; case gfx::AttributeDataType::UByte4: - return vk::Format::eR8G8B8A8Uscaled; + return vk::Format::eR8G8B8A8Uint; case gfx::AttributeDataType::Short: - return vk::Format::eR16Sscaled; + return vk::Format::eR16Sint; case gfx::AttributeDataType::Short2: - return vk::Format::eR16G16Sscaled; + return vk::Format::eR16G16Sint; case gfx::AttributeDataType::Short3: - return vk::Format::eR16G16B16Sscaled; + return vk::Format::eR16G16B16Sint; case gfx::AttributeDataType::Short4: - return vk::Format::eR16G16B16A16Sscaled; + return vk::Format::eR16G16B16A16Sint; case gfx::AttributeDataType::UShort: - return vk::Format::eR16Uscaled; + return vk::Format::eR16Uint; case gfx::AttributeDataType::UShort2: - return vk::Format::eR16G16Uscaled; + return vk::Format::eR16G16Uint; case gfx::AttributeDataType::UShort3: - return vk::Format::eR16G16B16Uscaled; + return vk::Format::eR16G16B16Uint; case gfx::AttributeDataType::UShort4: - return vk::Format::eR16G16B16A16Uscaled; + return vk::Format::eR16G16B16A16Uint; case gfx::AttributeDataType::Int: return vk::Format::eR32Sint; case gfx::AttributeDataType::Int2: @@ -401,7 +401,7 @@ std::size_t PipelineInfo::hash() const { vertexInputHash); } -void PipelineInfo::setDynamicValues(const vk::UniqueCommandBuffer& buffer) const { +void PipelineInfo::setDynamicValues(const RendererBackend& backend, const vk::UniqueCommandBuffer& buffer) const { if (dynamicValues.blendConstants.has_value()) { buffer->setBlendConstants(dynamicValues.blendConstants.value().data()); } @@ -412,12 +412,12 @@ void PipelineInfo::setDynamicValues(const vk::UniqueCommandBuffer& buffer) const buffer->setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, dynamicValues.stencilRef); } - if (wideLines) { + if (backend.getDeviceFeatures().wideLines && wideLines) { buffer->setLineWidth(dynamicValues.lineWidth); } } -std::vector PipelineInfo::getDynamicStates() const { +std::vector PipelineInfo::getDynamicStates(const RendererBackend& backend) const { std::vector dynamicStates; if (usesBlendConstants()) { @@ -430,7 +430,7 @@ std::vector PipelineInfo::getDynamicStates() const { dynamicStates.push_back(vk::DynamicState::eStencilReference); } - if (wideLines) { + if (backend.getDeviceFeatures().wideLines && wideLines) { dynamicStates.push_back(vk::DynamicState::eLineWidth); } diff --git a/src/mbgl/vulkan/renderable_resource.cpp b/src/mbgl/vulkan/renderable_resource.cpp index 3ee95ed40..89bbbb6a3 100644 --- a/src/mbgl/vulkan/renderable_resource.cpp +++ b/src/mbgl/vulkan/renderable_resource.cpp @@ -70,7 +70,7 @@ void SurfaceRenderableResource::initColor(uint32_t w, uint32_t h) { } } -void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) { +void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h, vk::PresentModeKHR presentMode) { const auto& physicalDevice = backend.getPhysicalDevice(); const auto& device = backend.getDevice(); @@ -83,7 +83,16 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) { if (formatIt == formats.end()) throw std::runtime_error("No suitable swapchain format found"); // only vk::PresentModeKHR::eFifo (vsync on) is guaranteed - // TODO check for vk::PresentModeKHR::eImmediate when uncapped + if (presentMode != vk::PresentModeKHR::eFifo) { + const std::vector& presentModes = physicalDevice.getSurfacePresentModesKHR(surface.get()); + if (std::find(presentModes.begin(), presentModes.end(), presentMode) == presentModes.end()) { + mbgl::Log::Error( + mbgl::Event::Render, + "Requested PresentModeKHR not available (" + std::to_string(static_cast(presentMode)) + ")"); + + presentMode = vk::PresentModeKHR::eFifo; + } + } // pick surface size const vk::SurfaceCapabilitiesKHR& capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.get()); @@ -105,7 +114,7 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) { .setMinImageCount(swapchainImageCount) .setImageFormat(formatIt->format) .setImageColorSpace(formatIt->colorSpace) - .setPresentMode(vk::PresentModeKHR::eFifo) + .setPresentMode(presentMode) .setImageExtent(extent) .setImageArrayLayers(1) .setImageUsage(vk::ImageUsageFlagBits::eColorAttachment); @@ -114,7 +123,7 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) { int32_t presentQueueIndex = backend.getPresentQueueIndex(); if (graphicsQueueIndex != presentQueueIndex) { - // TODO if this scenario is widespred and performance is a problem (shouldn't be the case on most hardware) + // TODO if this scenario is widespread and performance is a problem (shouldn't be the case on most hardware) // rework to vk::SharingMode::eExclusive + queue ownership swapchainCreateInfo.setImageSharingMode(vk::SharingMode::eConcurrent); const std::array queueIndices = {static_cast(graphicsQueueIndex), @@ -124,7 +133,7 @@ void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) { swapchainCreateInfo.setImageSharingMode(vk::SharingMode::eExclusive); } - swapchainCreateInfo.setPreTransform(capabilities.currentTransform); + swapchainCreateInfo.setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity); swapchainCreateInfo.setClipped(VK_TRUE); if (capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eInherit) { @@ -209,6 +218,11 @@ void SurfaceRenderableResource::initDepthStencil() { backend.setDebugName(depthAllocation->imageView.get(), "SwapchainDepthImageView"); } +void SurfaceRenderableResource::swap() { + auto& context = static_cast(backend.getContext()); + context.submitFrame(); +} + const vk::Image SurfaceRenderableResource::getAcquiredImage() const { if (surface) { return swapchainImages[acquiredImageIndex]; @@ -233,8 +247,7 @@ void SurfaceRenderableResource::init(uint32_t w, uint32_t h) { .setViewType(vk::ImageViewType::e2D) .setFormat(colorFormat) .setComponents(vk::ComponentMapping()) // defaults to vk::ComponentSwizzle::eIdentity - .setSubresourceRange( - vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); + .setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}); for (const auto& image : swapchainImages) { imageViewCreateInfo.setImage(image); @@ -267,7 +280,7 @@ void SurfaceRenderableResource::init(uint32_t w, uint32_t h) { .setSamples(vk::SampleCountFlagBits::e1) .setLoadOp(vk::AttachmentLoadOp::eClear) .setStoreOp(vk::AttachmentStoreOp::eDontCare) - .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare) + .setStencilLoadOp(vk::AttachmentLoadOp::eClear) .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare) .setInitialLayout(vk::ImageLayout::eUndefined) .setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal); @@ -337,7 +350,7 @@ void SurfaceRenderableResource::recreateSwapchain() { } const vk::UniqueFramebuffer& SurfaceRenderableResource::getFramebuffer() const { - return swapchainFramebuffers[backend.getContext().getCurrentFrameResourceIndex()]; + return swapchainFramebuffers[acquiredImageIndex]; } } // namespace vulkan diff --git a/src/mbgl/vulkan/renderer_backend.cpp b/src/mbgl/vulkan/renderer_backend.cpp index 70718a9f7..1dbed858d 100644 --- a/src/mbgl/vulkan/renderer_backend.cpp +++ b/src/mbgl/vulkan/renderer_backend.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -443,13 +444,15 @@ void RendererBackend::initDevice() { const auto& queues = candidate.getQueueFamilyProperties(); + // Use to test on specific GPU type (if multiple) + // if (candidate.getProperties().deviceType != vk::PhysicalDeviceType::eIntegratedGpu) return false; + for (auto i = 0u; i < queues.size(); ++i) { const auto& queue = queues[i]; if (queue.queueCount == 0) continue; if (queue.queueFlags & vk::QueueFlagBits::eGraphics) graphicsQueueIndex = i; - if (surface && candidate.getSurfaceSupportKHR(i, surface)) presentQueueIndex = i; if (graphicsQueueIndex != -1 && (!surface || presentQueueIndex != -1)) break; @@ -476,9 +479,9 @@ void RendererBackend::initDevice() { } } - physicalDeviceProperties = physicalDevice.getProperties(); - if (!physicalDevice) throw std::runtime_error("No suitable GPU found"); + + physicalDeviceProperties = physicalDevice.getProperties(); }; pickPhysicalDevice(); @@ -492,12 +495,15 @@ void RendererBackend::initDevice() { if (surface && graphicsQueueIndex != presentQueueIndex) queueCreateInfos.emplace_back(vk::DeviceQueueCreateFlags(), presentQueueIndex, 1, &queuePriority); - const auto& supportedDeviceFeatures = physicalDevice.getFeatures(); - - vk::PhysicalDeviceFeatures enabledDeviceFeatures; + [[maybe_unused]] const auto& supportedDeviceFeatures = physicalDevice.getFeatures(); + physicalDeviceFeatures = vk::PhysicalDeviceFeatures(); + // TODO + // - WideLines disabled on Android (20.77% device coverage https://vulkan.gpuinfo.org/listfeaturescore10.php) + // - Rework this to a dynamic toggle based on MLN_TRIANGULATE_FILL_OUTLINES/MLN_ENABLE_POLYLINE_DRAWABLES +#if !defined(__ANDROID__) && !defined(__apple__) if (supportedDeviceFeatures.wideLines) { - enabledDeviceFeatures.setWideLines(true); + physicalDeviceFeatures.setWideLines(true); // more wideLines info // physicalDeviceProperties.limits.lineWidthRange; @@ -505,12 +511,13 @@ void RendererBackend::initDevice() { } else { mbgl::Log::Error(mbgl::Event::Render, "Wide line support not available"); } +#endif auto createInfo = vk::DeviceCreateInfo() .setQueueCreateInfos(queueCreateInfos) .setEnabledExtensionCount(static_cast(extensions.size())) .setPpEnabledExtensionNames(extensions.data()) - .setPEnabledFeatures(&enabledDeviceFeatures); + .setPEnabledFeatures(&physicalDeviceFeatures); // this is not needed for newer implementations createInfo.setPEnabledLayerNames(layers); @@ -528,11 +535,12 @@ void RendererBackend::initSwapchain() { const auto& renderable = getDefaultRenderable(); auto& renderableResource = renderable.getResource(); const auto& size = renderable.getSize(); - renderableResource.init(size.width, size.height); - if (renderableResource.getPlatformSurface()) { - maxFrames = renderableResource.getImageCount(); - } + // use triple buffering if rendering to a surface + // no buffering when using headless + maxFrames = renderableResource.getPlatformSurface() ? 3 : 1; + + renderableResource.init(size.width, size.height); } void RendererBackend::initCommandPool() { diff --git a/src/mbgl/vulkan/texture2d.cpp b/src/mbgl/vulkan/texture2d.cpp index 8b9c20757..a83351338 100644 --- a/src/mbgl/vulkan/texture2d.cpp +++ b/src/mbgl/vulkan/texture2d.cpp @@ -10,19 +10,22 @@ namespace mbgl { namespace vulkan { bool ImageAllocation::create(const VmaAllocationCreateInfo& allocInfo, const vk::ImageCreateInfo& imageInfo) { + VkImage image_; VkResult result = vmaCreateImage( - allocator, reinterpret_cast(&imageInfo), &allocInfo, &image, &allocation, nullptr); + allocator, reinterpret_cast(&imageInfo), &allocInfo, &image_, &allocation, nullptr); if (result != VK_SUCCESS) { return false; } + image = vk::Image(image_); return true; } void ImageAllocation::destroy() { imageView.reset(); - vmaDestroyImage(allocator, image, allocation); + vmaDestroyImage(allocator, VkImage(image), allocation); + image = nullptr; } void ImageAllocation::setName([[maybe_unused]] const std::string& name) const {