From 072aa32e8b4fbf1a07acb446447fc52362a9da29 Mon Sep 17 00:00:00 2001 From: Adrian Cojocaru Date: Thu, 1 Aug 2024 22:49:49 +0300 Subject: [PATCH] Vulkan renderer backend (#2564) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bart Louwers --- .github/workflows/linux-ci.yml | 9 +- .gitmodules | 9 + CMakeLists.txt | 152 ++- include/mbgl/gfx/backend.hpp | 5 + include/mbgl/gfx/drawable.hpp | 6 +- include/mbgl/gfx/uniform_block.hpp | 6 +- include/mbgl/gl/uniform_block_gl.hpp | 15 +- include/mbgl/gl/vertex_attribute_gl.hpp | 14 +- include/mbgl/mtl/uniform_block.hpp | 14 +- include/mbgl/mtl/vertex_attribute.hpp | 9 +- include/mbgl/shaders/vulkan/background.hpp | 152 +++ include/mbgl/shaders/vulkan/circle.hpp | 295 +++++ include/mbgl/shaders/vulkan/clipping_mask.hpp | 47 + include/mbgl/shaders/vulkan/collision.hpp | 189 +++ include/mbgl/shaders/vulkan/common.hpp | 104 ++ include/mbgl/shaders/vulkan/debug.hpp | 76 ++ include/mbgl/shaders/vulkan/fill.hpp | 1097 +++++++++++++++++ include/mbgl/shaders/vulkan/heatmap.hpp | 200 +++ include/mbgl/shaders/vulkan/hillshade.hpp | 227 ++++ include/mbgl/shaders/vulkan/line.hpp | 1070 ++++++++++++++++ include/mbgl/shaders/vulkan/raster.hpp | 126 ++ include/mbgl/shaders/vulkan/shader_group.hpp | 92 ++ .../mbgl/shaders/vulkan/shader_program.hpp | 95 ++ include/mbgl/shaders/vulkan/symbol.hpp | 890 +++++++++++++ include/mbgl/vulkan/buffer_resource.hpp | 92 ++ include/mbgl/vulkan/command_encoder.hpp | 46 + include/mbgl/vulkan/context.hpp | 203 +++ include/mbgl/vulkan/drawable.hpp | 60 + include/mbgl/vulkan/drawable_builder.hpp | 28 + include/mbgl/vulkan/index_buffer_resource.hpp | 28 + include/mbgl/vulkan/layer_group.hpp | 37 + include/mbgl/vulkan/offscreen_texture.hpp | 23 + include/mbgl/vulkan/pipeline.hpp | 93 ++ include/mbgl/vulkan/render_pass.hpp | 41 + include/mbgl/vulkan/renderable_resource.hpp | 76 ++ include/mbgl/vulkan/renderer_backend.hpp | 116 ++ include/mbgl/vulkan/texture2d.hpp | 128 ++ include/mbgl/vulkan/tile_layer_group.hpp | 39 + include/mbgl/vulkan/uniform_block.hpp | 59 + include/mbgl/vulkan/uniform_buffer.hpp | 50 + include/mbgl/vulkan/upload_pass.hpp | 93 ++ include/mbgl/vulkan/vertex_attribute.hpp | 52 + .../mbgl/vulkan/vertex_buffer_resource.hpp | 32 + metrics/ignores/linux-vulkan.json | 3 + metrics/linux-vulkan.json | 16 + .../include/mbgl/vulkan/headless_backend.hpp | 48 + .../src/mbgl/vulkan/headless_backend.cpp | 113 ++ platform/glfw/CMakeLists.txt | 12 + platform/glfw/glfw_view.cpp | 6 +- platform/glfw/glfw_vulkan_backend.cpp | 82 ++ platform/glfw/glfw_vulkan_backend.hpp | 35 + platform/ios/test/common/BUILD.bazel | 2 +- platform/linux/linux.cmake | 12 + platform/windows/Get-VendorPackages.ps1 | 1 + platform/windows/windows.cmake | 14 + render-test/filesystem.hpp | 4 +- render-test/ios/BUILD.bazel | 2 +- render-test/manifest_parser.cpp | 49 +- render-test/parser.cpp | 10 +- render-test/render_test.cpp | 8 +- render-test/runner.cpp | 2 +- src/mbgl/gl/program.hpp | 12 +- src/mbgl/programs/program.hpp | 8 +- src/mbgl/programs/symbol_program.hpp | 6 +- .../renderer/layers/fill_layer_tweaker.cpp | 1 + src/mbgl/renderer/paint_parameters.cpp | 44 + src/mbgl/renderer/renderer_impl.cpp | 15 +- src/mbgl/shaders/vulkan/background.cpp | 28 + src/mbgl/shaders/vulkan/circle.cpp | 24 + src/mbgl/shaders/vulkan/clipping_mask.cpp | 10 + src/mbgl/shaders/vulkan/collision.cpp | 31 + src/mbgl/shaders/vulkan/debug.cpp | 17 + src/mbgl/shaders/vulkan/fill.cpp | 116 ++ src/mbgl/shaders/vulkan/heatmap.cpp | 32 + src/mbgl/shaders/vulkan/hillshade.cpp | 32 + src/mbgl/shaders/vulkan/line.cpp | 90 ++ src/mbgl/shaders/vulkan/raster.cpp | 20 + src/mbgl/shaders/vulkan/shader_program.cpp | 269 ++++ src/mbgl/shaders/vulkan/symbol.cpp | 98 ++ src/mbgl/vulkan/buffer_resource.cpp | 164 +++ src/mbgl/vulkan/command_encoder.cpp | 42 + src/mbgl/vulkan/context.cpp | 560 +++++++++ src/mbgl/vulkan/drawable.cpp | 453 +++++++ src/mbgl/vulkan/drawable_builder.cpp | 47 + src/mbgl/vulkan/drawable_impl.hpp | 53 + src/mbgl/vulkan/index_buffer_resource.cpp | 26 + src/mbgl/vulkan/layer_group.cpp | 89 ++ src/mbgl/vulkan/offscreen_texture.cpp | 148 +++ src/mbgl/vulkan/pipeline.cpp | 441 +++++++ src/mbgl/vulkan/render_pass.cpp | 85 ++ src/mbgl/vulkan/renderable_resource.cpp | 344 ++++++ src/mbgl/vulkan/renderer_backend.cpp | 616 +++++++++ src/mbgl/vulkan/texture2d.cpp | 508 ++++++++ src/mbgl/vulkan/tile_layer_group.cpp | 135 ++ src/mbgl/vulkan/uniform_buffer.cpp | 42 + src/mbgl/vulkan/upload_pass.cpp | 201 +++ src/mbgl/vulkan/vertex_attribute.cpp | 50 + src/mbgl/vulkan/vertex_buffer_resource.cpp | 26 + test/CMakeLists.txt | 16 + vendor/Vulkan-Headers | 1 + vendor/VulkanMemoryAllocator | 1 + vendor/glslang | 1 + vendor/vulkan.cmake | 78 ++ 103 files changed, 11459 insertions(+), 135 deletions(-) create mode 100644 include/mbgl/shaders/vulkan/background.hpp create mode 100644 include/mbgl/shaders/vulkan/circle.hpp create mode 100644 include/mbgl/shaders/vulkan/clipping_mask.hpp create mode 100644 include/mbgl/shaders/vulkan/collision.hpp create mode 100644 include/mbgl/shaders/vulkan/common.hpp create mode 100644 include/mbgl/shaders/vulkan/debug.hpp create mode 100644 include/mbgl/shaders/vulkan/fill.hpp create mode 100644 include/mbgl/shaders/vulkan/heatmap.hpp create mode 100644 include/mbgl/shaders/vulkan/hillshade.hpp create mode 100644 include/mbgl/shaders/vulkan/line.hpp create mode 100644 include/mbgl/shaders/vulkan/raster.hpp create mode 100644 include/mbgl/shaders/vulkan/shader_group.hpp create mode 100644 include/mbgl/shaders/vulkan/shader_program.hpp create mode 100644 include/mbgl/shaders/vulkan/symbol.hpp create mode 100644 include/mbgl/vulkan/buffer_resource.hpp create mode 100644 include/mbgl/vulkan/command_encoder.hpp create mode 100644 include/mbgl/vulkan/context.hpp create mode 100644 include/mbgl/vulkan/drawable.hpp create mode 100644 include/mbgl/vulkan/drawable_builder.hpp create mode 100644 include/mbgl/vulkan/index_buffer_resource.hpp create mode 100644 include/mbgl/vulkan/layer_group.hpp create mode 100644 include/mbgl/vulkan/offscreen_texture.hpp create mode 100644 include/mbgl/vulkan/pipeline.hpp create mode 100644 include/mbgl/vulkan/render_pass.hpp create mode 100644 include/mbgl/vulkan/renderable_resource.hpp create mode 100644 include/mbgl/vulkan/renderer_backend.hpp create mode 100644 include/mbgl/vulkan/texture2d.hpp create mode 100644 include/mbgl/vulkan/tile_layer_group.hpp create mode 100644 include/mbgl/vulkan/uniform_block.hpp create mode 100644 include/mbgl/vulkan/uniform_buffer.hpp create mode 100644 include/mbgl/vulkan/upload_pass.hpp create mode 100644 include/mbgl/vulkan/vertex_attribute.hpp create mode 100644 include/mbgl/vulkan/vertex_buffer_resource.hpp create mode 100644 metrics/ignores/linux-vulkan.json create mode 100644 metrics/linux-vulkan.json create mode 100644 platform/default/include/mbgl/vulkan/headless_backend.hpp create mode 100644 platform/default/src/mbgl/vulkan/headless_backend.cpp create mode 100644 platform/glfw/glfw_vulkan_backend.cpp create mode 100644 platform/glfw/glfw_vulkan_backend.hpp create mode 100644 src/mbgl/shaders/vulkan/background.cpp create mode 100644 src/mbgl/shaders/vulkan/circle.cpp create mode 100644 src/mbgl/shaders/vulkan/clipping_mask.cpp create mode 100644 src/mbgl/shaders/vulkan/collision.cpp create mode 100644 src/mbgl/shaders/vulkan/debug.cpp create mode 100644 src/mbgl/shaders/vulkan/fill.cpp create mode 100644 src/mbgl/shaders/vulkan/heatmap.cpp create mode 100644 src/mbgl/shaders/vulkan/hillshade.cpp create mode 100644 src/mbgl/shaders/vulkan/line.cpp create mode 100644 src/mbgl/shaders/vulkan/raster.cpp create mode 100644 src/mbgl/shaders/vulkan/shader_program.cpp create mode 100644 src/mbgl/shaders/vulkan/symbol.cpp create mode 100644 src/mbgl/vulkan/buffer_resource.cpp create mode 100644 src/mbgl/vulkan/command_encoder.cpp create mode 100644 src/mbgl/vulkan/context.cpp create mode 100644 src/mbgl/vulkan/drawable.cpp create mode 100644 src/mbgl/vulkan/drawable_builder.cpp create mode 100644 src/mbgl/vulkan/drawable_impl.hpp create mode 100644 src/mbgl/vulkan/index_buffer_resource.cpp create mode 100644 src/mbgl/vulkan/layer_group.cpp create mode 100644 src/mbgl/vulkan/offscreen_texture.cpp create mode 100644 src/mbgl/vulkan/pipeline.cpp create mode 100644 src/mbgl/vulkan/render_pass.cpp create mode 100644 src/mbgl/vulkan/renderable_resource.cpp create mode 100644 src/mbgl/vulkan/renderer_backend.cpp create mode 100644 src/mbgl/vulkan/texture2d.cpp create mode 100644 src/mbgl/vulkan/tile_layer_group.cpp create mode 100644 src/mbgl/vulkan/uniform_buffer.cpp create mode 100644 src/mbgl/vulkan/upload_pass.cpp create mode 100644 src/mbgl/vulkan/vertex_attribute.cpp create mode 100644 src/mbgl/vulkan/vertex_buffer_resource.cpp create mode 160000 vendor/Vulkan-Headers create mode 160000 vendor/VulkanMemoryAllocator create mode 160000 vendor/glslang create mode 100644 vendor/vulkan.cmake diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 74290c5d3..2bc2053a5 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -50,7 +50,7 @@ jobs: strategy: fail-fast: true matrix: - renderer: [legacy, drawable] + renderer: [legacy, drawable, vulkan] runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -83,7 +83,8 @@ jobs: libpng-dev \ libglfw3-dev \ libwebp-dev \ - libopengl0 + libopengl0 \ + mesa-vulkan-drivers - if: matrix.renderer == 'drawable' run: echo renderer_flag_cmake=-DMLN_DRAWABLE_RENDERER=ON >> "$GITHUB_ENV" @@ -91,6 +92,9 @@ jobs: - if: matrix.renderer == 'legacy' run: echo renderer_flag_cmake=-DMLN_LEGACY_RENDERER=ON >> "$GITHUB_ENV" + - if: matrix.renderer == 'vulkan' + run: echo renderer_flag_cmake="-DMLN_DRAWABLE_RENDERER=ON -DMLN_LEGACY_RENDERER=OFF -DMLN_WITH_VULKAN=ON -DMLN_WITH_OPENGL=OFF" >> "$GITHUB_ENV" + - name: Build MapLibre Native Core env: CI: 1 @@ -143,6 +147,7 @@ jobs: - run: chmod +x build/mbgl-test-runner - name: Run C++ tests + continue-on-error: ${{ matrix.renderer == 'vulkan' }} run: xvfb-run -a build/mbgl-test-runner # render tests diff --git a/.gitmodules b/.gitmodules index c94a93a6d..c878230b9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -49,3 +49,12 @@ [submodule "vendor/unordered_dense"] path = vendor/unordered_dense url = https://github.com/martinus/unordered_dense.git +[submodule "vendor/Vulkan-Headers"] + path = vendor/Vulkan-Headers + url = https://github.com/KhronosGroup/Vulkan-Headers.git +[submodule "vendor/VulkanMemoryAllocator"] + path = vendor/VulkanMemoryAllocator + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git +[submodule "vendor/glslang"] + path = vendor/glslang + url = https://github.com/KhronosGroup/glslang.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3de77415f..4a79cfcb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ option(MLN_WITH_SANITIZER "Use [address|thread|undefined] here" OFF) option(MLN_WITH_RTTI "Compile with runtime type information" OFF) option(MLN_WITH_OPENGL "Build with OpenGL renderer" ON) option(MLN_WITH_EGL "Build with EGL renderer" OFF) +option(MLN_WITH_VULKAN "Build with Vulkan renderer" OFF) option(MLN_WITH_OSMESA "Build with OSMesa (Software) renderer" OFF) option(MLN_WITH_WERROR "Make all compilation warnings errors" ON) option(MLN_LEGACY_RENDERER "Include the legacy rendering pathway" ON) @@ -162,16 +163,6 @@ if(MLN_DRAWABLE_RENDERER) ${PROJECT_SOURCE_DIR}/include/mbgl/renderer/render_target.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_program_base.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/util/suppress_copies.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_info.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/buffer_allocator.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl_builder.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/layer_group_gl.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/uniform_block_gl.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/uniform_buffer_gl.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/vertex_attribute_gl.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/gl/texture2d.hpp ) list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/drawable.cpp @@ -219,16 +210,12 @@ if(MLN_DRAWABLE_RENDERER) ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/collision_layer_tweaker.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/collision_layer_tweaker.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/shader_program_base.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_program_gl.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/buffer_allocator.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl_builder.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl_impl.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/layer_group_gl.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/texture2d.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/uniform_block_gl.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/uniform_buffer_gl.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_attribute_gl.cpp + ) + + target_compile_definitions( + mbgl-core + PUBLIC + "MLN_DRAWABLE_RENDERER=$" ) endif() @@ -252,6 +239,10 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/shader_group.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/shader.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/types.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/backend.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer_render_parameters.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/custom_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/background_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/circle_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/fill_extrusion_layer_factory.hpp @@ -854,6 +845,11 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/style_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/style_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/types.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_render_parameters.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/custom_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_impl.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_impl.hpp ${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 @@ -1005,11 +1001,6 @@ if(MLN_WITH_OPENGL) ) list(APPEND INCLUDE_FILES - ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/backend.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer_render_parameters.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp - ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/custom_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderable_resource.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderer_backend.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/location_indicator_layer_factory.hpp @@ -1081,11 +1072,6 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/src/mbgl/gl/context.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/fence.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/fence.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_render_parameters.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/custom_layer_factory.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_impl.cpp - ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_layer_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/debugging_extension.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/debugging_extension.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/defines.hpp @@ -1159,6 +1145,7 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/symbol_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/widevector_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/gl/buffer_allocator.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl_builder.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gl/layer_group_gl.hpp @@ -1170,6 +1157,7 @@ if(MLN_WITH_OPENGL) list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_info.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_program_gl.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gl/buffer_allocator.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl_builder.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl_impl.hpp @@ -1287,6 +1275,96 @@ if(MBGL_WITH_METAL) ) endif() +if(MLN_WITH_VULKAN) + message(STATUS "Configuring Vulkan renderer backend") + + target_compile_definitions( + mbgl-core + PRIVATE + MLN_RENDER_BACKEND_VULKAN=1 + ) + + list(APPEND + INCLUDE_FILES + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/buffer_resource.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/command_encoder.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/context.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/drawable.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/drawable_builder.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/index_buffer_resource.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/layer_group.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/offscreen_texture.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/pipeline.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/renderer_backend.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/render_pass.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/renderable_resource.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/texture2d.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/tile_layer_group.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/uniform_buffer.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/upload_pass.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/vertex_attribute.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/shader_group.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/shader_program.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/background.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/circle.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/clipping_mask.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/collision.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/common.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/debug.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/fill.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/heatmap.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/hillshade.hpp + ${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 + ) + + list(APPEND + SRC_FILES + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/buffer_resource.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/command_encoder.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/context.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/drawable.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/drawable_builder.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/drawable_impl.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/index_buffer_resource.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/layer_group.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/offscreen_texture.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/pipeline.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/renderer_backend.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/render_pass.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/renderable_resource.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/texture2d.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/tile_layer_group.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/uniform_buffer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/upload_pass.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/vertex_attribute.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/shader_program.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/background.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/circle.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/clipping_mask.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/collision.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/debug.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/fill.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/heatmap.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/hillshade.cpp + ${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 + ) + + #TODO merge with MLN_DRAWABLE_RENDERER? These are added only for GL path + list(APPEND + SRC_FILES + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_drawable_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/custom_drawable_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_drawable_layer_impl.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_drawable_layer_impl.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_drawable_layer.hpp + ) +endif() + target_sources( mbgl-core PRIVATE ${INCLUDE_FILES} @@ -1424,6 +1502,20 @@ export(TARGETS FILE MapboxCoreTargets.cmake ) +if(MLN_WITH_VULKAN) + include(${PROJECT_SOURCE_DIR}/vendor/vulkan.cmake) + + target_link_libraries( + mbgl-core + PUBLIC + mbgl-vendor-vulkan-headers + mbgl-vendor-VulkanMemoryAllocator + glslang + SPIRV + glslang-default-resource-limits + ) +endif() + set_target_properties( mbgl-core PROPERTIES diff --git a/include/mbgl/gfx/backend.hpp b/include/mbgl/gfx/backend.hpp index 66bbe4a3b..28c5a4ebc 100644 --- a/include/mbgl/gfx/backend.hpp +++ b/include/mbgl/gfx/backend.hpp @@ -13,12 +13,15 @@ public: enum class Type : uint8_t { OpenGL, ///< The OpenGL API backend Metal, ///< The Metal API backend + Vulkan, ///< The Vulkan API backend TYPE_MAX, ///< Not a valid backend type, used to determine the number ///< of available backends (ie for array allocation). }; #if MLN_RENDER_BACKEND_METAL static constexpr Type DefaultType = Type::Metal; +#elif MLN_RENDER_BACKEND_VULKAN + static constexpr Type DefaultType = Type::Vulkan; #else // assume MLN_RENDER_BACKEND_OPENGL static constexpr Type DefaultType = Type::OpenGL; #endif @@ -38,6 +41,8 @@ public: static std::unique_ptr Create(Args... args) { #if MLN_RENDER_BACKEND_METAL return Create(std::forward(args)...); +#elif MLN_RENDER_BACKEND_VULKAN + return Create(std::forward(args)...); #else // assume MLN_RENDER_BACKEND_OPENGL return Create(std::forward(args)...); #endif diff --git a/include/mbgl/gfx/drawable.hpp b/include/mbgl/gfx/drawable.hpp index 3153a0cf0..c90448036 100644 --- a/include/mbgl/gfx/drawable.hpp +++ b/include/mbgl/gfx/drawable.hpp @@ -97,7 +97,7 @@ public: int32_t getLineWidth() const { return lineWidth; } /// Set line width - void setLineWidth(int32_t value) { lineWidth = value; } + virtual void setLineWidth(int32_t value) { lineWidth = value; } /// @brief Get the texture at the given internal ID. const gfx::Texture2DPtr& getTexture(size_t id) const; @@ -122,7 +122,7 @@ public: bool getEnableColor() const { return enableColor; } /// Set whether to render to the color target - void setEnableColor(bool value) { enableColor = value; } + virtual void setEnableColor(bool value) { enableColor = value; } /// Whether to do stenciling (based on the Tile ID or 3D) bool getEnableStencil() const { return enableStencil; } @@ -175,7 +175,7 @@ public: const gfx::CullFaceMode& getCullFaceMode() const; /// Set cull face mode - void setCullFaceMode(const gfx::CullFaceMode&); + virtual void setCullFaceMode(const gfx::CullFaceMode&); /// Get color mode const gfx::ColorMode& getColorMode() const; diff --git a/include/mbgl/gfx/uniform_block.hpp b/include/mbgl/gfx/uniform_block.hpp index 70f267a6c..3f196b856 100644 --- a/include/mbgl/gfx/uniform_block.hpp +++ b/include/mbgl/gfx/uniform_block.hpp @@ -12,8 +12,8 @@ using UniqueUniformBlock = std::unique_ptr; /// @brief This class represents an uniform block class UniformBlock { -protected: - /// @brief Constructor. Can only be created by UniformBlockArray implementations +public: + /// @brief Constructor /// @param index_ /// @param size_ UniformBlock(int index_, std::size_t size_) @@ -23,8 +23,6 @@ protected: UniformBlock(UniformBlock&& other) : index(other.index), size(other.size) {} - -public: /// @brief Destructor virtual ~UniformBlock() = default; diff --git a/include/mbgl/gl/uniform_block_gl.hpp b/include/mbgl/gl/uniform_block_gl.hpp index 7776461dc..c91cfc55f 100644 --- a/include/mbgl/gl/uniform_block_gl.hpp +++ b/include/mbgl/gl/uniform_block_gl.hpp @@ -6,21 +6,15 @@ namespace mbgl { namespace gl { -class UniformBlockArrayGL; - class UniformBlockGL final : public gfx::UniformBlock { - // Can only be created by UniformBlockArrayGL -private: - friend UniformBlockArrayGL; - +public: UniformBlockGL(int index_, std::size_t size_) : UniformBlock(index_, size_) {} UniformBlockGL(const UniformBlockGL& other) : UniformBlock(other) {} UniformBlockGL(UniformBlockGL&& other) : UniformBlock(std::move(other)) {} - -public: + ~UniformBlockGL() override = default; void bindBuffer(const gfx::UniformBuffer& uniformBuffer) override; void unbindBuffer() override; }; @@ -44,11 +38,10 @@ public: private: std::unique_ptr create(int index, std::size_t size) override { - return std::unique_ptr(new UniformBlockGL(index, size)); + return std::make_unique(index, size); } std::unique_ptr copy(const gfx::UniformBlock& uniformBlocks) override { - return std::unique_ptr( - new UniformBlockGL(static_cast(uniformBlocks))); + return std::make_unique(static_cast(uniformBlocks)); } }; diff --git a/include/mbgl/gl/vertex_attribute_gl.hpp b/include/mbgl/gl/vertex_attribute_gl.hpp index d57d5dbbc..ec83bd899 100644 --- a/include/mbgl/gl/vertex_attribute_gl.hpp +++ b/include/mbgl/gl/vertex_attribute_gl.hpp @@ -11,13 +11,10 @@ class VertexBufferResource; } // namespace gfx namespace gl { -class VertexAttributeArrayGL; class UploadPass; class VertexAttributeGL final : public gfx::VertexAttribute { - // Can only be created by VertexAttributeArrayGL -private: - friend VertexAttributeArrayGL; +public: VertexAttributeGL(int index_, gfx::AttributeDataType dataType_, std::size_t count_) : VertexAttribute(index_, dataType_, count_) {} VertexAttributeGL(const VertexAttributeGL& other) @@ -26,8 +23,6 @@ private: VertexAttributeGL(VertexAttributeGL&& other) : VertexAttribute(std::move(other)), glType(other.glType) {} - -public: ~VertexAttributeGL() override = default; platform::GLenum getGLType() const { return glType; } @@ -70,12 +65,11 @@ private: std::unique_ptr create(int index, gfx::AttributeDataType dataType, std::size_t count) const override { - return std::unique_ptr(new VertexAttributeGL(index, dataType, count)); + return std::make_unique(index, dataType, count); } - using gfx::VertexAttributeArray::copy; + std::unique_ptr copy(const gfx::VertexAttribute& attr) const override { - return std::unique_ptr( - new VertexAttributeGL(static_cast(attr))); + return std::make_unique(static_cast(attr)); } }; diff --git a/include/mbgl/mtl/uniform_block.hpp b/include/mbgl/mtl/uniform_block.hpp index 50f43113d..dcfef87f7 100644 --- a/include/mbgl/mtl/uniform_block.hpp +++ b/include/mbgl/mtl/uniform_block.hpp @@ -6,21 +6,15 @@ namespace mbgl { namespace mtl { -class UniformBlockArray; - class UniformBlock final : public gfx::UniformBlock { - // Can only be created by UniformBlockArray -private: - friend UniformBlockArray; - +public: UniformBlock(int index_, std::size_t size_) : gfx::UniformBlock(index_, size_) {} UniformBlock(const UniformBlock& other) : gfx::UniformBlock(other) {} UniformBlock(UniformBlock&& other) : gfx::UniformBlock(std::move(other)) {} - -public: + ~UniformBlock() override = default; void bindBuffer(const gfx::UniformBuffer& uniformBuffer) override; void unbindBuffer() override; @@ -54,10 +48,10 @@ public: private: std::unique_ptr create(int index, std::size_t size) override { - return std::unique_ptr(new UniformBlock(index, size)); + return std::make_unique(index, size); } std::unique_ptr copy(const gfx::UniformBlock& uniformBlocks) override { - return std::unique_ptr(new UniformBlock(static_cast(uniformBlocks))); + return std::make_unique(static_cast(uniformBlocks)); } }; diff --git a/include/mbgl/mtl/vertex_attribute.hpp b/include/mbgl/mtl/vertex_attribute.hpp index 699e5bf64..b956b8a00 100644 --- a/include/mbgl/mtl/vertex_attribute.hpp +++ b/include/mbgl/mtl/vertex_attribute.hpp @@ -9,20 +9,15 @@ class VertexBufferResource; } // namespace gfx namespace mtl { -class VertexAttributeArray; class UploadPass; class VertexAttribute final : public gfx::VertexAttribute { - // Can only be created by VertexAttributeArray -private: - friend VertexAttributeArray; +public: VertexAttribute(int index_, gfx::AttributeDataType dataType_, std::size_t count_) : gfx::VertexAttribute(index_, dataType_, count_) {} VertexAttribute(const VertexAttribute& other) = delete; VertexAttribute(VertexAttribute&& other) : gfx::VertexAttribute(std::move(other)) {} - -public: ~VertexAttribute() override = default; static const gfx::UniqueVertexBufferResource& getBuffer(gfx::VertexAttribute&, @@ -48,7 +43,7 @@ public: private: gfx::UniqueVertexAttribute create(int index, gfx::AttributeDataType dataType, std::size_t count) const override { - return gfx::UniqueVertexAttribute(new VertexAttribute(index, dataType, count)); + return std::make_unique(index, dataType, count); } }; diff --git a/include/mbgl/shaders/vulkan/background.hpp b/include/mbgl/shaders/vulkan/background.hpp new file mode 100644 index 000000000..7066891e4 --- /dev/null +++ b/include/mbgl/shaders/vulkan/background.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "BackgroundShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +layout(set = 0, binding = 1) uniform BackgroundDrawableUBO { + mat4 matrix; +} drawable; + +void main() { + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.y *= -1.0; +} +)"; + + static constexpr auto fragment = R"( +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 2) uniform BackgroundLayerUBO { + vec4 color; + float opacity; + float pad1, pad2, pad3; +} layer; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + out_color = layer.color * layer.opacity; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "BackgroundPatternShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +layout(set = 0, binding = 1) uniform BackgroundPatternDrawableUBO { + mat4 matrix; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + float tile_units_to_pixels; + float pad1, pad2, pad3; +} drawable; + +layout(set = 0, binding = 2) uniform BackgroundPatternLayerUBO { + vec2 pattern_tl_a; + vec2 pattern_br_a; + vec2 pattern_tl_b; + vec2 pattern_br_b; + vec2 pattern_size_a; + vec2 pattern_size_b; + float scale_a; + float scale_b; + float mix; + float opacity; +} layer; + +layout(location = 0) out vec2 frag_pos_a; +layout(location = 1) out vec2 frag_pos_b; + +void main() { + + frag_pos_a = get_pattern_pos(drawable.pixel_coord_upper, + drawable.pixel_coord_lower, + layer.scale_a * layer.pattern_size_a, + drawable.tile_units_to_pixels, + in_position); + frag_pos_b = get_pattern_pos(drawable.pixel_coord_upper, + drawable.pixel_coord_lower, + layer.scale_b * layer.pattern_size_b, + drawable.tile_units_to_pixels, + in_position); + + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.y *= -1.0; +} +)"; + + static constexpr auto fragment = R"( +layout(location = 0) in vec2 frag_pos_a; +layout(location = 1) in vec2 frag_pos_b; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 2) uniform BackgroundPatternLayerUBO { + vec2 pattern_tl_a; + vec2 pattern_br_a; + vec2 pattern_tl_b; + vec2 pattern_br_b; + vec2 pattern_size_a; + vec2 pattern_size_b; + float scale_a; + float scale_b; + float mix; + float opacity; +} layer; + +layout(set = 1, binding = 0) uniform sampler2D image_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + const vec2 texsize = global.pattern_atlas_texsize; + + const vec2 imagecoord = mod(frag_pos_a, 1.0); + const vec2 pos = mix(layer.pattern_tl_a / texsize, layer.pattern_br_a / texsize, imagecoord); + const vec4 color1 = texture(image_sampler, pos); + + const vec2 imagecoord_b = mod(frag_pos_b, 1.0); + const vec2 pos2 = mix(layer.pattern_tl_b / texsize, layer.pattern_br_b / texsize, imagecoord_b); + const vec4 color2 = texture(image_sampler, pos2); + + out_color = mix(color1, color2, layer.mix) * layer.opacity; +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/circle.hpp b/include/mbgl/shaders/vulkan/circle.hpp new file mode 100644 index 000000000..30ebacedd --- /dev/null +++ b/include/mbgl/shaders/vulkan/circle.hpp @@ -0,0 +1,295 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "CircleShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 1) in vec4 in_color; +#endif + +#if !defined(HAS_UNIFORM_u_radius) +layout(location = 2) in vec2 in_radius; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 3) in vec2 in_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 4) in vec2 in_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_color) +layout(location = 5) in vec4 in_stroke_color; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_width) +layout(location = 6) in vec2 in_stroke_width; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_opacity) +layout(location = 7) in vec2 in_stroke_opacity; +#endif + +layout(set = 0, binding = 1) uniform CircleDrawableUBO { + mat4 matrix; + vec2 extrude_scale; + vec2 padding; +} drawable; + +layout(set = 0, binding = 2) uniform CircleEvaluatedPropsUBO { + vec4 color; + vec4 stroke_color; + float radius; + float blur; + float opacity; + float stroke_width; + float stroke_opacity; + bool scale_with_map; + bool pitch_with_map; + float padding; +} props; + +layout(set = 0, binding = 3) uniform CircleInterpolateUBO { + float color_t; + float radius_t; + float blur_t; + float opacity_t; + float stroke_color_t; + float stroke_width_t; + float stroke_opacity_t; + float pad1_; +} interp; + +layout(location = 0) out vec2 frag_extrude; +layout(location = 1) out float frag_antialiasblur; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 2) out vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_radius) +layout(location = 3) out mediump float frag_radius; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) out lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) out lowp float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_color) +layout(location = 6) out vec4 frag_stroke_color; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_width) +layout(location = 7) out mediump float frag_stroke_width; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_opacity) +layout(location = 8) out lowp float frag_stroke_opacity; +#endif + +void main() { + +#if defined(HAS_UNIFORM_u_radius) + const float radius = props.radius; +#else + const float radius = unpack_mix_float(in_radius, interp.radius_t); +#endif + +#if defined(HAS_UNIFORM_u_stroke_width) + const float stroke_width = props.stroke_width; +#else + const float stroke_width = unpack_mix_float(in_stroke_width, interp.stroke_width_t); +#endif + + // unencode the extrusion vector that we snuck into the a_pos vector + const vec2 extrude = mod(in_position, 2.0) * 2.0 - 1.0; + const vec2 scaled_extrude = extrude * drawable.extrude_scale; + + // multiply a_pos by 0.5, since we had it * 2 in order to sneak in extrusion data + const vec2 circle_center = floor(in_position * 0.5); + + if (props.pitch_with_map) { + vec2 corner_position = circle_center; + if (props.scale_with_map) { + corner_position += scaled_extrude * (radius + stroke_width); + } else { + // Pitching the circle with the map effectively scales it with the map + // To counteract the effect for pitch-scale: viewport, we rescale the + // whole circle based on the pitch scaling effect at its central point + const vec4 projected_center = drawable.matrix * vec4(circle_center, 0, 1); + corner_position += scaled_extrude * (radius + stroke_width) * + (projected_center.w / global.camera_to_center_distance); + } + + gl_Position = drawable.matrix * vec4(corner_position, 0, 1); + } else { + gl_Position = drawable.matrix * vec4(circle_center, 0, 1); + + const float factor = props.scale_with_map ? global.camera_to_center_distance : gl_Position.w; + gl_Position.xy += scaled_extrude * (radius + stroke_width) * factor; + } + + gl_Position.y *= -1.0; + + // This is a minimum blur distance that serves as a faux-antialiasing for + // the circle. since blur is a ratio of the circle's size and the intent is + // to keep the blur at roughly 1px, the two are inversely related. + frag_antialiasblur = 1.0 / DEVICE_PIXEL_RATIO / (radius + stroke_width); + + frag_extrude = extrude; + +#if !defined(HAS_UNIFORM_u_color) + frag_color = unpack_mix_color(in_color, interp.color_t); +#endif + +#if !defined(HAS_UNIFORM_u_radius) + frag_radius = radius; +#endif + +#if !defined(HAS_UNIFORM_u_blur) + frag_blur = unpack_mix_float(in_blur, interp.blur_t); +#endif + +#if !defined(HAS_UNIFORM_u_opacity) + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + +#if !defined(HAS_UNIFORM_u_stroke_color) + frag_stroke_color = unpack_mix_color(in_stroke_color, interp.stroke_color_t); +#endif + +#if !defined(HAS_UNIFORM_u_stroke_width) + frag_stroke_width = stroke_width; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_opacity) + frag_stroke_opacity = unpack_mix_float(in_stroke_opacity, interp.stroke_opacity_t); +#endif +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_extrude; +layout(location = 1) in float frag_antialiasblur; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 2) in vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_radius) +layout(location = 3) in mediump float frag_radius; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) in lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) in lowp float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_color) +layout(location = 6) in vec4 frag_stroke_color; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_width) +layout(location = 7) in mediump float frag_stroke_width; +#endif + +#if !defined(HAS_UNIFORM_u_stroke_opacity) +layout(location = 8) in lowp float frag_stroke_opacity; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 2) uniform CircleEvaluatedPropsUBO { + vec4 color; + vec4 stroke_color; + float radius; + float blur; + float opacity; + float stroke_width; + float stroke_opacity; + bool scale_with_map; + bool pitch_with_map; + float padding; +} props; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_color) + const vec4 color = props.color; +#else + const vec4 color = frag_color; +#endif +#if defined(HAS_UNIFORM_u_radius) + const float radius = props.radius; +#else + const float radius = frag_radius; +#endif +#if defined(HAS_UNIFORM_u_blur) + const float blur = props.blur; +#else + const float blur = frag_blur; +#endif +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = props.opacity; +#else + const float opacity = frag_opacity; +#endif +#if defined(HAS_UNIFORM_u_stroke_color) + const vec4 stroke_color = props.stroke_color; +#else + const vec4 stroke_color = frag_stroke_color; +#endif +#if defined(HAS_UNIFORM_u_stroke_width) + const float stroke_width = props.stroke_width; +#else + const float stroke_width = frag_stroke_width; +#endif +#if defined(HAS_UNIFORM_u_stroke_opacity) + const float stroke_opacity = props.stroke_opacity; +#else + const float stroke_opacity = frag_stroke_opacity; +#endif + + const float extrude_length = length(frag_extrude); + const float antialiased_blur = -max(blur, frag_antialiasblur); + const float opacity_t = smoothstep(0.0, antialiased_blur, extrude_length - 1.0); + const float color_t = (stroke_width < 0.01) ? 0.0 : + smoothstep(antialiased_blur, 0.0, extrude_length - radius / (radius + stroke_width)); + + out_color = opacity_t * mix(color * opacity, stroke_color * stroke_opacity, color_t); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/clipping_mask.hpp b/include/mbgl/shaders/vulkan/clipping_mask.hpp new file mode 100644 index 000000000..feab7b024 --- /dev/null +++ b/include/mbgl/shaders/vulkan/clipping_mask.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace shaders { + +struct ClipUBO { + matf4 matrix; + std::uint32_t stencil_ref; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "ClippingMaskProgram"; + + static constexpr std::array uniforms{}; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + layout(location = 0) in vec2 position; + + layout(push_constant) uniform constants { + mat4 matrix; + } constant; + + void main() { + gl_Position = constant.matrix * vec4(position, 0.0, 1.0); + gl_Position.y *= -1.0; + } + )"; + + static constexpr auto fragment = R"( + layout(location = 0) out vec4 outColor; + + void main() { + outColor = vec4(0.0f); + } + )"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/collision.hpp b/include/mbgl/shaders/vulkan/collision.hpp new file mode 100644 index 000000000..4cc2f8d2e --- /dev/null +++ b/include/mbgl/shaders/vulkan/collision.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "CollisionBoxShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + 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 = 4) in vec2 in_shift; + +layout(set = 0, binding = 1) uniform CollisionBoxUBO { + mat4 matrix; + vec2 extrude_scale; + float overscale_factor; + float pad1; +} drawable; + +layout(location = 0) out float frag_placed; +layout(location = 1) out float frag_notUsed; + +void main() { + + vec4 projectedPoint = drawable.matrix * vec4(in_anchor_position, 0.0, 1.0); + float camera_to_anchor_distance = projectedPoint.w; + float collision_perspective_ratio = clamp( + 0.5 + 0.5 * (global.camera_to_center_distance / camera_to_anchor_distance), + 0.0, // Prevents oversized near-field boxes in pitched/overzoomed tiles + 4.0); + + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.xy += (in_extrude + in_shift) * drawable.extrude_scale * gl_Position.w * collision_perspective_ratio; + gl_Position.y *= -1.0; + + frag_placed = in_placed.x; + frag_notUsed = in_placed.y; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in float frag_placed; +layout(location = 1) in float frag_notUsed; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform CollisionBoxUBO { + mat4 matrix; + vec2 extrude_scale; + float overscale_factor; + float pad1; +} drawable; + +void main() { + + float alpha = 0.5; + + // Red = collision, hide label + vec4 color = vec4(1.0, 0.0, 0.0, 1.0) * alpha; + + // Blue = no collision, label is showing + if (frag_placed > 0.5) { + color = vec4(0.0, 0.0, 1.0, 0.5) * alpha; + } + + if (frag_notUsed > 0.5) { + // This box not used, fade it out + color *= 0.1; + } + + out_color = color; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "CollisionCircleShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + 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(set = 0, binding = 1) uniform CollisionCircleUBO { + mat4 matrix; + vec2 extrude_scale; + float overscale_factor; + float pad1; +} drawable; + +layout(location = 0) out float frag_placed; +layout(location = 1) out float frag_notUsed; +layout(location = 2) out float frag_radius; +layout(location = 3) out vec2 frag_extrude; +layout(location = 4) out vec2 frag_extrude_scale; + +void main() { + + vec4 projectedPoint = drawable.matrix * vec4(in_anchor_position, 0, 1); + float camera_to_anchor_distance = projectedPoint.w; + float collision_perspective_ratio = clamp( + 0.5 + 0.5 * (global.camera_to_center_distance / camera_to_anchor_distance), + 0.0, // Prevents oversized near-field circles in pitched/overzoomed tiles + 4.0); + + float padding_factor = 1.2; // Pad the vertices slightly to make room for anti-alias blur + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.xy += in_extrude * drawable.extrude_scale * padding_factor * gl_Position.w * collision_perspective_ratio; + gl_Position.y *= -1.0; + + frag_placed = in_placed.x; + frag_notUsed = in_placed.y; + frag_radius = abs(float(in_extrude.y)); // We don't pitch the circles, so both units of the extrusion vector are equal in magnitude to the radius + frag_extrude = in_extrude * padding_factor; + frag_extrude_scale = drawable.extrude_scale * global.camera_to_center_distance * collision_perspective_ratio; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in float frag_placed; +layout(location = 1) in float frag_notUsed; +layout(location = 2) in float frag_radius; +layout(location = 3) in vec2 frag_extrude; +layout(location = 4) in vec2 frag_extrude_scale; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform CollisionCircleUBO { + mat4 matrix; + vec2 extrude_scale; + float overscale_factor; + float pad1; +} drawable; + +void main() { + + float alpha = 0.5; + + // Red = collision, hide label + vec4 color = vec4(1.0, 0.0, 0.0, 1.0) * alpha; + + // Blue = no collision, label is showing + if (frag_placed > 0.5) { + color = vec4(0.0, 0.0, 1.0, 0.5) * alpha; + } + + if (frag_notUsed > 0.5) { + // This box not used, fade it out + color *= 0.2; + } + + float extrude_scale_length = length(frag_extrude_scale); + float extrude_length = length(frag_extrude) * extrude_scale_length; + float stroke_width = 15.0 * extrude_scale_length / drawable.overscale_factor; + float radius = frag_radius * extrude_scale_length; + + float distance_to_edge = abs(extrude_length - radius); + float opacity_t = smoothstep(-stroke_width, 0.0, -distance_to_edge); + + out_color = color * opacity_t; +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/common.hpp b/include/mbgl/shaders/vulkan/common.hpp new file mode 100644 index 000000000..c9909a692 --- /dev/null +++ b/include/mbgl/shaders/vulkan/common.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "Prelude"; + + static constexpr auto vertex = R"( + +#define M_PI 3.1415926535897932384626433832795 + +// The maximum allowed miter limit is 2.0 at the moment. the extrude normal is stored +// in a byte (-128..127). We scale regular normals up to length 63, but there are also +// "special" normals that have a bigger length (of up to 126 in this case). +#define LINE_NORMAL_SCALE (1.0 / (127 / 2)) + +// The attribute conveying progress along a line is scaled to [0, 2^15). +#define MAX_LINE_DISTANCE 32767.0 + +// Unpack a pair of values that have been packed into a single float. +// The packed values are assumed to be 8-bit unsigned integers, and are +// packed like so: +// packedValue = floor(input[0]) * 256 + input[1], +vec2 unpack_float(const float packedValue) { + int packedIntValue = int(packedValue); + int v0 = packedIntValue / 256; + return vec2(v0, packedIntValue - v0 * 256); +} + +vec2 unpack_opacity(const float packedOpacity) { + int intOpacity = int(packedOpacity) / 2; + return vec2(float(intOpacity) / 127.0, mod(packedOpacity, 2.0)); +} + +// To minimize the number of attributes needed, we encode a 4-component +// color into a pair of floats (i.e. a vec2) as follows: +// [ floor(color.r * 255) * 256 + color.g * 255, +// floor(color.b * 255) * 256 + color.g * 255 ] +vec4 decode_color(const vec2 encodedColor) { + return vec4( + unpack_float(encodedColor[0]) / 255.0, + unpack_float(encodedColor[1]) / 255.0 + ); +} + +// Unpack a pair of paint values and interpolate between them. +float unpack_mix_float(const vec2 packedValue, const float t) { + return mix(packedValue[0], packedValue[1], t); +} + +// Unpack a pair of paint values and interpolate between them. +vec4 unpack_mix_color(const vec4 packedColors, const float t) { + vec4 minColor = decode_color(vec2(packedColors[0], packedColors[1])); + vec4 maxColor = decode_color(vec2(packedColors[2], packedColors[3])); + return mix(minColor, maxColor, t); +} + +// unpack pattern position +vec2 get_pattern_pos(const vec2 pixel_coord_upper, const vec2 pixel_coord_lower, + const vec2 pattern_size, const float tile_units_to_pixels, const vec2 pos) { + const vec2 offset = mod(mod(mod(pixel_coord_upper, pattern_size) * 256.0, pattern_size) * 256.0 + pixel_coord_lower, pattern_size); + return (tile_units_to_pixels * pos + offset) / pattern_size; +} + +layout(set = 0, binding = 0) uniform GlobalPaintParamsUBO { + vec2 pattern_atlas_texsize; + vec2 units_to_pixels; + vec2 world_size; + float camera_to_center_distance; + float symbol_fade_change; + float aspect_ratio; + float pixel_ratio; + float zoom; + float pad1; +} global; + +)"; + + static constexpr auto fragment = R"( + +#define M_PI 3.1415926535897932384626433832795 +#define SDF_PX 8.0 + +layout(set = 0, binding = 0) uniform GlobalPaintParamsUBO { + vec2 pattern_atlas_texsize; + vec2 units_to_pixels; + vec2 world_size; + float camera_to_center_distance; + float symbol_fade_change; + float aspect_ratio; + float pixel_ratio; + float zoom; + float pad1; +} global; + +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/debug.hpp b/include/mbgl/shaders/vulkan/debug.hpp new file mode 100644 index 000000000..0e2759e14 --- /dev/null +++ b/include/mbgl/shaders/vulkan/debug.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "DebugShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +layout(set = 0, binding = 1) uniform DebugUBO { + mat4 matrix; + vec4 color; + float overlay_scale; + float pad1, pad2, pad3; +} debug; + +layout(location = 0) out vec2 frag_uv; + +void main() { + + gl_Position = debug.matrix * vec4(in_position * debug.overlay_scale, 0, 1); + gl_Position.y *= -1.0; + + // This vertex shader expects a EXTENT x EXTENT quad, + // The UV co-ordinates for the overlay texture can be calculated using that knowledge + frag_uv = in_position / 8192.0; +} +)"; + + 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); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "WideVectorShader"; + + static constexpr std::array uniforms{}; + static constexpr std::array attributes{}; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"()"; + static constexpr auto fragment = R"()"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/fill.hpp b/include/mbgl/shaders/vulkan/fill.hpp new file mode 100644 index 000000000..4ba9dc3f9 --- /dev/null +++ b/include/mbgl/shaders/vulkan/fill.hpp @@ -0,0 +1,1097 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "FillShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 1) in vec4 in_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 2) in vec2 in_opacity; +#endif + +layout(set = 0, binding = 1) uniform FillDrawableUBO { + mat4 matrix; +} drawable; + +layout(set = 0, binding = 3) uniform FillInterpolateUBO { + float color_t; + float opacity_t; +} interp; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 0) out vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 1) out lowp float frag_opacity; +#endif + +void main() { + +#if !defined(HAS_UNIFORM_u_color) + frag_color = vec4(unpack_mix_color(in_color, interp.color_t)); +#endif + +#if !defined(HAS_UNIFORM_u_opacity) + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.y *= -1.0; +} +)"; + + static constexpr auto fragment = R"( + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 0) in vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 1) in lowp float frag_opacity; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 4) uniform FillEvaluatedPropsUBO { + vec4 color; + vec4 outline_color; + float opacity; + float fade; + float from_scale; + float to_scale; +} props; + +void main() { + +#if defined(HAS_UNIFORM_u_color) + const vec4 color = props.color; +#else + const vec4 color = frag_color; +#endif + +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = props.opacity; +#else + const float opacity = frag_opacity; +#endif + + out_color = color * opacity; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "FillOutlineShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +#if !defined(HAS_UNIFORM_u_outline_color) +layout(location = 1) in vec4 in_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 2) in vec2 in_opacity; +#endif + +layout(set = 0, binding = 1) uniform FillDrawableUBO { + mat4 matrix; +} drawable; + +layout(set = 0, binding = 3) uniform FillInterpolateUBO { + float color_t; + float opacity_t; +} interp; + +#if !defined(HAS_UNIFORM_u_outline_color) +layout(location = 0) out vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 1) out lowp float frag_opacity; +#endif + +layout(location = 2) out vec2 frag_position; + +void main() { + +#if !defined(HAS_UNIFORM_u_outline_color) + frag_color = vec4(unpack_mix_color(in_color, interp.color_t)); +#endif + +#if !defined(HAS_UNIFORM_u_opacity) + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.y *= -1.0; + + frag_position = (gl_Position.xy / gl_Position.w + 1.0) / 2.0 * global.world_size; +} +)"; + + static constexpr auto fragment = R"( + +#if !defined(HAS_UNIFORM_u_outline_color) +layout(location = 0) in vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 1) in float frag_opacity; +#endif + +layout(location = 2) in vec2 frag_position; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 4) uniform FillEvaluatedPropsUBO { + vec4 color; + vec4 outline_color; + float opacity; + float fade; + float from_scale; + float to_scale; +} props; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_outline_color) + const vec4 color = props.outline_color; +#else + const vec4 color = frag_color; +#endif + +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = props.opacity; +#else + const float opacity = frag_opacity; +#endif + + float dist = length(frag_position - gl_FragCoord.xy); + float alpha = 1.0 - smoothstep(0.0, 1.0, dist); + out_color = color * opacity * alpha; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "FillPatternShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 1) in mediump vec4 in_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 2) in mediump vec4 in_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 3) in vec2 in_opacity; +#endif + +layout(set = 0, binding = 1) uniform FillPatternDrawableUBO { + mat4 matrix; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + vec2 texsize; + float tile_ratio; + float pad; +} drawable; + +layout(set = 0, binding = 2) uniform FillPatternTilePropsUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 0, binding = 3) uniform FillPatternInterpolateUBO { + float pattern_from_t; + float pattern_to_t; + float opacity_t; +} interp; + +layout(set = 0, binding = 4) uniform FillEvaluatedPropsUBO { + vec4 color; + vec4 outline_color; + float opacity; + float fade; + float from_scale; + float to_scale; +} props; + +layout(location = 0) out vec2 frag_pos_a; +layout(location = 1) out vec2 frag_pos_b; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 2) out mediump vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 3) out mediump vec4 frag_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 4) out lowp float frag_opacity; +#endif + +void main() { + +#if defined(HAS_UNIFORM_u_pattern_from) + const vec4 frag_pattern_from = tile.pattern_from; +#else + frag_pattern_from = in_pattern_from; +#endif + +#if defined(HAS_UNIFORM_u_pattern_to) + const vec4 frag_pattern_to = tile.pattern_to; +#else + frag_pattern_to = in_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + + const vec2 pattern_tl_a = frag_pattern_from.xy; + const vec2 pattern_br_a = frag_pattern_from.zw; + const vec2 pattern_tl_b = frag_pattern_to.xy; + const vec2 pattern_br_b = frag_pattern_to.zw; + + const float pixelRatio = global.pixel_ratio; + const float tileZoomRatio = drawable.tile_ratio; + const float fromScale = props.from_scale; + const float toScale = props.to_scale; + + const vec2 display_size_a = vec2((pattern_br_a.x - pattern_tl_a.x) / pixelRatio, (pattern_br_a.y - pattern_tl_a.y) / pixelRatio); + const vec2 display_size_b = vec2((pattern_br_b.x - pattern_tl_b.x) / pixelRatio, (pattern_br_b.y - pattern_tl_b.y) / pixelRatio); + + frag_pos_a = get_pattern_pos(drawable.pixel_coord_upper, drawable.pixel_coord_lower, fromScale * display_size_a, tileZoomRatio, in_position), + frag_pos_b = get_pattern_pos(drawable.pixel_coord_upper, drawable.pixel_coord_lower, toScale * display_size_b, tileZoomRatio, in_position), + + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.y *= -1.0; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_pos_a; +layout(location = 1) in vec2 frag_pos_b; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 2) in vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 3) in vec4 frag_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 4) in lowp float frag_opacity; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform FillPatternDrawableUBO { + mat4 matrix; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + vec2 texsize; + float tile_ratio; + float pad; +} drawable; + +layout(set = 0, binding = 2) uniform FillPatternTilePropsUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 0, binding = 4) uniform FillEvaluatedPropsUBO { + vec4 color; + vec4 outline_color; + float opacity; + float fade; + float from_scale; + float to_scale; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_pattern_from) + const vec4 pattern_from = tile.pattern_from; +#else + const vec4 pattern_from = frag_pattern_from; +#endif + +#if defined(HAS_UNIFORM_u_pattern_to) + const vec4 pattern_to = tile.pattern_to; +#else + const vec4 pattern_to = frag_pattern_to; +#endif + +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = props.opacity; +#else + const float opacity = frag_opacity; +#endif + + const vec2 pattern_tl_a = pattern_from.xy; + const vec2 pattern_br_a = pattern_from.zw; + const vec2 pattern_tl_b = pattern_to.xy; + const vec2 pattern_br_b = pattern_to.zw; + + const vec2 imagecoord = mod(frag_pos_a, 1.0); + const vec2 pos = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, imagecoord); + const vec4 color1 = texture(image0_sampler, pos); + + const vec2 imagecoord_b = mod(frag_pos_b, 1.0); + const vec2 pos2 = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, imagecoord_b); + const vec4 color2 = texture(image0_sampler, pos2); + + out_color = mix(color1, color2, props.fade) * opacity; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "FillOutlinePatternShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 1) in mediump vec4 in_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 2) in mediump vec4 in_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 3) in vec2 in_opacity; +#endif + +layout(set = 0, binding = 1) uniform FillOutlinePatternDrawableUBO { + mat4 matrix; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + vec2 texsize; + float tile_ratio; + float pad; +} drawable; + +layout(set = 0, binding = 2) uniform FillOutlinePatternTilePropsUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 0, binding = 3) uniform FillOutlinePatternInterpolateUBO { + float pattern_from_t; + float pattern_to_t; + float opacity_t; +} interp; + +layout(set = 0, binding = 4) uniform FillEvaluatedPropsUBO { + vec4 color; + vec4 outline_color; + float opacity; + float fade; + float from_scale; + float to_scale; +} props; + +layout(location = 0) out vec2 frag_pos_a; +layout(location = 1) out vec2 frag_pos_b; +layout(location = 2) out vec2 frag_pos; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 3) out mediump vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 4) out mediump vec4 frag_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) out lowp float frag_opacity; +#endif + +void main() { + +#if defined(HAS_UNIFORM_u_pattern_from) + const vec4 frag_pattern_from = tile.pattern_from; +#else + frag_pattern_from = in_pattern_from; +#endif + +#if defined(HAS_UNIFORM_u_pattern_to) + const vec4 frag_pattern_to = tile.pattern_to; +#else + frag_pattern_to = in_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + + const vec2 pattern_tl_a = frag_pattern_from.xy; + const vec2 pattern_br_a = frag_pattern_from.zw; + const vec2 pattern_tl_b = frag_pattern_to.xy; + const vec2 pattern_br_b = frag_pattern_to.zw; + + const float pixelRatio = global.pixel_ratio; + const float tileZoomRatio = drawable.tile_ratio; + const float fromScale = props.from_scale; + const float toScale = props.to_scale; + + const vec2 display_size_a = vec2((pattern_br_a.x - pattern_tl_a.x) / pixelRatio, (pattern_br_a.y - pattern_tl_a.y) / pixelRatio); + const vec2 display_size_b = vec2((pattern_br_b.x - pattern_tl_b.x) / pixelRatio, (pattern_br_b.y - pattern_tl_b.y) / pixelRatio); + + const vec2 position2 = in_position.xy; + vec4 position = drawable.matrix * vec4(in_position, 0.0, 1.0); + position.y *= -1.0; + + frag_pos_a = get_pattern_pos(drawable.pixel_coord_upper, drawable.pixel_coord_lower, fromScale * display_size_a, tileZoomRatio, position2), + frag_pos_b = get_pattern_pos(drawable.pixel_coord_upper, drawable.pixel_coord_lower, toScale * display_size_b, tileZoomRatio, position2), + frag_pos = (position.xy / position.w + 1.0) / 2.0 * global.world_size; + + gl_Position = position; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_pos_a; +layout(location = 1) in vec2 frag_pos_b; +layout(location = 2) in vec2 frag_pos; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 3) in vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 4) in vec4 frag_pattern_to; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) in lowp float frag_opacity; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform FillOutlinePatternDrawableUBO { + mat4 matrix; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + vec2 texsize; + float tile_ratio; + float pad; +} drawable; + +layout(set = 0, binding = 2) uniform FillOutlinePatternTilePropsUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 0, binding = 4) uniform FillEvaluatedPropsUBO { + vec4 color; + vec4 outline_color; + float opacity; + float fade; + float from_scale; + float to_scale; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_pattern_from) + const vec4 pattern_from = tile.pattern_from; +#else + const vec4 pattern_from = frag_pattern_from; +#endif + +#if defined(HAS_UNIFORM_u_pattern_to) + const vec4 pattern_to = tile.pattern_to; +#else + const vec4 pattern_to = frag_pattern_to; +#endif + +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = props.opacity; +#else + const float opacity = frag_opacity; +#endif + + const vec2 pattern_tl_a = pattern_from.xy; + const vec2 pattern_br_a = pattern_from.zw; + const vec2 pattern_tl_b = pattern_to.xy; + const vec2 pattern_br_b = pattern_to.zw; + + const vec2 imagecoord = mod(frag_pos_a, 1.0); + const vec2 pos = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, imagecoord); + const vec4 color1 = texture(image0_sampler, pos); + + const vec2 imagecoord_b = mod(frag_pos_b, 1.0); + const vec2 pos2 = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, imagecoord_b); + const vec4 color2 = texture(image0_sampler, pos2); + + const float dist = length(frag_pos - gl_FragCoord.xy); + const float alpha = 1.0 - smoothstep(0.0, 1.0, dist); + + out_color = mix(color1, color2, props.fade) * alpha * opacity; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "FillOutlineTriangulatedShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_pos_normal; +layout(location = 1) in vec4 in_data; + +layout(set = 0, binding = 1) uniform FillOutlineTriangulatedDrawableUBO { + mat4 matrix; + float ratio; + float pad1, pad2, pad3; +} drawable; + +layout(location = 0) out float frag_width2; +layout(location = 1) out vec2 frag_normal; +layout(location = 2) out float frag_gamma_scale; + +void main() { + // the distance over which the line edge fades out. + // Retina devices need a smaller distance to avoid aliasing. + float ANTIALIASING = 1.0 / DEVICE_PIXEL_RATIO / 2.0; + + const vec2 a_extrude = in_data.xy - 128.0; + const vec2 pos = floor(in_pos_normal * 0.5); + + // x is 1 if it's a round cap, 0 otherwise + // y is 1 if the normal points up, and -1 if it points down + // 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); + + // 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 outset = halfwidth + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); + + // Scale the extrusion vector down to a normal and then up by the line width + // of this vertex. + 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; + + // 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; + +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in float frag_width2; +layout(location = 1) in vec2 frag_normal; +layout(location = 2) in float frag_gamma_scale; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 4) uniform FillEvaluatedPropsUBO { + vec4 color; + vec4 outline_color; + float opacity; + float fade; + float from_scale; + float to_scale; +} props; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + // Calculate the distance of the pixel from the line in pixels. + const float dist = length(frag_normal) * frag_width2; + + // Calculate the antialiasing fade factor. This is either when fading in the + // line in case of an offset line (`v_width2.y`) or when fading out (`v_width2.x`) + 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)); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "FillExtrusionShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec4 in_normal_ed; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 2) in vec4 in_color; +#endif + +#if !defined(HAS_UNIFORM_u_base) +layout(location = 3) in vec2 in_base; +#endif + +#if !defined(HAS_UNIFORM_u_height) +layout(location = 4) in vec2 in_height; +#endif + +layout(set = 0, binding = 1) uniform FillExtrusionDrawableUBO { + mat4 matrix; + vec2 texsize; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + float height_factor; + float tile_ratio; +} drawable; + +layout(set = 0, binding = 2) uniform FillExtrusionPropsUBO { + vec4 color; + vec4 light_color_pad; + vec4 light_position_base; + float height; + float light_intensity; + float vertical_gradient; + float opacity; + float fade; + float from_scale; + float to_scale; + float pad2; +} props; + +layout(set = 0, binding = 4) uniform FillExtrusionInterpolateUBO { + float base_t; + float height_t; + float color_t; + float pattern_from_t; + float pattern_to_t; + float pad1, pad2, pad3; +} interp; + +layout(location = 0) out mediump vec4 frag_color; + +void main() { + +#if defined(HAS_UNIFORM_u_base) + const float base = props.light_position_base.w; +#else + const float base = max(unpack_mix_float(in_base, interp.base_t), 0.0); +#endif + +#if defined(HAS_UNIFORM_u_height) + const float height = props.height; +#else + const float height = max(unpack_mix_float(in_height, interp.height_t), 0.0); +#endif + +#if defined(HAS_UNIFORM_u_color) + vec4 color = props.color; +#else + vec4 color = unpack_mix_color(in_color, interp.color_t); +#endif + + const vec3 normal = in_normal_ed.xyz; + const float t = mod(normal.x, 2.0); + const float z = t != 0.0 ? height : base; + + gl_Position = drawable.matrix * vec4(in_position, z, 1.0); + gl_Position.y *= -1.0; + +#if defined(OVERDRAW_INSPECTOR) + frag_color = vec4(1.0); + return; +#endif + + // Relative luminance (how dark/bright is the surface color?) + const float luminance = color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722; + + vec4 vcolor = vec4(0.0, 0.0, 0.0, 1.0); + + // Add slight ambient lighting so no extrusions are totally black + color += vec4(0.03, 0.03, 0.03, 1.0); + + // Calculate cos(theta), where theta is the angle between surface normal and diffuse light ray + const float directionalFraction = clamp(dot(normal / 16384.0, props.light_position_base.xyz), 0.0, 1.0); + + // Adjust directional so that the range of values for highlight/shading is + // narrower with lower light intensity and with lighter/brighter surface colors + const float minDirectional = 1.0 - props.light_intensity; + const float maxDirectional = max(1.0 - luminance + props.light_intensity, 1.0); + float directional = mix(minDirectional, maxDirectional, directionalFraction); + + // Add gradient along z axis of side surfaces + if (normal.y != 0.0) { + // This avoids another branching statement, but multiplies by a constant of 0.84 if no + // vertical gradient, and otherwise calculates the gradient based on base + height + // TODO: If we're optimizing to the level of avoiding branches, we should pre-compute + // the square root when height is a uniform. + const float fMin = mix(0.7, 0.98, 1.0 - props.light_intensity); + const float factor = clamp((t + base) * pow(height / 150.0, 0.5), fMin, 1.0); + directional *= (1.0 - props.vertical_gradient) + (props.vertical_gradient * factor); + } + + // Assign final color based on surface + ambient light color, diffuse light directional, + // and light color with lower bounds adjusted to hue of light so that shading is tinted + // with the complementary (opposite) color to the light color + const vec3 light_color = props.light_color_pad.rgb; + const vec3 minLight = mix(vec3(0.0), vec3(0.3), 1.0 - light_color.rgb); + vcolor += vec4(clamp(color.rgb * directional * light_color.rgb, minLight, vec3(1.0)), 0.0); + + frag_color = vcolor * props.opacity; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec4 frag_color; +layout(location = 0) out vec4 out_color; + +void main() { + out_color = frag_color; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "FillExtrusionPatternShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec4 in_normal_ed; + +#if !defined(HAS_UNIFORM_u_base) +layout(location = 2) in vec2 in_base; +#endif + +#if !defined(HAS_UNIFORM_u_height) +layout(location = 3) in vec2 in_height; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 4) in vec4 in_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 5) in vec4 in_pattern_to; +#endif + +layout(set = 0, binding = 1) uniform FillExtrusionDrawableUBO { + mat4 matrix; + vec2 texsize; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + float height_factor; + float tile_ratio; +} drawable; + +layout(set = 0, binding = 2) uniform FillExtrusionPropsUBO { + vec4 color; + vec4 light_color_pad; + vec4 light_position_base; + float height; + float light_intensity; + float vertical_gradient; + float opacity; + float fade; + float from_scale; + float to_scale; + float pad2; +} props; + +layout(set = 0, binding = 3) uniform FillExtrusionTilePropsUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 0, binding = 4) uniform FillExtrusionInterpolateUBO { + float base_t; + float height_t; + float color_t; + float pattern_from_t; + float pattern_to_t; + float pad1, pad2, pad3; +} interp; + +layout(location = 0) out mediump vec4 frag_lighting; +layout(location = 1) out mediump vec2 frag_pos_a; +layout(location = 2) out mediump vec2 frag_pos_b; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 3) out mediump vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 4) out mediump vec4 frag_pattern_to; +#endif + +void main() { + +#if defined(HAS_UNIFORM_u_base) + const float base = props.light_position_base.w; +#else + const float base = max(unpack_mix_float(in_base, interp.base_t), 0.0); +#endif + +#if defined(HAS_UNIFORM_u_height) + const float height = props.height; +#else + const float height = max(unpack_mix_float(in_height, interp.height_t), 0.0); +#endif + + const vec3 normal = in_normal_ed.xyz; + const float edgedistance = in_normal_ed.w; + const float t = mod(normal.x, 2.0); + const float z = t != 0.0 ? height : base; + + gl_Position = drawable.matrix * vec4(in_position, z, 1.0); + gl_Position.y *= -1.0; + +#if defined(OVERDRAW_INSPECTOR) + frag_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_pattern_from) + const mediump vec4 pattern_from = tile.pattern_from; +#else + const mediump vec4 pattern_from = in_pattern_from; + frag_pattern_from = in_pattern_from; +#endif + +#if defined(HAS_UNIFORM_u_pattern_to) + const mediump vec4 pattern_to = tile.pattern_to; +#else + const mediump vec4 pattern_to = in_pattern_to; + frag_pattern_to = in_pattern_to; +#endif + + const vec2 pattern_tl_a = pattern_from.xy; + const vec2 pattern_br_a = pattern_from.zw; + const vec2 pattern_tl_b = pattern_to.xy; + const vec2 pattern_br_b = pattern_to.zw; + + const float pixelRatio = global.pixel_ratio; + const float tileZoomRatio = drawable.tile_ratio; + const float fromScale = props.from_scale; + const float toScale = props.to_scale; + + const vec2 display_size_a = vec2((pattern_br_a.x - pattern_tl_a.x) / pixelRatio, (pattern_br_a.y - pattern_tl_a.y) / pixelRatio); + const vec2 display_size_b = vec2((pattern_br_b.x - pattern_tl_b.x) / pixelRatio, (pattern_br_b.y - pattern_tl_b.y) / pixelRatio); + + const vec2 pos = normal.x == 1.0 && normal.y == 0.0 && normal.z == 16384.0 + ? vec2(in_position) // extrusion top + : vec2(edgedistance, z * drawable.height_factor); // extrusion side + + vec4 lighting = vec4(0.0, 0.0, 0.0, 1.0); + float directional = clamp(dot(normal / 16383.0, props.light_position_base.xyz), 0.0, 1.0); + directional = mix((1.0 - props.light_intensity), max((0.5 + props.light_intensity), 1.0), directional); + + if (normal.y != 0.0) { + // This avoids another branching statement, but multiplies by a constant of 0.84 if no vertical gradient, + // and otherwise calculates the gradient based on base + height + directional *= ( + (1.0 - props.vertical_gradient) + + (props.vertical_gradient * clamp((t + base) * pow(height / 150.0, 0.5), mix(0.7, 0.98, 1.0 - props.light_intensity), 1.0))); + } + + lighting.rgb += clamp(directional * props.light_color_pad.rgb, mix(vec3(0.0), vec3(0.3), 1.0 - props.light_color_pad.rgb), vec3(1.0)); + lighting *= props.opacity; + + frag_lighting = lighting; + frag_pos_a = get_pattern_pos(drawable.pixel_coord_upper, drawable.pixel_coord_lower, fromScale * display_size_a, tileZoomRatio, pos); + frag_pos_b = get_pattern_pos(drawable.pixel_coord_upper, drawable.pixel_coord_lower, toScale * display_size_b, tileZoomRatio, pos); + +#if !defined(HAS_UNIFORM_u_pattern_from) + frag_pattern_from = in_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) + frag_pattern_to = in_pattern_to; +#endif +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in mediump vec4 frag_lighting; +layout(location = 1) in mediump vec2 frag_pos_a; +layout(location = 2) in mediump vec2 frag_pos_b; + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 3) in mediump vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 4) in mediump vec4 frag_pattern_to; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform FillExtrusionDrawableUBO { + mat4 matrix; + vec2 texsize; + vec2 pixel_coord_upper; + vec2 pixel_coord_lower; + float height_factor; + float tile_ratio; +} drawable; + +layout(set = 0, binding = 2) uniform FillExtrusionPropsUBO { + vec4 color; + vec4 light_color_pad; + vec4 light_position_base; + float height; + float light_intensity; + float vertical_gradient; + float opacity; + float fade; + float from_scale; + float to_scale; + float pad2; +} props; + +layout(set = 0, binding = 3) uniform FillExtrusionTilePropsUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_pattern_from) + const vec4 pattern_from = tile.pattern_from; +#else + const vec4 pattern_from = frag_pattern_from; +#endif +#if defined(HAS_UNIFORM_u_pattern_to) + const vec4 pattern_to = tile.pattern_to; +#else + const vec4 pattern_to = frag_pattern_to; +#endif + + const vec2 pattern_tl_a = pattern_from.xy; + const vec2 pattern_br_a = pattern_from.zw; + const vec2 pattern_tl_b = pattern_to.xy; + const vec2 pattern_br_b = pattern_to.zw; + + const vec2 imagecoord = mod(frag_pos_a, 1.0); + const vec2 pos = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, imagecoord); + const vec4 color1 = texture(image0_sampler, pos); + + const vec2 imagecoord_b = mod(frag_pos_b, 1.0); + const vec2 pos2 = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, imagecoord_b); + const vec4 color2 = texture(image0_sampler, pos2); + + out_color = mix(color1, color2, props.fade) * frag_lighting; +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/heatmap.hpp b/include/mbgl/shaders/vulkan/heatmap.hpp new file mode 100644 index 000000000..6da0c53ff --- /dev/null +++ b/include/mbgl/shaders/vulkan/heatmap.hpp @@ -0,0 +1,200 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "HeatmapShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +// Effective "0" in the kernel density texture to adjust the kernel size to; +// this empirically chosen number minimizes artifacts on overlapping kernels +// for typical heatmap cases (assuming clustered source) +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; + +#if !defined(HAS_UNIFORM_u_weight) +layout(location = 1) in vec2 in_weight; +#endif + +#if !defined(HAS_UNIFORM_u_radius) +layout(location = 2) in vec2 in_radius; +#endif + +layout(set = 0, binding = 1) uniform HeatmapDrawableUBO { + mat4 matrix; + float extrude_scale; + float pad1; + vec2 pad2; +} drawable; + +layout(set = 0, binding = 2) uniform HeatmapEvaluatedPropsUBO { + float weight; + float radius; + float intensity; + float pad1; +} props; + +layout(set = 0, binding = 3) uniform HeatmapInterpolateUBO { + float weight_t; + float radius_t; + vec2 pad1; +} interp; + +layout(location = 0) out float frag_weight; +layout(location = 1) out lowp vec2 frag_extrude; + +void main() { + +#if defined(HAS_UNIFORM_u_weight) + const float weight = props.weight; +#else + const float weight = unpack_mix_float(in_weight, interp.weight_t); +#endif + +#if defined(HAS_UNIFORM_u_radius) + const float radius = props.radius; +#else + const float radius = unpack_mix_float(in_radius, interp.radius_t); +#endif + + // unencode the extrusion vector that we snuck into the a_pos vector + const vec2 unscaled_extrude = mod(in_position, 2.0) * 2.0 - 1.0; + + // This 'extrude' comes in ranging from [-1, -1], to [1, 1]. We'll use + // it to produce the vertices of a square mesh framing the point feature + // we're adding to the kernel density texture. We'll also pass it as + // a varying, so that the fragment shader can determine the distance of + // each fragment from the point feature. + // Before we do so, we need to scale it up sufficiently so that the + // kernel falls effectively to zero at the edge of the mesh. + // That is, we want to know S such that + // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO + // Which solves to: + // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 + const float S = sqrt(-2.0 * log(ZERO / weight / props.intensity / GAUSS_COEF)) / 3.0; + + // Pass the varying in units of radius + const vec2 extrude = S * unscaled_extrude; + + // Scale by radius and the zoom-based scale factor to produce actual + // mesh position + const vec2 scaled_extrude = extrude * radius * drawable.extrude_scale; + + // multiply a_pos by 0.5, since we had it * 2 in order to sneak + // in extrusion data + gl_Position = drawable.matrix * vec4(floor(in_position * 0.5) + scaled_extrude, 0, 1); + gl_Position.y *= -1.0; + + frag_weight = weight; + frag_extrude = extrude; +} +)"; + + static constexpr auto fragment = R"( + +// Gaussian kernel coefficient: 1 / sqrt(2 * PI) +#define GAUSS_COEF 0.3989422804014327 + +layout(location = 0) in float frag_weight; +layout(location = 1) in lowp vec2 frag_extrude; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 2) uniform HeatmapEvaluatedPropsUBO { + float weight; + float radius; + float intensity; + float pad1; +} props; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + // Kernel density estimation with a Gaussian kernel of size 5x5 + const float d = -0.5 * 3.0 * 3.0 * dot(frag_extrude, frag_extrude); + const float val = frag_weight * props.intensity * GAUSS_COEF * exp(d); + + out_color = vec4(val, 1.0, 1.0, 1.0); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "HeatmapTextureShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; + +layout(set = 0, binding = 1) uniform HeatmapTexturePropsUBO { + mat4 matrix; + float opacity; + float pad1, pad2, pad3; +} props; + +layout(location = 0) out vec2 frag_position; + +void main() { + + gl_Position = props.matrix * vec4(in_position * global.world_size, 0, 1); + gl_Position.y *= -1.0; + + frag_position = in_position; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_position; +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform HeatmapTexturePropsUBO { + mat4 matrix; + float opacity; + float pad1, pad2, pad3; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image_sampler; +layout(set = 1, binding = 1) uniform sampler2D color_ramp_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + const float t = texture(image_sampler, frag_position).r; + const vec4 color = texture(color_ramp_sampler, vec2(t, 0.5)); + out_color = color * props.opacity; +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/hillshade.hpp b/include/mbgl/shaders/vulkan/hillshade.hpp new file mode 100644 index 000000000..e583bf0c6 --- /dev/null +++ b/include/mbgl/shaders/vulkan/hillshade.hpp @@ -0,0 +1,227 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "HillshadePrepareShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec2 in_texture_position; + +layout(set = 0, binding = 1) uniform HillshadePrepareDrawableUBO { + mat4 matrix; + vec4 unpack; + vec2 dimension; + float zoom; + float maxzoom; +} drawable; + +layout(location = 0) out vec2 frag_position; + +void main() { + + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.y *= -1.0; + + const vec2 epsilon = vec2(1.0) / drawable.dimension; + const float scale = (drawable.dimension.x - 2.0) / drawable.dimension.x; + frag_position = in_texture_position / 8192.0 * scale + epsilon; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_position; +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform HillshadePrepareDrawableUBO { + mat4 matrix; + vec4 unpack; + vec2 dimension; + float zoom; + float maxzoom; +} drawable; + +layout(set = 1, binding = 0) uniform sampler2D image_sampler; + +float getElevation(vec2 coord, float bias, sampler2D image_sampler, vec4 unpack) { + // Convert encoded elevation value to meters + vec4 data = texture(image_sampler, coord) * 255.0; + data.a = -1.0; + return dot(data, unpack) / 4.0; +} + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + const vec2 epsilon = 1.0 / drawable.dimension; + + // queried pixels: + // +-----------+ + // | | | | + // | a | b | c | + // | | | | + // +-----------+ + // | | | | + // | d | e | f | + // | | | | + // +-----------+ + // | | | | + // | g | h | i | + // | | | | + // +-----------+ + + float a = getElevation(frag_position + vec2(-epsilon.x, -epsilon.y), 0.0, image_sampler, drawable.unpack); + float b = getElevation(frag_position + vec2(0, -epsilon.y), 0.0, image_sampler, drawable.unpack); + float c = getElevation(frag_position + vec2(epsilon.x, -epsilon.y), 0.0, image_sampler, drawable.unpack); + float d = getElevation(frag_position + vec2(-epsilon.x, 0), 0.0, image_sampler, drawable.unpack); + //float e = getElevation(frag_position, 0.0, image_sampler, drawable.unpack); + float f = getElevation(frag_position + vec2(epsilon.x, 0), 0.0, image_sampler, drawable.unpack); + float g = getElevation(frag_position + vec2(-epsilon.x, epsilon.y), 0.0, image_sampler, drawable.unpack); + float h = getElevation(frag_position + vec2(0, epsilon.y), 0.0, image_sampler, drawable.unpack); + float i = getElevation(frag_position + vec2(epsilon.x, epsilon.y), 0.0, image_sampler, drawable.unpack); + + // here we divide the x and y slopes by 8 * pixel size + // where pixel size (aka meters/pixel) is: + // circumference of the world / (pixels per tile * number of tiles) + // which is equivalent to: 8 * 40075016.6855785 / (512 * pow(2, u_zoom)) + // which can be reduced to: pow(2, 19.25619978527 - u_zoom) + // we want to vertically exaggerate the hillshading though, because otherwise + // it is barely noticeable at low zooms. to do this, we multiply this by some + // scale factor pow(2, (u_zoom - u_maxzoom) * a) where a is an arbitrary value + // Here we use a=0.3 which works out to the expression below. see + // nickidlugash's awesome breakdown for more info + // https://github.com/mapbox/mapbox-gl-js/pull/5286#discussion_r148419556 + float exaggeration = drawable.zoom < 2.0 ? 0.4 : drawable.zoom < 4.5 ? 0.35 : 0.3; + + vec2 deriv = vec2( + (c + f + f + i) - (a + d + d + g), + (g + h + h + i) - (a + b + b + c) + ) / pow(2.0, (drawable.zoom - drawable.maxzoom) * exaggeration + 19.2562 - drawable.zoom); + + out_color = clamp(vec4( + deriv.x / 2.0 + 0.5, + deriv.y / 2.0 + 0.5, + 1.0, + 1.0), 0.0, 1.0); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "HillshadeShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec2 in_texture_position; + +layout(set = 0, binding = 1) uniform HillshadeDrawableUBO { + mat4 matrix; + vec2 latrange; + vec2 light; +} drawable; + +layout(location = 0) out vec2 frag_position; + +void main() { + + gl_Position = drawable.matrix * vec4(in_position, 0.0, 1.0); + gl_Position.y *= -1.0; + + frag_position = vec2(in_texture_position) / 8192.0; + frag_position.y = 1.0 - frag_position.y; // TODO check this. prepare should ignore the flip +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_position; +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform HillshadeDrawableUBO { + mat4 matrix; + vec2 latrange; + vec2 light; +} drawable; + +layout(set = 0, binding = 2) uniform HillshadeEvaluatedPropsUBO { + vec4 highlight; + vec4 shadow; + vec4 accent; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + vec4 pixel = texture(image_sampler, frag_position); + + vec2 deriv = pixel.rg * 2.0 - 1.0; + + // We divide the slope by a scale factor based on the cosin of the pixel's approximate latitude + // to account for mercator projection distortion. see #4807 for details + float scaleFactor = cos(radians((drawable.latrange[0] - drawable.latrange[1]) * frag_position.y + drawable.latrange[1])); + // We also multiply the slope by an arbitrary z-factor of 1.25 + float slope = atan(1.25 * length(deriv) / scaleFactor); + float aspect = deriv.x != 0.0 ? atan(deriv.y, -deriv.x) : M_PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); + + float intensity = drawable.light.x; + // We add PI to make this property match the global light object, which adds PI/2 to the light's azimuthal + // position property to account for 0deg corresponding to north/the top of the viewport in the style spec + // and the original shader was written to accept (-illuminationDirection - 90) as the azimuthal. + float azimuth = drawable.light.y + M_PI; + + // We scale the slope exponentially based on intensity, using a calculation similar to + // the exponential interpolation function in the style spec: + // https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/expression/definitions/interpolate.js#L217-L228 + // so that higher intensity values create more opaque hillshading. + float base = 1.875 - intensity * 1.75; + float maxValue = 0.5 * M_PI; + float scaledSlope = intensity != 0.5 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope; + + // The accent color is calculated with the cosine of the slope while the shade color is calculated with the sine + // so that the accent color's rate of change eases in while the shade color's eases out. + float accent = cos(scaledSlope); + // We multiply both the accent and shade color by a clamped intensity value + // so that intensities >= 0.5 do not additionally affect the color values + // while intensity values < 0.5 make the overall color more transparent. + vec4 accent_color = (1.0 - accent) * props.accent * clamp(intensity * 2.0, 0.0, 1.0); + float shade = abs(mod((aspect + azimuth) / M_PI + 0.5, 2.0) - 1.0); + vec4 shade_color = mix(props.shadow, props.highlight, shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); + vec4 color = accent_color * (1.0 - shade_color.a) + shade_color; + + out_color = color; +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/line.hpp b/include/mbgl/shaders/vulkan/line.hpp new file mode 100644 index 000000000..a0672c49d --- /dev/null +++ b/include/mbgl/shaders/vulkan/line.hpp @@ -0,0 +1,1070 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "LineShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static constexpr std::array textures{}; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_pos_normal; +layout(location = 1) in vec4 in_data; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 2) in vec4 in_color; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 3) in vec2 in_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 4) in vec2 in_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_gapwidth) +layout(location = 5) in vec2 in_gapwidth; +#endif + +#if !defined(HAS_UNIFORM_u_offset) +layout(location = 6) in vec2 in_offset; +#endif + +#if !defined(HAS_UNIFORM_u_width) +layout(location = 7) in vec2 in_width; +#endif + +layout(set = 0, binding = 1) uniform LineDrawableUBO { + mat4 matrix; + mediump float ratio; + float pad1, pad2, pad3; +} drawable; + +layout(set = 0, binding = 2) uniform LineInterpolationUBO { + float color_t; + float blur_t; + float opacity_t; + float gapwidth_t; + float offset_t; + float width_t; + float pad1, pad2; +} interp; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +layout(location = 0) out lowp vec2 frag_normal; +layout(location = 1) out lowp vec2 frag_width2; +layout(location = 2) out lowp float frag_gamma_scale; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 3) out lowp vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) out lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) out lowp float frag_opacity; +#endif + +void main() { + +#ifndef HAS_UNIFORM_u_color + frag_color = unpack_mix_color(in_color, interp.color_t); +#endif + +#ifndef HAS_UNIFORM_u_blur + frag_blur = unpack_mix_float(in_blur, interp.blur_t); +#endif + +#ifndef HAS_UNIFORM_u_opacity + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + +#ifndef HAS_UNIFORM_u_gapwidth + const mediump float gapwidth = unpack_mix_float(in_gapwidth, interp.gapwidth_t) / 2.0; +#else + const mediump float gapwidth = props.gapwidth / 2.0; +#endif + +#ifndef HAS_UNIFORM_u_offset + const lowp float offset = unpack_mix_float(in_offset, interp.offset_t) * -1.0; +#else + const lowp float offset = props.offset * -1.0; +#endif + +#ifndef HAS_UNIFORM_u_width + mediump float width = unpack_mix_float(in_width, interp.width_t); +#else + mediump float width = props.width; +#endif + + // the distance over which the line edge fades out. + // Retina devices need a smaller distance to avoid aliasing. + const float ANTIALIASING = 1.0 / DEVICE_PIXEL_RATIO / 2.0; + + vec2 a_extrude = in_data.xy - 128.0; + float a_direction = mod(in_data.z, 4.0) - 1.0; + vec2 pos = floor(in_pos_normal * 0.5); + + // x is 1 if it's a round cap, 0 otherwise + // y is 1 if the normal points up, and -1 if it points down + // 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); + + // these transformations used to be applied in the JS and native code bases. + // moved them into the shader for clarity and simplicity. + float halfwidth = width / 2.0; + + float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); + float outset = gapwidth + halfwidth * (gapwidth > 0.0 ? 2.0 : 1.0) + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); + + // Scale the extrusion vector down to a normal and then up by the line width + // of this vertex. + mediump vec2 dist = outset * a_extrude * LINE_NORMAL_SCALE; + + // Calculate the offset when drawing a line that is to the side of the actual line. + // We do this by creating a vector that points towards the extrude, but rotate + // it when we're drawing round end points (a_direction = -1 or 1) since their + // extrude vector points in another direction. + mediump float u = 0.5 * a_direction; + mediump float t = 1.0 - abs(u); + mediump vec2 offset2 = offset * a_extrude * LINE_NORMAL_SCALE * frag_normal.y * mat2(t, -u, u, t); + + vec4 projected_extrude = drawable.matrix * vec4(dist / drawable.ratio, 0.0, 0.0); + gl_Position = drawable.matrix * vec4(pos + offset2 / drawable.ratio, 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 * global.units_to_pixels); + frag_gamma_scale = extrude_length_without_perspective / extrude_length_with_perspective; + + frag_width2 = vec2(outset, inset); +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in lowp vec2 frag_normal; +layout(location = 1) in lowp vec2 frag_width2; +layout(location = 2) in lowp float frag_gamma_scale; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 3) in lowp vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) in lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) in lowp float frag_opacity; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +void main() { + +#ifdef OVERDRAW_INSPECTOR + out_color = vec4(1.0); + return; +#endif + +#ifdef HAS_UNIFORM_u_color + highp vec4 color = props.color; +#else + highp vec4 color = frag_color; +#endif + +#ifdef HAS_UNIFORM_u_blur + lowp float blur = props.blur; +#else + lowp float blur = frag_blur; +#endif + +#ifdef HAS_UNIFORM_u_opacity + lowp float opacity = props.opacity; +#else + lowp float opacity = frag_opacity; +#endif + + // Calculate the distance of the pixel from the line in pixels. + float dist = length(frag_normal) * frag_width2.s; + + // Calculate the antialiasing fade factor. This is either when fading in + // the line in case of an offset line (frag_width2.t) or when fading out + // (frag_width2.s) + float blur2 = (blur + 1.0 / DEVICE_PIXEL_RATIO) * frag_gamma_scale; + float alpha = clamp(min(dist - (frag_width2.t - blur2), frag_width2.s - dist) / blur2, 0.0, 1.0); + + out_color = color * (alpha * opacity); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "LineGradientShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_pos_normal; +layout(location = 1) in vec4 in_data; + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 2) in vec2 in_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 3) in vec2 in_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_gapwidth) +layout(location = 4) in vec2 in_gapwidth; +#endif + +#if !defined(HAS_UNIFORM_u_offset) +layout(location = 5) in vec2 in_offset; +#endif + +#if !defined(HAS_UNIFORM_u_width) +layout(location = 6) in vec2 in_width; +#endif + +layout(set = 0, binding = 1) uniform LineDrawableUBO { + mat4 matrix; + mediump float ratio; + float pad1, pad2, pad3; +} drawable; + +layout(set = 0, binding = 2) uniform LineInterpolationUBO { + float color_t; + float blur_t; + float opacity_t; + float gapwidth_t; + float offset_t; + float width_t; + float pad1, pad2; +} interp; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +layout(location = 0) out lowp vec2 frag_normal; +layout(location = 1) out lowp vec2 frag_width2; +layout(location = 2) out lowp float frag_gamma_scale; +layout(location = 3) out float frag_lineprogress; + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) out lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) out lowp float frag_opacity; +#endif + +void main() { + +#ifndef HAS_UNIFORM_u_blur + frag_blur = unpack_mix_float(in_blur, interp.blur_t); +#endif + +#ifndef HAS_UNIFORM_u_opacity + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + +#ifndef HAS_UNIFORM_u_gapwidth + mediump float gapwidth = unpack_mix_float(in_gapwidth, interp.gapwidth_t) / 2.0; +#else + mediump float gapwidth = props.gapwidth / 2.0; +#endif + +#ifndef HAS_UNIFORM_u_offset + const lowp float offset = unpack_mix_float(in_offset, interp.offset_t) * -1.0; +#else + const lowp float offset = props.offset * -1.0; +#endif + +#ifndef HAS_UNIFORM_u_width + mediump float width = unpack_mix_float(in_width, interp.width_t); +#else + mediump float width = props.width; +#endif + + // the distance over which the line edge fades out. + // Retina devices need a smaller distance to avoid aliasing. + const float ANTIALIASING = 1.0 / DEVICE_PIXEL_RATIO / 2.0; + + vec2 a_extrude = in_data.xy - 128.0; + float a_direction = mod(in_data.z, 4.0) - 1.0; + vec2 pos = floor(in_pos_normal * 0.5); + frag_lineprogress = (floor(in_data.z / 4.0) + in_data.w * 64.0) * 2.0 / MAX_LINE_DISTANCE; + + // x is 1 if it's a round cap, 0 otherwise + // y is 1 if the normal points up, and -1 if it points down + // 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); + + // these transformations used to be applied in the JS and native code bases. + // moved them into the shader for clarity and simplicity. + float halfwidth = width / 2.0; + + float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); + float outset = gapwidth + halfwidth * (gapwidth > 0.0 ? 2.0 : 1.0) + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); + + // Scale the extrusion vector down to a normal and then up by the line width + // of this vertex. + mediump vec2 dist = outset * a_extrude * LINE_NORMAL_SCALE; + + // Calculate the offset when drawing a line that is to the side of the actual line. + // We do this by creating a vector that points towards the extrude, but rotate + // it when we're drawing round end points (a_direction = -1 or 1) since their + // extrude vector points in another direction. + mediump float u = 0.5 * a_direction; + mediump float t = 1.0 - abs(u); + mediump vec2 offset2 = offset * a_extrude * LINE_NORMAL_SCALE * frag_normal.y * mat2(t, -u, u, t); + + vec4 projected_extrude = drawable.matrix * vec4(dist / drawable.ratio, 0.0, 0.0); + gl_Position = drawable.matrix * vec4(pos + offset2 / drawable.ratio, 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 * global.units_to_pixels); + frag_gamma_scale = extrude_length_without_perspective / extrude_length_with_perspective; + + frag_width2 = vec2(outset, inset); +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in lowp vec2 frag_normal; +layout(location = 1) in lowp vec2 frag_width2; +layout(location = 2) in lowp float frag_gamma_scale; +layout(location = 3) in float frag_lineprogress; + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) in lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) in lowp float frag_opacity; +#endif + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +void main() { + +#ifdef OVERDRAW_INSPECTOR + out_color = vec4(1.0); + return; +#endif + +#ifdef HAS_UNIFORM_u_blur + lowp float blur = props.blur; +#else + lowp float blur = frag_blur; +#endif + +#ifdef HAS_UNIFORM_u_opacity + lowp float opacity = props.opacity; +#else + lowp float opacity = frag_opacity; +#endif + + // Calculate the distance of the pixel from the line in pixels. + float dist = length(frag_normal) * frag_width2.s; + + // Calculate the antialiasing fade factor. This is either when fading in + // the line in case of an offset line (frag_width2.t) or when fading out + // (frag_width2.s) + float blur2 = (blur + 1.0 / DEVICE_PIXEL_RATIO) * frag_gamma_scale; + float alpha = clamp(min(dist - (frag_width2.t - blur2), frag_width2.s - dist) / blur2, 0.0, 1.0); + + // For gradient lines, lineprogress is the ratio along the entire line, + // scaled to [0, 2^15), and the gradient ramp is stored in a texture. + const vec4 color = texture(image0_sampler, vec2(frag_lineprogress, 0.5)); + + out_color = color * (alpha * opacity); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "LinePatternShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_pos_normal; +layout(location = 1) in vec4 in_data; + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 2) in vec2 in_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 3) in vec2 in_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_gapwidth) +layout(location = 4) in vec2 in_gapwidth; +#endif + +#if !defined(HAS_UNIFORM_u_offset) +layout(location = 5) in vec2 in_offset; +#endif + +#if !defined(HAS_UNIFORM_u_width) +layout(location = 6) in vec2 in_width; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 7) in vec4 in_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 8) in vec4 in_pattern_to; +#endif + +layout(set = 0, binding = 1) uniform LineDrawableUBO { + mat4 matrix; + vec4 scale; + vec2 texsize; + float ratio; + float fade; +} drawable; + +layout(set = 0, binding = 2) uniform LineInterpolationUBO { + float blur_t; + float opacity_t; + float offset_t; + float gapwidth_t; + float width_t; + float pattern_from_t; + float pattern_to_t; + float pad1; +} interp; + +layout(set = 0, binding = 3) uniform LinePatternTilePropertiesUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +layout(location = 0) out lowp vec2 frag_normal; +layout(location = 1) out lowp vec2 frag_width2; +layout(location = 2) out lowp float frag_gamma_scale; +layout(location = 3) out float frag_linesofar; + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) out lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) out lowp float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 6) out mediump vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 7) out mediump vec4 frag_pattern_to; +#endif + +void main() { + +#ifndef HAS_UNIFORM_u_blur + frag_blur = unpack_mix_float(in_blur, interp.blur_t); +#endif + +#ifndef HAS_UNIFORM_u_opacity + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + +#ifndef HAS_UNIFORM_u_gapwidth + const mediump float gapwidth = unpack_mix_float(in_gapwidth, interp.gapwidth_t) / 2.0; +#else + const mediump float gapwidth = props.gapwidth / 2.0; +#endif + +#ifndef HAS_UNIFORM_u_offset + const lowp float offset = unpack_mix_float(in_offset, interp.offset_t) * -1.0; +#else + const lowp float offset = props.offset * -1.0; +#endif + +#ifndef HAS_UNIFORM_u_width + mediump float width = unpack_mix_float(in_width, interp.width_t); +#else + mediump float width = props.width; +#endif + +#ifndef HAS_UNIFORM_u_pattern_from + frag_pattern_from = in_pattern_from; +#endif + +#ifndef HAS_UNIFORM_u_pattern_to + frag_pattern_to = in_pattern_to; +#endif + + // the distance over which the line edge fades out. + // Retina devices need a smaller distance to avoid aliasing. + const float ANTIALIASING = 1.0 / DEVICE_PIXEL_RATIO / 2.0; + const float LINE_DISTANCE_SCALE = 2.0; + + vec2 a_extrude = in_data.xy - 128.0; + float a_direction = mod(in_data.z, 4.0) - 1.0; + vec2 pos = floor(in_pos_normal * 0.5); + + // x is 1 if it's a round cap, 0 otherwise + // y is 1 if the normal points up, and -1 if it points down + // 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); + + // these transformations used to be applied in the JS and native code bases. + // moved them into the shader for clarity and simplicity. + float halfwidth = width / 2.0; + + float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); + float outset = gapwidth + halfwidth * (gapwidth > 0.0 ? 2.0 : 1.0) + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); + + // Scale the extrusion vector down to a normal and then up by the line width + // of this vertex. + mediump vec2 dist = outset * a_extrude * LINE_NORMAL_SCALE; + + // Calculate the offset when drawing a line that is to the side of the actual line. + // We do this by creating a vector that points towards the extrude, but rotate + // it when we're drawing round end points (a_direction = -1 or 1) since their + // extrude vector points in another direction. + mediump float u = 0.5 * a_direction; + mediump float t = 1.0 - abs(u); + mediump vec2 offset2 = offset * a_extrude * LINE_NORMAL_SCALE * frag_normal.y * mat2(t, -u, u, t); + + vec4 projected_extrude = drawable.matrix * vec4(dist / drawable.ratio, 0.0, 0.0); + gl_Position = drawable.matrix * vec4(pos + offset2 / drawable.ratio, 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 * global.units_to_pixels); + frag_gamma_scale = extrude_length_without_perspective / extrude_length_with_perspective; + + frag_width2 = vec2(outset, inset); + frag_linesofar = (floor(in_data.z / 4.0) + in_data.w * 64.0) * LINE_DISTANCE_SCALE; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in lowp vec2 frag_normal; +layout(location = 1) in lowp vec2 frag_width2; +layout(location = 2) in lowp float frag_gamma_scale; +layout(location = 3) in float frag_linesofar; + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 4) in lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) in lowp float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_from) +layout(location = 6) in mediump vec4 frag_pattern_from; +#endif + +#if !defined(HAS_UNIFORM_u_pattern_to) +layout(location = 7) in mediump vec4 frag_pattern_to; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform LineDrawableUBO { + mat4 matrix; + vec4 scale; + vec2 texsize; + float ratio; + float fade; +} drawable; + +layout(set = 0, binding = 3) uniform LinePatternTilePropertiesUBO { + vec4 pattern_from; + vec4 pattern_to; +} tile; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +void main() { + +#ifdef OVERDRAW_INSPECTOR + out_color = vec4(1.0); + return; +#endif + +#ifdef HAS_UNIFORM_u_blur + const lowp float blur = props.blur; +#else + const lowp float blur = frag_blur; +#endif + +#ifdef HAS_UNIFORM_u_opacity + const lowp float opacity = props.opacity; +#else + const lowp float opacity = frag_opacity; +#endif + +#ifdef HAS_UNIFORM_u_pattern_from + const lowp vec4 pattern_from = tile.pattern_from; +#else + const lowp vec4 pattern_from = frag_pattern_from; +#endif + +#ifdef HAS_UNIFORM_u_pattern_to + const lowp vec4 pattern_to = tile.pattern_to; +#else + const lowp vec4 pattern_to = frag_pattern_to; +#endif + + const vec2 pattern_tl_a = pattern_from.xy; + const vec2 pattern_br_a = pattern_from.zw; + const vec2 pattern_tl_b = pattern_to.xy; + const vec2 pattern_br_b = pattern_to.zw; + + const float pixelRatio = drawable.scale.x; + const float tileZoomRatio = drawable.scale.y; + const float fromScale = drawable.scale.z; + const float toScale = drawable.scale.w; + + const vec2 display_size_a = vec2((pattern_br_a.x - pattern_tl_a.x) / pixelRatio, (pattern_br_a.y - pattern_tl_a.y) / pixelRatio); + const vec2 display_size_b = vec2((pattern_br_b.x - pattern_tl_b.x) / pixelRatio, (pattern_br_b.y - pattern_tl_b.y) / pixelRatio); + + const vec2 pattern_size_a = vec2(display_size_a.x * fromScale / tileZoomRatio, display_size_a.y); + const vec2 pattern_size_b = vec2(display_size_b.x * toScale / tileZoomRatio, display_size_b.y); + + // Calculate the distance of the pixel from the line in pixels. + const float dist = length(frag_normal) * frag_width2.x; + + // Calculate the antialiasing fade factor. This is either when fading in + // the line in case of an offset line (frag_width2.y) or when fading out + // (frag_width2.x) + const float blur2 = (blur + 1.0 / DEVICE_PIXEL_RATIO) * frag_gamma_scale; + const float alpha = clamp(min(dist - (frag_width2.y - blur2), frag_width2.x - dist) / blur2, 0.0, 1.0); + + const float x_a = mod(frag_linesofar / pattern_size_a.x, 1.0); + const float x_b = mod(frag_linesofar / pattern_size_b.x, 1.0); + + // frag_normal.y is 0 at the midpoint of the line, -1 at the lower edge, 1 at the upper edge + // we clamp the line width outset to be between 0 and half the pattern height plus padding (2.0) + // to ensure we don't sample outside the designated symbol on the sprite sheet. + // 0.5 is added to shift the component to be bounded between 0 and 1 for interpolation of + // the texture coordinate + const float y_a = 0.5 + (frag_normal.y * clamp(frag_width2.x, 0.0, (pattern_size_a.y + 2.0) / 2.0) / pattern_size_a.y); + const float y_b = 0.5 + (frag_normal.y * clamp(frag_width2.x, 0.0, (pattern_size_b.y + 2.0) / 2.0) / pattern_size_b.y); + const vec2 pos_a = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, vec2(x_a, y_a)); + const vec2 pos_b = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, vec2(x_b, y_b)); + + const vec4 color = mix(texture(image0_sampler, pos_a), texture(image0_sampler, pos_b), drawable.fade); + + out_color = color * (alpha * opacity); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "LineSDFShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_pos_normal; +layout(location = 1) in vec4 in_data; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 2) in vec4 in_color; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 3) in vec2 in_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 4) in vec2 in_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_gapwidth) +layout(location = 5) in vec2 in_gapwidth; +#endif + +#if !defined(HAS_UNIFORM_u_offset) +layout(location = 6) in vec2 in_offset; +#endif + +#if !defined(HAS_UNIFORM_u_width) +layout(location = 7) in vec2 in_width; +#endif + +#if !defined(HAS_UNIFORM_u_floorwidth) +layout(location = 8) in vec2 in_floorwidth; +#endif + +layout(set = 0, binding = 1) uniform LineSDFDrawableUBO { + mat4 matrix; + vec2 patternscale_a; + vec2 patternscale_b; + float ratio; + float tex_y_a; + float tex_y_b; + float sdfgamma; + float mix; + float pad1, pad2, pad3; +} drawable; + +layout(set = 0, binding = 2) uniform LineSDFInterpolationUBO { + float color_t; + float blur_t; + float opacity_t; + float gapwidth_t; + float offset_t; + float width_t; + float floorwidth_t; + float pad1; +} interp; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +layout(location = 0) out lowp vec2 frag_normal; +layout(location = 1) out lowp vec2 frag_width2; +layout(location = 2) out lowp float frag_gamma_scale; +layout(location = 3) out vec2 frag_tex_a; +layout(location = 4) out vec2 frag_tex_b; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 5) out lowp vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 6) out lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 7) out lowp float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_floorwidth) +layout(location = 8) out mediump float frag_floorwidth; +#endif + +void main() { + +#ifndef HAS_UNIFORM_u_color + frag_color = unpack_mix_color(in_color, interp.color_t); +#endif + +#ifndef HAS_UNIFORM_u_blur + frag_blur = unpack_mix_float(in_blur, interp.blur_t); +#endif + +#ifndef HAS_UNIFORM_u_opacity + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif + +#ifndef HAS_UNIFORM_u_floorwidth + const float floorwidth = unpack_mix_float(in_floorwidth, interp.floorwidth_t); + frag_floorwidth = floorwidth; +#else + const float floorwidth = props.floorwidth; +#endif + +#ifndef HAS_UNIFORM_u_offset + const mediump float offset = unpack_mix_float(in_offset, interp.offset_t); +#else + const mediump float offset = props.offset; +#endif + +#ifndef HAS_UNIFORM_u_width + const mediump float width = unpack_mix_float(in_width, interp.width_t); +#else + const mediump float width = props.width; +#endif + +#ifndef HAS_UNIFORM_u_gapwidth + const mediump float gapwidth = unpack_mix_float(in_gapwidth, interp.gapwidth_t) / 2.0; +#else + const mediump float gapwidth = props.gapwidth / 2.0; +#endif + + // the distance over which the line edge fades out. + // Retina devices need a smaller distance to avoid aliasing. + const float ANTIALIASING = 1.0 / DEVICE_PIXEL_RATIO / 2.0; + const float LINE_DISTANCE_SCALE = 2.0; + + vec2 a_extrude = in_data.xy - 128.0; + float a_direction = mod(in_data.z, 4.0) - 1.0; + float linesofar = (floor(in_data.z / 4.0) + in_data.w * 64.0) * LINE_DISTANCE_SCALE; + vec2 pos = floor(in_pos_normal * 0.5); + + // x is 1 if it's a round cap, 0 otherwise + // y is 1 if the normal points up, and -1 if it points down + // 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 = width / 2.0; + + float inset = gapwidth + (gapwidth > 0.0 ? ANTIALIASING : 0.0); + float outset = gapwidth + halfwidth * (gapwidth > 0.0 ? 2.0 : 1.0) + (halfwidth == 0.0 ? 0.0 : ANTIALIASING); + + // Scale the extrusion vector down to a normal and then up by the line width + // of this vertex. + mediump vec2 dist = outset * a_extrude * LINE_NORMAL_SCALE; + + // Calculate the offset when drawing a line that is to the side of the actual line. + // We do this by creating a vector that points towards the extrude, but rotate + // it when we're drawing round end points (a_direction = -1 or 1) since their + // extrude vector points in another direction. + mediump float u = 0.5 * a_direction; + mediump float t = 1.0 - abs(u); + mediump vec2 offset2 = offset * a_extrude * LINE_NORMAL_SCALE * frag_normal.y * mat2(t, -u, u, t); + + vec4 projected_extrude = drawable.matrix * vec4(dist / drawable.ratio, 0.0, 0.0); + gl_Position = drawable.matrix * vec4(pos + offset2 / drawable.ratio, 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 * global.units_to_pixels); + frag_gamma_scale = extrude_length_without_perspective / extrude_length_with_perspective; + + frag_width2 = vec2(outset, inset); + + frag_tex_a = vec2(linesofar * drawable.patternscale_a.x / floorwidth, (normal.y * drawable.patternscale_a.y + drawable.tex_y_a) * 2.0); + frag_tex_b = vec2(linesofar * drawable.patternscale_b.x / floorwidth, (normal.y * drawable.patternscale_b.y + drawable.tex_y_b) * 2.0); +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in lowp vec2 frag_normal; +layout(location = 1) in lowp vec2 frag_width2; +layout(location = 2) in lowp float frag_gamma_scale; +layout(location = 3) in vec2 frag_tex_a; +layout(location = 4) in vec2 frag_tex_b; + +#if !defined(HAS_UNIFORM_u_color) +layout(location = 5) in lowp vec4 frag_color; +#endif + +#if !defined(HAS_UNIFORM_u_blur) +layout(location = 6) in lowp float frag_blur; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 7) in lowp float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_floorwidth) +layout(location = 8) in mediump float frag_floorwidth; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform LineSDFDrawableUBO { + mat4 matrix; + vec2 patternscale_a; + vec2 patternscale_b; + float ratio; + float tex_y_a; + float tex_y_b; + float sdfgamma; + float mix; + float pad1, pad2, pad3; +} drawable; + +layout(set = 0, binding = 2) uniform LineSDFInterpolationUBO { + float color_t; + float blur_t; + float opacity_t; + float gapwidth_t; + float offset_t; + float width_t; + float floorwidth_t; + float pad1; +} interp; + +layout(set = 0, binding = 4) uniform LineEvaluatedPropsUBO { + vec4 color; + float blur; + float opacity; + float gapwidth; + float offset; + float width; + float floorwidth; + uint expressionMask; + float pad1; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +void main() { + +#ifdef OVERDRAW_INSPECTOR + out_color = vec4(1.0); + return; +#endif + +#ifdef HAS_UNIFORM_u_color + const lowp vec4 color = props.color; +#else + const lowp vec4 color = frag_color; +#endif + +#ifdef HAS_UNIFORM_u_blur + const lowp float blur = props.blur; +#else + const lowp float blur = frag_blur; +#endif + +#ifdef HAS_UNIFORM_u_opacity + const lowp float opacity = props.opacity; +#else + const lowp float opacity = frag_opacity; +#endif + +#ifdef HAS_UNIFORM_u_floorwidth + const lowp float floorwidth = props.floorwidth; +#else + const lowp float floorwidth = frag_floorwidth; +#endif + + // Calculate the antialiasing fade factor. This is either when fading in the + // line in case of an offset line (`v_width2.y`) or when fading out (`v_width2.x`) + const float blur2 = (blur + 1.0 / DEVICE_PIXEL_RATIO) * frag_gamma_scale; + + const float sdfdist_a = texture(image0_sampler, frag_tex_a).a; + const float sdfdist_b = texture(image0_sampler, frag_tex_b).a; + const float sdfdist = mix(sdfdist_a, sdfdist_b, drawable.mix); + const float dist = length(frag_normal) * frag_width2.x; + const float alpha = clamp(min(dist - (frag_width2.y - blur2), frag_width2.x - dist) / blur2, 0.0, 1.0) * + smoothstep(0.5 - drawable.sdfgamma / floorwidth, 0.5 + drawable.sdfgamma / floorwidth, sdfdist); + + out_color = color * (alpha * opacity); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/raster.hpp b/include/mbgl/shaders/vulkan/raster.hpp new file mode 100644 index 000000000..cffeb7073 --- /dev/null +++ b/include/mbgl/shaders/vulkan/raster.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "RasterShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec2 in_texture_position; + +layout(set = 0, binding = 1) uniform RasterDrawableUBO { + mat4 matrix; +} drawable; + +layout(set = 0, binding = 2) uniform RasterEvaluatedPropsUBO { + vec4 spin_weights; + vec2 tl_parent; + float scale_parent; + float buffer_scale; + float fade_t; + float opacity; + float brightness_low; + float brightness_high; + float saturation_factor; + float contrast_factor; + float pad1, pad2; +} props; + +layout(location = 0) out vec2 frag_position0; +layout(location = 1) out vec2 frag_position1; + +void main() { + + gl_Position = drawable.matrix * vec4(in_position, 0, 1); + gl_Position.y *= -1.0; + + // We are using Int16 for texture position coordinates to give us enough precision for + // fractional coordinates. We use 8192 to scale the texture coordinates in the buffer + // as an arbitrarily high number to preserve adequate precision when rendering. + // This is also the same value as the EXTENT we are using for our tile buffer pos coordinates, + // so math for modifying either is consistent. + frag_position0 = (((in_texture_position / 8192.0) - 0.5) / props.buffer_scale ) + 0.5; + frag_position1 = (frag_position0 * props.scale_parent) + props.tl_parent; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_position0; +layout(location = 1) in vec2 frag_position1; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 2) uniform RasterEvaluatedPropsUBO { + vec4 spin_weights; + vec2 tl_parent; + float scale_parent; + float buffer_scale; + float fade_t; + float opacity; + float brightness_low; + float brightness_high; + float saturation_factor; + float contrast_factor; + float pad1, pad2; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; +layout(set = 1, binding = 1) uniform sampler2D image1_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + // read and cross-fade colors from the main and parent tiles + vec4 color0 = texture(image0_sampler, frag_position0); + vec4 color1 = texture(image1_sampler, frag_position1); + if (color0.a > 0.0) { + color0.rgb = color0.rgb / color0.a; + } + if (color1.a > 0.0) { + color1.rgb = color1.rgb / color1.a; + } + vec4 color = mix(color0, color1, props.fade_t); + color.a *= props.opacity; + vec3 rgb = color.rgb; + + // spin + rgb = vec3( + dot(rgb, props.spin_weights.xyz), + dot(rgb, props.spin_weights.zxy), + dot(rgb, props.spin_weights.yzx)); + + // saturation + float average = (color.r + color.g + color.b) / 3.0; + rgb += (average - rgb) * props.saturation_factor; + + // contrast + rgb = (rgb - 0.5) * props.contrast_factor + 0.5; + + // brightness + vec3 high_vec = vec3(props.brightness_low, props.brightness_low, props.brightness_low); + vec3 low_vec = vec3(props.brightness_high, props.brightness_high, props.brightness_high); + + out_color = vec4(vec3(mix(high_vec, low_vec, rgb) * color.a), color.a); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/shader_group.hpp b/include/mbgl/shaders/vulkan/shader_group.hpp new file mode 100644 index 000000000..751f28432 --- /dev/null +++ b/include/mbgl/shaders/vulkan/shader_group.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +class ShaderGroupBase : public gfx::ShaderGroup { +protected: + ShaderGroupBase(const ProgramParameters& parameters_) + : programParameters(parameters_) {} + + using DefinesMap = mbgl::unordered_map; + void addAdditionalDefines(const StringIDSetsPair& propertiesAsUniforms, DefinesMap& additionalDefines) { + additionalDefines.reserve(propertiesAsUniforms.first.size()); + for (const auto name : propertiesAsUniforms.first) { + // We expect the names to be prefixed by "a_", but we need just the base here. + const auto* base = (name[0] == 'a' && name[1] == '_') ? &name[2] : name.data(); + additionalDefines.insert(std::make_pair(std::string(uniformPrefix) + base, std::string())); + } + } + + ProgramParameters programParameters; + +private: + static constexpr auto uniformPrefix = "HAS_UNIFORM_u_"; +}; + +template +class ShaderGroup final : public ShaderGroupBase { +public: + ShaderGroup(const ProgramParameters& programParameters_) + : ShaderGroupBase(programParameters_) {} + ~ShaderGroup() noexcept override = default; + + gfx::ShaderPtr getOrCreateShader(gfx::Context& gfxContext, + const StringIDSetsPair& propertiesAsUniforms, + std::string_view /*firstAttribName*/) override { + using ShaderSource = shaders::ShaderSource; + constexpr auto& name = ShaderSource::name; + constexpr auto& vert = ShaderSource::vertex; + constexpr auto& frag = ShaderSource::fragment; + + std::size_t seed = 0; + mbgl::util::hash_combine(seed, propertyHash(propertiesAsUniforms)); + mbgl::util::hash_combine(seed, programParameters.getDefinesHash()); + const std::string shaderName = getShaderName(name, seed); + + auto shader = get(shaderName); + if (!shader) { + DefinesMap additionalDefines; + addAdditionalDefines(propertiesAsUniforms, additionalDefines); + + auto& context = static_cast(gfxContext); + shader = context.createProgram(shaderName, vert, frag, programParameters, additionalDefines); + assert(shader); + if (!shader || !registerShader(shader, shaderName)) { + assert(false); + throw std::runtime_error("Failed to register " + shaderName + " with shader group!"); + } + + using ShaderClass = shaders::ShaderSource; + for (const auto& attrib : ShaderClass::attributes) { + if (!propertiesAsUniforms.second.count(attrib.id)) { + shader->initAttribute(attrib); + } + } + for (const auto& attrib : ShaderClass::instanceAttributes) { + shader->initInstanceAttribute(attrib); + } + for (const auto& uniform : ShaderClass::uniforms) { + shader->initUniformBlock(uniform); + } + for (const auto& texture : ShaderClass::textures) { + shader->initTexture(texture); + } + } + return shader; + } +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/shader_program.hpp b/include/mbgl/shaders/vulkan/shader_program.hpp new file mode 100644 index 000000000..e4f911a27 --- /dev/null +++ b/include/mbgl/shaders/vulkan/shader_program.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mbgl { +namespace shaders { +struct UniformBlockInfo { + constexpr UniformBlockInfo(bool vertex_, bool fragment_, std::size_t size_, std::size_t id_) + : index(id_), + vertex(vertex_), + fragment(fragment_), + size(size_), + id(id_) {} + std::size_t index; + bool vertex; + bool fragment; + std::size_t size; + std::size_t id; +}; +struct AttributeInfo { + constexpr AttributeInfo(std::size_t index_, gfx::AttributeDataType dataType_, std::size_t id_) + : index(index_), + dataType(dataType_), + id(id_) {} + std::size_t index; + gfx::AttributeDataType dataType; + std::size_t id; +}; +struct TextureInfo { + constexpr TextureInfo(std::size_t index_, std::size_t id_) + : index(index_), + id(id_) {} + std::size_t index; + std::size_t id; +}; +} // namespace shaders + +namespace vulkan { +class RenderableResource; +class RendererBackend; +class ShaderProgram; +using UniqueShaderProgram = std::unique_ptr; + +class ShaderProgram final : public gfx::ShaderProgramBase { +public: + ShaderProgram(const std::string& name, + const std::string_view& vertex, + const std::string_view& fragment, + const ProgramParameters& programParameters, + const mbgl::unordered_map& additionalDefines, + RendererBackend& backend); + ~ShaderProgram() noexcept override; + + static constexpr std::string_view Name{"GenericVulkanShader"}; + const std::string_view typeName() const noexcept override { return Name; } + + const vk::UniquePipeline& getPipeline(const PipelineInfo& pipelineInfo); + + std::optional getSamplerLocation(const size_t id) const override { return textureBindings[id]; } + const gfx::VertexAttributeArray& getVertexAttributes() const override { return vertexAttributes; } + const gfx::VertexAttributeArray& getInstanceAttributes() const override { return instanceAttributes; } + + const gfx::UniformBlockArray& getUniformBlocks() const override { return uniformBlocks; } + gfx::UniformBlockArray& mutableUniformBlocks() override { return uniformBlocks; } + bool hasTextures() const; + + void initAttribute(const shaders::AttributeInfo&); + void initInstanceAttribute(const shaders::AttributeInfo&); + void initUniformBlock(const shaders::UniformBlockInfo&); + void initTexture(const shaders::TextureInfo&); + +protected: + std::string shaderName; + RendererBackend& backend; + + vk::UniqueShaderModule vertexShader; + vk::UniqueShaderModule fragmentShader; + std::unordered_map pipelines; + + UniformBlockArray uniformBlocks; + VertexAttributeArray vertexAttributes; + VertexAttributeArray instanceAttributes; + std::array, shaders::maxTextureCountPerShader> textureBindings; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/symbol.hpp b/include/mbgl/shaders/vulkan/symbol.hpp new file mode 100644 index 000000000..39a16995a --- /dev/null +++ b/include/mbgl/shaders/vulkan/symbol.hpp @@ -0,0 +1,890 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "SymbolIconShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + 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 = 3) in vec3 in_projected_pos; +layout(location = 4) in float in_fade_opacity; + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 5) in vec2 in_opacity; +#endif + +layout(set = 0, binding = 1) uniform SymbolDrawableUBO { + mat4 matrix; + mat4 label_plane_matrix; + mat4 coord_matrix; + vec2 texsize; + vec2 texsize_icon; + float gamma_scale; + bool rotate_symbol; + vec2 pad; +} drawable; + +layout(set = 0, binding = 2) uniform SymbolTilePropsUBO { + bool is_text; + bool is_halo; + bool pitch_with_map; + bool is_size_zoom_constant; + bool is_size_feature_constant; + float size_t; + float size; + float padding; +} tile; + +layout(set = 0, binding = 3) uniform SymbolInterpolateUBO { + float fill_color_t; + float halo_color_t; + float opacity_t; + float halo_width_t; + float halo_blur_t; + float pad1, pad2, pad3; +} interp; + +layout(location = 0) out mediump vec2 frag_tex; +layout(location = 1) out mediump float frag_opacity; + +void main() { + + const vec2 a_pos = in_pos_offset.xy; + const vec2 a_offset = in_pos_offset.zw; + + const vec2 a_tex = in_data.xy; + const vec2 a_size = in_data.zw; + + const float a_size_min = floor(a_size[0] * 0.5); + const vec2 a_pxoffset = in_pixeloffset.xy; + const vec2 a_minFontScale = in_pixeloffset.zw / 256.0; + + const float segment_angle = -in_projected_pos[2]; + + float size; + if (!tile.is_size_zoom_constant && !tile.is_size_feature_constant) { + size = mix(a_size_min, a_size[1], tile.size_t) / 128.0; + } else if (tile.is_size_zoom_constant && !tile.is_size_feature_constant) { + size = a_size_min / 128.0; + } else { + size = tile.size; + } + + const vec4 projectedPoint = drawable.matrix * vec4(a_pos, 0, 1); + const float camera_to_anchor_distance = projectedPoint.w; + // See comments in symbol_sdf.vertex + const float distance_ratio = tile.pitch_with_map ? + camera_to_anchor_distance / global.camera_to_center_distance : + global.camera_to_center_distance / camera_to_anchor_distance; + const float perspective_ratio = clamp( + 0.5 + 0.5 * distance_ratio, + 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles + 4.0); + + size *= perspective_ratio; + + const float fontScale = tile.is_text ? size / 24.0 : size; + + float symbol_rotation = 0.0; + if (drawable.rotate_symbol) { + // See comments in symbol_sdf.vertex + const vec4 offsetProjectedPoint = drawable.matrix * vec4(a_pos + vec2(1, 0), 0, 1); + + const vec2 a = projectedPoint.xy / projectedPoint.w; + const vec2 b = offsetProjectedPoint.xy / offsetProjectedPoint.w; + symbol_rotation = atan((b.y - a.y) / global.aspect_ratio, b.x - a.x); + } + + const float angle_sin = sin(segment_angle + symbol_rotation); + const float angle_cos = cos(segment_angle + symbol_rotation); + const mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); + + const vec4 projected_pos = drawable.label_plane_matrix * vec4(in_projected_pos.xy, 0.0, 1.0); + const vec2 pos0 = projected_pos.xy / projected_pos.w; + const vec2 posOffset = a_offset * max(a_minFontScale, fontScale) / 32.0 + a_pxoffset / 16.0; + gl_Position = drawable.coord_matrix * vec4(pos0 + rotation_matrix * posOffset, 0.0, 1.0); + gl_Position.y *= -1.0; + + const vec2 raw_fade_opacity = unpack_opacity(in_fade_opacity); + const float fade_change = raw_fade_opacity[1] > 0.5 ? global.symbol_fade_change : -global.symbol_fade_change; + const float fade_opacity = max(0.0, min(1.0, raw_fade_opacity[0] + fade_change)); + + frag_tex = a_tex / drawable.texsize; + +#if defined(HAS_UNIFORM_u_opacity) + frag_opacity = fade_opacity; +#else + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t) * fade_opacity; +#endif +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in mediump vec2 frag_tex; +layout(location = 1) in mediump float frag_opacity; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 2) uniform SymbolTilePropsUBO { + bool is_text; + bool is_halo; + bool pitch_with_map; + bool is_size_zoom_constant; + bool is_size_feature_constant; + float size_t; + float size; + float padding; +} tile; + +layout(set = 0, binding = 4) uniform SymbolEvaluatedPropsUBO { + vec4 text_fill_color; + vec4 text_halo_color; + float text_opacity; + float text_halo_width; + float text_halo_blur; + float pad1; + vec4 icon_fill_color; + vec4 icon_halo_color; + float icon_opacity; + float icon_halo_width; + float icon_halo_blur; + float pad2; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +void main() { +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = (tile.is_text ? props.text_opacity : props.icon_opacity) * frag_opacity; +#else + const float opacity = frag_opacity; // fade_opacity is baked in for this case +#endif + + out_color = texture(image0_sampler, frag_tex) * opacity; +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "SymbolSDFIconShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + 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 = 3) in vec3 in_projected_pos; +layout(location = 4) in float in_fade_opacity; + +#if !defined(HAS_UNIFORM_u_fill_color) +layout(location = 5) in vec4 in_fill_color; +#endif + +#if !defined(HAS_UNIFORM_u_halo_color) +layout(location = 6) in vec4 in_halo_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 7) in vec2 in_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_halo_width) +layout(location = 8) in vec2 in_halo_width; +#endif + +#if !defined(HAS_UNIFORM_u_halo_blur) +layout(location = 9) in vec2 in_halo_blur; +#endif + +layout(set = 0, binding = 1) uniform SymbolDrawableUBO { + mat4 matrix; + mat4 label_plane_matrix; + mat4 coord_matrix; + vec2 texsize; + vec2 texsize_icon; + float gamma_scale; + bool rotate_symbol; + vec2 pad; +} drawable; + +layout(set = 0, binding = 2) uniform SymbolTilePropsUBO { + bool is_text; + bool is_halo; + bool pitch_with_map; + bool is_size_zoom_constant; + bool is_size_feature_constant; + float size_t; + float size; + float padding; +} tile; + +layout(set = 0, binding = 3) uniform SymbolInterpolateUBO { + float fill_color_t; + float halo_color_t; + float opacity_t; + float halo_width_t; + float halo_blur_t; + float pad1, pad2, pad3; +} interp; + +layout(location = 0) out mediump vec2 frag_tex; +layout(location = 1) out mediump float frag_fade_opacity; +layout(location = 2) out mediump float frag_font_scale; +layout(location = 3) out mediump float frag_gamma_scale; + +#if !defined(HAS_UNIFORM_u_fill_color) +layout(location = 4) out mediump vec4 frag_fill_color; +#endif + +#if !defined(HAS_UNIFORM_u_halo_color) +layout(location = 5) out mediump vec4 frag_halo_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 6) out mediump float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_halo_width) +layout(location = 7) out mediump float frag_halo_width; +#endif + +#if !defined(HAS_UNIFORM_u_halo_blur) +layout(location = 8) out mediump float frag_halo_blur; +#endif + +void main() { + + const vec2 a_pos = in_pos_offset.xy; + const vec2 a_offset = in_pos_offset.zw; + + const vec2 a_tex = in_data.xy; + const vec2 a_size = in_data.zw; + + const float a_size_min = floor(a_size[0] * 0.5); + const vec2 a_pxoffset = in_pixeloffset.xy; + const float segment_angle = -in_projected_pos[2]; + + float size; + if (!tile.is_size_zoom_constant && !tile.is_size_feature_constant) { + size = mix(a_size_min, a_size[1], tile.size_t) / 128.0; + } else if (tile.is_size_zoom_constant && !tile.is_size_feature_constant) { + size = a_size_min / 128.0; + } else { + size = tile.size; + } + + const vec4 projectedPoint = drawable.matrix * vec4(a_pos, 0, 1); + const float camera_to_anchor_distance = projectedPoint.w; + // If the label is pitched with the map, layout is done in pitched space, + // which makes labels in the distance smaller relative to viewport space. + // We counteract part of that effect by multiplying by the perspective ratio. + // If the label isn't pitched with the map, we do layout in viewport space, + // which makes labels in the distance larger relative to the features around + // them. We counteract part of that effect by dividing by the perspective ratio. + const float distance_ratio = tile.pitch_with_map ? + camera_to_anchor_distance / global.camera_to_center_distance : + global.camera_to_center_distance / camera_to_anchor_distance; + const float perspective_ratio = clamp( + 0.5 + 0.5 * distance_ratio, + 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles + 4.0); + + size *= perspective_ratio; + + const float fontScale = tile.is_text ? size / 24.0 : size; + + float symbol_rotation = 0.0; + if (drawable.rotate_symbol) { + // Point labels with 'rotation-alignment: map' are horizontal with respect to tile units + // To figure out that angle in projected space, we draw a short horizontal line in tile + // space, project it, and measure its angle in projected space. + const vec4 offsetProjectedPoint = drawable.matrix * vec4(a_pos + vec2(1, 0), 0, 1); + + const vec2 a = projectedPoint.xy / projectedPoint.w; + const vec2 b = offsetProjectedPoint.xy / offsetProjectedPoint.w; + symbol_rotation = atan((b.y - a.y) / global.aspect_ratio, b.x - a.x); + } + + const float angle_sin = sin(segment_angle + symbol_rotation); + const float angle_cos = cos(segment_angle + symbol_rotation); + const mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); + + const vec4 projected_pos = drawable.label_plane_matrix * vec4(in_projected_pos.xy, 0.0, 1.0); + const vec2 pos_rot = a_offset / 32.0 * fontScale + a_pxoffset; + const vec2 pos0 = projected_pos.xy / projected_pos.w + rotation_matrix * pos_rot; + gl_Position = drawable.coord_matrix * vec4(pos0, 0.0, 1.0); + gl_Position.y *= -1.0; + + const vec2 raw_fade_opacity = unpack_opacity(in_fade_opacity); + const float fade_change = raw_fade_opacity[1] > 0.5 ? global.symbol_fade_change : -global.symbol_fade_change; + + frag_tex = a_tex / drawable.texsize; + frag_fade_opacity = max(0.0, min(1.0, raw_fade_opacity[0] + fade_change)); + frag_font_scale = fontScale; + frag_gamma_scale = gl_Position.w; + +#if !defined(HAS_UNIFORM_u_fill_color) + frag_fill_color = unpack_mix_color(in_fill_color, interp.fill_color_t); +#endif +#if !defined(HAS_UNIFORM_u_halo_color) + frag_halo_color = unpack_mix_color(in_halo_color, interp.halo_color_t); +#endif +#if !defined(HAS_UNIFORM_u_halo_width) + frag_halo_width = unpack_mix_float(in_halo_width, interp.halo_width_t); +#endif +#if !defined(HAS_UNIFORM_u_halo_blur) + frag_halo_blur = unpack_mix_float(in_halo_blur, interp.halo_blur_t); +#endif +#if !defined(HAS_UNIFORM_u_opacity) + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in mediump vec2 frag_tex; +layout(location = 1) in mediump float frag_fade_opacity; +layout(location = 2) in mediump float frag_font_scale; +layout(location = 3) in mediump float frag_gamma_scale; + +#if !defined(HAS_UNIFORM_u_fill_color) +layout(location = 4) in mediump vec4 frag_fill_color; +#endif + +#if !defined(HAS_UNIFORM_u_halo_color) +layout(location = 5) in mediump vec4 frag_halo_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 6) in mediump float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_halo_width) +layout(location = 7) in mediump float frag_halo_width; +#endif + +#if !defined(HAS_UNIFORM_u_halo_blur) +layout(location = 8) in mediump float frag_halo_blur; +#endif + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform SymbolDrawableUBO { + mat4 matrix; + mat4 label_plane_matrix; + mat4 coord_matrix; + vec2 texsize; + vec2 texsize_icon; + float gamma_scale; + bool rotate_symbol; + vec2 pad; +} drawable; + +layout(set = 0, binding = 2) uniform SymbolTilePropsUBO { + bool is_text; + bool is_halo; + bool pitch_with_map; + bool is_size_zoom_constant; + bool is_size_feature_constant; + float size_t; + float size; + float padding; +} tile; + +layout(set = 0, binding = 4) uniform SymbolEvaluatedPropsUBO { + vec4 text_fill_color; + vec4 text_halo_color; + float text_opacity; + float text_halo_width; + float text_halo_blur; + float pad1; + vec4 icon_fill_color; + vec4 icon_halo_color; + float icon_opacity; + float icon_halo_width; + float icon_halo_blur; + float pad2; +} props; + +layout(set = 1, binding = 0) uniform sampler2D image0_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_fill_color) + const vec4 fill_color = tile.is_text ? props.text_fill_color : props.icon_fill_color; +#else + const vec4 fill_color = frag_fill_color; +#endif +#if defined(HAS_UNIFORM_u_halo_color) + const vec4 halo_color = tile.is_text ? props.text_halo_color : props.icon_halo_color; +#else + const vec4 halo_color = frag_halo_color; +#endif +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = tile.is_text ? props.text_opacity : props.icon_opacity; +#else + const float opacity = frag_opacity; +#endif +#if defined(HAS_UNIFORM_u_halo_width) + const float halo_width = tile.is_text ? props.text_halo_width : props.icon_halo_width; +#else + const float halo_width = frag_halo_width; +#endif +#if defined(HAS_UNIFORM_u_halo_blur) + const float halo_blur = tile.is_text ? props.text_halo_blur : props.icon_halo_blur; +#else + const float halo_blur = frag_halo_blur; +#endif + + const float EDGE_GAMMA = 0.105 / DEVICE_PIXEL_RATIO; + const float fontGamma = frag_font_scale * drawable.gamma_scale; + const vec4 color = tile.is_halo ? halo_color : fill_color; + const float gamma = ((tile.is_halo ? (halo_blur * 1.19 / SDF_PX) : 0) + EDGE_GAMMA) / fontGamma; + const float buff = tile.is_halo ? (6.0 - halo_width / frag_font_scale) / SDF_PX : (256.0 - 64.0) / 256.0; + const float dist = texture(image0_sampler, frag_tex).a; + const float gamma_scaled = gamma * frag_gamma_scale; + const float alpha = smoothstep(buff - gamma_scaled, buff + gamma_scaled, dist); + + out_color = color * (alpha * opacity * frag_fade_opacity); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "SymbolTextAndIconShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +#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 = 2) in vec3 in_projected_pos; +layout(location = 3) in float in_fade_opacity; + +#if !defined(HAS_UNIFORM_u_fill_color) +layout(location = 4) in vec4 in_fill_color; +#endif + +#if !defined(HAS_UNIFORM_u_halo_color) +layout(location = 5) in vec4 in_halo_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 6) in vec2 in_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_halo_width) +layout(location = 7) in vec2 in_halo_width; +#endif + +#if !defined(HAS_UNIFORM_u_halo_blur) +layout(location = 8) in vec2 in_halo_blur; +#endif + +layout(set = 0, binding = 1) uniform SymbolDrawableUBO { + mat4 matrix; + mat4 label_plane_matrix; + mat4 coord_matrix; + vec2 texsize; + vec2 texsize_icon; + float gamma_scale; + bool rotate_symbol; + vec2 pad; +} drawable; + +layout(set = 0, binding = 2) uniform SymbolTilePropsUBO { + bool is_text; + bool is_halo; + bool pitch_with_map; + bool is_size_zoom_constant; + bool is_size_feature_constant; + float size_t; + float size; + float padding; +} tile; + +layout(set = 0, binding = 3) uniform SymbolInterpolateUBO { + float fill_color_t; + float halo_color_t; + float opacity_t; + float halo_width_t; + float halo_blur_t; + float pad1, pad2, pad3; +} interp; + +layout(location = 0) out mediump vec2 frag_tex; +layout(location = 1) out mediump float frag_fade_opacity; +layout(location = 2) out mediump float frag_font_scale; +layout(location = 3) out mediump float frag_gamma_scale; + +#if !defined(HAS_UNIFORM_u_fill_color) +layout(location = 4) out mediump vec4 frag_fill_color; +#endif + +#if !defined(HAS_UNIFORM_u_halo_color) +layout(location = 5) out mediump vec4 frag_halo_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 6) out mediump float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_halo_width) +layout(location = 7) out mediump float frag_halo_width; +#endif + +#if !defined(HAS_UNIFORM_u_halo_blur) +layout(location = 8) out mediump float frag_halo_blur; +#endif + +layout(location = 9) out int frag_is_icon; + +void main() { + + const vec2 a_pos = in_pos_offset.xy; + const vec2 a_offset = in_pos_offset.zw; + + const vec2 a_tex = in_data.xy; + const vec2 a_size = in_data.zw; + + const float a_size_min = floor(a_size[0] * 0.5); + const float is_sdf = a_size[0] - 2.0 * a_size_min; + const float segment_angle = -in_projected_pos[2]; + + float size; + if (!tile.is_size_zoom_constant && !tile.is_size_feature_constant) { + size = mix(a_size_min, a_size[1], tile.size_t) / 128.0; + } else if (tile.is_size_zoom_constant && !tile.is_size_feature_constant) { + size = a_size_min / 128.0; + } else { + size = tile.size; + } + + const vec4 projectedPoint = drawable.matrix * vec4(a_pos, 0, 1); + const float camera_to_anchor_distance = projectedPoint.w; + // If the label is pitched with the map, layout is done in pitched space, + // which makes labels in the distance smaller relative to viewport space. + // We counteract part of that effect by multiplying by the perspective ratio. + // If the label isn't pitched with the map, we do layout in viewport space, + // which makes labels in the distance larger relative to the features around + // them. We counteract part of that effect by dividing by the perspective ratio. + const float distance_ratio = tile.pitch_with_map ? + camera_to_anchor_distance / global.camera_to_center_distance : + global.camera_to_center_distance / camera_to_anchor_distance; + const float perspective_ratio = clamp( + 0.5 + 0.5 * distance_ratio, + 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles + 4.0); + + size *= perspective_ratio; + + const float fontScale = size / 24.0; + + float symbol_rotation = 0.0; + if (drawable.rotate_symbol) { + // Point labels with 'rotation-alignment: map' are horizontal with respect to tile units + // To figure out that angle in projected space, we draw a short horizontal line in tile + // space, project it, and measure its angle in projected space. + const vec4 offsetProjectedPoint = drawable.matrix * vec4(a_pos + vec2(1, 0), 0, 1); + + const vec2 a = projectedPoint.xy / projectedPoint.w; + const vec2 b = offsetProjectedPoint.xy / offsetProjectedPoint.w; + symbol_rotation = atan((b.y - a.y) / global.aspect_ratio, b.x - a.x); + } + + const float angle_sin = sin(segment_angle + symbol_rotation); + const float angle_cos = cos(segment_angle + symbol_rotation); + const mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); + + const vec4 projected_pos = drawable.label_plane_matrix * vec4(in_projected_pos.xy, 0.0, 1.0); + const vec2 pos_rot = a_offset / 32.0 * fontScale; + const vec2 pos0 = projected_pos.xy / projected_pos.w + rotation_matrix * pos_rot; + gl_Position = drawable.coord_matrix * vec4(pos0, 0.0, 1.0); + gl_Position.y *= -1.0; + + const vec2 raw_fade_opacity = unpack_opacity(in_fade_opacity); + const float fade_change = raw_fade_opacity[1] > 0.5 ? global.symbol_fade_change : -global.symbol_fade_change; + + const bool is_icon = (is_sdf == ICON); + frag_is_icon = int(is_icon); + + frag_tex = a_tex / (is_icon ? drawable.texsize_icon : drawable.texsize); + frag_fade_opacity = max(0.0, min(1.0, raw_fade_opacity[0] + fade_change)); + frag_font_scale = fontScale; + frag_gamma_scale = gl_Position.w; + +#if !defined(HAS_UNIFORM_u_fill_color) + frag_fill_color = unpack_mix_color(in_fill_color, interp.fill_color_t); +#endif +#if !defined(HAS_UNIFORM_u_halo_color) + frag_halo_color = unpack_mix_color(in_halo_color, interp.halo_color_t); +#endif +#if !defined(HAS_UNIFORM_u_halo_width) + frag_halo_width = unpack_mix_float(in_halo_width, interp.halo_width_t); +#endif +#if !defined(HAS_UNIFORM_u_halo_blur) + frag_halo_blur = unpack_mix_float(in_halo_blur, interp.halo_blur_t); +#endif +#if !defined(HAS_UNIFORM_u_opacity) + frag_opacity = unpack_mix_float(in_opacity, interp.opacity_t); +#endif +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in mediump vec2 frag_tex; +layout(location = 1) in mediump float frag_fade_opacity; +layout(location = 2) in mediump float frag_font_scale; +layout(location = 3) in mediump float frag_gamma_scale; + +#if !defined(HAS_UNIFORM_u_fill_color) +layout(location = 4) in mediump vec4 frag_fill_color; +#endif + +#if !defined(HAS_UNIFORM_u_halo_color) +layout(location = 5) in mediump vec4 frag_halo_color; +#endif + +#if !defined(HAS_UNIFORM_u_opacity) +layout(location = 6) in mediump float frag_opacity; +#endif + +#if !defined(HAS_UNIFORM_u_halo_width) +layout(location = 7) in mediump float frag_halo_width; +#endif + +#if !defined(HAS_UNIFORM_u_halo_blur) +layout(location = 8) in mediump float frag_halo_blur; +#endif + +layout(location = 9) flat in int frag_is_icon; + +layout(location = 0) out vec4 out_color; + +layout(set = 0, binding = 1) uniform SymbolDrawableUBO { + mat4 matrix; + mat4 label_plane_matrix; + mat4 coord_matrix; + vec2 texsize; + vec2 texsize_icon; + float gamma_scale; + bool rotate_symbol; + vec2 pad; +} drawable; + +layout(set = 0, binding = 2) uniform SymbolTilePropsUBO { + bool is_text; + bool is_halo; + bool pitch_with_map; + bool is_size_zoom_constant; + bool is_size_feature_constant; + float size_t; + float size; + float padding; +} tile; + +layout(set = 0, binding = 4) uniform SymbolEvaluatedPropsUBO { + vec4 text_fill_color; + vec4 text_halo_color; + float text_opacity; + float text_halo_width; + float text_halo_blur; + float pad1; + vec4 icon_fill_color; + vec4 icon_halo_color; + float icon_opacity; + float icon_halo_width; + float icon_halo_blur; + float pad2; +} props; + +layout(set = 1, binding = 0) uniform sampler2D glyph_image; +layout(set = 1, binding = 1) uniform sampler2D icon_image; + +void main() { +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + +#if defined(HAS_UNIFORM_u_fill_color) + const vec4 fill_color = tile.is_text ? props.text_fill_color : props.icon_fill_color; +#else + const vec4 fill_color = frag_fill_color; +#endif +#if defined(HAS_UNIFORM_u_halo_color) + const vec4 halo_color = tile.is_text ? props.text_halo_color : props.icon_halo_color; +#else + const vec4 halo_color = frag_halo_color; +#endif +#if defined(HAS_UNIFORM_u_opacity) + const float opacity = tile.is_text ? props.text_opacity : props.icon_opacity; +#else + const float opacity = frag_opacity; +#endif +#if defined(HAS_UNIFORM_u_halo_width) + const float halo_width = tile.is_text ? props.text_halo_width : props.icon_halo_width; +#else + const float halo_width = frag_halo_width; +#endif +#if defined(HAS_UNIFORM_u_halo_blur) + const float halo_blur = tile.is_text ? props.text_halo_blur : props.icon_halo_blur; +#else + const float halo_blur = frag_halo_blur; +#endif + + if (bool(frag_is_icon)) { + const float alpha = opacity * frag_fade_opacity; + out_color = texture(icon_image, frag_tex) * alpha; + return; + } + + const float EDGE_GAMMA = 0.105 / DEVICE_PIXEL_RATIO; + const float fontGamma = frag_font_scale * drawable.gamma_scale; + const vec4 color = tile.is_halo ? halo_color : fill_color; + const float gamma = ((tile.is_halo ? (halo_blur * 1.19 / SDF_PX) : 0) + EDGE_GAMMA) / fontGamma; + const float buff = tile.is_halo ? (6.0 - halo_width / frag_font_scale) / SDF_PX : (256.0 - 64.0) / 256.0; + const float dist = texture(glyph_image, frag_tex).a; + const float gamma_scaled = gamma * frag_gamma_scale; + const float alpha = smoothstep(buff - gamma_scaled, buff + gamma_scaled, dist); + + out_color = color * (alpha * opacity * frag_fade_opacity); +} +)"; +}; + +template <> +struct ShaderSource { + static constexpr const char* name = "CustomSymbolIconShader"; + + static const std::array uniforms; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec2 in_tex; + +layout(set = 0, binding = 1) uniform CustomSymbolIconDrawableUBO { + mat4 matrix; +} drawable; + +layout(set = 0, binding = 2) uniform CustomSymbolIconParametersUBO { + vec2 extrude_scale; + vec2 anchor; + float angle_degrees; + bool scale_with_map; + bool pitch_with_map; + float camera_to_center_distance; + float aspect_ratio; + float pad0, pad1, pad3; +} parameters; + +layout(location = 0) out vec2 frag_tex; + +vec2 rotateVec2(vec2 v, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + return vec2(v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA); +} + +vec2 ellipseRotateVec2(vec2 v, float angle, float radiusRatio /* A/B */) { + float cosA = cos(angle); + float sinA = sin(angle); + float invRatio = 1.0 / radiusRatio; + return vec2(v.x * cosA - radiusRatio * v.y * sinA, invRatio * v.x * sinA + v.y * cosA); +} + +void main() { + const vec2 extrude = mod(in_position, 2.0) * 2.0 - 1.0; + const vec2 anchor = (parameters.anchor - vec2(0.5, 0.5)) * 2.0; + const vec2 center = floor(in_position * 0.5); + const float angle = radians(-parameters.angle_degrees); + vec2 corner = extrude - anchor; + + vec4 position; + if (parameters.pitch_with_map) { + if (parameters.scale_with_map) { + corner *= parameters.extrude_scale; + } else { + vec4 projected_center = drawable.matrix * vec4(center, 0, 1); + corner *= parameters.extrude_scale * (projected_center.w / parameters.camera_to_center_distance); + } + corner = center + rotateVec2(corner, angle); + position = drawable.matrix * vec4(corner, 0, 1); + } else { + position = drawable.matrix * vec4(center, 0, 1); + const float factor = parameters.scale_with_map ? parameters.camera_to_center_distance : position.w; + position.xy += ellipseRotateVec2(corner * parameters.extrude_scale * factor, angle, parameters.aspect_ratio); + } + + gl_Position = position; + gl_Position.y *= -1.0; + + frag_tex = in_tex; +} +)"; + + static constexpr auto fragment = R"( +layout(location = 0) in vec2 frag_tex; +layout(location = 0) out vec4 out_color; + +layout(set = 1, binding = 0) uniform sampler2D image_sampler; + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + out_color = texture(image_sampler, frag_tex); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/vulkan/buffer_resource.hpp b/include/mbgl/vulkan/buffer_resource.hpp new file mode 100644 index 000000000..58cdc3006 --- /dev/null +++ b/include/mbgl/vulkan/buffer_resource.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include + +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +class Context; + +struct BufferAllocation { + const VmaAllocator& allocator; + VmaAllocation allocation{}; + VkBuffer buffer{}; + void* mappedBuffer{}; + + BufferAllocation() = delete; + BufferAllocation(BufferAllocation&) = delete; + BufferAllocation& operator=(const BufferAllocation& other) = delete; + + 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); + void destroy(); + + void setName(const std::string& name) const; +}; + +using UniqueBufferAllocation = std::unique_ptr; +using SharedBufferAllocation = std::shared_ptr; + +class BufferResource { +public: + BufferResource() noexcept = delete; + + BufferResource(Context& context_, const void* raw, std::size_t size, std::uint32_t usage, bool persistent); + BufferResource(BufferResource&&) noexcept; + virtual ~BufferResource() noexcept; + + BufferResource& operator=(BufferResource&&) noexcept; + + BufferResource clone() const; + + void update(const void* data, std::size_t size, std::size_t offset) noexcept; + + std::size_t getSizeInBytes() const noexcept { return size; } + 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; } + std::size_t getVulkanBufferOffset() const noexcept; + std::size_t getVulkanBufferSize() const noexcept; + + bool isValid() const noexcept { return !raw.empty(); } + operator bool() const noexcept { return isValid(); } + bool operator!() const noexcept { return !isValid(); } + + using VersionType = std::uint16_t; + + /// Used to detect whether buffer contents have changed + VersionType getVersion() const noexcept { return version; } + +protected: + Context& context; + std::vector raw; + std::size_t size; + std::uint32_t usage; + std::uint16_t version = 0; + bool persistent; + + SharedBufferAllocation bufferAllocation; + size_t bufferWindowSize = 0; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/command_encoder.hpp b/include/mbgl/vulkan/command_encoder.hpp new file mode 100644 index 000000000..81f896837 --- /dev/null +++ b/include/mbgl/vulkan/command_encoder.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace gfx { +class Renderable; +} // namespace gfx + +namespace vulkan { + +class Context; +class RenderPass; +class UploadPass; + +class CommandEncoder final : public gfx::CommandEncoder { +public: + explicit CommandEncoder(Context& context_, const vk::UniqueCommandBuffer& buffer_); + ~CommandEncoder() override; + + vulkan::Context& getContext() { return context; } + const vulkan::Context& getContext() const { return context; } + const vk::UniqueCommandBuffer& getCommandBuffer() const { return commandBuffer; } + + std::unique_ptr createUploadPass(const char* name, gfx::Renderable&) override; + std::unique_ptr createRenderPass(const char* name, const gfx::RenderPassDescriptor&) override; + + void present(gfx::Renderable&) override; + +private: + void pushDebugGroup(const char* name) override; + void pushDebugGroup(const char* name, const std::array& color); + void popDebugGroup() override; + +private: + friend class RenderPass; + friend class UploadPass; + + vulkan::Context& context; + const vk::UniqueCommandBuffer& commandBuffer; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/context.hpp b/include/mbgl/vulkan/context.hpp new file mode 100644 index 000000000..6001879e1 --- /dev/null +++ b/include/mbgl/vulkan/context.hpp @@ -0,0 +1,203 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace mbgl { + +class ProgramParameters; +class RenderStaticData; + +namespace gfx { +class VertexAttributeArray; +using VertexAttributeArrayPtr = std::shared_ptr; +} // namespace gfx + +namespace shaders { +struct ClipUBO; +} // namespace shaders + +namespace vulkan { + +class RenderPass; +class RendererBackend; +class ShaderProgram; +class VertexBufferResource; +class Texture2D; + +using UniqueShaderProgram = std::unique_ptr; +using UniqueVertexBufferResource = std::unique_ptr; + +class Context final : public gfx::Context { +public: + Context(RendererBackend&); + ~Context() noexcept override; + Context(const Context&) = delete; + Context& operator=(const Context& other) = delete; + + RendererBackend& getBackend() const { return backend; } + + void beginFrame() override; + void endFrame() override; + void waitFrame() const; + + std::unique_ptr createCommandEncoder() override; + + BufferResource createBuffer(const void* data, std::size_t size, std::uint32_t usage, bool persistent) const; + + UniqueShaderProgram createProgram(std::string name, + const std::string_view vertex, + const std::string_view fragment, + const ProgramParameters& programParameters, + const mbgl::unordered_map& additionalDefines); + + /// Called at the end of a frame. + void performCleanup() override {} + void reduceMemoryUsage() override {} + + gfx::UniqueDrawableBuilder createDrawableBuilder(std::string name) override; + gfx::UniformBufferPtr createUniformBuffer(const void* data, std::size_t size, bool persistent) override; + + gfx::ShaderProgramBasePtr getGenericShader(gfx::ShaderRegistry&, const std::string& name) override; + + TileLayerGroupPtr createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) override; + + LayerGroupPtr createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) override; + + gfx::Texture2DPtr createTexture2D() override; + + RenderTargetPtr createRenderTarget(const Size size, const gfx::TextureChannelDataType type) override; + + void resetState(gfx::DepthMode, gfx::ColorMode) override {} + + bool emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr&, + const void* data, + std::size_t size, + bool persistent = false) override; + + void setDirtyState() override {} + + std::unique_ptr createOffscreenTexture(Size, gfx::TextureChannelDataType, bool, bool); + + std::unique_ptr createOffscreenTexture(Size, gfx::TextureChannelDataType) override; + + std::unique_ptr createTextureResource(Size, + gfx::TexturePixelType, + gfx::TextureChannelDataType) override; + + std::unique_ptr createRenderbufferResource(gfx::RenderbufferPixelType, + Size size) override; + + std::unique_ptr createDrawScopeResource() override; + + gfx::VertexAttributeArrayPtr createVertexAttributeArray() const override; + +#if !defined(NDEBUG) + void visualizeStencilBuffer() override; + void visualizeDepthBuffer(float depthRangeSize) override; +#endif + + void clearStencilBuffer(int32_t) override; + + /// Get the global uniform buffers + const gfx::UniformBufferArray& getGlobalUniformBuffers() const override { return globalUniformBuffers; }; + + /// Get the mutable global uniform buffer array + gfx::UniformBufferArray& mutableGlobalUniformBuffers() override { return globalUniformBuffers; }; + + /// Bind the global uniform buffers + void bindGlobalUniformBuffers(gfx::RenderPass&) const noexcept override; + + /// Unbind the global uniform buffers + void unbindGlobalUniformBuffers(gfx::RenderPass&) const noexcept override {} + + bool renderTileClippingMasks(gfx::RenderPass& renderPass, + RenderStaticData& staticData, + const std::vector& tileUBOs); + + const std::unique_ptr& getDummyVertexBuffer(); + const std::unique_ptr& getDummyUniformBuffer(); + const std::unique_ptr& getDummyTexture(); + const vk::UniqueDescriptorSetLayout& getUniformDescriptorSetLayout(); + const vk::UniqueDescriptorSetLayout& getImageDescriptorSetLayout(); + const std::vector& getDescriptorSetLayouts(); + const vk::UniquePipelineLayout& getGeneralPipelineLayout(); + const vk::UniquePipelineLayout& getPushConstantPipelineLayout(); + + uint8_t getCurrentFrameResourceIndex() const { return frameResourceIndex; } + const vk::UniqueDescriptorPool& getCurrentDescriptorPool() const; + void enqueueDeletion(std::function&& function); + void submitOneTimeCommand(const std::function& function); + +private: + struct FrameResources { + vk::UniqueCommandBuffer commandBuffer; + vk::UniqueDescriptorPool descriptorPool; + + vk::UniqueSemaphore surfaceSemaphore; + vk::UniqueSemaphore frameSemaphore; + vk::UniqueFence flightFrameFence; + + std::vector> deletionQueue; + + FrameResources(vk::UniqueCommandBuffer& cb, + vk::UniqueDescriptorPool&& dp, + vk::UniqueSemaphore&& surf, + vk::UniqueSemaphore&& frame, + vk::UniqueFence&& flight) + : commandBuffer(std::move(cb)), + descriptorPool(std::move(dp)), + surfaceSemaphore(std::move(surf)), + frameSemaphore(std::move(frame)), + flightFrameFence(std::move(flight)) {} + + void runDeletionQueue(const Context&); + }; + + void initFrameResources(); + void destroyResources(); + +private: + RendererBackend& backend; + + vulkan::UniformBufferArray globalUniformBuffers; + + std::unique_ptr dummyVertexBuffer; + std::unique_ptr dummyUniformBuffer; + std::unique_ptr dummyTexture2D; + vk::UniqueDescriptorSetLayout uniformDescriptorSetLayout; + vk::UniqueDescriptorSetLayout imageDescriptorSetLayout; + std::vector descriptorSetLayouts; + vk::UniquePipelineLayout generalPipelineLayout; + vk::UniquePipelineLayout pushConstantPipelineLayout; + + uint8_t frameResourceIndex = 0; + std::vector frameResources; + + struct { + gfx::ShaderProgramBasePtr shader; + std::optional vertexBuffer; + std::optional indexBuffer; + uint32_t indexCount = 0; + + PipelineInfo pipelineInfo; + } clipping; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/drawable.hpp b/include/mbgl/vulkan/drawable.hpp new file mode 100644 index 000000000..92bf8aaf9 --- /dev/null +++ b/include/mbgl/vulkan/drawable.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#include + +namespace mbgl { + +namespace gfx { + +class UploadPass; +class DepthMode; +class StencilMode; + +} // namespace gfx + +namespace vulkan { + +class CommandEncoder; +class UploadPass; + +class Drawable : public gfx::Drawable { +public: + Drawable(std::string name); + ~Drawable() override; + + void upload(gfx::UploadPass&); + void draw(PaintParameters&) const override; + + void setIndexData(gfx::IndexVectorBasePtr, std::vector segments) override; + void setVertices(std::vector&&, std::size_t, gfx::AttributeDataType) override; + + const gfx::UniformBufferArray& getUniformBuffers() const override; + gfx::UniformBufferArray& mutableUniformBuffers() override; + + void setEnableColor(bool value) override; + void setColorMode(const gfx::ColorMode& value) override; + void setEnableDepth(bool value) override; + void setDepthType(gfx::DepthMaskType value) override; + void setDepthModeFor3D(const gfx::DepthMode& value); + void setStencilModeFor3D(const gfx::StencilMode& value); + + void setLineWidth(int32_t value) override; + void setCullFaceMode(const gfx::CullFaceMode&) override; + +protected: + void buildVulkanInputBindings() noexcept; + + bool bindAttributes(CommandEncoder&) const noexcept; + bool bindDescriptors(CommandEncoder&) const noexcept; + + void uploadTextures(UploadPass&) const noexcept; + + class Impl; + const std::unique_ptr impl; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/drawable_builder.hpp b/include/mbgl/vulkan/drawable_builder.hpp new file mode 100644 index 000000000..3dc69494d --- /dev/null +++ b/include/mbgl/vulkan/drawable_builder.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace mbgl { +namespace vulkan { + +/** + Base class for Vulkan-specific drawable builders. + */ +class DrawableBuilder final : public gfx::DrawableBuilder { +public: + DrawableBuilder(std::string name_) + : gfx::DrawableBuilder(std::move(name_)) {} + ~DrawableBuilder() override = default; + + using DrawSegment = gfx::Drawable::DrawSegment; + std::unique_ptr createSegment(gfx::DrawMode, SegmentBase&&) override; + +protected: + gfx::UniqueDrawable createDrawable() const override; + + /// Setup the SDK-specific aspects after all the values are present + void init() override; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/index_buffer_resource.hpp b/include/mbgl/vulkan/index_buffer_resource.hpp new file mode 100644 index 000000000..79f3e132b --- /dev/null +++ b/include/mbgl/vulkan/index_buffer_resource.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace vulkan { + +class IndexBufferResource : public gfx::IndexBufferResource { +public: + IndexBufferResource() noexcept = delete; + IndexBufferResource(BufferResource&&) noexcept; + IndexBufferResource(IndexBufferResource&& other) noexcept + : buffer(std::move(other.buffer)) {} + ~IndexBufferResource() noexcept override; + + std::size_t getSizeInBytes() const noexcept { return buffer.getSizeInBytes(); } + const void* contents() const noexcept { return buffer.contents(); } + + BufferResource& get() noexcept { return buffer; } + const BufferResource& get() const noexcept { return buffer; } + +protected: + BufferResource buffer; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/layer_group.hpp b/include/mbgl/vulkan/layer_group.hpp new file mode 100644 index 000000000..b94779483 --- /dev/null +++ b/include/mbgl/vulkan/layer_group.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +namespace mbgl { + +class PaintParameters; + +namespace vulkan { + +class RenderPass; + +/** + A layer group for non-tile-based drawables + */ +class LayerGroup : public mbgl::LayerGroup { +public: + LayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name); + ~LayerGroup() override {} + + void upload(gfx::UploadPass&) override; + void render(RenderOrchestrator&, PaintParameters&) override; + + const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; + + gfx::UniformBufferArray& mutableUniformBuffers() override { return uniformBuffers; }; + + void bindUniformBuffers(RenderPass&) const noexcept; + void unbindUniformBuffers(RenderPass&) const noexcept {} + +protected: + UniformBufferArray uniformBuffers; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/offscreen_texture.hpp b/include/mbgl/vulkan/offscreen_texture.hpp new file mode 100644 index 000000000..0c466cab7 --- /dev/null +++ b/include/mbgl/vulkan/offscreen_texture.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace vulkan { + +class Context; +class Texture2D; + +class OffscreenTexture final : public gfx::OffscreenTexture { +public: + OffscreenTexture(Context&, Size size, gfx::TextureChannelDataType type, bool depth, bool stencil); + + bool isRenderable() override; + + PremultipliedImage readStillImage() override; + const gfx::Texture2DPtr& getTexture() override; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/pipeline.hpp b/include/mbgl/vulkan/pipeline.hpp new file mode 100644 index 000000000..00be06fa3 --- /dev/null +++ b/include/mbgl/vulkan/pipeline.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +class PipelineInfo final { +public: + PipelineInfo() noexcept = default; + ~PipelineInfo() noexcept = default; + + // used to select the pipeline layout + bool usePushConstants = false; + + vk::PrimitiveTopology topology = vk::PrimitiveTopology::eTriangleList; + vk::CullModeFlagBits cullMode = vk::CullModeFlagBits::eNone; + vk::FrontFace frontFace = vk::FrontFace::eClockwise; + vk::PolygonMode polygonMode = vk::PolygonMode::eFill; + + bool colorBlend = true; + vk::BlendOp colorBlendFunction = vk::BlendOp::eAdd; + vk::BlendFactor srcBlendFactor = vk::BlendFactor::eOne; + vk::BlendFactor dstBlendFactor = vk::BlendFactor::eZero; + vk::ColorComponentFlags colorMask = vk::FlagTraits::allFlags; + + bool depthTest = false; + bool depthWrite = true; + vk::CompareOp depthFunction = vk::CompareOp::eAlways; + + bool stencilTest = false; + vk::CompareOp stencilFunction = vk::CompareOp::eNever; + vk::StencilOp stencilPass = vk::StencilOp::eKeep; + vk::StencilOp stencilFail = vk::StencilOp::eKeep; + vk::StencilOp stencilDepthFail = vk::StencilOp::eKeep; + + bool wideLines = false; + + // external values (used in hash) + vk::RenderPass renderPass{}; + vk::Extent2D viewExtent{}; + + // dynamic values (not part of the pipeline/ignored in hash) + struct { + std::optional> blendConstants; + uint32_t stencilWriteMask = 0; + uint32_t stencilCompareMask = 0; + uint32_t stencilRef = 0; + float lineWidth = 1.0f; + } dynamicValues; + + std::vector inputBindings; + std::vector inputAttributes; + +public: + static vk::Format vulkanFormat(const gfx::AttributeDataType& value); + static vk::PrimitiveTopology vulkanPrimitiveTopology(const gfx::DrawModeType& value); + static vk::CullModeFlagBits vulkanCullMode(const gfx::CullFaceSideType& value); + static vk::FrontFace vulkanFrontFace(const gfx::CullFaceWindingType& value); + static vk::BlendOp vulkanBlendOp(const gfx::ColorBlendEquationType& value); + static vk::BlendFactor vulkanBlendFactor(const gfx::ColorBlendFactorType& value); + static vk::CompareOp vulkanCompareOp(const gfx::DepthFunctionType& value); + static vk::CompareOp vulkanCompareOp(const gfx::StencilFunctionType& value); + static vk::StencilOp vulkanStencilOp(const gfx::StencilOpType& value); + + void setCullMode(const gfx::CullFaceMode& value); + void setDrawMode(const gfx::DrawModeType& value); + void setDrawMode(const gfx::DrawMode& value); + void setColorBlend(const gfx::ColorMode& value); + void setColorBlendFunction(const gfx::ColorMode::BlendFunction& value); + void setDepthWrite(const gfx::DepthMaskType& value); + void setDepthMode(const gfx::DepthMode& value); + void setStencilMode(const gfx::StencilMode& value); + void setRenderable(const gfx::Renderable& value); + void setLineWidth(float value); + + bool usesBlendConstants() const; + void updateVertexInputHash(); + std::size_t hash() const; + + void setDynamicValues(const vk::UniqueCommandBuffer& buffer) const; + std::vector getDynamicStates() const; + +protected: + std::size_t vertexInputHash{0}; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/render_pass.hpp b/include/mbgl/vulkan/render_pass.hpp new file mode 100644 index 000000000..ec23fe53e --- /dev/null +++ b/include/mbgl/vulkan/render_pass.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include +#include + +namespace mbgl { +namespace vulkan { + +class BufferResource; +class CommandEncoder; +class Context; + +class RenderPass final : public gfx::RenderPass { +public: + RenderPass(CommandEncoder&, const char* name, const gfx::RenderPassDescriptor&); + ~RenderPass() override; + + vulkan::CommandEncoder& getEncoder() { return commandEncoder; } + const gfx::RenderPassDescriptor& getDescriptor() { return descriptor; } + void endEncoding(); + + void clearStencil(uint32_t value = 0) const; + + void addDebugSignpost(const char* name) override; + + void bindVertex(const BufferResource&, std::size_t offset, std::size_t index, std::size_t size = 0); + void bindFragment(const BufferResource&, std::size_t offset, std::size_t index, std::size_t size = 0); + +private: + void pushDebugGroup(const char* name) override; + void popDebugGroup() override; + +private: + gfx::RenderPassDescriptor descriptor; + vulkan::CommandEncoder& commandEncoder; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/renderable_resource.hpp b/include/mbgl/vulkan/renderable_resource.hpp new file mode 100644 index 000000000..7a2bbf983 --- /dev/null +++ b/include/mbgl/vulkan/renderable_resource.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +class Context; + +class RenderableResource : public gfx::RenderableResource { +protected: + explicit RenderableResource(RendererBackend& backend_) + : backend(backend_) {} + ~RenderableResource() override = default; + +public: + const vk::Extent2D& getExtent() const { return extent; } + const vk::UniqueRenderPass& getRenderPass() const { return renderPass; } + virtual const vk::UniqueFramebuffer& getFramebuffer() const = 0; + +protected: + RendererBackend& backend; + + vk::Extent2D extent; + vk::UniqueRenderPass renderPass; +}; + +class SurfaceRenderableResource : public RenderableResource { +protected: + explicit SurfaceRenderableResource(RendererBackend& backend_) + : RenderableResource(backend_) {} + ~SurfaceRenderableResource() override; + + void initColor(uint32_t w, uint32_t h); + void initSwapchain(uint32_t w, uint32_t h); + + void initDepthStencil(); + +public: + virtual void createPlatformSurface() = 0; + virtual std::vector getDeviceExtensions() { return {}; } + + const vk::UniqueSurfaceKHR& getPlatformSurface() const { return surface; } + const vk::UniqueSwapchainKHR& getSwapchain() const { return swapchain; } + const vk::UniqueFramebuffer& getFramebuffer() const override; + + uint32_t getImageCount() const { return static_cast(swapchainFramebuffers.size()); }; + uint32_t getAcquiredImageIndex() const { return acquiredImageIndex; }; + void setAcquiredImageIndex(uint32_t index) { acquiredImageIndex = index; }; + const vk::Image getAcquiredImage() const; + + void init(uint32_t w, uint32_t h); + void recreateSwapchain(); + +protected: + vk::UniqueSurfaceKHR surface; + vk::UniqueSwapchainKHR swapchain; + + uint32_t acquiredImageIndex{0}; + + // optional color textures when no surface is available + std::vector colorAllocations; + + std::vector swapchainImages; + std::vector swapchainImageViews; + std::vector swapchainFramebuffers; + vk::Format colorFormat{vk::Format::eUndefined}; + + UniqueImageAllocation depthAllocation; + vk::Format depthFormat{vk::Format::eUndefined}; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/renderer_backend.hpp b/include/mbgl/vulkan/renderer_backend.hpp new file mode 100644 index 000000000..6c696573a --- /dev/null +++ b/include/mbgl/vulkan/renderer_backend.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include + +#ifdef _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#elif defined(__ANDROID__) +#define VK_USE_PLATFORM_ANDROID_KHR +#endif + +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#include + +#define VMA_VULKAN_VERSION 1000000 +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#include "vk_mem_alloc.h" + +#ifndef NDEBUG +#define ENABLE_VULKAN_VALIDATION +// #define ENABLE_VULKAN_GPU_ASSISTED_VALIDATION +// #define ENABLE_VMA_DEBUG +#endif + +namespace mbgl { + +class ProgramParameters; + +namespace vulkan { + +class RendererBackend : public gfx::RendererBackend { +public: + RendererBackend(gfx::ContextMode); + ~RendererBackend() override; + + /// One-time shader initialization + void initShaders(gfx::ShaderRegistry&, const ProgramParameters& programParameters) override; + void init(); + + const vk::UniqueInstance& getInstance() const { return instance; } + const vk::PhysicalDevice& getPhysicalDevice() const { return physicalDevice; } + const vk::UniqueDevice& getDevice() const { return device; } + const vk::UniqueCommandPool& getCommandPool() const { return commandPool; } + const vk::Queue& getGraphicsQueue() const { return graphicsQueue; } + const vk::Queue& getPresentQueue() const { return presentQueue; } + uint32_t getMaxFrames() const { return maxFrames; } + const VmaAllocator& getAllocator() const { return allocator; } + const vk::PhysicalDeviceProperties& getDeviceProperties() const { return physicalDeviceProperties; } + int32_t getGraphicsQueueIndex() const { return graphicsQueueIndex; } + int32_t getPresentQueueIndex() const { return presentQueueIndex; } + + template ::value>> + void setDebugName([[maybe_unused]] const T& object, [[maybe_unused]] const std::string& name) const { +#ifdef ENABLE_VULKAN_VALIDATION + if (!debugUtilsEnabled) return; + const uint64_t handle = reinterpret_cast(static_cast(object)); + device->setDebugUtilsObjectNameEXT(vk::DebugUtilsObjectNameInfoEXT() + .setObjectType(object.objectType) + .setObjectHandle(handle) + .setPObjectName(name.c_str())); +#endif + } + + void beginDebugLabel(const vk::CommandBuffer& buffer, const char* name, const std::array& color) const; + void endDebugLabel(const vk::CommandBuffer& buffer) const; + void insertDebugLabel(const vk::CommandBuffer& buffer, const char* name) const; + + void startFrameCapture(); + void endFrameCapture(); + +protected: + std::unique_ptr createContext() override; + + virtual std::vector getLayers(); + virtual std::vector getInstanceExtensions(); + virtual std::vector getDeviceExtensions(); + std::vector getDebugExtensions(); + + void initInstance(); + void initDebug(); + void initSurface(); + void initDevice(); + void initAllocator(); + void initSwapchain(); + void initCommandPool(); + void initFrameCapture(); + + void destroyResources(); + +protected: + vk::DynamicLoader dynamicLoader; + vk::UniqueInstance instance; + vk::UniqueDebugUtilsMessengerEXT debugUtilsCallback; + vk::UniqueDebugReportCallbackEXT debugReportCallback; + + vk::PhysicalDevice physicalDevice; + vk::UniqueDevice device; + vk::PhysicalDeviceProperties physicalDeviceProperties; + + int32_t graphicsQueueIndex = -1; + int32_t presentQueueIndex = -1; + + vk::Queue graphicsQueue; + vk::Queue presentQueue; + + vk::UniqueCommandPool commandPool; + uint32_t maxFrames = 1; + + VmaAllocator allocator; + + bool debugUtilsEnabled{false}; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/texture2d.hpp b/include/mbgl/vulkan/texture2d.hpp new file mode 100644 index 000000000..f238a3818 --- /dev/null +++ b/include/mbgl/vulkan/texture2d.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include + +#include + +namespace mbgl { +namespace vulkan { + +class Context; +class RenderPass; + +struct ImageAllocation { + const VmaAllocator& allocator; + VmaAllocation allocation{}; + VkImage image{}; + vk::UniqueImageView imageView{}; + + ImageAllocation() = delete; + ImageAllocation(ImageAllocation&) = delete; + ImageAllocation& operator=(const ImageAllocation& other) = delete; + + 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); + void destroy(); + + void setName(const std::string& name) const; +}; + +using UniqueImageAllocation = std::unique_ptr; +using SharedImageAllocation = std::shared_ptr; + +enum class Texture2DUsage { + ShaderInput, + Attachment, + Read, +}; + +class Texture2D : public gfx::Texture2D { +public: + Texture2D(Context& context_); + ~Texture2D() override; + + gfx::Texture2D& setSamplerConfiguration(const SamplerState&) noexcept override; + gfx::Texture2D& setFormat(gfx::TexturePixelType, gfx::TextureChannelDataType) noexcept override; + gfx::Texture2D& setSize(Size size_) noexcept override; + gfx::Texture2D& setImage(std::shared_ptr) noexcept override; + gfx::Texture2D& setUsage(Texture2DUsage) noexcept; + + Size getSize() const noexcept override { return size; } + size_t getDataSize() const noexcept override; + size_t getPixelStride() const noexcept override; + size_t numChannels() const noexcept override; + + void create() noexcept override; + + void upload() noexcept override; + void upload(const void* pixelData, const Size& size_) noexcept override; + void uploadSubRegion(const void* pixelData, const Size& size, uint16_t xOffset, uint16_t yOffset) noexcept override; + void uploadSubRegion(const void* pixelData, + const Size& size, + uint16_t xOffset, + uint16_t yOffset, + const vk::UniqueCommandBuffer& buffer) noexcept; + + bool needsUpload() const noexcept override { return !!imageData; }; + + vk::Format getVulkanFormat() const { return vulkanFormat(pixelFormat, channelType); } + + const vk::ImageLayout& getVulkanImageLayout() const { return imageLayout; } + const vk::UniqueImageView& getVulkanImageView() const { return imageAllocation->imageView; } + const vk::Sampler& getVulkanSampler(); + + void copyImage(vk::Image image); + std::shared_ptr readImage(); + +private: + static vk::Format vulkanFormat(const gfx::TexturePixelType, const gfx::TextureChannelDataType); + static vk::Filter vulkanFilter(const gfx::TextureFilterType); + static vk::SamplerAddressMode vulkanAddressMode(const gfx::TextureWrapType); + + void createTexture(); + void createSampler(); + + void destroyTexture(); + void destroySampler(); + + void transitionToTransferLayout(const vk::UniqueCommandBuffer&); + void transitionToShaderReadLayout(const vk::UniqueCommandBuffer&); + void transitionToGeneralLayout(const vk::UniqueCommandBuffer&); + +private: + Context& context; + + Size size{0, 0}; + gfx::TexturePixelType pixelFormat{gfx::TexturePixelType::RGBA}; + gfx::TextureChannelDataType channelType{gfx::TextureChannelDataType::UnsignedByte}; + SamplerState samplerState{}; + + std::shared_ptr imageData{nullptr}; + bool textureDirty{true}; + bool samplerStateDirty{true}; + + SharedImageAllocation imageAllocation; + vk::ImageLayout imageLayout{vk::ImageLayout::eUndefined}; + + vk::Sampler sampler{}; + + Texture2DUsage textureUsage{Texture2DUsage::ShaderInput}; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/tile_layer_group.hpp b/include/mbgl/vulkan/tile_layer_group.hpp new file mode 100644 index 000000000..2b08d7a99 --- /dev/null +++ b/include/mbgl/vulkan/tile_layer_group.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include + +namespace mbgl { + +class PaintParameters; + +namespace vulkan { + +class RenderPass; + +/** + A layer group for tile-based drawables + */ +class TileLayerGroup : public mbgl::TileLayerGroup { +public: + TileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name); + ~TileLayerGroup() override {} + + void upload(gfx::UploadPass&) override; + void render(RenderOrchestrator&, PaintParameters&) override; + + const gfx::UniformBufferArray& getUniformBuffers() const override { return uniformBuffers; }; + + gfx::UniformBufferArray& mutableUniformBuffers() override { return uniformBuffers; }; + + void bindUniformBuffers(RenderPass&) const noexcept; + void unbindUniformBuffers(RenderPass&) const noexcept {} + +protected: + UniformBufferArray uniformBuffers; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/uniform_block.hpp b/include/mbgl/vulkan/uniform_block.hpp new file mode 100644 index 000000000..e32c9ad60 --- /dev/null +++ b/include/mbgl/vulkan/uniform_block.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include + +namespace mbgl { +namespace vulkan { + +class UniformBlock final : public gfx::UniformBlock { +public: + UniformBlock(int index_, std::size_t size_) + : gfx::UniformBlock(index_, size_) {} + UniformBlock(const UniformBlock& other) + : gfx::UniformBlock(other) {} + UniformBlock(UniformBlock&& other) + : gfx::UniformBlock(std::move(other)) {} + ~UniformBlock() override = default; + + void bindBuffer(const gfx::UniformBuffer&) override {} + void unbindBuffer() override {} + + bool getBindVertex() const { return bindVertex; } + void setBindVertex(bool value) { bindVertex = value; } + + bool getBindFragment() const { return bindFragment; } + void setBindFragment(bool value) { bindFragment = value; } + +protected: + bool bindVertex = false; + bool bindFragment = false; +}; + +/// Stores a collection of uniform blocks by name +class UniformBlockArray final : public gfx::UniformBlockArray { +public: + UniformBlockArray() = default; + UniformBlockArray(UniformBlockArray&& other) + : gfx::UniformBlockArray(std::move(other)) {} + UniformBlockArray(const UniformBlockArray&) = delete; + + UniformBlockArray& operator=(UniformBlockArray&& other) { + gfx::UniformBlockArray::operator=(std::move(other)); + return *this; + } + UniformBlockArray& operator=(const UniformBlockArray& other) { + gfx::UniformBlockArray::operator=(other); + return *this; + } + +private: + std::unique_ptr create(int index, std::size_t size) override { + return std::make_unique(index, size); + } + std::unique_ptr copy(const gfx::UniformBlock& uniformBlocks) override { + return std::make_unique(static_cast(uniformBlocks)); + } +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/uniform_buffer.hpp b/include/mbgl/vulkan/uniform_buffer.hpp new file mode 100644 index 000000000..7ae14fd22 --- /dev/null +++ b/include/mbgl/vulkan/uniform_buffer.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace vulkan { + +class UniformBuffer final : public gfx::UniformBuffer { +public: + UniformBuffer(BufferResource&&); + UniformBuffer(const UniformBuffer&) = delete; + UniformBuffer(UniformBuffer&&); + ~UniformBuffer() override; + + const BufferResource& getBufferResource() const { return buffer; } + + UniformBuffer clone() const { return {buffer.clone()}; } + + void update(const void* data, std::size_t size_) override; + +protected: + BufferResource buffer; +}; + +/// Stores a collection of uniform buffers by name +class UniformBufferArray final : public gfx::UniformBufferArray { +public: + UniformBufferArray() = default; + UniformBufferArray(UniformBufferArray&& other) + : gfx::UniformBufferArray(std::move(other)) {} + UniformBufferArray(const UniformBufferArray&) = delete; + + UniformBufferArray& operator=(UniformBufferArray&& other) { + gfx::UniformBufferArray::operator=(std::move(other)); + return *this; + } + UniformBufferArray& operator=(const UniformBufferArray& other) { + gfx::UniformBufferArray::operator=(other); + return *this; + } + +private: + gfx::UniqueUniformBuffer copy(const gfx::UniformBuffer& buffer) override { + return std::make_unique(static_cast(buffer).clone()); + } +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/upload_pass.hpp b/include/mbgl/vulkan/upload_pass.hpp new file mode 100644 index 000000000..d799b14b0 --- /dev/null +++ b/include/mbgl/vulkan/upload_pass.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace gfx { + +class CommandEncoder; +class Renderable; +class VertexVectorBase; +using VertexVectorBasePtr = std::shared_ptr; + +} // namespace gfx + +namespace vulkan { + +class BufferResource; +class CommandEncoder; +class Context; +class VertexArray; +class Texture2D; + +class RenderbufferResource : public gfx::RenderbufferResource { +public: + RenderbufferResource() = default; +}; + +class UploadPass final : public gfx::UploadPass { +public: + UploadPass(gfx::Renderable&, CommandEncoder&, const char* name); + ~UploadPass() override; + + gfx::Context& getContext() override; + const gfx::Context& getContext() const override; + + std::unique_ptr createVertexBufferResource(const void* data, + std::size_t size, + gfx::BufferUsageType, + bool persistent) override; + void updateVertexBufferResource(gfx::VertexBufferResource&, const void* data, std::size_t size) override; + + std::unique_ptr createIndexBufferResource(const void* data, + std::size_t size, + gfx::BufferUsageType, + bool persistent) override; + void updateIndexBufferResource(gfx::IndexBufferResource&, const void* data, std::size_t size) override; + + const gfx::UniqueVertexBufferResource& getBuffer(const gfx::VertexVectorBasePtr&, gfx::BufferUsageType); + + gfx::AttributeBindingArray buildAttributeBindings( + const std::size_t vertexCount, + const gfx::AttributeDataType vertexType, + const std::size_t vertexAttributeIndex, + const std::vector& vertexData, + const gfx::VertexAttributeArray& defaults, + const gfx::VertexAttributeArray& overrides, + gfx::BufferUsageType, + /*out*/ std::vector>& outBuffers) override; + + std::unique_ptr createTextureResource(Size, + const void* data, + gfx::TexturePixelType, + gfx::TextureChannelDataType) override; + void updateTextureResource( + gfx::TextureResource&, Size, const void* data, gfx::TexturePixelType, gfx::TextureChannelDataType) override; + + void updateTextureResourceSub(gfx::TextureResource&, + uint16_t xOffset, + uint16_t yOffset, + Size, + const void* data, + gfx::TexturePixelType, + gfx::TextureChannelDataType) override; + +private: + void pushDebugGroup(const char* name) override; + void popDebugGroup() override; + + void endEncoding(); + +private: + CommandEncoder& commandEncoder; + std::vector> debugGroups; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/vertex_attribute.hpp b/include/mbgl/vulkan/vertex_attribute.hpp new file mode 100644 index 000000000..80bc0e40b --- /dev/null +++ b/include/mbgl/vulkan/vertex_attribute.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace gfx { +class VertexBufferResource; +} // namespace gfx +namespace vulkan { + +class VertexAttributeArray; +class UploadPass; + +class VertexAttribute final : public gfx::VertexAttribute { +public: + VertexAttribute(int index_, gfx::AttributeDataType dataType_, std::size_t count_) + : gfx::VertexAttribute(index_, dataType_, count_) {} + VertexAttribute(VertexAttribute&& other) + : gfx::VertexAttribute(std::move(other)) {} + VertexAttribute(const VertexAttribute& other) = delete; + ~VertexAttribute() override = default; + + static const gfx::UniqueVertexBufferResource& getBuffer(gfx::VertexAttribute&, + UploadPass&, + const gfx::BufferUsageType); +}; + +/// Stores a collection of vertex attributes by name +class VertexAttributeArray final : public gfx::VertexAttributeArray { +public: + VertexAttributeArray() = default; + VertexAttributeArray(VertexAttributeArray&& other) + : gfx::VertexAttributeArray(std::move(other)) {} + + VertexAttributeArray& operator=(VertexAttributeArray&& other) { + gfx::VertexAttributeArray::operator=(std::move(other)); + return *this; + } + VertexAttributeArray& operator=(const VertexAttributeArray& other) = delete; + + /// Indicates whether any values have changed + bool isDirty() const override; + +private: + gfx::UniqueVertexAttribute create(int index, gfx::AttributeDataType dataType, std::size_t count) const override { + return std::make_unique(index, dataType, count); + } +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/include/mbgl/vulkan/vertex_buffer_resource.hpp b/include/mbgl/vulkan/vertex_buffer_resource.hpp new file mode 100644 index 000000000..ac23cf832 --- /dev/null +++ b/include/mbgl/vulkan/vertex_buffer_resource.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include + +namespace mbgl { +namespace vulkan { + +class VertexBufferResource : public gfx::VertexBufferResource { +public: + VertexBufferResource() noexcept = delete; + VertexBufferResource(BufferResource&& buffer_) noexcept + : buffer(std::move(buffer_)) {} + VertexBufferResource(VertexBufferResource&& other) noexcept + : buffer(std::move(other.buffer)) {} + ~VertexBufferResource() noexcept override = default; + + std::size_t getSizeInBytes() const noexcept { return buffer.getSizeInBytes(); } + const void* contents() const noexcept { return buffer.contents(); } + + BufferResource& get() noexcept { return buffer; } + const BufferResource& get() const noexcept { return buffer; } + +protected: + BufferResource buffer; +}; + +using UniqueVertexBufferResource = std::unique_ptr; + +} // namespace vulkan +} // namespace mbgl diff --git a/metrics/ignores/linux-vulkan.json b/metrics/ignores/linux-vulkan.json new file mode 100644 index 000000000..78e8cf67c --- /dev/null +++ b/metrics/ignores/linux-vulkan.json @@ -0,0 +1,3 @@ +{ + "render-tests/fill-extrusion-color/function": "failing for Vulkan" +} \ No newline at end of file diff --git a/metrics/linux-vulkan.json b/metrics/linux-vulkan.json new file mode 100644 index 000000000..915a3e65f --- /dev/null +++ b/metrics/linux-vulkan.json @@ -0,0 +1,16 @@ +{ + "base_test_path": "integration", + "cache_path": "cache-style.db", + "expectation_paths": [ + ], + "ignore_paths": [ + "ignores/platform-all.json", + "ignores/platform-linux.json", + "ignores/linux-drawable.json", + "ignores/linux-vulkan.json" + ], + "metric_path": "linux-gcc8-release", + "probes": [ + "probeNetwork" + ] +} diff --git a/platform/default/include/mbgl/vulkan/headless_backend.hpp b/platform/default/include/mbgl/vulkan/headless_backend.hpp new file mode 100644 index 000000000..72fecf6b3 --- /dev/null +++ b/platform/default/include/mbgl/vulkan/headless_backend.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +class Texture2D; + +class HeadlessBackend final : public vulkan::RendererBackend, public gfx::HeadlessBackend { +public: + HeadlessBackend(Size = {256, 256}, + SwapBehaviour = SwapBehaviour::NoFlush, + gfx::ContextMode = gfx::ContextMode::Unique); + ~HeadlessBackend() override; + gfx::Renderable& getDefaultRenderable() override; + PremultipliedImage readStillImage() override; + RendererBackend* getRendererBackend() override; + + void swap(); + + class Impl { + public: + virtual ~Impl() = default; + virtual bool glNeedsActiveContextOnDestruction() const { return false; } + virtual void activateContext() {} + virtual void deactivateContext() {} + virtual void getContext() {} + }; + +private: + void activate() override; + void deactivate() override; + + void createImpl(); + +private: + std::unique_ptr impl; + bool active = false; + + std::unique_ptr texture; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/platform/default/src/mbgl/vulkan/headless_backend.cpp b/platform/default/src/mbgl/vulkan/headless_backend.cpp new file mode 100644 index 000000000..3dc2cec25 --- /dev/null +++ b/platform/default/src/mbgl/vulkan/headless_backend.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +class HeadlessRenderableResource final : public vulkan::SurfaceRenderableResource { +public: + HeadlessRenderableResource(HeadlessBackend& backend_) + : SurfaceRenderableResource(backend_) { + if (backend_.getDevice() && !renderPass) { + init(backend_.getSize().width, backend_.getSize().height); + } + } + + ~HeadlessRenderableResource() noexcept override = default; + + void createPlatformSurface() override {} + void bind() override {} +}; + +HeadlessBackend::HeadlessBackend(const Size size_, + gfx::HeadlessBackend::SwapBehaviour, + const gfx::ContextMode contextMode_) + : mbgl::vulkan::RendererBackend(contextMode_), + mbgl::gfx::HeadlessBackend(size_) { + init(); +} + +HeadlessBackend::~HeadlessBackend() { + gfx::BackendScope guard{*this, gfx::BackendScope::ScopeType::Implicit}; + + texture.reset(); + + // Explicitly reset the renderable resource + resource.reset(); + // Explicitly reset the context so that it is destructed and cleaned up + // before we destruct the impl object. + getThreadPool().runRenderJobs(true /* closeQueue */); + context.reset(); +} + +void HeadlessBackend::activate() { + active = true; + + if (!impl) { + createImpl(); + } + + assert(impl); +} + +void HeadlessBackend::deactivate() { + assert(impl); + active = false; +} + +gfx::Renderable& HeadlessBackend::getDefaultRenderable() { + if (!resource) { + resource = std::make_unique(*this); + } + return *this; +} + +void HeadlessBackend::swap() { + static_cast(*context).waitFrame(); +} + +PremultipliedImage HeadlessBackend::readStillImage() { + auto& contextImpl = static_cast(*context); + auto& resourceImpl = static_cast(*resource); + + if (!texture) { + texture = std::make_unique(contextImpl); + texture->setFormat(gfx::TexturePixelType::RGBA, gfx::TextureChannelDataType::UnsignedByte); + texture->setSize(size); + texture->setUsage(Texture2DUsage::Read); + } + + contextImpl.waitFrame(); + texture->copyImage(resourceImpl.getAcquiredImage()); + + return std::move(*texture->readImage()); +} + +RendererBackend* HeadlessBackend::getRendererBackend() { + return this; +} + +void HeadlessBackend::createImpl() { + assert(!impl); + impl = std::make_unique(); +} + +} // namespace vulkan + +namespace gfx { + +template <> +std::unique_ptr Backend::Create( + const Size size, gfx::Renderable::SwapBehaviour swapBehavior, const gfx::ContextMode contextMode) { + return std::make_unique(size, swapBehavior, contextMode); +} + +} // namespace gfx +} // namespace mbgl diff --git a/platform/glfw/CMakeLists.txt b/platform/glfw/CMakeLists.txt index 53eaec65c..ea9546420 100644 --- a/platform/glfw/CMakeLists.txt +++ b/platform/glfw/CMakeLists.txt @@ -47,6 +47,18 @@ if(MLN_WITH_EGL) ) endif() +if(MLN_WITH_VULKAN) + target_sources( + mbgl-glfw + PRIVATE ${PROJECT_SOURCE_DIR}/platform/glfw/glfw_vulkan_backend.cpp + ) + + target_compile_definitions( + mbgl-glfw + PRIVATE MLN_RENDER_BACKEND_VULKAN=1 + ) +endif() + target_include_directories( mbgl-glfw PRIVATE diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp index ffb1a44d1..a3e7226d5 100644 --- a/platform/glfw/glfw_view.cpp +++ b/platform/glfw/glfw_view.cpp @@ -46,6 +46,10 @@ #define GLFW_INCLUDE_ES3 #endif +#if MLN_RENDER_BACKEND_VULKAN +#define GLFW_INCLUDE_VULKAN +#endif + #define GL_GLEXT_PROTOTYPES #include @@ -1104,7 +1108,7 @@ void GLFWView::report(float duration) { std::ostringstream oss; oss.precision(2); oss << "Frame time: " << std::fixed << frameTime << "ms (" << 1000 / frameTime << "fps)"; - mbgl::Log::Info(mbgl::Event::OpenGL, oss.str()); + mbgl::Log::Info(mbgl::Event::Render, oss.str()); frames = 0; frameTime = 0; diff --git a/platform/glfw/glfw_vulkan_backend.cpp b/platform/glfw/glfw_vulkan_backend.cpp new file mode 100644 index 000000000..75a71eb14 --- /dev/null +++ b/platform/glfw/glfw_vulkan_backend.cpp @@ -0,0 +1,82 @@ +#include "glfw_vulkan_backend.hpp" + +#include +#include +#include + +#ifdef _WIN32 +#define VK_USE_PLATFORM_WIN32_KHR +#endif + +#define GLFW_INCLUDE_VULKAN +#include + +class GLFWVulkanRenderableResource final : public mbgl::vulkan::SurfaceRenderableResource { +public: + explicit GLFWVulkanRenderableResource(GLFWVulkanBackend& backend_) + : SurfaceRenderableResource(backend_) {} + + std::vector getDeviceExtensions() override { return {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; } + + void createPlatformSurface() override { + auto& glfwBackend = static_cast(backend); + + VkSurfaceKHR surface_; + + VkResult result = glfwCreateWindowSurface( + glfwBackend.getInstance().get(), glfwBackend.getWindow(), nullptr, &surface_); + if (result != VK_SUCCESS) throw std::runtime_error("Failed to create glfw window surface"); + + surface = vk::UniqueSurfaceKHR( + surface_, vk::ObjectDestroy(glfwBackend.getInstance().get())); + } + + void bind() override {} +}; + +GLFWVulkanBackend::GLFWVulkanBackend(GLFWwindow* window_, const bool) + : mbgl::vulkan::RendererBackend(mbgl::gfx::ContextMode::Unique), + mbgl::gfx::Renderable( + [window_] { + int fbWidth; + int fbHeight; + glfwGetFramebufferSize(window_, &fbWidth, &fbHeight); + return mbgl::Size{static_cast(fbWidth), static_cast(fbHeight)}; + }(), + std::make_unique(*this)), + window(window_) { + init(); +} + +GLFWVulkanBackend::~GLFWVulkanBackend() { + context.reset(); +} + +mbgl::Size GLFWVulkanBackend::getSize() const { + return size; +} + +void GLFWVulkanBackend::setSize(const mbgl::Size newSize) { + size = newSize; +} + +std::vector GLFWVulkanBackend::getInstanceExtensions() { + auto extensions = mbgl::vulkan::RendererBackend::getInstanceExtensions(); + + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::copy(glfwExtensions, glfwExtensions + glfwExtensionCount, std::back_inserter(extensions)); + return extensions; +} + +namespace mbgl { +namespace gfx { + +template <> +std::unique_ptr Backend::Create(GLFWwindow* window, bool capFrameRate) { + return std::make_unique(window, capFrameRate); +} + +} // namespace gfx +} // namespace mbgl diff --git a/platform/glfw/glfw_vulkan_backend.hpp b/platform/glfw/glfw_vulkan_backend.hpp new file mode 100644 index 000000000..49596e735 --- /dev/null +++ b/platform/glfw/glfw_vulkan_backend.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "glfw_backend.hpp" + +#include +#include + +struct GLFWwindow; + +class GLFWVulkanBackend final : public GLFWBackend, public mbgl::vulkan::RendererBackend, public mbgl::gfx::Renderable { +public: + GLFWVulkanBackend(GLFWwindow*, bool capFrameRate); + ~GLFWVulkanBackend() override; + + GLFWwindow* getWindow() { return window; } + + // GLFWRendererBackend implementation +public: + mbgl::gfx::RendererBackend& getRendererBackend() override { return *this; } + mbgl::Size getSize() const override; + void setSize(mbgl::Size) override; + + // mbgl::gfx::RendererBackend implementation +public: + mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } + +protected: + std::vector getInstanceExtensions() override; + + void activate() override {} + void deactivate() override {} + +private: + GLFWwindow* window; +}; diff --git a/platform/ios/test/common/BUILD.bazel b/platform/ios/test/common/BUILD.bazel index 5713f583f..63032ff29 100644 --- a/platform/ios/test/common/BUILD.bazel +++ b/platform/ios/test/common/BUILD.bazel @@ -41,7 +41,7 @@ ios_application( infoplists = [ "//render-test/ios:Info.plist", ], - minimum_os_version = "12.0", + minimum_os_version = "13.0", provisioning_profile = "//platform/ios:xcode_profile", visibility = [ "//visibility:public", diff --git a/platform/linux/linux.cmake b/platform/linux/linux.cmake index f816a4aad..7d1140f60 100644 --- a/platform/linux/linux.cmake +++ b/platform/linux/linux.cmake @@ -91,6 +91,18 @@ if(MLN_WITH_EGL) WL_EGL_PLATFORM ) endif() +elseif(MLN_WITH_VULKAN) + target_include_directories( + mbgl-core + PRIVATE + ${PROJECT_SOURCE_DIR}/vendor/Vulkan-Headers/include + ) + + target_sources( + mbgl-core + PRIVATE + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/vulkan/headless_backend.cpp + ) else() find_package(OpenGL REQUIRED GLX) target_sources( diff --git a/platform/windows/Get-VendorPackages.ps1 b/platform/windows/Get-VendorPackages.ps1 index fe18fc034..8ebac6c2e 100644 --- a/platform/windows/Get-VendorPackages.ps1 +++ b/platform/windows/Get-VendorPackages.ps1 @@ -23,6 +23,7 @@ switch($Renderer) 'EGL' { $renderer_packages = @('egl', 'opengl-registry'); break } 'OSMesa' { $renderer_packages = @(''); break } 'OpenGL' { $renderer_packages = @('opengl-registry'); break } + 'Vulkan' { $renderer_packages = @(''); break } } if(-not (Test-Path "$vcpkg_temp_dir\vcpkg.exe")) diff --git a/platform/windows/windows.cmake b/platform/windows/windows.cmake index bcc1b8d86..c1c590dd6 100644 --- a/platform/windows/windows.cmake +++ b/platform/windows/windows.cmake @@ -2,6 +2,8 @@ if(MLN_WITH_EGL) set(_RENDERER EGL) elseif(MLN_WITH_OSMESA) set(_RENDERER OSMesa) +elseif(MLN_WITH_VULKAN) + set(_RENDERER Vulkan) else() set(_RENDERER OpenGL) endif() @@ -120,6 +122,18 @@ elseif(MLN_WITH_OSMESA) OSMesa::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 + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/vulkan/headless_backend.cpp + ) else() find_package(OpenGL REQUIRED) target_sources( diff --git a/render-test/filesystem.hpp b/render-test/filesystem.hpp index cee7e9d91..63917c900 100644 --- a/render-test/filesystem.hpp +++ b/render-test/filesystem.hpp @@ -1,9 +1,9 @@ #pragma once -#include +#include namespace mbgl { -namespace filesystem = ghc::filesystem; +namespace filesystem = std::filesystem; } // namespace mbgl diff --git a/render-test/ios/BUILD.bazel b/render-test/ios/BUILD.bazel index 9f9dfd20f..8bcec3e6a 100644 --- a/render-test/ios/BUILD.bazel +++ b/render-test/ios/BUILD.bazel @@ -51,7 +51,7 @@ objc_library( ios_unit_test( name = "RenderTest", - minimum_os_version = "12.0", + minimum_os_version = "13.0", provisioning_profile = "//platform/ios:xcode_profile", runner = "@build_bazel_rules_apple//apple/testing/default_runner:ios_xctestrun_ordered_runner", test_host = "//platform/ios/test/common:RenderTestApp", diff --git a/render-test/manifest_parser.cpp b/render-test/manifest_parser.cpp index c0c205704..c6f716bee 100644 --- a/render-test/manifest_parser.cpp +++ b/render-test/manifest_parser.cpp @@ -103,12 +103,12 @@ mbgl::filesystem::path getValidPath(const std::string& manifestPath, const std:: std::optional ManifestParser::parseManifest(const std::string& manifestPath, std::string testFilter) { Manifest manifest; const auto filePath = mbgl::filesystem::path(manifestPath); - manifest.manifestPath = manifestPath.substr(0, manifestPath.find(filePath.filename())); + manifest.manifestPath = manifestPath.substr(0, manifestPath.find(filePath.filename().generic_string())); auto contents = readJson(filePath); if (!contents.is()) { mbgl::Log::Error(mbgl::Event::General, - "Provided manifest file: " + std::string(filePath) + " is not a valid json"); + "Provided manifest file: " + filePath.generic_string() + " is not a valid json"); return std::nullopt; } @@ -116,11 +116,12 @@ std::optional ManifestParser::parseManifest(const std::string& manifes if (document.HasMember("result_path")) { const auto& resultPathValue = document["result_path"]; if (!resultPathValue.IsString()) { - mbgl::Log::Warning(mbgl::Event::General, - "Invalid result_path is provided inside the manifest file: " + std::string(filePath)); + mbgl::Log::Warning( + mbgl::Event::General, + "Invalid result_path is provided inside the manifest file: " + filePath.generic_string()); return std::nullopt; } - manifest.resultPath = (getValidPath(manifest.manifestPath, resultPathValue.GetString()) / "").string(); + manifest.resultPath = (getValidPath(manifest.manifestPath, resultPathValue.GetString()) / "").generic_string(); if (manifest.resultPath.empty()) { return std::nullopt; } @@ -129,10 +130,10 @@ std::optional ManifestParser::parseManifest(const std::string& manifes const auto& cachePathValue = document["cache_path"]; if (!cachePathValue.IsString()) { mbgl::Log::Warning(mbgl::Event::General, - "Invalid cache_path is provided inside the manifest file: " + std::string(filePath)); + "Invalid cache_path is provided inside the manifest file: " + filePath.generic_string()); return std::nullopt; } - manifest.cachePath = (getValidPath(manifest.manifestPath, ".") / cachePathValue.GetString()).string(); + manifest.cachePath = (getValidPath(manifest.manifestPath, ".") / cachePathValue.GetString()).generic_string(); if (manifest.cachePath.empty()) { return std::nullopt; } @@ -141,8 +142,9 @@ std::optional ManifestParser::parseManifest(const std::string& manifes if (document.HasMember("access_token")) { const auto& apiKeyValue = document["access_token"]; if (!apiKeyValue.IsString()) { - mbgl::Log::Warning(mbgl::Event::General, - "Invalid access_token is provided inside the manifest file: " + std::string(filePath)); + mbgl::Log::Warning( + mbgl::Event::General, + "Invalid access_token is provided inside the manifest file: " + filePath.generic_string()); return std::nullopt; } manifest.apiKey = apiKeyValue.GetString(); @@ -157,7 +159,7 @@ std::optional ManifestParser::parseManifest(const std::string& manifes mbgl::Log::Warning(mbgl::Event::General, "Invalid base_test_path is provided inside the manifest " "file: " + - std::string(filePath)); + filePath.generic_string()); return std::nullopt; } baseTestPath = getValidPath(manifest.manifestPath, testPathValue.GetString()); @@ -169,8 +171,9 @@ std::optional ManifestParser::parseManifest(const std::string& manifes if (document.HasMember("metric_path")) { const auto& metricPathValue = document["metric_path"]; if (!metricPathValue.IsString()) { - mbgl::Log::Warning(mbgl::Event::General, - "Invalid metric_path is provided inside the manifest file: " + std::string(filePath)); + mbgl::Log::Warning( + mbgl::Event::General, + "Invalid metric_path is provided inside the manifest file: " + filePath.generic_string()); return std::nullopt; } expectedMetricPath = getValidPath(manifest.manifestPath, metricPathValue.GetString()); @@ -185,7 +188,7 @@ std::optional ManifestParser::parseManifest(const std::string& manifes mbgl::Log::Warning(mbgl::Event::General, "Provided expectation_paths inside the manifest file: %s is " "not a valid array" + - std::string(filePath)); + filePath.generic_string()); return std::nullopt; } for (const auto& value : expectationPathValue.GetArray()) { @@ -193,7 +196,7 @@ std::optional ManifestParser::parseManifest(const std::string& manifes mbgl::Log::Warning(mbgl::Event::General, "Invalid expectation path item is provided inside the " "manifest file: " + - std::string(filePath)); + filePath.generic_string()); return std::nullopt; } expectationPaths.emplace_back(getValidPath(manifest.manifestPath, value.GetString())); @@ -206,9 +209,9 @@ std::optional ManifestParser::parseManifest(const std::string& manifes if (document.HasMember("ignore_paths")) { const auto& ignorePathValue = document["ignore_paths"]; if (!ignorePathValue.IsArray()) { - mbgl::Log::Warning( - mbgl::Event::General, - "Provided ignore_paths inside the manifest file: " + std::string(filePath) + " is not a valid array"); + mbgl::Log::Warning(mbgl::Event::General, + "Provided ignore_paths inside the manifest file: " + filePath.generic_string() + + " is not a valid array"); return std::nullopt; } for (const auto& value : ignorePathValue.GetArray()) { @@ -216,7 +219,7 @@ std::optional ManifestParser::parseManifest(const std::string& manifes mbgl::Log::Warning(mbgl::Event::General, "Invalid ignore path item is provided inside the manifest " "file: " + - std::string(filePath)); + filePath.generic_string()); return std::nullopt; } ignorePaths.emplace_back(getValidPath(manifest.manifestPath, value.GetString())); @@ -232,7 +235,7 @@ std::optional ManifestParser::parseManifest(const std::string& manifes if (!probesValue.IsArray()) { mbgl::Log::Warning( mbgl::Event::General, - "Provided probes inside the manifest file: " + std::string(filePath) + "is not a valid array"); + "Provided probes inside the manifest file: " + filePath.generic_string() + "is not a valid array"); return std::nullopt; } for (const auto& value : probesValue.GetArray()) { @@ -240,7 +243,7 @@ std::optional ManifestParser::parseManifest(const std::string& manifes mbgl::Log::Warning(mbgl::Event::General, "Invalid probe type is provided inside the manifest " "file: " + - std::string(filePath)); + filePath.generic_string()); return std::nullopt; } manifest.probes.emplace(value.GetString()); @@ -251,7 +254,7 @@ std::optional ManifestParser::parseManifest(const std::string& manifes const auto& filterValue = document["filter"]; if (!filterValue.IsString()) { mbgl::Log::Warning(mbgl::Event::General, - "Invalid filter is provided inside the manifest file: " + std::string(filePath)); + "Invalid filter is provided inside the manifest file: " + filePath.generic_string()); return std::nullopt; } @@ -276,14 +279,14 @@ std::optional ManifestParser::parseManifest(const std::string& manifes for (auto& testPath : mbgl::filesystem::recursive_directory_iterator(path)) { // Skip paths that fail regexp search. - if (!testFilter.empty() && !std::regex_search(testPath.path().string(), std::regex(testFilter))) { + if (!testFilter.empty() && !std::regex_search(testPath.path().generic_string(), std::regex(testFilter))) { continue; } if (testPath.path().filename() == "style.json") { const auto defaultExpectationPath{std::move(mbgl::filesystem::path(testPath).remove_filename())}; const auto rootLength = manifest.testRootPath.length(); - auto testId = defaultExpectationPath.string(); + auto testId = defaultExpectationPath.generic_string(); testId = testId.substr(rootLength + 1, testId.length() - rootLength - 1); std::vector expectedMetricPaths{expectedMetricPath}; diff --git a/render-test/parser.cpp b/render-test/parser.cpp index bcc5527c4..1bd2ad138 100644 --- a/render-test/parser.cpp +++ b/render-test/parser.cpp @@ -171,7 +171,7 @@ std::string toJSON(const std::vector& features, unsigned indent, } JSONReply readJson(const mbgl::filesystem::path& jsonPath) { - auto maybeJSON = mbgl::util::readFile(jsonPath); + auto maybeJSON = mbgl::util::readFile(jsonPath.generic_string()); if (!maybeJSON) { return {std::string("Unable to open file ") + jsonPath.string()}; } @@ -311,17 +311,17 @@ std::vector readExpectedEntries(const std::regex& regex, const mbgl } // namespace std::vector readExpectedImageEntries(const mbgl::filesystem::path& base) { - static const std::regex regex(".*/expected.*.png"); + static const std::regex regex(".*[/\\\\]expected.*.png"); return readExpectedEntries(regex, base); } std::vector readExpectedMetricEntries(const mbgl::filesystem::path& base) { - static const std::regex regex(".*/metrics.json"); + static const std::regex regex(".*[/\\\\]metrics.json"); return readExpectedEntries(regex, base); } std::vector readExpectedJSONEntries(const mbgl::filesystem::path& base) { - static const std::regex regex(".*/expected.*.json"); + static const std::regex regex(".*[/\\\\]expected.*.json"); return readExpectedEntries(regex, base); } @@ -960,7 +960,7 @@ TestOperations parseTestOperations(TestMetadata& metadata) { } size_t size = 0; if (compressed) { - size = mbgl::util::compress(*mbgl::util::readFile(filePath)).size(); + size = mbgl::util::compress(*mbgl::util::readFile(filePath.generic_string())).size(); } else { size = mbgl::filesystem::file_size(filePath); } diff --git a/render-test/render_test.cpp b/render-test/render_test.cpp index 13a1ee615..ba5fadb06 100644 --- a/render-test/render_test.cpp +++ b/render-test/render_test.cpp @@ -127,7 +127,7 @@ ArgumentsTuple parseArguments(int argc, char** argv) { shuffle, online, seed, - manifestPath.string(), + manifestPath.generic_string(), updateResults, std::move(testFilter)}; } @@ -194,7 +194,7 @@ int runRenderTests(int argc, char** argv, std::function testStatus) { bool shouldIgnore = false; std::string ignoreReason; - const std::string ignoreName = id; + const mbgl::filesystem::path ignoreName(id); const auto it = std::find_if( ignores.cbegin(), ignores.cend(), [&ignoreName](auto pair) { return pair.first == ignoreName; }); if (it != ignores.end()) { @@ -266,7 +266,7 @@ int runRenderTests(int argc, char** argv, std::function testStatus) { } } - const std::string manifestName = mbgl::filesystem::path(manifestPath).stem(); + const std::string manifestName = mbgl::filesystem::path(manifestPath).stem().generic_string(); const std::string resultPath = manifest.getResultPath() + "/" + manifestName + ".html"; std::string resultsHTML = createResultPage(stats, metadatas, shuffle, seed); mbgl::util::write_file(resultPath, resultsHTML); @@ -300,7 +300,7 @@ int runRenderTests(int argc, char** argv, std::function testStatus) { 100.0 * stats.erroredTests / count); } - printf("Results at: %s\n", mbgl::filesystem::canonical(resultPath).c_str()); + printf("Results at: %s\n", mbgl::filesystem::canonical(resultPath).generic_string().c_str()); return returnCode; } diff --git a/render-test/runner.cpp b/render-test/runner.cpp index 401687a04..ddf00b023 100644 --- a/render-test/runner.cpp +++ b/render-test/runner.cpp @@ -290,7 +290,7 @@ void TestRunner::checkProbingResults(TestMetadata& resultMetadata) { const std::string& message = std::string()) { try { mbgl::filesystem::create_directories(path); - mbgl::util::write_file(path / "metrics.json", serializeMetrics(resultMetadata.metrics)); + mbgl::util::write_file((path / "metrics.json").generic_string(), serializeMetrics(resultMetadata.metrics)); resultMetadata.errorMessage += message; } catch (mbgl::filesystem::filesystem_error& ex) { const auto msg = "Failed to write metrics. path='" + path.string() + "' err='" + ex.what() + diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index f44bb7392..22d97a062 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -50,17 +50,11 @@ public: } static std::unique_ptr createInstance(gl::Context& context, - const ProgramParameters& programParameters, - const std::string& additionalDefines) { + [[maybe_unused]] const ProgramParameters& programParameters, + [[maybe_unused]] const std::string& additionalDefines) { #if MLN_RENDER_BACKEND_OPENGL constexpr auto backend = gfx::Backend::Type::OpenGL; -#elif MLN_RENDER_BACKEND_METAL - constexpr auto backend = gfx::Backend::Type::Metal; -#endif -#if MLN_RENDER_BACKEND_METAL - return std::make_unique(context); -#else // Compile the shader std::initializer_list vertexSource = { "#version 300 es\n", @@ -77,6 +71,8 @@ public: programParameters.fragmentSource(gfx::Backend::Type::OpenGL).c_str()}; return std::make_unique(context, vertexSource, fragmentSource); +#else + return std::make_unique(context); #endif } diff --git a/src/mbgl/programs/program.hpp b/src/mbgl/programs/program.hpp index 44d8d7d16..439c36ca5 100644 --- a/src/mbgl/programs/program.hpp +++ b/src/mbgl/programs/program.hpp @@ -14,7 +14,7 @@ #include #include -#if !MLN_RENDER_BACKEND_METAL +#if MLN_RENDER_BACKEND_OPENGL #include #endif @@ -52,12 +52,16 @@ public: std::unique_ptr> program; - Program(const ProgramParameters& programParameters) { + Program([[maybe_unused]] const ProgramParameters& programParameters) { switch (gfx::Backend::GetType()) { #if MLN_RENDER_BACKEND_METAL case gfx::Backend::Type::Metal: { break; } +#elif MLN_RENDER_BACKEND_VULKAN + case gfx::Backend::Type::Vulkan: { + break; + } #else // MLN_RENDER_BACKEND_OPENGL case gfx::Backend::Type::OpenGL: { program = std::make_unique>(programParameters.withDefaultSource( diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 5c01eb939..6ece031e9 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -18,7 +18,7 @@ #include #include -#if !MLN_RENDER_BACKEND_METAL +#if MLN_RENDER_BACKEND_OPENGL #include #endif @@ -261,9 +261,9 @@ public: std::unique_ptr> program; - SymbolProgram(const ProgramParameters& programParameters) { + SymbolProgram([[maybe_unused]] const ProgramParameters& programParameters) { switch (gfx::Backend::GetType()) { -#if !MLN_RENDER_BACKEND_METAL +#if MLN_RENDER_BACKEND_OPENGL case gfx::Backend::Type::OpenGL: { program = std::make_unique>(programParameters.withDefaultSource( {gfx::Backend::Type::OpenGL, diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp index 3bc95059f..6c6642147 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp @@ -87,6 +87,7 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters } auto& drawableUniforms = drawable.mutableUniformBuffers(); + switch (static_cast(drawable.getType())) { case RenderFillLayer::FillVariant::Fill: { const FillDrawableUBO drawableUBO = {/*.matrix=*/util::cast(matrix)}; diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 77ba42d48..bff2dbec8 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -16,6 +16,12 @@ #include #endif // MLN_RENDER_BACKEND_METAL +#if MLN_RENDER_BACKEND_VULKAN +#include +#include +#include +#endif // MLN_RENDER_BACKEND_VULKAN + namespace mbgl { TransformParameters::TransformParameters(const TransformState& state_) @@ -139,6 +145,11 @@ void PaintParameters::clearStencil() { 0, 0}}; mtlContext.renderTileClippingMasks(*renderPass, staticData, tileUBO); + context.renderingStats().stencilClears++; +#elif MLN_RENDER_BACKEND_VULKAN + const auto& vulkanRenderPass = static_cast(*renderPass); + vulkanRenderPass.clearStencil(); + context.renderingStats().stencilClears++; #else // !MLN_RENDER_BACKEND_METAL context.clearStencilBuffer(0b00000000); @@ -198,6 +209,39 @@ void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) { mtlContext.renderingStats().stencilUpdates++; } +#elif MLN_RENDER_BACKEND_VULKAN + + std::vector tileUBOs; + for (const auto& tileRef : *renderTiles) { + const auto& tileID = tileRef.get().id; + + const uint32_t stencilID = nextStencilID; + const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID)); + if (result.second) { + // inserted + nextStencilID++; + } else { + // already present + continue; + } + + if (tileUBOs.empty()) { + tileUBOs.reserve(count); + } + + tileUBOs.emplace_back(shaders::ClipUBO{util::cast(matrixForTile(tileID)), stencilID}); + } + + if (!tileUBOs.empty()) { +#if !defined(NDEBUG) + const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks"); +#endif + + auto& vulkanContext = static_cast(context); + vulkanContext.renderTileClippingMasks(*renderPass, staticData, tileUBOs); + vulkanContext.renderingStats().stencilUpdates++; + } + #else // !MLN_RENDER_BACKEND_METAL auto program = staticData.shaders->getLegacyGroup().get(); diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index b71429702..07aa75dfc 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -278,13 +278,14 @@ void Renderer::Impl::render(const RenderTree& renderTree, const auto debugGroup(parameters.encoder->createDebugGroup("common-3d")); parameters.pass = RenderPass::Pass3D; - if (!parameters.staticData.depthRenderbuffer || - parameters.staticData.depthRenderbuffer->getSize() != parameters.staticData.backendSize) { - parameters.staticData.depthRenderbuffer = - parameters.context.createRenderbuffer( - parameters.staticData.backendSize); - } - parameters.staticData.depthRenderbuffer->setShouldClear(true); + // TODO is this needed? + // if (!parameters.staticData.depthRenderbuffer || + // parameters.staticData.depthRenderbuffer->getSize() != parameters.staticData.backendSize) { + // parameters.staticData.depthRenderbuffer = + // parameters.context.createRenderbuffer( + // parameters.staticData.backendSize); + //} + // parameters.staticData.depthRenderbuffer->setShouldClear(true); } }; diff --git a/src/mbgl/shaders/vulkan/background.cpp b/src/mbgl/shaders/vulkan/background.cpp new file mode 100644 index 000000000..e6f4b4bbb --- /dev/null +++ b/src/mbgl/shaders/vulkan/background.cpp @@ -0,0 +1,28 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(BackgroundDrawableUBO), idBackgroundDrawableUBO}, + UniformBlockInfo{false, true, sizeof(BackgroundLayerUBO), idBackgroundLayerUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{false, true, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, false, sizeof(BackgroundPatternDrawableUBO), idBackgroundDrawableUBO}, + UniformBlockInfo{true, true, sizeof(BackgroundPatternLayerUBO), idBackgroundLayerUBO}, +}; +const std::array + ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Float3, idBackgroundPosVertexAttribute}, +}; +const std::array ShaderSource::textures = + {TextureInfo{0, idBackgroundImageTexture}}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/circle.cpp b/src/mbgl/shaders/vulkan/circle.cpp new file mode 100644 index 000000000..914fcbfa9 --- /dev/null +++ b/src/mbgl/shaders/vulkan/circle.cpp @@ -0,0 +1,24 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, false, sizeof(CircleDrawableUBO), idCircleDrawableUBO}, + UniformBlockInfo{true, true, sizeof(CircleEvaluatedPropsUBO), idCircleEvaluatedPropsUBO}, + UniformBlockInfo{true, false, sizeof(CircleInterpolateUBO), idCircleInterpolateUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idCirclePosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float4, idCircleColorVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idCircleRadiusVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idCircleBlurVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idCircleOpacityVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float4, idCircleStrokeColorVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idCircleStrokeWidthVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idCircleStrokeOpacityVertexAttribute}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/clipping_mask.cpp b/src/mbgl/shaders/vulkan/clipping_mask.cpp new file mode 100644 index 000000000..6a96a21b3 --- /dev/null +++ b/src/mbgl/shaders/vulkan/clipping_mask.cpp @@ -0,0 +1,10 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::attributes = + {AttributeInfo{0, gfx::AttributeDataType::Short2, idClippingMaskPosVertexAttribute}}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/collision.cpp b/src/mbgl/shaders/vulkan/collision.cpp new file mode 100644 index 000000000..28bfff6f5 --- /dev/null +++ b/src/mbgl/shaders/vulkan/collision.cpp @@ -0,0 +1,31 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = + { + UniformBlockInfo{true, true, sizeof(CollisionUBO), idCollisionUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idCollisionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idCollisionAnchorPosVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short2, idCollisionExtrudeVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::UShort2, idCollisionPlacedVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idCollisionShiftVertexAttribute}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, true, sizeof(CollisionUBO), idCollisionUBO}, +}; +const std::array + ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idCollisionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idCollisionAnchorPosVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short2, idCollisionExtrudeVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::UShort2, idCollisionPlacedVertexAttribute}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/debug.cpp b/src/mbgl/shaders/vulkan/debug.cpp new file mode 100644 index 000000000..42bd3d0f8 --- /dev/null +++ b/src/mbgl/shaders/vulkan/debug.cpp @@ -0,0 +1,17 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, true, sizeof(DebugUBO), idDebugUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idDebugPosVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idDebugOverlayTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/fill.cpp b/src/mbgl/shaders/vulkan/fill.cpp new file mode 100644 index 000000000..7ae42d8b6 --- /dev/null +++ b/src/mbgl/shaders/vulkan/fill.cpp @@ -0,0 +1,116 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(FillDrawableUBO), idFillDrawableUBO}, + UniformBlockInfo{true, false, sizeof(FillInterpolateUBO), idFillInterpolateUBO}, + UniformBlockInfo{true, true, sizeof(FillEvaluatedPropsUBO), idFillEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float4, idFillColorVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, +}; + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, false, sizeof(FillOutlineDrawableUBO), idFillDrawableUBO}, + UniformBlockInfo{true, false, sizeof(FillOutlineInterpolateUBO), idFillInterpolateUBO}, + UniformBlockInfo{true, true, sizeof(FillEvaluatedPropsUBO), idFillEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float4, idFillOutlineColorVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, +}; + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(FillPatternDrawableUBO), idFillDrawableUBO}, + UniformBlockInfo{true, true, sizeof(FillPatternTilePropsUBO), idFillTilePropsUBO}, + UniformBlockInfo{true, false, sizeof(FillPatternInterpolateUBO), idFillInterpolateUBO}, + UniformBlockInfo{true, true, sizeof(FillEvaluatedPropsUBO), idFillEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idFillPatternFromVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::UShort4, idFillPatternToVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idFillImageTexture}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(FillOutlinePatternDrawableUBO), idFillDrawableUBO}, + UniformBlockInfo{true, true, sizeof(FillOutlinePatternTilePropsUBO), idFillTilePropsUBO}, + UniformBlockInfo{true, false, sizeof(FillOutlinePatternInterpolateUBO), idFillInterpolateUBO}, + UniformBlockInfo{true, true, sizeof(FillEvaluatedPropsUBO), idFillEvaluatedPropsUBO}, +}; +const std::array + ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idFillPatternFromVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::UShort4, idFillPatternToVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute}, +}; +const std::array ShaderSource::textures = + { + TextureInfo{0, idFillImageTexture}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, false, sizeof(FillOutlineTriangulatedDrawableUBO), idFillDrawableUBO}, + UniformBlockInfo{true, true, sizeof(FillEvaluatedPropsUBO), idFillEvaluatedPropsUBO}, +}; +const std::array + ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, +}; + +const std::array ShaderSource::uniforms = + { + UniformBlockInfo{true, false, sizeof(FillExtrusionDrawableUBO), idFillExtrusionDrawableUBO}, + UniformBlockInfo{true, false, sizeof(FillExtrusionPropsUBO), idFillExtrusionPropsUBO}, + UniformBlockInfo{true, false, sizeof(FillExtrusionInterpolateUBO), idFillExtrusionInterpolateUBO}, +}; +const std::array ShaderSource::attributes = + { + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillExtrusionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short4, idFillExtrusionNormalEdVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float4, idFillExtrusionColorVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idFillExtrusionBaseVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idFillExtrusionHeightVertexAttribute}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(FillExtrusionDrawableUBO), idFillExtrusionDrawableUBO}, + UniformBlockInfo{true, true, sizeof(FillExtrusionPropsUBO), idFillExtrusionPropsUBO}, + UniformBlockInfo{true, true, sizeof(FillExtrusionTilePropsUBO), idFillExtrusionTilePropsUBO}, + UniformBlockInfo{true, false, sizeof(FillExtrusionInterpolateUBO), idFillExtrusionInterpolateUBO}, +}; +const std::array + ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idFillExtrusionPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short4, idFillExtrusionNormalEdVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idFillExtrusionBaseVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idFillExtrusionHeightVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::UShort4, idFillExtrusionPatternFromVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::UShort4, idFillExtrusionPatternToVertexAttribute}, +}; +const std::array + ShaderSource::textures = { + TextureInfo{0, idFillExtrusionImageTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/heatmap.cpp b/src/mbgl/shaders/vulkan/heatmap.cpp new file mode 100644 index 000000000..7ed3fcbf8 --- /dev/null +++ b/src/mbgl/shaders/vulkan/heatmap.cpp @@ -0,0 +1,32 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(HeatmapDrawableUBO), idHeatmapDrawableUBO}, + UniformBlockInfo{true, true, sizeof(HeatmapEvaluatedPropsUBO), idHeatmapEvaluatedPropsUBO}, + UniformBlockInfo{true, false, sizeof(HeatmapInterpolateUBO), idHeatmapInterpolateUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idHeatmapPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float2, idHeatmapWeightVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idHeatmapRadiusVertexAttribute}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(HeatmapTexturePropsUBO), idHeatmapTexturePropsUBO}, +}; +const std::array ShaderSource::attributes = + { + AttributeInfo{0, gfx::AttributeDataType::Short2, idHeatmapPosVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idHeatmapImageTexture}, + TextureInfo{1, idHeatmapColorRampTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/hillshade.cpp b/src/mbgl/shaders/vulkan/hillshade.cpp new file mode 100644 index 000000000..a71502dea --- /dev/null +++ b/src/mbgl/shaders/vulkan/hillshade.cpp @@ -0,0 +1,32 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, true, sizeof(HillshadePrepareDrawableUBO), idHillshadePrepareDrawableUBO}, +}; +const std::array + ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idHillshadePosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idHillshadeTexturePosVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idHillshadeImageTexture}, +}; + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, true, sizeof(HillshadeDrawableUBO), idHillshadeDrawableUBO}, + UniformBlockInfo{false, true, sizeof(HillshadeEvaluatedPropsUBO), idHillshadeEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idHillshadePosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idHillshadeTexturePosVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idHillshadeImageTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/line.cpp b/src/mbgl/shaders/vulkan/line.cpp new file mode 100644 index 000000000..80c541e72 --- /dev/null +++ b/src/mbgl/shaders/vulkan/line.cpp @@ -0,0 +1,90 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, true, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, false, sizeof(LineDrawableUBO), idLineDrawableUBO}, + UniformBlockInfo{true, false, sizeof(LineInterpolationUBO), idLineInterpolationUBO}, + UniformBlockInfo{true, true, sizeof(LineEvaluatedPropsUBO), idLineEvaluatedPropsUBO}, +}; + +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float4, idLineColorVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, +}; + +const std::array ShaderSource::uniforms = + { + UniformBlockInfo{true, true, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, false, sizeof(LineDrawableUBO), idLineDrawableUBO}, + UniformBlockInfo{true, false, sizeof(LineInterpolationUBO), idLineInterpolationUBO}, + UniformBlockInfo{true, true, sizeof(LineEvaluatedPropsUBO), idLineEvaluatedPropsUBO}, +}; + +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, +}; + +const std::array ShaderSource::textures = { + TextureInfo{0, idLineImageTexture}, +}; + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, true, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(LinePatternDrawableUBO), idLineDrawableUBO}, + UniformBlockInfo{true, false, sizeof(LinePatternInterpolationUBO), idLineInterpolationUBO}, + UniformBlockInfo{true, true, sizeof(LinePatternTilePropertiesUBO), idLineTilePropertiesUBO}, + UniformBlockInfo{true, true, sizeof(LineEvaluatedPropsUBO), idLineEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::UShort4, idLinePatternFromVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::UShort4, idLinePatternToVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idLineImageTexture}, +}; + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, true, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(LineSDFDrawableUBO), idLineDrawableUBO}, + UniformBlockInfo{true, false, sizeof(LineSDFInterpolationUBO), idLineInterpolationUBO}, + UniformBlockInfo{true, true, sizeof(LineEvaluatedPropsUBO), idLineEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float4, idLineColorVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::Float2, idLineFloorWidthVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idLineImageTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/raster.cpp b/src/mbgl/shaders/vulkan/raster.cpp new file mode 100644 index 000000000..9e7db414f --- /dev/null +++ b/src/mbgl/shaders/vulkan/raster.cpp @@ -0,0 +1,20 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(RasterDrawableUBO), idRasterDrawableUBO}, + UniformBlockInfo{true, true, sizeof(RasterEvaluatedPropsUBO), idRasterEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idRasterPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idRasterTexturePosVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idRasterImage0Texture}, + TextureInfo{1, idRasterImage1Texture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/shader_program.cpp b/src/mbgl/shaders/vulkan/shader_program.cpp new file mode 100644 index 000000000..f86e07e24 --- /dev/null +++ b/src/mbgl/shaders/vulkan/shader_program.cpp @@ -0,0 +1,269 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace std::string_literals; + +namespace mbgl { + +namespace vulkan { + +ShaderProgram::ShaderProgram(const std::string& name, + const std::string_view& vertex, + const std::string_view& fragment, + const ProgramParameters& programParameters, + const mbgl::unordered_map& additionalDefines, + RendererBackend& backend_) + : ShaderProgramBase(), + shaderName(name), + backend(backend_) { + constexpr auto targetClientVersion = glslang::EShTargetVulkan_1_0; + constexpr auto targetLanguageVersion = glslang::EShTargetSpv_1_0; + constexpr auto defaultVersion = 450; + constexpr auto messages = EShMsgSpvRules | EShMsgVulkanRules; + const auto defaultResources = GetDefaultResources(); + + std::string defineStr = programParameters.getDefinesString() + "\n\n"; + for (const auto& define : additionalDefines) { + defineStr += "#define " + define.first + " " + define.second + "\n"; + } + + const auto compileGlsl = [&](const EShLanguage& language, const std::string_view& data, const char* prelude) { + glslang::TShader glslShader(language); + + const auto preamble = defineStr + "\n" + prelude; + const char* shaderData = data.data(); + const int shaderDataSize = data.size(); + + glslShader.setPreamble(preamble.c_str()); + glslShader.setStringsWithLengths(&shaderData, &shaderDataSize, 1); + glslShader.setEnvClient(glslang::EShClientVulkan, targetClientVersion); + glslShader.setEnvTarget(glslang::EShTargetSpv, targetLanguageVersion); + glslShader.setEntryPoint("main"); + + if (!glslShader.parse(defaultResources, defaultVersion, ENoProfile, false, true, messages)) { + mbgl::Log::Error(mbgl::Event::Shader, shaderName + " - " + glslShader.getInfoLog()); + return std::vector(); + } + + glslang::TProgram glslProgram; + glslProgram.addShader(&glslShader); + + if (!glslProgram.link(messages)) { + mbgl::Log::Error(mbgl::Event::Shader, shaderName + " - " + glslProgram.getInfoLog()); + return std::vector(); + } + + const auto intermediate = glslProgram.getIntermediate(language); + + std::vector spirv; + glslang::GlslangToSpv(*intermediate, spirv); + + return spirv; + }; + + const auto& vertexSpirv = compileGlsl( + EShLanguage::EShLangVertex, + vertex, + shaders::ShaderSource::vertex); + const auto& fragmentSpirv = compileGlsl( + EShLanguage::EShLangFragment, + fragment, + shaders::ShaderSource::fragment); + + if (vertexSpirv.empty() || fragmentSpirv.empty()) return; + + const auto& device = backend.getDevice(); + + vertexShader = device->createShaderModuleUnique( + vk::ShaderModuleCreateInfo(vk::ShaderModuleCreateFlags(), vertexSpirv)); + fragmentShader = device->createShaderModuleUnique( + vk::ShaderModuleCreateInfo(vk::ShaderModuleCreateFlags(), fragmentSpirv)); + + backend.setDebugName(vertexShader.get(), shaderName + ".vert"); + backend.setDebugName(fragmentShader.get(), shaderName + ".frag"); +} + +ShaderProgram::~ShaderProgram() noexcept = default; + +const vk::UniquePipeline& ShaderProgram::getPipeline(const PipelineInfo& pipelineInfo) { + auto& pipeline = pipelines[pipelineInfo.hash()]; + if (pipeline) return pipeline; + + const auto vertexInputState = vk::PipelineVertexInputStateCreateInfo() + .setVertexBindingDescriptions(pipelineInfo.inputBindings) + .setVertexAttributeDescriptions(pipelineInfo.inputAttributes); + + const auto inputAssemblyState = vk::PipelineInputAssemblyStateCreateInfo().setTopology(pipelineInfo.topology); + + const auto renderableExtent = pipelineInfo.viewExtent; + + const vk::Viewport viewportExtent(0.0f, 0.0f, renderableExtent.width, renderableExtent.height, 0.0f, 1.0f); + const vk::Rect2D scissorRect({}, {renderableExtent.width, renderableExtent.height}); + + const auto viewportState = vk::PipelineViewportStateCreateInfo() + .setViewportCount(1) + .setPViewports(&viewportExtent) + .setScissorCount(1) + .setPScissors(&scissorRect); + + const auto rasterState = vk::PipelineRasterizationStateCreateInfo() + .setCullMode(pipelineInfo.cullMode) + .setFrontFace(pipelineInfo.frontFace) + .setPolygonMode(pipelineInfo.polygonMode) + .setLineWidth(1.0f); + + const auto multisampleState = vk::PipelineMultisampleStateCreateInfo().setRasterizationSamples( + vk::SampleCountFlagBits::e1); + + const auto stencilState = vk::StencilOpState() + .setCompareOp(pipelineInfo.stencilFunction) + .setPassOp(pipelineInfo.stencilPass) + .setFailOp(pipelineInfo.stencilFail) + .setDepthFailOp(pipelineInfo.stencilDepthFail); + + const auto depthStencilState = vk::PipelineDepthStencilStateCreateInfo() + .setDepthTestEnable(pipelineInfo.depthTest) + .setDepthWriteEnable(pipelineInfo.depthWrite) + .setDepthBoundsTestEnable(false) + .setMinDepthBounds(0.0f) + .setMaxDepthBounds(1.0f) + .setDepthCompareOp(pipelineInfo.depthFunction) + .setStencilTestEnable(pipelineInfo.stencilTest) + .setFront(stencilState) + .setBack(stencilState); + + const auto colorBlendAttachments = vk::PipelineColorBlendAttachmentState() + .setBlendEnable(pipelineInfo.colorBlend) + .setColorBlendOp(pipelineInfo.colorBlendFunction) + .setSrcColorBlendFactor(pipelineInfo.srcBlendFactor) + .setDstColorBlendFactor(pipelineInfo.dstBlendFactor) + .setAlphaBlendOp(pipelineInfo.colorBlendFunction) + .setSrcAlphaBlendFactor(pipelineInfo.srcBlendFactor) + .setDstAlphaBlendFactor(pipelineInfo.dstBlendFactor) + .setColorWriteMask(pipelineInfo.colorMask); + + const auto colorBlendState = vk::PipelineColorBlendStateCreateInfo() + .setAttachmentCount(1) + .setPAttachments(&colorBlendAttachments) + .setLogicOpEnable(VK_FALSE) + .setLogicOp(vk::LogicOp::eCopy); + + // values available for core 1.0 + // vk::DynamicState::eViewport, + // vk::DynamicState::eScissor, + // vk::DynamicState::eLineWidth, + // vk::DynamicState::eStencilCompareMask, + // vk::DynamicState::eStencilWriteMask, + // vk::DynamicState::eStencilReference, + // vk::DynamicState::eBlendConstants, + // vk::DynamicState::eDepthBias, + // vk::DynamicState::eDepthBounds, + + const auto& dynamicValues = pipelineInfo.getDynamicStates(); + const vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicValues); + + const auto& device = backend.getDevice(); + auto& context = static_cast(backend.getContext()); + const auto& pipelineLayout = pipelineInfo.usePushConstants ? context.getPushConstantPipelineLayout() + : context.getGeneralPipelineLayout(); + + const std::array shaderStages = { + vk::PipelineShaderStageCreateInfo() + .setStage(vk::ShaderStageFlagBits::eVertex) + .setModule(vertexShader.get()) + .setPName("main"), + + vk::PipelineShaderStageCreateInfo() + .setStage(vk::ShaderStageFlagBits::eFragment) + .setModule(fragmentShader.get()) + .setPName("main")}; + + const auto pipelineCreateInfo = vk::GraphicsPipelineCreateInfo() + .setStages(shaderStages) + .setPVertexInputState(&vertexInputState) + .setPInputAssemblyState(&inputAssemblyState) + .setPViewportState(&viewportState) + .setPRasterizationState(&rasterState) + .setPMultisampleState(&multisampleState) + .setPDepthStencilState(&depthStencilState) + .setPColorBlendState(&colorBlendState) + .setPDynamicState(&dynamicState) + .setLayout(pipelineLayout.get()) + .setRenderPass(pipelineInfo.renderPass); + + pipeline = std::move(device->createGraphicsPipelineUnique(nullptr, pipelineCreateInfo).value); + backend.setDebugName(pipeline.get(), shaderName + "_pipeline"); + + return pipeline; +} + +bool ShaderProgram::hasTextures() const { + return std::any_of( + textureBindings.begin(), textureBindings.end(), [](const auto& texture) { return texture.has_value(); }); +} + +void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) { + const auto index = static_cast(info.index); +#if !defined(NDEBUG) + // Indexes must be unique, if there's a conflict check the `attributes` array in the shader + vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); + instanceAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); + uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); +#endif + vertexAttributes.set(info.id, index, info.dataType, 1); +} + +void ShaderProgram::initInstanceAttribute(const shaders::AttributeInfo& info) { + // Index is the block index of the instance attribute + const auto index = static_cast(info.index); +#if !defined(NDEBUG) + // Indexes must not be reused by regular attributes or uniform blocks + // More than one instance attribute can have the same index, if they share the block + vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); + uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); +#endif + instanceAttributes.set(info.id, index, info.dataType, 1); +} + +void ShaderProgram::initUniformBlock(const shaders::UniformBlockInfo& info) { + const auto index = static_cast(info.index); +#if !defined(NDEBUG) + // Indexes must be unique, if there's a conflict check the `attributes` array in the shader + uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); +#endif + if (const auto& block_ = uniformBlocks.set(info.id, index, info.size)) { + auto& block = static_cast(*block_); + block.setBindVertex(info.vertex); + block.setBindFragment(info.fragment); + } +} + +void ShaderProgram::initTexture(const shaders::TextureInfo& info) { + assert(info.id < textureBindings.size()); + if (info.id >= textureBindings.size()) { + return; + } + textureBindings[info.id] = info.index; +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/shaders/vulkan/symbol.cpp b/src/mbgl/shaders/vulkan/symbol.cpp new file mode 100644 index 000000000..ea8862494 --- /dev/null +++ b/src/mbgl/shaders/vulkan/symbol.cpp @@ -0,0 +1,98 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, false, sizeof(SymbolDrawableUBO), idSymbolDrawableUBO}, + UniformBlockInfo{true, true, sizeof(SymbolTilePropsUBO), idSymbolTilePropsUBO}, + UniformBlockInfo{true, false, sizeof(SymbolInterpolateUBO), idSymbolInterpolateUBO}, + UniformBlockInfo{false, true, sizeof(SymbolEvaluatedPropsUBO), idSymbolEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short4, idSymbolPixelOffsetVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float, idSymbolFadeOpacityVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float, idSymbolOpacityVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idSymbolImageTexture}, +}; + +const std::array ShaderSource::uniforms = + { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(SymbolDrawableUBO), idSymbolDrawableUBO}, + UniformBlockInfo{true, true, sizeof(SymbolTilePropsUBO), idSymbolTilePropsUBO}, + UniformBlockInfo{true, false, sizeof(SymbolInterpolateUBO), idSymbolInterpolateUBO}, + UniformBlockInfo{false, true, sizeof(SymbolEvaluatedPropsUBO), idSymbolEvaluatedPropsUBO}, +}; +const std::array ShaderSource::attributes = + { + // always attributes + AttributeInfo{0, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Short4, idSymbolPixelOffsetVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute}, + AttributeInfo{4, gfx::AttributeDataType::Float2, idSymbolFadeOpacityVertexAttribute}, + + // sometimes uniforms + AttributeInfo{5, gfx::AttributeDataType::Float4, idSymbolColorVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float4, idSymbolHaloColorVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idSymbolOpacityVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::Float2, idSymbolHaloWidthVertexAttribute}, + AttributeInfo{9, gfx::AttributeDataType::Float2, idSymbolHaloBlurVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idSymbolImageTexture}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(GlobalPaintParamsUBO), idGlobalPaintParamsUBO}, + UniformBlockInfo{true, true, sizeof(SymbolDrawableUBO), idSymbolDrawableUBO}, + UniformBlockInfo{true, true, sizeof(SymbolTilePropsUBO), idSymbolTilePropsUBO}, + UniformBlockInfo{true, false, sizeof(SymbolInterpolateUBO), idSymbolInterpolateUBO}, + UniformBlockInfo{false, true, sizeof(SymbolEvaluatedPropsUBO), idSymbolEvaluatedPropsUBO}, +}; +const std::array + ShaderSource::attributes = { + // always attributes + AttributeInfo{0, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute}, + AttributeInfo{2, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute}, + AttributeInfo{3, gfx::AttributeDataType::Float2, idSymbolFadeOpacityVertexAttribute}, + + // sometimes uniforms + AttributeInfo{4, gfx::AttributeDataType::Float4, idSymbolColorVertexAttribute}, + AttributeInfo{5, gfx::AttributeDataType::Float4, idSymbolHaloColorVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Float2, idSymbolOpacityVertexAttribute}, + AttributeInfo{7, gfx::AttributeDataType::Float2, idSymbolHaloWidthVertexAttribute}, + AttributeInfo{8, gfx::AttributeDataType::Float2, idSymbolHaloBlurVertexAttribute}, +}; +const std::array ShaderSource::textures = + { + TextureInfo{0, idSymbolImageTexture}, + TextureInfo{1, idSymbolImageIconTexture}, +}; + +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{true, false, sizeof(CustomSymbolIconDrawableUBO), idCustomSymbolDrawableUBO}, + UniformBlockInfo{true, false, sizeof(CustomSymbolIconParametersUBO), idCustomSymbolParametersUBO}, +}; +const std::array + ShaderSource::attributes = { + // always attributes + AttributeInfo{0, gfx::AttributeDataType::Float2, idCustomSymbolPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Float2, idCustomSymbolTexVertexAttribute}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idCustomSymbolImageTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/vulkan/buffer_resource.cpp b/src/mbgl/vulkan/buffer_resource.cpp new file mode 100644 index 000000000..26419d5f7 --- /dev/null +++ b/src/mbgl/vulkan/buffer_resource.cpp @@ -0,0 +1,164 @@ +#include + +#include +#include +#include + +#include + +namespace mbgl { +namespace vulkan { + +bool BufferAllocation::create(const VmaAllocationCreateInfo& allocInfo, const vk::BufferCreateInfo& bufferInfo) { + VkResult result = vmaCreateBuffer( + allocator, reinterpret_cast(&bufferInfo), &allocInfo, &buffer, &allocation, nullptr); + + if (result != VK_SUCCESS) { + return false; + } + + return true; +} + +void BufferAllocation::destroy() { + if (mappedBuffer) vmaUnmapMemory(allocator, allocation); + vmaDestroyBuffer(allocator, buffer, allocation); + mappedBuffer = nullptr; +} + +void BufferAllocation::setName([[maybe_unused]] const std::string& name) const { +#ifdef ENABLE_VMA_DEBUG + if (allocation) { + vmaSetAllocationName(allocator, allocation, name.data()); + } +#endif +} + +BufferResource::BufferResource( + Context& context_, const void* data, std::size_t size_, std::uint32_t usage_, bool persistent_) + : context(context_), + size(size_), + usage(usage_), + persistent(persistent_) { + const auto& allocator = context.getBackend().getAllocator(); + + std::size_t totalSize = size; + + // TODO -> check avg minUniformBufferOffsetAlignment vs individual buffers + if (usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) { + const auto& backend = context.getBackend(); + const auto& deviceProps = backend.getDeviceProperties(); + const auto& align = deviceProps.limits.minUniformBufferOffsetAlignment; + bufferWindowSize = (size + align - 1) & ~(align - 1); + + assert(bufferWindowSize != 0); + + totalSize = bufferWindowSize * backend.getMaxFrames(); + } + + const auto bufferInfo = vk::BufferCreateInfo() + .setSize(totalSize) + .setUsage(vk::BufferUsageFlags(usage)) + .setSharingMode(vk::SharingMode::eExclusive); + + VmaAllocationCreateInfo allocationInfo = {}; + + allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + allocationInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + + bufferAllocation = std::make_shared(allocator); + if (!bufferAllocation->create(allocationInfo, bufferInfo)) { + mbgl::Log::Error(mbgl::Event::Render, "Vulkan buffer allocation failed"); + return; + } + + vmaMapMemory(allocator, bufferAllocation->allocation, &bufferAllocation->mappedBuffer); + + if (data) { + raw.resize(size); + std::memcpy(raw.data(), data, size); + std::memcpy(static_cast(bufferAllocation->mappedBuffer) + getVulkanBufferOffset(), data, size); + } + + if (isValid()) { + auto& stats = context.renderingStats(); + stats.numBuffers++; + stats.memBuffers += totalSize; + stats.totalBuffers++; + + stats.totalBufferObjs++; + } +} + +BufferResource::BufferResource(BufferResource&& other) noexcept + : context(other.context), + raw(std::move(other.raw)), + size(other.size), + usage(other.usage), + persistent(other.persistent), + bufferAllocation(std::move(other.bufferAllocation)), + bufferWindowSize(other.bufferWindowSize) { + other.bufferAllocation = nullptr; +} + +BufferResource::~BufferResource() noexcept { + if (isValid()) { + context.renderingStats().numBuffers--; + context.renderingStats().memBuffers -= size; + } + + if (!bufferAllocation) return; + + context.enqueueDeletion([allocation = std::move(bufferAllocation)](const auto&) mutable { allocation.reset(); }); +} + +BufferResource BufferResource::clone() const { + return {context, contents(), size, usage, persistent}; +} + +BufferResource& BufferResource::operator=(BufferResource&& other) noexcept { + assert(&context == &other.context); + if (isValid()) { + context.renderingStats().numBuffers--; + context.renderingStats().memBuffers -= size; + }; + raw = std::move(other.raw); + size = other.size; + usage = other.usage; + persistent = other.persistent; + bufferAllocation = std::move(other.bufferAllocation); + bufferWindowSize = other.bufferWindowSize; + return *this; +} + +void BufferResource::update(const void* newData, std::size_t updateSize, std::size_t offset) noexcept { + assert(size >= 0 && updateSize + offset <= size); + updateSize = std::min(updateSize, size - offset); + if (updateSize <= 0) { + return; + } + + auto& stats = context.renderingStats(); + + std::memcpy(raw.data() + offset, newData, updateSize); + std::memcpy( + static_cast(bufferAllocation->mappedBuffer) + getVulkanBufferOffset() + offset, newData, updateSize); + stats.bufferUpdateBytes += updateSize; + + stats.bufferUpdates++; + version++; +} + +std::size_t BufferResource::getVulkanBufferOffset() const noexcept { + if (bufferWindowSize > 0) return 0; + + return context.getCurrentFrameResourceIndex() * bufferWindowSize; +} + +std::size_t BufferResource::getVulkanBufferSize() const noexcept { + return bufferWindowSize > 0 ? bufferWindowSize : size; +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/command_encoder.cpp b/src/mbgl/vulkan/command_encoder.cpp new file mode 100644 index 000000000..bcb061fb8 --- /dev/null +++ b/src/mbgl/vulkan/command_encoder.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace vulkan { + +CommandEncoder::CommandEncoder(Context& context_, const vk::UniqueCommandBuffer& buffer_) + : context(context_), + commandBuffer(buffer_) {} + +CommandEncoder::~CommandEncoder() {} + +std::unique_ptr CommandEncoder::createUploadPass(const char* name, gfx::Renderable& renderable) { + return std::make_unique(renderable, *this, name); +} + +std::unique_ptr CommandEncoder::createRenderPass(const char* name, + const gfx::RenderPassDescriptor& descriptor) { + return std::make_unique(*this, name, descriptor); +} + +void CommandEncoder::present(gfx::Renderable&) {} + +void CommandEncoder::pushDebugGroup(const char* name) { + pushDebugGroup(name, {}); +} + +void CommandEncoder::pushDebugGroup(const char* name, const std::array& color) { + context.getBackend().beginDebugLabel(commandBuffer.get(), name, color); +} + +void CommandEncoder::popDebugGroup() { + context.getBackend().endDebugLabel(commandBuffer.get()); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/context.cpp b/src/mbgl/vulkan/context.cpp new file mode 100644 index 000000000..d7eab8d7c --- /dev/null +++ b/src/mbgl/vulkan/context.cpp @@ -0,0 +1,560 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#ifndef MLN_VULKAN_DESCRIPTOR_POOL_SIZE +#define MLN_VULKAN_DESCRIPTOR_POOL_SIZE 10000 +#endif + +namespace mbgl { +namespace vulkan { + +// Maximum number of vertex attributes, per vertex descriptor +// 32 on most devices (~30% Android use 16), +// per https://vulkan.gpuinfo.org/displaydevicelimit.php?name=maxVertexInputBindings +// this can be queried at runtime (VkPhysicalDeviceLimits.maxVertexInputBindings) +constexpr uint32_t maximumVertexBindingCount = 16; +static uint32_t glslangRefCount = 0; + +class RenderbufferResource : public gfx::RenderbufferResource { +public: + RenderbufferResource() = default; +}; + +Context::Context(RendererBackend& backend_) + : gfx::Context(vulkan::maximumVertexBindingCount), + backend(backend_) { + if (glslangRefCount++ == 0) { + glslang::InitializeProcess(); + } + + initFrameResources(); +} + +Context::~Context() noexcept { + backend.getThreadPool().runRenderJobs(true /* closeQueue */); + + destroyResources(); + + if (--glslangRefCount == 0) { + glslang::FinalizeProcess(); + } +} + +void Context::initFrameResources() { + const auto& device = backend.getDevice(); + const auto frameCount = backend.getMaxFrames(); + + // command buffers + const vk::CommandBufferAllocateInfo allocateInfo( + backend.getCommandPool().get(), vk::CommandBufferLevel::ePrimary, frameCount); + + auto commandBuffers = backend.getDevice()->allocateCommandBuffersUnique(allocateInfo); + + // descriptor pool info + const std::vector poolSizes = { + {vk::DescriptorType::eUniformBuffer, MLN_VULKAN_DESCRIPTOR_POOL_SIZE}, + {vk::DescriptorType::eCombinedImageSampler, MLN_VULKAN_DESCRIPTOR_POOL_SIZE}, + }; + + const auto descriptorPoolInfo = vk::DescriptorPoolCreateInfo().setPoolSizes(poolSizes).setMaxSets( + MLN_VULKAN_DESCRIPTOR_POOL_SIZE); + + frameResources.reserve(frameCount); + + for (uint32_t index = 0; index < frameCount; ++index) { + frameResources.emplace_back(commandBuffers[index], + device->createDescriptorPoolUnique(descriptorPoolInfo), + device->createSemaphoreUnique({}), + device->createSemaphoreUnique({}), + device->createFenceUnique(vk::FenceCreateInfo(vk::FenceCreateFlagBits::eSignaled))); + + const auto& frame = frameResources.back(); + + backend.setDebugName(frame.commandBuffer.get(), "FrameCommandBuffer_" + std::to_string(index)); + backend.setDebugName(frame.descriptorPool.get(), "DescriptorPool_" + std::to_string(index)); + backend.setDebugName(frame.frameSemaphore.get(), "FrameSemaphore_" + std::to_string(index)); + backend.setDebugName(frame.surfaceSemaphore.get(), "SurfaceSemaphore_" + std::to_string(index)); + backend.setDebugName(frame.flightFrameFence.get(), "FrameFence_" + std::to_string(index)); + } + + // force placeholder texture upload before any descriptor sets + (void)getDummyTexture(); +} + +void Context::destroyResources() { + backend.getDevice()->waitIdle(); + + for (auto& frame : frameResources) { + frame.runDeletionQueue(*this); + } + + // all resources have unique handles + frameResources.clear(); +} + +const vk::UniqueDescriptorPool& Context::getCurrentDescriptorPool() const { + return frameResources[frameResourceIndex].descriptorPool; +} + +void Context::enqueueDeletion(std::function&& function) { + if (frameResources.empty()) { + function(*this); + return; + } + + frameResources[frameResourceIndex].deletionQueue.push_back(std::move(function)); +} + +void Context::submitOneTimeCommand(const std::function& function) { + const vk::CommandBufferAllocateInfo allocateInfo( + backend.getCommandPool().get(), vk::CommandBufferLevel::ePrimary, 1); + + const auto& commandBuffers = backend.getDevice()->allocateCommandBuffersUnique(allocateInfo); + auto& commandBuffer = commandBuffers.front(); + + backend.setDebugName(commandBuffer.get(), "OneTimeSubmitCommandBuffer"); + + commandBuffer->begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); + function(commandBuffer); + commandBuffer->end(); + + const auto submitInfo = vk::SubmitInfo().setCommandBuffers(commandBuffer.get()); + + backend.getGraphicsQueue().submit(submitInfo); + backend.getDevice()->waitIdle(); +} + +void Context::waitFrame() const { + const auto& device = backend.getDevice(); + auto& frame = frameResources[frameResourceIndex]; + constexpr uint64_t timeout = std::numeric_limits::max(); + + const vk::Result waitFenceResult = device->waitForFences(1, &frame.flightFrameFence.get(), VK_TRUE, timeout); + if (waitFenceResult != vk::Result::eSuccess) { + mbgl::Log::Error(mbgl::Event::Render, "Wait fence failed"); + } +} +void Context::beginFrame() { + backend.startFrameCapture(); + + const auto& device = backend.getDevice(); + auto& renderableResource = backend.getDefaultRenderable().getResource(); + auto& frame = frameResources[frameResourceIndex]; + constexpr uint64_t timeout = std::numeric_limits::max(); + + waitFrame(); + + device->resetDescriptorPool(getCurrentDescriptorPool().get()); + frame.runDeletionQueue(*this); + + if (renderableResource.getPlatformSurface()) { + try { + const vk::ResultValue acquireImageResult = device->acquireNextImageKHR( + renderableResource.getSwapchain().get(), timeout, frame.surfaceSemaphore.get(), nullptr); + + if (acquireImageResult.result == vk::Result::eSuccess) + renderableResource.setAcquiredImageIndex(acquireImageResult.value); + else if (acquireImageResult.result == vk::Result::eSuboptimalKHR) + renderableResource.recreateSwapchain(); + + } catch (const vk::OutOfDateKHRError& e) { + renderableResource.recreateSwapchain(); + } + } else { + renderableResource.setAcquiredImageIndex(frameResourceIndex); + } + + frame.commandBuffer->reset(vk::CommandBufferResetFlagBits::eReleaseResources); + frame.commandBuffer->begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); + + backend.getThreadPool().runRenderJobs(); +} + +void Context::endFrame() { + const auto& frame = frameResources[frameResourceIndex]; + frame.commandBuffer->end(); + + const auto& device = backend.getDevice(); + const auto& graphicsQueue = backend.getGraphicsQueue(); + auto& renderableResource = backend.getDefaultRenderable().getResource(); + + // submit frame commands + const vk::PipelineStageFlags waitStageMask[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; + auto submitInfo = vk::SubmitInfo().setCommandBuffers(frame.commandBuffer.get()); + + if (renderableResource.getPlatformSurface()) { + submitInfo.setSignalSemaphores(frame.frameSemaphore.get()) + .setWaitSemaphores(frame.surfaceSemaphore.get()) + .setWaitDstStageMask(waitStageMask); + } + + const vk::Result resetFenceResult = device->resetFences(1, &frame.flightFrameFence.get()); + if (resetFenceResult != vk::Result::eSuccess) { + mbgl::Log::Error(mbgl::Event::Render, "Reset fence failed"); + } + + graphicsQueue.submit(submitInfo, frame.flightFrameFence.get()); + + // present rendered frame + if (renderableResource.getPlatformSurface()) { + const auto acquiredImage = renderableResource.getAcquiredImageIndex(); + const auto presentInfo = vk::PresentInfoKHR() + .setSwapchains(renderableResource.getSwapchain().get()) + .setWaitSemaphores(frame.frameSemaphore.get()) + .setImageIndices(acquiredImage); + + try { + const auto& presentQueue = backend.getPresentQueue(); + const vk::Result presentResult = presentQueue.presentKHR(presentInfo); + if (presentResult == vk::Result::eSuboptimalKHR) renderableResource.recreateSwapchain(); + } catch (const vk::OutOfDateKHRError& e) { + renderableResource.recreateSwapchain(); + } + } + + frameResourceIndex = (frameResourceIndex + 1) % frameResources.size(); + + backend.endFrameCapture(); +} + +std::unique_ptr Context::createCommandEncoder() { + const auto& frame = frameResources[frameResourceIndex]; + return std::make_unique(*this, frame.commandBuffer); +} + +BufferResource Context::createBuffer(const void* data, std::size_t size, std::uint32_t usage, bool persistent) const { + return BufferResource(const_cast(*this), data, size, usage, persistent); +} + +UniqueShaderProgram Context::createProgram(std::string name, + const std::string_view vertex, + const std::string_view fragment, + const ProgramParameters& programParameters, + const mbgl::unordered_map& additionalDefines) { + return std::make_unique(name, vertex, fragment, programParameters, additionalDefines, backend); +} + +gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) { + return std::make_unique(std::move(name)); +} + +gfx::UniformBufferPtr Context::createUniformBuffer(const void* data, std::size_t size, bool persistent) { + return std::make_shared(createBuffer(data, size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, persistent)); +} + +gfx::ShaderProgramBasePtr Context::getGenericShader(gfx::ShaderRegistry& shaders, const std::string& name) { + const auto shaderGroup = shaders.getShaderGroup(name); + auto shader = shaderGroup ? shaderGroup->getOrCreateShader(*this, {}) : gfx::ShaderProgramBasePtr{}; + return std::static_pointer_cast(std::move(shader)); +} + +TileLayerGroupPtr Context::createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) { + return std::make_shared(layerIndex, initialCapacity, std::move(name)); +} + +LayerGroupPtr Context::createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) { + return std::make_shared(layerIndex, initialCapacity, name); +} + +bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& buffer, + const void* data, + std::size_t size, + bool persistent) { + if (buffer) { + buffer->update(data, size); + return false; + } else { + buffer = createUniformBuffer(data, size, persistent); + return true; + } +} + +gfx::Texture2DPtr Context::createTexture2D() { + return std::make_shared(*this); +} + +RenderTargetPtr Context::createRenderTarget(const Size size, const gfx::TextureChannelDataType type) { + return std::make_shared(*this, size, type); +} + +std::unique_ptr Context::createOffscreenTexture(Size size, + gfx::TextureChannelDataType type, + bool depth, + bool stencil) { + return std::make_unique(*this, size, type, depth, stencil); +} + +std::unique_ptr Context::createOffscreenTexture(Size size, gfx::TextureChannelDataType type) { + return createOffscreenTexture(size, type, false, false); +} + +std::unique_ptr Context::createTextureResource(Size, + gfx::TexturePixelType, + gfx::TextureChannelDataType) { + throw std::runtime_error("Vulkan TextureResource not implemented"); + return nullptr; +} + +std::unique_ptr Context::createRenderbufferResource(gfx::RenderbufferPixelType, Size) { + return std::make_unique(); +} + +std::unique_ptr Context::createDrawScopeResource() { + throw std::runtime_error("Vulkan DrawScopeResource not implemented"); + return nullptr; +} + +gfx::VertexAttributeArrayPtr Context::createVertexAttributeArray() const { + return std::make_shared(); +} + +#if !defined(NDEBUG) +void Context::visualizeStencilBuffer() {} + +void Context::visualizeDepthBuffer(float) {} +#endif // !defined(NDEBUG) + +void Context::clearStencilBuffer(int32_t) { + // See `PaintParameters::clearStencil` + assert(false); +} + +void Context::bindGlobalUniformBuffers(gfx::RenderPass&) const noexcept {} + +bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass, + RenderStaticData& staticData, + const std::vector& tileUBOs) { + using ShaderClass = shaders::ShaderSource; + + if (!clipping.shader) { + const auto group = staticData.shaders->getShaderGroup("ClippingMaskProgram"); + if (group) { + clipping.shader = std::static_pointer_cast(group->getOrCreateShader(*this, {})); + } + } + if (!clipping.shader) { + assert(!"Failed to create shader for clip masking"); + return false; + } + + // Create a vertex buffer from the fixed tile coordinates + if (!clipping.vertexBuffer) { + const auto vertices = RenderStaticData::tileVertices(); + clipping.vertexBuffer.emplace( + createBuffer(vertices.data(), vertices.bytes(), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, false)); + } + + // Create a buffer from the fixed tile indexes + if (!clipping.indexBuffer) { + const auto indices = RenderStaticData::quadTriangleIndices(); + clipping.indexBuffer.emplace( + createBuffer(indices.data(), indices.bytes(), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, false)); + clipping.indexCount = 6; + } + + // build pipeline + if (clipping.pipelineInfo.inputAttributes.empty()) { + clipping.pipelineInfo.usePushConstants = true; + + clipping.pipelineInfo.colorBlend = false; + clipping.pipelineInfo.colorMask = vk::ColorComponentFlags(); + + clipping.pipelineInfo.depthTest = false; + clipping.pipelineInfo.depthWrite = false; + + clipping.pipelineInfo.stencilTest = true; + clipping.pipelineInfo.stencilFunction = vk::CompareOp::eAlways; + clipping.pipelineInfo.stencilPass = vk::StencilOp::eReplace; + clipping.pipelineInfo.dynamicValues.stencilWriteMask = 0b11111111; + clipping.pipelineInfo.dynamicValues.stencilRef = 0b11111111; + + clipping.pipelineInfo.inputBindings.push_back( + vk::VertexInputBindingDescription() + .setBinding(0) + .setStride(VertexAttribute::getStrideOf(ShaderClass::attributes[0].dataType)) + .setInputRate(vk::VertexInputRate::eVertex)); + + clipping.pipelineInfo.inputAttributes.push_back( + vk::VertexInputAttributeDescription() + .setBinding(0) + .setLocation(ShaderClass::attributes[0].index) + .setFormat(PipelineInfo::vulkanFormat(ShaderClass::attributes[0].dataType))); + } + + auto& shaderImpl = static_cast(*clipping.shader); + auto& renderPassImpl = static_cast(renderPass); + auto& commandBuffer = renderPassImpl.getEncoder().getCommandBuffer(); + + clipping.pipelineInfo.setRenderable(renderPassImpl.getDescriptor().renderable); + + const auto& pipeline = shaderImpl.getPipeline(clipping.pipelineInfo); + + commandBuffer->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get()); + clipping.pipelineInfo.setDynamicValues(commandBuffer); + + const std::array vertexBuffers = {clipping.vertexBuffer->getVulkanBuffer()}; + const std::array offset = {0}; + + commandBuffer->bindVertexBuffers(0, vertexBuffers, offset); + commandBuffer->bindIndexBuffer(clipping.indexBuffer->getVulkanBuffer(), 0, vk::IndexType::eUint16); + + for (const auto& tileInfo : tileUBOs) { + commandBuffer->setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, tileInfo.stencil_ref); + + commandBuffer->pushConstants( + getPushConstantPipelineLayout().get(), + vk::ShaderStageFlags() | vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, + 0, + sizeof(tileInfo.matrix), + &tileInfo.matrix); + commandBuffer->drawIndexed(clipping.indexCount, 1, 0, 0, 0); + } + + stats.numDrawCalls++; + stats.totalDrawCalls++; + return true; +} + +const std::unique_ptr& Context::getDummyVertexBuffer() { + if (!dummyVertexBuffer) + dummyVertexBuffer = std::make_unique( + *this, nullptr, 16, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, false); + return dummyVertexBuffer; +} + +const std::unique_ptr& Context::getDummyUniformBuffer() { + if (!dummyUniformBuffer) + dummyUniformBuffer = std::make_unique( + *this, nullptr, 16, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, false); + return dummyUniformBuffer; +} + +const std::unique_ptr& Context::getDummyTexture() { + if (!dummyTexture2D) { + const Size size(2, 2); + const std::vector data(4ull * size.width * size.height, Color::white()); + + dummyTexture2D = std::make_unique(*this); + dummyTexture2D->setFormat(gfx::TexturePixelType::RGBA, gfx::TextureChannelDataType::UnsignedByte); + dummyTexture2D->setSize(size); + + submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) { + dummyTexture2D->uploadSubRegion(data.data(), size, 0, 0, commandBuffer); + }); + } + + return dummyTexture2D; +} + +const vk::UniqueDescriptorSetLayout& Context::getUniformDescriptorSetLayout() { + if (!uniformDescriptorSetLayout) { + std::vector bindings; + const auto stageFlags = vk::ShaderStageFlags() | vk::ShaderStageFlagBits::eVertex | + vk::ShaderStageFlagBits::eFragment; + + for (size_t i = 0; i < shaders::maxUBOCountPerShader; ++i) { + bindings.push_back(vk::DescriptorSetLayoutBinding() + .setBinding(i) + .setStageFlags(stageFlags) + .setDescriptorType(vk::DescriptorType::eUniformBuffer) + .setDescriptorCount(1)); + } + + const auto descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo().setBindings(bindings); + uniformDescriptorSetLayout = backend.getDevice()->createDescriptorSetLayoutUnique( + descriptorSetLayoutCreateInfo); + backend.setDebugName(uniformDescriptorSetLayout.get(), "UniformDescriptorSetLayout"); + } + + return uniformDescriptorSetLayout; +} + +const vk::UniqueDescriptorSetLayout& Context::getImageDescriptorSetLayout() { + if (!imageDescriptorSetLayout) { + std::vector bindings; + + for (size_t i = 0; i < shaders::maxTextureCountPerShader; ++i) { + bindings.push_back(vk::DescriptorSetLayoutBinding() + .setBinding(i) + .setStageFlags(vk::ShaderStageFlagBits::eFragment) + .setDescriptorType(vk::DescriptorType::eCombinedImageSampler) + .setDescriptorCount(1)); + } + + const auto descriptorSetLayoutCreateInfo = vk::DescriptorSetLayoutCreateInfo().setBindings(bindings); + imageDescriptorSetLayout = backend.getDevice()->createDescriptorSetLayoutUnique(descriptorSetLayoutCreateInfo); + backend.setDebugName(imageDescriptorSetLayout.get(), "ImageDescriptorSetLayout"); + } + + return imageDescriptorSetLayout; +} + +const std::vector& Context::getDescriptorSetLayouts() { + if (descriptorSetLayouts.empty()) { + descriptorSetLayouts = {getUniformDescriptorSetLayout().get(), getImageDescriptorSetLayout().get()}; + } + + return descriptorSetLayouts; +} + +const vk::UniquePipelineLayout& Context::getGeneralPipelineLayout() { + if (generalPipelineLayout) return generalPipelineLayout; + + const auto& descriptorLayouts = getDescriptorSetLayouts(); + + generalPipelineLayout = backend.getDevice()->createPipelineLayoutUnique( + vk::PipelineLayoutCreateInfo().setSetLayouts(descriptorLayouts)); + + backend.setDebugName(generalPipelineLayout.get(), "PipelineLayout_general"); + + return generalPipelineLayout; +} + +const vk::UniquePipelineLayout& Context::getPushConstantPipelineLayout() { + if (pushConstantPipelineLayout) return pushConstantPipelineLayout; + + const auto stages = vk::ShaderStageFlags() | vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment; + const auto pushConstant = vk::PushConstantRange().setSize(sizeof(matf4)).setStageFlags(stages); + + pushConstantPipelineLayout = backend.getDevice()->createPipelineLayoutUnique( + vk::PipelineLayoutCreateInfo().setPushConstantRanges(pushConstant)); + + backend.setDebugName(pushConstantPipelineLayout.get(), "PipelineLayout_pushConstants"); + + return pushConstantPipelineLayout; +} + +void Context::FrameResources::runDeletionQueue(const Context& context) { + for (const auto& function : deletionQueue) function(context); + + deletionQueue.clear(); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/drawable.cpp b/src/mbgl/vulkan/drawable.cpp new file mode 100644 index 000000000..0eb42011e --- /dev/null +++ b/src/mbgl/vulkan/drawable.cpp @@ -0,0 +1,453 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if !defined(NDEBUG) +#include +#endif + +namespace mbgl { +namespace vulkan { + +struct IndexBuffer : public gfx::IndexBufferBase { + IndexBuffer(std::unique_ptr&& buffer_) + : buffer(std::move(buffer_)) {} + ~IndexBuffer() override = default; + + std::unique_ptr buffer; +}; + +#if !defined(NDEBUG) +static std::string debugLabel(const gfx::Drawable& drawable) { + std::ostringstream oss; + oss << drawable.getID().id() << "/" << drawable.getName() << "/tile="; + + if (const auto& tileID = drawable.getTileID()) { + oss << util::toString(*tileID); + } else { + oss << "(none)"; + } + + return oss.str(); +} +#endif // !defined(NDEBUG) + +Drawable::Drawable(std::string name_) + : gfx::Drawable(std::move(name_)), + impl(std::make_unique()) {} + +Drawable::~Drawable() {} + +void Drawable::setEnableColor(bool value) { + gfx::Drawable::setEnableColor(value); + + if (value) { + impl->pipelineInfo.colorMask = vk::FlagTraits::allFlags; + } else { + impl->pipelineInfo.colorMask = vk::ColorComponentFlags(); + } +} + +void Drawable::setColorMode(const gfx::ColorMode& value) { + gfx::Drawable::setColorMode(value); + impl->pipelineInfo.setColorBlend(value); +} + +void Drawable::setEnableDepth(bool value) { + gfx::Drawable::setEnableDepth(value); + impl->pipelineInfo.depthTest = value; +} + +void Drawable::setDepthType(gfx::DepthMaskType value) { + gfx::Drawable::setDepthType(value); + impl->pipelineInfo.setDepthWrite(value); +} + +void Drawable::setDepthModeFor3D(const gfx::DepthMode& value) { + impl->depthFor3D = value; +} + +void Drawable::setStencilModeFor3D(const gfx::StencilMode& value) { + impl->stencilFor3D = value; +} + +void Drawable::setLineWidth(int32_t value) { + gfx::Drawable::setLineWidth(value); + impl->pipelineInfo.wideLines = value != 1; + impl->pipelineInfo.dynamicValues.lineWidth = static_cast(value); +} + +void Drawable::setCullFaceMode(const gfx::CullFaceMode& mode) { + gfx::Drawable::setCullFaceMode(mode); + impl->pipelineInfo.setCullMode(mode); +} + +void Drawable::upload(gfx::UploadPass& uploadPass_) { + if (isCustom) { + return; + } + + if (!shader) { + Log::Warning(Event::General, "Missing shader for drawable " + util::toString(getID()) + "/" + getName()); + assert(false); + return; + } + + auto& uploadPass = static_cast(uploadPass_); + constexpr auto usage = gfx::BufferUsageType::StaticDraw; + + // We need either raw index data or a buffer already created from them. + // We can have a buffer and no indexes, but only if it's not marked dirty. + if (!impl->indexes || (impl->indexes->empty() && (!impl->indexes->getBuffer() || impl->indexes->getDirty()))) { + assert(!"Missing index data"); + return; + } + + if (impl->indexes->getDirty()) { + // Create or update a buffer for the index data. We don't update any + // existing buffer because it may still be in use by the previous frame. + auto indexBufferResource{uploadPass.createIndexBufferResource( + impl->indexes->data(), impl->indexes->bytes(), usage, /*persistent=*/false)}; + auto indexBuffer = std::make_unique(impl->indexes->elements(), + std::move(indexBufferResource)); + auto buffer = std::make_unique(std::move(indexBuffer)); + + impl->indexes->setBuffer(std::move(buffer)); + impl->indexes->setDirty(false); + } + + const bool buildAttribs = !vertexAttributes || vertexAttributes->isDirty() || + impl->pipelineInfo.inputAttributes.empty(); + + if (buildAttribs) { +#if !defined(NDEBUG) + const auto debugGroup = uploadPass.createDebugGroup(debugLabel(*this)); +#endif + + if (!vertexAttributes) { + vertexAttributes = std::make_shared(); + } + + // Apply drawable values to shader defaults + std::vector> vertexBuffers; + auto attributeBindings_ = uploadPass.buildAttributeBindings(impl->vertexCount, + impl->vertexType, + /*vertexAttributeIndex=*/-1, + /*vertexData=*/{}, + shader->getVertexAttributes(), + *vertexAttributes, + usage, + vertexBuffers); + + vertexAttributes->visitAttributes([](gfx::VertexAttribute& attrib) { attrib.setDirty(false); }); + + if (impl->attributeBindings != attributeBindings_) { + impl->attributeBindings = std::move(attributeBindings_); + } + } + + // build instance buffer + const bool buildInstanceBuffer = (instanceAttributes && instanceAttributes->isDirty()); + + if (buildInstanceBuffer) { + // Build instance attribute buffers + std::vector> instanceBuffers; + auto instanceBindings_ = uploadPass.buildAttributeBindings(instanceAttributes->getMaxCount(), + /*vertexType*/ gfx::AttributeDataType::Byte, + /*vertexAttributeIndex=*/-1, + /*vertexData=*/{}, + shader->getInstanceAttributes(), + *instanceAttributes, + usage, + instanceBuffers); + + // clear dirty flag + instanceAttributes->visitAttributes([](gfx::VertexAttribute& attrib) { attrib.setDirty(false); }); + + if (impl->instanceBindings != instanceBindings_) { + impl->instanceBindings = std::move(instanceBindings_); + } + } + + if (buildAttribs || buildInstanceBuffer) { + buildVulkanInputBindings(); + } + + const bool texturesNeedUpload = std::any_of( + textures.begin(), textures.end(), [](const auto& texture) { return texture && texture->needsUpload(); }); + + if (texturesNeedUpload) { + uploadTextures(uploadPass); + } +} + +void Drawable::draw(PaintParameters& parameters) const { + if (isCustom) { + return; + } + + auto& context = static_cast(parameters.context); + auto& renderPass = static_cast(*parameters.renderPass); + auto& encoder = renderPass.getEncoder(); + auto& commandBuffer = encoder.getCommandBuffer(); + + auto& shaderImpl = static_cast(*shader); + + if (!bindAttributes(encoder)) return; + if (!bindDescriptors(encoder)) return; + + if (is3D) { + impl->pipelineInfo.setDepthMode(impl->depthFor3D); + impl->pipelineInfo.setStencilMode(impl->stencilFor3D); + } else { + if (enableDepth) { + const auto& depthMode = parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType()); + impl->pipelineInfo.setDepthMode(depthMode); + } else { + impl->pipelineInfo.setDepthMode(gfx::DepthMode::disabled()); + } + + if (enableStencil) { + const auto& stencilMode = parameters.stencilModeForClipping(tileID->toUnwrapped()); + impl->pipelineInfo.setStencilMode(stencilMode); + } else { + impl->pipelineInfo.setStencilMode(gfx::StencilMode::disabled()); + } + } + + impl->pipelineInfo.setRenderable(renderPass.getDescriptor().renderable); + + const uint32_t instances = instanceAttributes ? instanceAttributes->getMaxCount() : 1; + + for (const auto& seg : impl->segments) { + const auto& segment = seg->getSegment(); + + // update pipeline info with per segment modifiers + impl->pipelineInfo.setDrawMode(seg->getMode()); + + impl->pipelineInfo.setDynamicValues(commandBuffer); + + const auto& pipeline = shaderImpl.getPipeline(impl->pipelineInfo); + commandBuffer->bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline.get()); + + if (segment.indexLength) { + commandBuffer->drawIndexed(segment.indexLength, instances, segment.indexOffset, segment.vertexOffset, 0); + } else { + commandBuffer->draw(segment.vertexLength, instances, segment.vertexOffset, 0); + } + + context.renderingStats().numDrawCalls++; + } +} + +void Drawable::setIndexData(gfx::IndexVectorBasePtr indexes, std::vector segments) { + assert(indexes && indexes->elements()); + impl->indexes = std::move(indexes); + impl->segments = std::move(segments); +} + +void Drawable::setVertices(std::vector&& data, std::size_t count, gfx::AttributeDataType type) { + impl->vertexCount = count; + impl->vertexType = type; + + if (count && type != gfx::AttributeDataType::Invalid && !data.empty()) { + if (!vertexAttributes) { + vertexAttributes = std::make_shared(); + } + if (auto& attrib = vertexAttributes->set(impl->vertexAttrId, /*index=*/-1, type)) { + attrib->setRawData(std::move(data)); + attrib->setStride(VertexAttribute::getStrideOf(type)); + } else { + using namespace std::string_literals; + Log::Warning(Event::General, + "Vertex attribute type mismatch: "s + name + " / " + util::toString(impl->vertexAttrId)); + assert(false); + } + } +} + +const gfx::UniformBufferArray& Drawable::getUniformBuffers() const { + return impl->uniformBuffers; +} + +gfx::UniformBufferArray& Drawable::mutableUniformBuffers() { + return impl->uniformBuffers; +} + +void Drawable::buildVulkanInputBindings() noexcept { + impl->vulkanVertexBuffers.clear(); + impl->vulkanVertexOffsets.clear(); + + impl->pipelineInfo.inputAttributes.clear(); + impl->pipelineInfo.inputBindings.clear(); + + std::vector uniqueBuffers; + + const auto buildBindings = [&](const gfx::AttributeBindingArray& bindings, vk::VertexInputRate inputRate) { + for (size_t i = 0; i < bindings.size(); ++i) { + const auto& binding = bindings[i]; + if (!binding.has_value() || !binding->vertexBufferResource) continue; + + const auto& vertexBuffer = static_cast(binding->vertexBufferResource); + const auto& buffer = vertexBuffer->get(); + + const auto& buffIt = std::find(uniqueBuffers.begin(), uniqueBuffers.end(), binding->vertexBufferResource); + uint32_t bindingIndex = 0; + + if (buffIt == uniqueBuffers.end()) { + bindingIndex = impl->pipelineInfo.inputBindings.size(); + + uniqueBuffers.push_back(binding->vertexBufferResource); + + // add new buffer binding + impl->pipelineInfo.inputBindings.push_back(vk::VertexInputBindingDescription() + .setBinding(bindingIndex) + .setStride(binding->vertexStride) + .setInputRate(inputRate)); + + impl->vulkanVertexBuffers.push_back(buffer.getVulkanBuffer()); + impl->vulkanVertexOffsets.push_back(0u); + } else { + bindingIndex = std::distance(uniqueBuffers.begin(), buffIt); + } + + impl->pipelineInfo.inputAttributes.push_back( + vk::VertexInputAttributeDescription() + .setBinding(bindingIndex) + .setLocation(i) + .setFormat(PipelineInfo::vulkanFormat(binding->attribute.dataType)) + .setOffset(binding->attribute.offset)); + } + }; + + buildBindings(impl->attributeBindings, vk::VertexInputRate::eVertex); + buildBindings(impl->instanceBindings, vk::VertexInputRate::eInstance); + + impl->pipelineInfo.updateVertexInputHash(); +} + +bool Drawable::bindAttributes(CommandEncoder& encoder) const noexcept { + if (impl->vulkanVertexBuffers.empty()) return false; + + const auto& commandBuffer = encoder.getCommandBuffer(); + + commandBuffer->bindVertexBuffers(0, impl->vulkanVertexBuffers, impl->vulkanVertexOffsets); + + if (impl->indexes) { + const auto* indexBuffer = static_cast(impl->indexes->getBuffer()); + const auto& indexBufferResource = indexBuffer->buffer->getResource().get(); + commandBuffer->bindIndexBuffer(indexBufferResource.getVulkanBuffer(), 0, vk::IndexType::eUint16); + } + + return true; +} + +bool Drawable::bindDescriptors(CommandEncoder& encoder) const noexcept { + if (!shader) return false; + + auto& context = encoder.getContext(); + const auto& device = context.getBackend().getDevice(); + const auto& descriptorPool = context.getCurrentDescriptorPool(); + const auto& descriptorSetLayouts = context.getDescriptorSetLayouts(); + + const auto descriptorAllocInfo = + vk::DescriptorSetAllocateInfo().setDescriptorPool(*descriptorPool).setSetLayouts(descriptorSetLayouts); + + const auto& drawableDescriptorSets = device->allocateDescriptorSets(descriptorAllocInfo); + const auto& uniformDescriptorSet = drawableDescriptorSets[0]; + + const auto updateUniformDescriptors = [&](const auto& buffer, bool fillGaps) { + for (size_t id = 0; id < buffer.allocatedSize(); ++id) { + vk::DescriptorBufferInfo descriptorBufferInfo; + + if (const auto& uniformBuffer = buffer.get(id)) { + const auto& uniformBufferImpl = static_cast(*uniformBuffer); + const auto& bufferResource = uniformBufferImpl.getBufferResource(); + descriptorBufferInfo.setBuffer(bufferResource.getVulkanBuffer()) + .setOffset(bufferResource.getVulkanBufferOffset()) + .setRange(bufferResource.getVulkanBufferSize()); + } else if (fillGaps) { + descriptorBufferInfo.setBuffer(context.getDummyUniformBuffer()->getVulkanBuffer()) + .setOffset(0) + .setRange(VK_WHOLE_SIZE); + } else { + continue; + } + + const auto writeDescriptorSet = vk::WriteDescriptorSet() + .setBufferInfo(descriptorBufferInfo) + .setDescriptorCount(1) + .setDescriptorType(vk::DescriptorType::eUniformBuffer) + .setDstBinding(id) + .setDstSet(uniformDescriptorSet); + + device->updateDescriptorSets(writeDescriptorSet, nullptr); + } + }; + const auto& globalUniforms = context.getGlobalUniformBuffers(); + for (size_t i = 0; i < globalUniforms.allocatedSize(); ++i) { + if (globalUniforms.get(i)) impl->uniformBuffers.set(i, globalUniforms.get(i)); + } + + updateUniformDescriptors(getUniformBuffers(), true); + + if (drawableDescriptorSets.size() >= 2) { + const auto& imageDescriptorSet = drawableDescriptorSets[1]; + + for (size_t id = 0; id < shaders::maxTextureCountPerShader; ++id) { + const auto& texture = id < textures.size() ? textures[id] : nullptr; + auto& textureImpl = texture ? static_cast(*texture) : *context.getDummyTexture(); + + const auto descriptorImageInfo = vk::DescriptorImageInfo() + .setImageLayout(textureImpl.getVulkanImageLayout()) + .setImageView(textureImpl.getVulkanImageView().get()) + .setSampler(textureImpl.getVulkanSampler()); + + const auto writeDescriptorSet = vk::WriteDescriptorSet() + .setImageInfo(descriptorImageInfo) + .setDescriptorCount(1) + .setDescriptorType(vk::DescriptorType::eCombinedImageSampler) + .setDstBinding(id) + .setDstSet(imageDescriptorSet); + + device->updateDescriptorSets(writeDescriptorSet, nullptr); + } + } + + if (drawableDescriptorSets.empty()) return true; + + const auto& commandBuffer = encoder.getCommandBuffer(); + commandBuffer->bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, context.getGeneralPipelineLayout().get(), 0, drawableDescriptorSets, nullptr); + + return true; +} + +void Drawable::uploadTextures(UploadPass&) const noexcept { + for (const auto& texture : textures) { + if (texture) { + texture->upload(); + } + } +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/drawable_builder.cpp b/src/mbgl/vulkan/drawable_builder.cpp new file mode 100644 index 000000000..55e5c583b --- /dev/null +++ b/src/mbgl/vulkan/drawable_builder.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace vulkan { + +gfx::UniqueDrawable DrawableBuilder::createDrawable() const { + return std::make_unique(drawableName.empty() ? name : drawableName); +}; + +std::unique_ptr DrawableBuilder::createSegment(gfx::DrawMode mode, SegmentBase&& seg) { + return std::make_unique(mode, std::move(seg)); +} + +void DrawableBuilder::init() { + auto& drawable = static_cast(*currentDrawable); + + if (impl->rawVerticesCount) { + auto raw = impl->rawVertices; + drawable.setVertices(std::move(raw), impl->rawVerticesCount, impl->rawVerticesType); + } else { + const auto& verts = impl->vertices.vector(); + constexpr auto vertSize = sizeof(std::remove_reference::type::value_type); + std::vector raw(verts.size() * vertSize); + std::memcpy(raw.data(), verts.data(), raw.size()); + drawable.setVertices(std::move(raw), verts.size(), gfx::AttributeDataType::Short2); + } + + if (!impl->sharedIndexes && !impl->buildIndexes.empty()) { + impl->sharedIndexes = std::make_shared(std::move(impl->buildIndexes)); + } + assert(impl->sharedIndexes && impl->sharedIndexes->elements()); + drawable.setIndexData(std::move(impl->sharedIndexes), std::move(impl->segments)); + + impl->clear(); + textures.fill(nullptr); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/drawable_impl.hpp b/src/mbgl/vulkan/drawable_impl.hpp new file mode 100644 index 000000000..a057460e4 --- /dev/null +++ b/src/mbgl/vulkan/drawable_impl.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +using namespace platform; + +class Drawable::Impl final { +public: + Impl() = default; + ~Impl() = default; + + std::vector segments; + + gfx::IndexVectorBasePtr indexes; + std::size_t vertexCount = 0; + gfx::AttributeDataType vertexType = gfx::AttributeDataType::Invalid; + + gfx::AttributeBindingArray attributeBindings; + gfx::AttributeBindingArray instanceBindings; + + vulkan::UniformBufferArray uniformBuffers; + + std::size_t vertexAttrId = 0; + + std::optional renderPassDescriptor; + + gfx::DepthMode depthFor3D = gfx::DepthMode::disabled(); + gfx::StencilMode stencilFor3D = gfx::StencilMode::disabled(); + + PipelineInfo pipelineInfo; + + std::vector vulkanVertexBuffers; + std::vector vulkanVertexOffsets; +}; + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/index_buffer_resource.cpp b/src/mbgl/vulkan/index_buffer_resource.cpp new file mode 100644 index 000000000..e67d04c40 --- /dev/null +++ b/src/mbgl/vulkan/index_buffer_resource.cpp @@ -0,0 +1,26 @@ +#include + +#include + +namespace mbgl { +namespace vulkan { + +IndexBufferResource::IndexBufferResource(BufferResource&& ptr) noexcept + : buffer(std::move(ptr)) { + if (buffer) { + auto& stats = buffer.getContext().renderingStats(); + stats.numIndexBuffers++; + stats.memIndexBuffers += buffer.getSizeInBytes(); + } +} + +IndexBufferResource::~IndexBufferResource() noexcept { + if (buffer) { + auto& stats = buffer.getContext().renderingStats(); + stats.numIndexBuffers--; + stats.memIndexBuffers -= buffer.getSizeInBytes(); + } +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/layer_group.cpp b/src/mbgl/vulkan/layer_group.cpp new file mode 100644 index 000000000..e5344c681 --- /dev/null +++ b/src/mbgl/vulkan/layer_group.cpp @@ -0,0 +1,89 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +LayerGroup::LayerGroup(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) + : mbgl::LayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} + +void LayerGroup::upload(gfx::UploadPass& uploadPass) { + if (!enabled) { + return; + } + +#if !defined(NDEBUG) + const auto debugGroup = uploadPass.createDebugGroup(getName() + "-upload"); +#endif + + visitDrawables([&](gfx::Drawable& drawable_) { + if (drawable_.getEnabled()) { + auto& drawable = static_cast(drawable_); + drawable.upload(uploadPass); + } + }); +} + +void LayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { + if (!enabled || !getDrawableCount() || !parameters.renderPass) { + return; + } + +#if !defined(NDEBUG) + const auto debugGroup = parameters.encoder->createDebugGroup(getName() + "-render"); +#endif + + auto& renderPass = static_cast(*parameters.renderPass); + + bool bindUBOs = false; + visitDrawables([&](gfx::Drawable& drawable) { + if (!drawable.getEnabled() || !drawable.hasRenderPass(parameters.pass)) { + return; + } + + for (const auto& tweaker : drawable.getTweakers()) { + tweaker->execute(drawable, parameters); + } + + if (!bindUBOs) { + bindUniformBuffers(renderPass); + bindUBOs = true; + } + + auto& drawableUniforms = drawable.mutableUniformBuffers(); + for (size_t i = 0; i < uniformBuffers.allocatedSize(); ++i) { + if (uniformBuffers.get(i)) { + drawableUniforms.set(i, uniformBuffers.get(i)); + } + } + + drawable.draw(parameters); + }); + + if (bindUBOs) { + unbindUniformBuffers(renderPass); + } +} + +void LayerGroup::bindUniformBuffers(RenderPass& renderPass) const noexcept { + for (size_t id = 0; id < uniformBuffers.allocatedSize(); id++) { + const auto& uniformBuffer = uniformBuffers.get(id); + if (!uniformBuffer) continue; + const auto& buffer = static_cast(*uniformBuffer); + const auto& resource = buffer.getBufferResource(); + renderPass.bindVertex(resource, 0, id); + renderPass.bindFragment(resource, 0, id); + } +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/offscreen_texture.cpp b/src/mbgl/vulkan/offscreen_texture.cpp new file mode 100644 index 000000000..0e8797af3 --- /dev/null +++ b/src/mbgl/vulkan/offscreen_texture.cpp @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +class OffscreenTextureResource final : public RenderableResource { +public: + OffscreenTextureResource(RendererBackend& backend_, const Size size_, const gfx::TextureChannelDataType type_) + : RenderableResource(backend_), + size(size_), + type(type_) { + assert(!size.isEmpty()); + + extent.width = size.width; + extent.height = size.height; + + colorTexture = backend.getContext().createTexture2D(); + auto& texture = static_cast(*colorTexture); + + texture.setSize(size); + texture.setFormat(gfx::TexturePixelType::RGBA, type); + texture.setSamplerConfiguration( + {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + texture.setUsage(Texture2DUsage::Attachment); + + backend.getContext().renderingStats().numFrameBuffers++; + } + + ~OffscreenTextureResource() noexcept override { + framebuffer.reset(); + renderPass.reset(); + colorTexture.reset(); + + backend.getContext().renderingStats().numFrameBuffers--; + } + + void bind() override { + colorTexture->create(); + createRenderPass(); + } + + const vk::UniqueFramebuffer& getFramebuffer() const override { return framebuffer; }; + + PremultipliedImage readStillImage() { + assert(false); + return {}; + } + + gfx::Texture2DPtr& getTexture() { + assert(colorTexture); + return colorTexture; + } + + void createRenderPass() { + if (renderPass) return; + + assert(colorTexture); + auto& texture = static_cast(*colorTexture); + + const auto colorAttachment = vk::AttachmentDescription(vk::AttachmentDescriptionFlags()) + .setFormat(texture.getVulkanFormat()) + .setSamples(vk::SampleCountFlagBits::e1) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare) + .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare) + .setInitialLayout(vk::ImageLayout::eUndefined) + .setFinalLayout(vk::ImageLayout::eShaderReadOnlyOptimal); + + const vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal); + + const auto subpass = vk::SubpassDescription(vk::SubpassDescriptionFlags()) + .setPipelineBindPoint(vk::PipelineBindPoint::eGraphics) + .setColorAttachments(colorAttachmentRef); + + const auto subpassSrcStageMask = vk::PipelineStageFlags() | vk::PipelineStageFlagBits::eColorAttachmentOutput | + vk::PipelineStageFlagBits::eLateFragmentTests; + + const auto subpassDstStageMask = vk::PipelineStageFlags() | vk::PipelineStageFlagBits::eColorAttachmentOutput | + vk::PipelineStageFlagBits::eEarlyFragmentTests; + + const auto subpassSrcAccessMask = vk::AccessFlags() | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + + const auto subpassDstAccessMask = vk::AccessFlags() | vk::AccessFlagBits::eColorAttachmentWrite | + vk::AccessFlagBits::eDepthStencilAttachmentWrite; + + const auto subpassDependency = vk::SubpassDependency() + .setSrcSubpass(VK_SUBPASS_EXTERNAL) + .setDstSubpass(0) + .setSrcStageMask(subpassSrcStageMask) + .setDstStageMask(subpassDstStageMask) + .setSrcAccessMask(subpassSrcAccessMask) + .setDstAccessMask(subpassDstAccessMask); + + const auto renderPassCreateInfo = vk::RenderPassCreateInfo() + .setAttachments(colorAttachment) + .setSubpasses(subpass) + .setDependencies(subpassDependency); + + renderPass = backend.getDevice()->createRenderPassUnique(renderPassCreateInfo); + + const auto framebufferCreateInfo = vk::FramebufferCreateInfo() + .setRenderPass(renderPass.get()) + .setAttachments(texture.getVulkanImageView().get()) + .setAttachmentCount(1) + .setWidth(extent.width) + .setHeight(extent.height) + .setLayers(1); + + framebuffer = backend.getDevice()->createFramebufferUnique(framebufferCreateInfo); + } + +private: + const Size size; + const gfx::TextureChannelDataType type; + + gfx::Texture2DPtr colorTexture; + vk::UniqueFramebuffer framebuffer; +}; + +OffscreenTexture::OffscreenTexture(Context& context, + const Size size_, + const gfx::TextureChannelDataType type, + [[maybe_unused]] bool depth, + [[maybe_unused]] bool stencil) + : gfx::OffscreenTexture(size, std::make_unique(context.getBackend(), size_, type)) { + assert(!depth); + assert(!stencil); +} + +bool OffscreenTexture::isRenderable() { + return true; +} + +PremultipliedImage OffscreenTexture::readStillImage() { + return getResource().readStillImage(); +} + +const gfx::Texture2DPtr& OffscreenTexture::getTexture() { + return getResource().getTexture(); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/pipeline.cpp b/src/mbgl/vulkan/pipeline.cpp new file mode 100644 index 000000000..2ad334427 --- /dev/null +++ b/src/mbgl/vulkan/pipeline.cpp @@ -0,0 +1,441 @@ +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +vk::Format PipelineInfo::vulkanFormat(const gfx::AttributeDataType& value) { + switch (value) { + case gfx::AttributeDataType::Byte: + return vk::Format::eR8Sscaled; + case gfx::AttributeDataType::Byte2: + return vk::Format::eR8G8Sscaled; + case gfx::AttributeDataType::Byte3: + return vk::Format::eR8G8B8Sscaled; + case gfx::AttributeDataType::Byte4: + return vk::Format::eR8G8B8A8Sscaled; + case gfx::AttributeDataType::UByte: + return vk::Format::eR8Uscaled; + case gfx::AttributeDataType::UByte2: + return vk::Format::eR8G8Uscaled; + case gfx::AttributeDataType::UByte3: + return vk::Format::eR8G8B8Uscaled; + case gfx::AttributeDataType::UByte4: + return vk::Format::eR8G8B8A8Uscaled; + case gfx::AttributeDataType::Short: + return vk::Format::eR16Sscaled; + case gfx::AttributeDataType::Short2: + return vk::Format::eR16G16Sscaled; + case gfx::AttributeDataType::Short3: + return vk::Format::eR16G16B16Sscaled; + case gfx::AttributeDataType::Short4: + return vk::Format::eR16G16B16A16Sscaled; + case gfx::AttributeDataType::UShort: + return vk::Format::eR16Uscaled; + case gfx::AttributeDataType::UShort2: + return vk::Format::eR16G16Uscaled; + case gfx::AttributeDataType::UShort3: + return vk::Format::eR16G16B16Uscaled; + case gfx::AttributeDataType::UShort4: + return vk::Format::eR16G16B16A16Uscaled; + case gfx::AttributeDataType::Int: + return vk::Format::eR32Sint; + case gfx::AttributeDataType::Int2: + return vk::Format::eR32G32Sint; + case gfx::AttributeDataType::Int3: + return vk::Format::eR32G32B32Sint; + case gfx::AttributeDataType::Int4: + return vk::Format::eR32G32B32A32Sint; + case gfx::AttributeDataType::UInt: + return vk::Format::eR32Uint; + case gfx::AttributeDataType::UInt2: + return vk::Format::eR32G32Uint; + case gfx::AttributeDataType::UInt3: + return vk::Format::eR32G32B32Uint; + case gfx::AttributeDataType::UInt4: + return vk::Format::eR32G32B32A32Uint; + case gfx::AttributeDataType::Float: + return vk::Format::eR32Sfloat; + case gfx::AttributeDataType::Float2: + return vk::Format::eR32G32Sfloat; + case gfx::AttributeDataType::Float3: + return vk::Format::eR32G32B32Sfloat; + case gfx::AttributeDataType::Float4: + return vk::Format::eR32G32B32A32Sfloat; + + default: + [[fallthrough]]; + case gfx::AttributeDataType::UShort8: + [[fallthrough]]; + case gfx::AttributeDataType::Invalid: + return vk ::Format::eUndefined; + } +} + +vk::PrimitiveTopology PipelineInfo::vulkanPrimitiveTopology(const gfx::DrawModeType& value) { + switch (value) { + case gfx::DrawModeType::Points: + return vk::PrimitiveTopology::ePointList; + case gfx::DrawModeType::Lines: + return vk::PrimitiveTopology::eLineList; + case gfx::DrawModeType::LineLoop: + return vk::PrimitiveTopology::eLineStrip; + case gfx::DrawModeType::LineStrip: + return vk::PrimitiveTopology::eLineStrip; + case gfx::DrawModeType::Triangles: + return vk::PrimitiveTopology::eTriangleList; + case gfx::DrawModeType::TriangleStrip: + return vk::PrimitiveTopology::eTriangleStrip; + case gfx::DrawModeType::TriangleFan: + return vk::PrimitiveTopology::eTriangleFan; + default: + return vk::PrimitiveTopology::eTriangleList; + } +} + +vk::CullModeFlagBits PipelineInfo::vulkanCullMode(const gfx::CullFaceSideType& value) { + switch (value) { + case gfx::CullFaceSideType::Back: + return vk::CullModeFlagBits::eBack; + case gfx::CullFaceSideType::Front: + return vk::CullModeFlagBits::eFront; + case gfx::CullFaceSideType::FrontAndBack: + return vk::CullModeFlagBits::eFrontAndBack; + default: + return vk::CullModeFlagBits::eNone; + } +} + +vk::FrontFace PipelineInfo::vulkanFrontFace(const gfx::CullFaceWindingType& value) { + switch (value) { + default: + [[fallthrough]]; + case gfx::CullFaceWindingType::Clockwise: + return vk::FrontFace::eClockwise; + case gfx::CullFaceWindingType::CounterClockwise: + return vk::FrontFace::eCounterClockwise; + } +} + +vk::BlendOp PipelineInfo::vulkanBlendOp(const gfx::ColorBlendEquationType& value) { + switch (value) { + default: + [[fallthrough]]; + case gfx::ColorBlendEquationType::Add: + return vk::BlendOp::eAdd; + case gfx::ColorBlendEquationType::Subtract: + return vk::BlendOp::eSubtract; + case gfx::ColorBlendEquationType::ReverseSubtract: + return vk::BlendOp::eReverseSubtract; + } +} + +vk::BlendFactor PipelineInfo::vulkanBlendFactor(const gfx::ColorBlendFactorType& value) { + switch (value) { + default: + [[fallthrough]]; + case gfx::ColorBlendFactorType::Zero: + return vk::BlendFactor::eZero; + case gfx::ColorBlendFactorType::One: + return vk::BlendFactor::eOne; + case gfx::ColorBlendFactorType::SrcColor: + return vk::BlendFactor::eSrcColor; + case gfx::ColorBlendFactorType::OneMinusSrcColor: + return vk::BlendFactor::eOneMinusSrcColor; + case gfx::ColorBlendFactorType::SrcAlpha: + return vk::BlendFactor::eSrcAlpha; + case gfx::ColorBlendFactorType::OneMinusSrcAlpha: + return vk::BlendFactor::eOneMinusSrcAlpha; + case gfx::ColorBlendFactorType::DstAlpha: + return vk::BlendFactor::eDstAlpha; + case gfx::ColorBlendFactorType::OneMinusDstAlpha: + return vk::BlendFactor::eOneMinusDstAlpha; + case gfx::ColorBlendFactorType::DstColor: + return vk::BlendFactor::eDstColor; + case gfx::ColorBlendFactorType::OneMinusDstColor: + return vk::BlendFactor::eOneMinusDstColor; + case gfx::ColorBlendFactorType::SrcAlphaSaturate: + return vk::BlendFactor::eSrcAlphaSaturate; + case gfx::ColorBlendFactorType::ConstantColor: + return vk::BlendFactor::eConstantColor; + case gfx::ColorBlendFactorType::OneMinusConstantColor: + return vk::BlendFactor::eOneMinusConstantColor; + case gfx::ColorBlendFactorType::ConstantAlpha: + return vk::BlendFactor::eConstantAlpha; + case gfx::ColorBlendFactorType::OneMinusConstantAlpha: + return vk::BlendFactor::eOneMinusConstantAlpha; + } +} + +vk::CompareOp PipelineInfo::vulkanCompareOp(const gfx::DepthFunctionType& value) { + switch (value) { + default: + [[fallthrough]]; + case gfx::DepthFunctionType::Never: + return vk::CompareOp::eNever; + case gfx::DepthFunctionType::Less: + return vk::CompareOp::eLess; + case gfx::DepthFunctionType::Equal: + return vk::CompareOp::eEqual; + case gfx::DepthFunctionType::LessEqual: + return vk::CompareOp::eLessOrEqual; + case gfx::DepthFunctionType::Greater: + return vk::CompareOp::eGreater; + case gfx::DepthFunctionType::NotEqual: + return vk::CompareOp::eNotEqual; + case gfx::DepthFunctionType::GreaterEqual: + return vk::CompareOp::eGreaterOrEqual; + case gfx::DepthFunctionType::Always: + return vk::CompareOp::eAlways; + } +} + +vk::CompareOp PipelineInfo::vulkanCompareOp(const gfx::StencilFunctionType& value) { + switch (value) { + default: + [[fallthrough]]; + case gfx::StencilFunctionType::Never: + return vk::CompareOp::eNever; + case gfx::StencilFunctionType::Less: + return vk::CompareOp::eLess; + case gfx::StencilFunctionType::Equal: + return vk::CompareOp::eEqual; + case gfx::StencilFunctionType::LessEqual: + return vk::CompareOp::eLessOrEqual; + case gfx::StencilFunctionType::Greater: + return vk::CompareOp::eGreater; + case gfx::StencilFunctionType::NotEqual: + return vk::CompareOp::eNotEqual; + case gfx::StencilFunctionType::GreaterEqual: + return vk::CompareOp::eGreaterOrEqual; + case gfx::StencilFunctionType::Always: + return vk::CompareOp::eAlways; + } +} + +vk::StencilOp PipelineInfo::vulkanStencilOp(const gfx::StencilOpType& value) { + switch (value) { + default: + [[fallthrough]]; + case gfx::StencilOpType::Zero: + return vk::StencilOp::eZero; + case gfx::StencilOpType::Keep: + return vk::StencilOp::eKeep; + case gfx::StencilOpType::Replace: + return vk::StencilOp::eReplace; + case gfx::StencilOpType::Increment: + return vk::StencilOp::eIncrementAndClamp; + case gfx::StencilOpType::Decrement: + return vk::StencilOp::eDecrementAndClamp; + case gfx::StencilOpType::Invert: + return vk::StencilOp::eInvert; + case gfx::StencilOpType::IncrementWrap: + return vk::StencilOp::eIncrementAndWrap; + case gfx::StencilOpType::DecrementWrap: + return vk::StencilOp::eDecrementAndWrap; + } +} + +void PipelineInfo::setCullMode(const gfx::CullFaceMode& value) { + cullMode = value.enabled ? vulkanCullMode(value.side) : vk::CullModeFlagBits::eNone; + frontFace = vulkanFrontFace(value.winding); +} + +void PipelineInfo::setDrawMode(const gfx::DrawModeType& value) { + topology = vulkanPrimitiveTopology(value); +} + +void PipelineInfo::setDrawMode(const gfx::DrawMode& value) { + topology = vulkanPrimitiveTopology(value.type); + + if (value.type == gfx::DrawModeType::Lines || value.type == gfx::DrawModeType::LineStrip || + value.type == gfx::DrawModeType::LineLoop) { + setLineWidth(value.size); + } else { + setLineWidth(1.0f); + } +} + +void PipelineInfo::setColorBlend(const gfx::ColorMode& value) { + setColorBlendFunction(value.blendFunction); + + if (usesBlendConstants()) { + dynamicValues.blendConstants = value.blendColor; + } else { + dynamicValues.blendConstants.reset(); + } + + colorMask = vk::ColorComponentFlags(); + + if (value.mask.r) colorMask |= vk::ColorComponentFlagBits::eR; + if (value.mask.g) colorMask |= vk::ColorComponentFlagBits::eG; + if (value.mask.b) colorMask |= vk::ColorComponentFlagBits::eB; + if (value.mask.a) colorMask |= vk::ColorComponentFlagBits::eA; +} + +void PipelineInfo::setColorBlendFunction(const gfx::ColorMode::BlendFunction& value) { + colorBlend = !value.is(); + + apply_visitor( + [&](const auto& blendFunction) { + colorBlendFunction = vulkanBlendOp(blendFunction.equation); + srcBlendFactor = vulkanBlendFactor(blendFunction.srcFactor); + dstBlendFactor = vulkanBlendFactor(blendFunction.dstFactor); + }, + value); +} + +void PipelineInfo::setDepthWrite(const gfx::DepthMaskType& value) { + switch (value) { + default: + [[fallthrough]]; + case gfx::DepthMaskType::ReadOnly: + depthWrite = false; + break; + case gfx::DepthMaskType::ReadWrite: + depthWrite = true; + break; + } +} + +void PipelineInfo::setDepthMode(const gfx::DepthMode& value) { + depthFunction = vulkanCompareOp(value.func); + setDepthWrite(value.mask); +} + +void PipelineInfo::setStencilMode(const gfx::StencilMode& value) { + if (value.test.is()) { + stencilTest = false; + stencilFunction = vk::CompareOp::eNever; + return; + } + + stencilTest = true; + + if (value.test.is()) { + stencilFunction = vk::CompareOp::eNever; + return; + } + + apply_visitor( + [&](const auto& test) { + stencilFunction = vulkanCompareOp(test.func); + dynamicValues.stencilCompareMask = test.mask; + }, + value.test); + + dynamicValues.stencilWriteMask = value.mask; + dynamicValues.stencilRef = value.ref; + + stencilPass = vulkanStencilOp(value.pass); + stencilFail = vulkanStencilOp(value.fail); + stencilDepthFail = vulkanStencilOp(value.depthFail); +} + +void PipelineInfo::setRenderable(const gfx::Renderable& value) { + const auto& renderableResource = value.getResource(); + + renderPass = renderableResource.getRenderPass().get(); + viewExtent = renderableResource.getExtent(); +} + +void PipelineInfo::setLineWidth(float value) { + wideLines = value != 1.0f; + dynamicValues.lineWidth = value; +} + +bool PipelineInfo::usesBlendConstants() const { + if (srcBlendFactor == vk::BlendFactor::eConstantAlpha || srcBlendFactor == vk::BlendFactor::eConstantColor || + srcBlendFactor == vk::BlendFactor::eOneMinusConstantAlpha || + srcBlendFactor == vk::BlendFactor::eOneMinusConstantColor) { + return true; + } + + if (dstBlendFactor == vk::BlendFactor::eConstantAlpha || dstBlendFactor == vk::BlendFactor::eConstantColor || + dstBlendFactor == vk::BlendFactor::eOneMinusConstantAlpha || + dstBlendFactor == vk::BlendFactor::eOneMinusConstantColor) { + return true; + } + + return false; +} + +void PipelineInfo::updateVertexInputHash() { + vertexInputHash = 0; + + for (const auto& value : inputAttributes) { + util::hash_combine(vertexInputHash, value.binding); + util::hash_combine(vertexInputHash, value.format); + util::hash_combine(vertexInputHash, value.location); + util::hash_combine(vertexInputHash, value.offset); + } + + for (const auto& value : inputBindings) { + util::hash_combine(vertexInputHash, value.binding); + util::hash_combine(vertexInputHash, value.inputRate); + } +} + +std::size_t PipelineInfo::hash() const { + return util::hash(topology, + cullMode, + frontFace, + polygonMode, + colorBlend, + colorBlendFunction, + srcBlendFactor, + dstBlendFactor, + VkColorComponentFlags(colorMask), + depthTest, + depthWrite, + depthFunction, + stencilTest, + stencilFunction, + stencilPass, + stencilFail, + stencilDepthFail, + wideLines, + VkRenderPass(renderPass), + vertexInputHash); +} + +void PipelineInfo::setDynamicValues(const vk::UniqueCommandBuffer& buffer) const { + if (dynamicValues.blendConstants.has_value()) { + buffer->setBlendConstants(dynamicValues.blendConstants.value().data()); + } + + if (stencilTest) { + buffer->setStencilWriteMask(vk::StencilFaceFlagBits::eFrontAndBack, dynamicValues.stencilWriteMask); + buffer->setStencilCompareMask(vk::StencilFaceFlagBits::eFrontAndBack, dynamicValues.stencilCompareMask); + buffer->setStencilReference(vk::StencilFaceFlagBits::eFrontAndBack, dynamicValues.stencilRef); + } + + if (wideLines) { + buffer->setLineWidth(dynamicValues.lineWidth); + } +} + +std::vector PipelineInfo::getDynamicStates() const { + std::vector dynamicStates; + + if (usesBlendConstants()) { + dynamicStates.push_back(vk::DynamicState::eBlendConstants); + } + + if (stencilTest) { + dynamicStates.push_back(vk::DynamicState::eStencilCompareMask); + dynamicStates.push_back(vk::DynamicState::eStencilWriteMask); + dynamicStates.push_back(vk::DynamicState::eStencilReference); + } + + if (wideLines) { + dynamicStates.push_back(vk::DynamicState::eLineWidth); + } + + return dynamicStates; +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/render_pass.cpp b/src/mbgl/vulkan/render_pass.cpp new file mode 100644 index 000000000..5e5624798 --- /dev/null +++ b/src/mbgl/vulkan/render_pass.cpp @@ -0,0 +1,85 @@ +#include + +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const gfx::RenderPassDescriptor& descriptor) + : descriptor(descriptor), + commandEncoder(commandEncoder_) { + auto& resource = descriptor.renderable.getResource(); + + resource.bind(); + + std::array clearValues; + + if (descriptor.clearColor.has_value()) + clearValues[0].setColor(descriptor.clearColor.value().operator std::array()); + clearValues[1].depthStencil.setDepth(descriptor.clearDepth.value_or(1.0f)); + clearValues[1].depthStencil.setStencil(descriptor.clearStencil.value_or(0)); + + const auto renderPassBeginInfo = vk::RenderPassBeginInfo() + .setRenderPass(resource.getRenderPass().get()) + .setFramebuffer(resource.getFramebuffer().get()) + .setRenderArea({{0, 0}, resource.getExtent()}) + .setClearValues(clearValues); + + pushDebugGroup(name); + + commandEncoder.getCommandBuffer()->beginRenderPass(renderPassBeginInfo, vk::SubpassContents::eInline); + + commandEncoder.context.performCleanup(); +} + +RenderPass::~RenderPass() { + endEncoding(); + + popDebugGroup(); +} + +void RenderPass::endEncoding() { + commandEncoder.getCommandBuffer()->endRenderPass(); +} + +void RenderPass::clearStencil(uint32_t value) const { + const auto& resource = descriptor.renderable.getResource(); + const auto& extent = resource.getExtent(); + + const auto attach = vk::ClearAttachment() + .setAspectMask(vk::ImageAspectFlagBits::eStencil) + .setClearValue(vk::ClearDepthStencilValue(0.0f, value)); + + const auto& rect = vk::ClearRect().setBaseArrayLayer(0).setLayerCount(1).setRect( + {{0, 0}, {extent.width, extent.height}}); + + commandEncoder.getCommandBuffer()->clearAttachments(attach, rect); +} + +void RenderPass::pushDebugGroup(const char* name) { + commandEncoder.pushDebugGroup(name); +} + +void RenderPass::popDebugGroup() { + commandEncoder.popDebugGroup(); +} + +void RenderPass::addDebugSignpost(const char* name) { + commandEncoder.getContext().getBackend().insertDebugLabel(commandEncoder.getCommandBuffer().get(), name); +} + +void RenderPass::bindVertex(const BufferResource& buf, std::size_t offset, std::size_t, std::size_t size) { + [[maybe_unused]] const auto actualSize = size ? size : buf.getSizeInBytes() - offset; + assert(actualSize <= buf.getSizeInBytes()); +} + +void RenderPass::bindFragment(const BufferResource& buf, std::size_t offset, std::size_t, std::size_t size) { + [[maybe_unused]] const auto actualSize = size ? size : buf.getSizeInBytes() - offset; + assert(actualSize <= buf.getSizeInBytes()); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/renderable_resource.cpp b/src/mbgl/vulkan/renderable_resource.cpp new file mode 100644 index 000000000..3ee95ed40 --- /dev/null +++ b/src/mbgl/vulkan/renderable_resource.cpp @@ -0,0 +1,344 @@ +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +static bool hasMemoryType(const vk::PhysicalDevice& physicalDevice, const vk::MemoryPropertyFlagBits& type) { + const auto& memoryProps = physicalDevice.getMemoryProperties(); + for (uint32_t i = 0; i < memoryProps.memoryTypeCount; ++i) { + if (memoryProps.memoryTypes[i].propertyFlags & type) { + return true; + } + } + + return false; +} + +SurfaceRenderableResource::~SurfaceRenderableResource() { + // specific order + swapchainFramebuffers.clear(); + renderPass.reset(); + swapchainImageViews.clear(); + swapchainImages.clear(); + swapchain.reset(); + surface.reset(); + + depthAllocation.reset(); + colorAllocations.clear(); +} + +void SurfaceRenderableResource::initColor(uint32_t w, uint32_t h) { + const uint32_t imageCount = backend.getMaxFrames(); + + colorAllocations.reserve(imageCount); + swapchainImages.reserve(imageCount); + + colorFormat = vk::Format::eR8G8B8A8Unorm; + extent = vk::Extent2D(w, h); + + const auto imageUsage = vk::ImageUsageFlags() | vk::ImageUsageFlagBits::eColorAttachment | + vk::ImageUsageFlagBits::eTransferSrc; + + const auto imageCreateInfo = vk::ImageCreateInfo() + .setImageType(vk::ImageType::e2D) + .setFormat(colorFormat) + .setExtent({w, h, 1}) + .setMipLevels(1) + .setArrayLayers(1) + .setSamples(vk::SampleCountFlagBits::e1) + .setTiling(vk::ImageTiling::eOptimal) + .setUsage(imageUsage) + .setSharingMode(vk::SharingMode::eExclusive) + .setInitialLayout(vk::ImageLayout::eUndefined); + + VmaAllocationCreateInfo allocCreateInfo = {}; + + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + for (uint32_t i = 0; i < imageCount; ++i) { + colorAllocations.push_back(std::make_unique(backend.getAllocator())); + auto& colorAllocation = colorAllocations.back(); + + if (!colorAllocation->create(allocCreateInfo, imageCreateInfo)) { + mbgl::Log::Error(mbgl::Event::Render, "Vulkan color texture allocation failed"); + } + + swapchainImages.push_back(colorAllocation->image); + } +} + +void SurfaceRenderableResource::initSwapchain(uint32_t w, uint32_t h) { + const auto& physicalDevice = backend.getPhysicalDevice(); + const auto& device = backend.getDevice(); + + const std::vector& formats = physicalDevice.getSurfaceFormatsKHR(surface.get()); + const auto& formatIt = std::find_if(formats.begin(), formats.end(), [](const vk::SurfaceFormatKHR& format) { + return (format.format == vk::Format::eB8G8R8A8Unorm || format.format == vk::Format::eR8G8B8A8Unorm) && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear; + }); + + 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 + + // pick surface size + const vk::SurfaceCapabilitiesKHR& capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface.get()); + + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + extent = capabilities.currentExtent; + } else { + // update values based on surface limits + extent.width = std::min(std::max(w, capabilities.minImageExtent.width), capabilities.maxImageExtent.width); + extent.height = std::min(std::max(h, capabilities.minImageExtent.height), capabilities.maxImageExtent.height); + } + + uint32_t swapchainImageCount = capabilities.minImageCount + 1; + // check surface limits (0 is unlimited) + if (capabilities.maxImageCount > 0) swapchainImageCount = std::min(swapchainImageCount, capabilities.maxImageCount); + + auto swapchainCreateInfo = vk::SwapchainCreateInfoKHR() + .setSurface(surface.get()) + .setMinImageCount(swapchainImageCount) + .setImageFormat(formatIt->format) + .setImageColorSpace(formatIt->colorSpace) + .setPresentMode(vk::PresentModeKHR::eFifo) + .setImageExtent(extent) + .setImageArrayLayers(1) + .setImageUsage(vk::ImageUsageFlagBits::eColorAttachment); + + int32_t graphicsQueueIndex = backend.getGraphicsQueueIndex(); + 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) + // rework to vk::SharingMode::eExclusive + queue ownership + swapchainCreateInfo.setImageSharingMode(vk::SharingMode::eConcurrent); + const std::array queueIndices = {static_cast(graphicsQueueIndex), + static_cast(presentQueueIndex)}; + swapchainCreateInfo.setQueueFamilyIndices(queueIndices); + } else { + swapchainCreateInfo.setImageSharingMode(vk::SharingMode::eExclusive); + } + + swapchainCreateInfo.setPreTransform(capabilities.currentTransform); + swapchainCreateInfo.setClipped(VK_TRUE); + + if (capabilities.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eInherit) { + swapchainCreateInfo.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eInherit); + } else { + swapchainCreateInfo.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque); + } + + // update this when recreating + swapchainCreateInfo.setOldSwapchain(vk::SwapchainKHR(swapchain.get())); + + swapchain = device->createSwapchainKHRUnique(swapchainCreateInfo); + swapchainImages = device->getSwapchainImagesKHR(swapchain.get()); + + colorFormat = swapchainCreateInfo.imageFormat; + extent = swapchainCreateInfo.imageExtent; +} + +void SurfaceRenderableResource::initDepthStencil() { + const auto& physicalDevice = backend.getPhysicalDevice(); + const auto& device = backend.getDevice(); + + // check for depth format support + const std::vector formats = { + vk::Format::eD24UnormS8Uint, + vk::Format::eD32SfloatS8Uint, + vk::Format::eD16UnormS8Uint, + }; + + const auto& formatIt = std::find_if(formats.begin(), formats.end(), [&](const auto& format) { + const auto& formatProps = physicalDevice.getFormatProperties(format); + return formatProps.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment; + }); + + if (formatIt == formats.end()) { + mbgl::Log::Error(mbgl::Event::Render, "Depth/Stencil format not available"); + return; + } + + depthFormat = *formatIt; + + const bool hasLazyMemory = hasMemoryType(physicalDevice, vk::MemoryPropertyFlagBits::eLazilyAllocated); + const auto memoryUsage = hasLazyMemory ? VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED + : VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + + const auto imageUsage = vk::ImageUsageFlags() | vk::ImageUsageFlagBits::eDepthStencilAttachment | + vk::ImageUsageFlagBits::eTransientAttachment; + + const auto imageCreateInfo = vk::ImageCreateInfo() + .setImageType(vk::ImageType::e2D) + .setFormat(depthFormat) + .setExtent({extent.width, extent.height, 1}) + .setMipLevels(1) + .setArrayLayers(1) + .setSamples(vk::SampleCountFlagBits::e1) + .setTiling(vk::ImageTiling::eOptimal) + .setUsage(imageUsage) + .setSharingMode(vk::SharingMode::eExclusive) + .setInitialLayout(vk::ImageLayout::eUndefined); + + VmaAllocationCreateInfo allocCreateInfo = {}; + allocCreateInfo.usage = memoryUsage; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + + depthAllocation = std::make_unique(backend.getAllocator()); + if (!depthAllocation->create(allocCreateInfo, imageCreateInfo)) { + mbgl::Log::Error(mbgl::Event::Render, "Vulkan depth texture allocation failed"); + return; + } + + const auto imageViewCreateInfo = + vk::ImageViewCreateInfo() + .setImage(depthAllocation->image) + .setViewType(vk::ImageViewType::e2D) + .setFormat(depthFormat) + .setComponents(vk::ComponentMapping()) // defaults to vk::ComponentSwizzle::eIdentity + .setSubresourceRange(vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1)); + + depthAllocation->imageView = device->createImageViewUnique(imageViewCreateInfo); + + backend.setDebugName(vk::Image(depthAllocation->image), "SwapchainDepthImage"); + backend.setDebugName(depthAllocation->imageView.get(), "SwapchainDepthImageView"); +} + +const vk::Image SurfaceRenderableResource::getAcquiredImage() const { + if (surface) { + return swapchainImages[acquiredImageIndex]; + } + + return colorAllocations[acquiredImageIndex]->image; +} + +void SurfaceRenderableResource::init(uint32_t w, uint32_t h) { + if (surface) { + initSwapchain(w, h); + } else { + initColor(w, h); + } + + const auto& device = backend.getDevice(); + + // create swapchain image views + swapchainImageViews.reserve(swapchainImages.size()); + + auto imageViewCreateInfo = vk::ImageViewCreateInfo() + .setViewType(vk::ImageViewType::e2D) + .setFormat(colorFormat) + .setComponents(vk::ComponentMapping()) // defaults to vk::ComponentSwizzle::eIdentity + .setSubresourceRange( + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); + + for (const auto& image : swapchainImages) { + imageViewCreateInfo.setImage(image); + swapchainImageViews.push_back(device->createImageViewUnique(imageViewCreateInfo)); + + const size_t index = swapchainImageViews.size() - 1; + backend.setDebugName(vk::Image(image), "SwapchainImage_" + std::to_string(index)); + backend.setDebugName(vk::Image(image), "SwapchainImageView_" + std::to_string(index)); + } + + // depth resources + initDepthStencil(); + + // create render pass + const auto colorLayout = surface ? vk::ImageLayout::ePresentSrcKHR : vk::ImageLayout::eTransferSrcOptimal; + const auto colorAttachment = vk::AttachmentDescription(vk::AttachmentDescriptionFlags()) + .setFormat(colorFormat) + .setSamples(vk::SampleCountFlagBits::e1) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare) + .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare) + .setInitialLayout(vk::ImageLayout::eUndefined) + .setFinalLayout(colorLayout); + + const vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal); + + const auto depthAttachment = vk::AttachmentDescription() + .setFormat(depthFormat) + .setSamples(vk::SampleCountFlagBits::e1) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eDontCare) + .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare) + .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare) + .setInitialLayout(vk::ImageLayout::eUndefined) + .setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal); + + const vk::AttachmentReference depthAttachmentRef(1, vk::ImageLayout::eDepthStencilAttachmentOptimal); + + const auto subpass = vk::SubpassDescription() + .setPipelineBindPoint(vk::PipelineBindPoint::eGraphics) + .setColorAttachmentCount(1) + .setColorAttachments(colorAttachmentRef) + .setPDepthStencilAttachment(&depthAttachmentRef); + + const auto subpassSrcStageMask = vk::PipelineStageFlags() | vk::PipelineStageFlagBits::eColorAttachmentOutput | + vk::PipelineStageFlagBits::eLateFragmentTests; + + const auto subpassDstStageMask = vk::PipelineStageFlags() | vk::PipelineStageFlagBits::eColorAttachmentOutput | + vk::PipelineStageFlagBits::eEarlyFragmentTests; + + const auto subpassSrcAccessMask = vk::AccessFlags() | vk::AccessFlagBits::eDepthStencilAttachmentWrite; + + const auto subpassDstAccessMask = vk::AccessFlags() | vk::AccessFlagBits::eColorAttachmentWrite | + vk::AccessFlagBits::eDepthStencilAttachmentWrite; + + const auto subpassDependency = vk::SubpassDependency() + .setSrcSubpass(VK_SUBPASS_EXTERNAL) + .setDstSubpass(0) + .setSrcStageMask(subpassSrcStageMask) + .setDstStageMask(subpassDstStageMask) + .setSrcAccessMask(subpassSrcAccessMask) + .setDstAccessMask(subpassDstAccessMask); + + const std::array attachments = {colorAttachment, depthAttachment}; + const auto renderPassCreateInfo = + vk::RenderPassCreateInfo().setAttachments(attachments).setSubpasses(subpass).setDependencies(subpassDependency); + + renderPass = device->createRenderPassUnique(renderPassCreateInfo); + + // create swapchain framebuffers + swapchainFramebuffers.reserve(swapchainImageViews.size()); + + auto framebufferCreateInfo = vk::FramebufferCreateInfo() + .setRenderPass(renderPass.get()) + .setAttachmentCount(2) + .setWidth(extent.width) + .setHeight(extent.height) + .setLayers(1); + + for (const auto& imageView : swapchainImageViews) { + const std::array imageViews = {imageView.get(), depthAllocation->imageView.get()}; + + framebufferCreateInfo.setAttachments(imageViews); + swapchainFramebuffers.push_back(device->createFramebufferUnique(framebufferCreateInfo)); + } +} + +void SurfaceRenderableResource::recreateSwapchain() { + if (!surface) return; + + backend.getDevice()->waitIdle(); + + swapchainFramebuffers.clear(); + renderPass.reset(); + swapchainImageViews.clear(); + swapchainImages.clear(); + + init(extent.width, extent.height); +} + +const vk::UniqueFramebuffer& SurfaceRenderableResource::getFramebuffer() const { + return swapchainFramebuffers[backend.getContext().getCurrentFrameResourceIndex()]; +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/renderer_backend.cpp b/src/mbgl/vulkan/renderer_backend.cpp new file mode 100644 index 000000000..70718a9f7 --- /dev/null +++ b/src/mbgl/vulkan/renderer_backend.cpp @@ -0,0 +1,616 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef ENABLE_VULKAN_VALIDATION +#include +#endif + +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +#ifdef ENABLE_VMA_DEBUG + +#define VMA_DEBUG_MARGIN 16 +#define VMA_DEBUG_DETECT_CORRUPTION 1 +#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 + +#define VMA_LEAK_LOG_FORMAT(format, ...) \ + { \ + char buffer[4096]; \ + sprintf(buffer, format, __VA_ARGS__); \ + mbgl::Log::Info(mbgl::Event::Render, buffer); \ + } + +#endif + +#define VMA_IMPLEMENTATION +#include "vk_mem_alloc.h" + +#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE + +#ifdef _WIN32 +#include +#endif + +#include "renderdoc_app.h" +static RENDERDOC_API_1_1_2* g_rdoc_api = nullptr; + +#endif + +namespace mbgl { +namespace vulkan { + +template +static bool checkAvailability(const std::vector& availableValues, + const std::vector& requiredValues, + const F& getter) { + for (const auto& requiredValue : requiredValues) { + bool found = false; + for (const auto& availableValue : availableValues) { + if (strcmp(requiredValue, getter(availableValue)) == 0) { + found = true; + break; + } + } + + if (!found) return false; + } + + return true; +} + +RendererBackend::RendererBackend(const gfx::ContextMode contextMode_) + : gfx::RendererBackend(contextMode_), + allocator(nullptr) {} + +RendererBackend::~RendererBackend() { + destroyResources(); +} + +std::unique_ptr RendererBackend::createContext() { + return std::make_unique(*this); +} + +std::vector RendererBackend::getLayers() { + return { +#ifdef ENABLE_VULKAN_VALIDATION + "VK_LAYER_KHRONOS_validation" +#endif + }; +} + +std::vector RendererBackend::getInstanceExtensions() { + return {}; +} + +std::vector RendererBackend::getDeviceExtensions() { + return getDefaultRenderable().getResource().getDeviceExtensions(); +} + +std::vector RendererBackend::getDebugExtensions() { + std::vector extensions; + + const auto& availableExtensions = vk::enumerateInstanceExtensionProperties(); + + debugUtilsEnabled = checkAvailability( + availableExtensions, {VK_EXT_DEBUG_UTILS_EXTENSION_NAME}, [](const vk::ExtensionProperties& value) { + return value.extensionName.data(); + }); + + if (debugUtilsEnabled) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } else { + // check for VK_EXT_debug_report (deprecated) + bool debugReportAvailable = checkAvailability( + availableExtensions, {VK_EXT_DEBUG_REPORT_EXTENSION_NAME}, [](const vk::ExtensionProperties& value) { + return value.extensionName.data(); + }); + + if (debugReportAvailable) { + extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } else { + mbgl::Log::Error(mbgl::Event::Render, "No debugging extension available"); + } + } + + return extensions; +} + +void RendererBackend::beginDebugLabel([[maybe_unused]] const vk::CommandBuffer& buffer, + [[maybe_unused]] const char* name, + [[maybe_unused]] const std::array& color) const { +#ifdef ENABLE_VULKAN_VALIDATION + if (!debugUtilsEnabled) return; + buffer.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT().setPLabelName(name).setColor(color)); +#endif +} + +void RendererBackend::endDebugLabel([[maybe_unused]] const vk::CommandBuffer& buffer) const { +#ifdef ENABLE_VULKAN_VALIDATION + if (!debugUtilsEnabled) return; + buffer.endDebugUtilsLabelEXT(); +#endif +} + +void RendererBackend::insertDebugLabel([[maybe_unused]] const vk::CommandBuffer& buffer, + [[maybe_unused]] const char* name) const { +#ifdef ENABLE_VULKAN_VALIDATION + if (!debugUtilsEnabled) return; + buffer.insertDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT().setPLabelName(name)); +#endif +} + +void RendererBackend::initFrameCapture() { +#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE + +#ifdef _WIN32 + if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { + pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI"); + int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&g_rdoc_api); + assert(ret == 1); + } +#elif __unix__ + if (void* mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD)) { + pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI"); + int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&g_rdoc_api); + assert(ret == 1); + } +#endif + +#endif +} + +void RendererBackend::startFrameCapture() { +#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE + if (g_rdoc_api) { + g_rdoc_api->StartFrameCapture(nullptr, nullptr); + } +#endif +} + +void RendererBackend::endFrameCapture() { +#ifdef ENABLE_RENDERDOC_FRAME_CAPTURE + if (g_rdoc_api) { + g_rdoc_api->EndFrameCapture(nullptr, nullptr); + } +#endif +} + +#ifdef ENABLE_VULKAN_VALIDATION + +static VKAPI_ATTR VkBool32 VKAPI_CALL vkDebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT, + const VkDebugUtilsMessengerCallbackDataEXT* callbackData, + void*) { + EventSeverity mbglSeverity = EventSeverity::Debug; + + switch (messageSeverity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + mbglSeverity = EventSeverity::Debug; + break; + + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + mbglSeverity = EventSeverity::Info; + break; + + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + mbglSeverity = EventSeverity::Warning; + break; + + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + mbglSeverity = EventSeverity::Error; + break; + + default: + return VK_FALSE; + } + + mbgl::Log::Record(mbglSeverity, mbgl::Event::Render, callbackData->pMessage); + + return VK_FALSE; +} + +static VKAPI_ATTR VkBool32 vkDebugReportCallback(VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objectType, + [[maybe_unused]] uint64_t object, + [[maybe_unused]] size_t location, + int32_t messageCode, + const char* pLayerPrefix, + const char* pMessage, + [[maybe_unused]] void* pUserData) { + EventSeverity mbglSeverity = EventSeverity::Debug; + + if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { + mbglSeverity = EventSeverity::Info; + } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { + mbglSeverity = EventSeverity::Warning; + } else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) { + mbglSeverity = EventSeverity::Warning; + } else if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { + mbglSeverity = EventSeverity::Error; + } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { + mbglSeverity = EventSeverity::Debug; + } + + const std::string message = "[" + vk::to_string(vk::DebugReportObjectTypeEXT(objectType)) + "]" + "[code - " + + std::to_string(messageCode) + "]" + "[layer - " + pLayerPrefix + "]" + pMessage; + + mbgl::Log::Record(mbglSeverity, mbgl::Event::Render, message); + + return VK_FALSE; +} + +#endif + +void RendererBackend::initDebug() { +#ifdef ENABLE_VULKAN_VALIDATION + if (debugUtilsEnabled) { + const vk::DebugUtilsMessageSeverityFlagsEXT severity = vk::DebugUtilsMessageSeverityFlagsEXT() | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError; + + const vk::DebugUtilsMessageTypeFlagsEXT type = vk::DebugUtilsMessageTypeFlagsEXT() | + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | + vk::DebugUtilsMessageTypeFlagBitsEXT::eDeviceAddressBinding; + + const auto createInfo = + vk::DebugUtilsMessengerCreateInfoEXT().setMessageSeverity(severity).setMessageType(type).setPfnUserCallback( + vkDebugUtilsCallback); + + debugUtilsCallback = instance->createDebugUtilsMessengerEXTUnique(createInfo); + + if (!debugUtilsCallback) { + mbgl::Log::Error(mbgl::Event::Render, "Failed to register Vulkan debug utils callback"); + } + } else { + const vk::DebugReportFlagsEXT flags = vk::DebugReportFlagsEXT() | vk::DebugReportFlagBitsEXT::eDebug | + vk::DebugReportFlagBitsEXT::eError | + vk::DebugReportFlagBitsEXT::eInformation | + vk::DebugReportFlagBitsEXT::ePerformanceWarning | + vk::DebugReportFlagBitsEXT::eWarning; + + const auto createInfo = vk::DebugReportCallbackCreateInfoEXT().setFlags(flags).setPfnCallback( + vkDebugReportCallback); + + debugReportCallback = instance->createDebugReportCallbackEXTUnique(createInfo); + + if (!debugReportCallback) { + mbgl::Log::Error(mbgl::Event::Render, "Failed to register Vulkan debug report callback"); + } + } +#endif +} + +void RendererBackend::init() { + initFrameCapture(); + initInstance(); + initSurface(); + initDevice(); + initAllocator(); + initSwapchain(); + initCommandPool(); +} + +void RendererBackend::initInstance() { + // initialize minimal set of function pointers + VULKAN_HPP_DEFAULT_DISPATCHER.init(dynamicLoader); + + // Vulkan 1.1 on Android is supported on 71% of devices (compared to 1.3 with 6%) as of April 23 2024 + // https://vulkan.gpuinfo.org/ + vk::ApplicationInfo appInfo("maplibre-native", 1, "maplibre-native", 1, VK_API_VERSION_1_0); + vk::InstanceCreateInfo createInfo(vk::InstanceCreateFlags(), &appInfo); + + const auto& layers = getLayers(); + + bool layersAvailable = checkAvailability(vk::enumerateInstanceLayerProperties(), + layers, + [](const vk::LayerProperties& value) { return value.layerName.data(); }); + + if (layersAvailable) { + createInfo.setPEnabledLayerNames(layers); + } else { + mbgl::Log::Error(mbgl::Event::Render, "Vulkan layers not found"); + } + + auto extensions = getInstanceExtensions(); + + bool extensionsAvailable = checkAvailability( + vk::enumerateInstanceExtensionProperties(), extensions, [](const vk::ExtensionProperties& value) { + return value.extensionName.data(); + }); + +#ifdef ENABLE_VULKAN_VALIDATION + const auto& debugExtensions = getDebugExtensions(); + extensions.insert(extensions.end(), debugExtensions.begin(), debugExtensions.end()); + +#ifdef ENABLE_VULKAN_GPU_ASSISTED_VALIDATION + appInfo.setApiVersion(VK_API_VERSION_1_1); + + const std::array validationFeatures = { + vk::ValidationFeatureEnableEXT::eGpuAssisted, vk::ValidationFeatureEnableEXT::eGpuAssistedReserveBindingSlot}; + const vk::ValidationFeaturesEXT validationFeatureInfo(validationFeatures); + + createInfo.setPNext(&validationFeatureInfo); +#endif + +#endif + + if (extensionsAvailable) { + createInfo.setPEnabledExtensionNames(extensions); + } else { + mbgl::Log::Error(mbgl::Event::Render, "Vulkan extensions not found"); + } + + instance = vk::createInstanceUnique(createInfo); + + // initialize function pointers for instance + VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); + +#ifdef ENABLE_VULKAN_VALIDATION + // enable validation layer callback + initDebug(); +#endif +} + +void RendererBackend::initSurface() { + getDefaultRenderable().getResource().createPlatformSurface(); +} + +void RendererBackend::initAllocator() { + VmaVulkanFunctions functions = {}; + + functions.vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr; + functions.vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr; + + functions.vkGetPhysicalDeviceProperties = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetPhysicalDeviceProperties; + functions.vkGetPhysicalDeviceMemoryProperties = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetPhysicalDeviceMemoryProperties; + functions.vkAllocateMemory = VULKAN_HPP_DEFAULT_DISPATCHER.vkAllocateMemory; + functions.vkFreeMemory = VULKAN_HPP_DEFAULT_DISPATCHER.vkFreeMemory; + functions.vkMapMemory = VULKAN_HPP_DEFAULT_DISPATCHER.vkMapMemory; + functions.vkUnmapMemory = VULKAN_HPP_DEFAULT_DISPATCHER.vkUnmapMemory; + functions.vkFlushMappedMemoryRanges = VULKAN_HPP_DEFAULT_DISPATCHER.vkFlushMappedMemoryRanges; + functions.vkInvalidateMappedMemoryRanges = VULKAN_HPP_DEFAULT_DISPATCHER.vkInvalidateMappedMemoryRanges; + functions.vkBindBufferMemory = VULKAN_HPP_DEFAULT_DISPATCHER.vkBindBufferMemory; + functions.vkBindImageMemory = VULKAN_HPP_DEFAULT_DISPATCHER.vkBindImageMemory; + functions.vkGetBufferMemoryRequirements = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetBufferMemoryRequirements; + functions.vkGetImageMemoryRequirements = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetImageMemoryRequirements; + functions.vkCreateBuffer = VULKAN_HPP_DEFAULT_DISPATCHER.vkCreateBuffer; + functions.vkDestroyBuffer = VULKAN_HPP_DEFAULT_DISPATCHER.vkDestroyBuffer; + functions.vkCreateImage = VULKAN_HPP_DEFAULT_DISPATCHER.vkCreateImage; + functions.vkDestroyImage = VULKAN_HPP_DEFAULT_DISPATCHER.vkDestroyImage; + functions.vkCmdCopyBuffer = VULKAN_HPP_DEFAULT_DISPATCHER.vkCmdCopyBuffer; + functions.vkGetBufferMemoryRequirements2KHR = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetBufferMemoryRequirements2KHR; + functions.vkGetImageMemoryRequirements2KHR = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetImageMemoryRequirements2KHR; + functions.vkBindBufferMemory2KHR = VULKAN_HPP_DEFAULT_DISPATCHER.vkBindBufferMemory2KHR; + functions.vkBindImageMemory2KHR = VULKAN_HPP_DEFAULT_DISPATCHER.vkBindImageMemory2KHR; + functions.vkGetPhysicalDeviceMemoryProperties2KHR = + VULKAN_HPP_DEFAULT_DISPATCHER.vkGetPhysicalDeviceMemoryProperties2KHR; + + VmaAllocatorCreateInfo allocatorCreateInfo = {}; + + allocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_0; + allocatorCreateInfo.physicalDevice = physicalDevice; + allocatorCreateInfo.device = device.get(); + allocatorCreateInfo.instance = instance.get(); + allocatorCreateInfo.pVulkanFunctions = &functions; + + VkResult result = vmaCreateAllocator(&allocatorCreateInfo, &allocator); + if (result != VK_SUCCESS) { + throw std::runtime_error("Vulkan allocator init failed"); + } +} + +void RendererBackend::initDevice() { + const auto& extensions = getDeviceExtensions(); + const auto& layers = getLayers(); + const auto& surface = getDefaultRenderable().getResource().getPlatformSurface().get(); + + const auto& isPhysicalDeviceCompatible = [&](const vk::PhysicalDevice& candidate) -> bool { + bool extensionsAvailable = checkAvailability( + candidate.enumerateDeviceExtensionProperties(), extensions, [](const vk::ExtensionProperties& value) { + return value.extensionName.data(); + }); + + if (!extensionsAvailable) return false; + + graphicsQueueIndex = -1; + presentQueueIndex = -1; + + const auto& queues = candidate.getQueueFamilyProperties(); + + 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; + } + + if (graphicsQueueIndex == -1 || (surface && presentQueueIndex == -1)) return false; + + if (surface) { + if (candidate.getSurfaceFormatsKHR(surface).empty()) return false; + if (candidate.getSurfacePresentModesKHR(surface).empty()) return false; + } + + return true; + }; + + const auto& pickPhysicalDevice = [&]() { + const auto& physicalDevices = instance->enumeratePhysicalDevices(); + if (physicalDevices.empty()) throw std::runtime_error("No Vulkan compatible GPU found"); + + for (const auto& candidate : physicalDevices) { + if (isPhysicalDeviceCompatible(candidate)) { + physicalDevice = candidate; + break; + } + } + + physicalDeviceProperties = physicalDevice.getProperties(); + + if (!physicalDevice) throw std::runtime_error("No suitable GPU found"); + }; + + pickPhysicalDevice(); + + std::vector queueCreateInfos; + + float queuePriority = 1.0f; + + queueCreateInfos.emplace_back(vk::DeviceQueueCreateFlags(), graphicsQueueIndex, 1, &queuePriority); + + if (surface && graphicsQueueIndex != presentQueueIndex) + queueCreateInfos.emplace_back(vk::DeviceQueueCreateFlags(), presentQueueIndex, 1, &queuePriority); + + const auto& supportedDeviceFeatures = physicalDevice.getFeatures(); + + vk::PhysicalDeviceFeatures enabledDeviceFeatures; + + if (supportedDeviceFeatures.wideLines) { + enabledDeviceFeatures.setWideLines(true); + + // more wideLines info + // physicalDeviceProperties.limits.lineWidthRange; + // physicalDeviceProperties.limits.lineWidthGranularity; + } else { + mbgl::Log::Error(mbgl::Event::Render, "Wide line support not available"); + } + + auto createInfo = vk::DeviceCreateInfo() + .setQueueCreateInfos(queueCreateInfos) + .setEnabledExtensionCount(static_cast(extensions.size())) + .setPpEnabledExtensionNames(extensions.data()) + .setPEnabledFeatures(&enabledDeviceFeatures); + + // this is not needed for newer implementations + createInfo.setPEnabledLayerNames(layers); + + device = physicalDevice.createDeviceUnique(createInfo); + + // optional function pointer specialization for device + VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); + + graphicsQueue = device->getQueue(graphicsQueueIndex, 0); + if (presentQueueIndex != -1) presentQueue = device->getQueue(presentQueueIndex, 0); +} + +void RendererBackend::initSwapchain() { + const auto& renderable = getDefaultRenderable(); + auto& renderableResource = renderable.getResource(); + const auto& size = renderable.getSize(); + renderableResource.init(size.width, size.height); + + if (renderableResource.getPlatformSurface()) { + maxFrames = renderableResource.getImageCount(); + } +} + +void RendererBackend::initCommandPool() { + const vk::CommandPoolCreateInfo createInfo(vk::CommandPoolCreateFlagBits::eResetCommandBuffer, graphicsQueueIndex); + commandPool = device->createCommandPoolUnique(createInfo); +} + +void RendererBackend::destroyResources() { + if (device) device->waitIdle(); + + context.reset(); + commandPool.reset(); + + vmaDestroyAllocator(allocator); + + device.reset(); + + // destroy this last so we have cleanup validation + debugUtilsCallback.reset(); + debugReportCallback.reset(); + instance.reset(); +} + +/// @brief Register a list of types with a shader registry instance +/// @tparam ...ShaderID Pack of BuiltIn:: shader IDs +/// @param registry A shader registry instance +/// @param programParameters ProgramParameters used to initialize each instance +template +void registerTypes(gfx::ShaderRegistry& registry, const ProgramParameters& programParameters) { + /// The following fold expression will create a shader for every type + /// in the parameter pack and register it with the shader registry. + + // Registration calls are wrapped in a lambda that throws on registration + // failure, we shouldn't expect registration to faill unless the shader + // registry instance provided already has conflicting programs present. + ( + [&]() { + using namespace std::string_literals; + using ShaderClass = shaders::ShaderSource; + auto group = std::make_shared>(programParameters); + if (!registry.registerShaderGroup(std::move(group), ShaderClass::name)) { + assert(!"duplicate shader group"); + throw std::runtime_error("Failed to register "s + ShaderClass::name + " with shader registry!"); + } + }(), + ...); +} + +void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramParameters& programParameters) { + registerTypes(shaders, programParameters); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/texture2d.cpp b/src/mbgl/vulkan/texture2d.cpp new file mode 100644 index 000000000..8b9c20757 --- /dev/null +++ b/src/mbgl/vulkan/texture2d.cpp @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +bool ImageAllocation::create(const VmaAllocationCreateInfo& allocInfo, const vk::ImageCreateInfo& imageInfo) { + VkResult result = vmaCreateImage( + allocator, reinterpret_cast(&imageInfo), &allocInfo, &image, &allocation, nullptr); + + if (result != VK_SUCCESS) { + return false; + } + + return true; +} + +void ImageAllocation::destroy() { + imageView.reset(); + vmaDestroyImage(allocator, image, allocation); +} + +void ImageAllocation::setName([[maybe_unused]] const std::string& name) const { +#ifdef ENABLE_VMA_DEBUG + if (allocation) { + vmaSetAllocationName(allocator, allocation, name.data()); + } +#endif +} + +Texture2D::Texture2D(Context& context_) + : context(context_) {} + +Texture2D::~Texture2D() { + destroyTexture(); + destroySampler(); + + context.renderingStats().numActiveTextures--; + context.renderingStats().memTextures -= Texture2D::getDataSize(); +} + +gfx::Texture2D& Texture2D::setSamplerConfiguration(const SamplerState& samplerState_) noexcept { + samplerState = samplerState_; + samplerStateDirty = true; + return *this; +} + +gfx::Texture2D& Texture2D::setFormat(gfx::TexturePixelType pixelFormat_, + gfx::TextureChannelDataType channelType_) noexcept { + if (pixelFormat_ == pixelFormat && channelType_ == channelType) { + return *this; + } + pixelFormat = pixelFormat_; + channelType = channelType_; + textureDirty = true; + return *this; +} + +gfx::Texture2D& Texture2D::setSize(mbgl::Size size_) noexcept { + if (size_ == size) { + return *this; + } + size = size_; + textureDirty = true; + return *this; +} + +gfx::Texture2D& Texture2D::setImage(std::shared_ptr image_) noexcept { + imageData = std::move(image_); + return *this; +} + +gfx::Texture2D& Texture2D::setUsage(Texture2DUsage value) noexcept { + textureUsage = value; + textureDirty = true; + return *this; +} + +size_t Texture2D::getDataSize() const noexcept { + return Texture2D::getPixelStride() * size.width * size.height; +} + +size_t Texture2D::getPixelStride() const noexcept { + switch (channelType) { + case gfx::TextureChannelDataType::UnsignedByte: + return 1 * Texture2D::numChannels(); + case gfx::TextureChannelDataType::HalfFloat: + return 2 * Texture2D::numChannels(); + case gfx::TextureChannelDataType::Float: + return 4 * Texture2D::numChannels(); + default: + return 0; + } +} + +size_t Texture2D::numChannels() const noexcept { + switch (pixelFormat) { + case gfx::TexturePixelType::RGBA: + return 4; + case gfx::TexturePixelType::Alpha: + return 1; + case gfx::TexturePixelType::Stencil: + return 1; + case gfx::TexturePixelType::Depth: + return 1; + default: + return 0; + } +} + +void Texture2D::create() noexcept { + if (textureDirty) { + createTexture(); + } +} + +void Texture2D::upload() noexcept { + if (!imageData) return; + + upload(imageData->data.get(), imageData->size); + + imageData.reset(); +} + +void Texture2D::upload(const void* pixelData, const Size& size_) noexcept { + setSize(size_); + uploadSubRegion(pixelData, size_, 0, 0); +} + +void Texture2D::uploadSubRegion(const void* pixelData, const Size& size_, uint16_t xOffset, uint16_t yOffset) noexcept { + if (!pixelData || size_.width == 0 || size_.height == 0) return; + + const auto& encoder = context.createCommandEncoder(); + const auto& encoderImpl = static_cast(*encoder); + + uploadSubRegion(pixelData, size_, xOffset, yOffset, encoderImpl.getCommandBuffer()); +} + +void Texture2D::uploadSubRegion(const void* pixelData, + const Size& size_, + uint16_t xOffset, + uint16_t yOffset, + const vk::UniqueCommandBuffer& commandBuffer) noexcept { + if (!pixelData || size_.width == 0 || size_.height == 0) return; + + create(); + + if (!imageAllocation) return; + + const auto& allocator = context.getBackend().getAllocator(); + + const auto bufferInfo = vk::BufferCreateInfo() + .setSize(static_cast(size_.width) * size_.height * getPixelStride()) + .setUsage(vk::BufferUsageFlagBits::eTransferSrc) + .setSharingMode(vk::SharingMode::eExclusive); + + VmaAllocationCreateInfo allocationInfo = {}; + + allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + allocationInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + + SharedBufferAllocation bufferAllocation = std::make_shared(allocator); + if (!bufferAllocation->create(allocationInfo, bufferInfo)) { + mbgl::Log::Error(mbgl::Event::Render, "Vulkan texture buffer allocation failed"); + return; + } + + vmaMapMemory(allocator, bufferAllocation->allocation, &bufferAllocation->mappedBuffer); + memcpy(bufferAllocation->mappedBuffer, pixelData, bufferInfo.size); + + const auto enqueueCommands = [&](const auto& buffer) { + transitionToTransferLayout(buffer); + + const auto region = vk::BufferImageCopy() + .setBufferOffset(0) + .setBufferRowLength(size_.width) + .setImageSubresource( + vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1)) + .setImageOffset(vk::Offset3D(xOffset, yOffset)) + .setImageExtent(vk::Extent3D(size_.width, size_.height, 1)); + + buffer->copyBufferToImage(bufferAllocation->buffer, imageAllocation->image, imageLayout, region); + + transitionToShaderReadLayout(buffer); + }; + + enqueueCommands(commandBuffer); + + context.enqueueDeletion([buffAlloc = std::move(bufferAllocation)](const auto&) mutable { buffAlloc.reset(); }); + + context.renderingStats().numTextureUpdates++; +} + +vk::Format Texture2D::vulkanFormat(const gfx::TexturePixelType pixel, gfx::TextureChannelDataType channel) { + // depth/stencil are packed formats + if (pixel == gfx::TexturePixelType::Stencil) return vk::Format::eS8Uint; + + if (pixel == gfx::TexturePixelType::Depth) return vk::Format::eUndefined; + + if (pixel == gfx::TexturePixelType::Alpha) { + switch (channel) { + case gfx::TextureChannelDataType::UnsignedByte: + return vk::Format::eR8Unorm; + case gfx::TextureChannelDataType::HalfFloat: + return vk::Format::eR16Sfloat; + case gfx::TextureChannelDataType::Float: + return vk::Format::eR32Sfloat; + } + } + + if (pixel == gfx::TexturePixelType::RGBA) { + switch (channel) { + case gfx::TextureChannelDataType::UnsignedByte: + return vk::Format::eR8G8B8A8Unorm; + case gfx::TextureChannelDataType::HalfFloat: + return vk::Format::eR16G16B16A16Sfloat; + case gfx::TextureChannelDataType::Float: + return vk::Format::eR32G32B32A32Sfloat; + } + } + + return vk::Format::eUndefined; +} + +vk::Filter Texture2D::vulkanFilter(const gfx::TextureFilterType type) { + switch (type) { + default: + [[fallthrough]]; + case gfx::TextureFilterType::Nearest: + return vk::Filter::eNearest; + case gfx::TextureFilterType::Linear: + return vk::Filter::eLinear; + } +} + +vk::SamplerAddressMode Texture2D::vulkanAddressMode(const gfx::TextureWrapType type) { + switch (type) { + default: + [[fallthrough]]; + case gfx::TextureWrapType::Clamp: + return vk::SamplerAddressMode::eClampToEdge; + case gfx::TextureWrapType::Repeat: + return vk::SamplerAddressMode::eRepeat; + } +} + +void Texture2D::createTexture() { + if (size.width == 0 || size.height == 0) return; + + const auto& backend = context.getBackend(); + + const auto format = vulkanFormat(pixelFormat, channelType); + + vk::ImageUsageFlags imageUsage; + vk::ImageTiling imageTiling; + + switch (textureUsage) { + default: + case Texture2DUsage::ShaderInput: + imageUsage = vk::ImageUsageFlags() | vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eSampled; + imageTiling = vk::ImageTiling::eOptimal; + break; + + case Texture2DUsage::Attachment: + imageUsage = vk::ImageUsageFlags() | vk::ImageUsageFlagBits::eColorAttachment | + vk::ImageUsageFlagBits::eSampled; + imageTiling = vk::ImageTiling::eOptimal; + break; + + case Texture2DUsage::Read: + imageUsage = vk::ImageUsageFlags() | vk::ImageUsageFlagBits::eTransferDst; + imageTiling = vk::ImageTiling::eLinear; + break; + } + + const auto imageCreateInfo = vk::ImageCreateInfo() + .setImageType(vk::ImageType::e2D) + .setFormat(format) + .setExtent({size.width, size.height, 1}) + .setMipLevels(1) + .setArrayLayers(1) + .setSamples(vk::SampleCountFlagBits::e1) + .setTiling(imageTiling) + .setUsage(imageUsage) + .setSharingMode(vk::SharingMode::eExclusive) + .setInitialLayout(vk::ImageLayout::eUndefined); + + VmaAllocationCreateInfo allocCreateInfo = {}; + + if (textureUsage != Texture2DUsage::Read) { + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } else { + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + allocCreateInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + } + + imageAllocation = std::make_shared(backend.getAllocator()); + if (!imageAllocation->create(allocCreateInfo, imageCreateInfo)) { + mbgl::Log::Error(mbgl::Event::Render, "Vulkan texture allocation failed"); + return; + } + + // defaults to eIdentity + vk::ComponentMapping imageSwizzle; + + // alpha only format (eA8UnormKHR) is not part of core + // use a R8 texture and map red channel to alpha + if (pixelFormat == gfx::TexturePixelType::Alpha) { + imageSwizzle = vk::ComponentMapping(vk::ComponentSwizzle::eZero, + vk::ComponentSwizzle::eZero, + vk::ComponentSwizzle::eZero, + vk::ComponentSwizzle::eR); + } + + if (textureUsage != Texture2DUsage::Read) { + auto imageViewCreateInfo = vk::ImageViewCreateInfo() + .setImage(imageAllocation->image) + .setViewType(vk::ImageViewType::e2D) + .setFormat(format) + .setComponents(imageSwizzle) + .setSubresourceRange( + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); + + imageAllocation->imageView = backend.getDevice()->createImageViewUnique(imageViewCreateInfo); + } + + // if the image is used as an attachment + // it's layout is managed by the render pass + if (textureUsage == Texture2DUsage::Attachment) { + imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + } else { + imageLayout = imageCreateInfo.initialLayout; + } + + context.renderingStats().numCreatedTextures++; + context.renderingStats().numActiveTextures++; + context.renderingStats().memTextures += getDataSize(); + textureDirty = false; +} + +void Texture2D::createSampler() { + destroySampler(); + + const auto filter = vulkanFilter(samplerState.filter); + const auto addressModeU = vulkanAddressMode(samplerState.wrapU); + const auto addressModeV = vulkanAddressMode(samplerState.wrapV); + + const auto samplerCreateInfo = vk::SamplerCreateInfo() + .setMinFilter(filter) + .setMagFilter(filter) + .setMipmapMode(vk::SamplerMipmapMode::eNearest) + .setMinLod(-VK_LOD_CLAMP_NONE) + .setMaxLod(VK_LOD_CLAMP_NONE) + .setAddressModeU(addressModeU) + .setAddressModeV(addressModeV) + .setAddressModeW(vk::SamplerAddressMode::eRepeat) + .setAnisotropyEnable(false); + + sampler = context.getBackend().getDevice()->createSampler(samplerCreateInfo); + + samplerStateDirty = false; +} + +void Texture2D::destroyTexture() { + if (imageAllocation) { + context.enqueueDeletion([allocation = std::move(imageAllocation)](const auto&) mutable { allocation.reset(); }); + + imageLayout = vk::ImageLayout::eUndefined; + } +} + +void Texture2D::destroySampler() { + if (sampler) { + context.enqueueDeletion([sampler_ = std::move(sampler)](const auto& context_) mutable { + context_.getBackend().getDevice()->destroySampler(sampler_); + }); + + sampler = nullptr; + } +} + +void Texture2D::transitionToTransferLayout(const vk::UniqueCommandBuffer& buffer) { + const auto barrier = vk::ImageMemoryBarrier() + .setImage(imageAllocation->image) + .setOldLayout(imageLayout) + .setNewLayout(vk::ImageLayout::eTransferDstOptimal) + .setSrcAccessMask({}) + .setDstAccessMask(vk::AccessFlagBits::eTransferWrite) + .setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .setSubresourceRange( + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); + + buffer->pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, nullptr, barrier); + + imageLayout = barrier.newLayout; +} + +void Texture2D::transitionToShaderReadLayout(const vk::UniqueCommandBuffer& buffer) { + const auto barrier = vk::ImageMemoryBarrier() + .setImage(imageAllocation->image) + .setOldLayout(imageLayout) + .setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal) + .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite) + .setDstAccessMask(vk::AccessFlagBits::eShaderRead) + .setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .setSubresourceRange( + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); + + buffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, + {}, + nullptr, + nullptr, + barrier); + + imageLayout = barrier.newLayout; +} + +void Texture2D::transitionToGeneralLayout(const vk::UniqueCommandBuffer& buffer) { + const auto barrier = vk::ImageMemoryBarrier() + .setImage(imageAllocation->image) + .setOldLayout(imageLayout) + .setNewLayout(vk::ImageLayout::eGeneral) + .setSrcAccessMask(vk::AccessFlagBits::eTransferWrite) + .setDstAccessMask(vk::AccessFlagBits::eMemoryRead) + .setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .setSubresourceRange( + vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1)); + + buffer->pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, nullptr, barrier); + + imageLayout = barrier.newLayout; +} + +const vk::Sampler& Texture2D::getVulkanSampler() { + if (samplerStateDirty) { + createSampler(); + } + + return sampler; +} + +void Texture2D::copyImage(vk::Image image) { + if (!image) return; + + create(); + + context.submitOneTimeCommand([&](const vk::UniqueCommandBuffer& commandBuffer) { + const auto copyInfo = vk::ImageCopy() + .setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1}) + .setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1}) + .setExtent({size.width, size.height, 1}); + + transitionToTransferLayout(commandBuffer); + commandBuffer->copyImage( + image, vk::ImageLayout::eTransferSrcOptimal, imageAllocation->image, imageLayout, copyInfo); + transitionToGeneralLayout(commandBuffer); + }); +} + +std::shared_ptr Texture2D::readImage() { + if (!imageData) { + imageData = std::make_shared(); + } + + // check for offset/padding + const auto& device = context.getBackend().getDevice(); + const auto& layout = device->getImageSubresourceLayout(imageAllocation->image, + vk::ImageSubresource(vk::ImageAspectFlagBits::eColor, 0, 0)); + + imageData->resize(size); + const auto& imageSize = getDataSize(); + + void* mappedData_ = nullptr; + vmaMapMemory(context.getBackend().getAllocator(), imageAllocation->allocation, &mappedData_); + + uint8_t* mappedData = reinterpret_cast(mappedData_) + layout.offset; + + if (imageSize == layout.arrayPitch) { + memcpy(imageData->data.get(), mappedData, imageSize); + } else { + uint32_t rowSize = size.width * getPixelStride(); + for (uint32_t i = 0; i < size.height; ++i) { + memcpy(imageData->data.get() + rowSize * i, mappedData + layout.rowPitch * i, rowSize); + } + } + + vmaUnmapMemory(context.getBackend().getAllocator(), imageAllocation->allocation); + + return imageData; +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/tile_layer_group.cpp b/src/mbgl/vulkan/tile_layer_group.cpp new file mode 100644 index 000000000..766c42cb4 --- /dev/null +++ b/src/mbgl/vulkan/tile_layer_group.cpp @@ -0,0 +1,135 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace vulkan { + +TileLayerGroup::TileLayerGroup(int32_t layerIndex_, std::size_t initialCapacity, std::string name_) + : mbgl::TileLayerGroup(layerIndex_, initialCapacity, std::move(name_)) {} + +void TileLayerGroup::upload(gfx::UploadPass& uploadPass) { + if (!enabled || !getDrawableCount()) { + return; + } + +#if !defined(NDEBUG) + const auto debugGroup = uploadPass.createDebugGroup(getName() + "-upload"); +#endif + + visitDrawables([&](gfx::Drawable& drawable_) { + if (drawable_.getEnabled()) { + auto& drawable = static_cast(drawable_); + drawable.upload(uploadPass); + } + }); +} + +void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { + if (!enabled || !getDrawableCount() || !parameters.renderPass) { + return; + } + + auto& renderPass = static_cast(*parameters.renderPass); + + // `stencilModeFor3D` uses a different stencil mask value each time its called, so if the + // drawables in this layer use 3D stencil mode, we need to set it up here so that all the + // drawables end up using the same mode value. + // 2D and 3D features in the same layer group is not supported. + bool features3d = false; + bool stencil3d = false; + std::optional depthMode3d; + std::optional stencilMode3d; + + // If we're using stencil clipping, we need to handle 3D features separately + if (stencilTiles && !stencilTiles->empty()) { + // 2D and 3D features in the same layer group is not supported. + visitDrawables([&](const gfx::Drawable& drawable) { + if (drawable.getEnabled() && drawable.getIs3D() && drawable.hasRenderPass(parameters.pass)) { + features3d = true; + if (drawable.getEnableStencil()) { + stencil3d = true; + } + } + }); + } + +#if !defined(NDEBUG) + const auto debugGroupRender = parameters.encoder->createDebugGroup(getName() + "-render"); +#endif + + // If we're doing 3D stenciling and have any features to draw, set up the single-value stencil mask. + // If we're doing 2D stenciling and have any drawables with tile IDs, render each tile into the stencil buffer with + // a different value. + if (features3d) { + depthMode3d = parameters.depthModeFor3D(); + + if (stencil3d) { + stencilMode3d = parameters.stencilModeFor3D(); + } + } else if (stencilTiles && !stencilTiles->empty()) { + parameters.renderTileClippingMasks(stencilTiles); + } + + bool bindUBOs = false; + visitDrawables([&](gfx::Drawable& drawable) { + if (!drawable.getEnabled() || !drawable.hasRenderPass(parameters.pass)) { + return; + } + + for (const auto& tweaker : drawable.getTweakers()) { + tweaker->execute(drawable, parameters); + } + + if (!bindUBOs) { + bindUniformBuffers(renderPass); + bindUBOs = true; + } + + auto& drawableImpl = static_cast(drawable); + auto& drawableUniforms = drawable.mutableUniformBuffers(); + for (size_t id = 0; id < uniformBuffers.allocatedSize(); id++) { + const auto& uniformBuffer = uniformBuffers.get(id); + if (!uniformBuffer) continue; + + if (features3d) { + drawableImpl.setDepthModeFor3D(drawableImpl.getEnableDepth() ? depthMode3d.value() + : gfx::DepthMode::disabled()); + + drawableImpl.setStencilModeFor3D(drawableImpl.getEnableStencil() ? stencilMode3d.value() + : gfx::StencilMode::disabled()); + } + + drawableUniforms.set(id, uniformBuffer); + } + + drawable.draw(parameters); + }); + + if (bindUBOs) { + unbindUniformBuffers(renderPass); + } +} + +void TileLayerGroup::bindUniformBuffers(RenderPass& renderPass) const noexcept { + for (size_t id = 0; id < uniformBuffers.allocatedSize(); id++) { + const auto& uniformBuffer = uniformBuffers.get(id); + if (!uniformBuffer) continue; + const auto& buffer = static_cast(*uniformBuffer); + const auto& resource = buffer.getBufferResource(); + renderPass.bindVertex(resource, 0, id); + renderPass.bindFragment(resource, 0, id); + } +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/uniform_buffer.cpp b/src/mbgl/vulkan/uniform_buffer.cpp new file mode 100644 index 000000000..40ba00878 --- /dev/null +++ b/src/mbgl/vulkan/uniform_buffer.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +#include + +namespace mbgl { +namespace vulkan { + +UniformBuffer::UniformBuffer(BufferResource&& buffer_) + : gfx::UniformBuffer(buffer_.getSizeInBytes()), + buffer(std::move(buffer_)) { + buffer.getContext().renderingStats().numUniformBuffers++; + buffer.getContext().renderingStats().memUniformBuffers += size; +} + +UniformBuffer::UniformBuffer(UniformBuffer&& other) + : gfx::UniformBuffer(std::move(other)), + buffer(std::move(other.buffer)) {} + +UniformBuffer::~UniformBuffer() { + buffer.getContext().renderingStats().numUniformBuffers--; + buffer.getContext().renderingStats().memUniformBuffers -= size; +} + +void UniformBuffer::update(const void* data, std::size_t size_) { + assert(size == size_); + if (size != size_ || size != buffer.getSizeInBytes()) { + Log::Error( + Event::General, + "Mismatched size given to UBO update, expected " + std::to_string(size) + ", got " + std::to_string(size_)); + return; + } + + buffer.getContext().renderingStats().numUniformUpdates++; + buffer.getContext().renderingStats().uniformUpdateBytes += size_; + buffer.update(data, size, /*offset=*/0); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/upload_pass.cpp b/src/mbgl/vulkan/upload_pass.cpp new file mode 100644 index 000000000..e41a2f1d3 --- /dev/null +++ b/src/mbgl/vulkan/upload_pass.cpp @@ -0,0 +1,201 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace vulkan { + +UploadPass::UploadPass(gfx::Renderable&, CommandEncoder& commandEncoder_, const char* name) + : commandEncoder(commandEncoder_) { + // Push the group for the name provided + debugGroups.emplace_back(gfx::DebugGroup{*this, name}); +} + +UploadPass::~UploadPass() { + endEncoding(); +} + +void UploadPass::endEncoding() { + debugGroups.clear(); +} + +std::unique_ptr UploadPass::createVertexBufferResource(const void* data, + const std::size_t size, + const gfx::BufferUsageType, + bool persistent) { + return std::make_unique( + commandEncoder.context.createBuffer(data, size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, persistent)); +} + +void UploadPass::updateVertexBufferResource(gfx::VertexBufferResource& resource, const void* data, std::size_t size) { + static_cast(resource).get().update(data, size, /*offset=*/0); +} + +std::unique_ptr UploadPass::createIndexBufferResource(const void* data, + const std::size_t size, + const gfx::BufferUsageType, + bool persistent) { + return std::make_unique( + commandEncoder.context.createBuffer(data, size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, persistent)); +} + +void UploadPass::updateIndexBufferResource(gfx::IndexBufferResource& resource, const void* data, std::size_t size) { + static_cast(resource).get().update(data, size, /*offset=*/0); +} + +std::unique_ptr UploadPass::createTextureResource(const Size, + const void*, + gfx::TexturePixelType, + gfx::TextureChannelDataType) { + assert(false); + throw std::runtime_error("UploadPass::createTextureResource not implemented on Vulkan!"); +} + +void UploadPass::updateTextureResource( + gfx::TextureResource&, const Size, const void*, gfx::TexturePixelType, gfx::TextureChannelDataType) { + assert(false); + throw std::runtime_error("UploadPass::updateTextureResource not implemented on Vulkan!"); +} + +void UploadPass::updateTextureResourceSub(gfx::TextureResource&, + const uint16_t, + const uint16_t, + const Size, + const void*, + gfx::TexturePixelType, + gfx::TextureChannelDataType) { + assert(false); + throw std::runtime_error("UploadPass::updateTextureResourceSub not implemented on Vulkan!"); +} + +struct VertexBuffer : public gfx::VertexBufferBase { + ~VertexBuffer() override = default; + + std::unique_ptr resource; +}; + +static const std::unique_ptr noBuffer; + +const gfx::UniqueVertexBufferResource& UploadPass::getBuffer(const gfx::VertexVectorBasePtr& vec, + const gfx::BufferUsageType usage) { + if (vec) { + const auto* rawBufPtr = vec->getRawData(); + const auto rawBufSize = vec->getRawCount() * vec->getRawSize(); + + // If we already have a buffer... + if (auto* rawData = static_cast(vec->getBuffer()); rawData && rawData->resource) { + auto& resource = static_cast(*rawData->resource); + + // If it's changed, update it + if (rawBufSize <= resource.getSizeInBytes()) { + if (vec->getDirty()) { + updateVertexBufferResource(resource, rawBufPtr, rawBufSize); + vec->setDirty(false); + } + return rawData->resource; + } + } + // Otherwise, create a new one + if (rawBufSize > 0) { + auto buffer = std::make_unique(); + buffer->resource = createVertexBufferResource(rawBufPtr, rawBufSize, usage, /*persistent=*/false); + vec->setBuffer(std::move(buffer)); + vec->setDirty(false); + return static_cast(vec->getBuffer())->resource; + } + } + return noBuffer; +} + +gfx::AttributeBindingArray UploadPass::buildAttributeBindings( + const std::size_t, + const gfx::AttributeDataType, + const std::size_t, + const std::vector&, + const gfx::VertexAttributeArray& defaults, + const gfx::VertexAttributeArray& overrides, + const gfx::BufferUsageType usage, + /*out*/ std::vector>&) { + gfx::AttributeBindingArray bindings; + + // For each attribute in the program, with the corresponding default and optional override... + const auto resolveAttr = [&](const size_t, auto& default_, auto& override_) -> void { + auto& effectiveAttr = override_ ? *override_ : default_; + const auto& defaultAttr = static_cast(default_); + const auto index = static_cast(defaultAttr.getIndex()); + + bindings.resize(std::max(bindings.size(), index + 1)); + + if (!override_) { + // No attribute was provided, this value will be used from a uniform, but we have to set + // up a valid binding or the validation will complain when the shader code references the + // attribute at compile time, regardless of whether it's ever used at runtime. + bindings[index] = { + /*.attribute = */ {defaultAttr.getDataType(), /*offset=*/0}, + /*.vertexStride = */ static_cast(VertexAttribute::getStrideOf(defaultAttr.getDataType())), + /*.vertexBufferResource = */ nullptr, + /*.vertexOffset = */ 0, + }; + return; + } + + // If the attribute references data shared with a bucket, get the corresponding buffer. + if (const auto& buffer = getBuffer(effectiveAttr.getSharedRawData(), usage)) { + assert(effectiveAttr.getSharedStride() * effectiveAttr.getSharedVertexOffset() < + effectiveAttr.getSharedRawData()->getRawSize() * effectiveAttr.getSharedRawData()->getRawCount()); + + bindings[index] = { + /*.attribute = */ {effectiveAttr.getSharedType(), effectiveAttr.getSharedOffset()}, + /*.vertexStride = */ effectiveAttr.getSharedStride(), + /*.vertexBufferResource = */ buffer.get(), + /*.vertexOffset = */ effectiveAttr.getSharedVertexOffset(), + }; + return; + } + + assert(effectiveAttr.getStride() > 0); + + // Otherwise, turn the data managed by the attribute into a buffer. + if (const auto& buffer = VertexAttribute::getBuffer(effectiveAttr, *this, gfx::BufferUsageType::StaticDraw)) { + bindings[index] = { + /*.attribute = */ {effectiveAttr.getDataType(), /*offset=*/0}, + /*.vertexStride = */ static_cast(effectiveAttr.getStride()), + /*.vertexBufferResource = */ buffer.get(), + /*.vertexOffset = */ 0, + }; + return; + } + + assert(false); + }; + + defaults.resolve(overrides, resolveAttr); + + return bindings; +} + +void UploadPass::pushDebugGroup(const char* name) { + commandEncoder.pushDebugGroup(name); +} + +void UploadPass::popDebugGroup() { + commandEncoder.popDebugGroup(); +} + +gfx::Context& UploadPass::getContext() { + return commandEncoder.context; +} + +const gfx::Context& UploadPass::getContext() const { + return commandEncoder.context; +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/vertex_attribute.cpp b/src/mbgl/vulkan/vertex_attribute.cpp new file mode 100644 index 000000000..bba5f69c4 --- /dev/null +++ b/src/mbgl/vulkan/vertex_attribute.cpp @@ -0,0 +1,50 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace vulkan { + +const gfx::UniqueVertexBufferResource& VertexAttribute::getBuffer(gfx::VertexAttribute& attrib_, + UploadPass& uploadPass, + const gfx::BufferUsageType usage) { + if (!attrib_.getBuffer()) { + auto& attrib = static_cast(attrib_); + if (attrib.sharedRawData) { + return uploadPass.getBuffer(attrib.sharedRawData, usage); + } else { + if (!attrib.rawData.empty()) { + auto buffer = uploadPass.createVertexBufferResource( + attrib.rawData.data(), attrib.rawData.size(), usage, false); + attrib.setBuffer(std::move(buffer)); + attrib.setRawData({}); + } else { + assert(false); + } + } + } + return attrib_.getBuffer(); +} + +bool VertexAttributeArray::isDirty() const { + return std::any_of(attrs.begin(), attrs.end(), [](const auto& attr) { + if (attr) { + // If we have shared data, the dirty flag from that overrides ours + const auto& attrib = static_cast(*attr); + if (const auto& shared = attrib.getSharedRawData()) { + return shared->getDirty(); + } + } + return attr && attr->isDirty(); + }); +} + +} // namespace vulkan +} // namespace mbgl diff --git a/src/mbgl/vulkan/vertex_buffer_resource.cpp b/src/mbgl/vulkan/vertex_buffer_resource.cpp new file mode 100644 index 000000000..d8fe72391 --- /dev/null +++ b/src/mbgl/vulkan/vertex_buffer_resource.cpp @@ -0,0 +1,26 @@ +#include + +#include + +namespace mbgl { +namespace vulkan { + +VertexBufferResource::VertexBufferResource(BufferResource&& ptr) noexcept + : buffer(std::move(ptr)) { + if (buffer) { + auto& stats = buffer.getContext().renderingStats(); + stats.numVertexBuffers++; + stats.memVertexBuffers += buffer.getSizeInBytes(); + } +} + +VertexBufferResource::~VertexBufferResource() noexcept { + if (buffer) { + auto& stats = buffer.getContext().renderingStats(); + stats.numVertexBuffers--; + stats.memVertexBuffers -= buffer.getSizeInBytes(); + } +} + +} // namespace vulkan +} // namespace mbgl diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1adf20efa..94d11b670 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -144,6 +144,22 @@ if(MLN_WITH_OPENGL) ) endif() + +if(MLN_WITH_VULKAN) + target_sources( + mbgl-test + PRIVATE + ${PROJECT_SOURCE_DIR}/test/api/custom_layer.test.cpp + ${PROJECT_SOURCE_DIR}/test/api/custom_drawable_layer.test.cpp + ${PROJECT_SOURCE_DIR}/test/renderer/backend_scope.test.cpp + ${PROJECT_SOURCE_DIR}/test/util/offscreen_texture.test.cpp + ) + target_compile_definitions( + mbgl-test + PRIVATE MLN_RENDER_BACKEND_VULKAN=1 + ) +endif() + if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL Android) message("Target platform does not support HTTP tests or dependencies not found.") diff --git a/vendor/Vulkan-Headers b/vendor/Vulkan-Headers new file mode 160000 index 000000000..d192041a2 --- /dev/null +++ b/vendor/Vulkan-Headers @@ -0,0 +1 @@ +Subproject commit d192041a2fc9c9fd8ae67d8ae3f32c5511541f04 diff --git a/vendor/VulkanMemoryAllocator b/vendor/VulkanMemoryAllocator new file mode 160000 index 000000000..7942b7982 --- /dev/null +++ b/vendor/VulkanMemoryAllocator @@ -0,0 +1 @@ +Subproject commit 7942b798289f752dc23b0a79516fd8545febd718 diff --git a/vendor/glslang b/vendor/glslang new file mode 160000 index 000000000..a92c61f84 --- /dev/null +++ b/vendor/glslang @@ -0,0 +1 @@ +Subproject commit a92c61f8456fa9731c0b000a2c6fc52a740c2be7 diff --git a/vendor/vulkan.cmake b/vendor/vulkan.cmake new file mode 100644 index 000000000..59859b5f8 --- /dev/null +++ b/vendor/vulkan.cmake @@ -0,0 +1,78 @@ +add_library(mbgl-vendor-vulkan-headers INTERFACE) + +target_include_directories( + mbgl-vendor-vulkan-headers SYSTEM + INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/Vulkan-Headers/include/ +) + +set_target_properties( + mbgl-vendor-vulkan-headers + PROPERTIES + INTERFACE_MAPBOX_NAME "Vulkan-Headers" + INTERFACE_MAPBOX_URL "https://github.com/KhronosGroup/Vulkan-Headers.git" + INTERFACE_MAPBOX_LICENSE ${CMAKE_CURRENT_LIST_DIR}/Vulkan-Headers/LICENSE.md +) + +add_library(mbgl-vendor-VulkanMemoryAllocator INTERFACE) + +target_include_directories( + mbgl-vendor-VulkanMemoryAllocator SYSTEM + INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/VulkanMemoryAllocator/include/ +) + +set_target_properties( + mbgl-vendor-VulkanMemoryAllocator + PROPERTIES + INTERFACE_MAPBOX_NAME "VulkanMemoryAllocator" + INTERFACE_MAPBOX_URL "https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git" + INTERFACE_MAPBOX_LICENSE ${CMAKE_CURRENT_LIST_DIR}/VulkanMemoryAllocator/LICENSE.txt +) + +set(ENABLE_OPT OFF) +set(ENABLE_HLSL OFF) +set(ENABLE_GLSLANG_BINARIES OFF) +set(GLSLANG_TESTS_DEFAULT OFF) +set(ENABLE_PCH OFF) +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/glslang SYSTEM) + +get_target_property(glslang_inc glslang INTERFACE_INCLUDE_DIRECTORIES) +set_target_properties(glslang PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${glslang_inc}) + +set_target_properties( + glslang + PROPERTIES + INTERFACE_MAPBOX_NAME "glslang" + INTERFACE_MAPBOX_URL "https://github.com/KhronosGroup/glslang.git" + INTERFACE_MAPBOX_LICENSE ${CMAKE_CURRENT_LIST_DIR}/glslang/LICENSE.txt +) + +set_target_properties( + SPIRV + PROPERTIES + INTERFACE_MAPBOX_NAME "SPIRV" + INTERFACE_MAPBOX_URL "https://github.com/KhronosGroup/glslang.git" + INTERFACE_MAPBOX_LICENSE ${CMAKE_CURRENT_LIST_DIR}/glslang/LICENSE.txt +) + +set_target_properties( + glslang-default-resource-limits + PROPERTIES + INTERFACE_MAPBOX_NAME "glslang-default-resource-limits" + INTERFACE_MAPBOX_URL "https://github.com/KhronosGroup/glslang.git" + INTERFACE_MAPBOX_LICENSE ${CMAKE_CURRENT_LIST_DIR}/glslang/LICENSE.txt +) + +export(TARGETS + mbgl-vendor-vulkan-headers + mbgl-vendor-VulkanMemoryAllocator + glslang + SPIRV + glslang-default-resource-limits + MachineIndependent + OSDependent + GenericCodeGen + + APPEND FILE MapboxCoreTargets.cmake +) \ No newline at end of file