Add WebGPU Backend w/ Dawn Support (#3838)

This commit is contained in:
Birk Skyum
2025-10-16 20:35:33 +02:00
committed by GitHub
parent cd129e873a
commit 68459732be
119 changed files with 14067 additions and 28 deletions

7
.github/labeler.yml vendored
View File

@@ -56,3 +56,10 @@ metal:
- any-glob-to-any-file: - any-glob-to-any-file:
- '**/*metal*' - '**/*metal*'
- '**/*metal*/**' - '**/*metal*/**'
WebGPU:
- changed-files:
- any-glob-to-any-file:
- '**/*webgpu*'
- '**/*webgpu*/**'
- '**/*wgpu*'
- '**/*dawn*'

View File

@@ -12,4 +12,8 @@ DEBIAN_FRONTEND=noninteractive sudo apt-get install -y \
mesa-vulkan-drivers \ mesa-vulkan-drivers \
llvm \ llvm \
glslang-dev \ glslang-dev \
libbz2-dev libbz2-dev \
libxinerama-dev \
libxcursor-dev \
libxi-dev \
libx11-xcb-dev

View File

@@ -8,12 +8,15 @@ on:
paths: paths:
- 'platform/macos/**' - 'platform/macos/**'
- 'platform/darwin/**' - 'platform/darwin/**'
- 'platform/glfw/**'
- '.github/workflows/macos-ci.yml' - '.github/workflows/macos-ci.yml'
- 'bin/**' - 'bin/**'
- 'include/**' - 'include/**'
- 'platform/default/**' - 'platform/default/**'
- 'render-test/**'
- 'src/**' - 'src/**'
- 'vendor/**' - 'vendor/**'
- 'metrics/**'
- '.gitmodules' - '.gitmodules'
- '!**/*.md' - '!**/*.md'
- 'WORKSPACE' - 'WORKSPACE'
@@ -21,6 +24,9 @@ on:
- '.bazelrc' - '.bazelrc'
- '.bazelversion' - '.bazelversion'
- 'pnpm-lock.yaml' - 'pnpm-lock.yaml'
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'cmake/**'
pull_request: pull_request:
branches: branches:
@@ -28,12 +34,15 @@ on:
paths: paths:
- 'platform/macos/**' - 'platform/macos/**'
- 'platform/darwin/**' - 'platform/darwin/**'
- 'platform/glfw/**'
- '.github/workflows/macos-ci.yml' - '.github/workflows/macos-ci.yml'
- 'bin/**' - 'bin/**'
- 'include/**' - 'include/**'
- 'platform/default/**' - 'platform/default/**'
- 'render-test/**'
- 'src/**' - 'src/**'
- 'vendor/**' - 'vendor/**'
- 'metrics/**'
- '.gitmodules' - '.gitmodules'
- '!**/*.md' - '!**/*.md'
- 'WORKSPACE' - 'WORKSPACE'
@@ -41,6 +50,9 @@ on:
- '.bazelrc' - '.bazelrc'
- '.bazelversion' - '.bazelversion'
- 'pnpm-lock.yaml' - 'pnpm-lock.yaml'
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'cmake/**'
concurrency: concurrency:
# cancel jobs on PRs only # cancel jobs on PRs only
@@ -49,8 +61,8 @@ concurrency:
jobs: jobs:
build: build:
name: MacOS CI Tests name: MacOS Metal CI Tests
runs-on: [macos-14] runs-on: [macos-15]
defaults: defaults:
run: run:
working-directory: platform/macos working-directory: platform/macos
@@ -60,7 +72,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
with: with:
submodules: recursive submodules: recursive
persist-credentials: false fetch-depth: 0
- name: Cache Bazel - name: Cache Bazel
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
@@ -80,3 +92,60 @@ jobs:
- name: Generate Xcode project for macOS - name: Generate Xcode project for macOS
run: bazel run //platform/macos:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" run: bazel run //platform/macos:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal"
webgpu-cmake:
name: macOS WebGPU (${{ matrix.variant.name }})
runs-on: macos-15
strategy:
fail-fast: false
matrix:
variant:
- impl: dawn
name: Dawn
# - impl: wgpu
# name: wgpu-native
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4
with:
submodules: recursive
persist-credentials: false
- name: Install dependencies
env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
run: |
brew install cmake ninja pkg-config python@3.11 go glfw libuv icu4c jpeg-turbo webp libpng ccache
brew link --overwrite python@3.11
- name: Configure environment
run: |
{
echo "PKG_CONFIG_PATH=$(brew --prefix icu4c)/lib/pkgconfig:$(brew --prefix libuv)/lib/pkgconfig:${PKG_CONFIG_PATH}"
echo "PATH=$(brew --prefix python@3.11)/bin:$(brew --prefix go)/bin:$PATH"
echo "CC=clang"
echo "CXX=clang++"
} >> "$GITHUB_ENV"
- name: Configure build
run: cmake --preset macos-webgpu-${{ matrix.variant.impl }}
- name: Build targets
run: |
cmake --build build-macos-webgpu-${{ matrix.variant.impl }} --target \
mbgl-render-test-runner
- name: Prepare render test output directory
run: mkdir -p metrics/macos-webgpu-${{ matrix.variant.impl }}
- name: Run WebGPU render tests
id: render_test
run: build-macos-webgpu-${{ matrix.variant.impl }}/mbgl-render-test-runner --manifestPath metrics/macos-webgpu-${{ matrix.variant.impl }}.json
- name: Upload render report
if: always() && steps.render_test.outcome == 'failure'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: macos-webgpu-${{ matrix.variant.impl }}-render-report
path: metrics/macos-webgpu-${{ matrix.variant.impl }}.html

1
.gitignore vendored
View File

@@ -36,6 +36,7 @@ compile_commands.json
node_modules node_modules
.cxx .cxx
.env .env
vendor/dawn/
# Node binaries. # Node binaries.
/lib /lib

View File

@@ -12,6 +12,8 @@ load(
"MLN_DRAWABLES_MTL_HEADERS", "MLN_DRAWABLES_MTL_HEADERS",
"MLN_DRAWABLES_MTL_SOURCE", "MLN_DRAWABLES_MTL_SOURCE",
"MLN_DRAWABLES_SOURCE", "MLN_DRAWABLES_SOURCE",
"MLN_DRAWABLES_WEBGPU_HEADERS",
"MLN_DRAWABLES_WEBGPU_SOURCE",
"MLN_GENERATED_OPENGL_SHADER_HEADERS", "MLN_GENERATED_OPENGL_SHADER_HEADERS",
"MLN_GENERATED_SHADER_HEADERS", "MLN_GENERATED_SHADER_HEADERS",
"MLN_GENERATED_SHADER_SOURCE", "MLN_GENERATED_SHADER_SOURCE",
@@ -85,6 +87,7 @@ cc_library(
MLN_GENERATED_STYLE_SOURCE + select({ MLN_GENERATED_STYLE_SOURCE + select({
":drawable_renderer": MLN_OPENGL_SOURCE + MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_GL_SOURCE, ":drawable_renderer": MLN_OPENGL_SOURCE + MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_GL_SOURCE,
":metal_renderer": MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_MTL_SOURCE, ":metal_renderer": MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_MTL_SOURCE,
":webgpu_renderer": MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_WEBGPU_SOURCE,
"//conditions:default": [], "//conditions:default": [],
}) + select({ }) + select({
"//:harfbuzz_text_shaping": MLN_SHAPING_HARFBUZZ_SRCS, "//:harfbuzz_text_shaping": MLN_SHAPING_HARFBUZZ_SRCS,
@@ -93,6 +96,7 @@ cc_library(
hdrs = MLN_LAYER_PLUGIN_HEADERS + MLN_CORE_HEADERS + select({ hdrs = MLN_LAYER_PLUGIN_HEADERS + MLN_CORE_HEADERS + select({
":drawable_renderer": MLN_OPENGL_HEADERS + MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_GL_HEADERS, ":drawable_renderer": MLN_OPENGL_HEADERS + MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_GL_HEADERS,
":metal_renderer": MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_MTL_HEADERS, ":metal_renderer": MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_MTL_HEADERS,
":webgpu_renderer": MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_WEBGPU_HEADERS,
"//conditions:default": [], "//conditions:default": [],
}), }),
copts = CPP_FLAGS + MAPLIBRE_FLAGS, copts = CPP_FLAGS + MAPLIBRE_FLAGS,
@@ -103,6 +107,9 @@ cc_library(
":metal_renderer": [ ":metal_renderer": [
"MLN_RENDER_BACKEND_METAL=1", "MLN_RENDER_BACKEND_METAL=1",
], ],
":webgpu_renderer": [
"MLN_RENDER_BACKEND_WEBGPU=1",
],
}) + select({ }) + select({
"@platforms//os:ios": ["GLES_SILENCE_DEPRECATION=1"], "@platforms//os:ios": ["GLES_SILENCE_DEPRECATION=1"],
"//conditions:default": [], "//conditions:default": [],
@@ -188,6 +195,7 @@ string_flag(
values = [ values = [
"drawable", "drawable",
"metal", "metal",
"webgpu",
], ],
) )
@@ -205,6 +213,13 @@ config_setting(
}, },
) )
config_setting(
name = "webgpu_renderer",
flag_values = {
":renderer": "webgpu",
},
)
# Selects the shaping implementation to utilize in the core # Selects the shaping implementation to utilize in the core
string_flag( string_flag(

View File

@@ -12,6 +12,7 @@ option(MLN_WITH_OPENGL "Build with OpenGL renderer" OFF)
option(MLN_WITH_EGL "Build with EGL renderer" OFF) option(MLN_WITH_EGL "Build with EGL renderer" OFF)
option(MLN_WITH_VULKAN "Build with Vulkan renderer" OFF) option(MLN_WITH_VULKAN "Build with Vulkan renderer" OFF)
option(MLN_WITH_METAL "Build with Metal renderer" OFF) option(MLN_WITH_METAL "Build with Metal renderer" OFF)
option(MLN_WITH_WEBGPU "Build with WebGPU renderer" OFF)
option(MLN_WITH_PMTILES "Build with PMTiles support" ON) option(MLN_WITH_PMTILES "Build with PMTiles support" ON)
option(MLN_WITH_WERROR "Make all compilation warnings errors" ON) option(MLN_WITH_WERROR "Make all compilation warnings errors" ON)
option(MLN_USE_UNORDERED_DENSE "Use ankerl dense containers for performance" ON) option(MLN_USE_UNORDERED_DENSE "Use ankerl dense containers for performance" ON)
@@ -1309,6 +1310,119 @@ if(MLN_WITH_VULKAN)
) )
endif() endif()
if(MLN_WITH_WEBGPU)
message(STATUS "Configuring WebGPU renderer backend")
target_compile_definitions(
mbgl-core
PUBLIC
MLN_RENDER_BACKEND_WEBGPU=1
)
list(APPEND
SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/context.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/headless_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/draw_scope_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/wgsl_preprocessor.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/background.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/circle.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/clipping_mask.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/collision.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/custom_geometry.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/custom_symbol_icon.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/debug.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/fill.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/fill_extrusion.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/heatmap.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/heatmap_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/hillshade.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/hillshade_prepare.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/line.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/raster.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/widevector.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/tile_layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/layer_group.cpp
)
list(APPEND
INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/command_encoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/context.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/drawable.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/drawable_builder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/render_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/background.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/circle.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/collision.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/common.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/debug.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/fill.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/line.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/raster.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/symbol.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/widevector.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/vertex_buffer_resource.hpp
)
list(APPEND
SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/context.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/vertex_buffer_resource.cpp
)
# Include WebGPU vendor configuration (Dawn or wgpu)
include(${PROJECT_SOURCE_DIR}/vendor/webgpu.cmake)
# Include Dawn integration when requested
if(MLN_WEBGPU_IMPL STREQUAL "dawn")
include(${PROJECT_SOURCE_DIR}/vendor/dawn.cmake)
if(TARGET mbgl-vendor-dawn)
target_link_libraries(mbgl-core PUBLIC mbgl-vendor-dawn)
endif()
endif()
endif()
list(APPEND list(APPEND
SRC_FILES SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/custom_drawable_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/custom_drawable_layer_factory.cpp
@@ -1515,6 +1629,10 @@ if(MLN_TEXT_SHAPING_HARFBUZZ)
list(APPEND EXPORT_TARGETS mbgl-freetype mbgl-harfbuzz) list(APPEND EXPORT_TARGETS mbgl-freetype mbgl-harfbuzz)
endif() endif()
if(TARGET mbgl-vendor-dawn)
list(APPEND EXPORT_TARGETS mbgl-vendor-dawn)
endif()
export(TARGETS ${EXPORT_TARGETS} FILE MapboxCoreTargets.cmake) export(TARGETS ${EXPORT_TARGETS} FILE MapboxCoreTargets.cmake)
if(MLN_WITH_VULKAN) if(MLN_WITH_VULKAN)

View File

@@ -75,6 +75,34 @@
"MLN_DARWIN_USE_LIBUV": "OFF" "MLN_DARWIN_USE_LIBUV": "OFF"
} }
}, },
{
"name": "macos-webgpu-dawn",
"displayName": "macOS WebGPU (Dawn)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-macos-webgpu-dawn",
"inherits": "macos",
"cacheVariables": {
"MLN_WITH_METAL": "OFF",
"MLN_WITH_OPENGL": "OFF",
"MLN_WITH_WEBGPU": "ON",
"MLN_WEBGPU_IMPL": "dawn",
"MLN_DARWIN_USE_LIBUV": "ON"
}
},
{
"name": "macos-webgpu-wgpu",
"displayName": "macOS WebGPU (wgpu-native)",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-macos-webgpu-wgpu",
"inherits": "macos",
"cacheVariables": {
"MLN_WITH_METAL": "OFF",
"MLN_WITH_OPENGL": "OFF",
"MLN_WITH_WEBGPU": "ON",
"MLN_WEBGPU_IMPL": "wgpu",
"MLN_DARWIN_USE_LIBUV": "ON"
}
},
{ {
"name": "linux", "name": "linux",
"hidden": true, "hidden": true,
@@ -113,6 +141,28 @@
"MLN_WITH_X11": "ON" "MLN_WITH_X11": "ON"
} }
}, },
{
"name": "linux-webgpu-dawn",
"binaryDir": "${sourceDir}/build-linux-webgpu-dawn",
"inherits": "linux",
"cacheVariables": {
"MLN_WITH_WEBGPU": "ON",
"MLN_WEBGPU_IMPL": "dawn",
"MLN_WITH_WAYLAND": "OFF",
"MLN_WITH_X11": "ON"
}
},
{
"name": "linux-webgpu-wgpu",
"binaryDir": "${sourceDir}/build-linux-webgpu-wgpu",
"inherits": "linux",
"cacheVariables": {
"MLN_WITH_WEBGPU": "ON",
"MLN_WEBGPU_IMPL": "wgpu",
"MLN_WITH_WAYLAND": "OFF",
"MLN_WITH_X11": "ON"
}
},
{ {
"name": "linux-opengl-node", "name": "linux-opengl-node",
"inherits": "linux", "inherits": "linux",

View File

@@ -1101,6 +1101,91 @@ MLN_DRAWABLES_MTL_HEADERS = [
"include/mbgl/shaders/mtl/widevector.hpp", "include/mbgl/shaders/mtl/widevector.hpp",
] ]
MLN_DRAWABLES_WEBGPU_SOURCE = [
"src/mbgl/webgpu/buffer_resource.cpp",
"src/mbgl/webgpu/command_encoder.cpp",
"src/mbgl/webgpu/context.cpp",
"src/mbgl/webgpu/draw_scope_resource.cpp",
"src/mbgl/webgpu/drawable.cpp",
"src/mbgl/webgpu/drawable_builder.cpp",
"src/mbgl/webgpu/headless_backend.cpp",
"src/mbgl/webgpu/index_buffer_resource.cpp",
"src/mbgl/webgpu/layer_group.cpp",
"src/mbgl/webgpu/offscreen_texture.cpp",
"src/mbgl/webgpu/render_pass.cpp",
"src/mbgl/webgpu/renderer_backend.cpp",
"src/mbgl/webgpu/texture2d.cpp",
"src/mbgl/webgpu/tile_layer_group.cpp",
"src/mbgl/webgpu/uniform_buffer.cpp",
"src/mbgl/webgpu/upload_pass.cpp",
"src/mbgl/webgpu/vertex_attribute.cpp",
"src/mbgl/webgpu/vertex_buffer_resource.cpp",
"src/mbgl/shaders/webgpu/shader_program.cpp",
"src/mbgl/shaders/webgpu/wgsl_preprocessor.cpp",
"src/mbgl/shaders/webgpu/background.cpp",
"src/mbgl/shaders/webgpu/circle.cpp",
"src/mbgl/shaders/webgpu/clipping_mask.cpp",
"src/mbgl/shaders/webgpu/collision.cpp",
"src/mbgl/shaders/webgpu/custom_geometry.cpp",
"src/mbgl/shaders/webgpu/custom_symbol_icon.cpp",
"src/mbgl/shaders/webgpu/debug.cpp",
"src/mbgl/shaders/webgpu/fill.cpp",
"src/mbgl/shaders/webgpu/fill_extrusion.cpp",
"src/mbgl/shaders/webgpu/heatmap.cpp",
"src/mbgl/shaders/webgpu/heatmap_texture.cpp",
"src/mbgl/shaders/webgpu/hillshade.cpp",
"src/mbgl/shaders/webgpu/hillshade_prepare.cpp",
"src/mbgl/shaders/webgpu/line.cpp",
"src/mbgl/shaders/webgpu/location_indicator.cpp",
"src/mbgl/shaders/webgpu/raster.cpp",
"src/mbgl/shaders/webgpu/symbol.cpp",
"src/mbgl/shaders/webgpu/widevector.cpp",
]
MLN_DRAWABLES_WEBGPU_HEADERS = [
"include/mbgl/webgpu/buffer_resource.hpp",
"include/mbgl/webgpu/command_encoder.hpp",
"include/mbgl/webgpu/context.hpp",
"include/mbgl/webgpu/draw_scope_resource.hpp",
"include/mbgl/webgpu/drawable.hpp",
"include/mbgl/webgpu/drawable_builder.hpp",
"include/mbgl/webgpu/index_buffer_resource.hpp",
"include/mbgl/webgpu/layer_group.hpp",
"include/mbgl/webgpu/offscreen_texture.hpp",
"include/mbgl/webgpu/render_pass.hpp",
"include/mbgl/webgpu/renderable_resource.hpp",
"include/mbgl/webgpu/renderer_backend.hpp",
"include/mbgl/webgpu/texture2d.hpp",
"include/mbgl/webgpu/tile_layer_group.hpp",
"include/mbgl/webgpu/uniform_buffer.hpp",
"include/mbgl/webgpu/upload_pass.hpp",
"include/mbgl/webgpu/vertex_attribute.hpp",
"include/mbgl/webgpu/vertex_buffer_resource.hpp",
"include/mbgl/webgpu/wgpu_cpp_compat.hpp",
"include/mbgl/shaders/webgpu/background.hpp",
"include/mbgl/shaders/webgpu/circle.hpp",
"include/mbgl/shaders/webgpu/clipping_mask.hpp",
"include/mbgl/shaders/webgpu/collision.hpp",
"include/mbgl/shaders/webgpu/common.hpp",
"include/mbgl/shaders/webgpu/custom_geometry.hpp",
"include/mbgl/shaders/webgpu/custom_symbol_icon.hpp",
"include/mbgl/shaders/webgpu/debug.hpp",
"include/mbgl/shaders/webgpu/fill.hpp",
"include/mbgl/shaders/webgpu/fill_extrusion.hpp",
"include/mbgl/shaders/webgpu/heatmap.hpp",
"include/mbgl/shaders/webgpu/heatmap_texture.hpp",
"include/mbgl/shaders/webgpu/hillshade.hpp",
"include/mbgl/shaders/webgpu/hillshade_prepare.hpp",
"include/mbgl/shaders/webgpu/line.hpp",
"include/mbgl/shaders/webgpu/location_indicator.hpp",
"include/mbgl/shaders/webgpu/raster.hpp",
"include/mbgl/shaders/webgpu/shader_group.hpp",
"include/mbgl/shaders/webgpu/shader_program.hpp",
"include/mbgl/shaders/webgpu/symbol.hpp",
"include/mbgl/shaders/webgpu/wgsl_preprocessor.hpp",
"include/mbgl/shaders/webgpu/widevector.hpp",
]
MLN_SHAPING_HARFBUZZ_SRCS = [ MLN_SHAPING_HARFBUZZ_SRCS = [
"src/mbgl/text/freetype.hpp", "src/mbgl/text/freetype.hpp",
"src/mbgl/text/freetype.cpp", "src/mbgl/text/freetype.cpp",

View File

@@ -16,13 +16,16 @@ endif()
if (MLN_WITH_VULKAN) if (MLN_WITH_VULKAN)
math(EXPR backend_count "${backend_count} + 1") math(EXPR backend_count "${backend_count} + 1")
endif() endif()
if (MLN_WITH_WEBGPU)
math(EXPR backend_count "${backend_count} + 1")
endif()
if (backend_count EQUAL 0) if (backend_count EQUAL 0)
message(FATAL_ERROR message(FATAL_ERROR
"You need to set a rendering backend. " "You need to set a rendering backend. "
"Set exactly one of: MLN_WITH_OPENGL, MLN_WITH_METAL, or MLN_WITH_VULKAN.") "Set exactly one of: MLN_WITH_OPENGL, MLN_WITH_METAL, MLN_WITH_VULKAN, or MLN_WITH_WEBGPU.")
elseif (backend_count GREATER 1) elseif (backend_count GREATER 1)
message(FATAL_ERROR message(FATAL_ERROR
"Multiple rendering backends selected. " "Multiple rendering backends selected. "
"Please enable only one of: MLN_WITH_OPENGL, MLN_WITH_METAL, or MLN_WITH_VULKAN.") "Please enable only one of: MLN_WITH_OPENGL, MLN_WITH_METAL, MLN_WITH_VULKAN, or MLN_WITH_WEBGPU.")
endif() endif()

View File

@@ -28,15 +28,16 @@ Originally the project only supported OpenGL 2.0. In 2023, the [renderer was mod
What platforms support which rendering backend can be found below. What platforms support which rendering backend can be found below.
| Platform | OpenGL ES 3.0 | Vulkan 1.0 | Metal |
|---|---|---|---| | Platform | OpenGL ES 3.0 | Vulkan 1.0 | Metal | WebGPU |
| Android | ✅ | ✅ | ❌ | | -------- | -------------- | ---------- | ------ | ------- |
| iOS | ❌ | ❌ | ✅ | | Android | ✅ | ✅ | ❌ | ❌ |
| Linux | ✅ | ✅ | ❌ | | iOS | ❌ | ❌ | ✅ | ❌ |
| Windows | ✅ | ❌ | ❌ | | Linux | ✅ | ✅ | ❌ | ✅ |
| macOS | ❌ | ✅ | ✅[^1] | | Windows | ✅ | ❌ | ❌ | ❓ |
| Node.js | ✅ | ❌ | ✅ [^2] | | macOS | ❌ | ✅ | ✅[^1] | ✅ |
| Qt | ✅ | ❌ | ❌ | | Node.js | ✅ | ❌ | ✅[^2] | ❌ |
| Qt | ✅ | ❌ | ❌ | ❌ |
[^1]: Requires MoltenVK. Only available when built via CMake. [^1]: Requires MoltenVK. Only available when built via CMake.
[^2]: Issue reported, see [#2928](https://github.com/maplibre/maplibre-native/issues/2928). [^2]: Issue reported, see [#2928](https://github.com/maplibre/maplibre-native/issues/2928).

View File

@@ -14,11 +14,14 @@ public:
OpenGL, ///< The OpenGL API backend OpenGL, ///< The OpenGL API backend
Metal, ///< The Metal API backend Metal, ///< The Metal API backend
Vulkan, ///< The Vulkan API backend Vulkan, ///< The Vulkan API backend
WebGPU, ///< The WebGPU API backend
TYPE_MAX, ///< Not a valid backend type, used to determine the number TYPE_MAX, ///< Not a valid backend type, used to determine the number
///< of available backends (ie for array allocation). ///< of available backends (ie for array allocation).
}; };
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_WEBGPU
static constexpr Type DefaultType = Type::WebGPU;
#elif MLN_RENDER_BACKEND_METAL
static constexpr Type DefaultType = Type::Metal; static constexpr Type DefaultType = Type::Metal;
#elif MLN_RENDER_BACKEND_VULKAN #elif MLN_RENDER_BACKEND_VULKAN
static constexpr Type DefaultType = Type::Vulkan; static constexpr Type DefaultType = Type::Vulkan;
@@ -39,7 +42,9 @@ public:
template <typename T, typename... Args> template <typename T, typename... Args>
static std::unique_ptr<T> Create(Args... args) { static std::unique_ptr<T> Create(Args... args) {
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_WEBGPU
return Create<Type::WebGPU, T, Args...>(std::forward<Args>(args)...);
#elif MLN_RENDER_BACKEND_METAL
return Create<Type::Metal, T, Args...>(std::forward<Args>(args)...); return Create<Type::Metal, T, Args...>(std::forward<Args>(args)...);
#elif MLN_RENDER_BACKEND_VULKAN #elif MLN_RENDER_BACKEND_VULKAN
return Create<Type::Vulkan, T, Args...>(std::forward<Args>(args)...); return Create<Type::Vulkan, T, Args...>(std::forward<Args>(args)...);

View File

@@ -75,7 +75,8 @@ private:
std::mutex mutex; std::mutex mutex;
}; };
#define MLN_DEFER_UPLOAD_ON_RENDER_THREAD (MLN_RENDER_BACKEND_OPENGL || MLN_RENDER_BACKEND_VULKAN) #define MLN_DEFER_UPLOAD_ON_RENDER_THREAD \
(MLN_RENDER_BACKEND_OPENGL || MLN_RENDER_BACKEND_VULKAN || MLN_RENDER_BACKEND_WEBGPU)
} // namespace gfx } // namespace gfx
} // namespace mbgl } // namespace mbgl

View File

@@ -42,6 +42,10 @@ public:
return static_cast<T&>(*resource); return static_cast<T&>(*resource);
} }
bool hasResource() const { return resource != nullptr; }
void setResource(std::unique_ptr<RenderableResource> resource_) { resource = std::move(resource_); }
virtual void wait() {} virtual void wait() {}
bool operator!=(const Renderable& other) const { return resource.get() != other.resource.get(); } bool operator!=(const Renderable& other) const { return resource.get() != other.resource.get(); }

View File

@@ -65,6 +65,8 @@ enum {
idGlobalUBOIndex, idGlobalUBOIndex,
#elif MLN_RENDER_BACKEND_VULKAN #elif MLN_RENDER_BACKEND_VULKAN
idGlobalPlatformParamsUBO, idGlobalPlatformParamsUBO,
#elif MLN_RENDER_BACKEND_WEBGPU
idGlobalUBOIndex,
#endif #endif
globalUBOCount globalUBOCount
}; };
@@ -75,7 +77,7 @@ enum {
drawableReservedUBOCount drawableReservedUBOCount
}; };
#define MLN_UBO_CONSOLIDATION (MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_VULKAN) #define MLN_UBO_CONSOLIDATION (MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_VULKAN || MLN_RENDER_BACKEND_WEBGPU)
} // namespace shaders } // namespace shaders
} // namespace mbgl } // namespace mbgl

View File

@@ -0,0 +1,230 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/background_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::BackgroundShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "BackgroundShader";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(3) position: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
};
struct BackgroundDrawableUBO {
matrix: mat4x4<f32>,
};
struct BackgroundDrawableUnionUBO {
matrix_col0: vec4<f32>,
matrix_col1: vec4<f32>,
matrix_col2: vec4<f32>,
matrix_col3: vec4<f32>,
extra0: vec4<f32>,
extra1: vec4<f32>,
};
struct BackgroundPropsUBO {
color: vec4<f32>,
opacity: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<BackgroundDrawableUnionUBO>;
@group(0) @binding(4) var<uniform> props: BackgroundPropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let matrix = mat4x4<f32>(drawable.matrix_col0,
drawable.matrix_col1,
drawable.matrix_col2,
drawable.matrix_col3);
let clip = matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
out.position = clip;
return out;
}
)";
static constexpr auto fragment = R"(
struct BackgroundPropsUBO {
color: vec4<f32>,
opacity: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
@group(0) @binding(4) var<uniform> props: BackgroundPropsUBO;
@fragment
fn main() -> @location(0) vec4<f32> {
return props.color * props.opacity;
}
)";
};
template <>
struct ShaderSource<BuiltIn::BackgroundPatternShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "BackgroundPatternShader";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(3) position: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) pos_a: vec2<f32>,
@location(1) pos_b: vec2<f32>,
};
struct BackgroundPatternDrawableUnionUBO {
matrix_col0: vec4<f32>,
matrix_col1: vec4<f32>,
matrix_col2: vec4<f32>,
matrix_col3: vec4<f32>,
pixel_coord_upper: vec2<f32>,
pixel_coord_lower: vec2<f32>,
tile_units_to_pixels: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
struct BackgroundPatternPropsUBO {
pattern_tl_a: vec2<f32>,
pattern_br_a: vec2<f32>,
pattern_tl_b: vec2<f32>,
pattern_br_b: vec2<f32>,
pattern_size_a: vec2<f32>,
pattern_size_b: vec2<f32>,
scale_a: f32,
scale_b: f32,
mix: f32,
opacity: f32,
pad1: f32,
pad2: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<BackgroundPatternDrawableUnionUBO>;
@group(0) @binding(4) var<uniform> props: BackgroundPatternPropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let matrix = mat4x4<f32>(drawable.matrix_col0,
drawable.matrix_col1,
drawable.matrix_col2,
drawable.matrix_col3);
let pos = vec2<f32>(f32(in.position.x), f32(in.position.y));
// Use get_pattern_pos helper function for pattern positioning
out.pos_a = get_pattern_pos(
drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
props.scale_a * props.pattern_size_a,
drawable.tile_units_to_pixels,
pos
);
out.pos_b = get_pattern_pos(
drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
props.scale_b * props.pattern_size_b,
drawable.tile_units_to_pixels,
pos
);
let clip = matrix * vec4<f32>(pos, 0.0, 1.0);
out.position = clip;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) pos_a: vec2<f32>,
@location(1) pos_b: vec2<f32>,
};
struct BackgroundPatternPropsUBO {
pattern_tl_a: vec2<f32>,
pattern_br_a: vec2<f32>,
pattern_tl_b: vec2<f32>,
pattern_br_b: vec2<f32>,
pattern_size_a: vec2<f32>,
pattern_size_b: vec2<f32>,
scale_a: f32,
scale_b: f32,
mix: f32,
opacity: f32,
pad1: f32,
pad2: f32,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
// other fields...
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(4) var<uniform> props: BackgroundPatternPropsUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var pattern_texture: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let texsize = paintParams.pattern_atlas_texsize;
// Sample pattern A
let imagecoord_a = gl_mod(in.pos_a, vec2<f32>(1.0));
let pos_a = mix(props.pattern_tl_a / texsize, props.pattern_br_a / texsize, imagecoord_a);
let color_a = textureSample(pattern_texture, texture_sampler, pos_a);
// Sample pattern B
let imagecoord_b = gl_mod(in.pos_b, vec2<f32>(1.0));
let pos_b = mix(props.pattern_tl_b / texsize, props.pattern_br_b / texsize, imagecoord_b);
let color_b = textureSample(pattern_texture, texture_sampler, pos_b);
// Mix patterns and apply opacity
return mix(color_a, color_b, props.mix) * props.opacity;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,211 @@
#pragma once
#include <mbgl/shaders/circle_layer_ubo.hpp>
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::CircleShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "CircleShader";
static const std::array<AttributeInfo, 8> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto prelude = R"(
struct CircleDrawableUBO {
matrix: mat4x4<f32>,
extrude_scale: vec2<f32>,
color_t: f32,
radius_t: f32,
blur_t: f32,
opacity_t: f32,
stroke_color_t: f32,
stroke_width_t: f32,
stroke_opacity_t: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
struct CircleEvaluatedPropsUBO {
color: vec4<f32>,
stroke_color: vec4<f32>,
radius: f32,
blur: f32,
opacity: f32,
stroke_width: f32,
stroke_opacity: f32,
scale_with_map: i32,
pitch_with_map: i32,
pad1: f32,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<CircleDrawableUBO>;
@group(0) @binding(4) var<uniform> props: CircleEvaluatedPropsUBO;
)";
static constexpr auto vertex = R"(
struct VertexInput {
@location(0) position: vec2<i32>,
@location(1) color: vec4<f32>,
@location(2) radius: vec2<f32>,
@location(3) blur: vec2<f32>,
@location(4) opacity: vec2<f32>,
@location(5) stroke_color: vec4<f32>,
@location(6) stroke_width: vec2<f32>,
@location(7) stroke_opacity: vec2<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) extrude: vec2<f32>,
@location(1) circle_data: vec4<f32>,
@location(2) color: vec4<f32>,
@location(3) stroke_color: vec4<f32>,
@location(4) stroke_data: vec2<f32>,
};
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let scale_with_map = props.scale_with_map != 0;
let pitch_with_map = props.pitch_with_map != 0;
let pos_f = vec2<f32>(f32(in.position.x), f32(in.position.y));
let extrude = glMod2v(pos_f, vec2<f32>(2.0, 2.0)) * 2.0 - vec2<f32>(1.0, 1.0);
let scaled_extrude = extrude * drawable.extrude_scale;
let circle_center = floor(pos_f * 0.5);
var color = props.color;
#ifndef HAS_UNIFORM_u_color
color = unpack_mix_color(in.color, drawable.color_t);
#endif
var radius = props.radius;
#ifndef HAS_UNIFORM_u_radius
radius = unpack_mix_float(in.radius, drawable.radius_t);
#endif
var blur = props.blur;
#ifndef HAS_UNIFORM_u_blur
blur = unpack_mix_float(in.blur, drawable.blur_t);
#endif
var opacity = props.opacity;
#ifndef HAS_UNIFORM_u_opacity
opacity = unpack_mix_float(in.opacity, drawable.opacity_t);
#endif
var stroke_color = props.stroke_color;
#ifndef HAS_UNIFORM_u_stroke_color
stroke_color = unpack_mix_color(in.stroke_color, drawable.stroke_color_t);
#endif
var stroke_width = props.stroke_width;
#ifndef HAS_UNIFORM_u_stroke_width
stroke_width = unpack_mix_float(in.stroke_width, drawable.stroke_width_t);
#endif
var stroke_opacity = props.stroke_opacity;
#ifndef HAS_UNIFORM_u_stroke_opacity
stroke_opacity = unpack_mix_float(in.stroke_opacity, drawable.stroke_opacity_t);
#endif
let radius_with_stroke = radius + stroke_width;
var position: vec4<f32>;
if (pitch_with_map) {
var corner_position = circle_center;
if (scale_with_map) {
corner_position += scaled_extrude * radius_with_stroke;
} else {
let projected_center = drawable.matrix * vec4<f32>(circle_center, 0.0, 1.0);
corner_position += scaled_extrude * radius_with_stroke *
(projected_center.w / paintParams.camera_to_center_distance);
}
position = drawable.matrix * vec4<f32>(corner_position, 0.0, 1.0);
} else {
position = drawable.matrix * vec4<f32>(circle_center, 0.0, 1.0);
var factor = position.w;
if (scale_with_map) {
factor = paintParams.camera_to_center_distance;
}
let delta = scaled_extrude * radius_with_stroke * factor;
position = vec4<f32>(position.x + delta.x, position.y + delta.y, position.z, position.w);
}
let antialiasblur = 1.0 / max(paintParams.pixel_ratio * radius_with_stroke, 1e-6);
out.position = position;
out.extrude = extrude;
out.circle_data = vec4<f32>(antialiasblur, radius, blur, opacity);
out.color = color;
out.stroke_color = stroke_color;
out.stroke_data = vec2<f32>(stroke_width, stroke_opacity);
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) extrude: vec2<f32>,
@location(1) circle_data: vec4<f32>,
@location(2) color: vec4<f32>,
@location(3) stroke_color: vec4<f32>,
@location(4) stroke_data: vec2<f32>,
};
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let extrude_length = length(in.extrude);
let antialiasblur = in.circle_data.x;
let radius = in.circle_data.y;
let blur = in.circle_data.z;
let opacity = in.circle_data.w;
let stroke_width = in.stroke_data.x;
let stroke_opacity = in.stroke_data.y;
let antialiased_blur = -max(blur, antialiasblur);
let opacity_t = smoothstep(0.0, antialiased_blur, extrude_length - 1.0);
var color_t: f32;
if (stroke_width < 0.01) {
color_t = 0.0;
} else {
color_t = smoothstep(antialiased_blur, 0.0, extrude_length - radius / (radius + stroke_width));
}
let final_color = mix(in.color * opacity, in.stroke_color * stroke_opacity, color_t);
return opacity_t * final_color;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,65 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <array>
#include <cstdint>
namespace mbgl {
namespace shaders {
struct alignas(16) ClipUBO {
std::array<float, 4 * 4> matrix;
std::uint32_t stencil_ref;
float pad1;
float pad2;
float pad3;
};
static_assert(sizeof(ClipUBO) == 5 * 16, "wrong size");
template <>
struct ShaderSource<BuiltIn::ClippingMaskProgram, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "ClippingMaskProgram";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(2) position: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
};
struct ClipUBO {
matrix: mat4x4<f32>,
stencil_ref: u32,
pad1: f32,
pad2: f32,
pad3: f32,
};
@group(0) @binding(0) var<uniform> clip: ClipUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let clip_pos = clip.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
out.position = vec4<f32>(clip_pos.x, clip_pos.y, clip_pos.z, clip_pos.w);
return out;
}
)";
static constexpr auto fragment = R"(
@fragment
fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,240 @@
#pragma once
#include <mbgl/shaders/collision_layer_ubo.hpp>
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::CollisionBoxShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "CollisionBoxShader";
static const std::array<AttributeInfo, 5> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(3) position: vec2<i32>,
@location(4) anchor_position: vec2<i32>,
@location(5) extrude: vec2<i32>,
@location(6) placed: vec2<u32>,
@location(7) shift: vec2<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) placed: f32,
@location(1) not_used: f32,
};
struct CollisionDrawableUBO {
matrix: mat4x4<f32>,
};
struct CollisionTilePropsUBO {
extrude_scale: vec2<f32>,
overscale_factor: f32,
pad1: f32,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(2) var<uniform> drawable: CollisionDrawableUBO;
@group(0) @binding(4) var<uniform> tile_props: CollisionTilePropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let projected_point = drawable.matrix * vec4<f32>(f32(in.anchor_position.x), f32(in.anchor_position.y), 0.0, 1.0);
let camera_to_anchor_distance = projected_point.w;
let collision_perspective_ratio = clamp(
0.5 + 0.5 * (paintParams.camera_to_center_distance / camera_to_anchor_distance),
0.0,
4.0);
out.position = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
let extrude_shift = vec2<f32>(f32(in.extrude.x), f32(in.extrude.y)) + in.shift;
out.position.x += extrude_shift.x * tile_props.extrude_scale.x * out.position.w * collision_perspective_ratio;
out.position.y += extrude_shift.y * tile_props.extrude_scale.y * out.position.w * collision_perspective_ratio;
out.placed = f32(in.placed.x);
out.not_used = f32(in.placed.y);
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) placed: f32,
@location(1) not_used: f32,
};
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let alpha = 0.5;
// Red = collision, hide label
var color = vec4<f32>(1.0, 0.0, 0.0, 1.0) * alpha;
// Blue = no collision, label is showing
if (in.placed > 0.5) {
color = vec4<f32>(0.0, 0.0, 1.0, 0.5) * alpha;
}
if (in.not_used > 0.5) {
// This box not used, fade it out
color = color * 0.1;
}
return color;
}
)";
};
template <>
struct ShaderSource<BuiltIn::CollisionCircleShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "CollisionCircleShader";
static const std::array<AttributeInfo, 4> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(3) position: vec2<i32>,
@location(4) anchor_position: vec2<i32>,
@location(5) extrude: vec2<i32>,
@location(6) placed: vec2<u32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) placed: f32,
@location(1) not_used: f32,
@location(2) radius: f32,
@location(3) extrude: vec2<f32>,
@location(4) extrude_scale: vec2<f32>,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
struct CollisionDrawableUBO {
matrix: mat4x4<f32>,
};
struct CollisionTilePropsUBO {
extrude_scale: vec2<f32>,
overscale_factor: f32,
pad1: f32,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(2) var<uniform> drawable: CollisionDrawableUBO;
@group(0) @binding(4) var<uniform> tile_props: CollisionTilePropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let projected_point = drawable.matrix * vec4<f32>(f32(in.anchor_position.x), f32(in.anchor_position.y), 0.0, 1.0);
let camera_to_anchor_distance = projected_point.w;
let perspective = max(camera_to_anchor_distance, 1e-6);
let collision_perspective_ratio = clamp(
0.5 + 0.5 * (paintParams.camera_to_center_distance / perspective),
0.0,
4.0);
var position = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
let padding_factor = 1.2;
let extrude_vec = vec2<f32>(f32(in.extrude.x), f32(in.extrude.y));
position.x += extrude_vec.x * tile_props.extrude_scale.x * padding_factor * position.w * collision_perspective_ratio;
position.y += extrude_vec.y * tile_props.extrude_scale.y * padding_factor * position.w * collision_perspective_ratio;
out.position = position;
out.placed = f32(in.placed.x);
out.not_used = f32(in.placed.y);
out.radius = abs(f32(in.extrude.y));
out.extrude = extrude_vec * padding_factor;
out.extrude_scale = tile_props.extrude_scale * paintParams.camera_to_center_distance * collision_perspective_ratio;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) placed: f32,
@location(1) not_used: f32,
@location(2) radius: f32,
@location(3) extrude: vec2<f32>,
@location(4) extrude_scale: vec2<f32>,
};
struct CollisionTilePropsUBO {
extrude_scale: vec2<f32>,
overscale_factor: f32,
pad1: f32,
};
@group(0) @binding(4) var<uniform> tile_props: CollisionTilePropsUBO;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let alpha = 0.5;
// Red = collision, hide label
var color = vec4<f32>(1.0, 0.0, 0.0, 1.0) * alpha;
// Blue = no collision, label is showing
if (in.placed > 0.5) {
color = vec4<f32>(0.0, 0.0, 1.0, 0.5) * alpha;
}
if (in.not_used > 0.5) {
color = color * 0.2;
}
// Add circle outline effect
let extrude_scale_length = max(length(in.extrude_scale), 1e-6);
let extrude_length = length(in.extrude) * extrude_scale_length;
let overscale = max(tile_props.overscale_factor, 1.0);
let stroke_width = 15.0 * extrude_scale_length / overscale;
let radius = in.radius * extrude_scale_length;
let distance_to_edge = abs(extrude_length - radius);
let opacity_t = smoothstep(-stroke_width, 0.0, -distance_to_edge);
return opacity_t * color;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,90 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
namespace mbgl {
namespace shaders {
// The enum values are already defined in layer_ubo.hpp
// WebGPU uses the same indices as Metal
template <>
struct ShaderSource<BuiltIn::Prelude, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "Prelude";
// Common WGSL code that can be included in other shaders
static constexpr auto prelude = R"(
// Constants
const PI: f32 = 3.14159265358979323846;
const LINE_NORMAL_SCALE: f32 = 1.0 / 63.0; // 1.0 / (127 / 2)
const MAX_LINE_DISTANCE: f32 = 32767.0;
const SDF_PX: f32 = 8.0;
// Helper functions for unpacking data
fn unpack_float(packedValue: f32) -> vec2<f32> {
let packedIntValue = i32(packedValue);
let v0 = packedIntValue / 256;
return vec2<f32>(f32(v0), f32(packedIntValue - v0 * 256));
}
fn unpack_opacity(packedOpacity: f32) -> vec2<f32> {
let intOpacity = i32(packedOpacity) / 2;
return vec2<f32>(f32(intOpacity) / 127.0, packedOpacity % 2.0);
}
// Decode a color that has been packed into two floats
fn decode_color(encoded: vec2<f32>) -> vec4<f32> {
let e0 = unpack_float(encoded[0]) / 255.0;
let e1 = unpack_float(encoded[1]) / 255.0;
return vec4<f32>(e0.x, e0.y, e1.x, e1.y);
}
// Unpack and interpolate between two float values
fn unpack_mix_float(packedValue: vec2<f32>, t: f32) -> f32 {
return mix(packedValue[0], packedValue[1], t);
}
// Unpack and interpolate between two colors
fn unpack_mix_color(packedColors: vec4<f32>, t: f32) -> vec4<f32> {
let minColor = decode_color(vec2<f32>(packedColors[0], packedColors[1]));
let maxColor = decode_color(vec2<f32>(packedColors[2], packedColors[3]));
return mix(minColor, maxColor, t);
}
// Get pattern position for texture atlas lookups
fn get_pattern_pos(pixel_coord_upper: vec2<f32>, pixel_coord_lower: vec2<f32>,
pattern_size: vec2<f32>, tile_units_to_pixels: f32, pos: vec2<f32>) -> vec2<f32> {
let offset_a = (pixel_coord_upper % pattern_size) * 256.0;
let offset_b = (offset_a % pattern_size) * 256.0 + pixel_coord_lower;
let offset = offset_b % pattern_size;
return (tile_units_to_pixels * pos + offset) / pattern_size;
}
// Radians conversion
fn radians(degrees: f32) -> f32 {
return PI * degrees / 180.0;
}
// WebGPU equivalent of GLSL mod function
fn glMod(x: f32, y: f32) -> f32 {
return x - y * floor(x / y);
}
fn glMod2(x: vec2<f32>, y: f32) -> vec2<f32> {
return x - y * floor(x / y);
}
fn glMod2v(x: vec2<f32>, y: vec2<f32>) -> vec2<f32> {
return x - y * floor(x / y);
}
// GLSL compatibility helpers
fn gl_mod(x: vec2<f32>, y: vec2<f32>) -> vec2<f32> {
return glMod2v(x, y);
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,66 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/custom_geometry_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::CustomGeometryShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "CustomGeometryShader";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(3) position: vec3<f32>,
@location(4) texcoord: vec2<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
};
struct CustomDrawableUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> drawable: CustomDrawableUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.uv = in.texcoord;
out.position = drawable.matrix * vec4<f32>(in.position, 1.0);
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) uv: vec2<f32>,
};
struct CustomDrawableUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> drawable: CustomDrawableUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var texture_image: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
return textureSample(texture_image, texture_sampler, in.uv) * drawable.color;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,68 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/custom_drawable_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::CustomSymbolIconShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "CustomSymbolIconShader";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(4) position: vec3<f32>,
@location(5) texcoord: vec2<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
};
struct SymbolUBO {
matrix: mat4x4<f32>,
opacity: f32,
};
@group(0) @binding(0) var<uniform> symbol_ubo: SymbolUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = symbol_ubo.matrix * vec4<f32>(in.position, 1.0);
out.uv = in.texcoord;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) uv: vec2<f32>,
};
struct SymbolUBO {
matrix: mat4x4<f32>,
opacity: f32,
};
@group(0) @binding(0) var<uniform> symbol_ubo: SymbolUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var texture_image: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
var color = textureSample(texture_image, texture_sampler, in.uv);
color.a = color.a * symbol_ubo.opacity;
return color;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,80 @@
#pragma once
#include <mbgl/shaders/debug_layer_ubo.hpp>
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::DebugShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "DebugShader";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(5) position: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
};
struct DebugUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
overlay_scale: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
@group(0) @binding(0) var<uniform> debug: DebugUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let scaled_pos = vec2<f32>(f32(in.position.x), f32(in.position.y)) * debug.overlay_scale;
out.position = debug.matrix * vec4<f32>(scaled_pos, 0.0, 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
out.uv = vec2<f32>(f32(in.position.x), f32(in.position.y)) / 8192.0;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) uv: vec2<f32>,
};
struct DebugUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
overlay_scale: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
@group(0) @binding(0) var<uniform> debug: DebugUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var overlay_texture: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let overlay_color = textureSample(overlay_texture, texture_sampler, in.uv);
return mix(debug.color, overlay_color, overlay_color.a);
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,910 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/fill_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::FillShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "FillShader";
static const std::array<AttributeInfo, 3> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr const char* vertex = R"(
struct VertexInput {
@location(0) position: vec2<i32>,
#ifndef HAS_UNIFORM_u_color
@location(1) color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(2) opacity: vec2<f32>,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) opacity: f32,
};
struct FillDrawableUBO {
matrix: mat4x4<f32>,
color_t: f32,
opacity_t: f32,
pad1: f32,
pad2: f32,
};
struct FillDrawableUnionUBO {
fill: FillDrawableUBO,
padding: vec4<f32>,
};
struct FillEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<FillDrawableUnionUBO>;
@group(0) @binding(5) var<uniform> props: FillEvaluatedPropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
// Transform position using the matrix
let drawable = drawableVector[globalIndex.value].fill;
let clip = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
out.position = clip;
var color: vec4<f32>;
#ifdef HAS_UNIFORM_u_color
color = props.color;
#else
color = unpack_mix_color(in.color, drawable.color_t);
#endif
var opacity: f32;
#ifdef HAS_UNIFORM_u_opacity
opacity = props.opacity;
#else
opacity = unpack_mix_float(in.opacity, drawable.opacity_t);
#endif
out.color = color;
out.opacity = opacity;
return out;
}
)";
static constexpr const char* fragment = R"(
struct FillEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
@group(0) @binding(5) var<uniform> props: FillEvaluatedPropsUBO;
struct FragmentInput {
@location(0) color: vec4<f32>,
@location(1) opacity: f32,
};
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
var color: vec4<f32>;
#ifdef HAS_UNIFORM_u_color
color = props.color;
#else
color = in.color;
#endif
var opacity: f32;
#ifdef HAS_UNIFORM_u_opacity
opacity = props.opacity;
#else
opacity = in.opacity;
#endif
return color * opacity;
}
)";
};
template <>
struct ShaderSource<BuiltIn::FillOutlineShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "FillOutlineShader";
static const std::array<AttributeInfo, 3> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr const char* vertex = R"(
struct VertexInput {
@location(0) position: vec2<i32>,
#ifndef HAS_UNIFORM_u_outline_color
@location(1) outline_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(2) opacity: vec2<f32>,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) opacity: f32,
@location(2) pos: vec2<f32>,
};
struct FillOutlineDrawableUBO {
matrix: mat4x4<f32>,
outline_color_t: f32,
opacity_t: f32,
pad1: f32,
pad2: f32,
};
struct FillOutlineDrawableUnionUBO {
fill: FillOutlineDrawableUBO,
padding: vec4<f32>,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
struct FillOutlineEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<FillOutlineDrawableUnionUBO>;
@group(0) @binding(5) var<uniform> props: FillOutlineEvaluatedPropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
// Transform position using the matrix
let drawable = drawableVector[globalIndex.value].fill;
let clip = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
let invW = 1.0 / clip.w;
let ndcXY = clip.xy * invW;
out.position = clip;
out.pos = (ndcXY + vec2<f32>(1.0)) * 0.5 * paintParams.world_size;
var color: vec4<f32>;
#ifdef HAS_UNIFORM_u_outline_color
color = props.outline_color;
#else
color = unpack_mix_color(in.outline_color, drawable.outline_color_t);
#endif
var opacity: f32;
#ifdef HAS_UNIFORM_u_opacity
opacity = props.opacity;
#else
opacity = unpack_mix_float(in.opacity, drawable.opacity_t);
#endif
out.color = color;
out.opacity = opacity;
return out;
}
)";
static constexpr const char* fragment = R"(
struct FillOutlineEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
@group(0) @binding(5) var<uniform> props: FillOutlineEvaluatedPropsUBO;
struct FragmentInput {
@location(0) color: vec4<f32>,
@location(1) opacity: f32,
@location(2) pos: vec2<f32>,
};
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
var color: vec4<f32>;
#ifdef HAS_UNIFORM_u_outline_color
color = props.outline_color;
#else
color = in.color;
#endif
var opacity: f32;
#ifdef HAS_UNIFORM_u_opacity
opacity = props.opacity;
#else
opacity = in.opacity;
#endif
return color * opacity;
}
)";
};
template <>
struct ShaderSource<BuiltIn::FillPatternShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "FillPatternShader";
static const std::array<AttributeInfo, 4> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr const char* vertex = R"(
struct VertexInput {
@location(4) position: vec2<i32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(5) pattern_from: vec4<u32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(6) pattern_to: vec4<u32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(7) opacity: vec2<f32>,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) v_pos_a: vec2<f32>,
@location(1) v_pos_b: vec2<f32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(2) pattern_from: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(3) pattern_to: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(4) opacity: f32,
#endif
};
struct FillPatternDrawableUBO {
matrix: mat4x4<f32>,
pixel_coord_upper: vec2<f32>,
pixel_coord_lower: vec2<f32>,
tile_ratio: f32,
pattern_from_t: f32,
pattern_to_t: f32,
opacity_t: f32,
};
struct FillPatternTilePropsUBO {
pattern_from: vec4<f32>,
pattern_to: vec4<f32>,
texsize: vec2<f32>,
pad1: f32,
pad2: f32,
};
struct FillEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<FillPatternDrawableUBO>;
@group(0) @binding(4) var<storage, read> tilePropsVector: array<FillPatternTilePropsUBO>;
@group(0) @binding(5) var<uniform> props: FillEvaluatedPropsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let index = globalIndex.value;
let drawable = drawableVector[index];
let tileProps = tilePropsVector[index];
var pattern_from: vec4<f32>;
#ifdef HAS_UNIFORM_u_pattern_from
pattern_from = tileProps.pattern_from;
#else
pattern_from = vec4<f32>(in.pattern_from);
#endif
var pattern_to: vec4<f32>;
#ifdef HAS_UNIFORM_u_pattern_to
pattern_to = tileProps.pattern_to;
#else
pattern_to = vec4<f32>(in.pattern_to);
#endif
let pattern_tl_a = pattern_from.xy;
let pattern_br_a = pattern_from.zw;
let pattern_tl_b = pattern_to.xy;
let pattern_br_b = pattern_to.zw;
let pixelRatio = paintParams.pixel_ratio;
let tileZoomRatio = drawable.tile_ratio;
let fromScale = props.from_scale;
let toScale = props.to_scale;
let display_size_a = vec2<f32>(
(pattern_br_a.x - pattern_tl_a.x) / pixelRatio,
(pattern_br_a.y - pattern_tl_a.y) / pixelRatio
);
let display_size_b = vec2<f32>(
(pattern_br_b.x - pattern_tl_b.x) / pixelRatio,
(pattern_br_b.y - pattern_tl_b.y) / pixelRatio
);
let pos = vec2<f32>(f32(in.position.x), f32(in.position.y));
let clip = drawable.matrix * vec4<f32>(pos, 0.0, 1.0);
out.position = clip;
out.v_pos_a = get_pattern_pos(
drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
fromScale * display_size_a,
tileZoomRatio,
pos
);
out.v_pos_b = get_pattern_pos(
drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
toScale * display_size_b,
tileZoomRatio,
pos
);
#ifndef HAS_UNIFORM_u_pattern_from
out.pattern_from = pattern_from;
#endif
#ifndef HAS_UNIFORM_u_pattern_to
out.pattern_to = pattern_to;
#endif
#ifndef HAS_UNIFORM_u_opacity
out.opacity = unpack_mix_float(in.opacity, drawable.opacity_t);
#endif
return out;
}
)";
static constexpr const char* fragment = R"(
struct FragmentInput {
@location(0) v_pos_a: vec2<f32>,
@location(1) v_pos_b: vec2<f32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(2) pattern_from: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(3) pattern_to: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(4) opacity: f32,
#endif
};
struct FillPatternTilePropsUBO {
pattern_from: vec4<f32>,
pattern_to: vec4<f32>,
texsize: vec2<f32>,
pad1: f32,
pad2: f32,
};
struct FillEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(4) var<storage, read> tilePropsVector: array<FillPatternTilePropsUBO>;
@group(0) @binding(5) var<uniform> props: FillEvaluatedPropsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var pattern_texture: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let tileProps = tilePropsVector[globalIndex.value];
let pattern_from =
#ifdef HAS_UNIFORM_u_pattern_from
tileProps.pattern_from;
#else
in.pattern_from;
#endif
let pattern_to =
#ifdef HAS_UNIFORM_u_pattern_to
tileProps.pattern_to;
#else
in.pattern_to;
#endif
let pattern_tl_a = pattern_from.xy;
let pattern_br_a = pattern_from.zw;
let pattern_tl_b = pattern_to.xy;
let pattern_br_b = pattern_to.zw;
// Sample pattern A
let imagecoord_a = gl_mod(in.v_pos_a, vec2<f32>(1.0));
let pos_a = mix(pattern_tl_a / tileProps.texsize, pattern_br_a / tileProps.texsize, imagecoord_a);
let color_a = textureSample(pattern_texture, texture_sampler, pos_a);
// Sample pattern B
let imagecoord_b = gl_mod(in.v_pos_b, vec2<f32>(1.0));
let pos_b = mix(pattern_tl_b / tileProps.texsize, pattern_br_b / tileProps.texsize, imagecoord_b);
let color_b = textureSample(pattern_texture, texture_sampler, pos_b);
// Mix patterns and apply opacity
let opacity =
#ifdef HAS_UNIFORM_u_opacity
props.opacity;
#else
in.opacity;
#endif
return mix(color_a, color_b, props.fade) * opacity;
}
)";
};
template <>
struct ShaderSource<BuiltIn::FillOutlinePatternShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "FillOutlinePatternShader";
static const std::array<AttributeInfo, 4> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr const char* vertex = R"(
struct VertexInput {
@location(4) position: vec2<i32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(5) pattern_from: vec4<u32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(6) pattern_to: vec4<u32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(7) opacity: vec2<f32>,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) v_pos_a: vec2<f32>,
@location(1) v_pos_b: vec2<f32>,
@location(2) v_pos: vec2<f32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(3) pattern_from: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(4) pattern_to: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(5) opacity: f32,
#endif
};
struct FillOutlinePatternDrawableUBO {
matrix: mat4x4<f32>,
pixel_coord_upper: vec2<f32>,
pixel_coord_lower: vec2<f32>,
tile_ratio: f32,
pattern_from_t: f32,
pattern_to_t: f32,
opacity_t: f32,
};
struct FillOutlinePatternTilePropsUBO {
pattern_from: vec4<f32>,
pattern_to: vec4<f32>,
texsize: vec2<f32>,
pad1: f32,
pad2: f32,
};
struct FillEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<FillOutlinePatternDrawableUBO>;
@group(0) @binding(4) var<storage, read> tilePropsVector: array<FillOutlinePatternTilePropsUBO>;
@group(0) @binding(5) var<uniform> props: FillEvaluatedPropsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let index = globalIndex.value;
let drawable = drawableVector[index];
let tileProps = tilePropsVector[index];
var pattern_from: vec4<f32>;
#ifdef HAS_UNIFORM_u_pattern_from
pattern_from = tileProps.pattern_from;
#else
pattern_from = vec4<f32>(in.pattern_from);
#endif
var pattern_to: vec4<f32>;
#ifdef HAS_UNIFORM_u_pattern_to
pattern_to = tileProps.pattern_to;
#else
pattern_to = vec4<f32>(in.pattern_to);
#endif
let pattern_tl_a = pattern_from.xy;
let pattern_br_a = pattern_from.zw;
let pattern_tl_b = pattern_to.xy;
let pattern_br_b = pattern_to.zw;
let pixelRatio = paintParams.pixel_ratio;
let tileZoomRatio = drawable.tile_ratio;
let fromScale = props.from_scale;
let toScale = props.to_scale;
let display_size_a = vec2<f32>(
(pattern_br_a.x - pattern_tl_a.x) / pixelRatio,
(pattern_br_a.y - pattern_tl_a.y) / pixelRatio
);
let display_size_b = vec2<f32>(
(pattern_br_b.x - pattern_tl_b.x) / pixelRatio,
(pattern_br_b.y - pattern_tl_b.y) / pixelRatio
);
let pos = vec2<f32>(f32(in.position.x), f32(in.position.y));
let clip = drawable.matrix * vec4<f32>(pos, 0.0, 1.0);
let invW = 1.0 / clip.w;
let ndcXY = clip.xy * invW;
out.position = clip;
out.v_pos_a = get_pattern_pos(
drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
fromScale * display_size_a,
tileZoomRatio,
pos
);
out.v_pos_b = get_pattern_pos(
drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
toScale * display_size_b,
tileZoomRatio,
pos
);
out.v_pos = (ndcXY + vec2<f32>(1.0, 1.0)) * 0.5 * paintParams.world_size;
#ifndef HAS_UNIFORM_u_pattern_from
out.pattern_from = pattern_from;
#endif
#ifndef HAS_UNIFORM_u_pattern_to
out.pattern_to = pattern_to;
#endif
#ifndef HAS_UNIFORM_u_opacity
out.opacity = unpack_mix_float(in.opacity, drawable.opacity_t);
#endif
return out;
}
)";
static constexpr const char* fragment = R"(
struct FragmentInput {
@location(0) v_pos_a: vec2<f32>,
@location(1) v_pos_b: vec2<f32>,
@location(2) v_pos: vec2<f32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(3) pattern_from: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(4) pattern_to: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(5) opacity: f32,
#endif
};
struct FillOutlinePatternTilePropsUBO {
pattern_from: vec4<f32>,
pattern_to: vec4<f32>,
texsize: vec2<f32>,
pad1: f32,
pad2: f32,
};
struct FillEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(4) var<storage, read> tilePropsVector: array<FillOutlinePatternTilePropsUBO>;
@group(0) @binding(5) var<uniform> props: FillEvaluatedPropsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var pattern_texture: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let tileProps = tilePropsVector[globalIndex.value];
let pattern_from =
#ifdef HAS_UNIFORM_u_pattern_from
tileProps.pattern_from;
#else
in.pattern_from;
#endif
let pattern_to =
#ifdef HAS_UNIFORM_u_pattern_to
tileProps.pattern_to;
#else
in.pattern_to;
#endif
let pattern_tl_a = pattern_from.xy;
let pattern_br_a = pattern_from.zw;
let pattern_tl_b = pattern_to.xy;
let pattern_br_b = pattern_to.zw;
// Sample pattern A
let imagecoord_a = gl_mod(in.v_pos_a, vec2<f32>(1.0));
let pos_a = mix(pattern_tl_a / tileProps.texsize, pattern_br_a / tileProps.texsize, imagecoord_a);
let color_a = textureSample(pattern_texture, texture_sampler, pos_a);
// Sample pattern B
let imagecoord_b = gl_mod(in.v_pos_b, vec2<f32>(1.0));
let pos_b = mix(pattern_tl_b / tileProps.texsize, pattern_br_b / tileProps.texsize, imagecoord_b);
let color_b = textureSample(pattern_texture, texture_sampler, pos_b);
// Mix patterns and apply opacity
let opacity =
#ifdef HAS_UNIFORM_u_opacity
props.opacity;
#else
in.opacity;
#endif
return mix(color_a, color_b, props.fade) * opacity;
}
)";
};
template <>
struct ShaderSource<BuiltIn::FillOutlineTriangulatedShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "FillOutlineTriangulatedShader";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr const char* vertex = R"(
const SCALE: f32 = 0.015873016;
struct VertexInput {
@location(0) pos_normal: vec2<i32>,
@location(1) data: vec4<u32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) normal: vec2<f32>,
@location(1) width: f32,
@location(2) gamma_scale: f32,
};
struct FillOutlineTriangulatedDrawableUnionUBO {
matrix_col0: vec4<f32>,
matrix_col1: vec4<f32>,
matrix_col2: vec4<f32>,
matrix_col3: vec4<f32>,
params: vec4<f32>,
padding: vec4<f32>,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<FillOutlineTriangulatedDrawableUnionUBO>;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let matrix = mat4x4<f32>(drawable.matrix_col0,
drawable.matrix_col1,
drawable.matrix_col2,
drawable.matrix_col3);
let ratio = max(drawable.params.x, 1e-6);
let pos_normal = vec2<f32>(f32(in.pos_normal.x), f32(in.pos_normal.y));
let pos = floor(pos_normal * 0.5);
var normal = pos_normal - pos * 2.0;
normal.y = normal.y * 2.0 - 1.0;
let data = vec4<f32>(in.data);
let extrude = data.xy - vec2<f32>(128.0, 128.0);
let half_width = 0.5;
let antialiasing = 0.5 / paintParams.pixel_ratio;
let outset = half_width + antialiasing;
let dist = extrude * outset * SCALE;
let extrude_vec = dist / ratio;
let projected_extrude = matrix * vec4<f32>(extrude_vec, 0.0, 0.0);
let base = matrix * vec4<f32>(pos, 0.0, 1.0);
let clip = base + projected_extrude;
out.position = clip;
let extrude_length_without_perspective = length(dist);
let extrude_length_with_perspective = length((projected_extrude.xy / clip.w) * paintParams.units_to_pixels);
let gamma_denom = max(extrude_length_with_perspective, 1e-6);
out.normal = normal;
out.width = outset;
out.gamma_scale = extrude_length_without_perspective / gamma_denom;
return out;
}
)";
static constexpr const char* fragment = R"(
struct FragmentInput {
@location(0) normal: vec2<f32>,
@location(1) width: f32,
@location(2) gamma_scale: f32,
};
struct FillEvaluatedPropsUBO {
color: vec4<f32>,
outline_color: vec4<f32>,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(5) var<uniform> props: FillEvaluatedPropsUBO;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let dist = length(in.normal) * in.width;
let blur2 = (1.0 / paintParams.pixel_ratio) * in.gamma_scale;
let denom = max(blur2, 1e-6);
let alpha = clamp(min(dist + blur2, in.width - dist) / denom, 0.0, 1.0);
return props.outline_color * (alpha * props.opacity);
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,411 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/fill_extrusion_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::FillExtrusionShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "FillExtrusionShader";
static const std::array<AttributeInfo, 5> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(3) position: vec2<i32>,
@location(4) normal_ed: vec4<i32>,
#ifndef HAS_UNIFORM_u_color
@location(5) color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_base
@location(6) base: vec2<f32>,
#endif
#ifndef HAS_UNIFORM_u_height
@location(7) height: vec2<f32>,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
};
struct FillExtrusionDrawableUBO {
matrix: mat4x4<f32>,
pixel_coord_upper: vec2<f32>,
pixel_coord_lower: vec2<f32>,
height_factor: f32,
tile_ratio: f32,
base_t: f32,
height_t: f32,
color_t: f32,
pattern_from_t: f32,
pattern_to_t: f32,
pad1: f32,
};
struct FillExtrusionPropsUBO {
color: vec4<f32>,
light_color_pad: vec4<f32>,
light_position_base: vec4<f32>,
height: f32,
light_intensity: f32,
vertical_gradient: f32,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
pad2: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(2) var<storage, read> drawableVector: array<FillExtrusionDrawableUBO>;
@group(0) @binding(5) var<uniform> props: FillExtrusionPropsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
#ifndef HAS_UNIFORM_u_base
let baseValue = max(unpack_mix_float(in.base, drawable.base_t), 0.0);
#else
let baseValue = max(props.light_position_base.w, 0.0);
#endif
#ifndef HAS_UNIFORM_u_height
let heightValue = max(unpack_mix_float(in.height, drawable.height_t), 0.0);
#else
let heightValue = max(props.height, 0.0);
#endif
let normal_i = vec3<f32>(f32(in.normal_ed.x), f32(in.normal_ed.y), f32(in.normal_ed.z));
let t = glMod(normal_i.x, 2.0);
let z = select(baseValue, heightValue, t != 0.0);
out.position = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), z, 1.0);
#ifdef OVERDRAW_INSPECTOR
out.color = vec4<f32>(1.0, 1.0, 1.0, 1.0);
return out;
#endif
#ifndef HAS_UNIFORM_u_color
var color = unpack_mix_color(in.color, drawable.color_t);
#else
var color = props.color;
#endif
color = color + min(vec4<f32>(0.03, 0.03, 0.03, 1.0), vec4<f32>(1.0));
let luminance = dot(color.rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
let unitNormal = normal_i / 16384.0;
let directionalFraction = clamp(dot(unitNormal, props.light_position_base.xyz), 0.0, 1.0);
let minDirectional = 1.0 - props.light_intensity;
let maxDirectional = max(1.0 - luminance + props.light_intensity, 1.0);
var directional = mix(minDirectional, maxDirectional, directionalFraction);
if (normal_i.y != 0.0) {
let gradientMin = mix(0.7, 0.98, 1.0 - props.light_intensity);
let factor = clamp((t + baseValue) * pow(heightValue / 150.0, 0.5), gradientMin, 1.0);
directional *= (1.0 - props.vertical_gradient) + props.vertical_gradient * factor;
}
let lightColor = props.light_color_pad.xyz;
let minLight = mix(vec3<f32>(0.0), vec3<f32>(0.3), 1.0 - lightColor);
let lit = clamp(color.rgb * directional * lightColor, minLight, vec3<f32>(1.0));
var vcolor = vec4<f32>(0.0, 0.0, 0.0, 1.0);
vcolor += vec4<f32>(lit, 0.0);
out.color = vcolor * props.opacity;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) color: vec4<f32>,
};
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
return in.color;
}
)";
};
template <>
struct ShaderSource<BuiltIn::FillExtrusionPatternShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "FillExtrusionPatternShader";
static const std::array<AttributeInfo, 6> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(3) position: vec2<i32>,
@location(4) normal_ed: vec4<i32>,
#ifndef HAS_UNIFORM_u_base
@location(5) base: vec2<f32>,
#endif
#ifndef HAS_UNIFORM_u_height
@location(6) height: vec2<f32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_from
@location(7) pattern_from: vec4<u32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(8) pattern_to: vec4<u32>,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) lighting: vec4<f32>,
@location(1) pos_a: vec2<f32>,
@location(2) pos_b: vec2<f32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(3) pattern_from: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(4) pattern_to: vec4<f32>,
#endif
};
struct FillExtrusionPatternDrawableUBO {
matrix: mat4x4<f32>,
pixel_coord_upper: vec2<f32>,
pixel_coord_lower: vec2<f32>,
height_factor: f32,
tile_ratio: f32,
base_t: f32,
height_t: f32,
pattern_from_t: f32,
pattern_to_t: f32,
pad1: f32,
pad2: f32,
};
struct FillExtrusionPatternTilePropsUBO {
pattern_from: vec4<f32>,
pattern_to: vec4<f32>,
texsize: vec2<f32>,
pad1: f32,
pad2: f32,
};
struct FillExtrusionPropsUBO {
color: vec4<f32>,
light_color_pad: vec4<f32>,
light_position_base: vec4<f32>,
height: f32,
light_intensity: f32,
vertical_gradient: f32,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
pad2: f32,
};
struct GlobalPaintParamsUBO {
pixel_ratio: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<FillExtrusionPatternDrawableUBO>;
@group(0) @binding(4) var<storage, read> tilePropsVector: array<FillExtrusionPatternTilePropsUBO>;
@group(0) @binding(5) var<uniform> props: FillExtrusionPropsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let index = globalIndex.value;
let drawable = drawableVector[index];
#ifndef HAS_UNIFORM_u_base
let baseValue = max(unpack_mix_float(in.base, drawable.base_t), 0.0);
#else
let baseValue = max(props.light_position_base.w, 0.0);
#endif
#ifndef HAS_UNIFORM_u_height
let heightValue = max(unpack_mix_float(in.height, drawable.height_t), 0.0);
#else
let heightValue = max(props.height, 0.0);
#endif
let normal_i = vec3<f32>(f32(in.normal_ed.x), f32(in.normal_ed.y), f32(in.normal_ed.z));
let edgedistance = f32(in.normal_ed.w);
let t = glMod(normal_i.x, 2.0);
let z = select(baseValue, heightValue, t != 0.0);
out.position = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), z, 1.0);
var patternPos: vec2<f32>;
if (normal_i.x == 1.0 && normal_i.y == 0.0 && normal_i.z == 16384.0) {
patternPos = vec2<f32>(f32(in.position.x), f32(in.position.y));
} else {
patternPos = vec2<f32>(edgedistance, z * drawable.height_factor);
}
let pixelRatio = paintParams.pixel_ratio;
let tileZoomRatio = drawable.tile_ratio;
let fromScale = props.from_scale;
let toScale = props.to_scale;
#ifdef HAS_UNIFORM_u_pattern_from
let patternFrom = tilePropsVector[index].pattern_from;
#else
let patternFrom = vec4<f32>(in.pattern_from);
out.pattern_from = patternFrom;
#endif
#ifdef HAS_UNIFORM_u_pattern_to
let patternTo = tilePropsVector[index].pattern_to;
#else
let patternTo = vec4<f32>(in.pattern_to);
out.pattern_to = patternTo;
#endif
let pattern_tl_a = patternFrom.xy;
let pattern_br_a = patternFrom.zw;
let pattern_tl_b = patternTo.xy;
let pattern_br_b = patternTo.zw;
let display_size_a = vec2<f32>((pattern_br_a.x - pattern_tl_a.x) / pixelRatio,
(pattern_br_a.y - pattern_tl_a.y) / pixelRatio);
let display_size_b = vec2<f32>((pattern_br_b.x - pattern_tl_b.x) / pixelRatio,
(pattern_br_b.y - pattern_tl_b.y) / pixelRatio);
var lighting = vec4<f32>(0.0, 0.0, 0.0, 1.0);
var directional = clamp(dot(normal_i / 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_i.y != 0.0) {
let gradientMin = mix(0.7, 0.98, 1.0 - props.light_intensity);
let factor = clamp((t + baseValue) * pow(heightValue / 150.0, 0.5), gradientMin, 1.0);
directional *= (1.0 - props.vertical_gradient) + props.vertical_gradient * factor;
}
let lightColor = props.light_color_pad.xyz;
let lit = clamp(directional * lightColor,
mix(vec3<f32>(0.0), vec3<f32>(0.3), 1.0 - lightColor),
vec3<f32>(1.0));
lighting = vec4<f32>(lighting.rgb + lit, lighting.a);
lighting *= props.opacity;
out.lighting = lighting;
out.pos_a = get_pattern_pos(drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
fromScale * display_size_a,
tileZoomRatio,
patternPos);
out.pos_b = get_pattern_pos(drawable.pixel_coord_upper,
drawable.pixel_coord_lower,
toScale * display_size_b,
tileZoomRatio,
patternPos);
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) lighting: vec4<f32>,
@location(1) pos_a: vec2<f32>,
@location(2) pos_b: vec2<f32>,
#ifndef HAS_UNIFORM_u_pattern_from
@location(3) pattern_from: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_pattern_to
@location(4) pattern_to: vec4<f32>,
#endif
};
struct FillExtrusionPatternTilePropsUBO {
pattern_from: vec4<f32>,
pattern_to: vec4<f32>,
texsize: vec2<f32>,
pad1: f32,
pad2: f32,
};
struct FillExtrusionPropsUBO {
color: vec4<f32>,
light_color_pad: vec4<f32>,
light_position_base: vec4<f32>,
height: f32,
light_intensity: f32,
vertical_gradient: f32,
opacity: f32,
fade: f32,
from_scale: f32,
to_scale: f32,
pad2: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(4) var<storage, read> tilePropsVector: array<FillExtrusionPatternTilePropsUBO>;
@group(0) @binding(5) var<uniform> props: FillExtrusionPropsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var pattern_texture: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let tileProps = tilePropsVector[globalIndex.value];
#ifdef HAS_UNIFORM_u_pattern_from
let pattern_from = tileProps.pattern_from;
#else
let pattern_from = in.pattern_from;
#endif
#ifdef HAS_UNIFORM_u_pattern_to
let pattern_to = tileProps.pattern_to;
#else
let pattern_to = in.pattern_to;
#endif
let pattern_tl_a = pattern_from.xy;
let pattern_br_a = pattern_from.zw;
let pattern_tl_b = pattern_to.xy;
let pattern_br_b = pattern_to.zw;
let imagecoord_a = gl_mod(in.pos_a, vec2<f32>(1.0));
let pos_a = mix(pattern_tl_a / tileProps.texsize, pattern_br_a / tileProps.texsize, imagecoord_a);
let color_a = textureSample(pattern_texture, texture_sampler, pos_a);
let imagecoord_b = gl_mod(in.pos_b, vec2<f32>(1.0));
let pos_b = mix(pattern_tl_b / tileProps.texsize, pattern_br_b / tileProps.texsize, imagecoord_b);
let color_b = textureSample(pattern_texture, texture_sampler, pos_b);
let pattern_color = mix(color_a, color_b, props.fade);
return pattern_color * in.lighting;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,127 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/heatmap_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::HeatmapShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "HeatmapShader";
static const std::array<AttributeInfo, 3> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(0) position: vec2<i32>,
#ifndef HAS_UNIFORM_u_weight
@location(1) weight: vec2<f32>,
#endif
#ifndef HAS_UNIFORM_u_radius
@location(2) radius: vec2<f32>,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) weight: f32,
@location(1) extrude: vec2<f32>,
};
struct HeatmapDrawableUBO {
matrix: mat4x4<f32>,
extrude_scale: f32,
weight_t: f32,
radius_t: f32,
pad1: f32,
};
struct HeatmapEvaluatedPropsUBO {
weight: f32,
radius: f32,
intensity: f32,
padding: f32,
};
struct GlobalIndexUBO {
value: u32,
pad: vec3<u32>,
};
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<HeatmapDrawableUBO>;
@group(0) @binding(4) var<uniform> props: HeatmapEvaluatedPropsUBO;
const ZERO: f32 = 1.0 / 255.0 / 16.0;
const GAUSS_COEF: f32 = 0.3989422804014327;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
#ifdef HAS_UNIFORM_u_weight
let weight = props.weight;
#else
let weight = unpack_mix_float(in.weight, drawable.weight_t);
#endif
#ifdef HAS_UNIFORM_u_radius
let radius = props.radius;
#else
let radius = unpack_mix_float(in.radius, drawable.radius_t);
#endif
let pos = vec2<f32>(f32(in.position.x), f32(in.position.y));
let unscaled_extrude = fma(vec2<f32>(f32(in.position.x & 1), f32(in.position.y & 1)), vec2<f32>(2.0, 2.0),
vec2<f32>(-1.0, -1.0));
let S = sqrt(-2.0 * log(ZERO / (max(weight, ZERO) * max(props.intensity, ZERO) * GAUSS_COEF))) / 3.0;
let extrude = S * unscaled_extrude;
let scaled_extrude = extrude * radius * drawable.extrude_scale;
let base = floor(pos * 0.5);
out.position = drawable.matrix * vec4<f32>(base + scaled_extrude, 0.0, 1.0);
out.weight = weight;
out.extrude = extrude;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) weight: f32,
@location(1) extrude: vec2<f32>,
};
struct HeatmapEvaluatedPropsUBO {
weight: f32,
radius: f32,
intensity: f32,
padding: f32,
};
const GAUSS_COEF: f32 = 0.3989422804014327;
@group(0) @binding(4) var<uniform> props: HeatmapEvaluatedPropsUBO;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
#ifdef OVERDRAW_INSPECTOR
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
#else
let d = -0.5 * 3.0 * 3.0 * dot(in.extrude, in.extrude);
let val = in.weight * props.intensity * GAUSS_COEF * exp(d);
return vec4<f32>(val, 1.0, 1.0, 1.0);
#endif
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,87 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/heatmap_texture_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::HeatmapTextureShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "HeatmapTextureShader";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 2> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(5) position: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex_coord: vec2<f32>,
};
struct HeatmapTextureUBO {
matrix: mat4x4<f32>,
opacity: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
// remaining fields unused
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(4) var<uniform> ubo: HeatmapTextureUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let quad_pos = vec2<f32>(f32(in.position.x), f32(in.position.y));
let scaled = quad_pos * paintParams.world_size;
out.position = ubo.matrix * vec4<f32>(scaled, 0.0, 1.0);
out.tex_coord = quad_pos;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) tex_coord: vec2<f32>,
};
struct HeatmapTextureUBO {
matrix: mat4x4<f32>,
opacity: f32,
pad1: f32,
pad2: f32,
pad3: f32,
};
@group(0) @binding(4) var<uniform> ubo: HeatmapTextureUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var heatmap_texture: texture_2d<f32>;
@group(1) @binding(2) var color_ramp_texture: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let t = textureSample(heatmap_texture, texture_sampler, in.tex_coord).r;
let color = textureSample(color_ramp_texture, texture_sampler, vec2<f32>(t, 0.5));
return color * ubo.opacity;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,122 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/hillshade_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::HillshadeShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "HillshadeShader";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(5) position: vec2<i32>,
@location(6) texcoord: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex_coord: vec2<f32>,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
struct HillshadeDrawableUBO {
matrix: mat4x4<f32>,
};
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<HillshadeDrawableUBO>;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
out.position = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
var tex = vec2<f32>(f32(in.texcoord.x), f32(in.texcoord.y)) / 8192.0;
tex.y = 1.0 - tex.y;
out.tex_coord = tex;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) tex_coord: vec2<f32>,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
struct HillshadeTilePropsUBO {
latrange: vec2<f32>,
light: vec2<f32>,
};
struct HillshadeEvaluatedPropsUBO {
highlight: vec4<f32>,
shadow: vec4<f32>,
accent: vec4<f32>,
};
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(3) var<storage, read> tilePropsVector: array<HillshadeTilePropsUBO>;
@group(0) @binding(4) var<uniform> props: HillshadeEvaluatedPropsUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var hillshade_texture: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
#ifdef OVERDRAW_INSPECTOR
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
#endif
let tileProps = tilePropsVector[globalIndex.value];
let pixel = textureSample(hillshade_texture, texture_sampler, in.tex_coord);
let deriv = pixel.rg * 2.0 - vec2<f32>(1.0, 1.0);
let latRange = tileProps.latrange;
let latitude = (latRange.x - latRange.y) * in.tex_coord.y + latRange.y;
let scaleFactor = cos(radians(latitude));
let slope = atan(1.25 * length(deriv) / scaleFactor);
let aspectDefault = 0.5 * PI * select(-1.0, 1.0, deriv.y > 0.0);
let aspect = select(aspectDefault, atan2(deriv.y, -deriv.x), deriv.x != 0.0);
let intensity = tileProps.light.x;
let azimuth = tileProps.light.y + PI;
let base = 1.875 - intensity * 1.75;
let maxValue = 0.5 * PI;
let denom = pow(base, maxValue) - 1.0;
let useLinear = abs(intensity - 0.5) < 1e-6;
let scaledSlope = select(((pow(base, slope) - 1.0) / denom) * maxValue,
slope,
useLinear);
let accent = cos(scaledSlope);
let accentColor = (1.0 - accent) * props.accent * clamp(intensity * 2.0, 0.0, 1.0);
let shade = abs(glMod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0);
let shadeColor = mix(props.shadow, props.highlight, shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0);
let color = accentColor * (1.0 - shadeColor.a) + shadeColor;
return color;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,117 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/hillshade_prepare_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::HillshadePrepareShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "HillshadePrepareShader";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(5) position: vec2<i32>,
@location(6) texcoord: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex_coord: vec2<f32>,
};
struct HillshadePrepareDrawableUBO {
matrix: mat4x4<f32>,
};
struct HillshadePrepareTilePropsUBO {
unpack: vec4<f32>,
dimension: vec2<f32>,
zoom: f32,
maxzoom: f32,
};
@group(0) @binding(2) var<uniform> drawable: HillshadePrepareDrawableUBO;
@group(0) @binding(4) var<uniform> tileProps: HillshadePrepareTilePropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
let epsilon = vec2<f32>(1.0, 1.0) / tileProps.dimension;
let scale = (tileProps.dimension.x - 2.0) / tileProps.dimension.x;
out.tex_coord = vec2<f32>(f32(in.texcoord.x), f32(in.texcoord.y)) / 8192.0 * scale + epsilon;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) tex_coord: vec2<f32>,
};
struct HillshadePrepareTilePropsUBO {
unpack: vec4<f32>,
dimension: vec2<f32>,
zoom: f32,
maxzoom: f32,
};
@group(0) @binding(4) var<uniform> tileProps: HillshadePrepareTilePropsUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var dem_texture: texture_2d<f32>;
fn getElevation(coord: vec2<f32>) -> f32 {
var data = textureSample(dem_texture, texture_sampler, coord) * 255.0;
data.a = -1.0;
return dot(data, tileProps.unpack) / 4.0;
}
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
#ifdef OVERDRAW_INSPECTOR
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
#endif
let epsilon = vec2<f32>(1.0, 1.0) / tileProps.dimension;
let a = getElevation(in.tex_coord + vec2<f32>(-epsilon.x, -epsilon.y));
let b = getElevation(in.tex_coord + vec2<f32>(0.0, -epsilon.y));
let c = getElevation(in.tex_coord + vec2<f32>(epsilon.x, -epsilon.y));
let d = getElevation(in.tex_coord + vec2<f32>(-epsilon.x, 0.0));
let f = getElevation(in.tex_coord + vec2<f32>(epsilon.x, 0.0));
let g = getElevation(in.tex_coord + vec2<f32>(-epsilon.x, epsilon.y));
let h = getElevation(in.tex_coord + vec2<f32>(0.0, epsilon.y));
let i = getElevation(in.tex_coord + vec2<f32>(epsilon.x, epsilon.y));
var exaggeration = 0.3;
if (tileProps.zoom < 4.5) {
exaggeration = 0.35;
}
if (tileProps.zoom < 2.0) {
exaggeration = 0.4;
}
let denom = pow(2.0, (tileProps.zoom - tileProps.maxzoom) * exaggeration + 19.2562 - tileProps.zoom);
let deriv = vec2<f32>((c + f + f + i) - (a + d + d + g),
(g + h + h + i) - (a + b + b + c)) / denom;
let color = clamp(vec4<f32>(deriv.x / 2.0 + 0.5,
deriv.y / 2.0 + 0.5,
1.0,
1.0),
vec4<f32>(0.0, 0.0, 0.0, 0.0),
vec4<f32>(1.0, 1.0, 1.0, 1.0));
return color;
}
)";
};
} // namespace shaders
} // namespace mbgl

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/location_indicator_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::LocationIndicatorShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "LocationIndicatorShader";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 0> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(5) position: vec2<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
};
struct LocationIndicatorUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> ubo: LocationIndicatorUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = ubo.matrix * vec4<f32>(in.position, 0.0, 1.0);
return out;
}
)";
static constexpr auto fragment = R"(
struct LocationIndicatorUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> ubo: LocationIndicatorUBO;
@fragment
fn main(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
return ubo.color;
}
)";
};
template <>
struct ShaderSource<BuiltIn::LocationIndicatorTexturedShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "LocationIndicatorTexturedShader";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(5) position: vec2<f32>,
@location(6) uv: vec2<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
};
struct LocationIndicatorUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> ubo: LocationIndicatorUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.position = ubo.matrix * vec4<f32>(in.position, 0.0, 1.0);
out.uv = in.uv;
return out;
}
)";
static constexpr auto fragment = R"(
struct LocationIndicatorUBO {
matrix: mat4x4<f32>,
color: vec4<f32>,
};
@group(0) @binding(0) var<uniform> ubo: LocationIndicatorUBO;
@group(0) @binding(1) var colorTexture: texture_2d<f32>;
@group(0) @binding(2) var colorSampler: sampler;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let textureColor = textureSample(colorTexture, colorSampler, uv);
return textureColor * ubo.color;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,134 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/raster_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::RasterShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "RasterShader";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 2> textures;
static constexpr auto vertex = R"(
struct VertexInput {
@location(0) position: vec2<i32>,
@location(1) texcoord: vec2<i32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) pos0: vec2<f32>,
@location(1) pos1: vec2<f32>,
};
struct RasterDrawableUBO {
matrix: mat4x4<f32>,
};
struct RasterEvaluatedPropsUBO {
spin_weights: vec4<f32>,
tl_parent: vec2<f32>,
scale_parent: f32,
buffer_scale: f32,
fade_t: f32,
opacity: f32,
brightness_low: f32,
brightness_high: f32,
saturation_factor: f32,
contrast_factor: f32,
pad1: f32,
pad2: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<RasterDrawableUBO>;
@group(0) @binding(4) var<uniform> props: RasterEvaluatedPropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let clip = drawable.matrix * vec4<f32>(f32(in.position.x), f32(in.position.y), 0.0, 1.0);
out.position = clip;
let tex = vec2<f32>(f32(in.texcoord.x), f32(in.texcoord.y));
let pos0 = (((tex / 8192.0) - vec2<f32>(0.5, 0.5)) / props.buffer_scale) + vec2<f32>(0.5, 0.5);
out.pos0 = pos0;
out.pos1 = pos0 * props.scale_parent + props.tl_parent;
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) pos0: vec2<f32>,
@location(1) pos1: vec2<f32>,
};
struct RasterEvaluatedPropsUBO {
spin_weights: vec4<f32>,
tl_parent: vec2<f32>,
scale_parent: f32,
buffer_scale: f32,
fade_t: f32,
opacity: f32,
brightness_low: f32,
brightness_high: f32,
saturation_factor: f32,
contrast_factor: f32,
pad1: f32,
pad2: f32,
};
@group(0) @binding(4) var<uniform> props: RasterEvaluatedPropsUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var image0: texture_2d<f32>;
@group(1) @binding(2) var image1: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
var color0 = textureSample(image0, texture_sampler, in.pos0);
var color1 = textureSample(image1, texture_sampler, in.pos1);
if (color0.a > 0.0) {
color0 = vec4<f32>(color0.rgb / color0.a, color0.a);
}
if (color1.a > 0.0) {
color1 = vec4<f32>(color1.rgb / color1.a, color1.a);
}
var color = mix(color0, color1, props.fade_t);
color.a = color.a * props.opacity;
var rgb = color.rgb;
let spin = props.spin_weights;
rgb = vec3<f32>(dot(rgb, spin.xyz),
dot(rgb, spin.zxy),
dot(rgb, spin.yzx));
let average = (color.r + color.g + color.b) / 3.0;
rgb = rgb + (average - rgb) * props.saturation_factor;
rgb = (rgb - vec3<f32>(0.5, 0.5, 0.5)) * props.contrast_factor + vec3<f32>(0.5, 0.5, 0.5);
let high_vec = vec3<f32>(props.brightness_low, props.brightness_low, props.brightness_low);
let low_vec = vec3<f32>(props.brightness_high, props.brightness_high, props.brightness_high);
let final_rgb = mix(high_vec, low_vec, rgb) * color.a;
return vec4<f32>(final_rgb, color.a);
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,109 @@
#pragma once
#include <mbgl/gfx/shader_group.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/webgpu/common.hpp>
#include <mbgl/shaders/webgpu/wgsl_preprocessor.hpp>
#include <mbgl/shaders/program_parameters.hpp>
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/util/hash.hpp>
#include <mbgl/util/containers.hpp>
#include <mbgl/webgpu/context.hpp>
#include <string>
#include <type_traits>
#include <unordered_map>
namespace mbgl {
namespace webgpu {
class ShaderGroupBase : public gfx::ShaderGroup {
protected:
ShaderGroupBase(const ProgramParameters& parameters_)
: programParameters(parameters_) {}
using DefinesMap = mbgl::unordered_map<std::string, std::string>;
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 <shaders::BuiltIn ShaderID>
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<ShaderID, gfx::Backend::Type::WebGPU>;
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<webgpu::ShaderProgram>(shaderName);
if (!shader) {
DefinesMap additionalDefines;
addAdditionalDefines(propertiesAsUniforms, additionalDefines);
std::string vertexSource;
std::string fragmentSource;
if constexpr (requires { ShaderSource::prelude; }) {
vertexSource = std::string(ShaderSource::prelude) + vert;
fragmentSource = std::string(ShaderSource::prelude) + frag;
} else {
vertexSource = vert;
fragmentSource = frag;
}
std::unordered_map<std::string, bool> defineSet;
const auto& programDefines = programParameters.getDefines();
for (const auto& entry : programDefines) {
defineSet.emplace(entry.first, true);
}
for (const auto& entry : additionalDefines) {
defineSet.emplace(entry.first, true);
}
vertexSource = detail::preprocessWGSL(vertexSource, defineSet);
fragmentSource = detail::preprocessWGSL(fragmentSource, defineSet);
auto& context = static_cast<Context&>(gfxContext);
shader = std::make_shared<ShaderProgram>(context,
vertexSource,
fragmentSource,
ShaderSource::attributes,
ShaderSource::textures,
std::string(name));
assert(shader);
if (!shader || !registerShader(shader, shaderName)) {
assert(false);
throw std::runtime_error("Failed to register " + shaderName + " with shader group!");
}
}
return shader;
}
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,169 @@
#pragma once
#include <mbgl/shaders/shader_program_base.hpp>
#include <webgpu/webgpu.h>
#include <mbgl/gfx/gfx_types.hpp>
#include <mbgl/gfx/attribute.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/gfx/stencil_mode.hpp>
#include <string>
#include <unordered_map>
#include <array>
#include <optional>
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 webgpu {
class Context;
class RendererBackend;
class ShaderProgram final : public gfx::ShaderProgramBase {
public:
// Static name member required by is_shader_v trait
static constexpr std::string_view Name = "WebGPUShader";
enum class BindingType {
UniformBuffer,
ReadOnlyStorageBuffer,
StorageBuffer,
Sampler,
Texture
};
struct BindingInfo {
uint32_t group = 0;
uint32_t binding = 0;
BindingType type = BindingType::UniformBuffer;
WGPUShaderStage visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
};
ShaderProgram(std::string name,
RendererBackend& backend,
WGPUShaderModule vertexModule,
WGPUShaderModule fragmentModule);
ShaderProgram(Context& context, const std::string& vertexSource, const std::string& fragmentSource);
template <size_t N, size_t M>
ShaderProgram(Context& context,
const std::string& vertexSource,
const std::string& fragmentSource,
const std::array<shaders::AttributeInfo, N>& attrs,
const std::array<shaders::TextureInfo, M>& texts,
const std::string& name = "ShaderProgram")
: ShaderProgram(context, vertexSource, fragmentSource) {
if (name != "ShaderProgram") {
shaderName = name;
}
for (const auto& attr : attrs) {
initAttribute(attr);
}
for (const auto& tex : texts) {
initTexture(tex);
}
}
~ShaderProgram() override;
WGPURenderPipeline getRenderPipeline(const gfx::Renderable& renderable,
const WGPUVertexBufferLayout* vertexLayouts,
uint32_t vertexLayoutCount,
const gfx::ColorMode& colorMode,
const gfx::DepthMode& depthMode,
const gfx::StencilMode& stencilMode,
gfx::DrawModeType drawModeType,
const std::optional<std::size_t> reuseHash = std::nullopt);
const std::vector<BindingInfo>& getBindingInfos() const { return bindingInfos; }
const std::vector<BindingInfo>& getBindingInfosForGroup(uint32_t group) const;
WGPUBindGroupLayout getBindGroupLayout(uint32_t group) const;
const std::vector<WGPUBindGroupLayout>& getBindGroupLayouts() const { return bindGroupLayouts; }
const std::vector<uint32_t>& getBindGroupOrder() const { return bindGroupOrder; }
const std::string_view typeName() const noexcept override { return Name; }
std::optional<size_t> getSamplerLocation(const size_t id) const override;
const gfx::VertexAttributeArray& getVertexAttributes() const override { return vertexAttributes; }
const gfx::VertexAttributeArray& getInstanceAttributes() const override { return instanceAttributes; }
void initAttribute(const shaders::AttributeInfo& info);
void initInstanceAttribute(const shaders::AttributeInfo& info);
void initTexture(const shaders::TextureInfo& info);
protected:
void createPipelineLayout(const std::string& vertexSource, const std::string& fragmentSource);
WGPURenderPipeline createPipeline(const WGPUVertexBufferLayout* vertexLayouts,
uint32_t vertexLayoutCount,
const gfx::ColorMode& colorMode,
const gfx::DepthMode& depthMode,
const gfx::StencilMode& stencilMode,
gfx::DrawModeType drawModeType);
void analyzeShaderBindings(const std::string& source, WGPUShaderStage stage);
void rebuildBindGroupLayouts();
std::string shaderName;
RendererBackend& backend;
WGPUShaderModule vertexShaderModule = nullptr;
WGPUShaderModule fragmentShaderModule = nullptr;
std::vector<WGPUBindGroupLayout> bindGroupLayouts;
std::vector<uint32_t> bindGroupOrder;
WGPUPipelineLayout pipelineLayout = nullptr;
mutable std::unordered_map<std::size_t, WGPURenderPipeline> renderPipelineCache;
gfx::VertexAttributeArray vertexAttributes;
gfx::VertexAttributeArray instanceAttributes;
std::array<std::optional<size_t>, shaders::maxTextureCountPerShader> textureBindings;
std::vector<BindingInfo> bindingInfos;
std::vector<std::vector<BindingInfo>> bindingsPerGroup;
bool hasVertexEntryPoint = true;
bool hasFragmentEntryPoint = true;
bool loggedMissingEntryPoint = false;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,752 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/symbol_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
constexpr auto symbolShaderPrelude = R"(
// Symbol shader UBO indices
const idGlobalPaintParamsUBO = 0u;
const idGlobalUBOIndex = 1u;
const idSymbolDrawableUBO = 2u;
const idSymbolTilePropsUBO = 3u;
const idSymbolEvaluatedPropsUBO = 4u;
const symbolUBOCount = 5u;
struct GlobalPaintParamsUBO {
pattern_atlas_texsize: vec2<f32>,
units_to_pixels: vec2<f32>,
world_size: vec2<f32>,
camera_to_center_distance: f32,
symbol_fade_change: f32,
aspect_ratio: f32,
pixel_ratio: f32,
map_zoom: f32,
pad1: f32,
};
struct SymbolDrawableUBO {
matrix: mat4x4<f32>,
label_plane_matrix: mat4x4<f32>,
coord_matrix: mat4x4<f32>,
texsize: vec2<f32>,
texsize_icon: vec2<f32>,
is_text_prop: u32,
rotate_symbol: u32,
pitch_with_map: u32,
is_size_zoom_constant: u32,
is_size_feature_constant: u32,
size_t: f32,
size: f32,
// Interpolations
fill_color_t: f32,
halo_color_t: f32,
opacity_t: f32,
halo_width_t: f32,
halo_blur_t: f32,
};
struct SymbolTilePropsUBO {
is_text: u32,
is_halo: u32,
gamma_scale: f32,
pad1: f32,
};
struct SymbolEvaluatedPropsUBO {
text_fill_color: vec4<f32>,
text_halo_color: vec4<f32>,
text_opacity: f32,
text_halo_width: f32,
text_halo_blur: f32,
pad1: f32,
icon_fill_color: vec4<f32>,
icon_halo_color: vec4<f32>,
icon_opacity: f32,
icon_halo_width: f32,
icon_halo_blur: f32,
pad2: f32,
};
struct GlobalIndexUBO {
value: u32,
pad0: vec3<u32>,
};
const c_offscreen_degenerate_triangle_location: f32 = -2.0;
)";
template <>
struct ShaderSource<BuiltIn::SymbolIconShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "SymbolIconShader";
static const std::array<AttributeInfo, 6> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto prelude = symbolShaderPrelude;
static constexpr auto vertex = R"()"
R"(
struct VertexInput {
@location(3) pos_offset: vec4<i32>,
@location(4) data: vec4<u32>,
@location(5) pixeloffset: vec4<i32>,
@location(6) projected_pos: vec3<f32>,
@location(7) fade_opacity: f32,
#ifndef HAS_UNIFORM_u_opacity
@location(8) opacity: f32,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex: vec2<f32>,
@location(1) fade_opacity: f32,
#ifndef HAS_UNIFORM_u_opacity
@location(2) opacity: f32,
#endif
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<SymbolDrawableUBO>;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let raw_fade_opacity = unpack_opacity(in.fade_opacity);
let fade_change = select(-paintParams.symbol_fade_change, paintParams.symbol_fade_change, raw_fade_opacity.y > 0.5);
let fade_opacity = max(0.0, min(1.0, raw_fade_opacity.x + fade_change));
#ifdef HAS_UNIFORM_u_opacity
let final_opacity = fade_opacity;
#else
let paint_opacity = unpack_mix_float(vec2<f32>(in.opacity, in.opacity), drawable.opacity_t);
let final_opacity = paint_opacity * fade_opacity;
#endif
// Cull vertices with zero opacity
if (final_opacity == 0.0) {
out.position = vec4<f32>(c_offscreen_degenerate_triangle_location,
c_offscreen_degenerate_triangle_location,
c_offscreen_degenerate_triangle_location, 1.0);
return out;
}
let a_pos = vec2<f32>(f32(in.pos_offset.x), f32(in.pos_offset.y));
let a_offset = vec2<f32>(f32(in.pos_offset.z), f32(in.pos_offset.w));
let a_tex = vec2<f32>(f32(in.data.x), f32(in.data.y));
let a_size = vec2<f32>(f32(in.data.z), f32(in.data.w));
let a_size_min = floor(a_size.x * 0.5);
let a_pxoffset = vec2<f32>(f32(in.pixeloffset.x), f32(in.pixeloffset.y));
let a_minFontScale = vec2<f32>(f32(in.pixeloffset.z), f32(in.pixeloffset.w)) / 256.0;
let segment_angle = -in.projected_pos.z;
let size_zoom_constant = drawable.is_size_zoom_constant != 0u;
let size_feature_constant = drawable.is_size_feature_constant != 0u;
var size: f32;
if (!size_zoom_constant && !size_feature_constant) {
size = mix(a_size_min, a_size.y, drawable.size_t) / 128.0;
} else if (size_zoom_constant && !size_feature_constant) {
size = a_size_min / 128.0;
} else {
size = drawable.size;
}
let projectedPoint = drawable.matrix * vec4<f32>(a_pos, 0.0, 1.0);
let camera_to_anchor_distance = projectedPoint.w;
let pitch_with_map = drawable.pitch_with_map != 0u;
let distance_ratio = select(
paintParams.camera_to_center_distance / camera_to_anchor_distance,
camera_to_anchor_distance / paintParams.camera_to_center_distance,
pitch_with_map
);
let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0);
size *= perspective_ratio;
let is_text = drawable.is_text_prop != 0u;
let fontScale = select(size, size / 24.0, is_text);
var symbol_rotation = 0.0;
if (drawable.rotate_symbol != 0u) {
let offsetProjectedPoint = drawable.matrix * vec4<f32>(a_pos + vec2<f32>(1.0, 0.0), 0.0, 1.0);
let a = projectedPoint.xy / projectedPoint.w;
let b = offsetProjectedPoint.xy / offsetProjectedPoint.w;
symbol_rotation = atan2((b.y - a.y) / paintParams.aspect_ratio, b.x - a.x);
}
let angle_sin = sin(segment_angle + symbol_rotation);
let angle_cos = cos(segment_angle + symbol_rotation);
let rotation_matrix = mat2x2<f32>(angle_cos, -angle_sin, angle_sin, angle_cos);
let projected_pos = drawable.label_plane_matrix * vec4<f32>(in.projected_pos.xy, 0.0, 1.0);
let pos0 = projected_pos.xy / projected_pos.w;
let posOffset = a_offset * max(a_minFontScale, vec2<f32>(fontScale)) / 32.0 + a_pxoffset / 16.0;
let position = drawable.coord_matrix * vec4<f32>(pos0 + rotation_matrix * posOffset, 0.0, 1.0);
out.position = position;
out.tex = a_tex / drawable.texsize;
out.fade_opacity = fade_opacity;
#ifndef HAS_UNIFORM_u_opacity
out.opacity = final_opacity;
#endif
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) tex: vec2<f32>,
@location(1) fade_opacity: f32,
#ifndef HAS_UNIFORM_u_opacity
@location(2) opacity: f32,
#endif
};
@group(0) @binding(3) var<storage, read> tilePropsVector: array<SymbolTilePropsUBO>;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(4) var<uniform> props: SymbolEvaluatedPropsUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var image: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let tileProps = tilePropsVector[globalIndex.value];
#ifdef HAS_UNIFORM_u_opacity
let base_opacity = select(props.icon_opacity, props.text_opacity, tileProps.is_text != 0u);
let opacity = base_opacity * in.fade_opacity;
#else
let opacity = select(props.icon_opacity, props.text_opacity, tileProps.is_text != 0u) * in.opacity;
#endif
return textureSample(image, texture_sampler, in.tex) * opacity;
}
)";
};
template <>
struct ShaderSource<BuiltIn::SymbolSDFShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "SymbolSDFShader";
static const std::array<AttributeInfo, 10> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 1> textures;
static constexpr auto prelude = symbolShaderPrelude;
static constexpr auto vertex = R"()"
R"(
struct VertexInput {
@location(3) pos_offset: vec4<i32>,
@location(4) data: vec4<u32>,
@location(5) pixeloffset: vec4<i32>,
@location(6) projected_pos: vec3<f32>,
@location(7) fade_opacity: f32,
#ifndef HAS_UNIFORM_u_fill_color
@location(8) fill_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_halo_color
@location(9) halo_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(10) opacity: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_width
@location(11) halo_width: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_blur
@location(12) halo_blur: f32,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex: vec2<f32>,
@location(1) fade_opacity: f32,
@location(2) gamma_scale: f32,
@location(3) fontScale: f32,
#ifndef HAS_UNIFORM_u_fill_color
@location(4) fill_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_halo_color
@location(5) halo_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(6) opacity: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_width
@location(7) halo_width: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_blur
@location(8) halo_blur: f32,
#endif
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<SymbolDrawableUBO>;
@group(0) @binding(4) var<uniform> props: SymbolEvaluatedPropsUBO;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let fade_opacity = unpack_opacity(in.fade_opacity);
let fade_change = select(-paintParams.symbol_fade_change, paintParams.symbol_fade_change, fade_opacity.y > 0.5);
let fo = max(0.0, min(1.0, fade_opacity.x + fade_change));
// Cull vertices with zero opacity
if (fo == 0.0) {
out.position = vec4<f32>(c_offscreen_degenerate_triangle_location,
c_offscreen_degenerate_triangle_location,
c_offscreen_degenerate_triangle_location, 1.0);
return out;
}
let a_pos = vec2<f32>(f32(in.pos_offset.x), f32(in.pos_offset.y));
let a_offset = vec2<f32>(f32(in.pos_offset.z), f32(in.pos_offset.w));
let a_tex = vec2<f32>(f32(in.data.x), f32(in.data.y));
let a_size = vec2<f32>(f32(in.data.z), f32(in.data.w));
let a_size_min = floor(a_size.x * 0.5);
let a_pxoffset = vec2<f32>(f32(in.pixeloffset.x), f32(in.pixeloffset.y));
let segment_angle = -in.projected_pos.z;
var size: f32;
let size_zoom_constant = drawable.is_size_zoom_constant != 0u;
let size_feature_constant = drawable.is_size_feature_constant != 0u;
if (!size_zoom_constant && !size_feature_constant) {
size = mix(a_size_min, a_size.y, drawable.size_t) / 128.0;
} else if (size_zoom_constant && !size_feature_constant) {
size = a_size_min / 128.0;
} else {
size = drawable.size;
}
let projectedPoint = drawable.matrix * vec4<f32>(a_pos, 0.0, 1.0);
let camera_to_anchor_distance = projectedPoint.w;
let pitch_with_map = drawable.pitch_with_map != 0u;
let distance_ratio = select(
paintParams.camera_to_center_distance / camera_to_anchor_distance,
camera_to_anchor_distance / paintParams.camera_to_center_distance,
pitch_with_map
);
let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0);
size *= perspective_ratio;
let is_text = drawable.is_text_prop != 0u;
let fontScale = select(size, size / 24.0, is_text);
var symbol_rotation = 0.0;
if (drawable.rotate_symbol != 0u) {
let offsetProjectedPoint = drawable.matrix * vec4<f32>(a_pos + vec2<f32>(1.0, 0.0), 0.0, 1.0);
let a = projectedPoint.xy / projectedPoint.w;
let b = offsetProjectedPoint.xy / offsetProjectedPoint.w;
symbol_rotation = atan2((b.y - a.y) / paintParams.aspect_ratio, b.x - a.x);
}
let angle_sin = sin(segment_angle + symbol_rotation);
let angle_cos = cos(segment_angle + symbol_rotation);
let rotation_matrix = mat2x2<f32>(angle_cos, -angle_sin, angle_sin, angle_cos);
let projected_pos = drawable.label_plane_matrix * vec4<f32>(in.projected_pos.xy, 0.0, 1.0);
let pos_rot = a_offset / 32.0 * fontScale + a_pxoffset;
let pos0 = projected_pos.xy / projected_pos.w + rotation_matrix * pos_rot;
let position = drawable.coord_matrix * vec4<f32>(pos0, 0.0, 1.0);
out.position = position;
out.tex = a_tex / drawable.texsize;
out.gamma_scale = position.w;
out.fontScale = fontScale;
out.fade_opacity = fo;
#ifndef HAS_UNIFORM_u_fill_color
out.fill_color = unpack_mix_color(in.fill_color, drawable.fill_color_t);
#endif
#ifndef HAS_UNIFORM_u_halo_color
out.halo_color = unpack_mix_color(in.halo_color, drawable.halo_color_t);
#endif
#ifndef HAS_UNIFORM_u_halo_width
out.halo_width = unpack_mix_float(vec2<f32>(in.halo_width, in.halo_width), drawable.halo_width_t);
#endif
#ifndef HAS_UNIFORM_u_halo_blur
out.halo_blur = unpack_mix_float(vec2<f32>(in.halo_blur, in.halo_blur), drawable.halo_blur_t);
#endif
#ifndef HAS_UNIFORM_u_opacity
out.opacity = unpack_mix_float(vec2<f32>(in.opacity, in.opacity), drawable.opacity_t);
#endif
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) tex: vec2<f32>,
@location(1) fade_opacity: f32,
@location(2) gamma_scale: f32,
@location(3) fontScale: f32,
#ifndef HAS_UNIFORM_u_fill_color
@location(4) fill_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_halo_color
@location(5) halo_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(6) opacity: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_width
@location(7) halo_width: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_blur
@location(8) halo_blur: f32,
#endif
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(3) var<storage, read> tilePropsVector: array<SymbolTilePropsUBO>;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(4) var<uniform> props: SymbolEvaluatedPropsUBO;
@group(1) @binding(0) var texture_sampler: sampler;
@group(1) @binding(1) var glyph_atlas: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let tileProps = tilePropsVector[globalIndex.value];
#ifndef HAS_UNIFORM_u_fill_color
let fill_color = in.fill_color;
#else
let fill_color = select(props.icon_fill_color, props.text_fill_color, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_halo_color
let halo_color = in.halo_color;
#else
let halo_color = select(props.icon_halo_color, props.text_halo_color, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_opacity
let opacity = in.opacity;
#else
let opacity = select(props.icon_opacity, props.text_opacity, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_halo_width
let halo_width = in.halo_width;
#else
let halo_width = select(props.icon_halo_width, props.text_halo_width, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_halo_blur
let halo_blur = in.halo_blur;
#else
let halo_blur = select(props.icon_halo_blur, props.text_halo_blur, tileProps.is_text != 0u);
#endif
let EDGE_GAMMA = 0.105 / paintParams.pixel_ratio;
let fontGamma = in.fontScale * tileProps.gamma_scale;
let color = select(fill_color, halo_color, tileProps.is_halo != 0u);
let gamma = (select(0.0, halo_blur * 1.19 / SDF_PX, tileProps.is_halo != 0u) + EDGE_GAMMA) / fontGamma;
let gamma_scaled = gamma * in.gamma_scale;
// For halos, offset the inner edge outward to create the ring
var inner_edge = (256.0 - 64.0) / 256.0;
if (tileProps.is_halo != 0u) {
inner_edge = inner_edge + gamma_scaled;
}
let sample = textureSample(glyph_atlas, texture_sampler, in.tex);
let dist = select(sample.a, sample.r, tileProps.is_text != 0u);
var alpha = smoothstep(inner_edge - gamma_scaled, inner_edge + gamma_scaled, dist);
// When drawing halos, make the inside transparent so the fill can show through
if (tileProps.is_halo != 0u) {
let halo_edge = (6.0 - halo_width / in.fontScale) / SDF_PX;
alpha = min(smoothstep(halo_edge - gamma_scaled, halo_edge + gamma_scaled, dist), 1.0 - alpha);
}
let coverage = alpha * opacity * in.fade_opacity;
let outAlpha = color.a * coverage;
return vec4<f32>(color.rgb * coverage, outAlpha);
}
)";
};
template <>
struct ShaderSource<BuiltIn::SymbolTextAndIconShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "SymbolTextAndIconShader";
static const std::array<AttributeInfo, 9> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<TextureInfo, 2> textures;
static constexpr auto prelude = symbolShaderPrelude;
static constexpr auto vertex = R"()"
R"(
struct VertexInput {
@location(3) pos_offset: vec4<i32>,
@location(4) data: vec4<u32>,
@location(5) projected_pos: vec3<f32>,
@location(6) fade_opacity: f32,
#ifndef HAS_UNIFORM_u_fill_color
@location(7) fill_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_halo_color
@location(8) halo_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(9) opacity: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_width
@location(10) halo_width: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_blur
@location(11) halo_blur: f32,
#endif
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) tex: vec2<f32>,
@location(1) fade_opacity: f32,
@location(2) gamma_scale: f32,
@location(3) fontScale: f32,
@location(4) is_icon: f32,
#ifndef HAS_UNIFORM_u_fill_color
@location(5) fill_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_halo_color
@location(6) halo_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(7) opacity: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_width
@location(8) halo_width: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_blur
@location(9) halo_blur: f32,
#endif
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(2) var<storage, read> drawableVector: array<SymbolDrawableUBO>;
const SDF = 1.0;
const ICON = 0.0;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let drawable = drawableVector[globalIndex.value];
let fade_opacity = unpack_opacity(in.fade_opacity);
let fade_change = select(-paintParams.symbol_fade_change, paintParams.symbol_fade_change, fade_opacity.y > 0.5);
let fo = max(0.0, min(1.0, fade_opacity.x + fade_change));
// Cull vertices with zero opacity
if (fo == 0.0) {
out.position = vec4<f32>(c_offscreen_degenerate_triangle_location,
c_offscreen_degenerate_triangle_location,
c_offscreen_degenerate_triangle_location, 1.0);
return out;
}
let a_pos = vec2<f32>(f32(in.pos_offset.x), f32(in.pos_offset.y));
let a_offset = vec2<f32>(f32(in.pos_offset.z), f32(in.pos_offset.w));
let a_tex = vec2<f32>(f32(in.data.x), f32(in.data.y));
let a_size = vec2<f32>(f32(in.data.z), f32(in.data.w));
let a_size_min = floor(a_size.x * 0.5);
let is_sdf = a_size.x - 2.0 * a_size_min;
let segment_angle = -in.projected_pos.z;
let size_zoom_constant = drawable.is_size_zoom_constant != 0u;
let size_feature_constant = drawable.is_size_feature_constant != 0u;
var size: f32;
if (!size_zoom_constant && !size_feature_constant) {
size = mix(a_size_min, a_size.y, drawable.size_t) / 128.0;
} else if (size_zoom_constant && !size_feature_constant) {
size = a_size_min / 128.0;
} else {
size = drawable.size;
}
let projectedPoint = drawable.matrix * vec4<f32>(a_pos, 0.0, 1.0);
let camera_to_anchor_distance = projectedPoint.w;
let pitch_with_map = drawable.pitch_with_map != 0u;
let distance_ratio = select(
paintParams.camera_to_center_distance / camera_to_anchor_distance,
camera_to_anchor_distance / paintParams.camera_to_center_distance,
pitch_with_map
);
let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0);
size *= perspective_ratio;
let fontScale = size / 24.0;
var symbol_rotation = 0.0;
if (drawable.rotate_symbol != 0u) {
let offsetProjectedPoint = drawable.matrix * vec4<f32>(a_pos + vec2<f32>(1.0, 0.0), 0.0, 1.0);
let a = projectedPoint.xy / projectedPoint.w;
let b = offsetProjectedPoint.xy / offsetProjectedPoint.w;
symbol_rotation = atan2((b.y - a.y) / paintParams.aspect_ratio, b.x - a.x);
}
let angle_sin = sin(segment_angle + symbol_rotation);
let angle_cos = cos(segment_angle + symbol_rotation);
let rotation_matrix = mat2x2<f32>(angle_cos, -angle_sin, angle_sin, angle_cos);
let projected_pos = drawable.label_plane_matrix * vec4<f32>(in.projected_pos.xy, 0.0, 1.0);
let pos_rot = a_offset / 32.0 * fontScale;
let pos0 = projected_pos.xy / projected_pos.w + rotation_matrix * pos_rot;
let position = drawable.coord_matrix * vec4<f32>(pos0, 0.0, 1.0);
let gamma_scale = position.w;
let is_icon = select(1.0, 0.0, is_sdf == SDF);
out.position = position;
let inv_tex = 1.0 / select(drawable.texsize, drawable.texsize_icon, is_icon != 0.0);
out.tex = a_tex * inv_tex;
out.gamma_scale = gamma_scale;
out.fontScale = fontScale;
out.fade_opacity = fo;
out.is_icon = is_icon;
#ifndef HAS_UNIFORM_u_fill_color
out.fill_color = unpack_mix_color(in.fill_color, drawable.fill_color_t);
#endif
#ifndef HAS_UNIFORM_u_halo_color
out.halo_color = unpack_mix_color(in.halo_color, drawable.halo_color_t);
#endif
#ifndef HAS_UNIFORM_u_opacity
out.opacity = unpack_mix_float(vec2<f32>(in.opacity, in.opacity), drawable.opacity_t);
#endif
#ifndef HAS_UNIFORM_u_halo_width
out.halo_width = unpack_mix_float(vec2<f32>(in.halo_width, in.halo_width), drawable.halo_width_t);
#endif
#ifndef HAS_UNIFORM_u_halo_blur
out.halo_blur = unpack_mix_float(vec2<f32>(in.halo_blur, in.halo_blur), drawable.halo_blur_t);
#endif
return out;
}
)";
static constexpr auto fragment = R"(
struct FragmentInput {
@location(0) tex: vec2<f32>,
@location(1) fade_opacity: f32,
@location(2) gamma_scale: f32,
@location(3) fontScale: f32,
@location(4) is_icon: f32,
#ifndef HAS_UNIFORM_u_fill_color
@location(5) fill_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_halo_color
@location(6) halo_color: vec4<f32>,
#endif
#ifndef HAS_UNIFORM_u_opacity
@location(7) opacity: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_width
@location(8) halo_width: f32,
#endif
#ifndef HAS_UNIFORM_u_halo_blur
@location(9) halo_blur: f32,
#endif
};
@group(0) @binding(0) var<uniform> paintParams: GlobalPaintParamsUBO;
@group(0) @binding(3) var<storage, read> tilePropsVector: array<SymbolTilePropsUBO>;
@group(0) @binding(1) var<uniform> globalIndex: GlobalIndexUBO;
@group(0) @binding(4) var<uniform> props: SymbolEvaluatedPropsUBO;
@group(1) @binding(0) var glyph_sampler: sampler;
@group(1) @binding(1) var glyph_image: texture_2d<f32>;
@group(1) @binding(2) var icon_sampler: sampler;
@group(1) @binding(3) var icon_image: texture_2d<f32>;
@fragment
fn main(in: FragmentInput) -> @location(0) vec4<f32> {
let tileProps = tilePropsVector[globalIndex.value];
#ifndef HAS_UNIFORM_u_fill_color
let fill_color = in.fill_color;
#else
let fill_color = select(props.icon_fill_color, props.text_fill_color, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_halo_color
let halo_color = in.halo_color;
#else
let halo_color = select(props.icon_halo_color, props.text_halo_color, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_opacity
let opacity = in.opacity;
#else
let opacity = select(props.icon_opacity, props.text_opacity, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_halo_width
let halo_width = in.halo_width;
#else
let halo_width = select(props.icon_halo_width, props.text_halo_width, tileProps.is_text != 0u);
#endif
#ifndef HAS_UNIFORM_u_halo_blur
let halo_blur = in.halo_blur;
#else
let halo_blur = select(props.icon_halo_blur, props.text_halo_blur, tileProps.is_text != 0u);
#endif
if (in.is_icon != 0.0) {
let alpha = opacity * in.fade_opacity;
let iconSample = textureSampleLevel(icon_image, icon_sampler, in.tex, 0.0);
return iconSample * alpha;
}
let EDGE_GAMMA = 0.105 / paintParams.pixel_ratio;
let color = select(fill_color, halo_color, tileProps.is_halo != 0u);
let fontGamma = in.fontScale * tileProps.gamma_scale;
let gamma = (select(0.0, halo_blur * 1.19 / SDF_PX, tileProps.is_halo != 0u) + EDGE_GAMMA) / fontGamma;
let gamma_scaled = gamma * in.gamma_scale;
// For halos, offset the inner edge outward to create the ring
var inner_edge = (256.0 - 64.0) / 256.0;
if (tileProps.is_halo != 0u) {
inner_edge = inner_edge + gamma_scaled;
}
let sample = textureSampleLevel(glyph_image, glyph_sampler, in.tex, 0.0);
let dist = select(sample.a, sample.r, tileProps.is_text != 0u);
var alpha = smoothstep(inner_edge - gamma_scaled, inner_edge + gamma_scaled, dist);
// When drawing halos, make the inside transparent so the fill can show through
if (tileProps.is_halo != 0u) {
let halo_edge = (6.0 - halo_width / in.fontScale) / SDF_PX;
alpha = min(smoothstep(halo_edge - gamma_scaled, halo_edge + gamma_scaled, dist), 1.0 - alpha);
}
let coverage = alpha * opacity * in.fade_opacity;
let outAlpha = color.a * coverage;
return vec4<f32>(color.rgb * coverage, outAlpha);
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,16 @@
#pragma once
#include <string>
#include <unordered_map>
namespace mbgl {
namespace webgpu {
namespace detail {
bool isDirective(const std::string& line, const std::string& directive);
std::string getDirectiveArgument(const std::string& line);
std::string preprocessWGSL(const std::string& source, const std::unordered_map<std::string, bool>& defines);
} // namespace detail
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,82 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/widevector_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::WebGPU> {
static constexpr const char* name = "WideVectorShader";
static const std::array<AttributeInfo, 3> attributes;
static const std::array<AttributeInfo, 4> instanceAttributes;
static const std::array<TextureInfo, 0> textures;
// Simplified wide vector shader for WebGPU
static constexpr auto vertex = R"(
struct VertexInput {
@location(5) position: vec3<f32>,
@location(6) normal: vec2<f32>,
@location(7) texCoord: vec2<f32>,
};
struct InstanceInput {
@location(8) instanceCenter: vec3<f32>,
@location(9) instanceColor: vec4<f32>,
@location(10) instancePrev: i32,
@location(11) instanceNext: i32,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) texCoord: vec2<f32>,
};
struct WideVectorUBO {
mvpMatrix: mat4x4<f32>,
mvpMatrixDiff: mat4x4<f32>,
mvMatrix: mat4x4<f32>,
mvMatrixDiff: mat4x4<f32>,
pMatrix: mat4x4<f32>,
pMatrixDiff: mat4x4<f32>,
frameSize: vec2<f32>,
color: vec4<f32>,
width: f32,
offset: f32,
edge: f32,
texRepeat: f32,
texOffset: vec2<f32>,
};
@group(0) @binding(0) var<uniform> ubo: WideVectorUBO;
@vertex
fn main(in: VertexInput, instance: InstanceInput) -> VertexOutput {
var out: VertexOutput;
// Calculate position with offset based on normal
let worldPos = instance.instanceCenter + vec3<f32>(in.normal * ubo.width, 0.0);
out.position = ubo.mvpMatrix * vec4<f32>(worldPos, 1.0);
// Pass through color and texture coordinates
out.color = instance.instanceColor * ubo.color;
out.texCoord = in.texCoord * ubo.texRepeat + ubo.texOffset;
return out;
}
)";
static constexpr auto fragment = R"(
@fragment
fn main(@location(0) color: vec4<f32>, @location(1) texCoord: vec2<f32>) -> @location(0) vec4<f32> {
// Simple color output with alpha blending support
return color;
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,65 @@
#pragma once
#include <webgpu/webgpu.h>
#include <cstdint>
#include <cstddef>
#include <vector>
namespace mbgl {
namespace webgpu {
class Context;
class BufferResource {
public:
BufferResource() noexcept = delete;
BufferResource(Context& context,
const void* data,
std::size_t size,
uint32_t usage,
bool isIndexBuffer = false,
bool persistent = false);
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; }
WGPUBuffer getBuffer() const noexcept { return buffer; }
bool isValid() const noexcept { return buffer || !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; }
/// Indicates whether this buffer needs to be re-bound from a previous binding at the given version
bool needReBind(VersionType version) const noexcept;
uint32_t getUsage() const { return usage; }
bool isPersistent() const { return persistent; }
protected:
Context& context;
WGPUBuffer buffer = nullptr;
std::vector<std::uint8_t> raw;
std::size_t size = 0;
uint32_t usage = 0;
std::uint16_t version = 0;
bool isIndexBuffer = false;
bool persistent = false;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,84 @@
#pragma once
#include <mbgl/gfx/command_encoder.hpp>
#include <mbgl/util/containers.hpp>
#include <webgpu/webgpu.h>
#include <functional>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
namespace mbgl {
namespace gfx {
class Renderable;
} // namespace gfx
namespace webgpu {
class Context;
class RenderPass;
class UploadPass;
class CommandEncoder final : public gfx::CommandEncoder {
public:
explicit CommandEncoder(Context& context_);
~CommandEncoder() override;
webgpu::Context& getContext() { return context; }
const webgpu::Context& getContext() const { return context; }
WGPUCommandEncoder getEncoder() const { return encoder; }
std::unique_ptr<gfx::UploadPass> createUploadPass(const char* name, gfx::Renderable&) override;
std::unique_ptr<gfx::RenderPass> createRenderPass(const char* name, const gfx::RenderPassDescriptor&) override;
void present(gfx::Renderable&) override;
// WebGPU-specific: submit command buffer directly
void submitCommandBuffer();
template <typename TFunc>
void visitDebugGroups(TFunc f) {
for (const auto& item : debugGroupNames) {
f(item.name);
}
}
private:
friend class RenderPass;
friend class UploadPass;
// Track a RenderPass, propagating debug groups to it
void trackRenderPass(RenderPass*);
// Stop tracking a RenderPass
void forgetRenderPass(RenderPass*);
// Track an UploadPass, propagating debug groups to it
void trackUploadPass(UploadPass*);
// Stop tracking a RenderPass
void forgetUploadPass(UploadPass*);
void pushDebugGroup(const char* name) override;
void popDebugGroup() override;
protected:
struct GroupEntry {
std::string name;
std::size_t renderGroups;
std::size_t uploadGroups;
};
protected:
webgpu::Context& context;
WGPUCommandEncoder encoder = nullptr;
std::vector<GroupEntry> debugGroupNames;
std::vector<gfx::DebugGroup<gfx::RenderPass>> renderDebugGroups;
std::vector<gfx::DebugGroup<gfx::UploadPass>> uploadDebugGroups;
mbgl::unordered_set<RenderPass*> renderPasses;
mbgl::unordered_set<UploadPass*> uploadPasses;
UploadPass* currentUploadPass = nullptr;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,132 @@
#pragma once
#include <mbgl/gfx/context.hpp>
#include <mbgl/gfx/vertex_buffer.hpp>
#include <mbgl/webgpu/renderer_backend.hpp>
#include <mbgl/webgpu/buffer_resource.hpp>
#include <memory>
#include <optional>
#include <vector>
namespace mbgl {
class RenderStaticData;
namespace gfx {
// Forward declaration
class AttributeBinding;
using AttributeBindingArray = std::vector<std::optional<AttributeBinding>>;
class RenderPass;
class Renderable;
class ShaderProgramBase;
} // namespace gfx
namespace shaders {
struct ClipUBO;
} // namespace shaders
namespace webgpu {
// Forward declaration
class VertexBufferResource;
class Context : public gfx::Context {
public:
explicit Context(RendererBackend& backend);
~Context() override;
RendererBackend& getBackend() { return backend; }
const RendererBackend& getBackend() const { return backend; }
// Frame management
void beginFrame() override;
void endFrame() override;
void performCleanup() override;
void reduceMemoryUsage() override;
// Resource creation
std::unique_ptr<gfx::OffscreenTexture> createOffscreenTexture(Size, gfx::TextureChannelDataType, bool, bool);
std::unique_ptr<gfx::OffscreenTexture> createOffscreenTexture(Size, gfx::TextureChannelDataType) override;
std::unique_ptr<gfx::CommandEncoder> createCommandEncoder() override;
gfx::VertexAttributeArrayPtr createVertexAttributeArray() const override;
gfx::UniqueDrawableBuilder createDrawableBuilder(std::string name) override;
gfx::UniformBufferPtr createUniformBuffer(const void* data,
std::size_t size,
bool persistent = false,
bool ssbo = false) override;
gfx::UniqueUniformBufferArray createLayerUniformBufferArray() 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;
// State management
void resetState(gfx::DepthMode depthMode, gfx::ColorMode colorMode) override;
void setDirtyState() override;
void clearStencilBuffer(int32_t) override;
// Uniform buffer management
bool emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr&,
const void* data,
std::size_t size,
bool persistent = false) override;
const gfx::UniformBufferArray& getGlobalUniformBuffers() const override;
gfx::UniformBufferArray& mutableGlobalUniformBuffers() override;
void bindGlobalUniformBuffers(gfx::RenderPass&) const noexcept override;
void unbindGlobalUniformBuffers(gfx::RenderPass&) const noexcept override;
#if !defined(NDEBUG)
void visualizeStencilBuffer() override;
void visualizeDepthBuffer(float depthRangeSize) override;
#endif
// Buffer creation (aligned with Metal)
BufferResource createBuffer(
const void* data, std::size_t size, uint32_t usage, bool isIndexBuffer, bool persistent) const;
// Get reusable buffers (aligned with Metal)
const BufferResource& getTileVertexBuffer();
const BufferResource& getTileIndexBuffer();
// Tile clipping mask rendering
bool renderTileClippingMasks(gfx::RenderPass& renderPass,
RenderStaticData& staticData,
const std::vector<shaders::ClipUBO>& tileUBOs);
// Command encoder tracking
class CommandEncoder* getCurrentCommandEncoder() const { return currentCommandEncoder; }
void setCurrentCommandEncoder(class CommandEncoder* encoder) { currentCommandEncoder = encoder; }
// Vertex binding creation - needed for drawable geometry
gfx::AttributeBindingArray getOrCreateVertexBindings(gfx::Context&,
const gfx::AttributeDataType vertexType,
const size_t vertexAttributeIndex,
const std::vector<uint8_t>& vertexData,
const gfx::VertexAttributeArray& defaults,
const gfx::VertexAttributeArray& overrides,
gfx::BufferUsageType,
const std::optional<std::chrono::duration<double>>&,
std::shared_ptr<webgpu::VertexBufferResource>&) noexcept;
protected:
std::unique_ptr<gfx::RenderbufferResource> createRenderbufferResource(gfx::RenderbufferPixelType, Size) override;
std::unique_ptr<gfx::DrawScopeResource> createDrawScopeResource() override;
private:
RendererBackend& backend;
std::unique_ptr<gfx::UniformBufferArray> globalUniformBuffers;
class CommandEncoder* currentCommandEncoder = nullptr;
// Cached buffers (aligned with Metal)
std::optional<BufferResource> tileVertexBuffer;
std::optional<BufferResource> tileIndexBuffer;
// Cached clipping resources
gfx::ShaderProgramBasePtr clipMaskShader;
const gfx::Renderable* clipMaskRenderable = nullptr;
std::optional<std::size_t> clipMaskPipelineHash;
std::vector<BufferResource> clipMaskUniformBuffers;
std::vector<WGPUBindGroup> clipMaskActiveBindGroups;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,20 @@
#pragma once
#include <mbgl/gfx/draw_scope.hpp>
namespace mbgl {
namespace webgpu {
class Context;
class DrawScopeResource : public gfx::DrawScopeResource {
public:
explicit DrawScopeResource(Context& context);
~DrawScopeResource() override = default;
private:
Context& context;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,64 @@
#pragma once
#include <mbgl/gfx/drawable.hpp>
#include <mbgl/gfx/draw_mode.hpp>
#include <webgpu/webgpu.h>
#include <memory>
#include <vector>
// Forward declare WebGPU types
typedef struct WGPUBindGroupLayoutImpl* WGPUBindGroupLayout;
namespace mbgl {
namespace gfx {
class UploadPass;
class DepthMode;
class StencilMode;
} // namespace gfx
namespace webgpu {
class CommandEncoder;
class UploadPass;
class RenderPipeline;
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<UniqueDrawSegment> segments) override;
void setVertices(std::vector<uint8_t>&&, std::size_t, gfx::AttributeDataType) override;
const gfx::UniformBufferArray& getUniformBuffers() const override;
gfx::UniformBufferArray& mutableUniformBuffers() override;
void setColorMode(const gfx::ColorMode&) override;
void setShader(gfx::ShaderProgramBasePtr) override;
void setEnableStencil(bool) override;
void setEnableDepth(bool) override;
void setSubLayerIndex(int32_t) override;
void setDepthType(gfx::DepthMaskType) override;
void setDepthModeFor3D(const gfx::DepthMode&);
void setStencilModeFor3D(const gfx::StencilMode&);
void setVertexAttrId(const size_t);
void updateVertexAttributes(gfx::VertexAttributeArrayPtr,
std::size_t vertexCount,
gfx::DrawMode,
gfx::IndexVectorBasePtr,
const SegmentBase* segments,
std::size_t segmentCount) override;
class Impl;
const std::unique_ptr<Impl> impl;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,29 @@
#pragma once
#include <mbgl/gfx/drawable_builder.hpp>
#include <mbgl/webgpu/drawable.hpp>
#include <memory>
#include <string>
namespace mbgl {
namespace webgpu {
class Context;
class DrawableBuilder : public gfx::DrawableBuilder {
public:
explicit DrawableBuilder(std::string name);
~DrawableBuilder() override;
protected:
std::unique_ptr<gfx::Drawable> createDrawable() const override;
std::unique_ptr<gfx::Drawable::DrawSegment> createSegment(gfx::DrawMode, SegmentBase&&) override;
void init() override;
private:
// WebGPU-specific drawable configuration is handled in the drawable implementation
// Pipeline, buffers, and textures are managed by the Context and Drawable classes
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,33 @@
#pragma once
#include <mbgl/gfx/index_buffer.hpp>
#include <mbgl/webgpu/buffer_resource.hpp>
namespace mbgl {
namespace webgpu {
class IndexBufferResource : public gfx::IndexBufferResource {
public:
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; }
// Compatibility with existing code
const BufferResource& getBuffer() const { return buffer; }
BufferResource& getBuffer() { return buffer; }
void update(const void* data, std::size_t size) { buffer.update(data, size, 0); }
protected:
BufferResource buffer;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <mbgl/renderer/layer_group.hpp>
#include <mbgl/webgpu/uniform_buffer.hpp>
namespace mbgl {
class PaintParameters;
namespace webgpu {
/**
* 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; }
protected:
webgpu::UniformBufferArray uniformBuffers;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,22 @@
#pragma once
#include <mbgl/gfx/offscreen_texture.hpp>
#include <mbgl/gfx/types.hpp>
namespace mbgl {
namespace webgpu {
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 webgpu
} // namespace mbgl

View File

@@ -0,0 +1,52 @@
#pragma once
#include <mbgl/gfx/render_pass.hpp>
#include <memory>
// Forward declare WebGPU types
typedef struct WGPURenderPassEncoderImpl* WGPURenderPassEncoder;
namespace mbgl {
namespace gfx {
class CommandEncoder;
class UniformBufferArray;
} // namespace gfx
namespace webgpu {
class CommandEncoder;
class RenderPass final : public gfx::RenderPass {
public:
RenderPass(CommandEncoder& commandEncoder, const char* name, const gfx::RenderPassDescriptor& descriptor);
~RenderPass() override;
// Match Metal's interface
CommandEncoder& getCommandEncoder() { return commandEncoder; }
const CommandEncoder& getCommandEncoder() const { return commandEncoder; }
// Get the WebGPU render pass encoder (similar to Metal's getMetalEncoder)
WGPURenderPassEncoder getEncoder() const;
const gfx::RenderPassDescriptor& getDescriptor() const { return descriptor; }
// Set/get global uniform buffers for drawables to access
void setGlobalUniformBuffers(const gfx::UniformBufferArray* buffers);
const gfx::UniformBufferArray* getGlobalUniformBuffers() const;
private:
void pushDebugGroup(const char* name) override;
void popDebugGroup() override;
void addDebugSignpost(const char* name) override;
private:
gfx::RenderPassDescriptor descriptor;
CommandEncoder& commandEncoder;
const gfx::DebugGroup<gfx::CommandEncoder> debugGroup;
// Implementation details
class Impl;
std::unique_ptr<Impl> impl;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,54 @@
#pragma once
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/webgpu/wgpu_cpp_compat.hpp>
#include <cstdlib>
#include <optional>
namespace mbgl {
namespace webgpu {
class RendererBackend;
class RenderableResource : public gfx::RenderableResource {
protected:
explicit RenderableResource() = default;
public:
void bind() override {
// WebGPU binding happens at the command encoder level
// This is a no-op for now
}
virtual void swap() {
// Renderable resources that require a swap function to be called
// explicitly can override this method.
}
virtual const mbgl::webgpu::RendererBackend& getBackend() const = 0;
virtual const WGPUCommandEncoder& getCommandEncoder() const = 0;
virtual WGPURenderPassEncoder getRenderPassEncoder() const = 0;
/**
* Obtain a texture view that should be bound as the color attachment for
* the render pass targeting this resource. Implementations should return a
* valid `WGPUTextureView` or `nullptr` if the resource relies on the
* backend to supply the swapchain view (e.g. default framebuffer).
*/
virtual WGPUTextureView getColorTextureView() { return nullptr; }
virtual std::optional<wgpu::TextureFormat> getColorTextureFormat() const { return std::nullopt; }
/**
* Obtain a depth/stencil texture view for the render pass if the resource
* manages one. Returning `nullptr` indicates that the backend-provided
* depth/stencil view (if any) should be used instead.
*/
virtual WGPUTextureView getDepthStencilTextureView() { return nullptr; }
virtual std::optional<wgpu::TextureFormat> getDepthStencilTextureFormat() const { return std::nullopt; }
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,62 @@
#pragma once
#include <mbgl/gfx/renderer_backend.hpp>
#include <mbgl/util/size.hpp>
#include <mbgl/webgpu/wgpu_cpp_compat.hpp>
#include <memory>
namespace mbgl {
class ProgramParameters;
namespace gfx {
class ShaderRegistry;
}
namespace webgpu {
class Context;
class RendererBackend : public gfx::RendererBackend {
public:
explicit RendererBackend(gfx::ContextMode);
~RendererBackend() override;
// getDefaultRenderable() is pure virtual - must be implemented by platform backends
// Platform backends typically inherit from gfx::Renderable and return *this
void initShaders(gfx::ShaderRegistry&, const ProgramParameters&) override;
// Platform-specific surface creation
void setSurface(void* nativeWindow);
// WebGPU-specific methods
void setInstance(void* instance);
void setDevice(void* device);
void setQueue(void* queue);
void* getInstance() const;
void* getDevice() const;
void* getQueue() const;
void* getSurface() const;
// Surface texture access - can be overridden by platform backends
virtual void* getCurrentTextureView();
virtual void* getDepthStencilView();
virtual mbgl::Size getFramebufferSize() const;
void setDepthStencilFormat(wgpu::TextureFormat);
wgpu::TextureFormat getDepthStencilFormat() const;
void setColorFormat(wgpu::TextureFormat);
wgpu::TextureFormat getColorFormat() const;
protected:
std::unique_ptr<gfx::Context> createContext() override;
void activate() override;
void deactivate() override;
private:
class Impl;
std::unique_ptr<Impl> impl;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,64 @@
#pragma once
#include <mbgl/gfx/texture2d.hpp>
#include <webgpu/webgpu.h>
#include <mbgl/util/image.hpp>
#include <memory>
namespace mbgl {
namespace webgpu {
class Context;
class RenderPass;
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<PremultipliedImage>) noexcept override;
Texture2D& setUsage(uint32_t usageFlags_) noexcept;
void setSizeChangedCallback(std::function<void(const Size&)>) noexcept;
WGPUTextureFormat getNativeFormat() const noexcept { return nativeFormat; }
gfx::TexturePixelType getFormat() const noexcept override { return pixelFormat; }
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(const void* pixelData, const Size& size) noexcept override;
void upload() noexcept override;
void uploadSubRegion(const void* pixelData, const Size& size, uint16_t xOffset, uint16_t yOffset) noexcept override;
bool needsUpload() const noexcept override { return dirty; }
WGPUTexture getTexture() const { return texture; }
WGPUTextureView getTextureView() const { return textureView; }
WGPUSampler getSampler() const { return sampler; }
const SamplerState& getSamplerState() const noexcept { return samplerState; }
protected:
Context& context;
WGPUTexture texture = nullptr;
WGPUTextureView textureView = nullptr;
WGPUSampler sampler = nullptr;
uint32_t usageFlags = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst | WGPUTextureUsage_CopySrc;
WGPUTextureFormat nativeFormat = WGPUTextureFormat_Undefined;
Size size{0, 0};
gfx::TexturePixelType pixelFormat = gfx::TexturePixelType::RGBA;
gfx::TextureChannelDataType channelType = gfx::TextureChannelDataType::UnsignedByte;
SamplerState samplerState;
std::shared_ptr<PremultipliedImage> image;
bool dirty = false;
std::function<void(const Size&)> sizeChangedCallback;
};
} // namespace webgpu
} // namespace mbgl
#include <functional>

View File

@@ -0,0 +1,31 @@
#pragma once
#include <mbgl/renderer/layer_group.hpp>
#include <mbgl/webgpu/uniform_buffer.hpp>
namespace mbgl {
class PaintParameters;
namespace webgpu {
/**
* 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; }
protected:
webgpu::UniformBufferArray uniformBuffers;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,60 @@
#pragma once
#include <mbgl/gfx/uniform_buffer.hpp>
#include <webgpu/webgpu.h>
namespace mbgl {
namespace webgpu {
class Context;
class RenderPass;
class UniformBuffer final : public gfx::UniformBuffer {
public:
UniformBuffer(Context& context, const void* data, std::size_t size);
~UniformBuffer() override;
UniformBuffer(const UniformBuffer&) = delete;
UniformBuffer(UniformBuffer&& other) noexcept;
UniformBuffer& operator=(const UniformBuffer&) = delete;
UniformBuffer& operator=(UniformBuffer&&) = delete;
UniformBuffer clone() const;
void update(const void* data, std::size_t dataSize) override;
WGPUBuffer getBuffer() const { return buffer; }
private:
Context& context;
WGPUBuffer buffer = nullptr;
};
/// 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;
}
void bindWebgpu(RenderPass&) const noexcept;
void bind(gfx::RenderPass& renderPass) override;
private:
gfx::UniqueUniformBuffer copy(const gfx::UniformBuffer& buffer) override {
return std::make_unique<UniformBuffer>(static_cast<const UniformBuffer&>(buffer).clone());
}
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,70 @@
#pragma once
#include <mbgl/gfx/index_buffer.hpp>
#include <mbgl/gfx/vertex_buffer.hpp>
#include <mbgl/gfx/upload_pass.hpp>
#include <mbgl/gfx/renderbuffer.hpp>
namespace mbgl {
namespace gfx {
class CommandEncoder;
class Renderable;
} // namespace gfx
namespace webgpu {
class CommandEncoder;
class RenderbufferResource : public gfx::RenderbufferResource {
public:
RenderbufferResource() = default;
};
class UploadPass final : public gfx::UploadPass {
public:
UploadPass(gfx::Renderable& renderable, CommandEncoder& commandEncoder, const char* name);
~UploadPass() override;
private:
void pushDebugGroup(const char* name) override;
void popDebugGroup() override;
public:
gfx::Context& getContext() override;
const gfx::Context& getContext() const override;
std::unique_ptr<gfx::VertexBufferResource> 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<gfx::IndexBufferResource> 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;
gfx::AttributeBindingArray buildAttributeBindings(
const std::size_t vertexCount,
const gfx::AttributeDataType vertexType,
const std::size_t vertexAttributeIndex,
const std::vector<std::uint8_t>& vertexData,
const gfx::VertexAttributeArray& defaults,
const gfx::VertexAttributeArray& overrides,
gfx::BufferUsageType,
const std::optional<std::chrono::duration<double>> lastUpdate,
/*out*/ std::vector<std::unique_ptr<gfx::VertexBufferResource>>& outBuffers) override;
const gfx::UniqueVertexBufferResource& getBuffer(const gfx::VertexVectorBasePtr&,
gfx::BufferUsageType,
bool forceUpdate);
private:
CommandEncoder& commandEncoder;
std::vector<gfx::DebugGroup<gfx::UploadPass>> debugGroups;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,52 @@
#pragma once
#include <mbgl/gfx/types.hpp>
#include <mbgl/gfx/vertex_attribute.hpp>
namespace mbgl {
namespace gfx {
class VertexBufferResource;
} // namespace gfx
namespace webgpu {
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(const VertexAttribute& other) = delete;
VertexAttribute(VertexAttribute&& other)
: gfx::VertexAttribute(std::move(other)) {}
~VertexAttribute() override = default;
static const gfx::UniqueVertexBufferResource& getBuffer(gfx::VertexAttribute&,
UploadPass&,
const gfx::BufferUsageType,
bool forceUpdate);
};
/// 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 isModifiedAfter(std::chrono::duration<double> time) const;
private:
gfx::UniqueVertexAttribute create(int index, gfx::AttributeDataType dataType, std::size_t count) const override {
return std::make_unique<VertexAttribute>(index, dataType, count);
}
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,44 @@
#pragma once
#include <mbgl/gfx/vertex_buffer.hpp>
#include <mbgl/webgpu/buffer_resource.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <memory>
namespace mbgl {
namespace webgpu {
class Context;
class VertexBufferResource : public gfx::VertexBufferResource {
public:
VertexBufferResource(BufferResource&&) noexcept;
VertexBufferResource(VertexBufferResource&& other) noexcept
: buffer(std::move(other.buffer)) {}
~VertexBufferResource() 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; }
// Compatibility with existing code
const BufferResource& getBuffer() const { return buffer; }
BufferResource& getBuffer() { return buffer; }
std::chrono::duration<double> getLastUpdated() const { return lastUpdated; }
void setLastUpdated(std::chrono::duration<double> time) { lastUpdated = time; }
void update(const void* data, std::size_t size) { buffer.update(data, size, 0); }
protected:
BufferResource buffer;
std::chrono::duration<double> lastUpdated;
};
using UniqueVertexBufferResource = std::unique_ptr<VertexBufferResource>;
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,19 @@
#pragma once
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic ignored "-Wstrict-aliasing"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
#include <webgpu/webgpu_cpp.h>
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

View File

@@ -0,0 +1,8 @@
{
"render-tests/icon-text-fit/enlargen-both": "Needs to be investigated and fixed.",
"render-tests/regressions/mapbox-gl-js#5642": "Failing with mbgl-render-test",
"render-tests/regressions/mapbox-gl-js#7066": "Failing with mbgl-render-test",
"render-tests/fill-outline-color/zoom-and-property-function": "It will be fixed after triangulated lines will support data driven properties",
"render-tests/fill-outline-color/property-function": "It will be fixed after triangulated lines will support data driven properties",
"render-tests/fill-extrusion-color/function": "Layer Z Fighting: https://github.com/maplibre/maplibre-native/issues/1847"
}

View File

@@ -0,0 +1,15 @@
{
"base_test_path": "integration",
"cache_path": "cache-style.db",
"expectation_paths": [
"expectations/platform-linux"
],
"ignore_paths": [
"ignores/platform-all.json",
"ignores/platform-linux-webgpu.json"
],
"metric_path": "linux-webgpu-dawn",
"probes": [
"probeNetwork"
]
}

View File

@@ -0,0 +1,15 @@
{
"base_test_path": "integration",
"cache_path": "cache-style.db",
"expectation_paths": [
"expectations/platform-linux"
],
"ignore_paths": [
"ignores/platform-all.json",
"ignores/platform-linux-webgpu.json"
],
"metric_path": "linux-webgpu-wgpu",
"probes": [
"probeNetwork"
]
}

View File

@@ -0,0 +1,15 @@
{
"base_test_path": "integration",
"cache_path": "cache-style.db",
"expectation_paths": [
"expectations/platform-macos"
],
"ignore_paths": [
"ignores/platform-all.json",
"ignores/platform-macos-webgpu.json"
],
"metric_path": "macos-webgpu-dawn",
"probes": [
"probeNetwork"
]
}

View File

@@ -0,0 +1,15 @@
{
"base_test_path": "integration",
"cache_path": "cache-style.db",
"expectation_paths": [
"expectations/platform-macos"
],
"ignore_paths": [
"ignores/platform-all.json",
"ignores/platform-macos-webgpu.json"
],
"metric_path": "macos-webgpu-wgpu",
"probes": [
"probeNetwork"
]
}

View File

@@ -12,6 +12,18 @@ target_link_libraries(
z z
) )
if(TARGET mbgl-vendor-dawn)
target_link_libraries(
mbgl-vendor-dawn
INTERFACE
"-framework Metal"
"-framework QuartzCore"
"-framework IOKit"
"-framework IOSurface"
"-framework CoreGraphics"
)
endif()
if(MLN_DARWIN_USE_LIBUV) if(MLN_DARWIN_USE_LIBUV)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUV REQUIRED IMPORTED_TARGET libuv) pkg_check_modules(LIBUV REQUIRED IMPORTED_TARGET libuv)

View File

@@ -0,0 +1,38 @@
#pragma once
#include <mbgl/gfx/headless_backend.hpp>
#include <mbgl/webgpu/renderer_backend.hpp>
#include <memory>
namespace mbgl {
namespace webgpu {
class HeadlessBackend final : public webgpu::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;
// Override WebGPU-specific methods
void* getCurrentTextureView() override;
void* getDepthStencilView() override;
mbgl::Size getFramebufferSize() const override;
private:
void activate() override;
void deactivate() override;
void createOffscreenTextures();
private:
class Impl;
std::unique_ptr<Impl> impl;
bool active = false;
};
} // namespace webgpu
} // namespace mbgl

View File

@@ -69,6 +69,37 @@ if(MLN_WITH_METAL)
) )
endif() endif()
if(MLN_WITH_WEBGPU)
if(APPLE)
target_sources(
mbgl-glfw
PRIVATE ${PROJECT_SOURCE_DIR}/platform/glfw/glfw_webgpu_backend.mm
)
else()
target_sources(
mbgl-glfw
PRIVATE ${PROJECT_SOURCE_DIR}/platform/glfw/glfw_webgpu_backend.cpp
)
endif()
if(TARGET mbgl-vendor-dawn)
target_link_libraries(mbgl-glfw PRIVATE mbgl-vendor-dawn)
endif()
endif()
if(DEFINED MLN_WITH_X11 AND MLN_WITH_X11)
target_compile_definitions(
mbgl-glfw
PRIVATE MLN_WITH_X11=1
)
endif()
if(DEFINED MLN_WITH_WAYLAND AND MLN_WITH_WAYLAND)
target_compile_definitions(
mbgl-glfw
PRIVATE MLN_WITH_WAYLAND=1
)
endif()
target_include_directories( target_include_directories(
mbgl-glfw mbgl-glfw
PRIVATE PRIVATE

View File

@@ -222,6 +222,13 @@ GLFWView::GLFWView(bool fullscreen_,
glfwSetErrorCallback(glfwError); glfwSetErrorCallback(glfwError);
std::srand(static_cast<unsigned int>(std::time(nullptr))); std::srand(static_cast<unsigned int>(std::time(nullptr)));
#if defined(MLN_RENDER_BACKEND_WEBGPU)
#ifdef __linux__
// Force X11 platform for WebGPU compatibility (Dawn doesn't support Wayland yet)
// For now, always use X11 when WebGPU might be used
glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
#endif
#endif
if (!glfwInit()) { if (!glfwInit()) {
mbgl::Log::Error(mbgl::Event::OpenGL, "failed to initialize glfw"); mbgl::Log::Error(mbgl::Event::OpenGL, "failed to initialize glfw");

View File

@@ -9,7 +9,8 @@
#include <utility> #include <utility>
#include <optional> #include <optional>
#if (defined(MLN_RENDER_BACKEND_OPENGL) || defined(MLN_RENDER_BACKEND_VULKAN)) && \ #if (defined(MLN_RENDER_BACKEND_OPENGL) || defined(MLN_RENDER_BACKEND_VULKAN) || \
defined(MLN_RENDER_BACKEND_WEBGPU)) && \
!defined(MBGL_LAYER_CUSTOM_DISABLE_ALL) !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL)
#define ENABLE_LOCATION_INDICATOR #define ENABLE_LOCATION_INDICATOR
#endif #endif

View File

@@ -0,0 +1,4 @@
#ifndef __APPLE__
#define GLFW_WEBGPU_BACKEND_IMPLEMENTATION
#include "glfw_webgpu_backend_impl.ipp"
#endif

View File

@@ -0,0 +1,119 @@
#pragma once
#include "glfw_backend.hpp"
#include <mbgl/webgpu/renderer_backend.hpp>
#include <mbgl/webgpu/context.hpp>
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/gfx/renderer_backend.hpp>
#include <memory>
#include <webgpu/webgpu_cpp.h>
#include <queue>
#include <atomic>
#include <array>
struct GLFWwindow;
struct WGPUDeviceImpl;
struct WGPUSurfaceImpl;
namespace dawn::native {
class Instance;
class Adapter;
} // namespace dawn::native
// Multiple inheritance: GLFWBackend for window management,
// webgpu::RendererBackend for rendering, gfx::Renderable for framebuffer
class GLFWWebGPUBackend final : public GLFWBackend, public mbgl::webgpu::RendererBackend, public mbgl::gfx::Renderable {
public:
GLFWWebGPUBackend(GLFWwindow* window, bool capFrameRate);
~GLFWWebGPUBackend() override;
void swap();
// GLFWBackend implementation
public:
mbgl::gfx::RendererBackend& getRendererBackend() override;
mbgl::Size getSize() const override;
void setSize(mbgl::Size) override;
// mbgl::gfx::RendererBackend implementation
public:
mbgl::gfx::Renderable& getDefaultRenderable() override;
protected:
void activate() override;
void deactivate() override;
// WebGPU-specific methods
public:
// Override virtual methods from RendererBackend
void* getCurrentTextureView() override;
void* getDepthStencilView() override;
mbgl::Size getFramebufferSize() const override;
private:
class SpinLock {
public:
void lock();
void unlock();
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
mutable SpinLock textureStateLock;
GLFWwindow* window;
std::unique_ptr<dawn::native::Instance> instance;
wgpu::Device wgpuDevice; // This owns the device
wgpu::Queue queue;
wgpu::Surface wgpuSurface;
wgpu::TextureFormat swapChainFormat = wgpu::TextureFormat::BGRA8Unorm;
wgpu::TextureFormat depthStencilFormat = wgpu::TextureFormat::Undefined;
std::array<wgpu::TextureFormat, 1> configuredViewFormats{wgpu::TextureFormat::BGRA8Unorm};
// Protect texture view access from multiple threads
wgpu::TextureView currentTextureView; // Keep the current texture view alive
wgpu::Texture currentTexture; // Keep the texture alive as well
// Depth/stencil texture and view
wgpu::Texture depthStencilTexture;
wgpu::TextureView depthStencilView;
// Frame synchronization
std::atomic<bool> frameInProgress{false};
// Track if we have presented the current frame
std::atomic<bool> framePresented{true};
// Track if we're shutting down to prevent mutex usage during destruction
std::atomic<bool> isShuttingDown{false};
// Previous frame resources - keep alive until next swap
wgpu::TextureView previousTextureView;
wgpu::Texture previousTexture;
// Surface state tracking
std::atomic<bool> surfaceConfigured{false};
std::atomic<bool> surfaceNeedsReconfigure{false};
mbgl::Size lastConfiguredSize{0, 0};
// Error recovery
std::atomic<int> consecutiveErrors{0};
static constexpr int maxConsecutiveErrors = 3;
// Helper methods
void reconfigureSurface();
void createDepthStencilTexture(uint32_t width, uint32_t height);
bool waitForFrame(std::chrono::milliseconds timeout = std::chrono::milliseconds(100));
void signalFrameComplete();
void periodicMaintenance();
void processEvents();
// Debug triangle utilities
void ensureDebugTriangleResources();
void drawDebugTriangle(const wgpu::TextureView& targetView);
bool debugTriangleInitialized = false;
wgpu::RenderPipeline debugTrianglePipeline;
wgpu::Buffer debugTriangleVertexBuffer;
uint64_t debugTriangleVertexBufferSize = 0;
};

View File

@@ -0,0 +1,2 @@
#define GLFW_WEBGPU_BACKEND_IMPLEMENTATION
#include "glfw_webgpu_backend_impl.ipp"

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@
MLN_TRIANGULATE_FILL_OUTLINES = 0 : Simple line primitives will be generated. Draw using gfx::Lines MLN_TRIANGULATE_FILL_OUTLINES = 0 : Simple line primitives will be generated. Draw using gfx::Lines
MLN_TRIANGULATE_FILL_OUTLINES = 1 : Generate triangulated lines. Draw using gfx::Triangles and a Line shader. MLN_TRIANGULATE_FILL_OUTLINES = 1 : Generate triangulated lines. Draw using gfx::Triangles and a Line shader.
*/ */
#define MLN_TRIANGULATE_FILL_OUTLINES (MLN_RENDER_BACKEND_METAL) #define MLN_TRIANGULATE_FILL_OUTLINES (MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_WEBGPU)
#if MLN_TRIANGULATE_FILL_OUTLINES #if MLN_TRIANGULATE_FILL_OUTLINES
#include <mbgl/renderer/buckets/line_bucket.hpp> #include <mbgl/renderer/buckets/line_bucket.hpp>

View File

@@ -138,6 +138,40 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters
: evaluate<LineFloorWidth>(parameters), : evaluate<LineFloorWidth>(parameters),
.expressionMask = expressionMask, .expressionMask = expressionMask,
.pad1 = 0}; .pad1 = 0};
#elif MLN_RENDER_BACKEND_WEBGPU
expressionMask = LineExpressionMask::None;
if (evaluated.get<LineColor>().isConstant()) {
expressionMask |= LineExpressionMask::Color;
}
if (evaluated.get<LineBlur>().isConstant()) {
expressionMask |= LineExpressionMask::Blur;
}
if (evaluated.get<LineOpacity>().isConstant()) {
expressionMask |= LineExpressionMask::Opacity;
}
if (evaluated.get<LineGapWidth>().isConstant()) {
expressionMask |= LineExpressionMask::GapWidth;
}
if (evaluated.get<LineOffset>().isConstant()) {
expressionMask |= LineExpressionMask::Offset;
}
if (evaluated.get<LineWidth>().isConstant()) {
expressionMask |= LineExpressionMask::Width;
}
if (evaluated.get<LineFloorWidth>().isConstant()) {
expressionMask |= LineExpressionMask::FloorWidth;
}
const LineEvaluatedPropsUBO propsUBO{
.color = evaluated.get<LineColor>().constantOr(LineColor::defaultValue()),
.blur = evaluated.get<LineBlur>().constantOr(LineBlur::defaultValue()),
.opacity = evaluated.get<LineOpacity>().constantOr(LineOpacity::defaultValue()),
.gapwidth = evaluated.get<LineGapWidth>().constantOr(LineGapWidth::defaultValue()),
.offset = evaluated.get<LineOffset>().constantOr(LineOffset::defaultValue()),
.width = evaluated.get<LineWidth>().constantOr(LineWidth::defaultValue()),
.floorwidth = evaluated.get<LineFloorWidth>().constantOr(LineFloorWidth::defaultValue()),
.expressionMask = expressionMask,
.pad1 = 0};
#else #else
const LineEvaluatedPropsUBO propsUBO{/*color =*/evaluate<LineColor>(parameters), const LineEvaluatedPropsUBO propsUBO{/*color =*/evaluate<LineColor>(parameters),
/*blur =*/evaluate<LineBlur>(parameters), /*blur =*/evaluate<LineBlur>(parameters),

View File

@@ -3,9 +3,9 @@
#include <mbgl/renderer/layer_tweaker.hpp> #include <mbgl/renderer/layer_tweaker.hpp>
#include <mbgl/style/layers/line_layer_properties.hpp> #include <mbgl/style/layers/line_layer_properties.hpp>
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_WEBGPU
#include <mbgl/shaders/line_layer_ubo.hpp> #include <mbgl/shaders/line_layer_ubo.hpp>
#endif // MLN_RENDER_BACKEND_METAL #endif // MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_WEBGPU
#include <string> #include <string>
@@ -68,9 +68,12 @@ protected:
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_METAL
gfx::UniformBufferPtr expressionUniformBuffer; gfx::UniformBufferPtr expressionUniformBuffer;
Unevaluated::GPUExpressions gpuExpressions; Unevaluated::GPUExpressions gpuExpressions;
shaders::LineExpressionMask expressionMask = shaders::LineExpressionMask::None;
bool gpuExpressionsUpdated = true; bool gpuExpressionsUpdated = true;
#endif // MLN_RENDER_BACKEND_METAL #endif // MLN_RENDER_BACKEND_METAL
#if MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_WEBGPU
shaders::LineExpressionMask expressionMask = shaders::LineExpressionMask::None;
#endif
}; };
} // namespace mbgl } // namespace mbgl

View File

@@ -253,9 +253,29 @@ void RenderHeatmapLayer::update(gfx::ShaderRegistry& shaders,
} }
return true; return true;
}; };
#if MLN_RENDER_BACKEND_WEBGPU
bool anyUpdated = false;
bool skippedDrawables = false;
tileLayerGroup->visitDrawables(renderPass, tileID, [&](gfx::Drawable& drawable) {
if (updateExisting(drawable)) {
anyUpdated = true;
} else {
skippedDrawables = true;
}
});
if (!anyUpdated && skippedDrawables) {
removeTile(renderPass, tileID);
}
if (anyUpdated) {
continue;
}
#else
if (updateTile(renderPass, tileID, std::move(updateExisting))) { if (updateTile(renderPass, tileID, std::move(updateExisting))) {
continue; continue;
} }
#endif
if (!propertiesAsUniforms) { if (!propertiesAsUniforms) {
propertiesAsUniforms.emplace(); propertiesAsUniforms.emplace();

View File

@@ -15,6 +15,11 @@
#include <mbgl/shaders/gl/legacy/clipping_mask_program.hpp> #include <mbgl/shaders/gl/legacy/clipping_mask_program.hpp>
#endif #endif
#if MLN_RENDER_BACKEND_WEBGPU
#include <mbgl/webgpu/context.hpp>
#include <mbgl/shaders/webgpu/clipping_mask.hpp>
#endif
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_METAL
#include <mbgl/mtl/context.hpp> #include <mbgl/mtl/context.hpp>
#include <mbgl/shaders/mtl/clipping_mask.hpp> #include <mbgl/shaders/mtl/clipping_mask.hpp>
@@ -155,6 +160,9 @@ void PaintParameters::clearStencil() {
const auto& vulkanRenderPass = static_cast<vulkan::RenderPass&>(*renderPass); const auto& vulkanRenderPass = static_cast<vulkan::RenderPass&>(*renderPass);
vulkanRenderPass.clearStencil(); vulkanRenderPass.clearStencil();
context.renderingStats().stencilClears++;
#elif MLN_RENDER_BACKEND_WEBGPU
// WebGPU clears stencil through render pass descriptor
context.renderingStats().stencilClears++; context.renderingStats().stencilClears++;
#elif MLN_RENDER_BACKEND_OPENGL #elif MLN_RENDER_BACKEND_OPENGL
context.clearStencilBuffer(0b00000000); context.clearStencilBuffer(0b00000000);
@@ -176,7 +184,40 @@ void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) {
clearStencil(); clearStencil();
} }
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_WEBGPU
std::vector<shaders::ClipUBO> tileUBOs;
for (const auto& tileRef : *renderTiles) {
const auto& tileID = tileRef.get().id;
const int32_t stencilID = nextStencilID;
const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID));
if (result.second) {
nextStencilID++;
} else {
continue;
}
if (tileUBOs.empty()) {
tileUBOs.reserve(count);
}
tileUBOs.emplace_back(shaders::ClipUBO{/* .matrix = */ util::cast<float>(matrixForTile(tileID)),
/* .stencil_ref = */ static_cast<uint32_t>(stencilID),
/* .pad1 = */ 0,
/* .pad2 = */ 0,
/* .pad3 = */ 0});
}
if (!tileUBOs.empty()) {
#if !defined(NDEBUG)
const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks");
#endif
auto& webgpuContext = static_cast<webgpu::Context&>(context);
webgpuContext.renderTileClippingMasks(*renderPass, staticData, tileUBOs);
webgpuContext.renderingStats().stencilUpdates++;
}
#elif MLN_RENDER_BACKEND_METAL
// Assign a stencil ID and build a UBO for each tile in the set // Assign a stencil ID and build a UBO for each tile in the set
std::vector<shaders::ClipUBO> tileUBOs; std::vector<shaders::ClipUBO> tileUBOs;
for (const auto& tileRef : *renderTiles) { for (const auto& tileRef : *renderTiles) {

View File

@@ -26,7 +26,7 @@
#include <mbgl/gfx/drawable_tweaker.hpp> #include <mbgl/gfx/drawable_tweaker.hpp>
#include <mbgl/renderer/layer_tweaker.hpp> #include <mbgl/renderer/layer_tweaker.hpp>
#if MLN_RENDER_BACKEND_METAL || (MLN_RENDER_BACKEND_VULKAN && defined(__ANDROID__)) #if MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_WEBGPU || (MLN_RENDER_BACKEND_VULKAN && defined(__ANDROID__))
#define MLN_ENABLE_POLYLINE_DRAWABLES 1 #define MLN_ENABLE_POLYLINE_DRAWABLES 1
#else #else
#define MLN_ENABLE_POLYLINE_DRAWABLES 0 #define MLN_ENABLE_POLYLINE_DRAWABLES 0

View File

@@ -0,0 +1,29 @@
#include <mbgl/shaders/webgpu/background.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/background_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
//
// Background
using BackgroundShaderSource = ShaderSource<BuiltIn::BackgroundShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 1> BackgroundShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short2, idBackgroundPosVertexAttribute},
};
const std::array<TextureInfo, 0> BackgroundShaderSource::textures = {};
//
// Background pattern
using BackgroundPatternShaderSource = ShaderSource<BuiltIn::BackgroundPatternShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 1> BackgroundPatternShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short2, idBackgroundPosVertexAttribute},
};
const std::array<TextureInfo, 1> BackgroundPatternShaderSource::textures = {TextureInfo{0, idBackgroundImageTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,23 @@
#include <mbgl/shaders/webgpu/circle.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/circle_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using CircleShaderSource = ShaderSource<BuiltIn::CircleShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 8> CircleShaderSource::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},
};
const std::array<TextureInfo, 0> CircleShaderSource::textures = {};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,15 @@
#include <mbgl/shaders/webgpu/clipping_mask.hpp>
#include <mbgl/shaders/shader_defines.hpp>
namespace mbgl {
namespace shaders {
using ClippingMaskShaderSource = ShaderSource<BuiltIn::ClippingMaskProgram, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 1> ClippingMaskShaderSource::attributes = {
AttributeInfo{2, gfx::AttributeDataType::Short2, idClippingMaskPosVertexAttribute},
};
const std::array<TextureInfo, 0> ClippingMaskShaderSource::textures = {};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,36 @@
#include <mbgl/shaders/webgpu/collision.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/collision_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
//
// Collision box
using CollisionBoxShaderSource = ShaderSource<BuiltIn::CollisionBoxShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 5> CollisionBoxShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short2, idCollisionPosVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::Short2, idCollisionAnchorPosVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Short2, idCollisionExtrudeVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::UShort2, idCollisionPlacedVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idCollisionShiftVertexAttribute},
};
const std::array<TextureInfo, 0> CollisionBoxShaderSource::textures = {};
//
// Collision circle
using CollisionCircleShaderSource = ShaderSource<BuiltIn::CollisionCircleShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 4> CollisionCircleShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short2, idCollisionPosVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::Short2, idCollisionAnchorPosVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Short2, idCollisionExtrudeVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::UShort2, idCollisionPlacedVertexAttribute},
};
const std::array<TextureInfo, 0> CollisionCircleShaderSource::textures = {};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,19 @@
#include <mbgl/shaders/webgpu/custom_geometry.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/custom_geometry_ubo.hpp>
namespace mbgl {
namespace shaders {
using CustomGeometryShaderSource = ShaderSource<BuiltIn::CustomGeometryShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 2> CustomGeometryShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Float3, idCustomGeometryPosVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::Float2, idCustomGeometryTexVertexAttribute},
};
const std::array<TextureInfo, 1> CustomGeometryShaderSource::textures = {
TextureInfo{0, idCustomGeometryTexture},
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,17 @@
#include <mbgl/shaders/webgpu/custom_symbol_icon.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/custom_drawable_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using CustomSymbolIconShaderSource = ShaderSource<BuiltIn::CustomSymbolIconShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 2> CustomSymbolIconShaderSource::attributes = {
AttributeInfo{4, gfx::AttributeDataType::Float3, idCustomSymbolPosVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Float2, idCustomSymbolTexVertexAttribute},
};
const std::array<TextureInfo, 1> CustomSymbolIconShaderSource::textures = {TextureInfo{0, idCustomSymbolImageTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,16 @@
#include <mbgl/shaders/webgpu/debug.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/debug_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using DebugShaderSource = ShaderSource<BuiltIn::DebugShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 1> DebugShaderSource::attributes = {
AttributeInfo{5, gfx::AttributeDataType::Short2, idDebugPosVertexAttribute},
};
const std::array<TextureInfo, 1> DebugShaderSource::textures = {TextureInfo{0, idDebugOverlayTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,71 @@
#include <mbgl/shaders/webgpu/fill.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/fill_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
//
// Fill
using FillShaderSource = ShaderSource<BuiltIn::FillShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 3> FillShaderSource::attributes = {
AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute},
AttributeInfo{1, gfx::AttributeDataType::Float4, idFillColorVertexAttribute},
AttributeInfo{2, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute},
};
const std::array<TextureInfo, 0> FillShaderSource::textures = {};
//
// Fill outline
using FillOutlineShaderSource = ShaderSource<BuiltIn::FillOutlineShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 3> FillOutlineShaderSource::attributes = {
AttributeInfo{0, gfx::AttributeDataType::Short2, idFillPosVertexAttribute},
AttributeInfo{1, gfx::AttributeDataType::Float4, idFillOutlineColorVertexAttribute},
AttributeInfo{2, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute},
};
const std::array<TextureInfo, 0> FillOutlineShaderSource::textures = {};
//
// Fill pattern
using FillPatternShaderSource = ShaderSource<BuiltIn::FillPatternShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 4> FillPatternShaderSource::attributes = {
AttributeInfo{4, gfx::AttributeDataType::Short2, idFillPosVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::UShort4, idFillPatternFromVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::UShort4, idFillPatternToVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute},
};
const std::array<TextureInfo, 1> FillPatternShaderSource::textures = {TextureInfo{0, idFillImageTexture}};
//
// Fill outline pattern
using FillOutlinePatternShaderSource = ShaderSource<BuiltIn::FillOutlinePatternShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 4> FillOutlinePatternShaderSource::attributes = {
AttributeInfo{4, gfx::AttributeDataType::Short2, idFillPosVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::UShort4, idFillPatternFromVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::UShort4, idFillPatternToVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idFillOpacityVertexAttribute},
};
const std::array<TextureInfo, 1> FillOutlinePatternShaderSource::textures = {TextureInfo{0, idFillImageTexture}};
//
// Fill outline triangulated
using FillOutlineTriangulatedShaderSource =
ShaderSource<BuiltIn::FillOutlineTriangulatedShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 2> FillOutlineTriangulatedShaderSource::attributes = {
AttributeInfo{0, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute},
AttributeInfo{1, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute},
};
const std::array<TextureInfo, 0> FillOutlineTriangulatedShaderSource::textures = {};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,40 @@
#include <mbgl/shaders/webgpu/fill_extrusion.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/fill_extrusion_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
//
// Fill extrusion
using FillExtrusionShaderSource = ShaderSource<BuiltIn::FillExtrusionShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 5> FillExtrusionShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short2, idFillExtrusionPosVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::Short4, idFillExtrusionNormalEdVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Float4, idFillExtrusionColorVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float2, idFillExtrusionBaseVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idFillExtrusionHeightVertexAttribute},
};
const std::array<TextureInfo, 0> FillExtrusionShaderSource::textures = {};
//
// Fill extrusion pattern
using FillExtrusionPatternShaderSource = ShaderSource<BuiltIn::FillExtrusionPatternShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 6> FillExtrusionPatternShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short2, idFillExtrusionPosVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::Short4, idFillExtrusionNormalEdVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Float2, idFillExtrusionBaseVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float2, idFillExtrusionHeightVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::UShort4, idFillExtrusionPatternFromVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::UShort4, idFillExtrusionPatternToVertexAttribute},
};
const std::array<TextureInfo, 1> FillExtrusionPatternShaderSource::textures = {
TextureInfo{0, idFillExtrusionImageTexture},
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,18 @@
#include <mbgl/shaders/webgpu/heatmap.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/heatmap_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using HeatmapShaderSource = ShaderSource<BuiltIn::HeatmapShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 3> HeatmapShaderSource::attributes = {
AttributeInfo{0, gfx::AttributeDataType::Short2, idHeatmapPosVertexAttribute},
AttributeInfo{1, gfx::AttributeDataType::Float2, idHeatmapWeightVertexAttribute},
AttributeInfo{2, gfx::AttributeDataType::Float2, idHeatmapRadiusVertexAttribute},
};
const std::array<TextureInfo, 0> HeatmapShaderSource::textures = {};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,17 @@
#include <mbgl/shaders/webgpu/heatmap_texture.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/heatmap_texture_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using HeatmapTextureShaderSource = ShaderSource<BuiltIn::HeatmapTextureShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 1> HeatmapTextureShaderSource::attributes = {
AttributeInfo{5, gfx::AttributeDataType::Short2, idHeatmapPosVertexAttribute},
};
const std::array<TextureInfo, 2> HeatmapTextureShaderSource::textures = {TextureInfo{0, idHeatmapImageTexture},
TextureInfo{1, idHeatmapColorRampTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,17 @@
#include <mbgl/shaders/webgpu/hillshade.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/hillshade_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using HillshadeShaderSource = ShaderSource<BuiltIn::HillshadeShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 2> HillshadeShaderSource::attributes = {
AttributeInfo{5, gfx::AttributeDataType::Short2, idHillshadePosVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Short2, idHillshadeTexturePosVertexAttribute},
};
const std::array<TextureInfo, 1> HillshadeShaderSource::textures = {TextureInfo{0, idHillshadeImageTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,17 @@
#include <mbgl/shaders/webgpu/hillshade_prepare.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/hillshade_prepare_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using HillshadePrepareShaderSource = ShaderSource<BuiltIn::HillshadePrepareShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 2> HillshadePrepareShaderSource::attributes = {
AttributeInfo{5, gfx::AttributeDataType::Short2, idHillshadePosVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Short2, idHillshadeTexturePosVertexAttribute},
};
const std::array<TextureInfo, 1> HillshadePrepareShaderSource::textures = {TextureInfo{0, idHillshadeImageTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,69 @@
#include <mbgl/shaders/webgpu/line.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/line_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using LineShaderSource = ShaderSource<BuiltIn::LineShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 8> LineShaderSource::attributes = {
AttributeInfo{4, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float4, idLineColorVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute},
AttributeInfo{9, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute},
AttributeInfo{10, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute},
AttributeInfo{11, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute},
};
const std::array<TextureInfo, 0> LineShaderSource::textures = {};
// Line Gradient
using LineGradientShaderSource = ShaderSource<BuiltIn::LineGradientShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 7> LineGradientShaderSource::attributes = {
AttributeInfo{4, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute},
AttributeInfo{9, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute},
AttributeInfo{10, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute},
};
const std::array<TextureInfo, 1> LineGradientShaderSource::textures = {TextureInfo{0, idLineImageTexture}};
// Line Pattern
using LinePatternShaderSource = ShaderSource<BuiltIn::LinePatternShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 9> LinePatternShaderSource::attributes = {
AttributeInfo{4, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute},
AttributeInfo{9, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute},
AttributeInfo{10, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute},
AttributeInfo{11, gfx::AttributeDataType::UShort4, idLinePatternFromVertexAttribute},
AttributeInfo{12, gfx::AttributeDataType::UShort4, idLinePatternToVertexAttribute},
};
const std::array<TextureInfo, 1> LinePatternShaderSource::textures = {TextureInfo{0, idLineImageTexture}};
// Line SDF
using LineSDFShaderSource = ShaderSource<BuiltIn::LineSDFShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 9> LineSDFShaderSource::attributes = {
AttributeInfo{4, gfx::AttributeDataType::Short2, idLinePosNormalVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::UByte4, idLineDataVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float4, idLineColorVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float2, idLineBlurVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::Float2, idLineOpacityVertexAttribute},
AttributeInfo{9, gfx::AttributeDataType::Float2, idLineGapWidthVertexAttribute},
AttributeInfo{10, gfx::AttributeDataType::Float2, idLineOffsetVertexAttribute},
AttributeInfo{11, gfx::AttributeDataType::Float2, idLineWidthVertexAttribute},
AttributeInfo{12, gfx::AttributeDataType::Float2, idLineFloorWidthVertexAttribute},
};
const std::array<TextureInfo, 1> LineSDFShaderSource::textures = {TextureInfo{0, idLineImageTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,32 @@
#include <mbgl/shaders/webgpu/location_indicator.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/location_indicator_ubo.hpp>
namespace mbgl {
namespace shaders {
//
// Location indicator
using LocationIndicatorShaderSource = ShaderSource<BuiltIn::LocationIndicatorShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 1> LocationIndicatorShaderSource::attributes = {
AttributeInfo{5, gfx::AttributeDataType::Float2, idLocationIndicatorPosVertexAttribute},
};
const std::array<TextureInfo, 0> LocationIndicatorShaderSource::textures = {};
//
// Location indicator textured
using LocationIndicatorTexturedShaderSource =
ShaderSource<BuiltIn::LocationIndicatorTexturedShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 2> LocationIndicatorTexturedShaderSource::attributes = {
AttributeInfo{5, gfx::AttributeDataType::Float2, idLocationIndicatorPosVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float2, idLocationIndicatorTexVertexAttribute},
};
const std::array<TextureInfo, 1> LocationIndicatorTexturedShaderSource::textures = {
TextureInfo{0, idLocationIndicatorTexture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,18 @@
#include <mbgl/shaders/webgpu/raster.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/raster_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
using RasterShaderSource = ShaderSource<BuiltIn::RasterShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 2> RasterShaderSource::attributes = {
AttributeInfo{0, gfx::AttributeDataType::Short2, idRasterPosVertexAttribute},
AttributeInfo{1, gfx::AttributeDataType::Short2, idRasterTexturePosVertexAttribute},
};
const std::array<TextureInfo, 2> RasterShaderSource::textures = {TextureInfo{0, idRasterImage0Texture},
TextureInfo{1, idRasterImage1Texture}};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,797 @@
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/webgpu/common.hpp>
#include <mbgl/webgpu/context.hpp>
#include <mbgl/webgpu/renderer_backend.hpp>
#include <mbgl/webgpu/renderable_resource.hpp>
#include <mbgl/webgpu/uniform_buffer.hpp>
#include <mbgl/webgpu/vertex_attribute.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/gfx/stencil_mode.hpp>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <fstream>
#include <map>
#include <type_traits>
#include <mbgl/util/variant.hpp>
namespace mbgl {
namespace webgpu {
namespace {
std::string trimString(std::string_view value) {
auto begin = value.begin();
auto end = value.end();
while (begin != end && std::isspace(static_cast<unsigned char>(*begin))) {
++begin;
}
while (begin != end) {
auto prev = end - 1;
if (!std::isspace(static_cast<unsigned char>(*prev))) {
break;
}
end = prev;
}
return std::string(begin, end);
}
WGPUBlendOperation webgpuBlendOperation(const gfx::ColorBlendEquationType& equation) {
switch (equation) {
case gfx::ColorBlendEquationType::Add:
return WGPUBlendOperation_Add;
case gfx::ColorBlendEquationType::Subtract:
return WGPUBlendOperation_Subtract;
case gfx::ColorBlendEquationType::ReverseSubtract:
return WGPUBlendOperation_ReverseSubtract;
default:
return WGPUBlendOperation_Add;
}
}
WGPUBlendFactor webgpuBlendFactor(const gfx::ColorBlendFactorType& factor) {
switch (factor) {
case gfx::ColorBlendFactorType::Zero:
return WGPUBlendFactor_Zero;
case gfx::ColorBlendFactorType::One:
return WGPUBlendFactor_One;
case gfx::ColorBlendFactorType::SrcColor:
return WGPUBlendFactor_Src;
case gfx::ColorBlendFactorType::OneMinusSrcColor:
return WGPUBlendFactor_OneMinusSrc;
case gfx::ColorBlendFactorType::SrcAlpha:
return WGPUBlendFactor_SrcAlpha;
case gfx::ColorBlendFactorType::OneMinusSrcAlpha:
return WGPUBlendFactor_OneMinusSrcAlpha;
case gfx::ColorBlendFactorType::DstAlpha:
return WGPUBlendFactor_DstAlpha;
case gfx::ColorBlendFactorType::OneMinusDstAlpha:
return WGPUBlendFactor_OneMinusDstAlpha;
case gfx::ColorBlendFactorType::DstColor:
return WGPUBlendFactor_Dst;
case gfx::ColorBlendFactorType::OneMinusDstColor:
return WGPUBlendFactor_OneMinusDst;
case gfx::ColorBlendFactorType::SrcAlphaSaturate:
return WGPUBlendFactor_SrcAlphaSaturated;
case gfx::ColorBlendFactorType::ConstantColor:
return WGPUBlendFactor_Constant;
case gfx::ColorBlendFactorType::OneMinusConstantColor:
return WGPUBlendFactor_OneMinusConstant;
case gfx::ColorBlendFactorType::ConstantAlpha:
return WGPUBlendFactor_Constant;
case gfx::ColorBlendFactorType::OneMinusConstantAlpha:
return WGPUBlendFactor_OneMinusConstant;
default:
return WGPUBlendFactor_One;
}
}
WGPUPrimitiveTopology toPrimitiveTopology(gfx::DrawModeType drawModeType) {
switch (drawModeType) {
case gfx::DrawModeType::Points:
return WGPUPrimitiveTopology_PointList;
case gfx::DrawModeType::Lines:
case gfx::DrawModeType::LineLoop:
return WGPUPrimitiveTopology_LineList;
case gfx::DrawModeType::LineStrip:
return WGPUPrimitiveTopology_LineStrip;
case gfx::DrawModeType::TriangleStrip:
return WGPUPrimitiveTopology_TriangleStrip;
case gfx::DrawModeType::TriangleFan:
// WebGPU does not support triangle fan directly; approximate with triangle list
return WGPUPrimitiveTopology_TriangleList;
case gfx::DrawModeType::Triangles:
default:
return WGPUPrimitiveTopology_TriangleList;
}
}
WGPUCompareFunction toCompareFunction(const gfx::DepthFunctionType func) {
switch (func) {
case gfx::DepthFunctionType::Never:
return WGPUCompareFunction_Never;
case gfx::DepthFunctionType::Less:
return WGPUCompareFunction_Less;
case gfx::DepthFunctionType::Equal:
return WGPUCompareFunction_Equal;
case gfx::DepthFunctionType::LessEqual:
return WGPUCompareFunction_LessEqual;
case gfx::DepthFunctionType::Greater:
return WGPUCompareFunction_Greater;
case gfx::DepthFunctionType::NotEqual:
return WGPUCompareFunction_NotEqual;
case gfx::DepthFunctionType::GreaterEqual:
return WGPUCompareFunction_GreaterEqual;
case gfx::DepthFunctionType::Always:
default:
return WGPUCompareFunction_Always;
}
}
WGPUCompareFunction toCompareFunction(const gfx::StencilFunctionType func) {
switch (func) {
case gfx::StencilFunctionType::Never:
return WGPUCompareFunction_Never;
case gfx::StencilFunctionType::Less:
return WGPUCompareFunction_Less;
case gfx::StencilFunctionType::Equal:
return WGPUCompareFunction_Equal;
case gfx::StencilFunctionType::LessEqual:
return WGPUCompareFunction_LessEqual;
case gfx::StencilFunctionType::Greater:
return WGPUCompareFunction_Greater;
case gfx::StencilFunctionType::NotEqual:
return WGPUCompareFunction_NotEqual;
case gfx::StencilFunctionType::GreaterEqual:
return WGPUCompareFunction_GreaterEqual;
case gfx::StencilFunctionType::Always:
default:
return WGPUCompareFunction_Always;
}
}
WGPUStencilOperation toStencilOp(const gfx::StencilOpType op) {
switch (op) {
case gfx::StencilOpType::Zero:
return WGPUStencilOperation_Zero;
case gfx::StencilOpType::Keep:
return WGPUStencilOperation_Keep;
case gfx::StencilOpType::Replace:
return WGPUStencilOperation_Replace;
case gfx::StencilOpType::Increment:
return WGPUStencilOperation_IncrementClamp;
case gfx::StencilOpType::Decrement:
return WGPUStencilOperation_DecrementClamp;
case gfx::StencilOpType::Invert:
return WGPUStencilOperation_Invert;
case gfx::StencilOpType::IncrementWrap:
return WGPUStencilOperation_IncrementWrap;
case gfx::StencilOpType::DecrementWrap:
return WGPUStencilOperation_DecrementWrap;
default:
return WGPUStencilOperation_Keep;
}
}
} // namespace
ShaderProgram::ShaderProgram(std::string name,
RendererBackend& backend_,
WGPUShaderModule vertexModule,
WGPUShaderModule fragmentModule)
: ShaderProgramBase(),
shaderName(std::move(name)),
backend(backend_),
vertexShaderModule(vertexModule),
fragmentShaderModule(fragmentModule) {
createPipelineLayout({}, {});
}
// Minimal constructor for Context::createShader compatibility
ShaderProgram::ShaderProgram(Context& context, const std::string& vertexSource, const std::string& fragmentSource)
: ShaderProgramBase(),
shaderName("ContextShader"),
backend(static_cast<RendererBackend&>(context.getBackend())) {
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
if (!device) return;
std::string trimmedVertex = trimString(vertexSource);
std::string trimmedFragment = trimString(fragmentSource);
hasVertexEntryPoint = !trimmedVertex.empty();
hasFragmentEntryPoint = !trimmedFragment.empty();
// Get the prelude from common.hpp
using PreludeShader = shaders::ShaderSource<shaders::BuiltIn::Prelude, gfx::Backend::Type::WebGPU>;
std::string vertexWithPrelude;
if (hasVertexEntryPoint) {
vertexWithPrelude = std::string(PreludeShader::prelude) + "\n" + vertexSource;
WGPUShaderSourceWGSL wgslDesc = {};
wgslDesc.chain.sType = WGPUSType_ShaderSourceWGSL;
wgslDesc.chain.next = nullptr;
WGPUStringView vertexCode = {vertexWithPrelude.c_str(), vertexWithPrelude.length()};
wgslDesc.code = vertexCode;
WGPUShaderModuleDescriptor vertexShaderDesc = {};
WGPUStringView vertexLabel = {"Vertex Shader Module", strlen("Vertex Shader Module")};
vertexShaderDesc.label = vertexLabel;
vertexShaderDesc.nextInChain = (WGPUChainedStruct*)&wgslDesc;
vertexShaderModule = wgpuDeviceCreateShaderModule(device, &vertexShaderDesc);
if (!vertexShaderModule) {
Log::Error(Event::Render, "Failed to create vertex shader module for " + shaderName);
Log::Error(Event::Render, "Vertex shader source length: " + std::to_string(vertexWithPrelude.length()));
if (shaderName.find("CircleShader") != std::string::npos) {
std::ofstream dump("/tmp/circle_vertex.wgsl", std::ios::trunc);
dump << vertexSource;
}
}
}
std::string fragmentWithPrelude;
if (hasFragmentEntryPoint) {
fragmentWithPrelude = std::string(PreludeShader::prelude) + "\n" + fragmentSource;
WGPUShaderSourceWGSL wgslDesc = {};
wgslDesc.chain.sType = WGPUSType_ShaderSourceWGSL;
wgslDesc.chain.next = nullptr;
WGPUStringView fragmentCode = {fragmentWithPrelude.c_str(), fragmentWithPrelude.length()};
wgslDesc.code = fragmentCode;
WGPUShaderModuleDescriptor fragmentShaderDesc = {};
WGPUStringView fragmentLabel = {"Fragment Shader Module", strlen("Fragment Shader Module")};
fragmentShaderDesc.label = fragmentLabel;
fragmentShaderDesc.nextInChain = (WGPUChainedStruct*)&wgslDesc;
fragmentShaderModule = wgpuDeviceCreateShaderModule(device, &fragmentShaderDesc);
if (!fragmentShaderModule) {
Log::Error(Event::Render, "Failed to create fragment shader module for " + shaderName);
Log::Error(Event::Render, "Fragment shader source length: " + std::to_string(fragmentWithPrelude.length()));
if (shaderName.find("CircleShader") != std::string::npos) {
std::ofstream dump("/tmp/circle_fragment.wgsl", std::ios::trunc);
dump << fragmentSource;
}
}
}
createPipelineLayout(vertexWithPrelude, fragmentWithPrelude);
}
ShaderProgram::~ShaderProgram() {
// Release cached pipelines
for (auto& pair : renderPipelineCache) {
if (pair.second) {
wgpuRenderPipelineRelease(pair.second);
}
}
renderPipelineCache.clear();
// Release resources
if (pipelineLayout) {
wgpuPipelineLayoutRelease(pipelineLayout);
pipelineLayout = nullptr;
}
for (auto layout : bindGroupLayouts) {
if (layout) {
wgpuBindGroupLayoutRelease(layout);
}
}
bindGroupLayouts.clear();
bindGroupOrder.clear();
bindingsPerGroup.clear();
// Release shader modules if we created them
if (vertexShaderModule) {
wgpuShaderModuleRelease(vertexShaderModule);
vertexShaderModule = nullptr;
}
if (fragmentShaderModule) {
wgpuShaderModuleRelease(fragmentShaderModule);
fragmentShaderModule = nullptr;
}
}
WGPURenderPipeline ShaderProgram::getRenderPipeline(const gfx::Renderable& renderable,
const WGPUVertexBufferLayout* vertexLayouts,
uint32_t vertexLayoutCount,
const gfx::ColorMode& colorMode,
const gfx::DepthMode& depthMode,
const gfx::StencilMode& stencilMode,
gfx::DrawModeType drawModeType,
const std::optional<std::size_t> reuseHash) {
(void)renderable;
// Check cache first
if (reuseHash.has_value()) {
auto it = renderPipelineCache.find(reuseHash.value());
if (it != renderPipelineCache.end()) {
return it->second;
}
}
// Create new pipeline
WGPURenderPipeline pipeline = createPipeline(
vertexLayouts, vertexLayoutCount, colorMode, depthMode, stencilMode, drawModeType);
// Cache the pipeline if we have a reuse hash
if (reuseHash.has_value() && pipeline) {
renderPipelineCache[reuseHash.value()] = pipeline;
}
return pipeline;
}
std::optional<size_t> ShaderProgram::getSamplerLocation(const size_t id) const {
return (id < textureBindings.size()) ? textureBindings[id] : std::nullopt;
}
void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) {
const auto index = static_cast<int>(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); });
#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<int>(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); });
#endif
instanceAttributes.set(info.id, index, info.dataType, 1);
}
void ShaderProgram::initTexture(const shaders::TextureInfo& info) {
assert(info.id < textureBindings.size());
if (info.id >= textureBindings.size()) {
return;
}
textureBindings[info.id] = info.index;
}
const std::vector<ShaderProgram::BindingInfo>& ShaderProgram::getBindingInfosForGroup(uint32_t group) const {
for (size_t i = 0; i < bindGroupOrder.size(); ++i) {
if (bindGroupOrder[i] == group) {
return bindingsPerGroup[i];
}
}
static const std::vector<BindingInfo> empty;
return empty;
}
WGPUBindGroupLayout ShaderProgram::getBindGroupLayout(uint32_t group) const {
for (size_t i = 0; i < bindGroupOrder.size(); ++i) {
if (bindGroupOrder[i] == group) {
return bindGroupLayouts[i];
}
}
return nullptr;
}
void ShaderProgram::createPipelineLayout(const std::string& vertexSource, const std::string& fragmentSource) {
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
if (!device) {
return;
}
if (!vertexSource.empty() || !fragmentSource.empty()) {
bindingInfos.clear();
if (!vertexSource.empty()) {
analyzeShaderBindings(vertexSource, WGPUShaderStage_Vertex);
}
if (!fragmentSource.empty()) {
analyzeShaderBindings(fragmentSource, WGPUShaderStage_Fragment);
}
}
rebuildBindGroupLayouts();
if (pipelineLayout) {
wgpuPipelineLayoutRelease(pipelineLayout);
pipelineLayout = nullptr;
}
std::vector<WGPUBindGroupLayout> layoutHandles;
layoutHandles.reserve(bindGroupLayouts.size());
for (const auto& layout : bindGroupLayouts) {
layoutHandles.push_back(layout);
}
WGPUPipelineLayoutDescriptor pipelineLayoutDesc = {};
WGPUStringView pipelineLayoutLabel = {"Pipeline Layout", strlen("Pipeline Layout")};
pipelineLayoutDesc.label = pipelineLayoutLabel;
pipelineLayoutDesc.bindGroupLayoutCount = layoutHandles.size();
pipelineLayoutDesc.bindGroupLayouts = layoutHandles.empty() ? nullptr : layoutHandles.data();
pipelineLayout = wgpuDeviceCreatePipelineLayout(device, &pipelineLayoutDesc);
}
void ShaderProgram::analyzeShaderBindings(const std::string& source, WGPUShaderStage stage) {
size_t searchPos = 0;
while (true) {
size_t groupToken = source.find("@group", searchPos);
if (groupToken == std::string::npos) {
break;
}
size_t groupParen = source.find('(', groupToken);
if (groupParen == std::string::npos) {
break;
}
size_t groupStart = groupParen + 1;
while (groupStart < source.size() && std::isspace(static_cast<unsigned char>(source[groupStart]))) {
++groupStart;
}
size_t groupEnd = groupStart;
while (groupEnd < source.size() && std::isdigit(static_cast<unsigned char>(source[groupEnd]))) {
++groupEnd;
}
if (groupEnd == groupStart) {
searchPos = groupEnd;
continue;
}
uint32_t group = static_cast<uint32_t>(
std::strtoul(source.substr(groupStart, groupEnd - groupStart).c_str(), nullptr, 10));
size_t bindingToken = source.find("@binding", groupEnd);
if (bindingToken == std::string::npos) {
break;
}
size_t bindingParen = source.find('(', bindingToken);
if (bindingParen == std::string::npos) {
break;
}
size_t bindingStart = bindingParen + 1;
while (bindingStart < source.size() && std::isspace(static_cast<unsigned char>(source[bindingStart]))) {
++bindingStart;
}
size_t bindingEnd = bindingStart;
while (bindingEnd < source.size() && std::isdigit(static_cast<unsigned char>(source[bindingEnd]))) {
++bindingEnd;
}
if (bindingEnd == bindingStart) {
searchPos = bindingEnd;
continue;
}
uint32_t binding = static_cast<uint32_t>(
std::strtoul(source.substr(bindingStart, bindingEnd - bindingStart).c_str(), nullptr, 10));
size_t varPos = source.find("var", bindingEnd);
if (varPos == std::string::npos) {
break;
}
if (varPos > bindingEnd && std::isalpha(static_cast<unsigned char>(source[varPos - 1]))) {
searchPos = varPos + 3;
continue;
}
size_t afterVar = varPos + 3;
while (afterVar < source.size() && std::isspace(static_cast<unsigned char>(source[afterVar]))) {
++afterVar;
}
BindingType type = BindingType::UniformBuffer;
if (afterVar < source.size() && source[afterVar] == '<') {
size_t qualifierEnd = source.find('>', afterVar);
if (qualifierEnd == std::string::npos) {
break;
}
const auto qualifier = trimString(
std::string_view(source.data() + afterVar + 1, qualifierEnd - afterVar - 1));
if (qualifier.find("storage") != std::string::npos) {
if (qualifier.find("read_write") != std::string::npos) {
type = BindingType::StorageBuffer;
} else {
type = BindingType::ReadOnlyStorageBuffer;
}
} else {
type = BindingType::UniformBuffer;
}
afterVar = qualifierEnd + 1;
}
size_t colonPos = source.find(':', afterVar);
if (colonPos == std::string::npos) {
break;
}
size_t typeStart = colonPos + 1;
size_t typeEnd = typeStart;
while (typeEnd < source.size()) {
char c = source[typeEnd];
if (c == ';' || c == '\n' || c == '\r') {
break;
}
++typeEnd;
}
const auto typeName = trimString(std::string_view(source.data() + typeStart, typeEnd - typeStart));
if (type == BindingType::UniformBuffer) {
if (typeName.find("sampler") != std::string::npos) {
type = BindingType::Sampler;
} else if (typeName.find("texture") != std::string::npos) {
type = BindingType::Texture;
}
}
auto existing = std::find_if(bindingInfos.begin(), bindingInfos.end(), [&](const BindingInfo& info) {
return info.group == group && info.binding == binding;
});
if (existing == bindingInfos.end()) {
bindingInfos.push_back({group, binding, type, stage});
} else {
existing->visibility |= stage;
if (existing->type == BindingType::UniformBuffer && type != BindingType::UniformBuffer) {
existing->type = type;
}
}
searchPos = typeEnd;
}
}
void ShaderProgram::rebuildBindGroupLayouts() {
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
if (!device) {
return;
}
for (auto layout : bindGroupLayouts) {
if (layout) {
wgpuBindGroupLayoutRelease(layout);
}
}
bindGroupLayouts.clear();
bindGroupOrder.clear();
bindingsPerGroup.clear();
if (bindingInfos.empty()) {
bindingInfos.push_back({0u, 0u, BindingType::UniformBuffer, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment});
}
std::map<uint32_t, std::vector<BindingInfo>> grouped;
for (const auto& info : bindingInfos) {
grouped[info.group].push_back(info);
}
for (auto& entry : grouped) {
auto& infos = entry.second;
std::sort(infos.begin(), infos.end(), [](const BindingInfo& lhs, const BindingInfo& rhs) {
return lhs.binding < rhs.binding;
});
std::vector<WGPUBindGroupLayoutEntry> layoutEntries;
layoutEntries.reserve(infos.size());
for (const auto& info : infos) {
WGPUBindGroupLayoutEntry layoutEntry = {};
layoutEntry.binding = info.binding;
layoutEntry.visibility = info.visibility;
switch (info.type) {
case BindingType::UniformBuffer:
layoutEntry.buffer.type = WGPUBufferBindingType_Uniform;
layoutEntry.buffer.hasDynamicOffset = 0;
layoutEntry.buffer.minBindingSize = 0;
break;
case BindingType::ReadOnlyStorageBuffer:
layoutEntry.buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
layoutEntry.buffer.hasDynamicOffset = 0;
layoutEntry.buffer.minBindingSize = 0;
break;
case BindingType::StorageBuffer:
layoutEntry.buffer.type = WGPUBufferBindingType_Storage;
layoutEntry.buffer.hasDynamicOffset = 0;
layoutEntry.buffer.minBindingSize = 0;
break;
case BindingType::Sampler:
layoutEntry.sampler.type = WGPUSamplerBindingType_Filtering;
break;
case BindingType::Texture:
layoutEntry.texture.sampleType = WGPUTextureSampleType_Float;
layoutEntry.texture.viewDimension = WGPUTextureViewDimension_2D;
layoutEntry.texture.multisampled = false;
break;
}
layoutEntries.push_back(layoutEntry);
}
WGPUBindGroupLayoutDescriptor desc = {};
const std::string labelStr = "Shader Bind Group " + std::to_string(entry.first);
WGPUStringView labelView = {labelStr.c_str(), labelStr.length()};
desc.label = labelView;
desc.entryCount = layoutEntries.size();
desc.entries = layoutEntries.data();
WGPUBindGroupLayout layout = wgpuDeviceCreateBindGroupLayout(device, &desc);
if (layout) {
bindGroupOrder.push_back(entry.first);
bindingsPerGroup.push_back(infos);
bindGroupLayouts.push_back(layout);
}
}
}
WGPURenderPipeline ShaderProgram::createPipeline(const WGPUVertexBufferLayout* vertexLayouts,
uint32_t vertexLayoutCount,
const gfx::ColorMode& colorMode,
const gfx::DepthMode& depthMode,
const gfx::StencilMode& stencilMode,
gfx::DrawModeType drawModeType) {
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
if (!hasVertexEntryPoint || !hasFragmentEntryPoint) {
if (!loggedMissingEntryPoint) {
Log::Warning(
Event::Render,
"WebGPU: shader '" + shaderName + "' is missing WGSL entry points; skipping pipeline creation");
loggedMissingEntryPoint = true;
}
return nullptr;
}
if (!device || !vertexShaderModule || !fragmentShaderModule || !pipelineLayout) {
Log::Error(Event::Render,
"createPipeline missing requirement: device=" + std::to_string(device != nullptr) +
", vertexModule=" + std::to_string(vertexShaderModule != nullptr) +
", fragmentModule=" + std::to_string(fragmentShaderModule != nullptr) +
", pipelineLayout=" + std::to_string(pipelineLayout != nullptr));
return nullptr;
}
// Set up vertex state
WGPUVertexState vertexState = {};
vertexState.module = vertexShaderModule;
WGPUStringView vertexEntryPoint = {"main", strlen("main")};
vertexState.entryPoint = vertexEntryPoint;
vertexState.bufferCount = vertexLayoutCount;
vertexState.buffers = vertexLayouts;
// Set up fragment state with color mode blending
WGPUBlendComponent alphaBlend = {};
alphaBlend.operation = webgpuBlendOperation(gfx::ColorBlendEquationType::Add);
alphaBlend.srcFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::SrcAlpha);
alphaBlend.dstFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::OneMinusSrcAlpha);
WGPUBlendComponent colorBlend = {};
colorBlend.operation = webgpuBlendOperation(gfx::ColorBlendEquationType::Add);
colorBlend.srcFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::SrcAlpha);
colorBlend.dstFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::OneMinusSrcAlpha);
// Apply color mode blending if not replace
if (!colorMode.blendFunction.is<gfx::ColorMode::Replace>()) {
colorMode.blendFunction.match([&](const auto& blendFunction) {
colorBlend.operation = webgpuBlendOperation(gfx::ColorBlendEquationType(blendFunction.equation));
colorBlend.srcFactor = webgpuBlendFactor(gfx::ColorBlendFactorType(blendFunction.srcFactor));
colorBlend.dstFactor = webgpuBlendFactor(gfx::ColorBlendFactorType(blendFunction.dstFactor));
alphaBlend = colorBlend;
});
}
WGPUBlendState blendState = {};
blendState.color = colorBlend;
blendState.alpha = alphaBlend;
WGPUColorTargetState colorTarget = {};
auto colorFormat = backend.getColorFormat();
if (colorFormat == wgpu::TextureFormat::Undefined) {
colorFormat = wgpu::TextureFormat::BGRA8Unorm;
}
colorTarget.format = static_cast<WGPUTextureFormat>(colorFormat);
colorTarget.blend = &blendState;
colorTarget.writeMask = (colorMode.mask.r ? WGPUColorWriteMask_Red : WGPUColorWriteMask_None) |
(colorMode.mask.g ? WGPUColorWriteMask_Green : WGPUColorWriteMask_None) |
(colorMode.mask.b ? WGPUColorWriteMask_Blue : WGPUColorWriteMask_None) |
(colorMode.mask.a ? WGPUColorWriteMask_Alpha : WGPUColorWriteMask_None);
WGPUFragmentState fragmentState = {};
fragmentState.module = fragmentShaderModule;
WGPUStringView fragmentEntryPoint = {"main", strlen("main")};
fragmentState.entryPoint = fragmentEntryPoint;
fragmentState.targetCount = 1;
fragmentState.targets = &colorTarget;
// Set up primitive state
WGPUPrimitiveState primitiveState = {};
primitiveState.topology = toPrimitiveTopology(drawModeType);
primitiveState.stripIndexFormat = (drawModeType == gfx::DrawModeType::LineStrip ||
drawModeType == gfx::DrawModeType::TriangleStrip)
? WGPUIndexFormat_Uint16
: WGPUIndexFormat_Undefined;
primitiveState.frontFace = WGPUFrontFace_CCW;
primitiveState.cullMode = WGPUCullMode_None;
// Set up depth stencil state
WGPUDepthStencilState depthStencilState = {};
const auto depthFormat = backend.getDepthStencilFormat();
if (depthFormat != wgpu::TextureFormat::Undefined) {
depthStencilState.format = static_cast<WGPUTextureFormat>(depthFormat);
depthStencilState.depthWriteEnabled = depthMode.mask == gfx::DepthMaskType::ReadWrite ? WGPUOptionalBool_True
: WGPUOptionalBool_False;
depthStencilState.depthCompare = toCompareFunction(depthMode.func);
depthStencilState.stencilFront.compare = WGPUCompareFunction_Always;
depthStencilState.stencilFront.failOp = WGPUStencilOperation_Keep;
depthStencilState.stencilFront.depthFailOp = WGPUStencilOperation_Keep;
depthStencilState.stencilFront.passOp = WGPUStencilOperation_Keep;
depthStencilState.stencilBack = depthStencilState.stencilFront;
depthStencilState.stencilReadMask = 0;
depthStencilState.stencilWriteMask = 0;
bool stencilEnabled = true;
if (stencilMode.test.is<gfx::StencilMode::Always>() && stencilMode.mask == 0 &&
stencilMode.fail == gfx::StencilOpType::Keep && stencilMode.depthFail == gfx::StencilOpType::Keep &&
stencilMode.pass == gfx::StencilOpType::Keep) {
stencilEnabled = false;
}
if (stencilEnabled) {
gfx::StencilFunctionType function = gfx::StencilFunctionType::Always;
uint32_t readMask = 0xFF;
mapbox::util::apply_visitor(
[&](const auto& test) {
using TestType = std::decay_t<decltype(test)>;
function = TestType::func;
if constexpr (requires { test.mask; }) {
readMask = test.mask;
} else {
readMask = 0xFF;
}
},
stencilMode.test);
depthStencilState.stencilFront.compare = toCompareFunction(function);
depthStencilState.stencilBack = depthStencilState.stencilFront;
depthStencilState.stencilFront.failOp = toStencilOp(stencilMode.fail);
depthStencilState.stencilFront.depthFailOp = toStencilOp(stencilMode.depthFail);
depthStencilState.stencilFront.passOp = toStencilOp(stencilMode.pass);
depthStencilState.stencilBack = depthStencilState.stencilFront;
depthStencilState.stencilReadMask = readMask;
depthStencilState.stencilWriteMask = stencilMode.mask;
}
if (shaderName == "ClippingMaskProgram") {
depthStencilState.stencilFront.passOp = WGPUStencilOperation_Replace;
depthStencilState.stencilBack = depthStencilState.stencilFront;
if (depthStencilState.stencilWriteMask == 0) {
depthStencilState.stencilWriteMask = 0xFF;
}
if (depthStencilState.stencilReadMask == 0) {
depthStencilState.stencilReadMask = 0xFF;
}
depthStencilState.stencilFront.compare = WGPUCompareFunction_Always;
depthStencilState.stencilBack = depthStencilState.stencilFront;
}
}
// Create render pipeline
WGPURenderPipelineDescriptor pipelineDesc = {};
WGPUStringView pipelineLabel = {shaderName.c_str(), shaderName.length()};
pipelineDesc.label = pipelineLabel;
pipelineDesc.layout = pipelineLayout;
pipelineDesc.vertex = vertexState;
pipelineDesc.fragment = &fragmentState;
pipelineDesc.primitive = primitiveState;
pipelineDesc.depthStencil = depthFormat != wgpu::TextureFormat::Undefined ? &depthStencilState : nullptr;
pipelineDesc.multisample.count = 1;
pipelineDesc.multisample.mask = 0xFFFFFFFF;
pipelineDesc.multisample.alphaToCoverageEnabled = 0;
WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &pipelineDesc);
if (!pipeline) {
Log::Error(Event::Render, shaderName + " pipeline creation failed");
}
return pipeline;
}
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,65 @@
#include <mbgl/shaders/webgpu/symbol.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/symbol_layer_ubo.hpp>
namespace mbgl {
namespace shaders {
// Symbol Icon
using SymbolIconShaderSource = ShaderSource<BuiltIn::SymbolIconShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 6> SymbolIconShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Short4, idSymbolPixelOffsetVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float, idSymbolFadeOpacityVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::Float, idSymbolOpacityVertexAttribute},
};
const std::array<TextureInfo, 1> SymbolIconShaderSource::textures = {
TextureInfo{0, idSymbolImageTexture},
};
// Symbol SDF
using SymbolSDFShaderSource = ShaderSource<BuiltIn::SymbolSDFShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 10> SymbolSDFShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Short4, idSymbolPixelOffsetVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float, idSymbolFadeOpacityVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::Float4, idSymbolColorVertexAttribute},
AttributeInfo{9, gfx::AttributeDataType::Float4, idSymbolHaloColorVertexAttribute},
AttributeInfo{10, gfx::AttributeDataType::Float, idSymbolOpacityVertexAttribute},
AttributeInfo{11, gfx::AttributeDataType::Float, idSymbolHaloWidthVertexAttribute},
AttributeInfo{12, gfx::AttributeDataType::Float, idSymbolHaloBlurVertexAttribute},
};
const std::array<TextureInfo, 1> SymbolSDFShaderSource::textures = {
TextureInfo{0, idSymbolImageTexture},
};
// Symbol Text and Icon
using SymbolTextAndIconShaderSource = ShaderSource<BuiltIn::SymbolTextAndIconShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 9> SymbolTextAndIconShaderSource::attributes = {
AttributeInfo{3, gfx::AttributeDataType::Short4, idSymbolPosOffsetVertexAttribute},
AttributeInfo{4, gfx::AttributeDataType::UShort4, idSymbolDataVertexAttribute},
AttributeInfo{5, gfx::AttributeDataType::Float3, idSymbolProjectedPosVertexAttribute},
AttributeInfo{6, gfx::AttributeDataType::Float, idSymbolFadeOpacityVertexAttribute},
AttributeInfo{7, gfx::AttributeDataType::Float4, idSymbolColorVertexAttribute},
AttributeInfo{8, gfx::AttributeDataType::Float4, idSymbolHaloColorVertexAttribute},
AttributeInfo{9, gfx::AttributeDataType::Float, idSymbolOpacityVertexAttribute},
AttributeInfo{10, gfx::AttributeDataType::Float, idSymbolHaloWidthVertexAttribute},
AttributeInfo{11, gfx::AttributeDataType::Float, idSymbolHaloBlurVertexAttribute},
};
const std::array<TextureInfo, 2> SymbolTextAndIconShaderSource::textures = {
TextureInfo{0, idSymbolImageTexture},
TextureInfo{2, idSymbolImageIconTexture},
};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,115 @@
#include <mbgl/shaders/webgpu/wgsl_preprocessor.hpp>
#include <cctype>
#include <sstream>
#include <stack>
#include <unordered_map>
namespace mbgl {
namespace webgpu {
namespace detail {
bool isDirective(const std::string& line, const std::string& directive) {
std::size_t pos = 0;
while (pos < line.size() && std::isspace(static_cast<unsigned char>(line[pos]))) {
++pos;
}
if (pos >= line.size() || line[pos] != '#') {
return false;
}
++pos;
while (pos < line.size() && std::isspace(static_cast<unsigned char>(line[pos]))) {
++pos;
}
if (line.compare(pos, directive.size(), directive) != 0) {
return false;
}
pos += directive.size();
return pos == line.size() || std::isspace(static_cast<unsigned char>(line[pos]));
}
std::string getDirectiveArgument(const std::string& line) {
auto hash = line.find('#');
if (hash == std::string::npos) {
return {};
}
auto pos = line.find_first_not_of(" \t", hash + 1);
pos = line.find_first_of(" \t", pos);
if (pos == std::string::npos) {
return {};
}
pos = line.find_first_not_of(" \t", pos);
if (pos == std::string::npos) {
return {};
}
const auto end = line.find_first_of(" \t\r\n", pos);
return line.substr(pos, end == std::string::npos ? std::string::npos : end - pos);
}
std::string preprocessWGSL(const std::string& source, const std::unordered_map<std::string, bool>& defines) {
std::stringstream input(source);
std::string line;
std::string output;
output.reserve(source.size());
struct ConditionalFrame {
bool parentActive;
bool condition;
bool elseSeen;
};
std::stack<ConditionalFrame> stack;
bool currentActive = true;
while (std::getline(input, line)) {
if (isDirective(line, "ifdef")) {
const auto symbol = getDirectiveArgument(line);
const bool condition = defines.contains(symbol);
stack.push(ConditionalFrame{currentActive, condition, false});
currentActive = currentActive && condition;
continue;
}
if (isDirective(line, "ifndef")) {
const auto symbol = getDirectiveArgument(line);
const bool condition = !defines.contains(symbol);
stack.push(ConditionalFrame{currentActive, condition, false});
currentActive = currentActive && condition;
continue;
}
if (isDirective(line, "else")) {
if (stack.empty()) {
continue;
}
auto& frame = stack.top();
if (!frame.elseSeen) {
frame.elseSeen = true;
currentActive = frame.parentActive && !frame.condition;
} else {
currentActive = false;
}
continue;
}
if (isDirective(line, "endif")) {
if (stack.empty()) {
continue;
}
currentActive = stack.top().parentActive;
stack.pop();
continue;
}
if (currentActive) {
output.append(line);
output.push_back('\n');
}
}
return output;
}
} // namespace detail
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,26 @@
#include <mbgl/shaders/webgpu/widevector.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/widevector_ubo.hpp>
namespace mbgl {
namespace shaders {
using WideVectorShaderSource = ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::WebGPU>;
const std::array<AttributeInfo, 3> WideVectorShaderSource::attributes = {
AttributeInfo{wideVectorUBOCount + 0, gfx::AttributeDataType::Float3, idWideVectorScreenPos},
AttributeInfo{wideVectorUBOCount + 1, gfx::AttributeDataType::Float4, idWideVectorColor},
AttributeInfo{wideVectorUBOCount + 2, gfx::AttributeDataType::Int, idWideVectorIndex},
};
const std::array<AttributeInfo, 4> WideVectorShaderSource::instanceAttributes = {
AttributeInfo{wideVectorUBOCount + 3, gfx::AttributeDataType::Float3, idWideVectorInstanceCenter},
AttributeInfo{wideVectorUBOCount + 4, gfx::AttributeDataType::Float4, idWideVectorInstanceColor},
AttributeInfo{wideVectorUBOCount + 5, gfx::AttributeDataType::Int, idWideVectorInstancePrevious},
AttributeInfo{wideVectorUBOCount + 6, gfx::AttributeDataType::Int, idWideVectorInstanceNext},
};
const std::array<TextureInfo, 0> WideVectorShaderSource::textures = {};
} // namespace shaders
} // namespace mbgl

View File

@@ -0,0 +1,149 @@
#include <mbgl/webgpu/buffer_resource.hpp>
#include <mbgl/webgpu/context.hpp>
#include <mbgl/webgpu/renderer_backend.hpp>
#include <cstring>
#include <cassert>
namespace mbgl {
namespace webgpu {
BufferResource::BufferResource(
Context& context_, const void* data, std::size_t size_, uint32_t usage_, bool isIndexBuffer_, bool persistent_)
: context(context_),
size(size_),
usage(usage_),
isIndexBuffer(isIndexBuffer_),
persistent(persistent_) {
if (size == 0) {
return;
}
// Store raw data if provided
if (data) {
raw.resize(size);
std::memcpy(raw.data(), data, size);
}
auto& backend = static_cast<RendererBackend&>(context.getBackend());
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
if (!device) {
return;
}
// Create buffer descriptor
WGPUBufferDescriptor bufferDesc{};
WGPUStringView label = {"Buffer Resource", strlen("Buffer Resource")};
bufferDesc.label = label;
const std::size_t paddedSize = (size + 3u) & ~std::size_t(3);
bufferDesc.usage = usage | WGPUBufferUsage_CopyDst;
bufferDesc.size = paddedSize;
bufferDesc.mappedAtCreation = 0;
// Create buffer
buffer = wgpuDeviceCreateBuffer(device, &bufferDesc);
if (buffer && data) {
// Upload initial data via queue write
WGPUQueue queue = static_cast<WGPUQueue>(backend.getQueue());
if (queue) {
if (paddedSize == size) {
wgpuQueueWriteBuffer(queue, buffer, 0, data, size);
} else {
std::vector<std::uint8_t> padded(paddedSize, 0);
std::memcpy(padded.data(), data, size);
wgpuQueueWriteBuffer(queue, buffer, 0, padded.data(), paddedSize);
}
} else {
// Fallback to mapping when queue is unavailable (should be rare)
void* mapped = wgpuBufferGetMappedRange(buffer, 0, paddedSize);
if (mapped) {
std::memcpy(mapped, data, size);
if (paddedSize > size) {
std::memset(static_cast<std::uint8_t*>(mapped) + size, 0, paddedSize - size);
}
wgpuBufferUnmap(buffer);
}
}
}
}
BufferResource::BufferResource(BufferResource&& other) noexcept
: context(other.context),
buffer(other.buffer),
raw(std::move(other.raw)),
size(other.size),
usage(other.usage),
version(other.version),
isIndexBuffer(other.isIndexBuffer),
persistent(other.persistent) {
other.buffer = nullptr;
other.size = 0;
}
BufferResource::~BufferResource() noexcept {
if (buffer) {
wgpuBufferRelease(buffer);
buffer = nullptr;
}
}
BufferResource& BufferResource::operator=(BufferResource&& other) noexcept {
if (this != &other) {
// Can't reassign context reference, so check they match
assert(&context == &other.context);
if (buffer) {
wgpuBufferRelease(buffer);
}
buffer = other.buffer;
raw = std::move(other.raw);
size = other.size;
usage = other.usage;
version = other.version;
isIndexBuffer = other.isIndexBuffer;
persistent = other.persistent;
other.buffer = nullptr;
other.size = 0;
}
return *this;
}
BufferResource BufferResource::clone() const {
return BufferResource(context, raw.empty() ? nullptr : raw.data(), size, usage, isIndexBuffer, persistent);
}
void BufferResource::update(const void* data, std::size_t updateSize, std::size_t offset) noexcept {
if (!data || updateSize == 0) {
return;
}
// Update raw data if we have it
if (!raw.empty() && offset + updateSize <= raw.size()) {
std::memcpy(raw.data() + offset, data, updateSize);
}
// Update GPU buffer if we have one
if (buffer) {
// Get the queue from backend
auto& backend = static_cast<RendererBackend&>(context.getBackend());
WGPUQueue queue = static_cast<WGPUQueue>(backend.getQueue());
if (queue) {
// Write data to buffer using queue
wgpuQueueWriteBuffer(queue, buffer, offset, data, updateSize);
}
}
// Increment version to indicate change
++version;
}
bool BufferResource::needReBind(VersionType otherVersion) const noexcept {
return version != otherVersion;
}
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,118 @@
#include <mbgl/webgpu/command_encoder.hpp>
#include <mbgl/webgpu/context.hpp>
#include <mbgl/webgpu/render_pass.hpp>
#include <mbgl/webgpu/upload_pass.hpp>
#include <mbgl/webgpu/renderer_backend.hpp>
#include <mbgl/webgpu/renderable_resource.hpp>
#include <webgpu/webgpu.h>
#include <mbgl/util/logging.hpp>
#include <cstring> // for strlen
// Platform-specific includes for swap
#ifdef __APPLE__
#include "../../platform/glfw/glfw_webgpu_backend.hpp"
#endif
namespace mbgl {
namespace webgpu {
CommandEncoder::CommandEncoder(Context& ctx)
: context(ctx) {
// Initialize WebGPU command encoder
auto& backend = static_cast<RendererBackend&>(context.getBackend());
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
if (device) {
WGPUCommandEncoderDescriptor desc = {};
WGPUStringView label = {"MapLibre Command Encoder", strlen("MapLibre Command Encoder")};
desc.label = label;
encoder = wgpuDeviceCreateCommandEncoder(device, &desc);
}
}
CommandEncoder::~CommandEncoder() {
if (encoder) {
wgpuCommandEncoderRelease(encoder);
}
}
std::unique_ptr<gfx::RenderPass> CommandEncoder::createRenderPass(const char* name,
const gfx::RenderPassDescriptor& descriptor) {
return std::make_unique<RenderPass>(*this, name, descriptor);
}
std::unique_ptr<gfx::UploadPass> CommandEncoder::createUploadPass(const char* name, gfx::Renderable& renderable) {
return std::make_unique<UploadPass>(renderable, *this, name);
}
void CommandEncoder::present(gfx::Renderable& renderable) {
// Submit the command buffer first
submitCommandBuffer();
// Then call swap on the RenderableResource to present the surface
// This matches Metal's pattern where swap handles surface presentation
try {
renderable.getResource<webgpu::RenderableResource>().swap();
} catch (...) {
// No RenderableResource available - surface presentation handled elsewhere
}
}
void CommandEncoder::submitCommandBuffer() {
// Submit command buffer directly (for cases without RenderableResource)
auto& backend = static_cast<RendererBackend&>(context.getBackend());
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
WGPUQueue queue = static_cast<WGPUQueue>(backend.getQueue());
if (!device || !queue || !encoder) {
return;
}
// Finish and submit the command buffer
WGPUCommandBufferDescriptor cmdBufferDesc = {};
WGPUStringView label = {"Command Buffer", strlen("Command Buffer")};
cmdBufferDesc.label = label;
WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(encoder, &cmdBufferDesc);
if (cmdBuffer) {
wgpuQueueSubmit(queue, 1, &cmdBuffer);
wgpuCommandBufferRelease(cmdBuffer);
} else {
Log::Error(Event::Render, "WebGPU: Failed to finish command encoder");
}
// Create a new command encoder so subsequent passes in the same frame can continue recording
WGPUCommandEncoderDescriptor desc = {};
WGPUStringView encoderLabel = {"MapLibre Command Encoder", strlen("MapLibre Command Encoder")};
desc.label = encoderLabel;
encoder = wgpuDeviceCreateCommandEncoder(device, &desc);
if (encoder) {
context.setCurrentCommandEncoder(this);
} else {
context.setCurrentCommandEncoder(nullptr);
}
}
void CommandEncoder::pushDebugGroup(const char*) {
// Debug markers are currently handled at the render pass level.
}
void CommandEncoder::popDebugGroup() {
// Debug markers are currently handled at the render pass level.
}
void CommandEncoder::trackUploadPass(UploadPass* uploadPass) {
// Track the upload pass for debug group management
currentUploadPass = uploadPass;
}
void CommandEncoder::forgetUploadPass(UploadPass* uploadPass) {
// Forget the upload pass when it's destroyed
if (currentUploadPass == uploadPass) {
currentUploadPass = nullptr;
}
}
} // namespace webgpu
} // namespace mbgl

467
src/mbgl/webgpu/context.cpp Normal file
View File

@@ -0,0 +1,467 @@
#include <mbgl/webgpu/context.hpp>
#include <webgpu/webgpu.h>
#include <mbgl/webgpu/vertex_buffer_resource.hpp>
#include <mbgl/util/geometry.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <array>
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/webgpu/command_encoder.hpp>
#include <mbgl/webgpu/drawable_builder.hpp>
#include <mbgl/webgpu/draw_scope_resource.hpp>
#include <mbgl/webgpu/offscreen_texture.hpp>
#include <mbgl/gfx/upload_pass.hpp>
#include <mbgl/webgpu/uniform_buffer.hpp>
#include <mbgl/webgpu/upload_pass.hpp>
#include <mbgl/webgpu/vertex_attribute.hpp>
#include <mbgl/webgpu/texture2d.hpp>
#include <mbgl/renderer/render_static_data.hpp>
#include <mbgl/renderer/render_target.hpp>
#include <mbgl/webgpu/tile_layer_group.hpp>
#include <mbgl/webgpu/layer_group.hpp>
#include <mbgl/webgpu/render_pass.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/util/hash.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/shaders/attributes.hpp>
#include <mbgl/shaders/webgpu/clipping_mask.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/shaders/program_parameters.hpp>
#include <mbgl/gfx/gfx_types.hpp>
namespace mbgl {
namespace webgpu {
namespace {
const auto clipMaskStencilMode = gfx::StencilMode{
/*.test=*/gfx::StencilMode::Always(),
/*.ref=*/0,
/*.mask=*/0xFF,
/*.fail=*/gfx::StencilOpType::Keep,
/*.depthFail=*/gfx::StencilOpType::Keep,
/*.pass=*/gfx::StencilOpType::Replace,
};
const auto clipMaskDepthMode = gfx::DepthMode{.func = gfx::DepthFunctionType::Always,
.mask = gfx::DepthMaskType::ReadOnly};
} // namespace
Context::Context(RendererBackend& backend_)
: gfx::Context(gfx::Context::minimumRequiredVertexBindingCount),
backend(backend_),
globalUniformBuffers(std::make_unique<UniformBufferArray>()) {}
Context::~Context() = default;
void Context::beginFrame() {
// Begin a new frame - WebGPU command recording starts here
}
void Context::endFrame() {
// End the frame - submit WebGPU commands
}
void Context::performCleanup() {
// Clean up unused resources
}
void Context::reduceMemoryUsage() {
// Free cached resources to reduce memory
}
std::unique_ptr<gfx::OffscreenTexture> Context::createOffscreenTexture(Size size,
gfx::TextureChannelDataType type,
bool depth,
bool stencil) {
return std::make_unique<OffscreenTexture>(*this, size, type, depth, stencil);
}
std::unique_ptr<gfx::OffscreenTexture> Context::createOffscreenTexture(Size size, gfx::TextureChannelDataType type) {
return std::make_unique<OffscreenTexture>(*this, size, type, true, false);
}
std::unique_ptr<gfx::CommandEncoder> Context::createCommandEncoder() {
auto encoder = std::make_unique<CommandEncoder>(*this);
currentCommandEncoder = encoder.get();
return encoder;
}
gfx::VertexAttributeArrayPtr Context::createVertexAttributeArray() const {
return std::make_shared<VertexAttributeArray>();
}
gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) {
return std::make_unique<DrawableBuilder>(std::move(name));
}
gfx::UniformBufferPtr Context::createUniformBuffer(const void* data, std::size_t size, bool persistent, bool) {
(void)persistent;
return std::make_shared<UniformBuffer>(*this, data, size);
}
gfx::UniqueUniformBufferArray Context::createLayerUniformBufferArray() {
return std::make_unique<UniformBufferArray>();
}
gfx::ShaderProgramBasePtr Context::getGenericShader(gfx::ShaderRegistry& registry, const std::string& name) {
// Align with Metal - just get shader from registry without caching
const auto shaderGroup = registry.getShaderGroup(name);
auto shader = shaderGroup ? shaderGroup->getOrCreateShader(*this, {}) : gfx::ShaderProgramBasePtr{};
return std::static_pointer_cast<gfx::ShaderProgramBase>(std::move(shader));
}
TileLayerGroupPtr Context::createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
auto tileLayerGroup = std::make_shared<webgpu::TileLayerGroup>(layerIndex, initialCapacity, std::move(name));
return tileLayerGroup;
}
LayerGroupPtr Context::createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
auto layerGroup = std::make_shared<webgpu::LayerGroup>(layerIndex, initialCapacity, std::move(name));
return layerGroup;
}
gfx::Texture2DPtr Context::createTexture2D() {
return std::make_shared<Texture2D>(*this);
}
RenderTargetPtr Context::createRenderTarget(const Size size, const gfx::TextureChannelDataType type) {
return std::make_shared<RenderTarget>(*this, size, type);
}
void Context::resetState(gfx::DepthMode, gfx::ColorMode) {
// Reset WebGPU render state
}
void Context::setDirtyState() {
// Mark state as needing update
}
void Context::clearStencilBuffer(int32_t value) {
// Clear stencil buffer to specified value
(void)value;
}
bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& ptr,
const void* data,
std::size_t size,
bool persistent) {
if (!ptr) {
ptr = createUniformBuffer(data, size, persistent);
return true;
}
// Update existing buffer
auto* buffer = static_cast<UniformBuffer*>(ptr.get());
buffer->update(data, size);
return false;
}
const gfx::UniformBufferArray& Context::getGlobalUniformBuffers() const {
return *globalUniformBuffers;
}
gfx::UniformBufferArray& Context::mutableGlobalUniformBuffers() {
return *globalUniformBuffers;
}
void Context::bindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexcept {
// In WebGPU, we can't bind buffers globally to the render pass like Metal does.
// Instead, we need to ensure these buffers are available for drawables to include
// in their bind groups. The actual binding happens per-drawable.
// Store a reference to global buffers in the render pass for drawables to access
auto& webgpuRenderPass = static_cast<webgpu::RenderPass&>(renderPass);
webgpuRenderPass.setGlobalUniformBuffers(globalUniformBuffers.get());
}
void Context::unbindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexcept {
// Clear the global buffer reference from the render pass
auto& webgpuRenderPass = static_cast<webgpu::RenderPass&>(renderPass);
webgpuRenderPass.setGlobalUniformBuffers(nullptr);
}
#if !defined(NDEBUG)
void Context::visualizeStencilBuffer() {
// Debug visualization of stencil buffer
}
void Context::visualizeDepthBuffer([[maybe_unused]] float depthRangeSize) {
// Debug visualization of depth buffer
}
#endif
std::unique_ptr<gfx::RenderbufferResource> Context::createRenderbufferResource(gfx::RenderbufferPixelType type,
Size size) {
(void)type;
(void)size;
return std::make_unique<RenderbufferResource>();
}
std::unique_ptr<gfx::DrawScopeResource> Context::createDrawScopeResource() {
return std::make_unique<DrawScopeResource>(*this);
}
// Buffer creation (aligned with Metal)
BufferResource Context::createBuffer(
const void* data, std::size_t size, uint32_t usage, bool isIndexBuffer, bool persistent) const {
return BufferResource(const_cast<Context&>(*this), data, size, usage, isIndexBuffer, persistent);
}
// Get reusable tile vertex buffer (aligned with Metal)
const BufferResource& Context::getTileVertexBuffer() {
if (!tileVertexBuffer) {
const auto vertices = RenderStaticData::tileVertices();
const uint32_t usage = WGPUBufferUsage_Vertex;
tileVertexBuffer = createBuffer(vertices.data(), vertices.bytes(), usage, false, true);
}
return *tileVertexBuffer;
}
// Get reusable tile index buffer (aligned with Metal)
const BufferResource& Context::getTileIndexBuffer() {
if (!tileIndexBuffer) {
const auto indices = RenderStaticData::quadTriangleIndices();
const uint32_t usage = WGPUBufferUsage_Index;
tileIndexBuffer = createBuffer(indices.data(), indices.bytes(), usage, true, true);
}
return *tileIndexBuffer;
}
bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass,
RenderStaticData& staticData,
const std::vector<shaders::ClipUBO>& tileUBOs) {
using ShaderClass = shaders::ShaderSource<shaders::BuiltIn::ClippingMaskProgram, gfx::Backend::Type::WebGPU>;
if (tileUBOs.empty()) {
return false;
}
if (!clipMaskShader) {
if (auto group = staticData.shaders->getShaderGroup(ShaderClass::name)) {
clipMaskShader = std::static_pointer_cast<gfx::ShaderProgramBase>(group->getOrCreateShader(*this, {}));
}
}
if (!clipMaskShader) {
Log::Error(Event::Render, "WebGPU: Failed to acquire clipping mask shader");
return false;
}
auto& shader = static_cast<webgpu::ShaderProgram&>(*clipMaskShader);
auto& webgpuRenderPass = static_cast<webgpu::RenderPass&>(renderPass);
const auto encoder = webgpuRenderPass.getEncoder();
if (!encoder) {
Log::Error(Event::Render, "WebGPU: render pass encoder unavailable for clipping masks");
return false;
}
const auto& renderable = webgpuRenderPass.getDescriptor().renderable;
if (clipMaskRenderable != &renderable) {
clipMaskPipelineHash.reset();
clipMaskRenderable = &renderable;
}
const auto& vertexResource = getTileVertexBuffer();
const auto& indexResource = getTileIndexBuffer();
if (!vertexResource.getBuffer() || !indexResource.getBuffer()) {
Log::Error(Event::Render, "WebGPU: Missing tile geometry buffers for clipping masks");
return false;
}
const gfx::ColorMode colorMode = gfx::ColorMode::disabled();
WGPUVertexAttribute vertexAttribute{};
vertexAttribute.format = WGPUVertexFormat_Sint16x2;
vertexAttribute.offset = 0;
vertexAttribute.shaderLocation = static_cast<uint32_t>(ShaderClass::attributes[0].index);
WGPUVertexBufferLayout vertexLayout{};
vertexLayout.arrayStride = sizeof(gfx::Vertex<PositionOnlyLayoutAttributes>);
vertexLayout.attributeCount = 1;
vertexLayout.attributes = &vertexAttribute;
vertexLayout.stepMode = WGPUVertexStepMode_Vertex;
if (!clipMaskPipelineHash) {
clipMaskPipelineHash = util::hash(colorMode.hash(),
vertexLayout.arrayStride,
static_cast<uint32_t>(vertexAttribute.format),
vertexAttribute.shaderLocation);
}
const auto pipeline = shader.getRenderPipeline(renderable,
&vertexLayout,
1,
colorMode,
clipMaskDepthMode,
clipMaskStencilMode,
gfx::DrawModeType::Triangles,
clipMaskPipelineHash);
if (!pipeline) {
Log::Error(Event::Render, "WebGPU: Failed to create clipping mask pipeline");
return false;
}
wgpuRenderPassEncoderSetPipeline(encoder, pipeline);
wgpuRenderPassEncoderSetVertexBuffer(encoder,
/*slot=*/0,
vertexResource.getBuffer(),
/*offset=*/0,
vertexResource.getSizeInBytes());
wgpuRenderPassEncoderSetIndexBuffer(encoder,
indexResource.getBuffer(),
WGPUIndexFormat_Uint16,
/*offset=*/0,
indexResource.getSizeInBytes());
const uint64_t uboSize = sizeof(shaders::ClipUBO);
clipMaskUniformBuffers.clear();
clipMaskUniformBuffers.reserve(tileUBOs.size());
for (const auto& ubo : tileUBOs) {
auto buffer = createBuffer(&ubo, uboSize, WGPUBufferUsage_Uniform, false, false);
if (!buffer.getBuffer()) {
Log::Error(Event::Render, "WebGPU: Failed to allocate uniform buffer for clipping mask tile");
return false;
}
clipMaskUniformBuffers.emplace_back(std::move(buffer));
}
auto& rendererBackend = static_cast<RendererBackend&>(getBackend());
WGPUDevice device = static_cast<WGPUDevice>(rendererBackend.getDevice());
if (!device) {
Log::Error(Event::Render, "WebGPU: Device unavailable for clipping masks");
return false;
}
const auto& bindGroupOrder = shader.getBindGroupOrder();
if (bindGroupOrder.empty()) {
Log::Error(Event::Render, "WebGPU: No bind group layouts available for clipping mask shader");
return false;
}
const uint32_t bindGroupIndex = bindGroupOrder.front();
const auto layout = shader.getBindGroupLayout(bindGroupIndex);
if (!layout) {
Log::Error(Event::Render, "WebGPU: Bind group layout missing for clipping mask shader");
return false;
}
const auto& bindingInfos = shader.getBindingInfosForGroup(bindGroupIndex);
uint32_t uniformBinding = 0;
bool bindingFound = false;
for (const auto& info : bindingInfos) {
if (info.type == webgpu::ShaderProgram::BindingType::UniformBuffer) {
uniformBinding = info.binding;
bindingFound = true;
break;
}
}
if (!bindingFound) {
Log::Error(Event::Render, "WebGPU: Uniform binding not found for clipping mask shader");
return false;
}
clipMaskActiveBindGroups.clear();
clipMaskActiveBindGroups.reserve(tileUBOs.size());
for (std::size_t i = 0; i < tileUBOs.size(); ++i) {
WGPUBindGroupEntry entry{};
entry.binding = uniformBinding;
const auto& uboBuffer = clipMaskUniformBuffers[i];
entry.buffer = uboBuffer.getBuffer();
entry.offset = 0;
entry.size = uboSize;
WGPUBindGroupDescriptor descriptor{};
descriptor.layout = layout;
descriptor.entryCount = 1;
descriptor.entries = &entry;
const auto bindGroup = wgpuDeviceCreateBindGroup(device, &descriptor);
if (!bindGroup) {
Log::Error(Event::Render, "WebGPU: Failed to create bind group for clipping mask draw");
continue;
}
clipMaskActiveBindGroups.push_back(bindGroup);
wgpuRenderPassEncoderSetBindGroup(encoder, bindGroupIndex, bindGroup, 0, nullptr);
wgpuRenderPassEncoderSetStencilReference(encoder, tileUBOs[i].stencil_ref);
wgpuRenderPassEncoderDrawIndexed(encoder,
/*indexCount=*/6,
/*instanceCount=*/1,
/*firstIndex=*/0,
/*baseVertex=*/0,
/*firstInstance=*/0);
}
for (auto bindGroup : clipMaskActiveBindGroups) {
if (bindGroup) {
wgpuBindGroupRelease(bindGroup);
}
}
clipMaskActiveBindGroups.clear();
auto& renderStats = renderingStats();
renderStats.numDrawCalls += tileUBOs.size();
renderStats.totalDrawCalls += tileUBOs.size();
return true;
}
gfx::AttributeBindingArray Context::getOrCreateVertexBindings(gfx::Context&,
const gfx::AttributeDataType vertexType,
const size_t vertexAttributeIndex,
const std::vector<uint8_t>& vertexData,
const gfx::VertexAttributeArray& defaults,
const gfx::VertexAttributeArray& overrides,
gfx::BufferUsageType,
const std::optional<std::chrono::duration<double>>&,
std::shared_ptr<VertexBufferResource>&) noexcept {
static_cast<void>(vertexType);
static_cast<void>(vertexAttributeIndex);
static_cast<void>(vertexData);
gfx::AttributeBindingArray result;
result.resize(overrides.allocatedSize());
// Process each vertex attribute
overrides.visitAttributes([&](const gfx::VertexAttribute& attr) {
const auto index = static_cast<std::size_t>(attr.getIndex());
if (index >= result.size()) return;
// Get the shared raw data if available
gfx::VertexVectorBasePtr sharedRaw = attr.getSharedRawData();
if (!sharedRaw) {
// Try default attributes by ID
defaults.visitAttributes([&](const gfx::VertexAttribute& defaultAttr) {
if (!sharedRaw && defaultAttr.getIndex() == attr.getIndex()) {
sharedRaw = defaultAttr.getSharedRawData();
}
});
}
if (sharedRaw) {
// Create vertex buffer from the shared data
const auto dataSize = sharedRaw->getRawSize() * sharedRaw->getRawCount();
if (dataSize > 0) {
// Create GPU buffer for this vertex attribute
uint32_t usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
auto bufferResource = std::make_shared<VertexBufferResource>(
createBuffer(sharedRaw->getRawData(), dataSize, usage, false, false));
// Create attribute binding
result[index] = {/*.attribute = */ {attr.getDataType(), /*offset=*/0},
/*.vertexStride = */ static_cast<uint32_t>(attr.getStride()),
/*.vertexBufferResource = */ bufferResource.get(),
/*.vertexOffset = */ 0};
}
}
});
return result;
}
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,11 @@
#include <mbgl/webgpu/draw_scope_resource.hpp>
#include <mbgl/webgpu/context.hpp>
namespace mbgl {
namespace webgpu {
DrawScopeResource::DrawScopeResource(Context& context_)
: context(context_) {}
} // namespace webgpu
} // namespace mbgl

View File

@@ -0,0 +1,976 @@
#include <mbgl/webgpu/drawable.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/cull_face_mode.hpp>
#include <mbgl/gfx/depth_mode.hpp>
#include <mbgl/gfx/stencil_mode.hpp>
#include <mbgl/gfx/vertex_attribute.hpp>
#include <mbgl/gfx/vertex_vector.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/shaders/segment.hpp>
#include <mbgl/shaders/shader_defines.hpp>
#include <mbgl/shaders/webgpu/shader_program.hpp>
#include <mbgl/webgpu/command_encoder.hpp>
#include <mbgl/webgpu/context.hpp>
#include <mbgl/webgpu/drawable_impl.hpp>
#include <algorithm>
#include <sstream>
#include <mbgl/webgpu/index_buffer_resource.hpp>
#include <mbgl/webgpu/render_pass.hpp>
#include <mbgl/webgpu/renderer_backend.hpp>
#include <mbgl/webgpu/texture2d.hpp>
#include <mbgl/webgpu/uniform_buffer.hpp>
#include <mbgl/webgpu/upload_pass.hpp>
#include <mbgl/webgpu/vertex_buffer_resource.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/variant.hpp>
#include <mbgl/util/hash.hpp>
#include <mbgl/util/convert.hpp>
#include <mbgl/util/monotonic_timer.hpp>
#include <cassert>
#include <memory>
#if !defined(NDEBUG)
#include <sstream>
#endif
namespace mbgl {
namespace webgpu {
namespace {
#if !defined(NDEBUG)
std::string debugLabel(const Drawable& drawable) {
std::ostringstream ss;
ss << drawable.getName() << "#" << drawable.getID();
return ss.str();
}
#endif
struct alignas(16) GlobalUBOIndexData {
uint32_t value = 0;
uint32_t pad[3] = {0, 0, 0};
};
std::size_t hashStencilMode(const gfx::StencilMode& mode) {
std::size_t hash = util::hash(static_cast<int>(mode.ref),
static_cast<uint32_t>(mode.mask),
static_cast<int>(mode.fail),
static_cast<int>(mode.depthFail),
static_cast<int>(mode.pass));
mapbox::util::apply_visitor(
[&](const auto& test) {
using TestType = std::decay_t<decltype(test)>;
hash = util::hash(hash, static_cast<int>(TestType::func));
if constexpr (requires { test.mask; }) {
hash = util::hash(hash, static_cast<uint32_t>(test.mask));
}
},
mode.test);
return hash;
}
WGPUVertexFormat wgpuVertexFormatOf(gfx::AttributeDataType type) {
switch (type) {
case gfx::AttributeDataType::Byte:
return WGPUVertexFormat_Sint8;
case gfx::AttributeDataType::Byte2:
return WGPUVertexFormat_Sint8x2;
case gfx::AttributeDataType::Byte4:
return WGPUVertexFormat_Sint8x4;
case gfx::AttributeDataType::UByte:
return WGPUVertexFormat_Uint8;
case gfx::AttributeDataType::UByte2:
return WGPUVertexFormat_Uint8x2;
case gfx::AttributeDataType::UByte4:
return WGPUVertexFormat_Uint8x4;
case gfx::AttributeDataType::Short:
return WGPUVertexFormat_Sint16;
case gfx::AttributeDataType::Short2:
return WGPUVertexFormat_Sint16x2;
case gfx::AttributeDataType::Short4:
return WGPUVertexFormat_Sint16x4;
case gfx::AttributeDataType::UShort:
return WGPUVertexFormat_Uint16;
case gfx::AttributeDataType::UShort2:
return WGPUVertexFormat_Uint16x2;
case gfx::AttributeDataType::UShort4:
return WGPUVertexFormat_Uint16x4;
case gfx::AttributeDataType::Int:
return WGPUVertexFormat_Sint32;
case gfx::AttributeDataType::Int2:
return WGPUVertexFormat_Sint32x2;
case gfx::AttributeDataType::Int3:
return WGPUVertexFormat_Sint32x3;
case gfx::AttributeDataType::Int4:
return WGPUVertexFormat_Sint32x4;
case gfx::AttributeDataType::UInt:
return WGPUVertexFormat_Uint32;
case gfx::AttributeDataType::UInt2:
return WGPUVertexFormat_Uint32x2;
case gfx::AttributeDataType::UInt3:
return WGPUVertexFormat_Uint32x3;
case gfx::AttributeDataType::UInt4:
return WGPUVertexFormat_Uint32x4;
case gfx::AttributeDataType::Float:
return WGPUVertexFormat_Float32;
case gfx::AttributeDataType::Float2:
return WGPUVertexFormat_Float32x2;
case gfx::AttributeDataType::Float3:
return WGPUVertexFormat_Float32x3;
case gfx::AttributeDataType::Float4:
return WGPUVertexFormat_Float32x4;
default:
assert(!"Unsupported vertex attribute format");
return WGPUVertexFormat_Float32x2; // Default fallback
}
}
} // namespace
// Simple wrapper to adapt gfx::IndexBuffer to IndexBufferBase
struct IndexBuffer : public gfx::IndexBufferBase {
IndexBuffer(std::unique_ptr<gfx::IndexBuffer>&& buffer_)
: buffer(std::move(buffer_)) {}
~IndexBuffer() override = default;
std::unique_ptr<mbgl::gfx::IndexBuffer> buffer;
};
Drawable::Drawable(std::string name_)
: gfx::Drawable(std::move(name_)),
impl(std::make_unique<Impl>()) {}
Drawable::~Drawable() {
// Clean up WebGPU resources safely
// Note: We don't own the pipeline - it's owned by the shader program
// So we should NOT release it here
impl->pipelineState = nullptr;
for (auto& record : impl->bindGroups) {
if (record.handle) {
wgpuBindGroupRelease(record.handle);
}
}
impl->bindGroups.clear();
// Uniform buffers are managed by UniformBufferArray
// Vertex and index buffers are now managed through attributeBindings and indexes
}
namespace {
[[maybe_unused]] WGPUPrimitiveTopology getPrimitiveTopology(const gfx::DrawModeType type) noexcept {
switch (type) {
default:
assert(false);
[[fallthrough]];
case gfx::DrawModeType::Points:
return WGPUPrimitiveTopology_PointList;
case gfx::DrawModeType::Lines:
return WGPUPrimitiveTopology_LineList;
case gfx::DrawModeType::LineStrip:
return WGPUPrimitiveTopology_LineStrip;
case gfx::DrawModeType::Triangles:
return WGPUPrimitiveTopology_TriangleList;
}
}
#if !defined(NDEBUG)
[[maybe_unused]] 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 << "(null)";
}
return oss.str();
}
#endif // !defined(NDEBUG)
} // namespace
void Drawable::setColorMode(const gfx::ColorMode& value) {
gfx::Drawable::setColorMode(value);
impl->pipelineState = nullptr; // Rebuild pipeline to honour new color mode
}
void Drawable::setEnableStencil(bool value) {
if (getEnableStencil() == value) {
return;
}
gfx::Drawable::setEnableStencil(value);
impl->pipelineState = nullptr; // Reset pipeline to force recreation with new stencil state
}
void Drawable::setEnableDepth(bool value) {
if (getEnableDepth() == value) {
return;
}
gfx::Drawable::setEnableDepth(value);
impl->pipelineState = nullptr; // Reset pipeline to force recreation with new depth state
}
void Drawable::setSubLayerIndex(int32_t value) {
if (getSubLayerIndex() == value) {
return;
}
gfx::Drawable::setSubLayerIndex(value);
}
void Drawable::setDepthType(gfx::DepthMaskType value) {
if (getDepthType() == value) {
return;
}
gfx::Drawable::setDepthType(value);
impl->pipelineState = nullptr; // Reset pipeline to force recreation with new depth mask
}
void Drawable::setDepthModeFor3D(const gfx::DepthMode& value) {
if (impl->depthMode.func != value.func || impl->depthMode.mask != value.mask) {
impl->pipelineState = nullptr;
}
impl->depthMode = value;
}
void Drawable::setStencilModeFor3D(const gfx::StencilMode& value) {
const auto newHash = hashStencilMode(value);
if (!impl->stencilModeHashValid || impl->stencilModeHash != newHash || impl->stencilMode.ref != value.ref ||
impl->stencilMode.mask != value.mask || impl->stencilMode.fail != value.fail ||
impl->stencilMode.depthFail != value.depthFail || impl->stencilMode.pass != value.pass) {
impl->pipelineState = nullptr;
impl->stencilModeHashValid = false;
}
impl->stencilMode = value;
impl->stencilModeHash = newHash;
}
void Drawable::setShader(gfx::ShaderProgramBasePtr value) {
if (shader == value) {
return;
}
shader = std::move(value);
impl->pipelineState = nullptr; // Reset pipeline when shader changes
}
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& webgpuUploadPass = static_cast<webgpu::UploadPass&>(uploadPass);
auto& gfxContext = webgpuUploadPass.getContext();
[[maybe_unused]] auto& context = static_cast<webgpu::Context&>(gfxContext);
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->getBuffer() || 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 = webgpuUploadPass.createIndexBufferResource(impl->indexes->data(),
impl->indexes->bytes(),
usage,
/*persistent=*/false);
auto gfxIndexBuffer = std::make_unique<gfx::IndexBuffer>(impl->indexes->elements(),
std::move(indexBufferResource));
auto buffer = std::make_unique<IndexBuffer>(std::move(gfxIndexBuffer));
impl->indexes->setBuffer(std::move(buffer));
impl->indexes->setDirty(false);
}
// Upload vertex attributes (like Metal does)
const bool buildAttribs = !vertexAttributes || !attributeUpdateTime ||
vertexAttributes->isModifiedAfter(*attributeUpdateTime);
if (buildAttribs) {
#if !defined(NDEBUG)
const auto debugGroup = webgpuUploadPass.createDebugGroup(debugLabel(*this));
#endif
if (!vertexAttributes) {
vertexAttributes = std::make_shared<gfx::VertexAttributeArray>();
}
// Apply drawable values to shader defaults (matching Metal's approach)
std::vector<std::unique_ptr<gfx::VertexBufferResource>> vertexBuffers;
auto attributeBindings_ = webgpuUploadPass.buildAttributeBindings(impl->vertexCount,
impl->vertexType,
/*vertexAttributeIndex=*/-1,
/*vertexData=*/{},
shader->getVertexAttributes(),
*vertexAttributes,
usage,
attributeUpdateTime,
vertexBuffers);
if (impl->attributeBindings != attributeBindings_) {
impl->attributeBindings = std::move(attributeBindings_);
impl->vertexDescHash = 0; // Reset hash when bindings change
impl->pipelineState = nullptr; // Reset pipeline state when attribute bindings change
// Store the dummy vertex buffers to keep them alive
impl->dummyVertexBuffers = std::move(vertexBuffers);
}
}
// Build instance buffer (like Metal does)
const bool buildInstanceBuffer =
(instanceAttributes && (!attributeUpdateTime || instanceAttributes->isModifiedAfter(*attributeUpdateTime)));
if (buildInstanceBuffer) {
// Apply instance values to shader defaults (matching Metal's approach)
std::vector<std::unique_ptr<gfx::VertexBufferResource>> instanceBuffers;
auto instanceBindings_ = webgpuUploadPass.buildAttributeBindings(impl->vertexCount,
impl->vertexType,
/*vertexAttributeIndex=*/-1,
/*vertexData=*/{},
shader->getInstanceAttributes(),
*instanceAttributes,
usage,
attributeUpdateTime,
instanceBuffers);
if (impl->instanceBindings != instanceBindings_) {
impl->instanceBindings = std::move(instanceBindings_);
impl->pipelineState = nullptr; // Reset pipeline state when instance bindings change
// Store the dummy instance buffers to keep them alive
if (!instanceBuffers.empty()) {
impl->dummyVertexBuffers.insert(impl->dummyVertexBuffers.end(),
std::make_move_iterator(instanceBuffers.begin()),
std::make_move_iterator(instanceBuffers.end()));
}
}
}
// Upload uniform buffers to ensure they're ready for the GPU
// This is critical for making drawables visible
for (size_t i = 0; i < impl->uniformBuffers.allocatedSize(); ++i) {
auto& uniformBuffer = impl->uniformBuffers.get(i);
if (uniformBuffer) {
// Make sure the uniform buffer has its data uploaded
// The uniform buffer should handle its own upload internally
// but we need to ensure it's marked as not dirty
auto* webgpuUniformBuffer = static_cast<webgpu::UniformBuffer*>(uniformBuffer.get());
if (webgpuUniformBuffer) {
// The uniform buffer's data should already be uploaded when it was created/updated
// Just ensure we have a valid buffer handle
if (!webgpuUniformBuffer->getBuffer()) {
Log::Error(Event::Render, "Uniform buffer " + std::to_string(i) + " has no GPU buffer!");
}
}
}
}
// Upload textures if needed
const bool texturesNeedUpload = std::any_of(
textures.begin(), textures.end(), [](const auto& texture) { return texture && texture->needsUpload(); });
if (texturesNeedUpload) {
for (auto& texture : textures) {
if (texture && texture->needsUpload()) {
texture->upload();
}
}
}
attributeUpdateTime = util::MonotonicTimer::now();
}
void Drawable::draw(PaintParameters& parameters) const {
static int drawCallCount = 0;
drawCallCount++;
if (isCustom) {
return;
}
// Get WebGPU context and render pass (following Metal's pattern)
auto& context = static_cast<webgpu::Context&>(parameters.context);
auto& webgpuRenderPass = static_cast<webgpu::RenderPass&>(*parameters.renderPass);
WGPURenderPassEncoder renderPassEncoder = webgpuRenderPass.getEncoder();
if (!renderPassEncoder) {
Log::Error(Event::Render, "No render pass encoder available");
assert(false);
return;
}
if (!shader) {
Log::Warning(Event::General, "Missing shader for drawable " + util::toString(getID()) + "/" + getName());
assert(false);
return;
}
if (!getEnabled()) {
return;
}
// Get WebGPU backend and device
auto& backend = static_cast<webgpu::RendererBackend&>(context.getBackend());
WGPUDevice device = static_cast<WGPUDevice>(backend.getDevice());
WGPUQueue queue = static_cast<WGPUQueue>(backend.getQueue());
if (!device || !queue) {
return;
}
#if !defined(NDEBUG)
// Debug group creation would go here (Metal has this but we need to implement it for WebGPU)
// const auto debugGroup = parameters.encoder->createDebugGroup(getName());
#endif
// Handle uboIndex like Metal does
// In WebGPU, we'll need to handle this differently since we use bind groups
// Metal uses setVertexBytes/setFragmentBytes for the uboIndex
// For now, we'll store it for use in bind group creation
// Bind uniform buffers (like Metal does)
impl->uniformBuffers.bind(webgpuRenderPass);
// Check index buffer is valid (like Metal does)
if (impl->indexes && (!impl->indexes->getBuffer() || impl->indexes->getDirty())) {
assert(!"Index buffer not uploaded");
return;
}
// Build bind groups based on shader metadata
if (shader) {
auto webgpuShader = std::static_pointer_cast<mbgl::webgpu::ShaderProgram>(shader);
if (webgpuShader) {
WGPUDevice deviceHandle = static_cast<WGPUDevice>(backend.getDevice());
for (auto& record : impl->bindGroups) {
if (record.handle) {
wgpuBindGroupRelease(record.handle);
}
}
impl->bindGroups.clear();
if (deviceHandle) {
const auto& groupOrder = webgpuShader->getBindGroupOrder();
static bool loggedBindGroupOrder = false;
if (!loggedBindGroupOrder && getName().find("fill-outline") != std::string::npos) {
std::string orderStr;
for (size_t idx = 0; idx < groupOrder.size(); ++idx) {
orderStr += std::to_string(groupOrder[idx]);
if (idx + 1 < groupOrder.size()) orderStr += ",";
}
loggedBindGroupOrder = true;
}
const auto* globalBuffers = webgpuRenderPass.getGlobalUniformBuffers();
const auto findTextureForBinding = [&](uint32_t binding, bool samplerBinding) -> Texture2D* {
for (size_t texIndex = 0; texIndex < textures.size(); ++texIndex) {
if (!textures[texIndex]) {
continue;
}
if (const auto location = webgpuShader->getSamplerLocation(texIndex)) {
if ((samplerBinding && *location == binding) ||
(!samplerBinding && (*location == binding || *location + 1 == binding))) {
return static_cast<Texture2D*>(textures[texIndex].get());
}
}
}
return nullptr;
};
for (size_t slot = 0; slot < groupOrder.size(); ++slot) {
const uint32_t group = groupOrder[slot];
auto layout = webgpuShader->getBindGroupLayout(group);
if (!layout) {
const std::string shaderName = webgpuShader ? std::string(webgpuShader->typeName()) : "unknown";
Log::Warning(Event::Render,
"WebGPU: missing bind group layout for group " + std::to_string(group) +
" in shader '" + shaderName + "'");
continue;
}
const auto& bindingInfos = webgpuShader->getBindingInfosForGroup(group);
if (getName().find("fill-outline") != std::string::npos) {
std::string infoStr;
for (const auto& info : bindingInfos) {
infoStr += "(binding=" + std::to_string(info.binding) +
", type=" + std::to_string(static_cast<int>(info.type)) + ")";
}
}
std::vector<WGPUBindGroupEntry> entries;
entries.reserve(bindingInfos.size());
bool validBindings = true;
for (const auto& bindingInfo : bindingInfos) {
WGPUBindGroupEntry entry = {};
entry.binding = bindingInfo.binding;
switch (bindingInfo.type) {
case ShaderProgram::BindingType::UniformBuffer:
case ShaderProgram::BindingType::ReadOnlyStorageBuffer:
case ShaderProgram::BindingType::StorageBuffer: {
std::shared_ptr<gfx::UniformBuffer> buffer = impl->uniformBuffers.get(
bindingInfo.binding);
if (bindingInfo.binding == shaders::idGlobalUBOIndex) {
GlobalUBOIndexData indexData;
indexData.value = getUBOIndex();
context.emplaceOrUpdateUniformBuffer(
impl->uboIndexUniform, &indexData, sizeof(indexData), false);
if (impl->uboIndexUniform) {
impl->uniformBuffers.set(bindingInfo.binding, impl->uboIndexUniform);
buffer = impl->uboIndexUniform;
}
}
if (!buffer && globalBuffers) {
buffer = globalBuffers->get(bindingInfo.binding);
}
if (!buffer) {
if (drawCallCount <= 200) {
Log::Warning(Event::Render,
"No buffer found for binding " +
std::to_string(bindingInfo.binding) +
" type=" + std::to_string(static_cast<int>(bindingInfo.type)) +
" group=" + std::to_string(group));
}
validBindings = false;
break;
}
const auto* webgpuUniform = static_cast<const webgpu::UniformBuffer*>(buffer.get());
if (!webgpuUniform || !webgpuUniform->getBuffer()) {
if (drawCallCount <= 200) {
Log::Warning(Event::Render,
"Uniform buffer missing GPU handle for binding " +
std::to_string(bindingInfo.binding) +
" group=" + std::to_string(group));
}
validBindings = false;
break;
}
entry.buffer = webgpuUniform->getBuffer();
entry.offset = 0;
entry.size = buffer->getSize();
break;
}
case ShaderProgram::BindingType::Sampler: {
Texture2D* texture = findTextureForBinding(bindingInfo.binding, true);
if (!texture) {
validBindings = false;
break;
}
if (!texture->getSampler()) {
texture->setSamplerConfiguration(texture->getSamplerState());
}
entry.sampler = texture->getSampler();
break;
}
case ShaderProgram::BindingType::Texture: {
Texture2D* texture = findTextureForBinding(bindingInfo.binding, false);
if (!texture) {
validBindings = false;
break;
}
if (!texture->getTexture() || !texture->getTextureView()) {
texture->create();
}
if (!texture->getTextureView()) {
validBindings = false;
break;
}
entry.textureView = texture->getTextureView();
break;
}
}
if (!validBindings) {
break;
}
entries.push_back(entry);
}
if (!validBindings || entries.empty()) {
Log::Warning(Event::Render,
"WebGPU: invalid bindings for drawable '" + getName() +
"' (group=" + std::to_string(group) + ", slot=" + std::to_string(slot) + ")");
continue;
}
const std::string label = getName() + " bind-group " + std::to_string(group);
WGPUStringView labelView = {label.c_str(), label.length()};
WGPUBindGroupDescriptor descriptor = {};
descriptor.label = labelView;
descriptor.layout = layout;
descriptor.entryCount = entries.size();
descriptor.entries = entries.data();
WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(deviceHandle, &descriptor);
impl->bindGroups.push_back({static_cast<uint32_t>(slot), group, bindGroup});
}
}
}
}
const auto drawableTileID = getTileID();
gfx::DepthMode depthMode = gfx::DepthMode::disabled();
gfx::StencilMode stencilMode = gfx::StencilMode::disabled();
if (is3D) {
depthMode = impl->depthMode;
stencilMode = impl->stencilMode;
} else {
if (getEnableDepth()) {
depthMode = parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType());
}
if (getEnableStencil() && drawableTileID) {
stencilMode = parameters.stencilModeForClipping(drawableTileID->toUnwrapped());
}
}
const std::size_t newStencilHash = hashStencilMode(stencilMode);
if (!impl->stencilModeHashValid || impl->stencilModeHash != newStencilHash ||
impl->depthMode.func != depthMode.func || impl->depthMode.mask != depthMode.mask) {
impl->pipelineState = nullptr;
impl->stencilModeHashValid = true;
}
impl->depthMode = depthMode;
impl->stencilMode = stencilMode;
impl->stencilModeHash = newStencilHash;
impl->previousStencilMode = stencilMode;
// Get pipeline from shader if we don't have it yet (like Metal)
struct UniqueBinding {
const VertexBufferResource* vertexResource = nullptr;
const BufferResource* buffer = nullptr;
uint32_t stride = 0;
uint64_t baseOffset = 0;
WGPUVertexStepMode stepMode = WGPUVertexStepMode_Vertex;
std::vector<WGPUVertexAttribute> attributes;
std::vector<uint64_t> attributeByteOffsets;
};
auto accumulateBindings = [](const gfx::AttributeBindingArray& source,
const WGPUVertexStepMode stepMode,
std::vector<UniqueBinding>& out) {
for (size_t attributeIndex = 0; attributeIndex < source.size(); ++attributeIndex) {
const auto& binding = source[attributeIndex];
if (!binding.has_value() || !binding->vertexBufferResource) {
continue;
}
const auto* vertexBufferRes = static_cast<const VertexBufferResource*>(binding->vertexBufferResource);
if (!vertexBufferRes) {
continue;
}
const auto& bufferResource = vertexBufferRes->getBuffer();
if (!bufferResource.getBuffer()) {
continue;
}
const uint64_t bindingByteOffset = static_cast<uint64_t>(binding->vertexOffset) * binding->vertexStride;
auto it = std::find_if(out.begin(), out.end(), [&](const UniqueBinding& entry) {
return entry.vertexResource == vertexBufferRes && entry.stride == binding->vertexStride &&
entry.stepMode == stepMode;
});
if (it == out.end()) {
UniqueBinding entry;
entry.vertexResource = vertexBufferRes;
entry.buffer = &bufferResource;
entry.stride = binding->vertexStride;
entry.stepMode = stepMode;
entry.baseOffset = bindingByteOffset;
entry.attributes.reserve(4);
entry.attributeByteOffsets.reserve(4);
out.push_back(std::move(entry));
it = out.end() - 1;
} else {
it->baseOffset = std::min(it->baseOffset, bindingByteOffset);
}
WGPUVertexAttribute attr = {};
attr.format = wgpuVertexFormatOf(binding->attribute.dataType);
attr.shaderLocation = static_cast<uint32_t>(attributeIndex);
attr.offset = binding->attribute.offset;
it->attributes.push_back(attr);
it->attributeByteOffsets.push_back(bindingByteOffset);
}
};
std::vector<UniqueBinding> uniqueBindings;
uniqueBindings.reserve(impl->attributeBindings.size() + impl->instanceBindings.size());
accumulateBindings(impl->attributeBindings, WGPUVertexStepMode_Vertex, uniqueBindings);
accumulateBindings(impl->instanceBindings, WGPUVertexStepMode_Instance, uniqueBindings);
constexpr uint32_t maxVertexBufferSlots = 8;
if (uniqueBindings.size() > maxVertexBufferSlots) {
Log::Warning(Event::Render,
"WebGPU: reducing vertex buffer bindings from " + std::to_string(uniqueBindings.size()) + " to " +
std::to_string(maxVertexBufferSlots));
uniqueBindings.resize(maxVertexBufferSlots);
}
for (auto& entry : uniqueBindings) {
for (size_t j = 0; j < entry.attributes.size(); ++j) {
if (entry.attributeByteOffsets[j] < entry.baseOffset) {
continue;
}
const auto delta = entry.attributeByteOffsets[j] - entry.baseOffset;
entry.attributes[j].offset += static_cast<uint32_t>(delta);
}
}
auto& shaderWebGPU = static_cast<mbgl::webgpu::ShaderProgram&>(*shader);
if (!impl->pipelineState) {
// Create vertex layout from attribute bindings
std::vector<WGPUVertexBufferLayout> vertexLayouts;
vertexLayouts.reserve(uniqueBindings.size());
for (auto& binding : uniqueBindings) {
WGPUVertexBufferLayout layout = {};
layout.arrayStride = binding.stride;
layout.stepMode = binding.stepMode;
layout.attributeCount = binding.attributes.size();
layout.attributes = binding.attributes.data();
vertexLayouts.push_back(layout);
}
// Get render pipeline similar to Metal's getRenderPipelineState
// Use the actual color mode from the drawable (like Metal does)
const auto& colorMode = getColorMode();
// Get the renderable from render pass descriptor like Metal does
const auto& renderable = webgpuRenderPass.getDescriptor().renderable;
gfx::DrawModeType drawModeType = gfx::DrawModeType::Triangles;
if (!impl->segments.empty()) {
drawModeType = impl->segments.front()->getMode().type;
}
impl->pipelineState = shaderWebGPU.getRenderPipeline(renderable,
vertexLayouts.empty() ? nullptr : vertexLayouts.data(),
vertexLayouts.size(),
colorMode,
depthMode,
stencilMode,
drawModeType,
std::nullopt);
if (!impl->pipelineState) {
Log::Warning(Event::Render,
"WebGPU: skipping pipeline creation for drawable '" + getName() + "' (shader='" +
shaderWebGPU.typeName().data() + "')");
}
}
if (impl->pipelineState) {
if (!renderPassEncoder) {
Log::Error(Event::Render, "renderPassEncoder is null!");
return;
}
wgpuRenderPassEncoderSetPipeline(renderPassEncoder, impl->pipelineState);
if (getEnableStencil()) {
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, static_cast<uint32_t>(stencilMode.ref));
}
} else {
Log::Warning(Event::Render, "WebGPU: no pipeline available for drawable '" + getName() + "'; draw skipped");
return;
}
uint32_t bufferSlot = 0;
for (const auto& binding : uniqueBindings) {
if (!binding.buffer || !binding.buffer->getBuffer()) {
continue;
}
const uint64_t bufferSize = binding.buffer->getSizeInBytes();
if (binding.baseOffset >= bufferSize) {
Log::Warning(Event::Render,
"Skipping vertex buffer slot " + std::to_string(bufferSlot) + " due to base offset " +
std::to_string(binding.baseOffset) + " >= size " + std::to_string(bufferSize));
continue;
}
wgpuRenderPassEncoderSetVertexBuffer(renderPassEncoder,
bufferSlot,
binding.buffer->getBuffer(),
binding.baseOffset,
bufferSize - binding.baseOffset);
bufferSlot++;
}
// Bind index buffer if present
if (impl->indexes && impl->indexes->elements() > 0 && !impl->indexes->getDirty()) {
// Get the buffer from indexes
if (const auto* indexBufferBase = impl->indexes->getBuffer()) {
if (const auto* webgpuIndexBuffer = static_cast<const IndexBuffer*>(indexBufferBase)) {
if (webgpuIndexBuffer->buffer) {
const auto& indexBufferRes = webgpuIndexBuffer->buffer->getResource<IndexBufferResource>();
const auto& bufferResource = indexBufferRes.getBuffer();
if (bufferResource.getBuffer()) {
// Always use Uint16 format for now
WGPUIndexFormat indexFormat = WGPUIndexFormat_Uint16;
wgpuRenderPassEncoderSetIndexBuffer(renderPassEncoder,
bufferResource.getBuffer(),
indexFormat,
0,
bufferResource.getSizeInBytes());
}
}
}
}
}
// Set bind group
for (const auto& record : impl->bindGroups) {
if (record.handle) {
wgpuRenderPassEncoderSetBindGroup(renderPassEncoder, record.slot, record.handle, 0, nullptr);
}
}
// Handle depth and stencil states (like Metal does)
// For 3D mode, stenciling is handled by the layer group
if (!is3D) {
if (enableStencil) {
if (tileID) {
const auto expectedStencil = parameters.stencilModeForClipping(tileID->toUnwrapped());
#if !defined(NDEBUG)
assert(hashStencilMode(expectedStencil) == hashStencilMode(impl->stencilMode));
#else
if (hashStencilMode(expectedStencil) != hashStencilMode(impl->stencilMode)) {
Log::Warning(
Event::Render,
"WebGPU drawable stencil mismatch for '" + getName() + "' tile=" + util::toString(*tileID));
}
#endif
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, static_cast<uint32_t>(expectedStencil.ref));
}
} else {
// Reset the reference so subsequent drawables that re-enable stencilling start from a known value.
wgpuRenderPassEncoderSetStencilReference(renderPassEncoder, 0u);
}
if (getEnableDepth()) {
#if !defined(NDEBUG)
const auto expectedDepth = parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType());
assert(expectedDepth.func == impl->depthMode.func);
assert(expectedDepth.mask == impl->depthMode.mask);
#endif
}
}
// Draw indexed geometry - loop through segments (exactly like Metal does)
// int segmentCount = 0;
for (const auto& seg_ : impl->segments) {
const auto& segment = static_cast<DrawSegment&>(*seg_);
const auto& mlSegment = segment.getSegment();
if (mlSegment.indexLength > 0) {
const uint32_t instanceCount = instanceAttributes ? instanceAttributes->getMaxCount() : 1;
const uint32_t indexOffset = mlSegment.indexOffset;
const int32_t baseVertex = static_cast<int32_t>(mlSegment.vertexOffset);
const uint32_t baseInstance = 0;
// Check if encoder is valid before drawing
if (!renderPassEncoder) {
Log::Error(Event::Render, "Render pass encoder became null before draw!");
return;
}
// Make sure we have valid indices
if (mlSegment.indexLength == 0) {
Log::Warning(Event::Render, "Skipping draw with 0 indices");
continue;
}
wgpuRenderPassEncoderDrawIndexed(renderPassEncoder,
mlSegment.indexLength, // indexCount
instanceCount, // instanceCount
indexOffset, // firstIndex
baseVertex, // baseVertex
baseInstance); // firstInstance
context.renderingStats().numDrawCalls++;
}
}
}
void Drawable::setIndexData(gfx::IndexVectorBasePtr indices, std::vector<UniqueDrawSegment> segments) {
impl->indexes = std::move(indices);
impl->segments = std::move(segments);
}
void Drawable::setVertices(std::vector<uint8_t>&& data, std::size_t count, gfx::AttributeDataType dataType) {
impl->vertexCount = count;
impl->vertexType = dataType;
if (count && dataType != gfx::AttributeDataType::Invalid && !data.empty()) {
if (!vertexAttributes) {
vertexAttributes = std::make_shared<gfx::VertexAttributeArray>();
}
if (auto& attrib = vertexAttributes->set(impl->vertexAttrId, /*index=*/-1, dataType)) {
attrib->setRawData(std::move(data));
attrib->setStride(gfx::VertexAttribute::getStrideOf(dataType));
} 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::setVertexAttrId(const size_t id) {
impl->vertexAttrId = id;
}
void Drawable::updateVertexAttributes(gfx::VertexAttributeArrayPtr vertices,
std::size_t vertexCount,
gfx::DrawMode mode,
gfx::IndexVectorBasePtr indexes,
const SegmentBase* segments,
std::size_t segmentCount) {
// Store the vertex attributes (base class method)
gfx::Drawable::setVertexAttributes(std::move(vertices));
// Store vertex count
impl->vertexCount = vertexCount;
// Handle segments - check for nullptr
std::vector<UniqueDrawSegment> drawSegs;
if (segments && segmentCount > 0) {
drawSegs.reserve(segmentCount);
for (std::size_t i = 0; i < segmentCount; ++i) {
const auto& seg = segments[i];
drawSegs.push_back(std::make_unique<DrawSegment>(
mode, SegmentBase{seg.vertexOffset, seg.indexOffset, seg.vertexLength, seg.indexLength, seg.sortKey}));
}
}
// Set the index data with segments
setIndexData(std::move(indexes), std::move(drawSegs));
}
} // namespace webgpu
} // namespace mbgl

Some files were not shown because too many files have changed in this diff Show More