diff options
author | Randall Lee <randall.lee@mapbox.com> | 2018-05-22 14:09:36 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-22 14:09:36 -0400 |
commit | d858cb783b499a1cc77b48a0faee137ca5e6a423 (patch) | |
tree | 5c36c353730e3050d44e472545519c0429723266 /src/mbgl | |
parent | f93d722458be62d567aa152711a014ef51a90193 (diff) | |
parent | 60505b03174b5ec02ae723beafa7683f6ed54a62 (diff) | |
download | qtlocation-mapboxgl-upstream/rclee-async-setup.tar.gz |
Merge branch 'master' into rclee-async-setupupstream/rclee-async-setup
Diffstat (limited to 'src/mbgl')
95 files changed, 2115 insertions, 1243 deletions
diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index a237100d13..7d776f21c4 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -65,8 +65,8 @@ RenderAnnotationSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const { - return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); + const mat4& projMatrix) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, projMatrix); } std::vector<Feature> RenderAnnotationSource::querySourceFeatures(const SourceQueryOptions&) const { diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp index e812ec2883..da87d13814 100644 --- a/src/mbgl/annotation/render_annotation_source.hpp +++ b/src/mbgl/annotation/render_annotation_source.hpp @@ -28,7 +28,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const final; + const mat4& projMatrix) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/geometry/feature_index.cpp b/src/mbgl/geometry/feature_index.cpp index c67786274a..cc69a60ca0 100644 --- a/src/mbgl/geometry/feature_index.cpp +++ b/src/mbgl/geometry/feature_index.cpp @@ -7,7 +7,6 @@ #include <mbgl/util/math.hpp> #include <mbgl/math/minmax.hpp> #include <mbgl/style/filter.hpp> -#include <mbgl/style/filter_evaluator.hpp> #include <mbgl/tile/tile_id.hpp> #include <mapbox/geometry/envelope.hpp> @@ -25,34 +24,30 @@ FeatureIndex::FeatureIndex(std::unique_ptr<const GeometryTileData> tileData_) void FeatureIndex::insert(const GeometryCollection& geometries, std::size_t index, const std::string& sourceLayerName, - const std::string& bucketName) { + const std::string& bucketLeaderID) { for (const auto& ring : geometries) { auto envelope = mapbox::geometry::envelope(ring); - grid.insert(IndexedSubfeature(index, sourceLayerName, bucketName, sortIndex++), - {convertPoint<float>(envelope.min), convertPoint<float>(envelope.max)}); + if (envelope.min.x < util::EXTENT && + envelope.min.y < util::EXTENT && + envelope.max.x >= 0 && + envelope.max.y >= 0) { + grid.insert(IndexedSubfeature(index, sourceLayerName, bucketLeaderID, sortIndex++), + {convertPoint<float>(envelope.min), convertPoint<float>(envelope.max)}); + } } } -static bool topDown(const IndexedSubfeature& a, const IndexedSubfeature& b) { - return a.sortIndex > b.sortIndex; -} - -static bool topDownSymbols(const IndexedSubfeature& a, const IndexedSubfeature& b) { - return a.sortIndex < b.sortIndex; -} - void FeatureIndex::query( std::unordered_map<std::string, std::vector<Feature>>& result, const GeometryCoordinates& queryGeometry, - const float bearing, + const TransformState& transformState, + const mat4& posMatrix, const double tileSize, const double scale, const RenderedQueryOptions& queryOptions, const UnwrappedTileID& tileID, - const std::string& sourceID, const std::vector<const RenderLayer*>& layers, - const CollisionIndex& collisionIndex, - const float additionalQueryRadius) const { + const float additionalQueryPadding) const { if (!tileData) { return; @@ -60,15 +55,17 @@ void FeatureIndex::query( // Determine query radius const float pixelsToTileUnits = util::EXTENT / tileSize / scale; - const int16_t additionalRadius = std::min<int16_t>(util::EXTENT, additionalQueryRadius * pixelsToTileUnits); + const int16_t additionalPadding = std::min<int16_t>(util::EXTENT, additionalQueryPadding * pixelsToTileUnits); // Query the grid index mapbox::geometry::box<int16_t> box = mapbox::geometry::envelope(queryGeometry); - std::vector<IndexedSubfeature> features = grid.query({ convertPoint<float>(box.min - additionalRadius), - convertPoint<float>(box.max + additionalRadius) }); + std::vector<IndexedSubfeature> features = grid.query({ convertPoint<float>(box.min - additionalPadding), + convertPoint<float>(box.max + additionalPadding) }); - std::sort(features.begin(), features.end(), topDown); + std::sort(features.begin(), features.end(), [](const IndexedSubfeature& a, const IndexedSubfeature& b) { + return a.sortIndex > b.sortIndex; + }); size_t previousSortIndex = std::numeric_limits<size_t>::max(); for (const auto& indexedFeature : features) { @@ -76,25 +73,59 @@ void FeatureIndex::query( if (indexedFeature.sortIndex == previousSortIndex) continue; previousSortIndex = indexedFeature.sortIndex; - addFeature(result, indexedFeature, queryGeometry, queryOptions, tileID.canonical, layers, bearing, pixelsToTileUnits); + addFeature(result, indexedFeature, queryOptions, tileID.canonical, layers, queryGeometry, transformState, pixelsToTileUnits, posMatrix); } +} + +std::unordered_map<std::string, std::vector<Feature>> FeatureIndex::lookupSymbolFeatures(const std::vector<IndexedSubfeature>& symbolFeatures, + const RenderedQueryOptions& queryOptions, + const std::vector<const RenderLayer*>& layers, + const OverscaledTileID& tileID, + const std::shared_ptr<std::vector<size_t>>& featureSortOrder) const { + std::unordered_map<std::string, std::vector<Feature>> result; + if (!tileData) { + return result; + } + std::vector<IndexedSubfeature> sortedFeatures(symbolFeatures.begin(), symbolFeatures.end()); + + std::sort(sortedFeatures.begin(), sortedFeatures.end(), [featureSortOrder](const IndexedSubfeature& a, const IndexedSubfeature& b) { + // Same idea as the non-symbol sort order, but symbol features may have changed their sort order + // since their corresponding IndexedSubfeature was added to the CollisionIndex + // The 'featureSortOrder' is relatively inefficient for querying but cheap to build on every bucket sort + if (featureSortOrder) { + // queryRenderedSymbols documentation says we'll return features in + // "top-to-bottom" rendering order (aka last-to-first). + // Actually there can be multiple symbol instances per feature, so + // we sort each feature based on the first matching symbol instance. + auto sortedA = std::find(featureSortOrder->begin(), featureSortOrder->end(), a.index); + auto sortedB = std::find(featureSortOrder->begin(), featureSortOrder->end(), b.index); + assert(sortedA != featureSortOrder->end()); + assert(sortedB != featureSortOrder->end()); + return sortedA > sortedB; + } else { + // Bucket hasn't been re-sorted based on angle, so use same "reverse of appearance in source data" + // logic as non-symboles + return a.sortIndex > b.sortIndex; + } + }); - std::vector<IndexedSubfeature> symbolFeatures = collisionIndex.queryRenderedSymbols(queryGeometry, tileID, sourceID); - std::sort(symbolFeatures.begin(), symbolFeatures.end(), topDownSymbols); - for (const auto& symbolFeature : symbolFeatures) { - addFeature(result, symbolFeature, queryGeometry, queryOptions, tileID.canonical, layers, bearing, pixelsToTileUnits); + for (const auto& symbolFeature : sortedFeatures) { + mat4 unusedMatrix; + addFeature(result, symbolFeature, queryOptions, tileID.canonical, layers, GeometryCoordinates(), {}, 0, unusedMatrix); } + return result; } void FeatureIndex::addFeature( std::unordered_map<std::string, std::vector<Feature>>& result, const IndexedSubfeature& indexedFeature, - const GeometryCoordinates& queryGeometry, const RenderedQueryOptions& options, const CanonicalTileID& tileID, const std::vector<const RenderLayer*>& layers, - const float bearing, - const float pixelsToTileUnits) const { + const GeometryCoordinates& queryGeometry, + const TransformState& transformState, + const float pixelsToTileUnits, + const mat4& posMatrix) const { auto getRenderLayer = [&] (const std::string& layerID) -> const RenderLayer* { for (const auto& layer : layers) { @@ -109,7 +140,7 @@ void FeatureIndex::addFeature( std::unique_ptr<GeometryTileLayer> sourceLayer; std::unique_ptr<GeometryTileFeature> geometryTileFeature; - for (const std::string& layerID : bucketLayerIDs.at(indexedFeature.bucketName)) { + for (const std::string& layerID : bucketLayerIDs.at(indexedFeature.bucketLeaderID)) { const RenderLayer* renderLayer = getRenderLayer(layerID); if (!renderLayer) { continue; @@ -124,7 +155,7 @@ void FeatureIndex::addFeature( } if (!renderLayer->is<RenderSymbolLayer>() && - !renderLayer->queryIntersectsFeature(queryGeometry, *geometryTileFeature, tileID.z, bearing, pixelsToTileUnits)) { + !renderLayer->queryIntersectsFeature(queryGeometry, *geometryTileFeature, tileID.z, transformState, pixelsToTileUnits, posMatrix)) { continue; } @@ -158,8 +189,8 @@ optional<GeometryCoordinates> FeatureIndex::translateQueryGeometry( return translated; } -void FeatureIndex::setBucketLayerIDs(const std::string& bucketName, const std::vector<std::string>& layerIDs) { - bucketLayerIDs[bucketName] = layerIDs; +void FeatureIndex::setBucketLayerIDs(const std::string& bucketLeaderID, const std::vector<std::string>& layerIDs) { + bucketLayerIDs[bucketLeaderID] = layerIDs; } } // namespace mbgl diff --git a/src/mbgl/geometry/feature_index.hpp b/src/mbgl/geometry/feature_index.hpp index 9e0c172342..739c1f282f 100644 --- a/src/mbgl/geometry/feature_index.hpp +++ b/src/mbgl/geometry/feature_index.hpp @@ -5,6 +5,7 @@ #include <mbgl/tile/tile_id.hpp> #include <mbgl/util/grid_index.hpp> #include <mbgl/util/feature.hpp> +#include <mbgl/util/mat4.hpp> #include <vector> #include <string> @@ -14,6 +15,7 @@ namespace mbgl { class RenderedQueryOptions; class RenderLayer; +class TransformState; class CollisionIndex; @@ -23,29 +25,26 @@ public: IndexedSubfeature(std::size_t index_, std::string sourceLayerName_, std::string bucketName_, size_t sortIndex_) : index(index_) , sourceLayerName(std::move(sourceLayerName_)) - , bucketName(std::move(bucketName_)) + , bucketLeaderID(std::move(bucketName_)) , sortIndex(sortIndex_) - , tileID(0, 0, 0) + , bucketInstanceId(0) {} - IndexedSubfeature(std::size_t index_, std::string sourceLayerName_, std::string bucketName_, size_t sortIndex_, - std::string sourceID_, CanonicalTileID tileID_) - : index(index_) - , sourceLayerName(std::move(sourceLayerName_)) - , bucketName(std::move(bucketName_)) - , sortIndex(std::move(sortIndex_)) - , sourceID(std::move(sourceID_)) - , tileID(std::move(tileID_)) - {} + IndexedSubfeature(const IndexedSubfeature& other, uint32_t bucketInstanceId_) + : index(other.index) + , sourceLayerName(other.sourceLayerName) + , bucketLeaderID(other.bucketLeaderID) + , sortIndex(other.sortIndex) + , bucketInstanceId(bucketInstanceId_) + {} size_t index; std::string sourceLayerName; - std::string bucketName; + std::string bucketLeaderID; size_t sortIndex; // Only used for symbol features - std::string sourceID; - CanonicalTileID tileID; + uint32_t bucketInstanceId; }; class FeatureIndex { @@ -54,20 +53,19 @@ public: const GeometryTileData* getData() { return tileData.get(); } - void insert(const GeometryCollection&, std::size_t index, const std::string& sourceLayerName, const std::string& bucketName); + void insert(const GeometryCollection&, std::size_t index, const std::string& sourceLayerName, const std::string& bucketLeaderID); void query( std::unordered_map<std::string, std::vector<Feature>>& result, const GeometryCoordinates& queryGeometry, - const float bearing, + const TransformState&, + const mat4& posMatrix, const double tileSize, const double scale, const RenderedQueryOptions& options, const UnwrappedTileID&, - const std::string&, const std::vector<const RenderLayer*>&, - const CollisionIndex&, - const float additionalQueryRadius) const; + const float additionalQueryPadding) const; static optional<GeometryCoordinates> translateQueryGeometry( const GeometryCoordinates& queryGeometry, @@ -76,18 +74,26 @@ public: const float bearing, const float pixelsToTileUnits); - void setBucketLayerIDs(const std::string& bucketName, const std::vector<std::string>& layerIDs); + void setBucketLayerIDs(const std::string& bucketLeaderID, const std::vector<std::string>& layerIDs); + + std::unordered_map<std::string, std::vector<Feature>> lookupSymbolFeatures( + const std::vector<IndexedSubfeature>& symbolFeatures, + const RenderedQueryOptions& options, + const std::vector<const RenderLayer*>& layers, + const OverscaledTileID& tileID, + const std::shared_ptr<std::vector<size_t>>& featureSortOrder) const; private: void addFeature( std::unordered_map<std::string, std::vector<Feature>>& result, const IndexedSubfeature&, - const GeometryCoordinates& queryGeometry, const RenderedQueryOptions& options, const CanonicalTileID&, const std::vector<const RenderLayer*>&, - const float bearing, - const float pixelsToTileUnits) const; + const GeometryCoordinates& queryGeometry, + const TransformState& transformState, + const float pixelsToTileUnits, + const mat4& posMatrix) const; GridIndex<IndexedSubfeature> grid; unsigned int sortIndex = 0; diff --git a/src/mbgl/gl/attribute.cpp b/src/mbgl/gl/attribute.cpp index bb5b2ddc34..b2d05fe665 100644 --- a/src/mbgl/gl/attribute.cpp +++ b/src/mbgl/gl/attribute.cpp @@ -1,14 +1,20 @@ #include <mbgl/gl/attribute.hpp> +#include <mbgl/gl/context.hpp> #include <mbgl/gl/gl.hpp> namespace mbgl { namespace gl { -void bindAttributeLocation(ProgramID id, AttributeLocation location, const char* name) { - if (location >= MAX_ATTRIBUTES) { - throw gl::Error("too many vertex attributes"); +void bindAttributeLocation(Context& context, ProgramID id, AttributeLocation location, const char* name) { + // We're using sequentially numberered attribute locations starting with 0. Therefore we can use + // the location as a proxy for the number of attributes. + if (location >= context.maximumVertexBindingCount) { + // Don't bind the location on this hardware since it exceeds the limit (otherwise we'd get + // an OpenGL error). This means we'll see rendering errors, and possibly slow rendering due + // to unbound attributes. + } else { + MBGL_CHECK_ERROR(glBindAttribLocation(id, location, name)); } - MBGL_CHECK_ERROR(glBindAttribLocation(id, location, name)); } std::set<std::string> getActiveAttributes(ProgramID id) { diff --git a/src/mbgl/gl/attribute.hpp b/src/mbgl/gl/attribute.hpp index fa6c2ddeab..3763f0a583 100644 --- a/src/mbgl/gl/attribute.hpp +++ b/src/mbgl/gl/attribute.hpp @@ -17,8 +17,6 @@ namespace mbgl { namespace gl { -static constexpr std::size_t MAX_ATTRIBUTES = 8; - template <class> struct DataTypeOf; template <> struct DataTypeOf< int8_t> : std::integral_constant<DataType, DataType::Byte> {}; template <> struct DataTypeOf<uint8_t> : std::integral_constant<DataType, DataType::UnsignedByte> {}; @@ -45,7 +43,7 @@ public: } }; -using AttributeBindingArray = std::array<optional<AttributeBinding>, MAX_ATTRIBUTES>; +using AttributeBindingArray = std::vector<optional<AttributeBinding>>; /* gl::Attribute<T,N> manages the binding of a vertex buffer to a GL program attribute. @@ -214,7 +212,8 @@ const std::size_t Vertex<A1, A2, A3, A4, A5>::attributeOffsets[5] = { } // namespace detail -void bindAttributeLocation(ProgramID, AttributeLocation, const char * name); +class Context; +void bindAttributeLocation(Context&, ProgramID, AttributeLocation, const char * name); std::set<std::string> getActiveAttributes(ProgramID); template <class... As> @@ -231,13 +230,13 @@ public: using Vertex = detail::Vertex<typename As::Type...>; - static Locations bindLocations(const ProgramID& id) { + static Locations bindLocations(Context& context, const ProgramID& id) { std::set<std::string> activeAttributes = getActiveAttributes(id); AttributeLocation location = 0; auto maybeBindLocation = [&](const char* name) -> optional<AttributeLocation> { if (activeAttributes.count(name)) { - bindAttributeLocation(id, location, name); + bindAttributeLocation(context, id, location, name); return location++; } else { return {}; @@ -277,6 +276,7 @@ public: static AttributeBindingArray toBindingArray(const Locations& locations, const Bindings& bindings) { AttributeBindingArray result; + result.resize(sizeof...(As)); auto maybeAddBinding = [&] (const optional<AttributeLocation>& location, const optional<AttributeBinding>& binding) { @@ -289,6 +289,12 @@ public: return result; } + + static uint32_t activeBindingCount(const Bindings& bindings) { + uint32_t result = 0; + util::ignore({ ((result += bool(bindings.template get<As>())), 0)... }); + return result; + } }; namespace detail { diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index ba44adb42b..c6352d3e84 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -94,7 +94,13 @@ static_assert(underlying_type(BufferUsage::DynamicDraw) == GL_DYNAMIC_DRAW, "Ope static_assert(std::is_same<BinaryProgramFormat, GLenum>::value, "OpenGL type mismatch"); -Context::Context() = default; +Context::Context() + : maximumVertexBindingCount([] { + GLint value; + MBGL_CHECK_ERROR(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &value)); + return value; + }()) { +} Context::~Context() { if (cleanupOnDestruction) { @@ -351,7 +357,7 @@ VertexArray Context::createVertexArray() { VertexArrayID id = 0; MBGL_CHECK_ERROR(vertexArray->genVertexArrays(1, &id)); UniqueVertexArray vao(std::move(id), { this }); - return { UniqueVertexArrayState(new VertexArrayState(std::move(vao), *this), VertexArrayStateDeleter { true })}; + return { UniqueVertexArrayState(new VertexArrayState(std::move(vao)), VertexArrayStateDeleter { true })}; } else { // On GL implementations which do not support vertex arrays, attribute bindings are global state. // So return a VertexArray which shares our global state tracking and whose deleter is a no-op. diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 67624288e2..d95311115e 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -226,7 +226,7 @@ public: State<value::BindVertexBuffer> vertexBuffer; State<value::BindVertexArray, const Context&> bindVertexArray { *this }; - VertexArrayState globalVertexArrayState { UniqueVertexArray(0, { this }), *this }; + VertexArrayState globalVertexArrayState { UniqueVertexArray(0, { this }) }; State<value::PixelStorePack> pixelStorePack; State<value::PixelStoreUnpack> pixelStoreUnpack; @@ -239,6 +239,8 @@ public: #endif // MBGL_USE_GLES2 bool supportsHalfFloatTextures = false; + const uint32_t maximumVertexBindingCount; + static constexpr const uint32_t minimumRequiredVertexBindingCount = 8; private: State<value::StencilFunc> stencilFunc; diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index af02ad3d54..f33501cd11 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -35,7 +35,7 @@ public: context.createProgram(context.createShader(ShaderType::Vertex, vertexSource), context.createShader(ShaderType::Fragment, fragmentSource))), uniformsState((context.linkProgram(program), Uniforms::bindLocations(program))), - attributeLocations(Attributes::bindLocations(program)) { + attributeLocations(Attributes::bindLocations(context, program)) { // Re-link program after manually binding only active attributes in Attributes::bindLocations context.linkProgram(program); diff --git a/src/mbgl/gl/vertex_array.cpp b/src/mbgl/gl/vertex_array.cpp index 68a500ac45..d552a8292e 100644 --- a/src/mbgl/gl/vertex_array.cpp +++ b/src/mbgl/gl/vertex_array.cpp @@ -9,7 +9,11 @@ void VertexArray::bind(Context& context, BufferID indexBuffer, const AttributeBi context.bindVertexArray = state->vertexArray; state->indexBuffer = indexBuffer; - for (AttributeLocation location = 0; location < MAX_ATTRIBUTES; ++location) { + state->bindings.reserve(bindings.size()); + for (AttributeLocation location = 0; location < bindings.size(); ++location) { + if (state->bindings.size() <= location) { + state->bindings.emplace_back(context, AttributeLocation(location)); + } state->bindings[location] = bindings[location]; } } diff --git a/src/mbgl/gl/vertex_array.hpp b/src/mbgl/gl/vertex_array.hpp index 46c67017bb..604754f672 100644 --- a/src/mbgl/gl/vertex_array.hpp +++ b/src/mbgl/gl/vertex_array.hpp @@ -15,9 +15,8 @@ class Context; class VertexArrayState { public: - VertexArrayState(UniqueVertexArray vertexArray_, Context& context) - : vertexArray(std::move(vertexArray_)), - bindings(makeBindings(context, std::make_index_sequence<MAX_ATTRIBUTES>())) { + VertexArrayState(UniqueVertexArray vertexArray_) + : vertexArray(std::move(vertexArray_)) { } void setDirty() { @@ -31,13 +30,7 @@ public: State<value::BindElementBuffer> indexBuffer; using AttributeState = State<value::VertexAttribute, Context&, AttributeLocation>; - std::array<AttributeState, MAX_ATTRIBUTES> bindings; - -private: - template <std::size_t... I> - std::array<AttributeState, MAX_ATTRIBUTES> makeBindings(Context& context, std::index_sequence<I...>) { - return {{ AttributeState { context, I }... }}; - } + std::vector<AttributeState> bindings; }; class VertexArrayStateDeleter { diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 82a9255824..4249ffc24e 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -2,7 +2,6 @@ #include <mbgl/layout/merge_lines.hpp> #include <mbgl/layout/clip_lines.hpp> #include <mbgl/renderer/buckets/symbol_bucket.hpp> -#include <mbgl/style/filter_evaluator.hpp> #include <mbgl/renderer/bucket_parameters.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> #include <mbgl/renderer/image_atlas.hpp> @@ -42,7 +41,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, std::unique_ptr<GeometryTileLayer> sourceLayer_, ImageDependencies& imageDependencies, GlyphDependencies& glyphDependencies) - : bucketName(layers.at(0)->getID()), + : bucketLeaderID(layers.at(0)->getID()), sourceLayer(std::move(sourceLayer_)), overscaling(parameters.tileID.overscaleFactor()), zoom(parameters.tileID.overscaledZ), @@ -181,8 +180,7 @@ bool SymbolLayout::hasSymbolInstances() const { } void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions, - const ImageMap& imageMap, const ImagePositions& imagePositions, - const OverscaledTileID& tileID, const std::string& sourceID) { + const ImageMap& imageMap, const ImagePositions& imagePositions) { const bool textAlongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && layout.get<SymbolPlacement>() == SymbolPlacementType::Line; @@ -253,7 +251,7 @@ void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyph // if either shapedText or icon position is present, add the feature if (shapedTextOrientations.first || shapedIcon) { - addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap, tileID, sourceID); + addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap); } feature.geometry.clear(); @@ -266,9 +264,7 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolFeature& feature, const std::pair<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> shapedIcon, - const GlyphPositionMap& glyphPositionMap, - const OverscaledTileID& tileID, - const std::string& sourceID) { + const GlyphPositionMap& glyphPositionMap) { const float minScale = 0.5f; const float glyphSize = 24.0f; @@ -297,8 +293,7 @@ void SymbolLayout::addFeature(const std::size_t index, : layout.get<SymbolPlacement>(); const float textRepeatDistance = symbolSpacing / 2; - IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketName, symbolInstances.size(), - sourceID, tileID.canonical); + IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketLeaderID, symbolInstances.size()); auto addSymbolInstance = [&] (const GeometryCoordinates& line, Anchor& anchor) { // https://github.com/mapbox/vector-tile-spec/tree/master/2.1#41-layers @@ -424,7 +419,7 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(const bool showCollisionBoxes) const bool mayOverlap = layout.get<TextAllowOverlap>() || layout.get<IconAllowOverlap>() || layout.get<TextIgnorePlacement>() || layout.get<IconIgnorePlacement>(); - auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, std::move(symbolInstances)); + auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, bucketLeaderID, std::move(symbolInstances)); for (SymbolInstance &symbolInstance : bucket->symbolInstances) { diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 6951c29ada..43b577859f 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -34,8 +34,7 @@ public: GlyphDependencies&); void prepare(const GlyphMap&, const GlyphPositions&, - const ImageMap&, const ImagePositions&, - const OverscaledTileID&, const std::string&); + const ImageMap&, const ImagePositions&); std::unique_ptr<SymbolBucket> place(const bool showCollisionBoxes); @@ -44,7 +43,7 @@ public: std::map<std::string, std::pair<style::IconPaintProperties::PossiblyEvaluated, style::TextPaintProperties::PossiblyEvaluated>> layerPaintProperties; - const std::string bucketName; + const std::string bucketLeaderID; std::vector<SymbolInstance> symbolInstances; private: @@ -52,9 +51,7 @@ private: const SymbolFeature&, const std::pair<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> shapedIcon, - const GlyphPositionMap&, - const OverscaledTileID&, - const std::string&); + const GlyphPositionMap&); bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&); std::map<std::u16string, std::vector<Anchor>> compareText; diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index a7beb0f1e2..d81544eed5 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -747,6 +747,11 @@ void Map::Impl::onInvalidate() { } void Map::Impl::onUpdate() { + // Don't load/render anything in still mode until explicitly requested. + if (mode != MapMode::Continuous && !stillImageRequest) { + return; + } + TimePoint timePoint = mode == MapMode::Continuous ? Clock::now() : Clock::time_point::max(); transform.updateTransitions(timePoint); diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 105adf0400..da8e243d91 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -594,13 +594,10 @@ void Transform::startTransition(const CameraOptions& camera, animation.transitionFrameFn(t); } observer.onCameraIsChanging(); + return false; } else { - transitionFinishFn(); - transitionFinishFn = nullptr; - - // This callback gets destroyed here, - // we can only return after this point. - transitionFrameFn = nullptr; + // Indicate that we need to terminate this transition + return true; } }; @@ -615,7 +612,14 @@ void Transform::startTransition(const CameraOptions& camera, }; if (!isAnimated) { - transitionFrameFn(Clock::now()); + auto update = std::move(transitionFrameFn); + auto finish = std::move(transitionFinishFn); + + transitionFrameFn = nullptr; + transitionFinishFn = nullptr; + + update(Clock::now()); + finish(); } } @@ -624,8 +628,43 @@ bool Transform::inTransition() const { } void Transform::updateTransitions(const TimePoint& now) { - if (transitionFrameFn) { - transitionFrameFn(now); + + // Use a temporary function to ensure that the transitionFrameFn lambda is + // called only once per update. + + // This addresses the symptoms of https://github.com/mapbox/mapbox-gl-native/issues/11180 + // where setting a shape source to nil (or similar) in the `onCameraIsChanging` + // observer function causes `Map::Impl::onUpdate()` to be called which + // in turn calls this function (before the current iteration has completed), + // leading to an infinite loop. See https://github.com/mapbox/mapbox-gl-native/issues/5833 + // for a similar, related, issue. + // + // By temporarily nulling the `transitionFrameFn` (and then restoring it + // after the temporary has been called) we stop this recursion. + // + // It's important to note that the scope of this change is stop the above + // crashes. It doesn't address any potential deeper issue (for example + // user error, how often and when transition callbacks are called). + + auto transition = std::move(transitionFrameFn); + transitionFrameFn = nullptr; + + if (transition && transition(now)) { + // If the transition indicates that it is complete, then we should call + // the finish lambda (going via a temporary as above) + auto finish = std::move(transitionFinishFn); + + transitionFinishFn = nullptr; + transitionFrameFn = nullptr; + + if (finish) { + finish(); + } + } else if (!transitionFrameFn) { + // We have to check `transitionFrameFn` is nil here, since a new transition + // may have been triggered in a user callback (from the transition call + // above) + transitionFrameFn = std::move(transition); } } diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index d429c57661..bff44a2dcd 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -165,7 +165,7 @@ private: TimePoint transitionStart; Duration transitionDuration; - std::function<void(const TimePoint)> transitionFrameFn; + std::function<bool(const TimePoint)> transitionFrameFn; std::function<void()> transitionFinishFn; }; diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 18d2c24aee..a85b251fb4 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -421,4 +421,17 @@ float TransformState::getCameraToTileDistance(const UnwrappedTileID& tileID) con return projectedCenter[3]; } +float TransformState::maxPitchScaleFactor() const { + if (size.isEmpty()) { + return {}; + } + auto latLng = screenCoordinateToLatLng({ 0, static_cast<float>(getSize().height) }); + mat4 mat = coordinatePointMatrix(getZoom()); + Point<double> pt = Projection::project(latLng, scale) / double(util::tileSize); + vec4 p = {{ pt.x, pt.y, 0, 1 }}; + vec4 topPoint; + matrix::transformMat4(topPoint, p, mat); + return topPoint[3] / getCameraToCenterDistance(); +} + } // namespace mbgl diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index 451802034d..b6f8ae4424 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -87,6 +87,7 @@ public: } float getCameraToTileDistance(const UnwrappedTileID&) const; + float maxPitchScaleFactor() const; private: bool rotatedNorth() const; diff --git a/src/mbgl/programs/program.hpp b/src/mbgl/programs/program.hpp index bcdb270b9c..4d5de05337 100644 --- a/src/mbgl/programs/program.hpp +++ b/src/mbgl/programs/program.hpp @@ -46,26 +46,38 @@ public: Shaders::fragmentSource)) { } + static typename AllUniforms::Values computeAllUniformValues( + const UniformValues& uniformValues, + const PaintPropertyBinders& paintPropertyBinders, + const typename PaintProperties::PossiblyEvaluated& currentProperties, + float currentZoom) { + return uniformValues + .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); + } + + static typename Attributes::Bindings computeAllAttributeBindings( + const gl::VertexBuffer<LayoutVertex>& layoutVertexBuffer, + const PaintPropertyBinders& paintPropertyBinders, + const typename PaintProperties::PossiblyEvaluated& currentProperties) { + return LayoutAttributes::bindings(layoutVertexBuffer) + .concat(paintPropertyBinders.attributeBindings(currentProperties)); + } + + static uint32_t activeBindingCount(const typename Attributes::Bindings& allAttributeBindings) { + return Attributes::activeBindingCount(allAttributeBindings); + } + template <class DrawMode> void draw(gl::Context& context, DrawMode drawMode, gl::DepthMode depthMode, gl::StencilMode stencilMode, gl::ColorMode colorMode, - const UniformValues& uniformValues, - const gl::VertexBuffer<LayoutVertex>& layoutVertexBuffer, const gl::IndexBuffer<DrawMode>& indexBuffer, const SegmentVector<Attributes>& segments, - const PaintPropertyBinders& paintPropertyBinders, - const typename PaintProperties::PossiblyEvaluated& currentProperties, - float currentZoom, + const typename AllUniforms::Values& allUniformValues, + const typename Attributes::Bindings& allAttributeBindings, const std::string& layerID) { - typename AllUniforms::Values allUniformValues = uniformValues - .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); - - typename Attributes::Bindings allAttributeBindings = LayoutAttributes::bindings(layoutVertexBuffer) - .concat(paintPropertyBinders.attributeBindings(currentProperties)); - for (auto& segment : segments) { auto vertexArrayIt = segment.vertexArrays.find(layerID); diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 9b5037ed9f..51f9a48f7f 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -278,35 +278,45 @@ public: Shaders::fragmentSource)) { } + static typename AllUniforms::Values computeAllUniformValues( + const UniformValues& uniformValues, + const SymbolSizeBinder& symbolSizeBinder, + const PaintPropertyBinders& paintPropertyBinders, + const typename PaintProperties::PossiblyEvaluated& currentProperties, + float currentZoom) { + return uniformValues.concat(symbolSizeBinder.uniformValues(currentZoom)) + .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); + } + + static typename Attributes::Bindings computeAllAttributeBindings( + const gl::VertexBuffer<LayoutVertex>& layoutVertexBuffer, + const gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>& dynamicLayoutVertexBuffer, + const gl::VertexBuffer<SymbolOpacityAttributes::Vertex>& opacityVertexBuffer, + const PaintPropertyBinders& paintPropertyBinders, + const typename PaintProperties::PossiblyEvaluated& currentProperties) { + assert(layoutVertexBuffer.vertexCount == dynamicLayoutVertexBuffer.vertexCount && + layoutVertexBuffer.vertexCount == opacityVertexBuffer.vertexCount); + return LayoutAttributes::bindings(layoutVertexBuffer) + .concat(SymbolDynamicLayoutAttributes::bindings(dynamicLayoutVertexBuffer)) + .concat(SymbolOpacityAttributes::bindings(opacityVertexBuffer)) + .concat(paintPropertyBinders.attributeBindings(currentProperties)); + } + + static uint32_t activeBindingCount(const typename Attributes::Bindings& allAttributeBindings) { + return Attributes::activeBindingCount(allAttributeBindings); + } + template <class DrawMode> void draw(gl::Context& context, DrawMode drawMode, gl::DepthMode depthMode, gl::StencilMode stencilMode, gl::ColorMode colorMode, - const UniformValues& uniformValues, - const gl::VertexBuffer<LayoutVertex>& layoutVertexBuffer, - const gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>& dynamicLayoutVertexBuffer, - const gl::VertexBuffer<SymbolOpacityAttributes::Vertex>& opacityVertexBuffer, - const SymbolSizeBinder& symbolSizeBinder, const gl::IndexBuffer<DrawMode>& indexBuffer, const SegmentVector<Attributes>& segments, - const PaintPropertyBinders& paintPropertyBinders, - const typename PaintProperties::PossiblyEvaluated& currentProperties, - float currentZoom, + const typename AllUniforms::Values& allUniformValues, + const typename Attributes::Bindings& allAttributeBindings, const std::string& layerID) { - typename AllUniforms::Values allUniformValues = uniformValues - .concat(symbolSizeBinder.uniformValues(currentZoom)) - .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); - - typename Attributes::Bindings allAttributeBindings = LayoutAttributes::bindings(layoutVertexBuffer) - .concat(SymbolDynamicLayoutAttributes::bindings(dynamicLayoutVertexBuffer)) - .concat(SymbolOpacityAttributes::bindings(opacityVertexBuffer)) - .concat(paintPropertyBinders.attributeBindings(currentProperties)); - - assert(layoutVertexBuffer.vertexCount == dynamicLayoutVertexBuffer.vertexCount && - layoutVertexBuffer.vertexCount == opacityVertexBuffer.vertexCount); - for (auto& segment : segments) { auto vertexArrayIt = segment.vertexArrays.find(layerID); diff --git a/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp b/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp index 5e2c937091..4fcc761280 100644 --- a/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp +++ b/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp @@ -84,7 +84,7 @@ void FillExtrusionBucket::addFeature(const GeometryTileFeature& feature, if (nVertices == 0) continue; - auto edgeDistance = 0; + std::size_t edgeDistance = 0; for (uint32_t i = 0; i < nVertices; i++) { const auto& p1 = ring[i]; @@ -102,7 +102,7 @@ void FillExtrusionBucket::addFeature(const GeometryTileFeature& feature, const Point<double> perp = util::unit(util::perp(d1 - d2)); const auto dist = util::dist<int16_t>(d1, d2); - if (dist > std::numeric_limits<int16_t>::max()) { + if (edgeDistance + dist > std::numeric_limits<int16_t>::max()) { edgeDistance = 0; } diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index 60e8a0b504..4fe03eb453 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -18,11 +18,13 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo bool sdfIcons_, bool iconsNeedLinear_, bool sortFeaturesByY_, + const std::string bucketName_, const std::vector<SymbolInstance>&& symbolInstances_) : layout(std::move(layout_)), sdfIcons(sdfIcons_), iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()), sortFeaturesByY(sortFeaturesByY_), + bucketLeaderID(std::move(bucketName_)), symbolInstances(std::move(symbolInstances_)), textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())), iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())) { @@ -200,8 +202,12 @@ void SymbolBucket::sortFeatures(const float angle) { text.triangles.clear(); icon.triangles.clear(); + featureSortOrder = std::make_unique<std::vector<size_t>>(); + featureSortOrder->reserve(symbolInstanceIndexes.size()); + for (auto i : symbolInstanceIndexes) { const SymbolInstance& symbolInstance = symbolInstances[i]; + featureSortOrder->push_back(symbolInstance.featureIndex); if (symbolInstance.placedTextIndex) { addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedTextIndex]); diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index ed8afb052c..e4aaf5ba30 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -47,6 +47,7 @@ public: bool sdfIcons, bool iconsNeedLinear, bool sortFeaturesByY, + const std::string bucketLeaderID, const std::vector<SymbolInstance>&&); void upload(gl::Context&) override; @@ -64,6 +65,8 @@ public: const bool iconsNeedLinear; const bool sortFeaturesByY; + const std::string bucketLeaderID; + optional<float> sortedAngle; bool staticUploaded = false; @@ -131,6 +134,8 @@ public: uint32_t bucketInstanceId = 0; bool justReloaded = false; + + std::shared_ptr<std::vector<size_t>> featureSortOrder; }; } // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_background_layer.cpp b/src/mbgl/renderer/layers/render_background_layer.cpp index aebc4cc9aa..2dc5fe7339 100644 --- a/src/mbgl/renderer/layers/render_background_layer.cpp +++ b/src/mbgl/renderer/layers/render_background_layer.cpp @@ -7,6 +7,7 @@ #include <mbgl/programs/programs.hpp> #include <mbgl/programs/background_program.hpp> #include <mbgl/util/tile_cover.hpp> +#include <mbgl/map/transform_state.hpp> namespace mbgl { @@ -49,6 +50,35 @@ void RenderBackgroundLayer::render(PaintParameters& parameters, RenderSource*) { const Properties<>::PossiblyEvaluated properties; const BackgroundProgram::PaintPropertyBinders paintAttributeData(properties, 0); + auto draw = [&](auto& program, auto&& uniformValues) { + const auto allUniformValues = program.computeAllUniformValues( + std::move(uniformValues), + paintAttributeData, + properties, + parameters.state.getZoom() + ); + const auto allAttributeBindings = program.computeAllAttributeBindings( + parameters.staticData.tileVertexBuffer, + paintAttributeData, + properties + ); + + checkRenderability(parameters, program.activeBindingCount(allAttributeBindings)); + + program.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), + parameters.staticData.quadTriangleIndexBuffer, + parameters.staticData.tileTriangleSegments, + allUniformValues, + allAttributeBindings, + getID() + ); + }; + if (!evaluated.get<BackgroundPattern>().to.empty()) { optional<ImagePosition> imagePosA = parameters.imageManager.getPattern(evaluated.get<BackgroundPattern>().from); optional<ImagePosition> imagePosB = parameters.imageManager.getPattern(evaluated.get<BackgroundPattern>().to); @@ -59,12 +89,8 @@ void RenderBackgroundLayer::render(PaintParameters& parameters, RenderSource*) { parameters.imageManager.bind(parameters.context, 0); for (const auto& tileID : util::tileCover(parameters.state, parameters.state.getIntegerZoom())) { - parameters.programs.backgroundPattern.draw( - parameters.context, - gl::Triangles(), - parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), - gl::StencilMode::disabled(), - parameters.colorModeForRenderPass(), + draw( + parameters.programs.backgroundPattern, BackgroundPatternUniforms::values( parameters.matrixForTile(tileID), evaluated.get<BackgroundOpacity>(), @@ -74,36 +100,18 @@ void RenderBackgroundLayer::render(PaintParameters& parameters, RenderSource*) { evaluated.get<BackgroundPattern>(), tileID, parameters.state - ), - parameters.staticData.tileVertexBuffer, - parameters.staticData.quadTriangleIndexBuffer, - parameters.staticData.tileTriangleSegments, - paintAttributeData, - properties, - parameters.state.getZoom(), - getID() + ) ); } } else { for (const auto& tileID : util::tileCover(parameters.state, parameters.state.getIntegerZoom())) { - parameters.programs.background.draw( - parameters.context, - gl::Triangles(), - parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), - gl::StencilMode::disabled(), - parameters.colorModeForRenderPass(), + draw( + parameters.programs.background, BackgroundProgram::UniformValues { uniforms::u_matrix::Value{ parameters.matrixForTile(tileID) }, uniforms::u_color::Value{ evaluated.get<BackgroundColor>() }, uniforms::u_opacity::Value{ evaluated.get<BackgroundOpacity>() }, - }, - parameters.staticData.tileVertexBuffer, - parameters.staticData.quadTriangleIndexBuffer, - parameters.staticData.tileTriangleSegments, - paintAttributeData, - properties, - parameters.state.getZoom(), - getID() + } ); } } diff --git a/src/mbgl/renderer/layers/render_circle_layer.cpp b/src/mbgl/renderer/layers/render_circle_layer.cpp index 6092ff5452..b433a9d3fa 100644 --- a/src/mbgl/renderer/layers/render_circle_layer.cpp +++ b/src/mbgl/renderer/layers/render_circle_layer.cpp @@ -59,14 +59,11 @@ void RenderCircleLayer::render(PaintParameters& parameters, RenderSource*) { assert(dynamic_cast<CircleBucket*>(tile.tile.getBucket(*baseImpl))); CircleBucket& bucket = *reinterpret_cast<CircleBucket*>(tile.tile.getBucket(*baseImpl)); - parameters.programs.circle.get(evaluated).draw( - parameters.context, - gl::Triangles(), - parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), - parameters.mapMode != MapMode::Continuous - ? parameters.stencilModeForClipping(tile.clip) - : gl::StencilMode::disabled(), - parameters.colorModeForRenderPass(), + const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); + + auto& programInstance = parameters.programs.circle.get(evaluated); + + const auto allUniformValues = programInstance.computeAllUniformValues( CircleProgram::UniformValues { uniforms::u_matrix::Value{ tile.translatedMatrix(evaluated.get<CircleTranslate>(), @@ -82,38 +79,104 @@ void RenderCircleLayer::render(PaintParameters& parameters, RenderSource*) { uniforms::u_camera_to_center_distance::Value{ parameters.state.getCameraToCenterDistance() }, uniforms::u_pitch_with_map::Value{ pitchWithMap } }, + paintPropertyBinders, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( *bucket.vertexBuffer, + paintPropertyBinders, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + parameters.mapMode != MapMode::Continuous + ? parameters.stencilModeForClipping(tile.clip) + : gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), *bucket.indexBuffer, bucket.segments, - bucket.paintPropertyBinders.at(getID()), - evaluated, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); } } +GeometryCoordinate projectPoint(const GeometryCoordinate& p, const mat4& posMatrix, const Size& size) { + vec4 pos = {{ static_cast<double>(p.x), static_cast<double>(p.y), 0, 1 }}; + matrix::transformMat4(pos, pos, posMatrix); + return { + static_cast<int16_t>((static_cast<float>(pos[0] / pos[3]) + 1) * size.width * 0.5), + static_cast<int16_t>((static_cast<float>(pos[1] / pos[3]) + 1) * size.height * 0.5) + }; +} + +GeometryCoordinates projectQueryGeometry(const GeometryCoordinates& queryGeometry, const mat4& posMatrix, const Size& size) { + GeometryCoordinates projectedGeometry; + for (auto& p : queryGeometry) { + projectedGeometry.push_back(projectPoint(p, posMatrix, size)); + } + return projectedGeometry; +} + bool RenderCircleLayer::queryIntersectsFeature( const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float zoom, - const float bearing, - const float pixelsToTileUnits) const { + const TransformState& transformState, + const float pixelsToTileUnits, + const mat4& posMatrix) const { // Translate query geometry - auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry( + const GeometryCoordinates& translatedQueryGeometry = FeatureIndex::translateQueryGeometry( queryGeometry, evaluated.get<style::CircleTranslate>(), evaluated.get<style::CircleTranslateAnchor>(), - bearing, - pixelsToTileUnits); + transformState.getAngle(), + pixelsToTileUnits).value_or(queryGeometry); // Evaluate functions - auto radius = evaluated.evaluate<style::CircleRadius>(zoom, feature) * pixelsToTileUnits; - auto stroke = evaluated.evaluate<style::CircleStrokeWidth>(zoom, feature) * pixelsToTileUnits; + auto radius = evaluated.evaluate<style::CircleRadius>(zoom, feature); + auto stroke = evaluated.evaluate<style::CircleStrokeWidth>(zoom, feature); + auto size = radius + stroke; + + // For pitch-alignment: map, compare feature geometry to query geometry in the plane of the tile + // Otherwise, compare geometry in the plane of the viewport + // A circle with fixed scaling relative to the viewport gets larger in tile space as it moves into the distance + // A circle with fixed scaling relative to the map gets smaller in viewport space as it moves into the distance + bool alignWithMap = evaluated.evaluate<style::CirclePitchAlignment>(zoom, feature) == AlignmentType::Map; + const GeometryCoordinates& transformedQueryGeometry = alignWithMap ? + translatedQueryGeometry : + projectQueryGeometry(translatedQueryGeometry, posMatrix, transformState.getSize()); + auto transformedSize = alignWithMap ? size * pixelsToTileUnits : size; + + auto geometry = feature.getGeometries(); + for (auto& ring : geometry) { + for (auto& point : ring) { + const GeometryCoordinate& transformedPoint = alignWithMap ? point : projectPoint(point, posMatrix, transformState.getSize()); + + float adjustedSize = transformedSize; + vec4 center = {{ static_cast<double>(point.x), static_cast<double>(point.y), 0, 1 }}; + matrix::transformMat4(center, center, posMatrix); + auto pitchScale = evaluated.evaluate<style::CirclePitchScale>(zoom, feature); + auto pitchAlignment = evaluated.evaluate<style::CirclePitchAlignment>(zoom, feature); + if (pitchScale == CirclePitchScaleType::Viewport && pitchAlignment == AlignmentType::Map) { + adjustedSize *= center[3] / transformState.getCameraToCenterDistance(); + } else if (pitchScale == CirclePitchScaleType::Map && pitchAlignment == AlignmentType::Viewport) { + adjustedSize *= transformState.getCameraToCenterDistance() / center[3]; + } + + if (util::polygonIntersectsBufferedPoint(transformedQueryGeometry, transformedPoint, adjustedSize)) return true; + } + } - // Test intersection - return util::polygonIntersectsBufferedMultiPoint(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries(), radius + stroke); + return false; } } // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_circle_layer.hpp b/src/mbgl/renderer/layers/render_circle_layer.hpp index f31715f98f..c9eeae4652 100644 --- a/src/mbgl/renderer/layers/render_circle_layer.hpp +++ b/src/mbgl/renderer/layers/render_circle_layer.hpp @@ -20,8 +20,9 @@ public: const GeometryCoordinates&, const GeometryTileFeature&, const float, + const TransformState&, const float, - const float) const override; + const mat4&) const override; std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp index fbd6160e8a..d5282c9b0d 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -68,24 +68,53 @@ void RenderFillExtrusionLayer::render(PaintParameters& parameters, RenderSource* parameters.context.setStencilMode(gl::StencilMode::disabled()); parameters.context.clear(Color{ 0.0f, 0.0f, 0.0f, 0.0f }, depthClearValue, {}); + auto draw = [&](auto& programInstance, const auto& tileBucket, auto&& uniformValues) { + const auto& paintPropertyBinders = tileBucket.paintPropertyBinders.at(getID()); + + const auto allUniformValues = programInstance.computeAllUniformValues( + std::move(uniformValues), + paintPropertyBinders, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( + *tileBucket.vertexBuffer, + paintPropertyBinders, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeFor3D(gl::DepthMode::ReadWrite), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), + *tileBucket.indexBuffer, + tileBucket.triangleSegments, + allUniformValues, + allAttributeBindings, + getID()); + }; + if (evaluated.get<FillExtrusionPattern>().from.empty()) { for (const RenderTile& tile : renderTiles) { assert(dynamic_cast<FillExtrusionBucket*>(tile.tile.getBucket(*baseImpl))); FillExtrusionBucket& bucket = *reinterpret_cast<FillExtrusionBucket*>(tile.tile.getBucket(*baseImpl)); - parameters.programs.fillExtrusion.get(evaluated).draw( - parameters.context, gl::Triangles(), - parameters.depthModeFor3D(gl::DepthMode::ReadWrite), - gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), + draw( + parameters.programs.fillExtrusion.get(evaluated), + bucket, FillExtrusionUniforms::values( tile.translatedClipMatrix(evaluated.get<FillExtrusionTranslate>(), evaluated.get<FillExtrusionTranslateAnchor>(), parameters.state), - parameters.state, parameters.evaluatedLight), - *bucket.vertexBuffer, *bucket.indexBuffer, bucket.triangleSegments, - bucket.paintPropertyBinders.at(getID()), evaluated, parameters.state.getZoom(), - getID()); + parameters.state, + parameters.evaluatedLight + ) + ); } } else { optional<ImagePosition> imagePosA = @@ -104,10 +133,9 @@ void RenderFillExtrusionLayer::render(PaintParameters& parameters, RenderSource* FillExtrusionBucket& bucket = *reinterpret_cast<FillExtrusionBucket*>(tile.tile.getBucket(*baseImpl)); - parameters.programs.fillExtrusionPattern.get(evaluated).draw( - parameters.context, gl::Triangles(), - parameters.depthModeFor3D(gl::DepthMode::ReadWrite), - gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), + draw( + parameters.programs.fillExtrusionPattern.get(evaluated), + bucket, FillExtrusionPatternUniforms::values( tile.translatedClipMatrix(evaluated.get<FillExtrusionTranslate>(), evaluated.get<FillExtrusionTranslateAnchor>(), @@ -115,10 +143,9 @@ void RenderFillExtrusionLayer::render(PaintParameters& parameters, RenderSource* parameters.imageManager.getPixelSize(), *imagePosA, *imagePosB, evaluated.get<FillExtrusionPattern>(), tile.id, parameters.state, -std::pow(2, tile.id.canonical.z) / util::tileSize / 8.0f, - parameters.evaluatedLight), - *bucket.vertexBuffer, *bucket.indexBuffer, bucket.triangleSegments, - bucket.paintPropertyBinders.at(getID()), evaluated, parameters.state.getZoom(), - getID()); + parameters.evaluatedLight + ) + ); } } @@ -131,19 +158,39 @@ void RenderFillExtrusionLayer::render(PaintParameters& parameters, RenderSource* matrix::ortho(viewportMat, 0, size.width, size.height, 0, 0, 1); const Properties<>::PossiblyEvaluated properties; + const ExtrusionTextureProgram::PaintPropertyBinders paintAttributeData{ properties, 0 }; + + auto& programInstance = parameters.programs.extrusionTexture; - parameters.programs.extrusionTexture.draw( - parameters.context, gl::Triangles(), gl::DepthMode::disabled(), - gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), + const auto allUniformValues = programInstance.computeAllUniformValues( ExtrusionTextureProgram::UniformValues{ uniforms::u_matrix::Value{ viewportMat }, uniforms::u_world::Value{ size }, uniforms::u_image::Value{ 0 }, - uniforms::u_opacity::Value{ evaluated.get<FillExtrusionOpacity>() } }, + uniforms::u_opacity::Value{ evaluated.get<FillExtrusionOpacity>() } + }, + paintAttributeData, + properties, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( parameters.staticData.extrusionTextureVertexBuffer, + paintAttributeData, + properties + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + gl::Triangles(), + gl::DepthMode::disabled(), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.extrusionTextureSegments, - ExtrusionTextureProgram::PaintPropertyBinders{ properties, 0 }, properties, - parameters.state.getZoom(), getID()); + allUniformValues, + allAttributeBindings, + getID()); } } @@ -151,14 +198,15 @@ bool RenderFillExtrusionLayer::queryIntersectsFeature( const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float, - const float bearing, - const float pixelsToTileUnits) const { + const TransformState& transformState, + const float pixelsToTileUnits, + const mat4&) const { auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry( queryGeometry, evaluated.get<style::FillExtrusionTranslate>(), evaluated.get<style::FillExtrusionTranslateAnchor>(), - bearing, + transformState.getAngle(), pixelsToTileUnits); return util::polygonIntersectsMultiPolygon(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries()); diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp index 838494cf91..f7ba13c267 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp @@ -22,8 +22,9 @@ public: const GeometryCoordinates&, const GeometryTileFeature&, const float, + const TransformState&, const float, - const float) const override; + const mat4&) const override; std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index 22cb9563c1..c59ca6f906 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -69,12 +69,11 @@ void RenderFillLayer::render(PaintParameters& parameters, RenderSource*) { const auto& depthMode, const auto& indexBuffer, const auto& segments) { - program.get(evaluated).draw( - parameters.context, - drawMode, - depthMode, - parameters.stencilModeForClipping(tile.clip), - parameters.colorModeForRenderPass(), + auto& programInstance = program.get(evaluated); + + const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); + + const auto allUniformValues = programInstance.computeAllUniformValues( FillProgram::UniformValues { uniforms::u_matrix::Value{ tile.translatedMatrix(evaluated.get<FillTranslate>(), @@ -83,12 +82,28 @@ void RenderFillLayer::render(PaintParameters& parameters, RenderSource*) { }, uniforms::u_world::Value{ parameters.context.viewport.getCurrentValue().size }, }, + paintPropertyBinders, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( *bucket.vertexBuffer, + paintPropertyBinders, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + drawMode, + depthMode, + parameters.stencilModeForClipping(tile.clip), + parameters.colorModeForRenderPass(), indexBuffer, segments, - bucket.paintPropertyBinders.at(getID()), - evaluated, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); }; @@ -139,12 +154,11 @@ void RenderFillLayer::render(PaintParameters& parameters, RenderSource*) { const auto& depthMode, const auto& indexBuffer, const auto& segments) { - program.get(evaluated).draw( - parameters.context, - drawMode, - depthMode, - parameters.stencilModeForClipping(tile.clip), - parameters.colorModeForRenderPass(), + auto& programInstance = program.get(evaluated); + + const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); + + const auto allUniformValues = programInstance.computeAllUniformValues( FillPatternUniforms::values( tile.translatedMatrix(evaluated.get<FillTranslate>(), evaluated.get<FillTranslateAnchor>(), @@ -157,12 +171,28 @@ void RenderFillLayer::render(PaintParameters& parameters, RenderSource*) { tile.id, parameters.state ), + paintPropertyBinders, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( *bucket.vertexBuffer, + paintPropertyBinders, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + drawMode, + depthMode, + parameters.stencilModeForClipping(tile.clip), + parameters.colorModeForRenderPass(), indexBuffer, segments, - bucket.paintPropertyBinders.at(getID()), - evaluated, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); }; @@ -188,14 +218,15 @@ bool RenderFillLayer::queryIntersectsFeature( const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float, - const float bearing, - const float pixelsToTileUnits) const { + const TransformState& transformState, + const float pixelsToTileUnits, + const mat4&) const { auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry( queryGeometry, evaluated.get<style::FillTranslate>(), evaluated.get<style::FillTranslateAnchor>(), - bearing, + transformState.getAngle(), pixelsToTileUnits); return util::polygonIntersectsMultiPolygon(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries()); diff --git a/src/mbgl/renderer/layers/render_fill_layer.hpp b/src/mbgl/renderer/layers/render_fill_layer.hpp index a51865698f..bd195fb828 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.hpp +++ b/src/mbgl/renderer/layers/render_fill_layer.hpp @@ -20,8 +20,9 @@ public: const GeometryCoordinates&, const GeometryTileFeature&, const float, + const TransformState&, const float, - const float) const override; + const mat4&) const override; std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp index 4f2e899220..dec9edb318 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.cpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -92,23 +92,38 @@ void RenderHeatmapLayer::render(PaintParameters& parameters, RenderSource*) { ? parameters.stencilModeForClipping(tile.clip) : gl::StencilMode::disabled(); - parameters.programs.heatmap.get(evaluated).draw( + const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); + + auto& programInstance = parameters.programs.heatmap.get(evaluated); + + const auto allUniformValues = programInstance.computeAllUniformValues( + HeatmapProgram::UniformValues { + uniforms::u_intensity::Value{ evaluated.get<style::HeatmapIntensity>() }, + uniforms::u_matrix::Value{ tile.matrix }, + uniforms::heatmap::u_extrude_scale::Value{ extrudeScale } + }, + paintPropertyBinders, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( + *bucket.vertexBuffer, + paintPropertyBinders, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( parameters.context, gl::Triangles(), parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), stencilMode, gl::ColorMode::additive(), - HeatmapProgram::UniformValues { - uniforms::u_intensity::Value{evaluated.get<style::HeatmapIntensity>()}, - uniforms::u_matrix::Value{tile.matrix}, - uniforms::heatmap::u_extrude_scale::Value{extrudeScale} - }, - *bucket.vertexBuffer, *bucket.indexBuffer, bucket.segments, - bucket.paintPropertyBinders.at(getID()), - evaluated, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); } @@ -123,20 +138,41 @@ void RenderHeatmapLayer::render(PaintParameters& parameters, RenderSource*) { matrix::ortho(viewportMat, 0, size.width, size.height, 0, 0, 1); const Properties<>::PossiblyEvaluated properties; + const HeatmapTextureProgram::PaintPropertyBinders paintAttributeData{ properties, 0 }; + + auto& programInstance = parameters.programs.heatmapTexture; - parameters.programs.heatmapTexture.draw( - parameters.context, gl::Triangles(), gl::DepthMode::disabled(), - gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), + const auto allUniformValues = programInstance.computeAllUniformValues( HeatmapTextureProgram::UniformValues{ uniforms::u_matrix::Value{ viewportMat }, uniforms::u_world::Value{ size }, uniforms::u_image::Value{ 0 }, uniforms::u_color_ramp::Value{ 1 }, - uniforms::u_opacity::Value{ evaluated.get<HeatmapOpacity>() } }, + uniforms::u_opacity::Value{ evaluated.get<HeatmapOpacity>() } + }, + paintAttributeData, + properties, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( parameters.staticData.extrusionTextureVertexBuffer, + paintAttributeData, + properties + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + gl::Triangles(), + gl::DepthMode::disabled(), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.extrusionTextureSegments, - HeatmapTextureProgram::PaintPropertyBinders{ properties, 0 }, properties, - parameters.state.getZoom(), getID()); + allUniformValues, + allAttributeBindings, + getID() + ); } } @@ -165,12 +201,12 @@ bool RenderHeatmapLayer::queryIntersectsFeature( const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float zoom, - const float bearing, - const float pixelsToTileUnits) const { + const TransformState&, + const float pixelsToTileUnits, + const mat4&) const { (void) queryGeometry; (void) feature; (void) zoom; - (void) bearing; (void) pixelsToTileUnits; return false; } diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.hpp b/src/mbgl/renderer/layers/render_heatmap_layer.hpp index 3f0b1f91b4..29fad7d8b8 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.hpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp @@ -22,8 +22,9 @@ public: const GeometryCoordinates&, const GeometryTileFeature&, const float, + const TransformState&, const float, - const float) const override; + const mat4&) const override; void updateColorRamp(); diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index bcfd4ffe99..411305edf4 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -69,12 +69,11 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource* src const auto& indexBuffer, const auto& segments, const UnwrappedTileID& id) { - parameters.programs.hillshade.draw( - parameters.context, - gl::Triangles(), - parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), - gl::StencilMode::disabled(), - parameters.colorModeForRenderPass(), + auto& programInstance = parameters.programs.hillshade; + + const HillshadeProgram::PaintPropertyBinders paintAttributeData{ evaluated, 0 }; + + const auto allUniformValues = programInstance.computeAllUniformValues( HillshadeProgram::UniformValues { uniforms::u_matrix::Value{ matrix }, uniforms::u_image::Value{ 0 }, @@ -84,12 +83,28 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource* src uniforms::u_light::Value{ getLight(parameters) }, uniforms::u_latrange::Value{ getLatRange(id) }, }, + paintAttributeData, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( vertexBuffer, + paintAttributeData, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), indexBuffer, segments, - HillshadeProgram::PaintPropertyBinders { evaluated, 0 }, - evaluated, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); }; @@ -112,13 +127,11 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource* src parameters.context.bindTexture(*bucket.dem, 0, gl::TextureFilter::Nearest, gl::TextureMipMap::No, gl::TextureWrap::Clamp, gl::TextureWrap::Clamp); const Properties<>::PossiblyEvaluated properties; + const HillshadePrepareProgram::PaintPropertyBinders paintAttributeData{ properties, 0 }; - parameters.programs.hillshadePrepare.draw( - parameters.context, - gl::Triangles(), - parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), - gl::StencilMode::disabled(), - parameters.colorModeForRenderPass(), + auto& programInstance = parameters.programs.hillshadePrepare; + + const auto allUniformValues = programInstance.computeAllUniformValues( HillshadePrepareProgram::UniformValues { uniforms::u_matrix::Value { mat }, uniforms::u_dimension::Value { {{uint16_t(tilesize * 2), uint16_t(tilesize * 2) }} }, @@ -126,12 +139,28 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource* src uniforms::u_maxzoom::Value{ float(maxzoom) }, uniforms::u_image::Value{ 0 } }, + paintAttributeData, + properties, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( parameters.staticData.rasterVertexBuffer, + paintAttributeData, + properties + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.rasterSegments, - HillshadePrepareProgram::PaintPropertyBinders { properties, 0 }, - properties, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); bucket.texture = std::move(view.getTexture()); diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index 1b4a1c0ff7..361ad0c76b 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -62,19 +62,34 @@ void RenderLineLayer::render(PaintParameters& parameters, RenderSource*) { LineBucket& bucket = *reinterpret_cast<LineBucket*>(tile.tile.getBucket(*baseImpl)); auto draw = [&] (auto& program, auto&& uniformValues) { - program.get(evaluated).draw( + auto& programInstance = program.get(evaluated); + + const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); + + const auto allUniformValues = programInstance.computeAllUniformValues( + std::move(uniformValues), + paintPropertyBinders, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( + *bucket.vertexBuffer, + paintPropertyBinders, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( parameters.context, gl::Triangles(), parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), parameters.stencilModeForClipping(tile.clip), parameters.colorModeForRenderPass(), - std::move(uniformValues), - *bucket.vertexBuffer, *bucket.indexBuffer, bucket.segments, - bucket.paintPropertyBinders.at(getID()), - evaluated, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); }; @@ -162,15 +177,16 @@ bool RenderLineLayer::queryIntersectsFeature( const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float zoom, - const float bearing, - const float pixelsToTileUnits) const { + const TransformState& transformState, + const float pixelsToTileUnits, + const mat4&) const { // Translate query geometry auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry( queryGeometry, evaluated.get<style::LineTranslate>(), evaluated.get<style::LineTranslateAnchor>(), - bearing, + transformState.getAngle(), pixelsToTileUnits); // Evaluate function diff --git a/src/mbgl/renderer/layers/render_line_layer.hpp b/src/mbgl/renderer/layers/render_line_layer.hpp index 8bf7e2329d..5d5d79c044 100644 --- a/src/mbgl/renderer/layers/render_line_layer.hpp +++ b/src/mbgl/renderer/layers/render_line_layer.hpp @@ -29,8 +29,9 @@ public: const GeometryCoordinates&, const GeometryTileFeature&, const float, + const TransformState&, const float, - const float) const override; + const mat4&) const override; std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; diff --git a/src/mbgl/renderer/layers/render_raster_layer.cpp b/src/mbgl/renderer/layers/render_raster_layer.cpp index b41b2ac560..f202ed4ebb 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.cpp +++ b/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -73,16 +73,15 @@ void RenderRasterLayer::render(PaintParameters& parameters, RenderSource* source if (parameters.pass != RenderPass::Translucent) return; + RasterProgram::PaintPropertyBinders paintAttributeData{ evaluated, 0 }; + auto draw = [&] (const mat4& matrix, const auto& vertexBuffer, const auto& indexBuffer, const auto& segments) { - parameters.programs.raster.draw( - parameters.context, - gl::Triangles(), - parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), - gl::StencilMode::disabled(), - parameters.colorModeForRenderPass(), + auto& programInstance = parameters.programs.raster; + + const auto allUniformValues = programInstance.computeAllUniformValues( RasterProgram::UniformValues { uniforms::u_matrix::Value{ matrix }, uniforms::u_image0::Value{ 0 }, @@ -98,12 +97,28 @@ void RenderRasterLayer::render(PaintParameters& parameters, RenderSource* source uniforms::u_scale_parent::Value{ 1.0f }, uniforms::u_tl_parent::Value{ std::array<float, 2> {{ 0.0f, 0.0f }} }, }, + paintAttributeData, + evaluated, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( vertexBuffer, + paintAttributeData, + evaluated + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), indexBuffer, segments, - RasterProgram::PaintPropertyBinders { evaluated, 0 }, - evaluated, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); }; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 9e493003c0..e48c0e2f92 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -88,7 +88,26 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { const auto& binders, const auto& paintProperties) { - program.get(paintProperties).draw( + auto& programInstance = program.get(paintProperties); + + const auto allUniformValues = programInstance.computeAllUniformValues( + std::move(uniformValues), + *symbolSizeBinder, + binders, + paintProperties, + parameters.state.getZoom() + ); + const auto allAttributeBindings = programInstance.computeAllAttributeBindings( + *buffers.vertexBuffer, + *buffers.dynamicVertexBuffer, + *buffers.opacityVertexBuffer, + binders, + paintProperties + ); + + checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); + + programInstance.draw( parameters.context, gl::Triangles(), values_.pitchAlignment == AlignmentType::Map @@ -96,16 +115,10 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { : gl::DepthMode::disabled(), gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), - std::move(uniformValues), - *buffers.vertexBuffer, - *buffers.dynamicVertexBuffer, - *buffers.opacityVertexBuffer, - *symbolSizeBinder, *buffers.indexBuffer, buffers.segments, - binders, - paintProperties, - parameters.state.getZoom(), + allUniformValues, + allAttributeBindings, getID() ); }; diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp index bcdc175f14..a667d5837e 100644 --- a/src/mbgl/renderer/render_layer.cpp +++ b/src/mbgl/renderer/render_layer.cpp @@ -9,8 +9,10 @@ #include <mbgl/renderer/layers/render_raster_layer.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> #include <mbgl/renderer/layers/render_heatmap_layer.hpp> +#include <mbgl/renderer/paint_parameters.hpp> #include <mbgl/style/types.hpp> #include <mbgl/renderer/render_tile.hpp> +#include <mbgl/util/logging.hpp> namespace mbgl { @@ -73,5 +75,32 @@ void RenderLayer::setRenderTiles(std::vector<std::reference_wrapper<RenderTile>> renderTiles = std::move(tiles); } +void RenderLayer::checkRenderability(const PaintParameters& parameters, + const uint32_t activeBindingCount) { + // Only warn once for every layer. + if (hasRenderFailures) { + return; + } + + if (activeBindingCount > parameters.context.maximumVertexBindingCount) { + Log::Error(Event::OpenGL, + "The layer '%s' uses more data-driven properties than the current device " + "supports, and will have rendering errors. To ensure compatibility with this " + "device, use %d fewer data driven properties in this layer.", + getID().c_str(), + activeBindingCount - parameters.context.minimumRequiredVertexBindingCount); + hasRenderFailures = true; + } else if (activeBindingCount > parameters.context.minimumRequiredVertexBindingCount) { + Log::Error(Event::OpenGL, + "The layer '%s' uses more data-driven properties than some devices may support. " + "Though it will render correctly on this device, it may have rendering errors " + "on other devices. To ensure compatibility with all devices, use %d fewer " + "data-driven properties in this layer.", + getID().c_str(), + activeBindingCount - parameters.context.minimumRequiredVertexBindingCount); + hasRenderFailures = true; + } +} + } //namespace mbgl diff --git a/src/mbgl/renderer/render_layer.hpp b/src/mbgl/renderer/render_layer.hpp index 55831cb72c..3e2f1d7525 100644 --- a/src/mbgl/renderer/render_layer.hpp +++ b/src/mbgl/renderer/render_layer.hpp @@ -4,6 +4,7 @@ #include <mbgl/style/layer_impl.hpp> #include <mbgl/style/layer_type.hpp> #include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/util/mat4.hpp> #include <memory> #include <string> @@ -17,6 +18,7 @@ class PropertyEvaluationParameters; class PaintParameters; class RenderSource; class RenderTile; +class TransformState; class RenderLayer { protected: @@ -69,8 +71,9 @@ public: const GeometryCoordinates&, const GeometryTileFeature&, const float, + const TransformState&, const float, - const float) const { return false; }; + const mat4&) const { return false; }; virtual std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const = 0; @@ -82,6 +85,11 @@ public: friend std::string layoutKey(const RenderLayer&); protected: + // Checks whether the current hardware can render this layer. If it can't, we'll show a warning + // in the console to inform the developer. + void checkRenderability(const PaintParameters&, uint32_t activeBindingCount); + +protected: // renderTiles are exposed directly to CrossTileSymbolIndex and Placement so they // can update opacities in the symbol buckets immediately before rendering friend class CrossTileSymbolIndex; @@ -92,6 +100,12 @@ protected: // Stores what render passes this layer is currently enabled for. This depends on the // evaluated StyleProperties object and is updated accordingly. RenderPass passes = RenderPass::None; + +private: + // Some layers may not render correctly on some hardware when the vertex attribute limit of + // that GPU is exceeded. More attributes are used when adding many data driven paint properties + // to a layer. + bool hasRenderFailures = false; }; } // namespace mbgl diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index 53519c763e..dc80cb1dc6 100644 --- a/src/mbgl/renderer/render_source.hpp +++ b/src/mbgl/renderer/render_source.hpp @@ -65,7 +65,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const = 0; + const mat4& projMatrix) const = 0; virtual std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const = 0; diff --git a/src/mbgl/renderer/render_tile.cpp b/src/mbgl/renderer/render_tile.cpp index 35b34833e4..64790938ef 100644 --- a/src/mbgl/renderer/render_tile.cpp +++ b/src/mbgl/renderer/render_tile.cpp @@ -74,6 +74,8 @@ void RenderTile::finishRender(PaintParameters& parameters) { static const style::Properties<>::PossiblyEvaluated properties {}; static const DebugProgram::PaintPropertyBinders paintAttributeData(properties, 0); + auto& program = parameters.programs.debug; + if (parameters.debugOptions & (MapDebugOptions::Timestamps | MapDebugOptions::ParseStatus)) { if (!tile.debugBucket || tile.debugBucket->renderable != tile.isRenderable() || tile.debugBucket->complete != tile.isComplete() || @@ -85,41 +87,51 @@ void RenderTile::finishRender(PaintParameters& parameters) { tile.expires, parameters.debugOptions, parameters.context); } - parameters.programs.debug.draw( + const auto allAttributeBindings = program.computeAllAttributeBindings( + *tile.debugBucket->vertexBuffer, + paintAttributeData, + properties + ); + + program.draw( parameters.context, gl::Lines { 4.0f * parameters.pixelRatio }, gl::DepthMode::disabled(), parameters.stencilModeForClipping(clip), gl::ColorMode::unblended(), - DebugProgram::UniformValues { - uniforms::u_matrix::Value{ matrix }, - uniforms::u_color::Value{ Color::white() } - }, - *tile.debugBucket->vertexBuffer, *tile.debugBucket->indexBuffer, tile.debugBucket->segments, - paintAttributeData, - properties, - parameters.state.getZoom(), + program.computeAllUniformValues( + DebugProgram::UniformValues { + uniforms::u_matrix::Value{ matrix }, + uniforms::u_color::Value{ Color::white() } + }, + paintAttributeData, + properties, + parameters.state.getZoom() + ), + allAttributeBindings, "debug" ); - parameters.programs.debug.draw( + program.draw( parameters.context, gl::Lines { 2.0f * parameters.pixelRatio }, gl::DepthMode::disabled(), parameters.stencilModeForClipping(clip), gl::ColorMode::unblended(), - DebugProgram::UniformValues { - uniforms::u_matrix::Value{ matrix }, - uniforms::u_color::Value{ Color::black() } - }, - *tile.debugBucket->vertexBuffer, *tile.debugBucket->indexBuffer, tile.debugBucket->segments, - paintAttributeData, - properties, - parameters.state.getZoom(), + program.computeAllUniformValues( + DebugProgram::UniformValues { + uniforms::u_matrix::Value{ matrix }, + uniforms::u_color::Value{ Color::black() } + }, + paintAttributeData, + properties, + parameters.state.getZoom() + ), + allAttributeBindings, "debug" ); } @@ -131,16 +143,22 @@ void RenderTile::finishRender(PaintParameters& parameters) { gl::DepthMode::disabled(), parameters.stencilModeForClipping(clip), gl::ColorMode::unblended(), - DebugProgram::UniformValues { - uniforms::u_matrix::Value{ matrix }, - uniforms::u_color::Value{ Color::red() } - }, - parameters.staticData.tileVertexBuffer, parameters.staticData.tileBorderIndexBuffer, parameters.staticData.tileBorderSegments, - paintAttributeData, - properties, - parameters.state.getZoom(), + program.computeAllUniformValues( + DebugProgram::UniformValues { + uniforms::u_matrix::Value{ matrix }, + uniforms::u_color::Value{ Color::red() } + }, + paintAttributeData, + properties, + parameters.state.getZoom() + ), + program.computeAllAttributeBindings( + parameters.staticData.tileVertexBuffer, + paintAttributeData, + properties + ), "debug" ); } diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 2ac714e122..4380ef24a1 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -85,11 +85,6 @@ void Renderer::Impl::setObserver(RendererObserver* observer_) { void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (updateParameters.mode != MapMode::Continuous) { - // Don't load/render anyting in still mode until explicitly requested. - if (!updateParameters.stillImageRequest) { - return; - } - // Reset zoom history state. zoomHistory.first = true; } @@ -401,10 +396,6 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { } placementChanged = newPlacement->commit(*placement, parameters.timePoint); - // commitFeatureIndexes depends on the assumption that no new FeatureIndex has been loaded since placement - // started. If we violate this assumption, then we need to either make CollisionIndex completely independendent of - // FeatureIndex, or find a way for its entries to point to multiple FeatureIndexes. - commitFeatureIndexes(); crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers); if (placementChanged || symbolBucketsChanged) { placement = std::move(newPlacement); @@ -495,7 +486,9 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { static const ClippingMaskProgram::PaintPropertyBinders paintAttributeData(properties, 0); for (const auto& clipID : parameters.clipIDGenerator.getClipIDs()) { - parameters.staticData.programs.clippingMask.draw( + auto& program = parameters.staticData.programs.clippingMask; + + program.draw( parameters.context, gl::Triangles(), gl::DepthMode::disabled(), @@ -508,15 +501,21 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { gl::StencilMode::Replace }, gl::ColorMode::disabled(), - ClippingMaskProgram::UniformValues { - uniforms::u_matrix::Value{ parameters.matrixForTile(clipID.first) }, - }, - parameters.staticData.tileVertexBuffer, parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.tileTriangleSegments, - paintAttributeData, - properties, - parameters.state.getZoom(), + program.computeAllUniformValues( + ClippingMaskProgram::UniformValues { + uniforms::u_matrix::Value{ parameters.matrixForTile(clipID.first) }, + }, + paintAttributeData, + properties, + parameters.state.getZoom() + ), + program.computeAllAttributeBindings( + parameters.staticData.tileVertexBuffer, + paintAttributeData, + properties + ), "clipping" ); } @@ -673,6 +672,39 @@ std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin return queryRenderedFeatures(geometry, options, layers); } + +void Renderer::Impl::queryRenderedSymbols(std::unordered_map<std::string, std::vector<Feature>>& resultsByLayer, + const ScreenLineString& geometry, + const std::vector<const RenderLayer*>& layers, + const RenderedQueryOptions& options) const { + + auto renderedSymbols = placement->getCollisionIndex().queryRenderedSymbols(geometry); + std::vector<std::reference_wrapper<const RetainedQueryData>> bucketQueryData; + for (auto entry : renderedSymbols) { + bucketQueryData.push_back(placement->getQueryData(entry.first)); + } + // Although symbol query is global, symbol results are only sortable within a bucket + // For a predictable global sort order, we sort the buckets based on their corresponding tile position + std::sort(bucketQueryData.begin(), bucketQueryData.end(), [](const RetainedQueryData& a, const RetainedQueryData& b) { + return + std::tie(a.tileID.canonical.z, a.tileID.canonical.y, a.tileID.wrap, a.tileID.canonical.x) < + std::tie(b.tileID.canonical.z, b.tileID.canonical.y, b.tileID.wrap, b.tileID.canonical.x); + }); + + for (auto wrappedQueryData : bucketQueryData) { + auto& queryData = wrappedQueryData.get(); + auto bucketSymbols = queryData.featureIndex->lookupSymbolFeatures(renderedSymbols[queryData.bucketInstanceId], + options, + layers, + queryData.tileID, + queryData.featureSortOrder); + + for (auto layer : bucketSymbols) { + auto& resultFeatures = resultsByLayer[layer.first]; + std::move(layer.second.begin(), layer.second.end(), std::inserter(resultFeatures, resultFeatures.end())); + } + } +} std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineString& geometry, const RenderedQueryOptions& options, const std::vector<const RenderLayer*>& layers) const { std::unordered_set<std::string> sourceIDs; @@ -680,13 +712,18 @@ std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin sourceIDs.emplace(layer->baseImpl->source); } + mat4 projMatrix; + transformState.getProjMatrix(projMatrix); + std::unordered_map<std::string, std::vector<Feature>> resultsByLayer; for (const auto& sourceID : sourceIDs) { if (RenderSource* renderSource = getRenderSource(sourceID)) { - auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options, placement->getCollisionIndex()); + auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options, projMatrix); std::move(sourceResults.begin(), sourceResults.end(), std::inserter(resultsByLayer, resultsByLayer.begin())); } } + + queryRenderedSymbols(resultsByLayer, geometry, layers, options); std::vector<Feature> result; @@ -785,15 +822,6 @@ bool Renderer::Impl::hasTransitions(TimePoint timePoint) const { return false; } -void Renderer::Impl::commitFeatureIndexes() { - for (auto& source : renderSources) { - for (auto& renderTile : source.second->getRenderTiles()) { - Tile& tile = renderTile.get().tile; - tile.commitFeatureIndex(); - } - } -} - void Renderer::Impl::updateFadingTiles() { fadingTiles = false; for (auto& source : renderSources) { diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp index 4675ac79ea..4124f6f416 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -64,7 +64,12 @@ private: RenderLayer* getRenderLayer(const std::string& id); const RenderLayer* getRenderLayer(const std::string& id) const; - + + void queryRenderedSymbols(std::unordered_map<std::string, std::vector<Feature>>& resultsByLayer, + const ScreenLineString& geometry, + const std::vector<const RenderLayer*>& layers, + const RenderedQueryOptions& options) const; + std::vector<Feature> queryRenderedFeatures(const ScreenLineString&, const RenderedQueryOptions&, const std::vector<const RenderLayer*>&) const; // GlyphManagerObserver implementation. @@ -74,7 +79,6 @@ private: void onTileChanged(RenderSource&, const OverscaledTileID&) override; void onTileError(RenderSource&, const OverscaledTileID&, std::exception_ptr) override; - void commitFeatureIndexes(); void updateFadingTiles(); friend class Renderer; diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp index 057ad5a4a7..2d28b8dd84 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -68,8 +68,8 @@ RenderCustomGeometrySource::queryRenderedFeatures(const ScreenLineString& geomet const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const { - return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); + const mat4& projMatrix) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, projMatrix); } std::vector<Feature> RenderCustomGeometrySource::querySourceFeatures(const SourceQueryOptions& options) const { diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp index 033d731029..5533fe2b83 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp @@ -28,7 +28,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const final; + const mat4& projMatrix) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index cbf4db70b5..0e265efff4 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -86,8 +86,8 @@ RenderGeoJSONSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const { - return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); + const mat4& projMatrix) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, projMatrix); } std::vector<Feature> RenderGeoJSONSource::querySourceFeatures(const SourceQueryOptions& options) const { diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp index 72fccbd043..297fa09a29 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.hpp +++ b/src/mbgl/renderer/sources/render_geojson_source.hpp @@ -32,7 +32,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex&) const final; + const mat4& projMatrix) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_image_source.cpp b/src/mbgl/renderer/sources/render_image_source.cpp index 31a5916a34..dce5e40185 100644 --- a/src/mbgl/renderer/sources/render_image_source.cpp +++ b/src/mbgl/renderer/sources/render_image_source.cpp @@ -58,24 +58,32 @@ void RenderImageSource::finishRender(PaintParameters& parameters) { static const style::Properties<>::PossiblyEvaluated properties {}; static const DebugProgram::PaintPropertyBinders paintAttributeData(properties, 0); + auto& programInstance = parameters.programs.debug; + for (auto matrix : matrices) { - parameters.programs.debug.draw( + programInstance.draw( parameters.context, gl::LineStrip { 4.0f * parameters.pixelRatio }, gl::DepthMode::disabled(), gl::StencilMode::disabled(), gl::ColorMode::unblended(), - DebugProgram::UniformValues { - uniforms::u_matrix::Value{ matrix }, - uniforms::u_color::Value{ Color::red() } - }, - parameters.staticData.tileVertexBuffer, parameters.staticData.tileBorderIndexBuffer, parameters.staticData.tileBorderSegments, - paintAttributeData, - properties, - parameters.state.getZoom(), - "debug" + programInstance.computeAllUniformValues( + DebugProgram::UniformValues { + uniforms::u_matrix::Value{ matrix }, + uniforms::u_color::Value{ Color::red() } + }, + paintAttributeData, + properties, + parameters.state.getZoom() + ), + programInstance.computeAllAttributeBindings( + parameters.staticData.tileVertexBuffer, + paintAttributeData, + properties + ), + "image" ); } } @@ -85,7 +93,7 @@ RenderImageSource::queryRenderedFeatures(const ScreenLineString&, const TransformState&, const std::vector<const RenderLayer*>&, const RenderedQueryOptions&, - const CollisionIndex&) const { + const mat4&) const { return std::unordered_map<std::string, std::vector<Feature>> {}; } diff --git a/src/mbgl/renderer/sources/render_image_source.hpp b/src/mbgl/renderer/sources/render_image_source.hpp index 85ee0ace11..cf14e180fd 100644 --- a/src/mbgl/renderer/sources/render_image_source.hpp +++ b/src/mbgl/renderer/sources/render_image_source.hpp @@ -33,7 +33,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const final; + const mat4& projMatrix) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.cpp b/src/mbgl/renderer/sources/render_raster_dem_source.cpp index b3153622c3..fbf2c09d19 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.cpp @@ -147,7 +147,7 @@ RenderRasterDEMSource::queryRenderedFeatures(const ScreenLineString&, const TransformState&, const std::vector<const RenderLayer*>&, const RenderedQueryOptions&, - const CollisionIndex& ) const { + const mat4&) const { return std::unordered_map<std::string, std::vector<Feature>> {}; } diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.hpp b/src/mbgl/renderer/sources/render_raster_dem_source.hpp index 741214a14d..48c7803e92 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.hpp @@ -28,7 +28,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const final; + const mat4& projMatrix) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index 60b3fa9a3b..f97ce4e65b 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -77,7 +77,7 @@ RenderRasterSource::queryRenderedFeatures(const ScreenLineString&, const TransformState&, const std::vector<const RenderLayer*>&, const RenderedQueryOptions&, - const CollisionIndex& ) const { + const mat4&) const { return std::unordered_map<std::string, std::vector<Feature>> {}; } diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index 78eda199ac..32539a046d 100644 --- a/src/mbgl/renderer/sources/render_raster_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_source.hpp @@ -28,7 +28,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const final; + const mat4& projMatrix) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index e87bea5dcd..4de4f01e3f 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -80,8 +80,8 @@ RenderVectorSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const { - return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); + const mat4& projMatrix) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, projMatrix); } std::vector<Feature> RenderVectorSource::querySourceFeatures(const SourceQueryOptions& options) const { diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp index 592160dc16..6fd2425aa3 100644 --- a/src/mbgl/renderer/sources/render_vector_source.hpp +++ b/src/mbgl/renderer/sources/render_vector_source.hpp @@ -28,7 +28,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const final; + const mat4& projMatrix) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 8f83a0f982..d28e95181b 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -242,7 +242,7 @@ std::unordered_map<std::string, std::vector<Feature>> TilePyramid::queryRendered const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const { + const mat4& projMatrix) const { std::unordered_map<std::string, std::vector<Feature>> result; if (renderTiles.empty() || geometry.empty()) { return result; @@ -264,14 +264,19 @@ std::unordered_map<std::string, std::vector<Feature>> TilePyramid::queryRendered std::tie(b.id.canonical.z, b.id.canonical.y, b.id.wrap, b.id.canonical.x); }); + auto maxPitchScaleFactor = transformState.maxPitchScaleFactor(); + for (const RenderTile& renderTile : sortedTiles) { + const float scale = std::pow(2, transformState.getZoom() - renderTile.id.canonical.z); + auto queryPadding = maxPitchScaleFactor * renderTile.tile.getQueryPadding(layers) * util::EXTENT / util::tileSize / scale; + GeometryCoordinate tileSpaceBoundsMin = TileCoordinate::toGeometryCoordinate(renderTile.id, box.min); - if (tileSpaceBoundsMin.x >= util::EXTENT || tileSpaceBoundsMin.y >= util::EXTENT) { + if (tileSpaceBoundsMin.x - queryPadding >= util::EXTENT || tileSpaceBoundsMin.y - queryPadding >= util::EXTENT) { continue; } GeometryCoordinate tileSpaceBoundsMax = TileCoordinate::toGeometryCoordinate(renderTile.id, box.max); - if (tileSpaceBoundsMax.x < 0 || tileSpaceBoundsMax.y < 0) { + if (tileSpaceBoundsMax.x + queryPadding < 0 || tileSpaceBoundsMax.y + queryPadding < 0) { continue; } @@ -286,7 +291,7 @@ std::unordered_map<std::string, std::vector<Feature>> TilePyramid::queryRendered transformState, layers, options, - collisionIndex); + projMatrix); } return result; diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp index 2638599c38..0cef9e2c40 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -54,7 +54,7 @@ public: const TransformState& transformState, const std::vector<const RenderLayer*>&, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) const; + const mat4& projMatrix) const; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const; diff --git a/src/mbgl/shaders/line.cpp b/src/mbgl/shaders/line.cpp index c700295a15..68d2dcc468 100644 --- a/src/mbgl/shaders/line.cpp +++ b/src/mbgl/shaders/line.cpp @@ -31,6 +31,7 @@ uniform vec2 u_gl_units_to_pixels; varying vec2 v_normal; varying vec2 v_width2; varying float v_gamma_scale; +varying highp float v_linesofar; #ifndef HAS_UNIFORM_u_color @@ -131,6 +132,8 @@ void main() { vec2 a_extrude = a_data.xy - 128.0; float a_direction = mod(a_data.z, 4.0) - 1.0; + v_linesofar = (floor(a_data.z / 4.0) + a_data.w * 64.0) * 2.0; + vec2 pos = a_pos_normal.xy; // x is 1 if it's a round cap, 0 otherwise diff --git a/src/mbgl/storage/resource.cpp b/src/mbgl/storage/resource.cpp index 207dd2ee69..ba85f87dea 100644 --- a/src/mbgl/storage/resource.cpp +++ b/src/mbgl/storage/resource.cpp @@ -79,13 +79,13 @@ Resource Resource::spriteJSON(const std::string& base, float pixelRatio) { Resource Resource::glyphs(const std::string& urlTemplate, const FontStack& fontStack, const std::pair<uint16_t, uint16_t>& glyphRange) { return Resource { Resource::Kind::Glyphs, - util::replaceTokens(urlTemplate, [&](const std::string& token) { + util::replaceTokens(urlTemplate, [&](const std::string& token) -> optional<std::string> { if (token == "fontstack") { return util::percentEncode(fontStackToString(fontStack)); } else if (token == "range") { return util::toString(glyphRange.first) + "-" + util::toString(glyphRange.second); } else { - return std::string(); + return {}; } }) }; @@ -104,7 +104,7 @@ Resource Resource::tile(const std::string& urlTemplate, } return Resource { Resource::Kind::Tile, - util::replaceTokens(urlTemplate, [&](const std::string& token) { + util::replaceTokens(urlTemplate, [&](const std::string& token) -> optional<std::string> { if (token == "z") { return util::toString(z); } else if (token == "x") { @@ -123,7 +123,7 @@ Resource Resource::tile(const std::string& urlTemplate, } else if (token == "ratio") { return std::string(pixelRatio > 1.0 ? "@2x" : ""); } else { - return std::string(); + return {}; } }), Resource::TileData { diff --git a/src/mbgl/style/conversion/filter.cpp b/src/mbgl/style/conversion/filter.cpp index 3c941945fd..386d85e921 100644 --- a/src/mbgl/style/conversion/filter.cpp +++ b/src/mbgl/style/conversion/filter.cpp @@ -1,17 +1,39 @@ #include <mbgl/style/conversion/filter.hpp> +#include <mbgl/style/expression/literal.hpp> #include <mbgl/util/geometry.hpp> #include <mbgl/style/expression/expression.hpp> #include <mbgl/style/expression/type.hpp> -#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/boolean_operator.hpp> namespace mbgl { namespace style { namespace conversion { -using GeometryValue = mapbox::geometry::value; +using namespace mbgl::style::expression; + +static bool isExpression(const Convertible& filter); +std::unique_ptr<Expression> convertLegacyFilter(const Convertible& values, Error& error); + +optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const { + if (isExpression(value)) { + ParsingContext parsingContext(type::Boolean); + ParseResult parseResult = parsingContext.parseExpression(value); + if (!parseResult) { + error = { parsingContext.getCombinedErrors() }; + return {}; + } else { + return { Filter(std::move(parseResult)) }; + } + } else { + std::unique_ptr<Expression> expression = convertLegacyFilter(value, error); + if (!expression) return {}; + return Filter(optional<std::unique_ptr<Expression>>(std::move(expression))); + } +} // This is a port from https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/feature_filter/index.js -static bool isExpressionFilter(const Convertible& filter) { +bool isExpression(const Convertible& filter) { if (!isArray(filter) || arrayLength(filter) == 0) { return false; } @@ -20,7 +42,7 @@ static bool isExpressionFilter(const Convertible& filter) { if (!op) { return false; - + } else if (*op == "has") { if (arrayLength(filter) < 2) return false; optional<std::string> operand = toString(arrayMember(filter, 1)); @@ -35,7 +57,7 @@ static bool isExpressionFilter(const Convertible& filter) { } else if (*op == "any" || *op == "all") { for (std::size_t i = 1; i < arrayLength(filter); i++) { Convertible f = arrayMember(filter, i); - if (!isExpressionFilter(f) && !toBool(f)) { + if (!isExpression(f) && !toBool(f)) { return false; } } @@ -46,257 +68,136 @@ static bool isExpressionFilter(const Convertible& filter) { } } -static optional<GeometryValue> normalizeValue(const optional<GeometryValue>& value, Error& error) { - if (!value) { - error = { "filter expression value must be a boolean, number, or string" }; - return {}; - } else { - return *value; - } -} +std::unique_ptr<Expression> createExpression(std::string op, std::vector<std::unique_ptr<Expression>> args, Error& error) { + if (op == "any") { + return std::make_unique<Any>(std::move(args)); + + } else if (op == "all") { + return std::make_unique<All>(std::move(args)); -static optional<FeatureType> toFeatureType(const Convertible& value, Error& error) { - optional<std::string> type = toString(value); - if (!type) { - error = { "value for $type filter must be a string" }; - return {}; - } else if (*type == "Point") { - return FeatureType::Point; - } else if (*type == "LineString") { - return FeatureType::LineString; - } else if (*type == "Polygon") { - return FeatureType::Polygon; } else { - error = { "value for $type filter must be Point, LineString, or Polygon" }; - return {}; + ParsingContext parsingContext(type::Boolean); + ParseResult parseResult = createCompoundExpression(op, std::move(args), parsingContext); + if (!parseResult) { + error = { parsingContext.getCombinedErrors() }; + return {}; + } else { + return std::move(*parseResult); + } } } -static optional<FeatureIdentifier> toFeatureIdentifier(const Convertible& value, Error& error) { - optional<GeometryValue> identifier = toValue(value); - if (!identifier) { - error = { "filter expression value must be a boolean, number, or string" }; - return {}; - } else { - return (*identifier).match( - [] (uint64_t t) -> optional<FeatureIdentifier> { return { t }; }, - [] ( int64_t t) -> optional<FeatureIdentifier> { return { t }; }, - [] ( double t) -> optional<FeatureIdentifier> { return { t }; }, - [] (const std::string& t) -> optional<FeatureIdentifier> { return { t }; }, - [&] (const auto&) -> optional<FeatureIdentifier> { - error = { "filter expression value must be a boolean, number, or string" }; - return {}; - }); - } +std::unique_ptr<Expression> createExpression(std::string op, std::unique_ptr<Expression> expression, Error& error) { + std::vector<std::unique_ptr<Expression>> args; + args.push_back(std::move(expression)); + return createExpression(op, std::move(args), error); } -template <class FilterType, class IdentifierFilterType> -optional<Filter> convertUnaryFilter(const Convertible& value, Error& error) { - if (arrayLength(value) < 2) { - error = { "filter expression must have 2 elements" }; - return {}; - } - - optional<std::string> key = toString(arrayMember(value, 1)); - if (!key) { - error = { "filter expression key must be a string" }; - return {}; - } - - if (*key == "$id") { - return { IdentifierFilterType {} }; +std::unique_ptr<Expression> convertLiteral(const Convertible& convertible, Error& error) { + ParsingContext parsingContext; + ParseResult parseResult = Literal::parse(convertible, parsingContext); + if (parseResult) { + return std::move(*parseResult); } else { - return { FilterType { *key } }; + error = { parsingContext.getCombinedErrors() }; + return {}; } } -template <class FilterType, class TypeFilterType, class IdentifierFilterType> -optional<Filter> convertEqualityFilter(const Convertible& value, Error& error) { - if (arrayLength(value) < 3) { - error = { "filter expression must have 3 elements" }; - return {}; +std::vector<std::unique_ptr<Expression>> convertLiteralArray(const Convertible &input, Error& error, std::size_t startIndex = 0) { + std::vector<std::unique_ptr<Expression>> output; + for (std::size_t i = startIndex; i < arrayLength(input); i++) { + output.push_back(convertLiteral(arrayMember(input, i), error)); } + return output; +} - optional<std::string> key = toString(arrayMember(value, 1)); - if (!key) { - error = { "filter expression key must be a string" }; +std::unique_ptr<Expression> convertLegacyComparisonFilter(const Convertible& values, Error& error, optional<std::string> opOverride = {}) { + optional<std::string> op = opOverride ? opOverride : toString(arrayMember(values, 0)); + optional<std::string> property = toString(arrayMember(values, 1)); + + if (!property) { + error = { "filter property must be a string" }; return {}; - } - - if (*key == "$type") { - optional<FeatureType> filterValue = toFeatureType(arrayMember(value, 2), error); - if (!filterValue) { - return {}; - } - - return { TypeFilterType { *filterValue } }; - - } else if (*key == "$id") { - optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, 2), error); - if (!filterValue) { - return {}; - } - - return { IdentifierFilterType { *filterValue } }; - + } else if (*property == "$type") { + return createExpression("filter-type-" + *op, convertLiteralArray(values, error, 2), error); + } else if (*property == "$id") { + return createExpression("filter-id-" + *op, convertLiteralArray(values, error, 2), error); } else { - optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); - if (!filterValue) { - return {}; - } - - return { FilterType { *key, *filterValue } }; + return createExpression("filter-" + *op, convertLiteralArray(values, error, 1), error); } } - -template <class FilterType> -optional<Filter> convertBinaryFilter(const Convertible& value, Error& error) { - if (arrayLength(value) < 3) { - error = { "filter expression must have 3 elements" }; - return {}; - } - - optional<std::string> key = toString(arrayMember(value, 1)); - if (!key) { - error = { "filter expression key must be a string" }; - return {}; - } - - optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); - if (!filterValue) { + +std::unique_ptr<Expression> convertLegacyHasFilter(const Convertible& values, Error& error) { + optional<std::string> property = toString(arrayMember(values, 1)); + + if (!property) { + error = { "filter property must be a string" }; return {}; + } else if (*property == "$type") { + return std::make_unique<Literal>(true); + } else if (*property == "$id") { + return createExpression("filter-has-id", std::vector<std::unique_ptr<Expression>>(), error); + } else { + return createExpression("filter-has", std::make_unique<Literal>(*property), error); } - - return { FilterType { *key, *filterValue } }; } -template <class FilterType, class TypeFilterType, class IdentifierFilterType> -optional<Filter> convertSetFilter(const Convertible& value, Error& error) { - if (arrayLength(value) < 2) { - error = { "filter expression must at least 2 elements" }; - return {}; - } - - optional<std::string> key = toString(arrayMember(value, 1)); - if (!key) { - error = { "filter expression key must be a string" }; - return {}; - } - - if (*key == "$type") { - std::vector<FeatureType> values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - optional<FeatureType> filterValue = toFeatureType(arrayMember(value, i), error); - if (!filterValue) { - return {}; - } - values.push_back(*filterValue); - } - - return { TypeFilterType { std::move(values) } }; - - } else if (*key == "$id") { - std::vector<FeatureIdentifier> values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, i), error); - if (!filterValue) { - return {}; - } - values.push_back(*filterValue); - } - - return { IdentifierFilterType { std::move(values) } }; - +std::unique_ptr<Expression> convertLegacyInFilter(const Convertible& values, Error& error) { + optional<std::string> property = toString(arrayMember(values, 1)); + + if (!property) { + error = { "filter property must be a string" }; + return {}; + } else if (arrayLength(values) == 0) { + return std::make_unique<Literal>(false); + } else if (*property == "$type") { + return createExpression("filter-type-in", convertLiteralArray(values, error, 2), error); + } else if (*property == "$id") { + return createExpression("filter-id-in", convertLiteralArray(values, error, 2), error); } else { - std::vector<GeometryValue> values; - for (std::size_t i = 2; i < arrayLength(value); ++i) { - optional<GeometryValue> filterValue = normalizeValue(toValue(arrayMember(value, i)), error); - if (!filterValue) { - return {}; - } - values.push_back(*filterValue); - } - - return { FilterType { *key, std::move(values) } }; + return createExpression("filter-in", convertLiteralArray(values, error, 1), error); } } -template <class FilterType> -optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) { - std::vector<Filter> filters; - for (std::size_t i = 1; i < arrayLength(value); ++i) { - optional<Filter> element = convert<Filter>(arrayMember(value, i), error); - if (!element) { - return {}; - } - filters.push_back(*element); +std::vector<std::unique_ptr<Expression>> convertLegacyFilterArray(const Convertible &input, Error& error, std::size_t startIndex = 0) { + std::vector<std::unique_ptr<Expression>> output; + for (std::size_t i = startIndex; i < arrayLength(input); i++) { + output.push_back(convertLegacyFilter(arrayMember(input, i), error)); } - - return { FilterType { std::move(filters) } }; + return output; } -optional<Filter> convertExpressionFilter(const Convertible& value, Error& error) { - expression::ParsingContext ctx(expression::type::Boolean); - expression::ParseResult expression = ctx.parseExpression(value); - if (!expression) { - error = { ctx.getCombinedErrors() }; - return {}; - } - - return { ExpressionFilter { std::move(*expression) } }; -} - -optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const { - if (isExpressionFilter(value)) { - return convertExpressionFilter(value, error); +std::unique_ptr<Expression> convertLegacyFilter(const Convertible& values, Error& error) { + if (isUndefined(values)) { + return std::make_unique<Literal>(true); } - if (!isArray(value)) { - error = { "filter expression must be an array" }; - return {}; - } - - if (arrayLength(value) < 1) { - error = { "filter expression must have at least 1 element" }; - return {}; - } + optional<std::string> op = toString(arrayMember(values, 0)); - optional<std::string> op = toString(arrayMember(value, 0)); if (!op) { error = { "filter operator must be a string" }; return {}; + } else if (arrayLength(values) <= 1) { + return std::make_unique<Literal>(*op != "any"); + } else { + return ( + *op == "==" || + *op == "<" || + *op == ">" || + *op == "<=" || + *op == ">=" ? convertLegacyComparisonFilter(values, error) : + *op == "!=" ? createExpression("!", convertLegacyComparisonFilter(values, error, {"=="}), error) : + *op == "any" ? createExpression("any", convertLegacyFilterArray(values, error, 1), error) : + *op == "all" ? createExpression("all", convertLegacyFilterArray(values, error, 1), error) : + *op == "none" ? createExpression("!", createExpression("any", convertLegacyFilterArray(values, error, 1), error), error) : + *op == "in" ? convertLegacyInFilter(values, error) : + *op == "!in" ? createExpression("!", convertLegacyInFilter(values, error), error) : + *op == "has" ? convertLegacyHasFilter(values, error) : + *op == "!has" ? createExpression("!", convertLegacyHasFilter(values, error), error) : + std::make_unique<Literal>(true) + ); } - - if (*op == "==") { - return convertEqualityFilter<EqualsFilter, TypeEqualsFilter, IdentifierEqualsFilter>(value, error); - } else if (*op == "!=") { - return convertEqualityFilter<NotEqualsFilter, TypeNotEqualsFilter, IdentifierNotEqualsFilter>(value, error); - } else if (*op == ">") { - return convertBinaryFilter<GreaterThanFilter>(value, error); - } else if (*op == ">=") { - return convertBinaryFilter<GreaterThanEqualsFilter>(value, error); - } else if (*op == "<") { - return convertBinaryFilter<LessThanFilter>(value, error); - } else if (*op == "<=") { - return convertBinaryFilter<LessThanEqualsFilter>(value, error); - } else if (*op == "in") { - return convertSetFilter<InFilter, TypeInFilter, IdentifierInFilter>(value, error); - } else if (*op == "!in") { - return convertSetFilter<NotInFilter, TypeNotInFilter, IdentifierNotInFilter>(value, error); - } else if (*op == "all") { - return convertCompoundFilter<AllFilter>(value, error); - } else if (*op == "any") { - return convertCompoundFilter<AnyFilter>(value, error); - } else if (*op == "none") { - return convertCompoundFilter<NoneFilter>(value, error); - } else if (*op == "has") { - return convertUnaryFilter<HasFilter, HasIdentifierFilter>(value, error); - } else if (*op == "!has") { - return convertUnaryFilter<NotHasFilter, NotHasIdentifierFilter>(value, error); - } - - error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" }; - return {}; } } // namespace conversion diff --git a/src/mbgl/style/conversion/stringify.hpp b/src/mbgl/style/conversion/stringify.hpp index 7b7727d7c4..74171763a0 100644 --- a/src/mbgl/style/conversion/stringify.hpp +++ b/src/mbgl/style/conversion/stringify.hpp @@ -126,162 +126,9 @@ void stringify(Writer& writer, const FeatureIdentifier& id) { } template <class Writer> -class StringifyFilter { -public: - Writer& writer; - - void operator()(const NullFilter&) { - writer.Null(); - } - - void operator()(const EqualsFilter& f) { - stringifyBinaryFilter(f, "=="); - } - - void operator()(const NotEqualsFilter& f) { - stringifyBinaryFilter(f, "!="); - } - - void operator()(const LessThanFilter& f) { - stringifyBinaryFilter(f, "<"); - } - - void operator()(const LessThanEqualsFilter& f) { - stringifyBinaryFilter(f, "<="); - } - - void operator()(const GreaterThanFilter& f) { - stringifyBinaryFilter(f, ">"); - } - - void operator()(const GreaterThanEqualsFilter& f) { - stringifyBinaryFilter(f, ">="); - } - - void operator()(const InFilter& f) { - stringifySetFilter(f, "in"); - } - - void operator()(const NotInFilter& f) { - stringifySetFilter(f, "!in"); - } - - void operator()(const AllFilter& f) { - stringifyCompoundFilter(f, "all"); - } - - void operator()(const AnyFilter& f) { - stringifyCompoundFilter(f, "any"); - } - - void operator()(const NoneFilter& f) { - stringifyCompoundFilter(f, "none"); - } - - void operator()(const HasFilter& f) { - stringifyUnaryFilter("has", f.key); - } - - void operator()(const NotHasFilter& f) { - stringifyUnaryFilter("!has", f.key); - } - - void operator()(const TypeEqualsFilter& f) { - stringifyBinaryFilter(f, "==", "$type"); - } - - void operator()(const TypeNotEqualsFilter& f) { - stringifyBinaryFilter(f, "!=", "$type"); - } - - void operator()(const TypeInFilter& f) { - stringifySetFilter(f, "in", "$type"); - } - - void operator()(const TypeNotInFilter& f) { - stringifySetFilter(f, "!in", "$type"); - } - - void operator()(const IdentifierEqualsFilter& f) { - stringifyBinaryFilter(f, "==", "$id"); - } - - void operator()(const IdentifierNotEqualsFilter& f) { - stringifyBinaryFilter(f, "!=", "$id"); - } - - void operator()(const IdentifierInFilter& f) { - stringifySetFilter(f, "in", "$id"); - } - - void operator()(const IdentifierNotInFilter& f) { - stringifySetFilter(f, "!in", "$id"); - } - - void operator()(const HasIdentifierFilter&) { - stringifyUnaryFilter("has", "$id"); - } - - void operator()(const NotHasIdentifierFilter&) { - stringifyUnaryFilter("!has", "$id"); - } - - void operator()(const ExpressionFilter& filter) { - stringify(writer, filter.expression->serialize()); - } - -private: - template <class F> - void stringifyBinaryFilter(const F& f, const char * op) { - stringifyBinaryFilter(f, op, f.key); - } - - template <class F> - void stringifyBinaryFilter(const F& f, const char * op, const std::string& key) { - writer.StartArray(); - writer.String(op); - writer.String(key); - stringify(writer, f.value); - writer.EndArray(); - } - - template <class F> - void stringifySetFilter(const F& f, const char * op) { - stringifySetFilter(f, op, f.key); - } - - template <class F> - void stringifySetFilter(const F& f, const char * op, const std::string& key) { - writer.StartArray(); - writer.String(op); - writer.String(key); - for (const auto& value : f.values) { - stringify(writer, value); - } - writer.EndArray(); - } - - template <class F> - void stringifyCompoundFilter(const F& f, const char * op) { - writer.StartArray(); - writer.String(op); - for (const auto& filter : f.filters) { - Filter::visit(filter, *this); - } - writer.EndArray(); - } - - void stringifyUnaryFilter(const char * op, const std::string& key) { - writer.StartArray(); - writer.String(op); - writer.String(key); - writer.EndArray(); - } -}; - -template <class Writer> -void stringify(Writer& writer, const Filter& f) { - Filter::visit(f, StringifyFilter<Writer> { writer }); +void stringify(Writer& writer, const Filter& filter) { + if (!filter.expression) writer.Null(); + else stringify(writer, (*filter.expression)->serialize()); } template <class Writer> diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp index a2c4aa80b3..15ed10a90f 100644 --- a/src/mbgl/style/conversion/tileset.cpp +++ b/src/mbgl/style/conversion/tileset.cpp @@ -1,14 +1,11 @@ #include <mbgl/style/conversion/tileset.hpp> #include <mbgl/util/geo.hpp> +#include <mbgl/math/clamp.hpp> namespace mbgl { namespace style { namespace conversion { -bool validateLatitude(const double lat) { - return lat <= 90 && lat >= -90; -} - optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const { Tileset result; @@ -95,16 +92,20 @@ optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error error = { "bounds array must contain numeric longitude and latitude values" }; return {}; } - if (!validateLatitude(*bottom) || !validateLatitude(*top) || top <= bottom){ - error = { "bounds latitude values must be between -90 and 90 with bottom less than top" }; + + bottom = util::clamp(*bottom, -90.0, 90.0); + top = util::clamp(*top, -90.0, 90.0); + if (top <= bottom){ + error = { "bounds bottom latitude must be between smaller than top latitude" }; return {}; } + if(*left >= *right) { error = { "bounds left longitude should be less than right longitude" }; return {}; } - *left = util::max(-180.0, *left); - *right = util::min(180.0, *right); + left = util::max(-180.0, *left); + right = util::min(180.0, *right); result.bounds = LatLngBounds::hull({ *bottom, *left }, { *top, *right }); } diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp index c36ffa33e3..4226756fe4 100644 --- a/src/mbgl/style/expression/compound_expression.cpp +++ b/src/mbgl/style/expression/compound_expression.cpp @@ -3,6 +3,7 @@ #include <mbgl/style/expression/util.hpp> #include <mbgl/tile/geometry_tile_data.hpp> #include <mbgl/math/log2.hpp> +#include <mbgl/util/i18n.hpp> #include <mbgl/util/ignore.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/platform.hpp> @@ -18,7 +19,7 @@ namespace detail { The Signature<Fn> structs are wrappers around an "evaluate()" function whose purpose is to extract the necessary Type data from the evaluate function's type. There are three key (partial) specializations: - + Signature<R (Params...)>: Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result<U> @@ -29,9 +30,9 @@ namespace detail { Signature<R (const EvaluationContext&, Params...)>: Wraps an evaluate function that needs to access the expression evaluation parameters in addition to its subexpressions, i.e., - (const EvaluationParams& const T0&, const T1&, ...) -> Result<U>. Needed + (const EvaluationParams&, const T0&, const T1&, ...) -> Result<U>. Needed for expressions like ["zoom"], ["get", key], etc. - + In each of the above evaluate signatures, T0, T1, etc. are the types of the successfully evaluated subexpressions. */ @@ -42,7 +43,7 @@ struct Signature; template <class R, class... Params> struct Signature<R (Params...)> : SignatureBase { using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>; - + Signature(R (*evaluate_)(Params...), std::string name_) : SignatureBase( valueTypeToExpressionType<std::decay_t<typename R::Value>>(), @@ -54,7 +55,7 @@ struct Signature<R (Params...)> : SignatureBase { EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{}); } - + std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override { typename Signature::Args argsArray; std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin()); @@ -79,7 +80,7 @@ private: template <class R, typename T> struct Signature<R (const Varargs<T>&)> : SignatureBase { using Args = std::vector<std::unique_ptr<Expression>>; - + Signature(R (*evaluate_)(const Varargs<T>&), std::string name_) : SignatureBase( valueTypeToExpressionType<std::decay_t<typename R::Value>>(), @@ -88,11 +89,11 @@ struct Signature<R (const Varargs<T>&)> : SignatureBase { ), evaluate(evaluate_) {} - + std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override { return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args)); }; - + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { Varargs<T> evaluated; evaluated.reserve(args.size()); @@ -114,7 +115,7 @@ struct Signature<R (const Varargs<T>&)> : SignatureBase { template <class R, class... Params> struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase { using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>; - + Signature(R (*evaluate_)(const EvaluationContext&, Params...), std::string name_) : SignatureBase( valueTypeToExpressionType<std::decay_t<typename R::Value>>(), @@ -123,17 +124,17 @@ struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase { ), evaluate(evaluate_) {} - + std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override { typename Signature::Args argsArray; std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin()); return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray)); } - + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{}); } - + private: template <std::size_t ...I> EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const { @@ -149,6 +150,41 @@ private: R (*evaluate)(const EvaluationContext&, Params...); }; + +// Evaluate function needing EvaluationContext and Varargs +// (const EvaluationContext&, const Varargs<T>&) -> Result<U> +template <class R, typename T> +struct Signature<R (const EvaluationContext&, const Varargs<T>&)> : SignatureBase { + using Args = std::vector<std::unique_ptr<Expression>>; + + Signature(R (*evaluate_)(const EvaluationContext&, const Varargs<T>&), std::string name_) : + SignatureBase( + valueTypeToExpressionType<std::decay_t<typename R::Value>>(), + VarargsType { valueTypeToExpressionType<T>() }, + std::move(name_) + ), + evaluate(evaluate_) + {} + + std::unique_ptr<Expression> makeExpression(std::vector<std::unique_ptr<Expression>> args) const override { + return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args)); + }; + + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { + Varargs<T> evaluated; + evaluated.reserve(args.size()); + for (const auto& arg : args) { + const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters); + if(!evaluatedArg) return evaluatedArg.error(); + evaluated.push_back(*fromExpressionValue<std::decay_t<T>>(*evaluatedArg)); + } + const R value = evaluate(evaluationParameters, evaluated); + if (!value) return value.error(); + return *value; + } + + R (*evaluate)(const EvaluationContext&, const Varargs<T>&); +}; // Machinery to pull out function types from class methods, lambdas, etc. template <class R, class... Params> @@ -180,29 +216,104 @@ static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction, return std::make_unique<detail::Signature<Fn>>(evaluateFunction, std::move(name)); } +Value featureIdAsExpressionValue(EvaluationContext params) { + assert(params.feature); + auto id = params.feature->getID(); + if (!id) return Null; + return id->match([](const auto& idid) { + return toExpressionValue(mbgl::Value(idid)); + }); +}; + +optional<Value> featurePropertyAsExpressionValue(EvaluationContext params, const std::string& key) { + assert(params.feature); + auto property = params.feature->getValue(key); + return property ? toExpressionValue(*property) : optional<Value>(); +}; + +optional<std::string> featureTypeAsString(FeatureType type) { + switch(type) { + case FeatureType::Point: + return optional<std::string>("Point"); + case FeatureType::LineString: + return optional<std::string>("LineString"); + case FeatureType::Polygon: + return optional<std::string>("Polygon"); + case FeatureType::Unknown: + return optional<std::string>("Unknown"); + default: + return {}; + } +}; + +optional<double> featurePropertyAsDouble(EvaluationContext params, const std::string& key) { + assert(params.feature); + auto property = params.feature->getValue(key); + if (!property) return {}; + return property->match( + [](double value) { return value; }, + [](uint64_t value) { return optional<double>(static_cast<double>(value)); }, + [](int64_t value) { return optional<double>(static_cast<double>(value)); }, + [](auto) { return optional<double>(); } + ); +}; + +optional<std::string> featurePropertyAsString(EvaluationContext params, const std::string& key) { + assert(params.feature); + auto property = params.feature->getValue(key); + if (!property) return {}; + return property->match( + [](std::string value) { return value; }, + [](auto) { return optional<std::string>(); } + ); +}; + +optional<double> featureIdAsDouble(EvaluationContext params) { + assert(params.feature); + auto id = params.feature->getID(); + if (!id) return optional<double>(); + return id->match( + [](double value) { return value; }, + [](uint64_t value) { return optional<double>(static_cast<double>(value)); }, + [](int64_t value) { return optional<double>(static_cast<double>(value)); }, + [](auto) { return optional<double>(); } + ); +}; + +optional<std::string> featureIdAsString(EvaluationContext params) { + assert(params.feature); + auto id = params.feature->getID(); + if (!id) return optional<std::string>(); + return id->match( + [](std::string value) { return value; }, + [](auto) { return optional<std::string>(); } + ); +}; + std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initializeDefinitions() { std::unordered_map<std::string, CompoundExpressionRegistry::Definition> definitions; auto define = [&](std::string name, auto fn) { definitions[name].push_back(makeSignature(fn, name)); }; - + define("e", []() -> Result<double> { return 2.718281828459045; }); define("pi", []() -> Result<double> { return 3.141592653589793; }); define("ln2", []() -> Result<double> { return 0.6931471805599453; }); define("typeof", [](const Value& v) -> Result<std::string> { return toString(typeOf(v)); }); - + define("to-string", [](const Value& value) -> Result<std::string> { return value.match( + [](const NullValue&) -> Result<std::string> { return std::string(); }, [](const Color& c) -> Result<std::string> { return c.stringify(); }, // avoid quoting [](const std::string& s) -> Result<std::string> { return s; }, // avoid quoting [](const auto& v) -> Result<std::string> { return stringify(v); } ); }); - + define("to-boolean", [](const Value& v) -> Result<bool> { return v.match( - [&] (double f) { return (bool)f; }, + [&] (double f) { return static_cast<bool>(f); }, [&] (const std::string& s) { return s.length() > 0; }, [&] (bool b) { return b; }, [&] (const NullValue&) { return false; }, @@ -212,10 +323,10 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali define("to-rgba", [](const Color& color) -> Result<std::array<double, 4>> { return color.toArray(); }); - + define("rgba", rgba); define("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); }); - + define("zoom", [](const EvaluationContext& params) -> Result<double> { if (!params.zoom) { return EvaluationError { @@ -224,7 +335,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali } return *(params.zoom); }); - + define("heatmap-density", [](const EvaluationContext& params) -> Result<double> { if (!params.heatmapDensity) { return EvaluationError { @@ -240,7 +351,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali "Feature data is unavailable in the current evaluation context." }; } - + return params.feature->getValue(key) ? true : false; }); define("has", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<bool> { @@ -266,7 +377,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali } return object.at(key); }); - + define("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> { if (!params.feature) { return EvaluationError { @@ -280,14 +391,14 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali } return result; }); - + define("geometry-type", [](const EvaluationContext& params) -> Result<std::string> { if (!params.feature) { return EvaluationError { "Feature data is unavailable in the current evaluation context." }; } - + auto type = params.feature->getType(); if (type == FeatureType::Point) { return "Point"; @@ -299,14 +410,14 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali return "Unknown"; } }); - + define("id", [](const EvaluationContext& params) -> Result<Value> { if (!params.feature) { return EvaluationError { "Feature data is unavailable in the current evaluation context." }; } - + auto id = params.feature->getID(); if (!id) { return Null; @@ -317,7 +428,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali } ); }); - + define("+", [](const Varargs<double>& args) -> Result<double> { double sum = 0.0f; for (auto arg : args) { @@ -347,7 +458,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali define("asin", [](double x) -> Result<double> { return asin(x); }); define("acos", [](double x) -> Result<double> { return acos(x); }); define("atan", [](double x) -> Result<double> { return atan(x); }); - + define("min", [](const Varargs<double>& args) -> Result<double> { double result = std::numeric_limits<double>::infinity(); for (double arg : args) { @@ -363,7 +474,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali return result; }); - define("round", [](double x) -> Result<double> { return std::round(x); }); + define("round", [](double x) -> Result<double> { return ::round(x); }); define("floor", [](double x) -> Result<double> { return std::floor(x); }); define("ceil", [](double x) -> Result<double> { return std::ceil(x); }); define("abs", [](double x) -> Result<double> { return std::abs(x); }); @@ -376,9 +487,13 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali define("<", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs < rhs; }); define("<=", [](double lhs, double rhs) -> Result<bool> { return lhs <= rhs; }); define("<=", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs <= rhs; }); - + define("!", [](bool e) -> Result<bool> { return !e; }); + define("is-supported-script", [](const std::string& x) -> Result<bool> { + return util::i18n::isStringInSupportedScript(x); + }); + define("upcase", [](const std::string& input) -> Result<std::string> { return platform::uppercase(input); }); @@ -395,7 +510,130 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali define("error", [](const std::string& input) -> Result<type::ErrorType> { return EvaluationError { input }; }); + + // Legacy Filters + define("filter-==", [](const EvaluationContext& params, const std::string& key, const Value &lhs) -> Result<bool> { + const auto rhs = featurePropertyAsExpressionValue(params, key); + return rhs ? lhs == *rhs : false; + }); + + define("filter-id-==", [](const EvaluationContext& params, const Value &lhs) -> Result<bool> { + return lhs == featureIdAsExpressionValue(params); + }); + + define("filter-type-==", [](const EvaluationContext& params, const std::string &lhs) -> Result<bool> { + if (!params.feature) return false; + return featureTypeAsString(params.feature->getType()) == lhs; + }); + + define("filter-<", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> { + auto rhs = featurePropertyAsDouble(params, key); + return rhs ? rhs < lhs : false; + }); + + define("filter-<", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> { + auto rhs = featurePropertyAsString(params, key); + return rhs ? rhs < lhs : false; + }); + + define("filter-id-<", [](const EvaluationContext& params, double lhs) -> Result<bool> { + auto rhs = featureIdAsDouble(params); + return rhs ? rhs < lhs : false; + }); + + define("filter-id-<", [](const EvaluationContext& params, std::string lhs) -> Result<bool> { + auto rhs = featureIdAsString(params); + return rhs ? rhs < lhs : false; + }); + + define("filter->", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> { + auto rhs = featurePropertyAsDouble(params, key); + return rhs ? rhs > lhs : false; + }); + + define("filter->", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> { + auto rhs = featurePropertyAsString(params, key); + return rhs ? rhs > lhs : false; + }); + + define("filter-id->", [](const EvaluationContext& params, double lhs) -> Result<bool> { + auto rhs = featureIdAsDouble(params); + return rhs ? rhs > lhs : false; + }); + + define("filter-id->", [](const EvaluationContext& params, std::string lhs) -> Result<bool> { + auto rhs = featureIdAsString(params); + return rhs ? rhs > lhs : false; + }); + + define("filter-<=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> { + auto rhs = featurePropertyAsDouble(params, key); + return rhs ? rhs <= lhs : false; + }); + + define("filter-<=", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> { + auto rhs = featurePropertyAsString(params, key); + return rhs ? rhs <= lhs : false; + }); + + define("filter-id-<=", [](const EvaluationContext& params, double lhs) -> Result<bool> { + auto rhs = featureIdAsDouble(params); + return rhs ? rhs <= lhs : false; + }); + + define("filter-id-<=", [](const EvaluationContext& params, std::string lhs) -> Result<bool> { + auto rhs = featureIdAsString(params); + return rhs ? rhs <= lhs : false; + }); + + define("filter->=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> { + auto rhs = featurePropertyAsDouble(params, key); + return rhs ? rhs >= lhs : false; + }); + + define("filter->=", [](const EvaluationContext& params, const std::string& key, std::string lhs) -> Result<bool> { + auto rhs = featurePropertyAsString(params, key); + return rhs ? rhs >= lhs : false; + }); + + define("filter-id->=", [](const EvaluationContext& params, double lhs) -> Result<bool> { + auto rhs = featureIdAsDouble(params); + return rhs ? rhs >= lhs : false; + }); + + define("filter-id->=", [](const EvaluationContext& params, std::string lhs) -> Result<bool> { + auto rhs = featureIdAsString(params); + return rhs ? rhs >= lhs : false; + }); + + define("filter-has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> { + assert(params.feature); + return bool(params.feature->getValue(key)); + }); + + define("filter-has-id", [](const EvaluationContext& params) -> Result<bool> { + assert(params.feature); + return bool(params.feature->getID()); + }); + + define("filter-type-in", [](const EvaluationContext& params, const Varargs<std::string>& types) -> Result<bool> { + assert(params.feature); + optional<std::string> type = featureTypeAsString(params.feature->getType()); + return std::find(types.begin(), types.end(), type) != types.end(); + }); + define("filter-id-in", [](const EvaluationContext& params, const Varargs<Value>& ids) -> Result<bool> { + auto id = featureIdAsExpressionValue(params); + return std::find(ids.begin(), ids.end(), id) != ids.end(); + }); + + define("filter-in", [](const EvaluationContext& params, const Varargs<Value>& varargs) -> Result<bool> { + if (varargs.size() < 2) return false; + assert(varargs[0].is<std::string>()); + auto value = featurePropertyAsExpressionValue(params, varargs[0].get<std::string>()); + return value ? std::find(varargs.begin() + 1, varargs.end(), *value) != varargs.end() : false; + }); + return definitions; } @@ -414,7 +652,7 @@ ParseResult parseCompoundExpression(const std::string name, const Convertible& v return ParseResult(); } const CompoundExpressionRegistry::Definition& definition = it->second; - + auto length = arrayLength(value); // Check if we have a single signature with the correct number of @@ -440,14 +678,14 @@ ParseResult parseCompoundExpression(const std::string name, const Convertible& v args.reserve(length - 1); for (std::size_t i = 1; i < length; i++) { optional<type::Type> expected; - + if (singleMatchingSignature) { expected = definition[*singleMatchingSignature]->params.match( [](const VarargsType& varargs) { return varargs.type; }, [&](const std::vector<type::Type>& params_) { return params_[i - 1]; } ); } - + auto parsed = ctx.parse(arrayMember(value, i), i, expected); if (!parsed) { return parsed; @@ -474,7 +712,7 @@ ParseResult createCompoundExpression(const Definition& definition, for (const std::unique_ptr<detail::SignatureBase>& signature : definition) { signatureContext.clearErrors(); - + if (signature->params.is<std::vector<type::Type>>()) { const std::vector<type::Type>& params = signature->params.get<std::vector<type::Type>>(); if (params.size() != args.size()) { @@ -502,12 +740,12 @@ ParseResult createCompoundExpression(const Definition& definition, } } } - + if (signatureContext.getErrors().size() == 0) { return ParseResult(signature->makeExpression(std::move(args))); } } - + if (definition.size() == 1) { ctx.appendErrors(std::move(signatureContext)); } else { @@ -540,10 +778,32 @@ ParseResult createCompoundExpression(const Definition& definition, } ctx.error("Expected arguments of type " + signatures + ", but found (" + actualTypes + ") instead."); } - + return ParseResult(); } +ParseResult createCompoundExpression(const std::string& name, ParsingContext& ctx) { + return createCompoundExpression(name, std::vector<std::unique_ptr<Expression>>(), ctx); +} + +ParseResult createCompoundExpression(const std::string& name, + std::unique_ptr<Expression> arg1, + ParsingContext& ctx) { + std::vector<std::unique_ptr<Expression>> args; + args.push_back(std::move(arg1)); + return createCompoundExpression(name, std::move(args), ctx); +} + +ParseResult createCompoundExpression(const std::string& name, + std::unique_ptr<Expression> arg1, + std::unique_ptr<Expression> arg2, + ParsingContext& ctx) { + std::vector<std::unique_ptr<Expression>> args; + args.push_back(std::move(arg1)); + args.push_back(std::move(arg2)); + return createCompoundExpression(name, std::move(args), ctx); +} + } // namespace expression } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/expression/literal.cpp b/src/mbgl/style/expression/literal.cpp index 8a63980dba..f68cfd5cf5 100644 --- a/src/mbgl/style/expression/literal.cpp +++ b/src/mbgl/style/expression/literal.cpp @@ -109,6 +109,14 @@ mbgl::Value Literal::serialize() const { return *fromExpressionValue<mbgl::Value>(value); } } + +std::unique_ptr<Literal> createLiteral(const char* value) { + return createLiteral(std::string(value)); +} + +std::unique_ptr<Literal> createLiteral(Value value) { + return std::make_unique<Literal>(value); +} } // namespace expression } // namespace style diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp index 3d41f0bdd3..59123c9812 100644 --- a/src/mbgl/style/expression/match.cpp +++ b/src/mbgl/style/expression/match.cpp @@ -83,6 +83,10 @@ template<> EvaluationResult Match<std::string>::evaluate(const EvaluationContext return inputValue.error(); } + if (!inputValue->is<std::string>()) { + return otherwise->evaluate(params); + } + auto it = branches.find(inputValue->get<std::string>()); if (it != branches.end()) { return (*it).second->evaluate(params); @@ -96,7 +100,11 @@ template<> EvaluationResult Match<int64_t>::evaluate(const EvaluationContext& pa if (!inputValue) { return inputValue.error(); } - + + if (!inputValue->is<double>()) { + return otherwise->evaluate(params); + } + const auto numeric = inputValue->get<double>(); int64_t rounded = std::floor(numeric); if (numeric == rounded) { @@ -280,7 +288,7 @@ ParseResult parseMatch(const Convertible& value, ParsingContext& ctx) { branches.push_back(std::make_pair(std::move(labels), std::move(*output))); } - auto input = ctx.parse(arrayMember(value, 1), 1, inputType); + auto input = ctx.parse(arrayMember(value, 1), 1, {type::Value}); if (!input) { return ParseResult(); } @@ -292,6 +300,12 @@ ParseResult parseMatch(const Convertible& value, ParsingContext& ctx) { assert(inputType && outputType); + optional<std::string> err; + if ((*input)->getType() != type::Value && (err = type::checkSubtype(*inputType, (*input)->getType()))) { + ctx.error(*err, 1); + return ParseResult(); + } + return inputType->match( [&](const type::NumberType&) { return create<int64_t>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx); diff --git a/src/mbgl/style/filter.cpp b/src/mbgl/style/filter.cpp index 51aa6bcf82..2559eb4816 100644 --- a/src/mbgl/style/filter.cpp +++ b/src/mbgl/style/filter.cpp @@ -1,12 +1,20 @@ #include <mbgl/style/filter.hpp> -#include <mbgl/style/filter_evaluator.hpp> #include <mbgl/tile/geometry_tile_data.hpp> namespace mbgl { namespace style { bool Filter::operator()(const expression::EvaluationContext &context) const { - return FilterBase::visit(*this, FilterEvaluator { context }); + + if (!this->expression) return true; + + const expression::EvaluationResult result = (*this->expression)->evaluate(context); + if (result) { + const optional<bool> typed = expression::fromExpressionValue<bool>(*result); + return typed ? *typed : false; + } else { + return true; + } } } // namespace style diff --git a/src/mbgl/style/filter_evaluator.cpp b/src/mbgl/style/filter_evaluator.cpp deleted file mode 100644 index 72022172f4..0000000000 --- a/src/mbgl/style/filter_evaluator.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include <mbgl/style/filter.hpp> -#include <mbgl/style/filter_evaluator.hpp> -#include <mbgl/tile/geometry_tile_data.hpp> - -namespace mbgl { -namespace style { - -template <class Op> -struct Comparator { - const Op& op; - - template <class T> - bool operator()(const T& lhs, const T& rhs) const { - return op(lhs, rhs); - } - - template <class T0, class T1> - auto operator()(const T0& lhs, const T1& rhs) const - -> typename std::enable_if_t<std::is_arithmetic<T0>::value && !std::is_same<T0, bool>::value && - std::is_arithmetic<T1>::value && !std::is_same<T1, bool>::value, bool> { - return op(double(lhs), double(rhs)); - } - - template <class T0, class T1> - auto operator()(const T0&, const T1&) const - -> typename std::enable_if_t<!std::is_arithmetic<T0>::value || std::is_same<T0, bool>::value || - !std::is_arithmetic<T1>::value || std::is_same<T1, bool>::value, bool> { - return false; - } - - bool operator()(const NullValue&, - const NullValue&) const { - // Should be unreachable; null is not currently allowed by the style specification. - assert(false); - return false; - } - - bool operator()(const std::vector<Value>&, - const std::vector<Value>&) const { - // Should be unreachable; nested values are not currently allowed by the style specification. - assert(false); - return false; - } - - bool operator()(const PropertyMap&, - const PropertyMap&) const { - // Should be unreachable; nested values are not currently allowed by the style specification. - assert(false); - return false; - } -}; - -template <class Op> -bool compare(const Value& lhs, const Value& rhs, const Op& op) { - return Value::binary_visit(lhs, rhs, Comparator<Op> { op }); -} - -bool equal(const Value& lhs, const Value& rhs) { - return compare(lhs, rhs, [] (const auto& lhs_, const auto& rhs_) { return lhs_ == rhs_; }); -} - -bool FilterEvaluator::operator()(const NullFilter&) const { - return true; -} - -bool FilterEvaluator::operator()(const EqualsFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - return actual && equal(*actual, filter.value); -} - -bool FilterEvaluator::operator()(const NotEqualsFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - return !actual || !equal(*actual, filter.value); -} - -bool FilterEvaluator::operator()(const LessThanFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ < rhs_; }); -} - -bool FilterEvaluator::operator()(const LessThanEqualsFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ <= rhs_; }); -} - -bool FilterEvaluator::operator()(const GreaterThanFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ > rhs_; }); -} - -bool FilterEvaluator::operator()(const GreaterThanEqualsFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - return actual && compare(*actual, filter.value, [] (const auto& lhs_, const auto& rhs_) { return lhs_ >= rhs_; }); -} - -bool FilterEvaluator::operator()(const InFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - if (!actual) - return false; - for (const auto& v: filter.values) { - if (equal(*actual, v)) { - return true; - } - } - return false; -} - -bool FilterEvaluator::operator()(const NotInFilter& filter) const { - optional<Value> actual = context.feature->getValue(filter.key); - if (!actual) - return true; - for (const auto& v: filter.values) { - if (equal(*actual, v)) { - return false; - } - } - return true; -} - -bool FilterEvaluator::operator()(const AnyFilter& filter) const { - for (const auto& f: filter.filters) { - if (Filter::visit(f, *this)) { - return true; - } - } - return false; -} - -bool FilterEvaluator::operator()(const AllFilter& filter) const { - for (const auto& f: filter.filters) { - if (!Filter::visit(f, *this)) { - return false; - } - } - return true; -} - -bool FilterEvaluator::operator()(const NoneFilter& filter) const { - for (const auto& f: filter.filters) { - if (Filter::visit(f, *this)) { - return false; - } - } - return true; -} - -bool FilterEvaluator::operator()(const HasFilter& filter) const { - return bool(context.feature->getValue(filter.key)); -} - -bool FilterEvaluator::operator()(const NotHasFilter& filter) const { - return !context.feature->getValue(filter.key); -} - -bool FilterEvaluator::operator()(const TypeEqualsFilter& filter) const { - return context.feature->getType() == filter.value; -} - -bool FilterEvaluator::operator()(const TypeNotEqualsFilter& filter) const { - return context.feature->getType() != filter.value; -} - -bool FilterEvaluator::operator()(const TypeInFilter& filter) const { - for (const auto& v: filter.values) { - if (context.feature->getType() == v) { - return true; - } - } - return false; -} - -bool FilterEvaluator::operator()(const TypeNotInFilter& filter) const { - for (const auto& v: filter.values) { - if (context.feature->getType() == v) { - return false; - } - } - return true; -} - -bool FilterEvaluator::operator()(const IdentifierEqualsFilter& filter) const { - return context.feature->getID() == filter.value; -} - -bool FilterEvaluator::operator()(const IdentifierNotEqualsFilter& filter) const { - return context.feature->getID() != filter.value; -} - -bool FilterEvaluator::operator()(const IdentifierInFilter& filter) const { - for (const auto& v: filter.values) { - if (context.feature->getID() == v) { - return true; - } - } - return false; -} - -bool FilterEvaluator::operator()(const IdentifierNotInFilter& filter) const { - for (const auto& v: filter.values) { - if (context.feature->getID() == v) { - return false; - } - } - return true; -} - -bool FilterEvaluator::operator()(const HasIdentifierFilter&) const { - return bool(context.feature->getID()); -} - -bool FilterEvaluator::operator()(const NotHasIdentifierFilter&) const { - return !context.feature->getID(); -} - -bool FilterEvaluator::operator()(const ExpressionFilter& filter) const { - const expression::EvaluationResult result = filter.expression->evaluate(context); - if (result) { - const optional<bool> typed = expression::fromExpressionValue<bool>(*result); - return typed ? *typed : false; - } - return false; -} - -} // namespace style -} // namespace mbgl diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp index 833658c33e..091840a371 100644 --- a/src/mbgl/text/collision_index.cpp +++ b/src/mbgl/text/collision_index.cpp @@ -219,7 +219,7 @@ std::pair<bool,bool> CollisionIndex::placeLineFeature(CollisionFeature& feature, } -void CollisionIndex::insertFeature(CollisionFeature& feature, bool ignorePlacement) { +void CollisionIndex::insertFeature(CollisionFeature& feature, bool ignorePlacement, uint32_t bucketInstanceId) { if (feature.alongLine) { for (auto& circle : feature.boxes) { if (!circle.used) { @@ -227,18 +227,18 @@ void CollisionIndex::insertFeature(CollisionFeature& feature, bool ignorePlaceme } if (ignorePlacement) { - ignoredGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ circle.px, circle.py }, circle.radius}); + ignoredGrid.insert(IndexedSubfeature(feature.indexedFeature, bucketInstanceId), {{ circle.px, circle.py }, circle.radius}); } else { - collisionGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ circle.px, circle.py }, circle.radius}); + collisionGrid.insert(IndexedSubfeature(feature.indexedFeature, bucketInstanceId), {{ circle.px, circle.py }, circle.radius}); } } } else { assert(feature.boxes.size() == 1); auto& box = feature.boxes[0]; if (ignorePlacement) { - ignoredGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ box.px1, box.py1 }, { box.px2, box.py2 }}); + ignoredGrid.insert(IndexedSubfeature(feature.indexedFeature, bucketInstanceId), {{ box.px1, box.py1 }, { box.px2, box.py2 }}); } else { - collisionGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ box.px1, box.py1 }, { box.px2, box.py2 }}); + collisionGrid.insert(IndexedSubfeature(feature.indexedFeature, bucketInstanceId), {{ box.px1, box.py1 }, { box.px2, box.py2 }}); } } } @@ -262,66 +262,41 @@ bool polygonIntersectsBox(const LineString<float>& polygon, const GridIndex<Inde return util::polygonIntersectsPolygon(integerPolygon, bboxPoints); } -std::vector<IndexedSubfeature> CollisionIndex::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, const UnwrappedTileID& tileID, const std::string& sourceID) const { - std::vector<IndexedSubfeature> result; +std::unordered_map<uint32_t, std::vector<IndexedSubfeature>> CollisionIndex::queryRenderedSymbols(const ScreenLineString& queryGeometry) const { + std::unordered_map<uint32_t, std::vector<IndexedSubfeature>> result; if (queryGeometry.empty() || (collisionGrid.empty() && ignoredGrid.empty())) { return result; } - - mat4 posMatrix; - mat4 projMatrix; - transformState.getProjMatrix(projMatrix); - transformState.matrixFor(posMatrix, tileID); - matrix::multiply(posMatrix, projMatrix, posMatrix); - - // queryGeometry is specified in integer tile units, but in projecting we switch to float pixels - LineString<float> projectedQuery; + + LineString<float> gridQuery; for (const auto& point : queryGeometry) { - auto projected = projectPoint(posMatrix, convertPoint<float>(point)); - projectedQuery.push_back(projected); + gridQuery.emplace_back(point.x + viewportPadding, point.y + viewportPadding); } - auto envelope = mapbox::geometry::envelope(projectedQuery); + auto envelope = mapbox::geometry::envelope(gridQuery); using QueryResult = std::pair<IndexedSubfeature, GridIndex<IndexedSubfeature>::BBox>; - std::vector<QueryResult> thisTileFeatures; std::vector<QueryResult> features = collisionGrid.queryWithBoxes(envelope); - - for (auto& queryResult : features) { - auto& feature = queryResult.first; - if (feature.sourceID == sourceID && feature.tileID == tileID.canonical) { - // We only have to filter on the canonical ID because even if the feature is showing multiple times - // we treat it as one feature. - thisTileFeatures.push_back(queryResult); - } - } - std::vector<QueryResult> ignoredFeatures = ignoredGrid.queryWithBoxes(envelope); - for (auto& queryResult : ignoredFeatures) { - auto& feature = queryResult.first; - if (feature.sourceID == sourceID && feature.tileID == tileID.canonical) { - thisTileFeatures.push_back(queryResult); - } - } + features.insert(features.end(), ignoredFeatures.begin(), ignoredFeatures.end()); - std::unordered_map<std::string, std::unordered_map<std::string, std::unordered_set<std::size_t>>> sourceLayerFeatures; - for (auto& queryResult : thisTileFeatures) { + std::unordered_map<uint32_t, std::unordered_set<size_t>> seenBuckets; + for (auto& queryResult : features) { auto& feature = queryResult.first; auto& bbox = queryResult.second; // Skip already seen features. - auto& seenFeatures = sourceLayerFeatures[feature.sourceLayerName][feature.bucketName]; + auto& seenFeatures = seenBuckets[feature.bucketInstanceId]; if (seenFeatures.find(feature.index) != seenFeatures.end()) continue; - - seenFeatures.insert(feature.index); - if (!polygonIntersectsBox(projectedQuery, bbox)) { + if (!polygonIntersectsBox(gridQuery, bbox)) { continue; } - result.push_back(feature); + seenFeatures.insert(feature.index); + result[feature.bucketInstanceId].push_back(feature); } return result; diff --git a/src/mbgl/text/collision_index.hpp b/src/mbgl/text/collision_index.hpp index 8653c1d76c..b2be4c6ade 100644 --- a/src/mbgl/text/collision_index.hpp +++ b/src/mbgl/text/collision_index.hpp @@ -28,11 +28,10 @@ public: const bool pitchWithMap, const bool collisionDebug); - void insertFeature(CollisionFeature& feature, bool ignorePlacement); + void insertFeature(CollisionFeature& feature, bool ignorePlacement, uint32_t bucketInstanceId); - std::vector<IndexedSubfeature> queryRenderedSymbols(const GeometryCoordinates&, const UnwrappedTileID& tileID, const std::string& sourceID) const; + std::unordered_map<uint32_t, std::vector<IndexedSubfeature>> queryRenderedSymbols(const ScreenLineString&) const; - private: bool isOffscreen(const CollisionBox&) const; bool isInsideGrid(const CollisionBox&) const; diff --git a/src/mbgl/text/cross_tile_symbol_index.cpp b/src/mbgl/text/cross_tile_symbol_index.cpp index f88bab9d6f..b0c3511ce3 100644 --- a/src/mbgl/text/cross_tile_symbol_index.cpp +++ b/src/mbgl/text/cross_tile_symbol_index.cpp @@ -62,7 +62,7 @@ CrossTileSymbolLayerIndex::CrossTileSymbolLayerIndex() { } bool CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& tileID, SymbolBucket& bucket, uint32_t& maxCrossTileID) { - auto thisZoomIndexes = indexes[tileID.overscaledZ]; + const auto& thisZoomIndexes = indexes[tileID.overscaledZ]; auto previousIndex = thisZoomIndexes.find(tileID); if (previousIndex != thisZoomIndexes.end()) { if (previousIndex->second.bucketInstanceId == bucket.bucketInstanceId) { @@ -153,6 +153,10 @@ bool CrossTileSymbolIndex::addLayer(RenderSymbolLayer& symbolLayer) { auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); assert(dynamic_cast<SymbolBucket*>(bucket)); SymbolBucket& symbolBucket = *reinterpret_cast<SymbolBucket*>(bucket); + if (symbolBucket.bucketLeaderID != symbolLayer.getID()) { + // Only add this layer if it's the "group leader" for the bucket + continue; + } if (!symbolBucket.bucketInstanceId) { symbolBucket.bucketInstanceId = ++maxBucketInstanceId; diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 54b2b7539b..9883a1f456 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -49,17 +49,25 @@ void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatri if (!renderTile.tile.isRenderable()) { continue; } - - auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); + assert(dynamic_cast<GeometryTile*>(&renderTile.tile)); + GeometryTile& geometryTile = static_cast<GeometryTile&>(renderTile.tile); + + + auto bucket = geometryTile.getBucket(*symbolLayer.baseImpl); assert(dynamic_cast<SymbolBucket*>(bucket)); SymbolBucket& symbolBucket = *reinterpret_cast<SymbolBucket*>(bucket); + + if (symbolBucket.bucketLeaderID != symbolLayer.getID()) { + // Only place this layer if it's the "group leader" for the bucket + continue; + } auto& layout = symbolBucket.layout; const float pixelsToTileUnits = renderTile.id.pixelsToTileUnits(1, state.getZoom()); - const float scale = std::pow(2, state.getZoom() - renderTile.tile.id.overscaledZ); - const float textPixelRatio = (util::tileSize * renderTile.tile.id.overscaleFactor()) / util::EXTENT; + const float scale = std::pow(2, state.getZoom() - geometryTile.id.overscaledZ); + const float textPixelRatio = (util::tileSize * geometryTile.id.overscaleFactor()) / util::EXTENT; mat4 posMatrix; state.matrixFor(posMatrix, renderTile.id); @@ -76,7 +84,14 @@ void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatri layout.get<style::IconRotationAlignment>() == style::AlignmentType::Map, state, pixelsToTileUnits); - + + + // As long as this placement lives, we have to hold onto this bucket's + // matching FeatureIndex/data for querying purposes + retainedQueryData.emplace(std::piecewise_construct, + std::forward_as_tuple(symbolBucket.bucketInstanceId), + std::forward_as_tuple(symbolBucket.bucketInstanceId, geometryTile.getFeatureIndex(), geometryTile.id)); + placeLayerBucket(symbolBucket, posMatrix, textLabelPlaneMatrix, iconLabelPlaneMatrix, scale, textPixelRatio, showCollisionBoxes, seenCrossTileIDs, renderTile.tile.holdForFade()); } } @@ -150,11 +165,11 @@ void Placement::placeLayerBucket( } if (placeText) { - collisionIndex.insertFeature(symbolInstance.textCollisionFeature, bucket.layout.get<style::TextIgnorePlacement>()); + collisionIndex.insertFeature(symbolInstance.textCollisionFeature, bucket.layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId); } if (placeIcon) { - collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, bucket.layout.get<style::IconIgnorePlacement>()); + collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, bucket.layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId); } assert(symbolInstance.crossTileID != 0); @@ -220,6 +235,10 @@ void Placement::updateLayerOpacities(RenderSymbolLayer& symbolLayer) { auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); assert(dynamic_cast<SymbolBucket*>(bucket)); SymbolBucket& symbolBucket = *reinterpret_cast<SymbolBucket*>(bucket); + if (symbolBucket.bucketLeaderID != symbolLayer.getID()) { + // Only update opacities this layer if it's the "group leader" for the bucket + continue; + } updateBucketOpacities(symbolBucket, seenCrossTileIDs); } } @@ -305,6 +324,10 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& bucket.updateOpacity(); bucket.sortFeatures(state.getAngle()); + auto retainedData = retainedQueryData.find(bucket.bucketInstanceId); + if (retainedData != retainedQueryData.end()) { + retainedData->second.featureSortOrder = bucket.featureSortOrder; + } } float Placement::symbolFadeChange(TimePoint now) const { @@ -337,5 +360,13 @@ void Placement::setStale() { const CollisionIndex& Placement::getCollisionIndex() const { return collisionIndex; } + +const RetainedQueryData& Placement::getQueryData(uint32_t bucketInstanceId) const { + auto it = retainedQueryData.find(bucketInstanceId); + if (it == retainedQueryData.end()) { + throw std::runtime_error("Placement::getQueryData with unrecognized bucketInstanceId"); + } + return it->second; +} } // namespace mbgl diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp index 653ae352ed..0e1751b127 100644 --- a/src/mbgl/text/placement.hpp +++ b/src/mbgl/text/placement.hpp @@ -44,7 +44,21 @@ public: // visible right away. const bool skipFade; }; - + +struct RetainedQueryData { + uint32_t bucketInstanceId; + std::shared_ptr<FeatureIndex> featureIndex; + OverscaledTileID tileID; + std::shared_ptr<std::vector<size_t>> featureSortOrder; + + RetainedQueryData(uint32_t bucketInstanceId_, + std::shared_ptr<FeatureIndex> featureIndex_, + OverscaledTileID tileID_) + : bucketInstanceId(bucketInstanceId_) + , featureIndex(std::move(featureIndex_)) + , tileID(std::move(tileID_)) {} +}; + class Placement { public: Placement(const TransformState&, MapMode mapMode); @@ -59,6 +73,8 @@ public: bool stillRecent(TimePoint now) const; void setRecent(TimePoint now); void setStale(); + + const RetainedQueryData& getQueryData(uint32_t bucketInstanceId) const; private: void placeLayerBucket( @@ -85,6 +101,8 @@ private: TimePoint recentUntil; bool stale = false; + + std::unordered_map<uint32_t, RetainedQueryData> retainedQueryData; }; } // namespace mbgl diff --git a/src/mbgl/tile/custom_geometry_tile.cpp b/src/mbgl/tile/custom_geometry_tile.cpp index a2fefcfa9f..272a1594d4 100644 --- a/src/mbgl/tile/custom_geometry_tile.cpp +++ b/src/mbgl/tile/custom_geometry_tile.cpp @@ -3,7 +3,6 @@ #include <mbgl/renderer/query.hpp> #include <mbgl/renderer/tile_parameters.hpp> #include <mbgl/actor/scheduler.hpp> -#include <mbgl/style/filter_evaluator.hpp> #include <mbgl/util/string.hpp> #include <mbgl/tile/tile_observer.hpp> #include <mbgl/style/custom_tile_loader.hpp> diff --git a/src/mbgl/tile/geojson_tile.cpp b/src/mbgl/tile/geojson_tile.cpp index f211c03569..7a83da2267 100644 --- a/src/mbgl/tile/geojson_tile.cpp +++ b/src/mbgl/tile/geojson_tile.cpp @@ -2,7 +2,6 @@ #include <mbgl/tile/geojson_tile_data.hpp> #include <mbgl/renderer/query.hpp> #include <mbgl/renderer/tile_parameters.hpp> -#include <mbgl/style/filter_evaluator.hpp> namespace mbgl { diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index a99cb91d26..af28fe3963 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -16,7 +16,6 @@ #include <mbgl/storage/file_source.hpp> #include <mbgl/geometry/feature_index.hpp> #include <mbgl/map/transform_state.hpp> -#include <mbgl/style/filter_evaluator.hpp> #include <mbgl/util/logging.hpp> #include <mbgl/actor/scheduler.hpp> @@ -133,7 +132,7 @@ void GeometryTile::onLayout(LayoutResult result, const uint64_t resultCorrelatio buckets = std::move(result.buckets); - featureIndexPendingCommit = { std::move(result.featureIndex) }; + latestFeatureIndex = std::move(result.featureIndex); if (result.glyphAtlasImage) { glyphAtlasImage = std::move(*result.glyphAtlasImage); @@ -201,13 +200,15 @@ Bucket* GeometryTile::getBucket(const Layer::Impl& layer) const { return it->second.get(); } -void GeometryTile::commitFeatureIndex() { - // We commit our pending FeatureIndex when a global placement has run, - // synchronizing the global CollisionIndex with the latest buckets/FeatureIndex - if (featureIndexPendingCommit) { - featureIndex = std::move(*featureIndexPendingCommit); - featureIndexPendingCommit = nullopt; +float GeometryTile::getQueryPadding(const std::vector<const RenderLayer*>& layers) { + float queryPadding = 0; + for (const RenderLayer* layer : layers) { + auto bucket = getBucket(*layer->baseImpl); + if (bucket && bucket->hasData()) { + queryPadding = std::max(queryPadding, bucket->getQueryRadius(*layer)); + } } + return queryPadding; } void GeometryTile::queryRenderedFeatures( @@ -216,30 +217,26 @@ void GeometryTile::queryRenderedFeatures( const TransformState& transformState, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) { + const mat4& projMatrix) { if (!getData()) return; - // Determine the additional radius needed factoring in property functions - float additionalRadius = 0; - for (const RenderLayer* layer : layers) { - auto bucket = getBucket(*layer->baseImpl); - if (bucket) { - additionalRadius = std::max(additionalRadius, bucket->getQueryRadius(*layer)); - } - } - - featureIndex->query(result, - queryGeometry, - transformState.getAngle(), - util::tileSize * id.overscaleFactor(), - std::pow(2, transformState.getZoom() - id.overscaledZ), - options, - id.toUnwrapped(), - sourceID, - layers, - collisionIndex, - additionalRadius); + const float queryPadding = getQueryPadding(layers); + + mat4 posMatrix; + transformState.matrixFor(posMatrix, id.toUnwrapped()); + matrix::multiply(posMatrix, projMatrix, posMatrix); + + latestFeatureIndex->query(result, + queryGeometry, + transformState, + posMatrix, + util::tileSize * id.overscaleFactor(), + std::pow(2, transformState.getZoom() - id.overscaledZ), + options, + id.toUnwrapped(), + layers, + queryPadding * transformState.maxPitchScaleFactor()); } void GeometryTile::querySourceFeatures( diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index 418db4a0b2..af122474c2 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -5,7 +5,6 @@ #include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> #include <mbgl/util/feature.hpp> -#include <mbgl/util/throttler.hpp> #include <mbgl/actor/actor.hpp> #include <mbgl/geometry/feature_index.hpp> @@ -55,12 +54,14 @@ public: const TransformState&, const std::vector<const RenderLayer*>& layers, const RenderedQueryOptions& options, - const CollisionIndex& collisionIndex) override; + const mat4& projMatrix) override; void querySourceFeatures( std::vector<Feature>& result, const SourceQueryOptions&) override; + float getQueryPadding(const std::vector<const RenderLayer*>&) override; + void cancel() override; class LayoutResult { @@ -88,11 +89,11 @@ public: void markRenderedPreviously() override; void performedFadePlacement() override; - void commitFeatureIndex() override; + const std::shared_ptr<FeatureIndex> getFeatureIndex() const { return latestFeatureIndex; } protected: const GeometryTileData* getData() { - return featureIndex ? featureIndex->getData() : nullptr; + return latestFeatureIndex ? latestFeatureIndex->getData() : nullptr; } private: @@ -113,8 +114,7 @@ private: std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets; - optional<std::unique_ptr<FeatureIndex>> featureIndexPendingCommit; - std::unique_ptr<FeatureIndex> featureIndex; + std::shared_ptr<FeatureIndex> latestFeatureIndex; optional<AlphaImage> glyphAtlasImage; optional<PremultipliedImage> iconAtlasImage; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 1378ad5d3a..ca20c4b8ab 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -5,7 +5,6 @@ #include <mbgl/renderer/bucket_parameters.hpp> #include <mbgl/renderer/group_by_layout.hpp> #include <mbgl/style/filter.hpp> -#include <mbgl/style/filter_evaluator.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> #include <mbgl/renderer/buckets/symbol_bucket.hpp> @@ -441,8 +440,7 @@ void GeometryTileWorker::performSymbolLayout() { } symbolLayout->prepare(glyphMap, glyphAtlas.positions, - imageMap, imageAtlas.positions, - id, sourceID); + imageMap, imageAtlas.positions); } symbolLayoutsNeedPreparation = false; diff --git a/src/mbgl/tile/tile.cpp b/src/mbgl/tile/tile.cpp index 88db2ba07c..b95944f10e 100644 --- a/src/mbgl/tile/tile.cpp +++ b/src/mbgl/tile/tile.cpp @@ -38,7 +38,11 @@ void Tile::queryRenderedFeatures( const TransformState&, const std::vector<const RenderLayer*>&, const RenderedQueryOptions&, - const CollisionIndex&) {} + const mat4&) {} + +float Tile::getQueryPadding(const std::vector<const RenderLayer*>&) { + return 0; +} void Tile::querySourceFeatures( std::vector<Feature>&, diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 23365c6ae3..23d6864205 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -58,12 +58,14 @@ public: const TransformState&, const std::vector<const RenderLayer*>&, const RenderedQueryOptions& options, - const CollisionIndex&); + const mat4& projMatrix); virtual void querySourceFeatures( std::vector<Feature>& result, const SourceQueryOptions&); + virtual float getQueryPadding(const std::vector<const RenderLayer*>&); + void setTriedCache(); // Returns true when the tile source has received a first response, regardless of whether a load @@ -109,11 +111,6 @@ public: // and will have time to finish by the second placement. virtual void performedFadePlacement() {} - // FeatureIndexes are loaded asynchronously, but must be used with a CollisionIndex - // generated from the same data. Calling commitFeatureIndex signals the current - // CollisionIndex is up-to-date and allows us to start using the last loaded FeatureIndex - virtual void commitFeatureIndex() {} - void dumpDebugLogs() const; const OverscaledTileID id; diff --git a/src/mbgl/util/compression.cpp b/src/mbgl/util/compression.cpp index 30e813cbb8..ee3ebe7cae 100644 --- a/src/mbgl/util/compression.cpp +++ b/src/mbgl/util/compression.cpp @@ -1,6 +1,6 @@ #include <mbgl/util/compression.hpp> -#if defined(__QT__) && defined(_WINDOWS) +#if defined(__QT__) && defined(_WINDOWS) && !defined(__GNUC__) #include <QtZlib/zlib.h> #else #include <zlib.h> diff --git a/src/mbgl/util/http_header.cpp b/src/mbgl/util/http_header.cpp index 5921edfb14..8048dfe84a 100644 --- a/src/mbgl/util/http_header.cpp +++ b/src/mbgl/util/http_header.cpp @@ -8,6 +8,7 @@ #pragma GCC diagnostic ignored "-Wshadow" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshorten-64-to-32" +#pragma clang diagnostic ignored "-Wtautological-constant-compare" #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix_core.hpp> #include <boost/spirit/include/phoenix_operator.hpp> diff --git a/src/mbgl/util/i18n.cpp b/src/mbgl/util/i18n.cpp index 1fc13bfb7d..5530796915 100644 --- a/src/mbgl/util/i18n.cpp +++ b/src/mbgl/util/i18n.cpp @@ -1,4 +1,5 @@ -#include "i18n.hpp" +#include <mbgl/util/i18n.hpp> +#include <mbgl/util/utf.hpp> #include <algorithm> #include <map> @@ -65,7 +66,7 @@ DEFINE_IS_IN_UNICODE_BLOCK(UnifiedCanadianAboriginalSyllabics, 0x1400, 0x167F) // DEFINE_IS_IN_UNICODE_BLOCK(Hanunoo, 0x1720, 0x173F) // DEFINE_IS_IN_UNICODE_BLOCK(Buhid, 0x1740, 0x175F) // DEFINE_IS_IN_UNICODE_BLOCK(Tagbanwa, 0x1760, 0x177F) -// DEFINE_IS_IN_UNICODE_BLOCK(Khmer, 0x1780, 0x17FF) +DEFINE_IS_IN_UNICODE_BLOCK(Khmer, 0x1780, 0x17FF) // DEFINE_IS_IN_UNICODE_BLOCK(Mongolian, 0x1800, 0x18AF) DEFINE_IS_IN_UNICODE_BLOCK(UnifiedCanadianAboriginalSyllabicsExtended, 0x18B0, 0x18FF) // DEFINE_IS_IN_UNICODE_BLOCK(Limbu, 0x1900, 0x194F) @@ -581,6 +582,38 @@ std::u16string verticalizePunctuation(const std::u16string& input) { char16_t verticalizePunctuation(char16_t chr) { return verticalPunctuation.count(chr) ? verticalPunctuation.at(chr) : 0; } + +bool charInSupportedScript(char16_t chr) { + // This is a rough heuristic: whether we "can render" a script + // actually depends on the properties of the font being used + // and whether differences from the ideal rendering are considered + // semantically significant. + + // Even in Latin script, we "can't render" combinations such as the fi + // ligature, but we don't consider that semantically significant.n false; + if ((chr >= 0x0900 && chr <= 0x0DFF) || + // Main blocks for Indic scripts and Sinhala + (chr >= 0x0F00 && chr <= 0x109F) || + // Main blocks for Tibetan and Myanmar + isInKhmer(chr)) { + // These blocks cover common scripts that require + // complex text shaping, based on unicode script metadata: + // http://www.unicode.org/repos/cldr/trunk/common/properties/scriptMetadata.txt + // where "Web Rank <= 32" "Shaping Required = YES" + return false; + } + return true; +} + +bool isStringInSupportedScript(const std::string& input) { + auto u16string = util::utf8_to_utf16::convert(input); + for (char16_t chr : u16string) { + if (!charInSupportedScript(chr)) { + return false; + } + } + return true; +} } // namespace i18n } // namespace util diff --git a/src/mbgl/util/i18n.hpp b/src/mbgl/util/i18n.hpp index b3960c743c..a74215a134 100644 --- a/src/mbgl/util/i18n.hpp +++ b/src/mbgl/util/i18n.hpp @@ -72,6 +72,8 @@ std::u16string verticalizePunctuation(const std::u16string& input); @return The character’s specialized vertical form; 0 if not applicable. */ char16_t verticalizePunctuation(char16_t chr); + +bool isStringInSupportedScript(const std::string& input); } // namespace i18n } // namespace util diff --git a/src/mbgl/util/intersection_tests.cpp b/src/mbgl/util/intersection_tests.cpp index e6ce245c0e..780fce98f9 100644 --- a/src/mbgl/util/intersection_tests.cpp +++ b/src/mbgl/util/intersection_tests.cpp @@ -82,11 +82,16 @@ bool lineIntersectsBufferedLine(const GeometryCoordinates& lineA, const Geometry return false; } +bool polygonIntersectsBufferedPoint(const GeometryCoordinates& polygon, const GeometryCoordinate& point, float radius) { + if (polygonContainsPoint(polygon, point)) return true; + if (pointIntersectsBufferedLine(point, polygon, radius)) return true; + return false; +} + bool polygonIntersectsBufferedMultiPoint(const GeometryCoordinates& polygon, const GeometryCollection& rings, float radius) { for (auto& ring : rings) { for (auto& point : ring) { - if (polygonContainsPoint(polygon, point)) return true; - if (pointIntersectsBufferedLine(point, polygon, radius)) return true; + if (polygonIntersectsBufferedPoint(polygon, point, radius)) return true; } } return false; diff --git a/src/mbgl/util/intersection_tests.hpp b/src/mbgl/util/intersection_tests.hpp index 5bcb29c767..c105fe4dd0 100644 --- a/src/mbgl/util/intersection_tests.hpp +++ b/src/mbgl/util/intersection_tests.hpp @@ -9,6 +9,7 @@ bool polygonIntersectsBufferedMultiPoint(const GeometryCoordinates&, const Geome bool polygonIntersectsBufferedMultiLine(const GeometryCoordinates&, const GeometryCollection&, float radius); bool polygonIntersectsPolygon(const GeometryCoordinates&, const GeometryCoordinates&); bool polygonIntersectsMultiPolygon(const GeometryCoordinates&, const GeometryCollection&); +bool polygonIntersectsBufferedPoint(const GeometryCoordinates& polygon, const GeometryCoordinate& point, float radius); } // namespace util } // namespace mbgl diff --git a/src/mbgl/util/io.cpp b/src/mbgl/util/io.cpp index 6a6ed7b250..058cd0d202 100644 --- a/src/mbgl/util/io.cpp +++ b/src/mbgl/util/io.cpp @@ -20,7 +20,7 @@ void write_file(const std::string &filename, const std::string &data) { } std::string read_file(const std::string &filename) { - std::ifstream file(filename); + std::ifstream file(filename, std::ios::binary); if (file.good()) { std::stringstream data; data << file.rdbuf(); @@ -31,7 +31,7 @@ std::string read_file(const std::string &filename) { } optional<std::string> readFile(const std::string &filename) { - std::ifstream file(filename); + std::ifstream file(filename, std::ios::binary); if (file.good()) { std::stringstream data; data << file.rdbuf(); diff --git a/src/mbgl/util/throttler.cpp b/src/mbgl/util/throttler.cpp deleted file mode 100644 index 910810ce2f..0000000000 --- a/src/mbgl/util/throttler.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include <mbgl/util/throttler.hpp> - -namespace mbgl { -namespace util { - -Throttler::Throttler(Duration frequency_, std::function<void()>&& function_) - : frequency(frequency_) - , function(std::move(function_)) - , pendingInvocation(false) - , lastInvocation(TimePoint::min()) -{} - -void Throttler::invoke() { - if (pendingInvocation) { - return; - } - - Duration timeToNextInvocation = lastInvocation == TimePoint::min() - ? Duration::zero() - : (lastInvocation + frequency) - Clock::now(); - - if (timeToNextInvocation <= Duration::zero()) { - lastInvocation = Clock::now(); - function(); - } else { - pendingInvocation = true; - timer.start(timeToNextInvocation, Duration::zero(), [this]{ - pendingInvocation = false; - lastInvocation = Clock::now(); - function(); - }); - } -} - -} // namespace util -} // namespace mbgl diff --git a/src/mbgl/util/throttler.hpp b/src/mbgl/util/throttler.hpp deleted file mode 100644 index 175de7ccaf..0000000000 --- a/src/mbgl/util/throttler.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#include <mbgl/util/chrono.hpp> -#include <mbgl/util/timer.hpp> - -namespace mbgl { -namespace util { - -class Throttler { -public: - Throttler(Duration frequency, std::function<void()>&& function); - - void invoke(); -private: - Duration frequency; - std::function<void()> function; - - Timer timer; - bool pendingInvocation; - TimePoint lastInvocation; -}; - -} // namespace util -} // namespace mbgl diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index 39b562d811..488e6b88ce 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -2,13 +2,18 @@ #include <mbgl/util/constants.hpp> #include <mbgl/util/interpolate.hpp> #include <mbgl/map/transform_state.hpp> +#include <mbgl/util/tile_cover_impl.hpp> +#include <mbgl/util/tile_coordinate.hpp> #include <functional> +#include <list> namespace mbgl { namespace { +using ScanLine = const std::function<void(int32_t x0, int32_t x1, int32_t y)>; + // Taken from polymaps src/Layer.js // https://github.com/simplegeo/polymaps/blob/master/src/Layer.js#L333-L383 struct edge { @@ -27,8 +32,6 @@ struct edge { } }; -using ScanLine = const std::function<void(int32_t x0, int32_t x1, int32_t y)>; - // scan-line conversion static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) { double y0 = ::fmax(ymin, std::floor(e1.y0)); @@ -147,11 +150,11 @@ std::vector<UnwrappedTileID> tileCover(const LatLngBounds& bounds_, int32_t z) { { std::min(bounds_.north(), util::LATITUDE_MAX), bounds_.east() }); return tileCover( - TileCoordinate::fromLatLng(z, bounds.northwest()).p, - TileCoordinate::fromLatLng(z, bounds.northeast()).p, - TileCoordinate::fromLatLng(z, bounds.southeast()).p, - TileCoordinate::fromLatLng(z, bounds.southwest()).p, - TileCoordinate::fromLatLng(z, bounds.center()).p, + Projection::project(bounds.northwest(), z), + Projection::project(bounds.northeast(), z), + Projection::project(bounds.southeast(), z), + Projection::project(bounds.southwest(), z), + Projection::project(bounds.center(), z), z); } @@ -169,25 +172,80 @@ std::vector<UnwrappedTileID> tileCover(const TransformState& state, int32_t z) { z); } +std::vector<UnwrappedTileID> tileCover(const Geometry<double>& geometry, int32_t z) { + std::vector<UnwrappedTileID> result; + TileCover tc(geometry, z, true); + while (tc.hasNext()) { + result.push_back(*tc.next()); + }; + + return result; +} + // Taken from https://github.com/mapbox/sphericalmercator#xyzbbox-zoom-tms_style-srs // Computes the projected tiles for the lower left and upper right points of the bounds // and uses that to compute the tile cover count -uint64_t tileCount(const LatLngBounds& bounds, uint8_t zoom, uint16_t tileSize_){ - - auto sw = Projection::project(bounds.southwest().wrapped(), zoom, tileSize_); - auto ne = Projection::project(bounds.northeast().wrapped(), zoom, tileSize_); - - auto x1 = floor(sw.x/ tileSize_); - auto x2 = floor((ne.x - 1) / tileSize_); - auto y1 = floor(sw.y/ tileSize_); - auto y2 = floor((ne.y - 1) / tileSize_); - - auto minX = ::fmax(std::min(x1, x2), 0); - auto maxX = std::max(x1, x2); - auto minY = (std::pow(2, zoom) - 1) - std::max(y1, y2); - auto maxY = (std::pow(2, zoom) - 1) - ::fmax(std::min(y1, y2), 0); - - return (maxX - minX + 1) * (maxY - minY + 1); +uint64_t tileCount(const LatLngBounds& bounds, uint8_t zoom){ + if (zoom == 0) { + return 1; + } + auto sw = Projection::project(bounds.southwest(), zoom); + auto ne = Projection::project(bounds.northeast(), zoom); + auto maxTile = std::pow(2.0, zoom); + auto x1 = floor(sw.x); + auto x2 = ceil(ne.x) - 1; + auto y1 = util::clamp(floor(sw.y), 0.0, maxTile - 1); + auto y2 = util::clamp(floor(ne.y), 0.0, maxTile - 1); + + auto dx = x1 > x2 ? (maxTile - x1) + x2 : x2 - x1; + auto dy = y1 - y2; + return (dx + 1) * (dy + 1); +} + +uint64_t tileCount(const Geometry<double>& geometry, uint8_t z) { + uint64_t tileCount = 0; + + TileCover tc(geometry, z, true); + while (tc.next()) { + tileCount++; + }; + return tileCount; +} + +TileCover::TileCover(const LatLngBounds&bounds_, int32_t z) { + LatLngBounds bounds = LatLngBounds::hull( + { std::max(bounds_.south(), -util::LATITUDE_MAX), bounds_.west() }, + { std::min(bounds_.north(), util::LATITUDE_MAX), bounds_.east() }); + + if (bounds.isEmpty() || + bounds.south() > util::LATITUDE_MAX || + bounds.north() < -util::LATITUDE_MAX) { + bounds = LatLngBounds::world(); + } + + auto sw = Projection::project(bounds.southwest(), z); + auto ne = Projection::project(bounds.northeast(), z); + auto se = Projection::project(bounds.southeast(), z); + auto nw = Projection::project(bounds.northwest(), z); + + Polygon<double> p({ {sw, nw, ne, se, sw} }); + impl = std::make_unique<TileCover::Impl>(z, p, false); +} + +TileCover::TileCover(const Geometry<double>& geom, int32_t z, bool project/* = true*/) + : impl( std::make_unique<TileCover::Impl>(z, geom, project)) { +} + +TileCover::~TileCover() { + +} + +optional<UnwrappedTileID> TileCover::next() { + return impl->next(); +} + +bool TileCover::hasNext() { + return impl->hasNext(); } } // namespace util diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp index b2098b59b8..c953d764d2 100644 --- a/src/mbgl/util/tile_cover.hpp +++ b/src/mbgl/util/tile_cover.hpp @@ -2,9 +2,11 @@ #include <mbgl/tile/tile_id.hpp> #include <mbgl/style/types.hpp> -#include <mbgl/util/tile_coordinate.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/util/optional.hpp> #include <vector> +#include <memory> namespace mbgl { @@ -13,13 +15,31 @@ class LatLngBounds; namespace util { +// Helper class to stream tile-cover results per row +class TileCover { +public: + TileCover(const LatLngBounds&, int32_t z); + // When project == true, projects the geometry points to tile coordinates + TileCover(const Geometry<double>&, int32_t z, bool project = true); + ~TileCover(); + + optional<UnwrappedTileID> next(); + bool hasNext(); + +private: + class Impl; + std::unique_ptr<Impl> impl; +}; + int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize); std::vector<UnwrappedTileID> tileCover(const TransformState&, int32_t z); std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, int32_t z); +std::vector<UnwrappedTileID> tileCover(const Geometry<double>&, int32_t z); // Compute only the count of tiles needed for tileCover -uint64_t tileCount(const LatLngBounds&, uint8_t z, uint16_t tileSize); +uint64_t tileCount(const LatLngBounds&, uint8_t z); +uint64_t tileCount(const Geometry<double>&, uint8_t z); } // namespace util } // namespace mbgl diff --git a/src/mbgl/util/tile_cover_impl.cpp b/src/mbgl/util/tile_cover_impl.cpp new file mode 100644 index 0000000000..b3fc07f7dd --- /dev/null +++ b/src/mbgl/util/tile_cover_impl.cpp @@ -0,0 +1,365 @@ +#include <mbgl/util/tile_cover_impl.hpp> +#include <mbgl/util/tile_coordinate.hpp> + +#include <functional> +#include <cmath> +#include <assert.h> +#include <limits.h> +#include <algorithm> + +namespace mbgl { +namespace util { + +using PointList = std::vector<Point<double>>; + +struct TileSpan { + int32_t xmin, xmax; + bool winding; +}; + + +// Find the first local minimum going forward in the list. +void start_list_on_local_minimum(PointList& points) { + auto prev_pt = std::prev(points.end(), 2); + auto pt = points.begin(); + auto next_pt = std::next(pt); + while (pt != points.end()) { + if ((pt->y <= prev_pt->y) && + (pt->y < next_pt->y)) { + break; + } + prev_pt = pt; + pt++; + next_pt++; + if (next_pt == points.end()) { next_pt = std::next(points.begin()); } + } + //Re-close linear rings with first_pt = last_pt + if (points.back() == points.front()) { + points.pop_back(); + } + std::rotate(points.begin(), pt, points.end()); + points.push_back(*points.begin()); +} + +//Create a bound towards a local maximum point, starting from pt. +Bound create_bound_towards_maximum(PointList& points, PointList::iterator& pt) { + if (std::distance(pt, points.end()) < 2) { return {}; } + if (std::distance(pt, points.end()) == 2) { + Bound bnd; + if (pt->y < std::next(pt)->y) { + std::copy(pt, points.end(), std::back_inserter(bnd.points)); + bnd.winding = true; + } + else { + std::reverse_copy(pt, points.end(), std::back_inserter(bnd.points)); + bnd.winding = false; + } + pt = points.end(); + return bnd; + } + const auto begin = pt; + auto prev_pt = pt == points.begin() ? std::prev(points.end(), 2) : std::prev(pt); + auto next_pt = std::next(pt) == points.end() ? std::next(points.begin()) : std::next(pt); + while (pt != points.end()) { + if ((pt->y >= prev_pt->y) && + (pt->y > next_pt->y )) { + break; + } + prev_pt = pt; + pt++; + next_pt++; + if (next_pt == points.end()) { next_pt = std::next(points.begin()); } + } + + Bound bnd; + if (std::next(pt) == points.end()) { next_pt = points.end(); pt++; }; + bnd.points.reserve(static_cast<std::size_t>(std::distance(begin, next_pt))); + std::copy(begin, next_pt, std::back_inserter(bnd.points)); + bnd.winding = true; + return bnd; +} + +//Create a bound towards a local minimum point, starting from pt. +Bound create_bound_towards_minimum(PointList& points, PointList::iterator& pt) { + if (std::distance(pt, points.end()) < 2) { return {}; } + if (std::distance(pt, points.end()) == 2) { + Bound bnd; + if (pt->y < std::next(pt)->y) { + std::copy(pt, points.end(), std::back_inserter(bnd.points)); + bnd.winding = true; + } + else { + std::reverse_copy(pt, points.end(), std::back_inserter(bnd.points)); + bnd.winding = false; + } + pt = points.end(); + return bnd; + } + auto begin = pt; + auto prev_pt = pt == points.begin() ? std::prev(points.end(), 2) : std::prev(pt); + auto next_pt = std::next(pt) == points.end() ? std::next(points.begin()) : std::next(pt); + while (pt != points.end()) { + if ((pt->y <= prev_pt->y) && + (pt->y < next_pt->y)) { + break; + } + prev_pt = pt; + pt++; + next_pt++; + if (next_pt == points.end()) { next_pt = std::next(points.begin()); } + } + + Bound bnd; + if (std::next(pt) == points.end()) { next_pt = points.end(); pt++; }; + bnd.points.reserve(static_cast<std::size_t>(std::distance(begin, next_pt))); + //For bounds that start at a max, reverse copy so that all bounds start at a min + std::reverse_copy(begin, next_pt, std::back_inserter(bnd.points)); + bnd.winding = false; + return bnd; +} + +//Build a map of bounds and their starting Y tile coordinate. +void build_bounds_map(PointList& points, uint32_t maxTile, BoundsMap& et, bool closed = false) { + if (points.size() < 2) return; + //While traversing closed rings, start the bounds at a local minimum + if (closed) { + start_list_on_local_minimum(points); + } + + auto pointsIter = points.begin(); + while (pointsIter != points.end()) { + Bound to_max = create_bound_towards_maximum(points, pointsIter); + Bound to_min = create_bound_towards_minimum(points, pointsIter); + + if (to_max.points.size() > 0) { + // Projections may result in values beyond the bounds, clamp to max tile coordinates + const auto y = static_cast<uint32_t>(std::floor(clamp(to_max.points.front().y, 0.0, (double)maxTile))); + et[y].push_back(to_max); + } + if (to_min.points.size() > 0) { + const auto y = static_cast<uint32_t>(std::floor(clamp(to_min.points.front().y, 0.0, (double)maxTile))); + et[y].push_back(to_min); + } + } + assert(pointsIter == points.end()); +} + +void update_span(TileSpan& xp, double x) { + xp.xmin = std::min(xp.xmin, static_cast<int32_t>(std::floor(x))); + xp.xmax = std::max(xp.xmax, static_cast<int32_t>(std::ceil(x))); +} + +//Build a vector of X tile-coordinates spanned by each bound. +std::vector<TileSpan> scan_row(uint32_t y, Bounds& aet) { + std::vector<TileSpan> tile_range; + tile_range.reserve(aet.size()); + + for(Bound& b: aet) { + TileSpan xp = { INT_MAX, 0, b.winding }; + double x; + const auto numEdges = b.points.size() - 1; + assert(numEdges >= 1); + while (b.currentPoint < numEdges) { + x = b.interpolate(y); + update_span(xp, x); + + // If this edge ends beyond the current row, find the x-intercept where + // it exits the row + auto& p1 = b.points[b.currentPoint + 1]; + if (p1.y > y+1) { + x = b.interpolate(y+1); + update_span(xp, x); + break; + } else if(b.currentPoint == numEdges - 1) { + // For last edge, consider x-intercept at the end of the edge. + x = p1.x; + update_span(xp, x); + } + b.currentPoint++; + } + tile_range.push_back(xp); + } + // Erase bounds in the active table whose current edge ends inside this row, + // or there are no more edges + auto bound = aet.begin(); + while (bound != aet.end()) { + if ( bound->currentPoint == bound->points.size() - 1 && + bound->points[bound->currentPoint].y <= y+1) { + bound = aet.erase(bound); + } else { + bound++; + } + } + // Sort the X-extents of each crossing bound by x_min, x_max + std::sort(tile_range.begin(), tile_range.end(), [] (TileSpan& a, TileSpan& b) { + return std::tie(a.xmin, a.xmax) < std::tie(b.xmin, b.xmax); + }); + + return tile_range; +} + +struct BuildBoundsMap { + int32_t zoom; + bool project = false; + BuildBoundsMap(int32_t z, bool p): zoom(z), project(p) {} + + void buildTable(const std::vector<Point<double>>& points, BoundsMap& et, bool closed = false) const { + PointList projectedPoints; + if (project) { + projectedPoints.reserve(points.size()); + for(const auto&p : points) { + projectedPoints.push_back( + Projection::project(LatLng{ p.y, p.x }, zoom)); + } + } else { + projectedPoints.insert(projectedPoints.end(), points.begin(), points.end()); + } + build_bounds_map(projectedPoints, 1 << zoom, et, closed); + } + + void buildPolygonTable(const Polygon<double>& polygon, BoundsMap& et) const { + for(const auto&ring : polygon) { + buildTable(ring, et, true); + } + } + BoundsMap operator()(const Point<double>&p) const { + Bound bnd; + auto point = p; + if(project) { + point = Projection::project(LatLng{p.y, p.x}, zoom); + } + bnd.points.insert(bnd.points.end(), 2, point); + bnd.winding = false; + BoundsMap et; + const auto y = static_cast<uint32_t>(std::floor(clamp(point.y, 0.0, (double)(1 << zoom)))); + et[y].push_back(bnd); + return et; + } + + BoundsMap operator()(const MultiPoint<double>& points) const { + BoundsMap et; + for (const Point<double>& p: points) { + Bound bnd; + auto point = p; + if(project) { + point = Projection::project(LatLng{p.y, p.x}, zoom); + } + bnd.points.insert(bnd.points.end(), 2, point); + bnd.winding = false; + const auto y = static_cast<uint32_t>(std::floor(clamp(point.y, 0.0, (double)(1 << zoom)))); + et[y].push_back(bnd); + } + return et; + } + + BoundsMap operator()(const LineString<double>& lines) const { + BoundsMap et; + buildTable(lines, et); + return et; + } + + BoundsMap operator()(const MultiLineString<double>& lines) const { + BoundsMap et; + for(const auto&line : lines) { + buildTable(line, et); + } + return et; + } + + BoundsMap operator()(const Polygon<double>& polygon) const { + BoundsMap et; + buildPolygonTable(polygon, et); + return et; + } + + BoundsMap operator()(const MultiPolygon<double>& polygons) const { + BoundsMap et; + for(const auto& polygon: polygons) { + buildPolygonTable(polygon, et); + } + return et; + } + + BoundsMap operator()(const mapbox::geometry::geometry_collection<double>&) const { + return {}; + } +}; + +TileCover::Impl::Impl(int32_t z, const Geometry<double>& geom, bool project) + : zoom(z) { + ToFeatureType toFeatureType; + isClosed = apply_visitor(toFeatureType, geom) == FeatureType::Polygon; + + BuildBoundsMap toBoundsMap(z, project); + boundsMap = apply_visitor(toBoundsMap, geom); + if (boundsMap.size() == 0) return; + + //Iniitalize the active edge table, and current row span + currentBounds = boundsMap.begin(); + tileY = 0; + nextRow(); + if (tileXSpans.empty()) return; + tileX = tileXSpans.front().first; +} + +void TileCover::Impl::nextRow() { + // Update AET for next row + if (currentBounds != boundsMap.end()) { + if (activeBounds.size() == 0 && currentBounds->first > tileY) { + //For multi-geoms: use the next row with an edge table starting point + tileY = currentBounds->first; + } + if (tileY == currentBounds->first) { + + std::move(currentBounds->second.begin(), currentBounds->second.end(), std::back_inserter(activeBounds)); + currentBounds++; + } + } + //Scan aet and update currenRange with x_min, x_max pairs + auto xps = util::scan_row(tileY, activeBounds); + if (xps.size() == 0) { + return; + } + + auto x_min = xps[0].xmin; + auto x_max = xps[0].xmax; + int32_t nzRule = xps[0].winding ? 1 : -1; + for (size_t i = 1; i < xps.size(); i++) { + auto xp = xps[i]; + if (!(isClosed && nzRule != 0)) { + if (xp.xmin > x_max && xp.xmax >= x_max) { + tileXSpans.emplace(x_min, x_max); + x_min = xp.xmin; + } + } + nzRule += xp.winding ? 1 : -1; + x_max = std::max(x_min, xp.xmax); + } + tileXSpans.emplace(x_min, x_max); +} + +bool TileCover::Impl::hasNext() const { + return (!tileXSpans.empty() && tileX < tileXSpans.front().second && tileY < (1u << zoom)); +} + +optional<UnwrappedTileID> TileCover::Impl::next() { + if (!hasNext()) return {}; + + const auto x = tileX; + const auto y = tileY; + tileX++; + if (tileX >= tileXSpans.front().second) { + tileXSpans.pop(); + if(tileXSpans.empty()) { + tileY++; + nextRow(); + } + if (!tileXSpans.empty()) { + tileX = tileXSpans.front().first; + } + } + return UnwrappedTileID(zoom, x, y); +} + +} // namespace util +} // namespace mbgl diff --git a/src/mbgl/util/tile_cover_impl.hpp b/src/mbgl/util/tile_cover_impl.hpp new file mode 100644 index 0000000000..7c16718984 --- /dev/null +++ b/src/mbgl/util/tile_cover_impl.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include <mbgl/util/tile_cover.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/util/optional.hpp> + +#include <vector> +#include <map> +#include <queue> + +namespace mbgl { + +class TransformState; +class LatLngBounds; + +namespace util { + +struct Bound; + +using Bounds = std::vector<Bound>; +using BoundsMap = std::map<uint32_t, Bounds>; + +// A chain of points from a local minimum to a local maximum. `winding` indicates +// the direction of the original geometry. +struct Bound { + std::vector<Point<double>> points; + size_t currentPoint = 0; + bool winding = false; + + Bound() = default; + Bound(const Bound& rhs) { + points = rhs.points; + currentPoint = rhs.currentPoint; + winding = rhs.winding; + } + Bound& operator=(Bound&& rhs) { + points = std::move(rhs.points); + currentPoint = rhs.currentPoint; + winding = rhs.winding; + return *this; + } + + // Compute the interpolated x coordinate at y for the current edge + double interpolate(uint32_t y) { + const auto& p0 = points[currentPoint]; + const auto& p1 = points[currentPoint + 1]; + + const auto dx = p1.x - p0.x; + const auto dy = p1.y - p0.y; + auto x = p0.x; + if (dx == 0) { + return x; + } else if (dy == 0){ + return y <= p0.y ? p0.x : p1.x; + } + if (y < p0.y) return x; + if (y > p1.y) return p1.x; + x = (dx / dy) * (y - p0.y) + p0.x; + return x; + } +}; + +class TileCover::Impl { +public: + Impl(int32_t z, const Geometry<double>& geom, bool project = true); + ~Impl() = default; + + optional<UnwrappedTileID> next(); + bool hasNext() const; + +private: + using TileSpans = std::queue<std::pair<int32_t, int32_t>>; + + void nextRow(); + + const int32_t zoom; + bool isClosed; + + BoundsMap boundsMap; + BoundsMap::iterator currentBounds; + // List of bounds that begin at or before `tileY` + Bounds activeBounds; + + TileSpans tileXSpans; + uint32_t tileY; + int32_t tileX; +}; + +} // namespace util +} // namespace mbgl diff --git a/src/mbgl/util/tiny_sdf.cpp b/src/mbgl/util/tiny_sdf.cpp index 60839357d5..6edcd83bc2 100644 --- a/src/mbgl/util/tiny_sdf.cpp +++ b/src/mbgl/util/tiny_sdf.cpp @@ -95,7 +95,7 @@ AlphaImage transformRasterToSDF(const AlphaImage& rasterInput, double radius, do for (uint32_t i = 0; i < size; i++) { double distance = gridOuter[i] - gridInner[i]; - sdf.data[i] = std::max(0l, std::min(255l, std::lround(255.0 - 255.0 * (distance / radius + cutoff)))); + sdf.data[i] = std::max(0l, std::min(255l, ::lround(255.0 - 255.0 * (distance / radius + cutoff)))); } return sdf; diff --git a/src/mbgl/util/token.hpp b/src/mbgl/util/token.hpp index 149661e47e..dea12f9412 100644 --- a/src/mbgl/util/token.hpp +++ b/src/mbgl/util/token.hpp @@ -1,5 +1,7 @@ #pragma once +#include <mbgl/util/optional.hpp> + #include <map> #include <string> #include <algorithm> @@ -25,7 +27,14 @@ std::string replaceTokens(const std::string &source, const Lookup &lookup) { if (pos != end) { for (brace++; brace != end && tokenReservedChars.find(*brace) == std::string::npos; brace++); if (brace != end && *brace == '}') { - result.append(lookup({ pos + 1, brace })); + std::string key { pos + 1, brace }; + if (optional<std::string> replacement = lookup(key)) { + result.append(*replacement); + } else { + result.append("{"); + result.append(key); + result.append("}"); + } pos = brace + 1; } else { result.append(pos, brace); diff --git a/src/mbgl/util/url.cpp b/src/mbgl/util/url.cpp index 1f6dab9639..a4263502ef 100644 --- a/src/mbgl/util/url.cpp +++ b/src/mbgl/util/url.cpp @@ -130,7 +130,7 @@ Path::Path(const std::string& str, const size_t pos, const size_t count) } std::string transformURL(const std::string& tpl, const std::string& str, const URL& url) { - auto result = util::replaceTokens(tpl, [&](const std::string& token) -> std::string { + auto result = util::replaceTokens(tpl, [&](const std::string& token) -> optional<std::string> { if (token == "path") { return str.substr(url.path.first, url.path.second); } else if (token == "domain") { @@ -146,8 +146,9 @@ std::string transformURL(const std::string& tpl, const std::string& str, const U } else if (token == "extension") { const Path path(str, url.path.first, url.path.second); return str.substr(path.extension.first, path.extension.second); + } else { + return {}; } - return ""; }); // Append the query string if it exists. |