Add Android Vulkan backend (#2711)
Co-authored-by: Bart Louwers <bart.louwers@gmail.com>
This commit is contained in:
@@ -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 }} \
|
||||
|
||||
39
.github/workflows/android-ci.yml
vendored
39
.github/workflows/android-ci.yml
vendored
@@ -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
|
||||
|
||||
16
.github/workflows/android-device-test.yml
vendored
16
.github/workflows/android-device-test.yml
vendored
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ShaderSource<BuiltIn::BackgroundShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::BackgroundPatternShader, gfx::Backend::Type::Vulkan
|
||||
|
||||
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 BackgroundPatternDrawableUBO {
|
||||
mat4 matrix;
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ShaderSource<BuiltIn::CircleShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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;
|
||||
|
||||
@@ -22,7 +22,7 @@ struct ShaderSource<BuiltIn::ClippingMaskProgram, gfx::Backend::Type::Vulkan> {
|
||||
static constexpr std::array<TextureInfo, 0> 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;
|
||||
|
||||
@@ -17,10 +17,10 @@ struct ShaderSource<BuiltIn::CollisionBoxShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::CollisionCircleShader, gfx::Backend::Type::Vulkan>
|
||||
|
||||
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;
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ShaderSource<BuiltIn::DebugShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::WideVectorShader, gfx::Backend::Type::Vulkan> {
|
||||
static constexpr const char* name = "WideVectorShader";
|
||||
|
||||
static constexpr std::array<UniformBlockInfo, 0> uniforms{};
|
||||
static constexpr std::array<AttributeInfo, 0> attributes{};
|
||||
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
|
||||
static constexpr std::array<TextureInfo, 0> textures{};
|
||||
|
||||
static constexpr auto vertex = R"()";
|
||||
static constexpr auto fragment = R"()";
|
||||
};
|
||||
|
||||
} // namespace shaders
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ShaderSource<BuiltIn::FillShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::FillOutlineShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::FillPatternShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::FillOutlinePatternShader, gfx::Backend::Type::Vulka
|
||||
|
||||
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)
|
||||
@@ -615,8 +615,8 @@ struct ShaderSource<BuiltIn::FillOutlineTriangulatedShader, gfx::Backend::Type::
|
||||
|
||||
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;
|
||||
|
||||
layout(set = 0, binding = 1) uniform FillOutlineTriangulatedDrawableUBO {
|
||||
mat4 matrix;
|
||||
@@ -641,10 +641,12 @@ void main() {
|
||||
// We store these in the least significant bit of in_pos_normal
|
||||
mediump vec2 normal = in_pos_normal - 2.0 * pos;
|
||||
frag_normal = vec2(normal.x, normal.y * 2.0 - 1.0);
|
||||
frag_normal.y *= -1.0;
|
||||
|
||||
// these transformations used to be applied in the JS and native code bases.
|
||||
// moved them into the shader for clarity and simplicity.
|
||||
float halfwidth = drawable.width / 2.0;
|
||||
float width = 1.0;
|
||||
float halfwidth = width / 2.0;
|
||||
float outset = halfwidth + (halfwidth == 0.0 ? 0.0 : ANTIALIASING);
|
||||
|
||||
// Scale the extrusion vector down to a normal and then up by the line width
|
||||
@@ -652,17 +654,15 @@ void main() {
|
||||
mediump vec2 dist = outset * a_extrude * LINE_NORMAL_SCALE;
|
||||
|
||||
vec4 projected_extrude = drawable.matrix * vec4(dist / drawable.ratio, 0.0, 0.0);
|
||||
gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0) + projected_extrude;
|
||||
gl_Position = drawable.matrix * vec4(pos, 0.0, 1.0) + projected_extrude;
|
||||
gl_Position.y *= -1.0;
|
||||
|
||||
// calculate how much the perspective view squishes or stretches the extrude
|
||||
float extrude_length_without_perspective = length(dist);
|
||||
float extrude_length_with_perspective = length(projected_extrude.xy / gl_Position.w * drawable.units_to_pixels);
|
||||
|
||||
frag_width = outset;
|
||||
frag_gamma_scale = extrude_length_without_perspective / extrude_length_with_perspective
|
||||
|
||||
gl_Position.y *= -1.0;
|
||||
float extrude_length_with_perspective = length(projected_extrude.xy / gl_Position.w * global.units_to_pixels);
|
||||
|
||||
frag_width2 = outset;
|
||||
frag_gamma_scale = extrude_length_without_perspective / extrude_length_with_perspective;
|
||||
}
|
||||
)";
|
||||
|
||||
@@ -698,7 +698,7 @@ void main() {
|
||||
const float blur2 = (1.0 / DEVICE_PIXEL_RATIO) * frag_gamma_scale;
|
||||
const float alpha = clamp(min(dist + blur2, frag_width2 - dist) / blur2, 0.0, 1.0);
|
||||
|
||||
out_color = props.outline_color * (alpha * props.opacit));
|
||||
out_color = props.outline_color * alpha * props.opacity;
|
||||
}
|
||||
)";
|
||||
};
|
||||
@@ -714,8 +714,8 @@ struct ShaderSource<BuiltIn::FillExtrusionShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::FillExtrusionPatternShader, gfx::Backend::Type::Vul
|
||||
|
||||
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_base)
|
||||
layout(location = 2) in vec2 in_base;
|
||||
@@ -868,11 +868,11 @@ layout(location = 3) in vec2 in_height;
|
||||
#endif
|
||||
|
||||
#if !defined(HAS_UNIFORM_u_pattern_from)
|
||||
layout(location = 4) in vec4 in_pattern_from;
|
||||
layout(location = 4) in uvec4 in_pattern_from;
|
||||
#endif
|
||||
|
||||
#if !defined(HAS_UNIFORM_u_pattern_to)
|
||||
layout(location = 5) in vec4 in_pattern_to;
|
||||
layout(location = 5) in uvec4 in_pattern_to;
|
||||
#endif
|
||||
|
||||
layout(set = 0, binding = 1) uniform FillExtrusionDrawableUBO {
|
||||
|
||||
@@ -25,7 +25,7 @@ const float ZERO = 1.0 / 255.0 / 16.0;
|
||||
// Gaussian kernel coefficient: 1 / sqrt(2 * PI)
|
||||
#define GAUSS_COEF 0.3989422804014327
|
||||
|
||||
layout(location = 0) in vec2 in_position;
|
||||
layout(location = 0) in ivec2 in_position;
|
||||
|
||||
#if !defined(HAS_UNIFORM_u_weight)
|
||||
layout(location = 1) in vec2 in_weight;
|
||||
@@ -149,7 +149,7 @@ struct ShaderSource<BuiltIn::HeatmapTextureShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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;
|
||||
|
||||
@@ -17,8 +17,8 @@ struct ShaderSource<BuiltIn::HillshadePrepareShader, gfx::Backend::Type::Vulkan>
|
||||
|
||||
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<BuiltIn::HillshadeShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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;
|
||||
|
||||
@@ -17,8 +17,8 @@ struct ShaderSource<BuiltIn::LineShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::LineGradientShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::LinePatternShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::LineSDFShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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;
|
||||
|
||||
@@ -17,8 +17,8 @@ struct ShaderSource<BuiltIn::RasterShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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;
|
||||
|
||||
@@ -17,9 +17,9 @@ struct ShaderSource<BuiltIn::SymbolIconShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::SymbolSDFIconShader, gfx::Backend::Type::Vulkan> {
|
||||
|
||||
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<BuiltIn::SymbolTextAndIconShader, gfx::Backend::Type::Vulkan
|
||||
#define SDF 1.0
|
||||
#define ICON 0.0
|
||||
|
||||
layout(location = 0) in vec4 in_pos_offset;
|
||||
layout(location = 1) in vec4 in_data;
|
||||
layout(location = 0) in ivec4 in_pos_offset;
|
||||
layout(location = 1) in uvec4 in_data;
|
||||
layout(location = 2) in vec3 in_projected_pos;
|
||||
layout(location = 3) in float in_fade_opacity;
|
||||
|
||||
|
||||
173
include/mbgl/shaders/vulkan/widevector.hpp
Normal file
173
include/mbgl/shaders/vulkan/widevector.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/shaders/shader_source.hpp>
|
||||
#include <mbgl/shaders/vulkan/shader_program.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace shaders {
|
||||
|
||||
template <>
|
||||
struct ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::Vulkan> {
|
||||
static constexpr const char* name = "WideVectorShader";
|
||||
|
||||
static const std::array<UniformBlockInfo, 2> uniforms;
|
||||
static const std::array<AttributeInfo, 3> attributes;
|
||||
static const std::array<AttributeInfo, 4> instanceAttributes;
|
||||
static constexpr std::array<TextureInfo, 0> 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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
|
||||
void beginFrame() override;
|
||||
void endFrame() override;
|
||||
void submitFrame();
|
||||
void waitFrame() const;
|
||||
|
||||
std::unique_ptr<gfx::CommandEncoder> createCommandEncoder() override;
|
||||
@@ -145,6 +146,8 @@ public:
|
||||
void enqueueDeletion(std::function<void(const Context&)>&& function);
|
||||
void submitOneTimeCommand(const std::function<void(const vk::UniqueCommandBuffer&)>& function);
|
||||
|
||||
void requestSurfaceUpdate() { surfaceUpdateRequested = true; }
|
||||
|
||||
private:
|
||||
struct FrameResources {
|
||||
vk::UniqueCommandBuffer commandBuffer;
|
||||
@@ -189,6 +192,7 @@ private:
|
||||
|
||||
uint8_t frameResourceIndex = 0;
|
||||
std::vector<FrameResources> frameResources;
|
||||
bool surfaceUpdateRequested{false};
|
||||
|
||||
struct {
|
||||
gfx::ShaderProgramBasePtr shader;
|
||||
|
||||
@@ -82,8 +82,8 @@ public:
|
||||
void updateVertexInputHash();
|
||||
std::size_t hash() const;
|
||||
|
||||
void setDynamicValues(const vk::UniqueCommandBuffer& buffer) const;
|
||||
std::vector<vk::DynamicState> getDynamicStates() const;
|
||||
void setDynamicValues(const RendererBackend& backend, const vk::UniqueCommandBuffer& buffer) const;
|
||||
std::vector<vk::DynamicState> getDynamicStates(const RendererBackend& backend) const;
|
||||
|
||||
protected:
|
||||
std::size_t vertexInputHash{0};
|
||||
|
||||
@@ -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<const char*> getDeviceExtensions() { return {}; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "android_gl_renderer_backend.hpp"
|
||||
|
||||
#include <mbgl/gfx/backend_scope.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/renderable_resource.hpp>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
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<AndroidGLRendererBackend&>(backend).getSwapBehavior();
|
||||
if (swapBehaviour == gfx::Renderable::SwapBehaviour::Flush) {
|
||||
static_cast<gl::Context&>(backend.getContext()).finish();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
AndroidGLRendererBackend& backend;
|
||||
};
|
||||
|
||||
AndroidGLRendererBackend::AndroidGLRendererBackend()
|
||||
: gl::RendererBackend(gfx::ContextMode::Unique),
|
||||
mbgl::gfx::Renderable({64, 64}, std::make_unique<AndroidGLRenderableResource>(*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<uint32_t>(width), static_cast<uint32_t>(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<gl::Context>().setCleanupOnDestruction(false);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
} // namespace mbgl
|
||||
|
||||
namespace mbgl {
|
||||
namespace gfx {
|
||||
|
||||
template <>
|
||||
std::unique_ptr<android::AndroidRendererBackend> Backend::Create<mbgl::gfx::Backend::Type::OpenGL>(ANativeWindow*) {
|
||||
return std::make_unique<android::AndroidGLRendererBackend>();
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mbgl
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/gfx/renderable.hpp>
|
||||
#include <mbgl/gl/renderer_backend.hpp>
|
||||
#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
|
||||
@@ -1,78 +1,22 @@
|
||||
#include "android_renderer_backend.hpp"
|
||||
|
||||
#include <mbgl/gfx/backend_scope.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/renderable_resource.hpp>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
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<AndroidGLRenderableResource>(*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<uint32_t>(width), static_cast<uint32_t>(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<gl::Context>().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<gl::Context&>(getContext()).finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -1,46 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/gfx/backend.hpp>
|
||||
#include <mbgl/gfx/renderer_backend.hpp>
|
||||
#include <mbgl/gfx/renderable.hpp>
|
||||
#include <mbgl/gl/renderer_backend.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
|
||||
#include <android/native_window.h>
|
||||
|
||||
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<AndroidRendererBackend> Create(ANativeWindow* window) {
|
||||
return mbgl::gfx::Backend::Create<AndroidRendererBackend, ANativeWindow*>(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
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#include "android_vulkan_renderer_backend.hpp"
|
||||
|
||||
#include <mbgl/gfx/backend_scope.hpp>
|
||||
#include <mbgl/vulkan/context.hpp>
|
||||
#include <mbgl/vulkan/renderable_resource.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <vulkan/vulkan_android.h>
|
||||
|
||||
namespace mbgl {
|
||||
namespace android {
|
||||
|
||||
class AndroidVulkanRenderableResource final : public mbgl::vulkan::SurfaceRenderableResource {
|
||||
public:
|
||||
AndroidVulkanRenderableResource(AndroidVulkanRendererBackend& backend_)
|
||||
: SurfaceRenderableResource(backend_) {}
|
||||
|
||||
std::vector<const char*> getDeviceExtensions() override {
|
||||
return {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
void createPlatformSurface() override {
|
||||
auto& backendImpl = static_cast<AndroidVulkanRendererBackend&>(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<AndroidVulkanRendererBackend&>(backend).getSwapBehavior();
|
||||
if (swapBehaviour == gfx::Renderable::SwapBehaviour::Flush) {
|
||||
static_cast<vulkan::Context&>(backend.getContext()).waitFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
AndroidVulkanRendererBackend::AndroidVulkanRendererBackend(ANativeWindow* window_)
|
||||
: vulkan::RendererBackend(gfx::ContextMode::Unique),
|
||||
mbgl::gfx::Renderable({64, 64}, std::make_unique<AndroidVulkanRenderableResource>(*this)),
|
||||
window(window_) {
|
||||
init();
|
||||
}
|
||||
|
||||
AndroidVulkanRendererBackend::~AndroidVulkanRendererBackend() = default;
|
||||
|
||||
std::vector<const char*> 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<uint32_t>(width), static_cast<uint32_t>(height)};
|
||||
if (context) {
|
||||
static_cast<vulkan::Context&>(*context).requestSurfaceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
} // namespace mbgl
|
||||
|
||||
namespace mbgl {
|
||||
namespace gfx {
|
||||
|
||||
template <>
|
||||
std::unique_ptr<android::AndroidRendererBackend> Backend::Create<mbgl::gfx::Backend::Type::Vulkan>(
|
||||
ANativeWindow* window) {
|
||||
return std::make_unique<android::AndroidVulkanRendererBackend>(window);
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mbgl
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/gfx/renderable.hpp>
|
||||
#include <mbgl/vulkan/renderer_backend.hpp>
|
||||
#include "android_renderer_backend.hpp"
|
||||
#include <android/native_window.h>
|
||||
|
||||
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<const char*> 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
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <android/native_window_jni.h>
|
||||
|
||||
#include "attach_env.hpp"
|
||||
#include "android_renderer_backend.hpp"
|
||||
@@ -24,8 +25,7 @@ MapRenderer::MapRenderer(jni::JNIEnv& _env,
|
||||
localIdeographFontFamily(localIdeographFontFamily_ ? jni::Make<std::string>(_env, localIdeographFontFamily_)
|
||||
: std::optional<std::string>{}),
|
||||
threadPool(Scheduler::GetBackground(), {}),
|
||||
mailboxData(this),
|
||||
backend(std::make_unique<AndroidRendererBackend>(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<AndroidRendererBackend>(threadPool);
|
||||
swapBehaviorFlush = false;
|
||||
}
|
||||
|
||||
void MapRenderer::scheduleSnapshot(std::unique_ptr<SnapshotCallback> 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<AndroidSurface>& surface) {
|
||||
// Lock as the initialization can come from the main thread or the GL thread first
|
||||
std::lock_guard<std::mutex> 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, std::function<void(ANativeWindow*)>>(
|
||||
ANativeWindow_fromSurface(&env, reinterpret_cast<jobject>(surface.get())),
|
||||
[](ANativeWindow* window_) { ANativeWindow_release(window_); });
|
||||
}
|
||||
|
||||
// Create the new backend and renderer
|
||||
backend = std::make_unique<AndroidRendererBackend>(threadPool);
|
||||
renderer = std::make_unique<Renderer>(*backend, pixelRatio, localIdeographFontFamily);
|
||||
backend = AndroidRendererBackend::Create(window.get());
|
||||
renderer = std::make_unique<Renderer>(backend->getImpl(), pixelRatio, localIdeographFontFamily);
|
||||
rendererRef = std::make_unique<ActorRef<Renderer>>(*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<AndroidSurface>(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 //
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <optional>
|
||||
|
||||
#include <jni/jni.hpp>
|
||||
#include <android/native_window.h>
|
||||
|
||||
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<AndroidSurface>& surface);
|
||||
|
||||
void onSurfaceChanged(JNIEnv&, jint width, jint height);
|
||||
|
||||
@@ -132,12 +138,14 @@ private:
|
||||
std::unique_ptr<AndroidRendererBackend> backend;
|
||||
std::unique_ptr<Renderer> renderer;
|
||||
std::unique_ptr<ActorRef<Renderer>> rendererRef;
|
||||
std::unique_ptr<ANativeWindow, std::function<void(ANativeWindow*)>> window;
|
||||
|
||||
std::shared_ptr<UpdateParameters> updateParameters;
|
||||
std::mutex updateMutex;
|
||||
|
||||
bool framebufferSizeChanged = false;
|
||||
std::atomic<bool> destroyed{false};
|
||||
bool swapBehaviorFlush{true};
|
||||
|
||||
std::unique_ptr<SnapshotCallback> snapshotCallback;
|
||||
mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* Called from the renderer frontend to schedule a render.
|
||||
*/
|
||||
@Override
|
||||
public void requestRender() {
|
||||
glSurfaceView.requestRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* May be called from any thread.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>This method should be called once and only once in the life-cycle of
|
||||
* a SurfaceView.
|
||||
* The following SurfaceView methods can only be called <em>after</em>
|
||||
* setRenderer is called:
|
||||
* <ul>
|
||||
* <li>{@link #getRenderMode()}
|
||||
* <li>{@link #onPause()}
|
||||
* <li>{@link #onResume()}
|
||||
* <li>{@link #queueEvent(Runnable)}
|
||||
* <li>{@link #requestRender()}
|
||||
* <li>{@link #setRenderMode(int)}
|
||||
* </ul>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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()}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<Runnable> 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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* Called from the renderer frontend to schedule a render.
|
||||
*/
|
||||
@Override
|
||||
public void requestRender() {
|
||||
surfaceView.requestRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* May be called from any thread.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Runnable> eventQueue = new ArrayList<>();
|
||||
protected final ArrayList<Runnable> 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<TextureView> 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<TextureView> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<MapLibreGLSurfaceView> viewWeakReference = new WeakReference<>(this);
|
||||
|
||||
private static final String TAG = "GLSurfaceView";
|
||||
private static final GLThreadManager glThreadManager = new GLThreadManager();
|
||||
|
||||
private final WeakReference<MapLibreGLSurfaceView> 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.
|
||||
* <p>This method should be called once and only once in the life-cycle of
|
||||
* a GLSurfaceView.
|
||||
* <p>The following GLSurfaceView methods can only be called <em>before</em>
|
||||
* setRenderer is called:
|
||||
* <ul>
|
||||
* <li>{@link #setEGLConfigChooser(GLSurfaceView.EGLConfigChooser)}
|
||||
* </ul>
|
||||
* <p>
|
||||
* The following GLSurfaceView methods can only be called <em>after</em>
|
||||
* setRenderer is called:
|
||||
* <ul>
|
||||
* <li>{@link #getRenderMode()}
|
||||
* <li>{@link #onPause()}
|
||||
* <li>{@link #onResume()}
|
||||
* <li>{@link #queueEvent(Runnable)}
|
||||
* <li>{@link #requestRender()}
|
||||
* <li>{@link #setRenderMode(int)}
|
||||
* </ul>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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()}.
|
||||
* <p>
|
||||
* 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<MapLibreGLSurfaceView> glSurfaceViewWeakRef) {
|
||||
static class GLThread extends MapLibreSurfaceView.RenderThread {
|
||||
GLThread(WeakReference<MapLibreGLSurfaceView> 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<Runnable> 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<MapLibreGLSurfaceView> 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<MapLibreGLSurfaceView> mSurfaceViewWeakRef;
|
||||
}
|
||||
}
|
||||
@@ -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<TextureView> 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<TextureView> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<MapLibreVulkanSurfaceView> 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<MapLibreVulkanSurfaceView> 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<MapLibreVulkanSurfaceView> mSurfaceViewWeakRef;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,9 @@ android {
|
||||
create("drawable") {
|
||||
dimension = "renderer"
|
||||
}
|
||||
create("vulkan") {
|
||||
dimension = "renderer"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
51
platform/android/gradle/download-vulkan-validation.gradle
Normal file
51
platform/android/gradle/download-vulkan-validation.gradle
Normal file
@@ -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)
|
||||
}
|
||||
@@ -20,8 +20,6 @@ public:
|
||||
PremultipliedImage readStillImage() override;
|
||||
RendererBackend* getRendererBackend() override;
|
||||
|
||||
void swap();
|
||||
|
||||
class Impl {
|
||||
public:
|
||||
virtual ~Impl() = default;
|
||||
|
||||
@@ -24,6 +24,11 @@ public:
|
||||
|
||||
void createPlatformSurface() override {}
|
||||
void bind() override {}
|
||||
|
||||
void swap() override {
|
||||
SurfaceRenderableResource::swap();
|
||||
static_cast<Context&>(backend.getContext()).waitFrame();
|
||||
}
|
||||
};
|
||||
|
||||
HeadlessBackend::HeadlessBackend(const Size size_,
|
||||
@@ -69,10 +74,6 @@ gfx::Renderable& HeadlessBackend::getDefaultRenderable() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
void HeadlessBackend::swap() {
|
||||
static_cast<Context&>(*context).waitFrame();
|
||||
}
|
||||
|
||||
PremultipliedImage HeadlessBackend::readStillImage() {
|
||||
auto& contextImpl = static_cast<Context&>(*context);
|
||||
auto& resourceImpl = static_cast<HeadlessRenderableResource&>(*resource);
|
||||
|
||||
@@ -58,6 +58,9 @@ mbgl::Size GLFWVulkanBackend::getSize() const {
|
||||
|
||||
void GLFWVulkanBackend::setSize(const mbgl::Size newSize) {
|
||||
size = newSize;
|
||||
|
||||
auto& contextImpl = static_cast<mbgl::vulkan::Context&>(*context);
|
||||
contextImpl.requestSurfaceUpdate();
|
||||
}
|
||||
|
||||
std::vector<const char*> GLFWVulkanBackend::getInstanceExtensions() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -15,11 +15,8 @@
|
||||
#include <mbgl/geometry/feature_index.hpp>
|
||||
#include <mbgl/gfx/backend_scope.hpp>
|
||||
#include <mbgl/gfx/renderer_backend.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/renderable_resource.hpp>
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
#include <mbgl/math/angles.hpp>
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/renderer/bucket.hpp>
|
||||
#include <mbgl/renderer/layers/render_location_indicator_layer.hpp>
|
||||
#include <mbgl/renderer/paint_parameters.hpp>
|
||||
@@ -29,11 +26,25 @@
|
||||
#include <mbgl/util/mat4.hpp>
|
||||
|
||||
#include <mapbox/eternal.hpp>
|
||||
#include <mbgl/renderer/image_manager.hpp>
|
||||
|
||||
#if MLN_RENDER_BACKEND_OPENGL
|
||||
|
||||
#include <mbgl/platform/gl_functions.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/gl/renderable_resource.hpp>
|
||||
#include <mbgl/gl/defines.hpp>
|
||||
#include <mbgl/gl/texture.hpp>
|
||||
#include <mbgl/gl/texture_resource.hpp>
|
||||
#include <mbgl/gl/types.hpp>
|
||||
#include <mbgl/renderer/image_manager.hpp>
|
||||
|
||||
#else
|
||||
|
||||
#define MBGL_CHECK_ERROR(x) \
|
||||
while (0) { \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#include <numbers>
|
||||
|
||||
@@ -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<mbgl::Feature> feature;
|
||||
std::shared_ptr<mapbox::geometry::polygon<int64_t>> 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<gl::Context&>(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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -28,6 +28,13 @@
|
||||
#include <mbgl/renderer/layer_tweaker.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#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,
|
||||
|
||||
@@ -8,7 +8,7 @@ const std::array<UniformBlockInfo, 2> ShaderSource<BuiltIn::BackgroundShader, gf
|
||||
UniformBlockInfo{false, true, sizeof(BackgroundLayerUBO), idBackgroundLayerUBO},
|
||||
};
|
||||
const std::array<AttributeInfo, 1> ShaderSource<BuiltIn::BackgroundShader, gfx::Backend::Type::Vulkan>::attributes = {
|
||||
AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute},
|
||||
AttributeInfo{0, gfx::AttributeDataType::Short2, idBackgroundPosVertexAttribute},
|
||||
};
|
||||
|
||||
const std::array<UniformBlockInfo, 3>
|
||||
@@ -19,7 +19,7 @@ const std::array<UniformBlockInfo, 3>
|
||||
};
|
||||
const std::array<AttributeInfo, 1>
|
||||
ShaderSource<BuiltIn::BackgroundPatternShader, gfx::Backend::Type::Vulkan>::attributes = {
|
||||
AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute},
|
||||
AttributeInfo{0, gfx::AttributeDataType::Short2, idBackgroundPosVertexAttribute},
|
||||
};
|
||||
const std::array<TextureInfo, 1> ShaderSource<BuiltIn::BackgroundPatternShader, gfx::Backend::Type::Vulkan>::textures =
|
||||
{TextureInfo{0, idBackgroundImageTexture}};
|
||||
|
||||
@@ -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();
|
||||
|
||||
22
src/mbgl/shaders/vulkan/widevector.cpp
Normal file
22
src/mbgl/shaders/vulkan/widevector.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <mbgl/shaders/vulkan/widevector.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace shaders {
|
||||
|
||||
const std::array<UniformBlockInfo, 2> ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::Vulkan>::uniforms = {
|
||||
UniformBlockInfo{true, false, sizeof(WideVectorUniformsUBO), idWideVectorUniformsUBO},
|
||||
UniformBlockInfo{true, false, sizeof(WideVectorUniformWideVecUBO), idWideVectorUniformWideVecUBO},
|
||||
};
|
||||
const std::array<AttributeInfo, 3> ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::Vulkan>::attributes = {
|
||||
AttributeInfo{0, gfx::AttributeDataType::Float3, idWideVectorScreenPos},
|
||||
AttributeInfo{1, gfx::AttributeDataType::Float4, idWideVectorColor},
|
||||
AttributeInfo{2, gfx::AttributeDataType::Int, idWideVectorIndex}};
|
||||
const std::array<AttributeInfo, 4>
|
||||
ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::Vulkan>::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
|
||||
@@ -10,19 +10,27 @@ namespace mbgl {
|
||||
namespace vulkan {
|
||||
|
||||
bool BufferAllocation::create(const VmaAllocationCreateInfo& allocInfo, const vk::BufferCreateInfo& bufferInfo) {
|
||||
VkResult result = vmaCreateBuffer(
|
||||
allocator, reinterpret_cast<const VkBufferCreateInfo*>(&bufferInfo), &allocInfo, &buffer, &allocation, nullptr);
|
||||
VkBuffer buffer_;
|
||||
VkResult result = vmaCreateBuffer(allocator,
|
||||
reinterpret_cast<const VkBufferCreateInfo*>(&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;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ std::unique_ptr<gfx::RenderPass> CommandEncoder::createRenderPass(const char* na
|
||||
return std::make_unique<RenderPass>(*this, name, descriptor);
|
||||
}
|
||||
|
||||
void CommandEncoder::present(gfx::Renderable&) {}
|
||||
void CommandEncoder::present(gfx::Renderable& renderable) {
|
||||
renderable.getResource<RenderableResource>().swap();
|
||||
}
|
||||
|
||||
void CommandEncoder::pushDebugGroup(const char* name) {
|
||||
pushDebugGroup(name, {});
|
||||
|
||||
@@ -135,7 +135,8 @@ void Context::submitOneTimeCommand(const std::function<void(const vk::UniqueComm
|
||||
const vk::CommandBufferAllocateInfo allocateInfo(
|
||||
backend.getCommandPool().get(), vk::CommandBufferLevel::ePrimary, 1);
|
||||
|
||||
const auto& commandBuffers = backend.getDevice()->allocateCommandBuffersUnique(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::function<void(const vk::UniqueComm
|
||||
|
||||
const auto submitInfo = vk::SubmitInfo().setCommandBuffers(commandBuffer.get());
|
||||
|
||||
backend.getGraphicsQueue().submit(submitInfo);
|
||||
backend.getDevice()->waitIdle();
|
||||
const auto& fence = device->createFenceUnique(vk::FenceCreateInfo(vk::FenceCreateFlags()));
|
||||
backend.getGraphicsQueue().submit(submitInfo, fence.get());
|
||||
|
||||
constexpr uint64_t timeout = std::numeric_limits<uint64_t>::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<SurfaceRenderableResource>();
|
||||
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<uint64_t>::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<SurfaceRenderableResource>();
|
||||
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<vk::Buffer, 1> vertexBuffers = {clipping.vertexBuffer->getVulkanBuffer()};
|
||||
const std::array<vk::DeviceSize, 1> offset = {0};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<vk::DynamicState> PipelineInfo::getDynamicStates() const {
|
||||
std::vector<vk::DynamicState> PipelineInfo::getDynamicStates(const RendererBackend& backend) const {
|
||||
std::vector<vk::DynamicState> dynamicStates;
|
||||
|
||||
if (usesBlendConstants()) {
|
||||
@@ -430,7 +430,7 @@ std::vector<vk::DynamicState> PipelineInfo::getDynamicStates() const {
|
||||
dynamicStates.push_back(vk::DynamicState::eStencilReference);
|
||||
}
|
||||
|
||||
if (wideLines) {
|
||||
if (backend.getDeviceFeatures().wideLines && wideLines) {
|
||||
dynamicStates.push_back(vk::DynamicState::eLineWidth);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<vk::PresentModeKHR>& 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<int>(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<uint32_t, 2> queueIndices = {static_cast<uint32_t>(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<Context&>(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<Context&>().getCurrentFrameResourceIndex()];
|
||||
return swapchainFramebuffers[acquiredImageIndex];
|
||||
}
|
||||
|
||||
} // namespace vulkan
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <mbgl/shaders/vulkan/line.hpp>
|
||||
#include <mbgl/shaders/vulkan/raster.hpp>
|
||||
#include <mbgl/shaders/vulkan/symbol.hpp>
|
||||
#include <mbgl/shaders/vulkan/widevector.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
@@ -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<uint32_t>(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<SurfaceRenderableResource>();
|
||||
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() {
|
||||
|
||||
@@ -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<const VkImageCreateInfo*>(&imageInfo), &allocInfo, &image, &allocation, nullptr);
|
||||
allocator, reinterpret_cast<const VkImageCreateInfo*>(&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 {
|
||||
|
||||
Reference in New Issue
Block a user