diff --git a/include/mbgl/shaders/gl/symbol_icon.hpp b/include/mbgl/shaders/gl/symbol_icon.hpp index 9ac73b34aa43..79590f9ff6ee 100644 --- a/include/mbgl/shaders/gl/symbol_icon.hpp +++ b/include/mbgl/shaders/gl/symbol_icon.hpp @@ -39,6 +39,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -115,7 +116,9 @@ lowp float opacity = u_opacity; 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/include/mbgl/shaders/gl/symbol_sdf.hpp b/include/mbgl/shaders/gl/symbol_sdf.hpp index 23a039491c06..cf0b7039b3e5 100644 --- a/include/mbgl/shaders/gl/symbol_sdf.hpp +++ b/include/mbgl/shaders/gl/symbol_sdf.hpp @@ -47,6 +47,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -167,7 +168,9 @@ lowp float halo_blur = u_halo_blur; 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/include/mbgl/shaders/gl/symbol_text_and_icon.hpp b/include/mbgl/shaders/gl/symbol_text_and_icon.hpp index 39fc1906b1f0..88b1142ceb45 100644 --- a/include/mbgl/shaders/gl/symbol_text_and_icon.hpp +++ b/include/mbgl/shaders/gl/symbol_text_and_icon.hpp @@ -46,6 +46,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -166,7 +167,9 @@ lowp float halo_blur = u_halo_blur; 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = size / 24.0; diff --git a/include/mbgl/shaders/mtl/symbol.hpp b/include/mbgl/shaders/mtl/symbol.hpp index c4f22e6677a7..d04194b5e9ed 100644 --- a/include/mbgl/shaders/mtl/symbol.hpp +++ b/include/mbgl/shaders/mtl/symbol.hpp @@ -29,19 +29,20 @@ struct alignas(16) SymbolDrawableUBO { /* 216 */ /*bool*/ int pitch_with_map; /* 220 */ /*bool*/ int is_size_zoom_constant; /* 224 */ /*bool*/ int is_size_feature_constant; + /* 228 */ /*bool*/ int is_offset; - /* 228 */ float size_t; - /* 232 */ float size; + /* 232 */ float size_t; + /* 236 */ float size; // Interpolations - /* 236 */ float fill_color_t; - /* 240 */ float halo_color_t; - /* 244 */ float opacity_t; - /* 248 */ float halo_width_t; - /* 252 */ float halo_blur_t; - /* 256 */ + /* 240 */ float fill_color_t; + /* 244 */ float halo_color_t; + /* 248 */ float opacity_t; + /* 252 */ float halo_width_t; + /* 256 */ float halo_blur_t; + /* 260 */ }; -static_assert(sizeof(SymbolDrawableUBO) == 16 * 16, "wrong size"); +static_assert(sizeof(SymbolDrawableUBO) == 17 * 16, "wrong size"); struct alignas(16) SymbolTilePropsUBO { /* 0 */ /*bool*/ int is_text; @@ -171,7 +172,9 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -348,7 +351,9 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -583,7 +588,9 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = size / 24.0; diff --git a/include/mbgl/shaders/symbol_layer_ubo.hpp b/include/mbgl/shaders/symbol_layer_ubo.hpp index 84f176d0dc63..63c4cb84ee4e 100644 --- a/include/mbgl/shaders/symbol_layer_ubo.hpp +++ b/include/mbgl/shaders/symbol_layer_ubo.hpp @@ -18,19 +18,20 @@ struct alignas(16) SymbolDrawableUBO { /* 216 */ /*bool*/ int pitch_with_map; /* 220 */ /*bool*/ int is_size_zoom_constant; /* 224 */ /*bool*/ int is_size_feature_constant; + /* 228 */ /*bool*/ int is_offset; - /* 228 */ float size_t; - /* 232 */ float size; + /* 232 */ float size_t; + /* 236 */ float size; // Interpolations - /* 236 */ float fill_color_t; - /* 240 */ float halo_color_t; - /* 244 */ float opacity_t; - /* 248 */ float halo_width_t; - /* 252 */ float halo_blur_t; - /* 256 */ + /* 240 */ float fill_color_t; + /* 244 */ float halo_color_t; + /* 248 */ float opacity_t; + /* 252 */ float halo_width_t; + /* 256 */ float halo_blur_t; + /* 260 */ }; -static_assert(sizeof(SymbolDrawableUBO) == 16 * 16); +static_assert(sizeof(SymbolDrawableUBO) == 17 * 16); struct alignas(16) SymbolTilePropsUBO { /* 0 */ /*bool*/ int is_text; diff --git a/include/mbgl/shaders/vulkan/symbol.hpp b/include/mbgl/shaders/vulkan/symbol.hpp index 96edd885d610..9a2c0dfad76b 100644 --- a/include/mbgl/shaders/vulkan/symbol.hpp +++ b/include/mbgl/shaders/vulkan/symbol.hpp @@ -52,6 +52,7 @@ struct SymbolDrawableUBO { bool pitch_with_map; bool is_size_zoom_constant; bool is_size_feature_constant; + bool is_offset; float size_t; float size; @@ -106,7 +107,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -256,6 +259,7 @@ struct SymbolDrawableUBO { bool pitch_with_map; bool is_size_zoom_constant; bool is_size_feature_constant; + bool is_offset; float size_t; float size; @@ -335,7 +339,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -563,6 +569,7 @@ struct SymbolDrawableUBO { bool pitch_with_map; bool is_size_zoom_constant; bool is_size_feature_constant; + bool is_offset; float size_t; float size; @@ -644,7 +651,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = size / 24.0; diff --git a/include/mbgl/shaders/webgpu/symbol.hpp b/include/mbgl/shaders/webgpu/symbol.hpp index 557f22b6634f..fb0b3a9b0480 100644 --- a/include/mbgl/shaders/webgpu/symbol.hpp +++ b/include/mbgl/shaders/webgpu/symbol.hpp @@ -41,6 +41,7 @@ struct SymbolDrawableUBO { pitch_with_map: u32, is_size_zoom_constant: u32, is_size_feature_constant: u32, + is_offset: u32, size_t: f32, size: f32, @@ -175,7 +176,9 @@ fn main(in: VertexInput) -> VertexOutput { ); let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0); - size *= perspective_ratio; + if (drawable.is_offset == 0u) { + size *= perspective_ratio; + } let is_text = drawable.is_text_prop != 0u; let fontScale = select(size, size / 24.0, is_text); @@ -348,7 +351,9 @@ fn main(in: VertexInput) -> VertexOutput { ); let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0); - size *= perspective_ratio; + if (drawable.is_offset == 0u) { + size *= perspective_ratio; + } let is_text = drawable.is_text_prop != 0u; let fontScale = select(size, size / 24.0, is_text); @@ -597,7 +602,9 @@ fn main(in: VertexInput) -> VertexOutput { ); let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0); - size *= perspective_ratio; + if (drawable.is_offset == 0u) { + size *= perspective_ratio; + } let fontScale = size / 24.0; diff --git a/metrics/cache-style.db b/metrics/cache-style.db index 11658f27560f..5ff6f77a24a2 100644 Binary files a/metrics/cache-style.db and b/metrics/cache-style.db differ diff --git a/metrics/integration/render-tests/icon-offset/pitched-offset/expected.png b/metrics/integration/render-tests/icon-offset/pitched-offset/expected.png new file mode 100644 index 000000000000..dc506cd9f799 Binary files /dev/null and b/metrics/integration/render-tests/icon-offset/pitched-offset/expected.png differ diff --git a/metrics/integration/render-tests/icon-offset/pitched-offset/style.json b/metrics/integration/render-tests/icon-offset/pitched-offset/style.json new file mode 100644 index 000000000000..8aa7d6d18e8d --- /dev/null +++ b/metrics/integration/render-tests/icon-offset/pitched-offset/style.json @@ -0,0 +1,125 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 1048 + } + }, + "pitch": 60, + "center": [-118.78224, 39.32157], + "zoom": 14, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "LineString", + "coordinates": [ + [ + -118.78156097142148, + 39.47488496583114 + ], + [ + -118.7823961479745, + 39.28832054802072 + ] + ] + } + } +}, +"sprite": "local://sprites/almccon", + "layers": [ + { + "id": "background", + "type": "background", + "layout": {"visibility": "visible"}, + "paint": {"background-color": "rgba(204, 204, 204, 1)"} + }, + { + "id": "streets", + "type": "line", + "source": "geojson", + "layout": { + "visibility": "visible", + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "rgba(32, 32, 32, 1)", + "line-width": 150 + } + }, + { + "id": "road-lane-dashes-right", + "type": "line", + "source": "geojson", + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(0, 0%, 100%)", + "line-dasharray": [4, 12], + "line-offset": 7 + } + }, + { + "id": "road-lane-dashes-left", + "type": "line", + "source": "geojson", + "layout": {"line-cap": "round", "line-join": "round"}, + "paint": { + "line-color": "hsl(0, 0%, 100%)", + "line-dasharray": [4, 12], + "line-offset": -7 + } + }, + { + "id": "road-arrows-center", + "type": "symbol", + "source": "geojson", + "layout": { + "icon-allow-overlap": true, + "icon-image": "through", + "icon-offset": [0, 0], + "icon-rotate": 90, + "icon-rotation-alignment": "map", + "icon-pitch-alignment": "map", + "icon-size": 1, + "symbol-placement": "line", + "symbol-spacing": 300 + }, + "paint": {"icon-opacity": 0.8} + }, + { + "id": "road-arrows-right", + "type": "symbol", + "source": "geojson", + "layout": { + "icon-allow-overlap": true, + "icon-image": "through", + "icon-offset": [50.339999999999996, 0], + "icon-rotate": 90, + "icon-rotation-alignment": "map", + "icon-pitch-alignment": "map", + "icon-size": 1, + "symbol-placement": "line", + "symbol-spacing": 300 + }, + "paint": {"icon-opacity": 0.8} + }, + { + "id": "road-arrows-left", + "type": "symbol", + "source": "geojson", + "layout": { + "icon-allow-overlap": true, + "icon-image": "through", + "icon-offset": [-53.339999999999996, 0], + "icon-rotate": 90, + "icon-rotation-alignment": "map", + "icon-pitch-alignment": "map", + "icon-size": 1, + "symbol-placement": "line", + "symbol-spacing": 300 + }, + "paint": {"icon-opacity": 0.8} + } + ] +} diff --git a/metrics/integration/sprites/almccon.json b/metrics/integration/sprites/almccon.json new file mode 100644 index 000000000000..07d6b51b1b97 --- /dev/null +++ b/metrics/integration/sprites/almccon.json @@ -0,0 +1,23 @@ +{ + "through": { + "width": 48, + "height": 64, + "x": 531, + "y": 64, + "pixelRatio": 1 + }, + "through;right": { + "width": 48, + "height": 64, + "x": 579, + "y": 64, + "pixelRatio": 1 + }, + "through;slight_right": { + "width": 48, + "height": 64, + "x": 627, + "y": 64, + "pixelRatio": 1 + } +} diff --git a/metrics/integration/sprites/almccon.png b/metrics/integration/sprites/almccon.png new file mode 100644 index 000000000000..d4555fe1ae02 Binary files /dev/null and b/metrics/integration/sprites/almccon.png differ diff --git a/shaders/symbol_icon.vertex.glsl b/shaders/symbol_icon.vertex.glsl index 678f3323b74f..a2c5dffd7328 100644 --- a/shaders/symbol_icon.vertex.glsl +++ b/shaders/symbol_icon.vertex.glsl @@ -29,6 +29,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -98,7 +99,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/shaders/symbol_sdf.vertex.glsl b/shaders/symbol_sdf.vertex.glsl index 6c94980d7afe..edf31d4ef31a 100644 --- a/shaders/symbol_sdf.vertex.glsl +++ b/shaders/symbol_sdf.vertex.glsl @@ -37,6 +37,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -122,7 +123,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/shaders/symbol_text_and_icon.vertex.glsl b/shaders/symbol_text_and_icon.vertex.glsl index 6035d4512770..3e0557d62065 100644 --- a/shaders/symbol_text_and_icon.vertex.glsl +++ b/shaders/symbol_text_and_icon.vertex.glsl @@ -36,6 +36,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -121,7 +122,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = size / 24.0; diff --git a/src/mbgl/gfx/symbol_drawable_data.hpp b/src/mbgl/gfx/symbol_drawable_data.hpp index 6688b39ecaa1..fd7e3be04724 100644 --- a/src/mbgl/gfx/symbol_drawable_data.hpp +++ b/src/mbgl/gfx/symbol_drawable_data.hpp @@ -19,14 +19,16 @@ struct SymbolDrawableData : public DrawableData { const style::AlignmentType pitchAlignment_, const style::AlignmentType rotationAlignment_, const style::SymbolPlacementType placement_, - const style::IconTextFitType textFit_) + const style::IconTextFitType textFit_, + const bool isOffset_) : isHalo(isHalo_), bucketVariablePlacement(bucketVariablePlacement_), symbolType(symbolType_), pitchAlignment(pitchAlignment_), rotationAlignment(rotationAlignment_), placement(placement_), - textFit(textFit_) {} + textFit(textFit_), + isOffset(isOffset_) {} ~SymbolDrawableData() override = default; const bool isHalo; @@ -36,6 +38,7 @@ struct SymbolDrawableData : public DrawableData { const style::AlignmentType rotationAlignment; const style::SymbolPlacementType placement; const style::IconTextFitType textFit; + const bool isOffset; }; using UniqueSymbolDrawableData = std::unique_ptr; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 1c309f2bfcd2..4941699bb6ca 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -509,9 +509,11 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, }); } - const bool sortFeaturesByKey = !impl_cast(baseImpl).layout.get().isUndefined(); + const auto& layout = impl_cast(baseImpl).layout; + const bool sortFeaturesByKey = !layout.get().isUndefined(); std::multiset renderableSegments; std::unique_ptr builder; + const bool isOffset = !layout.get().isUndefined(); const auto currentZoom = static_cast(state.getZoom()); const auto layerPrefix = getID() + "/"; @@ -557,8 +559,9 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, return; } - const auto& layout = *bucket.layout; - const auto values = isText ? textPropertyValues(evaluated, layout) : iconPropertyValues(evaluated, layout); + const auto& bucketLayout = *bucket.layout; + const auto values = isText ? textPropertyValues(evaluated, bucketLayout) + : iconPropertyValues(evaluated, bucketLayout); const std::string suffix = isText ? "text/" : "icon/"; const auto addVertices = [&collisionBuilder](const auto& vertices) { @@ -705,8 +708,9 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, const auto& evaluated = getEvaluated(renderable.renderData.layerProperties); auto& bucketPaintProperties = bucket.paintProperties.at(getID()); - const auto& layout = *bucket.layout; - const auto values = isText ? textPropertyValues(evaluated, layout) : iconPropertyValues(evaluated, layout); + const auto& bucketLayout = *bucket.layout; + const auto values = isText ? textPropertyValues(evaluated, bucketLayout) + : iconPropertyValues(evaluated, bucketLayout); const auto& atlases = tile.getAtlasTextures(); if (!atlases) { @@ -756,7 +760,7 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, textSizeIsZoomConstant); } if (!isText && !tileInfo.iconTweaker) { - const bool iconScaled = layout.get().constantOr(1.0) != 1.0 || bucket.iconsNeedLinear; + const bool iconScaled = bucketLayout.get().constantOr(1.0) != 1.0 || bucket.iconsNeedLinear; tileInfo.iconTweaker = std::make_shared(atlases, idSymbolImageIconTexture, idSymbolImageTexture, @@ -816,8 +820,9 @@ void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders, /*.symbolType=*/renderable.type, /*.pitchAlignment=*/values.pitchAlignment, /*.rotationAlignment=*/values.rotationAlignment, - /*.placement=*/layout.get(), - /*.textFit=*/layout.get())); + /*.placement=*/bucketLayout.get(), + /*.textFit=*/bucketLayout.get(), + /*.isOffset=*/isOffset)); tileLayerGroup->addDrawable(passes, tileID, std::move(drawable)); ++stats.drawablesAdded; diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp index 8e08a7679ced..94f16f76aeac 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp @@ -181,6 +181,7 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete .pitch_with_map = (symbolData.pitchAlignment == style::AlignmentType::Map), .is_size_zoom_constant = size.isZoomConstant, .is_size_feature_constant = size.isFeatureConstant, + .is_offset = symbolData.isOffset, .size_t = size.sizeT, .size = size.size,