Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New paint property fill-comp-op for fill type layer #1191

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

tongust
Copy link

@tongust tongust commented Apr 19, 2022

Description

This PR is to add new paint property opacity-per-layer to draw fill type layer.
By setting fill-opacity, multiple polygons can be drawn with transparency for data visualization. But in some scenarios, the fill-opacity is misleading.

  • The opacity on overlapped/stacked polygons is increased. It can be opaque when stacking more polygons.
  • The color of overlapped area is mixed too. In the left below image , the overlaped region is not red but dark red.
Current fill opacity This new feature(by setting fill-per-layer-opacity as true)
image image

With this change, the opacity of whole layer is fixed and the color will always be same to one of the top most shape.

Note:

  • To open this feature, the option fill-per-layer-opacity need to be set as true explicitly
  • Currently it doesn't support line per-layer-opacity, which is one of my TODOs.

Launch Checklist

  • Confirm your changes do not include backports from Mapbox projects (unless with compliant license) - if you are not sure about this, please ask!
  • Briefly describe the changes in this PR.
  • Include before/after visuals or gifs if this PR includes visual changes.
  • Write tests for all new functionality.
  • Document any changes to public APIs.
  • Post benchmark scores.
  • Manually test the debug page.
  • Suggest a changelog category: bug/feature/docs/etc. or "skip changelog".
  • Add an entry inside this element for inclusion in the maplibre-gl-js changelog: <changelog></changelog>.

@tongust
Copy link
Author

tongust commented Apr 19, 2022

Benchmarks

Test1:

image

Test2:

image

All reports in PDF:

all.pdf

About the performance results of HillshadeLoad

The scores were tested between product and my local main branch. And the time cost is close to the one of feature branch. So my PR doesn't bring any regression for HillshadeLoad.

image

@github-actions
Copy link
Contributor

github-actions bot commented Apr 19, 2022

Bundle size report:

Size Change: +335 B
Total Size Before: 202 kB
Total Size After: 202 kB

Output file Before After Change
maplibre-gl.js 193 kB 193 kB +335 B
maplibre-gl.css 9.41 kB 9.41 kB 0 B
ℹ️ View Details
Source file Before After Change
src/style-spec/error/validation_error.ts 119 B 3.58 kB +3.46 kB
src/render/draw_fill.ts 1.01 kB 1.68 kB +666 B
src/render/program/fill_program.ts 573 B 812 B +239 B
src/shaders/fillfbo.fragment.glsl.g.ts 0 B 168 B +168 B
src/shaders/fillfbo.vertex.glsl.g.ts 0 B 144 B +144 B
node_modules/gl-matrix/esm/vec4.js 419 B 517 B +98 B
src/render/uniform_binding.ts 647 B 721 B +74 B
src/style/style_layer/fill_style_layer.ts 277 B 350 B +73 B
src/symbol/symbol_size.ts 575 B 645 B +70 B
src/util/actor.ts 813 B 845 B +32 B
node_modules/gl-matrix/esm/mat4.js 2.76 kB 2.79 kB +30 B
src/shaders/shaders.ts 1.49 kB 1.52 kB +30 B
src/render/program/program_uniforms.ts 926 B 956 B +30 B
src/data/bucket/pattern_attributes.ts 111 B 138 B +27 B
src/util/util.ts 1.83 kB 1.85 kB +19 B
src/data/feature_position_map.ts 554 B 569 B +15 B
src/symbol/symbol_layout.ts 3.63 kB 3.65 kB +15 B
src/style/style_layer/fill_style_layer_properties.g.ts 195 B 205 B +10 B
src/render/draw_debug.ts 1.13 kB 1.14 kB +10 B
src/style-spec/expression/types.ts 500 B 509 B +9 B
src/render/program/symbol_program.ts 1.29 kB 1.3 kB +9 B
node_modules/earcut/src/earcut.js 2.62 kB 2.63 kB +8 B
node_modules/@mapbox/whoots-js/index.mjs 273 B 281 B +8 B
src/util/debug.ts 156 B 164 B +8 B
src/data/segment.ts 446 B 453 B +7 B
node_modules/@mapbox/vector-tile/lib/vectortilefeature.js 1.01 kB 1.01 kB +7 B
src/index.ts 866 B 873 B +7 B
src/style-spec/expression/definitions/index.ts 1.63 kB 1.64 kB +6 B
src/style-spec/validate/validate_expression.ts 451 B 457 B +6 B
src/data/bucket/circle_bucket.ts 966 B 972 B +6 B
src/style/style_layer/hillshade_style_layer_properties.g.ts 155 B 161 B +6 B
src/symbol/shaping.ts 3.62 kB 3.63 kB +6 B
src/symbol/quads.ts 1.88 kB 1.89 kB +6 B
src/util/request_manager.ts 352 B 357 B +5 B
src/render/line_atlas.ts 983 B 988 B +5 B
src/util/dispatcher.ts 332 B 337 B +5 B
src/render/draw_hillshade.ts 1.15 kB 1.15 kB +5 B
src/ui/control/navigation_control.ts 1.61 kB 1.61 kB +5 B
src/style-spec/expression/types/formatted.ts 413 B 417 B +4 B
src/style-spec/validate/validate_object.ts 387 B 391 B +4 B
src/util/image.ts 674 B 678 B +4 B
src/data/bucket/fill_extrusion_bucket.ts 1.42 kB 1.43 kB +4 B
src/util/verticalize_punctuation.ts 584 B 588 B +4 B
src/style/style_layer/symbol_style_layer.ts 1.02 kB 1.02 kB +4 B
src/util/web_worker.ts 39 B 43 B +4 B
src/style/load_sprite.ts 406 B 410 B +4 B
src/ui/handler/box_zoom.ts 713 B 717 B +4 B
src/util/transferable_grid_index.ts 1.01 kB 1.02 kB +3 B
src/util/is_char_in_unicode_block.ts 876 B 879 B +3 B
src/style/evaluation_parameters.ts 387 B 390 B +3 B
src/data/bucket/circle_attributes.ts 92 B 95 B +3 B
src/style/style_layer/line_style_layer.ts 816 B 819 B +3 B
src/style/style_layer/custom_style_layer.ts 485 B 488 B +3 B
src/util/throttled_invoker.ts 209 B 212 B +3 B
src/data/feature_index.ts 1.74 kB 1.74 kB +3 B
src/source/geojson_source.ts 1.28 kB 1.29 kB +3 B
src/source/source_cache.ts 3.99 kB 3.99 kB +3 B
src/shaders/fill_extrusion_pattern.vertex.glsl.g.ts 826 B 829 B +3 B
src/render/program/pattern.ts 617 B 620 B +3 B
src/render/program/circle_program.ts 454 B 457 B +3 B
src/gl/index_buffer.ts 297 B 300 B +3 B
src/gl/context.ts 1.28 kB 1.28 kB +3 B
src/gl/cull_face_mode.ts 154 B 157 B +3 B
src/render/draw_fill_extrusion.ts 829 B 832 B +3 B
src/render/draw_raster.ts 1.04 kB 1.04 kB +3 B
src/util/smart_wrap.ts 234 B 237 B +3 B
src/ui/control/scale_control.ts 742 B 745 B +3 B
src/style-spec/expression/parsing_error.ts 91 B 93 B +2 B
src/style-spec/expression/definitions/format.ts 729 B 731 B +2 B
src/style-spec/expression/definitions/within.ts 1.38 kB 1.39 kB +2 B
src/style-spec/expression/definitions/at.ts 399 B 401 B +2 B
src/style-spec/validate/validate_light.ts 289 B 291 B +2 B
src/util/intersection_tests.ts 940 B 942 B +2 B
node_modules/gl-matrix/esm/common.js 182 B 184 B +2 B
src/data/bucket/heatmap_bucket.ts 80 B 82 B +2 B
src/util/classify_rings.ts 244 B 246 B +2 B
src/style/style_layer/fill_extrusion_style_layer.ts 923 B 925 B +2 B
src/symbol/collision_feature.ts 377 B 379 B +2 B
src/style/style_layer/heatmap_style_layer.ts 351 B 353 B +2 B
src/geo/lng_lat_bounds.ts 612 B 614 B +2 B
node_modules/gl-matrix/esm/vec2.js 225 B 227 B +2 B
src/data/raster_bounds_attributes.ts 96 B 98 B +2 B
src/source/query_features.ts 1.21 kB 1.21 kB +2 B
src/source/tile_cache.ts 555 B 557 B +2 B
src/symbol/placement.ts 4.8 kB 4.8 kB +2 B
src/render/terrain.ts 2.27 kB 2.27 kB +2 B
src/shaders/fill_outline.vertex.glsl.g.ts 216 B 218 B +2 B
src/shaders/fill_pattern.fragment.glsl.g.ts 394 B 396 B +2 B
src/shaders/line_pattern.vertex.glsl.g.ts 787 B 789 B +2 B
src/shaders/symbol_text_and_icon.vertex.glsl.g.ts 1.01 kB 1.01 kB +2 B
src/render/draw_symbol.ts 2.59 kB 2.59 kB +2 B
src/render/draw_terrain.ts 1.8 kB 1.8 kB +2 B
src/render/draw_custom.ts 333 B 335 B +2 B
src/geo/edge_insets.ts 428 B 430 B +2 B
node_modules/gl-matrix/esm/mat2.js 227 B 229 B +2 B
src/ui/handler/handler_util.ts 103 B 105 B +2 B
src/ui/handler/mouse.ts 554 B 556 B +2 B
src/ui/handler/click_zoom.ts 240 B 242 B +2 B
src/ui/handler/shim/drag_pan.ts 221 B 223 B +2 B
src/style-spec/expression/types/resolved_image.ts 151 B 152 B +1 B
src/style-spec/expression/definitions/var.ts 336 B 337 B +1 B
src/style-spec/expression/definitions/comparison.ts 871 B 872 B +1 B
src/style-spec/expression/definitions/length.ts 370 B 371 B +1 B
src/style-spec/util/get_type.ts 132 B 133 B +1 B
src/style-spec/expression/index.ts 1.6 kB 1.6 kB +1 B
src/style-spec/validate/validate_number.ts 221 B 222 B +1 B
src/style-spec/validate/validate_layer.ts 863 B 864 B +1 B
src/style-spec/validate/validate_terrain.ts 241 B 242 B +1 B
src/style-spec/validate/validate.ts 385 B 386 B +1 B
src/style-spec/validate/validate_color.ts 141 B 142 B +1 B
src/style-spec/validate/validate_image.ts 61 B 62 B +1 B
src/style-spec/validate/validate_glyphs_url.ts 171 B 172 B +1 B
src/util/color_ramp.ts 321 B 322 B +1 B
node_modules/@mapbox/vector-tile/index.js 90 B 91 B +1 B
src/data/bucket/line_attributes.ts 118 B 119 B +1 B
node_modules/ieee754/index.js 563 B 564 B +1 B
src/symbol/anchor.ts 170 B 171 B +1 B
src/symbol/check_max_angle.ts 286 B 287 B +1 B
src/style/style_layer/symbol_style_layer_properties.g.ts 652 B 653 B +1 B
src/style/style_layer/hillshade_style_layer.ts 137 B 138 B +1 B
src/style/style_layer/background_style_layer.ts 65 B 66 B +1 B
src/data/dem_data.ts 832 B 833 B +1 B
src/style-spec/util/deep_equal.ts 194 B 195 B +1 B
node_modules/gl-matrix/esm/vec3.js 813 B 814 B +1 B
src/render/glyph_manager.ts 955 B 956 B +1 B
src/source/video_source.ts 874 B 875 B +1 B
src/util/global_worker_pool.ts 313 B 314 B +1 B
src/symbol/path_interpolator.ts 311 B 312 B +1 B
src/style/pauseable_placement.ts 598 B 599 B +1 B
src/symbol/cross_tile_symbol_index.ts 1.13 kB 1.13 kB +1 B
src/shaders/line.fragment.glsl.g.ts 324 B 325 B +1 B
src/shaders/raster.fragment.glsl.g.ts 438 B 439 B +1 B
src/shaders/terrain_depth.fragment.glsl.g.ts 196 B 197 B +1 B
src/render/program.ts 1.14 kB 1.14 kB +1 B
src/render/program/heatmap_program.ts 559 B 560 B +1 B
src/gl/vertex_buffer.ts 540 B 541 B +1 B
src/gl/stencil_mode.ts 153 B 154 B +1 B
src/ui/hash.ts 934 B 935 B +1 B
src/util/throttle.ts 142 B 143 B +1 B
src/ui/handler/keyboard.ts 570 B 571 B +1 B
src/ui/control/attribution_control.ts 1.09 kB 1.09 kB +1 B
src/ui/control/logo_control.ts 502 B 503 B +1 B
src/ui/control/geolocate_control.ts 2.26 kB 2.26 kB +1 B
src/style-spec/util/unbundle_jsonlint.ts 180 B 179 B -1 B
src/style-spec/expression/definitions/image.ts 296 B 295 B -1 B
src/style-spec/expression/definitions/let.ts 474 B 473 B -1 B
src/style-spec/validate/validate_array.ts 356 B 355 B -1 B
src/style-spec/validate/validate_enum.ts 210 B 209 B -1 B
src/style-spec/feature_filter/index.ts 893 B 892 B -1 B
src/style-spec/validate/validate_source.ts 615 B 614 B -1 B
src/util/web_worker_transfer.ts 952 B 951 B -1 B
src/util/struct_array.ts 698 B 697 B -1 B
src/data/load_geometry.ts 255 B 254 B -1 B
src/data/evaluation_feature.ts 96 B 95 B -1 B
node_modules/@mapbox/vector-tile/lib/vectortilelayer.js 432 B 431 B -1 B
src/data/bucket/line_bucket.ts 2.42 kB 2.41 kB -1 B
src/symbol/transform_text.ts 206 B 205 B -1 B
src/util/dictionary_coder.ts 153 B 152 B -1 B
src/util/vectortile_to_geojson.ts 251 B 250 B -1 B
src/source/tile_bounds.ts 318 B 317 B -1 B
src/source/raster_tile_source.ts 912 B 911 B -1 B
src/source/canvas_source.ts 1.02 kB 1.02 kB -1 B
src/source/source_state.ts 603 B 602 B -1 B
src/util/worker_pool.ts 419 B 418 B -1 B
src/symbol/grid_index.ts 1.35 kB 1.35 kB -1 B
src/symbol/projection.ts 1.82 kB 1.82 kB -1 B
src/shaders/line_sdf.vertex.glsl.g.ts 808 B 807 B -1 B
src/shaders/raster.vertex.glsl.g.ts 202 B 201 B -1 B
src/shaders/terrain_coords.fragment.glsl.g.ts 169 B 168 B -1 B
node_modules/gl-matrix/esm/mat3.js 224 B 223 B -1 B
src/render/program/raster_program.ts 561 B 560 B -1 B
src/gl/framebuffer.ts 221 B 220 B -1 B
src/gl/color_mode.ts 173 B 172 B -1 B
src/ui/handler_inertia.ts 821 B 820 B -1 B
src/ui/handler/tap_zoom.ts 368 B 367 B -1 B
src/style-spec/expression/is_constant.ts 255 B 253 B -2 B
src/style-spec/validate/validate_paint_property.ts 54 B 52 B -2 B
src/style/validate_style.ts 154 B 152 B -2 B
src/style/style_layer/circle_style_layer.ts 530 B 528 B -2 B
src/data/bucket/pattern_bucket_features.ts 325 B 323 B -2 B
src/symbol/one_em.ts 32 B 30 B -2 B
node_modules/potpack/index.mjs 355 B 353 B -2 B
src/geo/mercator_coordinate.ts 348 B 346 B -2 B
src/source/load_tilejson.ts 272 B 270 B -2 B
src/style-spec/diff.ts 1.53 kB 1.53 kB -2 B
src/source/pixels_to_tile_units.ts 103 B 101 B -2 B
src/shaders/fill.vertex.glsl.g.ts 173 B 171 B -2 B
src/shaders/fill_pattern.vertex.glsl.g.ts 389 B 387 B -2 B
src/shaders/fill_extrusion_pattern.fragment.glsl.g.ts 417 B 415 B -2 B
src/shaders/line.vertex.glsl.g.ts 709 B 707 B -2 B
src/shaders/line_pattern.fragment.glsl.g.ts 707 B 705 B -2 B
src/shaders/symbol_text_and_icon.fragment.glsl.g.ts 598 B 596 B -2 B
src/geo/transform.ts 4.45 kB 4.45 kB -2 B
src/ui/handler/tap_recognizer.ts 536 B 534 B -2 B
src/ui/handler/scroll_zoom.ts 1.25 kB 1.25 kB -2 B
src/ui/handler/shim/touch_zoom_rotate.ts 292 B 290 B -2 B
src/util/task_queue.ts 268 B 266 B -2 B
src/ui/map.ts 6.33 kB 6.33 kB -2 B
src/ui/marker.ts 2.88 kB 2.88 kB -2 B
src/style-spec/validate/validate_function.ts 1.14 kB 1.14 kB -3 B
node_modules/murmurhash-js/murmurhash3_gc.js 371 B 368 B -3 B
src/style/style_layer/fill_extrusion_style_layer_properties.g.ts 191 B 188 B -3 B
src/util/resolve_tokens.ts 97 B 94 B -3 B
node_modules/@mapbox/mapbox-gl-supported/index.js 975 B 972 B -3 B
src/util/dom.ts 658 B 655 B -3 B
src/style/light.ts 559 B 556 B -3 B
src/source/raster_dem_tile_source.ts 971 B 968 B -3 B
src/source/source.ts 353 B 350 B -3 B
src/source/tile.ts 1.99 kB 1.99 kB -3 B
src/render/vertex_array_object.ts 581 B 578 B -3 B
src/render/program/terrain_program.ts 609 B 606 B -3 B
src/render/program/collision_program.ts 724 B 721 B -3 B
src/render/program/clipping_mask_program.ts 106 B 103 B -3 B
src/render/draw_heatmap.ts 1.04 kB 1.04 kB -3 B
src/ui/handler_manager.ts 2.46 kB 2.46 kB -3 B
src/ui/default_locale.ts 312 B 309 B -3 B
src/style/style_layer/circle_style_layer_properties.g.ts 229 B 225 B -4 B
src/render/image_atlas.ts 833 B 829 B -4 B
src/geo/lng_lat.ts 588 B 584 B -4 B
node_modules/@mapbox/tiny-sdf/index.js 1.1 kB 1.1 kB -4 B
src/symbol/collision_index.ts 1.64 kB 1.64 kB -4 B
src/style/style.ts 6.85 kB 6.85 kB -4 B
src/style-spec/validate/validate_constants.ts 116 B 111 B -5 B
src/style-spec/expression/definitions/number_format.ts 605 B 600 B -5 B
src/style/query_utils.ts 488 B 483 B -5 B
src/util/primitives.ts 1 kB 997 B -5 B
src/ui/camera.ts 3 kB 3 kB -5 B
src/ui/popup.ts 1.91 kB 1.91 kB -5 B
src/ui/control/fullscreen_control.ts 857 B 852 B -5 B
src/style-spec/expression/types/collator.ts 207 B 201 B -6 B
src/util/script_detection.ts 1.65 kB 1.65 kB -6 B
src/shaders/encode_attribute.ts 86 B 80 B -6 B
src/data/bucket/fill_attributes.ts 118 B 112 B -6 B
src/style/style_layer/background_style_layer_properties.g.ts 116 B 109 B -7 B
src/render/program/line_program.ts 1.16 kB 1.16 kB -7 B
src/render/program/background_program.ts 475 B 468 B -7 B
node_modules/csscolorparser/csscolorparser.js 2.06 kB 2.05 kB -8 B
node_modules/murmurhash-js/index.js 76 B 68 B -8 B
src/source/tile_id.ts 1.15 kB 1.14 kB -8 B
src/render/painter.ts 4.03 kB 4.02 kB -8 B
src/data/array_types.g.ts 2.82 kB 2.81 kB -10 B
src/style/style_layer/heatmap_style_layer_properties.g.ts 141 B 131 B -10 B
src/render/program/hillshade_program.ts 886 B 876 B -10 B
src/style/properties.ts 1.87 kB 1.86 kB -11 B
node_modules/quickselect/index.js 360 B 349 B -11 B
src/data/bucket/fill_extrusion_attributes.ts 134 B 123 B -11 B
node_modules/murmurhash-js/murmurhash2_gc.js 265 B 250 B -15 B
src/util/ajax.ts 2.37 kB 2.34 kB -35 B
src/style/create_style_layer.ts 387 B 345 B -42 B
src/source/rtl_text_plugin.ts 858 B 802 B -56 B
src/style/parse_glyph_pbf.ts 397 B 334 B -63 B
src/util/tile_request_cache.ts 930 B 849 B -81 B
src/util/performance.ts 785 B 682 B -103 B
src/util/evented.ts 3.94 kB 504 B -3.44 kB

@tongust tongust changed the title fill: opacity per layer Opacity-per-layer fill May 3, 2022
@tongust tongust force-pushed the fill-opacity-per-layer branch from f20692a to 9916f5b Compare May 3, 2022 14:51
@tongust tongust marked this pull request as ready for review May 3, 2022 15:07
@tongust tongust changed the title Opacity-per-layer fill Add Opacity-per-layer paint property to fill May 3, 2022
@tongust tongust changed the title Add Opacity-per-layer paint property to fill Add opacity-per-layer paint property to fill May 3, 2022
@tongust tongust changed the title Add opacity-per-layer paint property to fill New paint property opacity-per-layer for fill type layer May 3, 2022
@wipfli
Copy link
Contributor

wipfli commented May 3, 2022

Looks interesting, thanks. Can you post the full CLI benchmark results? And can you ping me on slack?

@tongust
Copy link
Author

tongust commented May 4, 2022

Looks interesting, thanks. Can you post the full CLI benchmark results? And can you ping me on slack?

Updated all benchmark results in above Benchmarks section.

@HarelM
Copy link
Collaborator

HarelM commented May 4, 2022

I'm not sure the name is intuitive, also I'm not sure I understand what it should solve.
Can you elaborate a bit more about this?
The triangles above are different but it's not clear why and what caused each rendering.

@tongust
Copy link
Author

tongust commented May 5, 2022

I'm not sure the name is intuitive, also I'm not sure I understand what it should solve. Can you elaborate a bit more about this? The triangles above are different but it's not clear why and what caused each rendering.

This pr is an attempt to address the legacy issue of mapbox https://github.com/mapbox/mapbox-gl-js/issues/4090. The fill opacity style of main branch is per geometry, in some cases, per-layer is needed.

  • Per-geometry (main branch). Each geometry is rendered immediately into main frame buffer (screen). And the results are:

    • The opacity on overlapping area is changed. And it could be opaque if more geometries are stacked.
    • Its color is not same to the one of the last shape to add to the map.
  • Per-layer. In each layer, every geometry is rendered into off-screen firstly and then all geometries of this layer are drawn into main frame buffer with some opacity. And the results are:

    • The opacity of whole layer is same.
    • The color is same to the one of top most shape.

And note that the per-layer opacity only supports fill type for now. For line and other type, they are in my TODO list.

@xabbu42
Copy link
Contributor

xabbu42 commented May 5, 2022

@HarelM I think this feature is very important especially for lines as the current rendering is often not the desired effect. We have for example the use case of a semi-transparent street-network overlay over satellite images. currently intersections are two dark because multiple streets overlap. There are a lot of use-cases with images in multiple mapbox issues which would benefit from this.

@tongust It was my understanding that this is difficult to achieve in a performant way. Do you have any input on possible performance impact of this pull request with and without enabling the feature?

I personally am not sure about the coverage of the benchmarks, but this seems as this should be covered so your change seems to not have any impact on existing styles. Is this result expected?

P.S. HillshadeLoad seems completely unproblematic and also almost certainly not connected to this pull request anyway. The difference just looks big in the graphic because this is a slow benchmark in general.

@tongust
Copy link
Author

tongust commented May 5, 2022

@HarelM I think this feature is very important especially for lines as the current rendering is often not the desired effect. We have for example the use case of a semi-transparent street-network overlay over satellite images. currently intersections are two dark because multiple streets overlap. There are a lot of use-cases with images in multiple mapbox issues which would benefit from this.

@tongust It was my understanding that this is difficult to achieve in a performant way. Do you have any input on possible performance impact of this pull request with and without enabling the feature?

I personally am not sure about the coverage of the benchmarks, but this seems as this should be covered so your change seems to not have any impact on existing styles. Is this result expected?

P.S. HillshadeLoad seems completely unproblematic and also almost certainly not connected to this pull request anyway. The difference just looks big in the graphic because this is a slow benchmark in general.

@xabbu42

  • The per-layer opacity for line type is one of the TODOs.

  • Yes, impact is measured between main and the branch whose feature is forced to be enabled. There is no regression (especially for LayerFill).

image

  • Agree with you. This pr should have no imapct on existing styles, and the feature is disabled by default.

@HarelM
Copy link
Collaborator

HarelM commented May 5, 2022

Thanks for the information!
Sorry for nagging, but in order to properly review this I need to better understand the design.
So some questions and requests:

  1. How does it help to render the polygons in the offscreen canvas first? Does it merge the polygons in a way that overlapping polygons will be joint?
  2. According to the explanation I would like to see a simpler tests: two polygons with the same color overlapping and creating a more opaque one before the fix and a smooth joint polygon after the fix, did I get it right?
  3. A layer can have the opacity and color changed by data driven properties, how will this affect this feature?
  4. Can you add some unit tests?

That's it for now, thanks a lot for all the hard work!

@tongust
Copy link
Author

tongust commented May 7, 2022

Thanks for the information! Sorry for nagging, but in order to properly review this I need to better understand the design. So some questions and requests:

  1. How does it help to render the polygons in the offscreen canvas first? Does it merge the polygons in a way that overlapping polygons will be joint?
  2. According to the explanation I would like to see a simpler tests: two polygons with the same color overlapping and creating a more opaque one before the fix and a smooth joint polygon after the fix, did I get it right?
  3. A layer can have the opacity and color changed by data driven properties, how will this affect this feature?
  4. Can you add some unit tests?

That's it for now, thanks a lot for all the hard work!

@HarelM

Sorry for the delayed reply.

1. About the design

Same to heatmap and hillshade types, one frame buffer object is created for fill to render to offscreen (not canvas actually). As we know, in painter, the offscreen render pass is prior to others (opaque/translucent... passes).

For per-geometry opacity, there is no offscreen pass. Each geometry is drawn into canvas (screen) using alpha blending.

For per-layer opacity, there are two render passes. First one is offscreen pass. The color mode of rendering is set to
gl.blendFunc(gl.ONE, gl.ZERO); (additive blending), which means the destinated color = 1 x source color + 0 x background, namely the color will always be overridden by the latest geometry. In the second pass, the offscreen texture will be alpha-blended with the canvas in the same way as per-geometry opacity does.

image

For example, there are two overlapping polygons red A and blue B. B is on the top of A. Their opacity is 0.5, and the background color is rgb(128, 128, 128)

In per-geometry opacity, the process is:

  • Alpha-blend A with background into screen: result color 0 = rgb(255, 0, 0) * 0.5 + rgb(128, 128, 128) * 0.5 = rgb(192, 64, 64)
  • Alpha-blend B with result color 0 into screen: final color = rgb(0, 0, 255) * 0.5 + rgb(192, 64, 64) * 0.5 = rgb(96,32,159)

In per-layer opacity, the process is:

  • Additive-blend A into fbo: result color 0 = rgb(255, 0, 0)
  • Additive-blend B into fbo: result color 1 = rgb(0, 0, 255)
  • Alpha-blend fbo with background into screen: final color = rgb(0, 0, 255) * 0.5 + rgb(128, 128, 128) * 0.5 = rgb(64,64,191)

2. Sampler test cases

Here are the cases (per-geometry opacity):

Current fill opacity This new feature(by setting fill-per-layer-opacity as true)
image image

3. Data driven impact.

Good points! There was one bug here and now it is fixed. The opacity and color can be changed by data driven properties in per-layer opacity.

In below case, the opacity of left green geometry is 0.9 while the right one is 0.4.
image

4. unit test.

Yes, the test draw_fill.test.ts is added.

Copy link
Collaborator

@HarelM HarelM left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit-picking.
I need someone to review the sharers code still.

src/render/draw_fill.ts Outdated Show resolved Hide resolved
src/style-spec/reference/v8.json Outdated Show resolved Hide resolved
@xabbu42
Copy link
Contributor

xabbu42 commented May 9, 2022

Regarding the style spec changes. #48 seems to be related (correct me if I'm wrong). I personally don't know about the use cases for other blending modes but this seems the right time to discuss if we ever want to support other blending modes. In that case we have the option to make an enum setting instead of a boolean to add different blending modes.

@HarelM
Copy link
Collaborator

HarelM commented May 9, 2022

@birkskyum @JannikGM @xabbu42 can any of you review the sharers part? Does it affect maplibre-native?
Also the mention about #48 is a valid point that we should consider, and the part about the lines, I'm just not sure it should be handled as part of this PR.
Maybe a PR to this branch so we can review it separately?
Other than the above I think this is a good improvement! Thanks!

@tongust tongust force-pushed the fill-opacity-per-layer branch from d0c7c8d to 1939f09 Compare May 10, 2022 16:41
@tongust
Copy link
Author

tongust commented May 10, 2022

Regarding the style spec changes. #48 seems to be related (correct me if I'm wrong). I personally don't know about the use cases for other blending modes but this seems the right time to discuss if we ever want to support other blending modes. In that case we have the option to make an enum setting instead of a boolean to add different blending modes.

@xabbu42 @HarelM
#48 is a good reference, Enabling more blending modes is one powerful feature to maplibre, which needs well design and lots of coding tasks. I can change the opation as enum to support other modes in the future. But I don't think all other modes should be added into this PR. Maybe we can have several PRs to complete this wonderful feature, how do you think so?

@tongust tongust changed the title New paint property opacity-per-layer for fill type layer New paint property fill-opacity-per-layer for fill type layer May 10, 2022
@tongust tongust changed the title New paint property fill-opacity-per-layer for fill type layer New paint property fill-opacity-per-geometry for fill type layer May 10, 2022
@xabbu42
Copy link
Contributor

xabbu42 commented May 10, 2022

I agree this PR should not be blocked just for supporting other layer types like line or more blending modes. The current limitations can be documented and we can add more support later without any backward compatibility problems.

If features as described in #48 will be possible and valuable in the future though, we should use an enum instead of a bool for the style spec already in this PR so that it is easier to add more blending modes later without any compatibility issues or inflating the spec with lots of not combinable bool settings.

Unfortunately I don't have the necessary knowledge to review the shader part.

@HarelM
Copy link
Collaborator

HarelM commented May 10, 2022

I'm not sure how urgent this is. We can make PRs against this branch to better define the spec possibilities. I prefer to take the time and define the spec well as it will be very hard to change it in the future...

@tongust
Copy link
Author

tongust commented May 11, 2022

I'm not sure how urgent this is. We can make PRs against this branch to better define the spec possibilities. I prefer to take the time and define the spec well as it will be very hard to change it in the future...

@HarelM It is not urgent 😊. Agree with you to define the spec to make it more extensible and maintainable.

@wipfli
Copy link
Contributor

wipfli commented May 28, 2022

I opened an issue in native to coordinate using the latest shaders, see maplibre/maplibre-native#293

@HarelM
Copy link
Collaborator

HarelM commented Jun 8, 2022

I believe this is good now. I'm happy with the new names.
The only question remaining in this thread is the per-line support. Let me know how you would like to move forward in this aspect.

@xabbu42
Copy link
Contributor

xabbu42 commented Jun 8, 2022

What does source mean in this? I personally would assume the source to be the complete layer and destination to be the map, and that also seems to be how carto uses it. But this patch conceptually does not change how the layer gets composited on the map, instead it changes how the features of the layer are composited on the layer itself. So source here is a single line feature and destination is the layer before composing it on the map. At least to me this names are therefore confusing.

P.S. You are certainly right that implementation of other modes does not need to be part of this PR. Unfortunately for style spec changes we do need to already think about possible future features to avoid problems with backward compatibility in the future.

@tongust
Copy link
Author

tongust commented Jun 8, 2022

I believe this is good now. I'm happy with the new names. The only question remaining in this thread is the per-line support. Let me know how you would like to move forward in this aspect.

Thanks @HarelM The per-line support is on my radar and I will start to work on it. And the feature will be added into another new PR (not mixed with this one), is that okay for you?

@tongust
Copy link
Author

tongust commented Jun 8, 2022

What does source mean in this? I personally would assume the source to be the complete layer and destination to be the map, and that also seems to be how carto uses it. But this patch conceptually does not change how the layer gets composited on the map, instead it changes how the features of the layer are composited on the layer itself. So source here is a single line feature and destination is the layer before composing it on the map. At least to me this names are therefore confusing.

P.S. You are certainly right that implementation of other modes does not need to be part of this PR. Unfortunately for style spec changes we do need to already think about possible future features to avoid problems with backward compatibility in the future.

@xabbu42 The source means the pixel to be drawn into the screen or offscreen in WebGL context (see https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/blendFunc).

For composting each geometry of fill, the source means the latest geometry to be blended with rest geometries. The destination means the composted geometries, but it does not mean the map. So the source is the concept during the process of composting the geometrie.
For source-only, there are two composite operations: blend each geometry and blend this layer with the map.
For source-over, it could have two processes, too. But for performance, each geometry is blended into the map directly since the two composite operations are both source-over.

The name fill-composite-operation is a little confusing here. To distinguish with blending the layer with the map, fill-geometry-composite-operation should be better.

@HarelM
Copy link
Collaborator

HarelM commented Jun 8, 2022

Sure, per line can be a postponed to a different PR, but if it's something that can be added soon I think it would better to wait and merge them together and release a version that had them both. My 2 cents anyway...

@xabbu42
Copy link
Contributor

xabbu42 commented Jun 8, 2022

Thanks for the detailed explanations @tongust. So my confusion was not unfounded.

If we want to be flexibel in the future and support all manner of blending combinations it would make sense to have layer-composite-operation and feature-composite-operation (or geometry-composite-operation).

But if we only want to ever support some combinations with known use-cases I would prefer one property without the word operation (as the property potentially influences multiple different operations), something like blending-mode with values feature-over and layer-over.

I personally would have an obscure use case for feature-copy (or just feature or source?), that is for layer-composite-operation and feature-composite-operation set to source, but I'm not sure that is enough to add this capability to the library.

Just my 2 cents anyway.

@xabbu42
Copy link
Contributor

xabbu42 commented Jun 9, 2022

I wonder if we can mark certain style spec features as experimental/use at your own risk. Then we could just go forward which changes like this, even include them in releases, without committing to support them exactly as they are implemented now.

@HarelM
Copy link
Collaborator

HarelM commented Jun 9, 2022

Maybe we can use [Experimental] in the deception so that it will be written to the docs as well?

@wipfli
Copy link
Contributor

wipfli commented Jun 9, 2022

Sounds like a good idea. It is really hard to never break the style spec but I think this should exactly be our goal... Mapbox aimed to never break the style spec, see mapbox/mapbox-gl-js#6584

@xabbu42 xabbu42 mentioned this pull request Jun 14, 2022
4 tasks
@xabbu42
Copy link
Contributor

xabbu42 commented Jun 14, 2022

I made an issue to move this forward. I don't think this should block on that issue though. Imho it is enough to just document the new features in this branch as experimental as @HarelM suggested to merge it. We can add all the necessary code changes for experimental features retroactively after #1307 is resolved.

@HarelM
Copy link
Collaborator

HarelM commented Jun 20, 2022

@xabbu42 where are we on this? Is the only pending comment is about the names?
I think the code it OK although I'm not very good with all the gl part so I assume this does what it should.

@xabbu42
Copy link
Contributor

xabbu42 commented Jun 23, 2022

I tested the new debug page locally and it behaves as I would expect. As said I can't review the shader part. From my side the only thing missing is to document that this features are experimental and subject to change. Then we can merge this and postpone the decision on the final style spec.

@tongust
Copy link
Author

tongust commented Jun 23, 2022

Sure, per line can be a postponed to a different PR, but if it's something that can be added soon I think it would better to wait and merge them together and release a version that had them both. My 2 cents anyway...

@HarelM I am tring to extend the per-layer feature to the line, but unfortunately it is not easy job because gl codes of draw line are more complicate than ones of draw fill. Maybe we could split it out into another PR.

So if you agree not to wait for supporting line, the only pending comments are about the naming and claiming Experimental in the description.

@xabbu42 Thanks for your suggestions, I think it may be better to have below changes:

  • fill-composite-operation to fill-feature-composite-operation
  • source-only to source. And keep source-over. I personally think source-over is better than feature-over or layer-over since it is more popular.

@wipfli
Copy link
Contributor

wipfli commented Jun 23, 2022

Introducing the experimental flag in the docs has consequences which we need to communicate clearly. Can we discuss this together in the next techincal steering committee meeting? Would be great if you could join @tongust and @xabbu42

@wipfli
Copy link
Contributor

wipfli commented Jun 23, 2022

Things to consider are:

  • deprecation strategy
  • overview of experimental parts
  • version does not affect full style spec
  • "available since" docs could have two versions, one for experimemtal and one for normal

@xabbu42
Copy link
Contributor

xabbu42 commented Jun 24, 2022

@tongust: Just for clarification: the suggestions of feature-over and layer-over are meant as values for a single property blending-mode where there is more than one source and a value like source-over would not be clear.

For the separate property fill-feature-composite-operation source-over is perfectly fine and it would be silly to repeat the feature part which is already in the property name.

@xabbu42
Copy link
Contributor

xabbu42 commented Jun 24, 2022

@wipfli I probably don't have the time today. Where would I find the info to join?

I do not have a strong opinion beyond what is already written in #1307 though.

@wipfli
Copy link
Contributor

wipfli commented Jun 24, 2022

I think we wanted to publish the link to the Technical Steering Committee Meeting in the slack channel, if it is not there, feel free to ping me... Everyone is welcome to join this meeting. This meeting takes place once a month and the next one is scheduled for Wednesday, July 13th, 2022, 20:05 Zurich time.

@maxammann
Copy link
Contributor

maxammann commented Jul 13, 2022

Things to consider are:

* deprecation strategy
* overview of experimental parts
* version does not affect full style spec
* "available since" docs could have two versions, one for experimemtal and one for normal

Proposal based on "Rust ideas":

Essentially every, addition, removal or change to the style specification corresponds to a new version of the style spec. For example the addition of a stable feature would mean that we go from v8 to v8.1.0. The addition of an unstable feature would not touch the style specification version. Whether a style property is stable or unstable can be specified in the style specification. This is similar to Rust stable features vs nightly features.

If you want to use an unstable feature of the style specification then you have to specify it in the style.json:

{
  "version": 8,
  "name": "OSM Liberty",
  "unstable-features": ["fill-comp-op", ...]
  ...
}

That way you have to opt-into unstable features in your style specification. unstable features are only available if you use a nightly build of maplibre (can also be done runtime).

If you use this style.json in a maplibre implementation which does not support any of the unstable-features or version should reject the style.json. Same if unstable features are used in a stable configuration of maplibre (runtime or build-time).

@nyurik thats basically rust nightly/unstable features in maplibre :D

@HarelM
Copy link
Collaborator

HarelM commented Jul 13, 2022

I was thinking more in the direction of a js property - i.e.:

import maplibre from 'maplibre-gl'
maplibre.useExperimetalFeatures = true;

Not sure I have a good reason as to why besides the fact that I want to change the spec as little as possible :-)
But it might be better to move this discussion to a different issue/PR/Discussion...

@ghost
Copy link

ghost commented Aug 31, 2022

I'm surprised I didn't comment here yet (I probably did on Slack?).


I feel this is a bit of a dangerous PR, because it can significantly raise rendering cost for a seemingly trivial property.
I also think that the parameterization isn't very reusable or flexible (..only works with fill type, no advanced blending modes, ..).

Maybe the shown approach is the more mapbox-ish thing to do, however, what I propose as an alternative is to add a new layer type (for advanced users) which can be used to split the layer stack into framebuffers.


(Click here if you want to see my approach to this problem)

My proposed alternative

Introduce new layer types for starting rendering to framebuffer (setting a framebuffer) + stop rendering to framebuffer (resetting to default screen framebuffer) + blitting framebuffers.
More generally: grouping layers into some cache + drawing a cache.

Example (layers from bottom to top):

  • type: cache-start, layoutProperties: { type: 'tiles' } layerId: basemap-cache
  • type: background, layerId: water
  • type: fill, layerId: land-rocks
  • type: fill, layerId: land-grass
  • type: fill, layerId: streets
  • type: cache-end, layerId: screen-rendering
  • type: cache-draw, layoutProperties: { cache: 'basemap-cache', [...] }, layerId: basemap
  • type: symbol+text, layerId: street-labels

What this means

When rendering, the framebuffer would be set by the "cache-set" type layer.
The following layers after that would be rendered into that buffer until a "cache-reset" or another "cache-set".
Then, the framebuffer would be rendered using the "cache-draw" type layer and the street-labels would be drawn last.

This essentially creates layer-groups. This would make maplibre similar to how layers work in graphics editing software, where you can group a bunch of layers, and then composite them differently.
By providing parameters or filters to the blit, we'd be able to implement many more effects. CustomBlitLayer anyone?

There are at least 2 good strategies for the framebuffer: screen-space and tiles.
If the underlying layers don't change, one could also re-use the texture and improve map performance.
In the past, to get a similar effect, we'd initialize multiple mapbox instances, one for the basemap and one for animations.

Since terrain was added, something similar must already exist, too. Because for terrain you also need to render layers into a draping texture (ideally that is also tiled - I don't remember how that works in maplibre) for draping.

That said, it might be a bit tricky to make this work in the existing ecosystem, like CustomLayers, Heatmap or Hillshade, which already do something similar internally (but I think it's still very doable?).

Design choices

Aside from the layer types I mention here, it could also be parameterized differently, such as a "group-start" layer type which sets a framebuffer and a "group-end" layer type which would switch to the screen framebuffer and automatically also blit the current group.
However, I really like that you can draw a cache multiple times, as you might want to create effects like a glow (by first drawing the scene blurred, and then drawing the same scene sharp).

There might also be choices how nested groups would work or if they'd be allowed.

For 3D scenes it might be cool if the depth buffer remained the same, so where you place your group would suddenly be relevant (as you could place your layers infront of buildings [group before extruding buildings, blit after buildings], or behind buildings [group after rendering extruded buildings, blit after buildings]).

Also wether the the previous frame (or multiple?) would be cached etc. might be relevant (and if you could draw before writing to the framebuffer) . Such things could be used for effects like motion-blur.

How to solve the original issue with my proposal?

  • [...]
  • type: cache-set, layoutProperties: { type: 'tiles' } layerId: fill-per-layer-cache
  • type: fill, layerId: fill-per-layer-contents
  • type: cache-reset, layerId: fill-per-layer-cache-end
  • type: cache-draw, layoutProperties: { cache: 'fill-per-layer-cache', [...] }, paintProperties: { opacity: 0.5 }, layerId: fill-per-layer
  • [...]

This is also similar to how this is implemented in this PR - except I propose that this technical detail is exposed for advanced user.


In fact, I did something similar for deck.gl (although it's quite hacky there, too). deck.gl is slightly better suited for it, because it has a layer hierarchy, rather than a list, and it takes whatever argument types you want (including WebGL textures/framebuffers). More importantly I don't need to modify deck.gl itself to implement this, I can just create my own layer outside of the deck.gl codebase.
So what I'd do in deck.gl is to wrap my layers in a "LayerGroup" and get a framebuffer which I can reference later.

That way, we can render some layers blurred (regardless of their type), or recolorize layers (including animations) without recomputing their entire content.

This is all very promising, but the issue with deck.gl is that I don't know what other layers do internally, and they also directly operate on WebGL, so a layer in a group can corrupt the "LayerGroup" renderer; also some built-in features of deck.gl are unaware of my cache.
I don't think this would be an issue for maplibre though.


Although, take this with a grain of salt, because I'm probably thinking about this a bit too technically (being a graphics programmer myself).

@HarelM
Copy link
Collaborator

HarelM commented Sep 8, 2022

@JannikGM there's a CustomLayer that one can use in order to do what you mentioned, I think.
Any chance you can show how this feature/issue can be implemented using this maplibre CustomLayer?
If this can be implemented, relatively easily, using a custom layer then this feature might be an edge case that can be solved this way and we wouldn't have to introduce this risk to maplibre's performance...?

@HarelM HarelM added the need more info Further information is requested label Sep 8, 2022
@ghost
Copy link

ghost commented Sep 9, 2022

@JannikGM there's a CustomLayer that one can use in order to do what you mentioned, I think.
Any chance you can show how this feature/issue can be implemented using this maplibre CustomLayer?

I don't think you can implement my proposal (or this PR) in a custom-layer.
Either of these solutions (the one by tongust or mine) require changes to existing layers (tongust) or the render-algorithm (mine).
However, invoking existing layers (for tongust approach) or modifying the main render-algorithm (for my approach) is impossible from a custom-layer.

A custom-layer is merely an implementation of the (abstracted and simplified) mapbox layer interface where you get to run your own WebGL commands (or whatever else you want to do) when your layer gets rendered - however, it has no context about the layers which come before or after it, so it can't affect them.

That a layer has side-effects on other layers (as in my propsal) would also be something very new (hence, why I called the current PR more mapbox-ish).

If this can be implemented, relatively easily, using a custom layer then this feature might be an edge case that can be solved this way and we wouldn't have to introduce this risk to maplibre's performance...?

To be clear: the performance risk only arises when people turn on the compositing mode for a fill layer, because at that point you end up with switching the framebuffer, which can be a costly operation. You also have to keep an extra framebuffer, for a 1080p resolution, that would likely be (2x 1920x1080 x 4 ~ 16MiB of video memory).
This cost of compositing might not be easy to understand for map-designers.
So the solution for the performance risk is to document that this can be a slow feature.
My proposal might be even worse in this regard.

The more serious issue with this PR (in my opinion), is that the implementation is in the fill layer, and not generic enough to be reusable with other layers or groups of layers. The solution to that is what I proposed, or at least moving it to the render-loop (similar to how we do terrain rendering probably).


I'm also fine with tongust approach, but I feel like we should implement it in the rendering loop (= so you could also have a source-comp operation on a symbol-layer for example) and not in each layer individually (leading to code duplication if we want to support other layers).
However, at that point, it would be similar to what I propose, with the exception that mine allows grouping multiple layers (for example, mixing a solid and fill layer, and then blending the combined result with an opacity).

For terrain rendering, we already render layers offscreen (= to a texture) in the main render loop, which is then draped over a terrain; so I assume that ingredients to this already exist. See this bit of code in the layer rendering loop which handles terrains:

if (renderToTexture && renderToTexture.renderLayer(layer)) continue;

Because it's so similar to a sub-problem of terrain rendering, I'd also be curious what @prozessor13 thinks (who is probably also most familiar with the revised maplibre layer rendering loop).

@prozessor13
Copy link
Collaborator

I did not digg in all details of this PR, and i can not say too much about gl-performance, but as @JannikGM already said, it is a similar scenario of rendering terrain. I think this feature would be very easy to integrate into the terrain-logic. Terrain-code currently already splits style-layers into stacks. e.g. a set of combinable layers which should be rendered to texture. So with some additions:

  • create a new stack with only one layer when comp-op is setted
  • drape the layer with opacity (or any other logic) to terrain

there would be (only in terrain3d) a general solution possible.

Next. Your implementation, same as heatmap, is not compatible with 3d, because all is rendered to one big framebuffer, instead of rendered to tiles. Hillshade, in contrast to heatmap, creates a separate FBO (with a separate texture for each FBO) for every tile, and i think this is the way to go to work in 2d and 3d.
Render-to-texture uses the same logic, but instead of a separate FBOs connected to tiles, there exists a render-pool with currently a set of 30 FBOs. Theoretically this renderpool can be re-used, but i think an implementation the same way as the hillshade layer it should be good.

Overall i think this is a great feature, i would needed such a feature more than one time. To go around of this lack of functionality i always had to combine areas in vectortiles. possible, but a lot of work.

@HarelM
Copy link
Collaborator

HarelM commented Jan 9, 2024

This has gone stale a bit, and we also moved the style-spec logic out of this repo.
Is this still interesting enough for someone to push forward or should we close this PR?

@sutarmin
Copy link

Not feeling competent enough to push this forward, but it would be amazing to this feature implemented one way or another.

My use case is I'm drawing a line layer with several translucent lines representing a route. I can't use a single line for that because different bits can have different styling (representing road surface info). If I'm using any other line-cap than round, line junctions look ugly, but with line-cap:round lines overlap a bit, creating a weird visual effect.

As I understand, such problems can be solved by the proposed feature.

Screenshot 2024-01-16 at 03 30 52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
need more info Further information is requested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants