#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { // Maps vertex range to feature index struct FeatureVertexRange { std::size_t featureIndex; std::size_t start; std::size_t end; }; using FeatureVertexRangeMap = std::map>; /* ZoomInterpolatedAttribute is a 'compound' attribute, representing two values of the the base attribute Attr. These two values are provided to the shader to allow interpolation between zoom levels, without the need to repopulate vertex buffers each frame as the map is being zoomed. */ template using ZoomInterpolatedAttributeType = gfx::AttributeType; inline std::array attributeValue(float v) { return {{ v }}; } /* * Pack a pair of values, interpreted as uint8's, into a single float. * Used to conserve vertex attributes. Values are unpacked in the vertex * shader using the `unpack_float()` function, defined in _prelude.vertex.glsl. */ template inline uint16_t packUint8Pair(T a, T b) { return static_cast(a) * 256 + static_cast(b); } /* Encode a four-component color value into a pair of floats. Since csscolorparser uses 8-bit precision for each color component, for each float we use the upper 8 bits for one component (e.g. (color.r * 255) * 256), and the lower 8 for another. Also note that colors come in as floats 0..1, so we scale by 255. */ inline std::array attributeValue(const Color& color) { return {{ static_cast(packUint8Pair(255 * color.r, 255 * color.g)), static_cast(packUint8Pair(255 * color.b, 255 * color.a)) }}; } template std::array zoomInterpolatedAttributeValue(const std::array& min, const std::array& max) { std::array result; for (size_t i = 0; i < N; i++) { result[i] = min[i]; result[i+N] = max[i]; } return result; } /* PaintPropertyBinder is an abstract class serving as the interface definition for the strategy used for constructing, uploading, and binding paint property data as GLSL attributes. It has three concrete subclasses, one for each of the three strategies we use: * For _constant_ properties -- those whose value is a constant, or the constant result of evaluating a camera function at a particular camera position -- we don't need a vertex buffer, and instead use a uniform. * For source functions, we use a vertex buffer with a single attribute value, the evaluated result of the source function for the given feature. * For composite functions, we use a vertex buffer with two attributes: min and max values covering the range of zooms at which we expect the tile to be displayed. These values are calculated by evaluating the composite function for the given feature at strategically chosen zoom levels. In addition to this attribute data, we also use a uniform value which the shader uses to interpolate between the min and max value at the final displayed zoom level. The use of a uniform allows us to cheaply update the value on every frame. Note that the shader source varies depending on whether we're using a uniform or attribute. Like GL JS, we dynamically compile shaders at runtime to accomodate this. */ template class PaintPropertyBinder { public: virtual ~PaintPropertyBinder() = default; virtual void populateVertexVector(const GeometryTileFeature& feature, std::size_t length, std::size_t index, const ImagePositions&, const optional&, const CanonicalTileID& canonical, const style::expression::Value&) = 0; virtual void updateVertexVectors(const FeatureStates&, const GeometryTileLayer&, const ImagePositions&) {} virtual void updateVertexVector(std::size_t, std::size_t, const GeometryTileFeature&, const FeatureState&) = 0; virtual void upload(gfx::UploadPass&) = 0; virtual void setPatternParameters(const optional&, const optional&, const CrossfadeParameters&) = 0; virtual std::tuple>...> attributeBinding(const PossiblyEvaluatedType& currentValue) const = 0; virtual std::tuple...> interpolationFactor(float currentZoom) const = 0; virtual std::tuple...> uniformValue(const PossiblyEvaluatedType& currentValue) const = 0; static std::unique_ptr create(const PossiblyEvaluatedType& value, float zoom, T defaultValue); PaintPropertyStatistics statistics; }; template class ConstantPaintPropertyBinder : public PaintPropertyBinder, A> { public: ConstantPaintPropertyBinder(T constant_) : constant(std::move(constant_)) { } void populateVertexVector(const GeometryTileFeature&, std::size_t, std::size_t, const ImagePositions&, const optional&, const CanonicalTileID&, const style::expression::Value&) override {} void updateVertexVector(std::size_t, std::size_t, const GeometryTileFeature&, const FeatureState&) override {} void upload(gfx::UploadPass&) override {} void setPatternParameters(const optional&, const optional&, const CrossfadeParameters&) override {}; std::tuple> attributeBinding(const PossiblyEvaluatedPropertyValue&) const override { return {}; } std::tuple interpolationFactor(float) const override { return std::tuple { 0.0f }; } std::tuple uniformValue(const PossiblyEvaluatedPropertyValue& currentValue) const override { return std::tuple { currentValue.constantOr(constant) }; } private: T constant; }; template class ConstantCrossFadedPaintPropertyBinder final : public PaintPropertyBinder,PossiblyEvaluatedPropertyValue>, As...> { public: ConstantCrossFadedPaintPropertyBinder(Faded constant_) : constant(std::move(constant_)), constantPatternPositions({}) { } void populateVertexVector(const GeometryTileFeature&, std::size_t, std::size_t, const ImagePositions&, const optional&, const CanonicalTileID&, const style::expression::Value&) override {} void updateVertexVector(std::size_t, std::size_t, const GeometryTileFeature&, const FeatureState&) override {} void upload(gfx::UploadPass&) override {} void setPatternParameters(const optional& posA, const optional& posB, const CrossfadeParameters&) override { if (!posA || !posB) { return; } else { constantPatternPositions = std::tuple, std::array> { posB->tlbr(), posA->tlbr() }; } } std::tuple, optional> attributeBinding(const PossiblyEvaluatedPropertyValue>&) const override { return {}; } std::tuple interpolationFactor(float) const override { return std::tuple { 0.0f, 0.0f }; } std::tuple, std::array> uniformValue(const PossiblyEvaluatedPropertyValue>&) const override { return constantPatternPositions; } private: Faded constant; std::tuple, std::array> constantPatternPositions; }; template class SourceFunctionPaintPropertyBinder final : public PaintPropertyBinder, A> { public: using BaseAttributeType = A; using BaseVertex = gfx::VertexType; using AttributeType = ZoomInterpolatedAttributeType; SourceFunctionPaintPropertyBinder(style::PropertyExpression expression_, T defaultValue_) : expression(std::move(expression_)), defaultValue(std::move(defaultValue_)) { } void setPatternParameters(const optional&, const optional&, const CrossfadeParameters&) override {}; void populateVertexVector(const GeometryTileFeature& feature, std::size_t length, std::size_t index, const ImagePositions&, const optional&, const CanonicalTileID& canonical, const style::expression::Value& formattedSection) override { using style::expression::EvaluationContext; auto evaluated = expression.evaluate( EvaluationContext(&feature).withFormattedSection(&formattedSection).withCanonicalTileID(&canonical), defaultValue); this->statistics.add(evaluated); auto value = attributeValue(evaluated); auto elements = vertexVector.elements(); for (std::size_t i = elements; i < length; ++i) { vertexVector.emplace_back(BaseVertex { value }); } optional idStr = featureIDtoString(feature.getID()); if (idStr) { featureMap[*idStr].emplace_back(FeatureVertexRange{index, elements, length}); } } void updateVertexVectors(const FeatureStates& states, const GeometryTileLayer& layer, const ImagePositions&) override { for (const auto& it : states) { const auto positions = featureMap.find(it.first); if (positions == featureMap.end()) { continue; } for (const auto& pos : positions->second) { std::unique_ptr feature = layer.getFeature(pos.featureIndex); if (feature) { updateVertexVector(pos.start, pos.end, *feature, it.second); } } } } void updateVertexVector(std::size_t start, std::size_t end, const GeometryTileFeature& feature, const FeatureState& state) override { using style::expression::EvaluationContext; auto evaluated = expression.evaluate(EvaluationContext(&feature).withFeatureState(&state), defaultValue); this->statistics.add(evaluated); auto value = attributeValue(evaluated); for (std::size_t i = start; i < end; ++i) { vertexVector.at(i) = BaseVertex{value}; } } void upload(gfx::UploadPass& uploadPass) override { vertexBuffer = uploadPass.createVertexBuffer(std::move(vertexVector)); } std::tuple> attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { return {}; } else { return std::tuple>{ gfx::attributeBinding(*vertexBuffer) }; } } std::tuple interpolationFactor(float) const override { return std::tuple { 0.0f }; } std::tuple uniformValue(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { return std::tuple{ *currentValue.constant() }; } else { // Uniform values for vertex attribute arrays are unused. return {}; } } private: style::PropertyExpression expression; T defaultValue; gfx::VertexVector vertexVector; optional> vertexBuffer; FeatureVertexRangeMap featureMap; }; template class CompositeFunctionPaintPropertyBinder final : public PaintPropertyBinder, A> { public: using AttributeType = ZoomInterpolatedAttributeType; using AttributeValue = typename AttributeType::Value; using Vertex = gfx::VertexType; CompositeFunctionPaintPropertyBinder(style::PropertyExpression expression_, float zoom, T defaultValue_) : expression(std::move(expression_)), defaultValue(std::move(defaultValue_)), zoomRange({zoom, zoom + 1}) { } void setPatternParameters(const optional&, const optional&, const CrossfadeParameters&) override {}; void populateVertexVector(const GeometryTileFeature& feature, std::size_t length, std::size_t index, const ImagePositions&, const optional&, const CanonicalTileID& canonical, const style::expression::Value& formattedSection) override { using style::expression::EvaluationContext; Range range = { expression.evaluate(EvaluationContext(zoomRange.min, &feature) .withFormattedSection(&formattedSection) .withCanonicalTileID(&canonical), defaultValue), expression.evaluate(EvaluationContext(zoomRange.max, &feature) .withFormattedSection(&formattedSection) .withCanonicalTileID(&canonical), defaultValue), }; this->statistics.add(range.min); this->statistics.add(range.max); AttributeValue value = zoomInterpolatedAttributeValue( attributeValue(range.min), attributeValue(range.max)); auto elements = vertexVector.elements(); for (std::size_t i = elements; i < length; ++i) { vertexVector.emplace_back(Vertex { value }); } optional idStr = featureIDtoString(feature.getID()); if (idStr) { featureMap[*idStr].emplace_back(FeatureVertexRange{index, elements, length}); } } void updateVertexVectors(const FeatureStates& states, const GeometryTileLayer& layer, const ImagePositions&) override { for (const auto& it : states) { const auto positions = featureMap.find(it.first); if (positions == featureMap.end()) { continue; } for (const auto& pos : positions->second) { std::unique_ptr feature = layer.getFeature(pos.featureIndex); if (feature) { updateVertexVector(pos.start, pos.end, *feature, it.second); } } } } void updateVertexVector(std::size_t start, std::size_t end, const GeometryTileFeature& feature, const FeatureState& state) override { using style::expression::EvaluationContext; Range range = { expression.evaluate(EvaluationContext(zoomRange.min, &feature, &state), defaultValue), expression.evaluate(EvaluationContext(zoomRange.max, &feature, &state), defaultValue), }; this->statistics.add(range.min); this->statistics.add(range.max); AttributeValue value = zoomInterpolatedAttributeValue(attributeValue(range.min), attributeValue(range.max)); for (std::size_t i = start; i < end; ++i) { vertexVector.at(i) = Vertex{value}; } } void upload(gfx::UploadPass& uploadPass) override { vertexBuffer = uploadPass.createVertexBuffer(std::move(vertexVector)); } std::tuple> attributeBinding(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { return {}; } else { return std::tuple>{ gfx::attributeBinding(*vertexBuffer) }; } } std::tuple interpolationFactor(float currentZoom) const override { const float possiblyRoundedZoom = expression.useIntegerZoom ? std::floor(currentZoom) : currentZoom; return std::tuple{ ::fmax(0.0, ::fmin(1.0, expression.interpolationFactor(zoomRange, possiblyRoundedZoom)))}; } std::tuple uniformValue(const PossiblyEvaluatedPropertyValue& currentValue) const override { if (currentValue.isConstant()) { return std::tuple { *currentValue.constant() }; } else { // Uniform values for vertex attribute arrays are unused. return {}; } } private: style::PropertyExpression expression; T defaultValue; Range zoomRange; gfx::VertexVector vertexVector; optional> vertexBuffer; FeatureVertexRangeMap featureMap; }; template class CompositeCrossFadedPaintPropertyBinder final : public PaintPropertyBinder, PossiblyEvaluatedPropertyValue>, A1, A2> { public: using AttributeType = ZoomInterpolatedAttributeType; using AttributeType2 = ZoomInterpolatedAttributeType; using BaseAttributeType = A1; using BaseAttributeType2 = A2; using Vertex = gfx::VertexType; using Vertex2 = gfx::VertexType; CompositeCrossFadedPaintPropertyBinder(style::PropertyExpression expression_, float zoom, T defaultValue_) : expression(std::move(expression_)), defaultValue(std::move(defaultValue_)), zoomRange({zoom, zoom + 1}) { } void setPatternParameters(const optional&, const optional&, const CrossfadeParameters& crossfade_) override { crossfade = crossfade_; }; void populateVertexVector(const GeometryTileFeature&, std::size_t length, std::size_t /* index */, const ImagePositions& patternPositions, const optional& patternDependencies, const CanonicalTileID&, const style::expression::Value&) override { if (!patternDependencies || patternDependencies->mid.empty()) { // Unlike other propperties with expressions that evaluate to null, the default value for `*-pattern` properties is an empty // string and will not have a valid entry in patternPositions. We still need to populate the attribute buffers to avoid crashes // when we try to draw the layer because we don't know at draw time if all features were evaluated to valid pattern dependencies. for (std::size_t i = zoomInVertexVector.elements(); i < length; ++i) { patternToVertexVector.emplace_back(Vertex { std::array{{0, 0, 0, 0}} }); zoomInVertexVector.emplace_back(Vertex2 { std::array{{0, 0, 0, 0}} } ); zoomOutVertexVector.emplace_back(Vertex2 { std::array{{0, 0, 0, 0}} }); } } else if (!patternPositions.empty()) { const auto min = patternPositions.find(patternDependencies->min); const auto mid = patternPositions.find(patternDependencies->mid); const auto max = patternPositions.find(patternDependencies->max); const auto end = patternPositions.end(); if (min == end || mid == end || max == end) return; const ImagePosition imageMin = min->second; const ImagePosition imageMid = mid->second; const ImagePosition imageMax = max->second; for (std::size_t i = zoomInVertexVector.elements(); i < length; ++i) { patternToVertexVector.emplace_back(Vertex { imageMid.tlbr() }); zoomInVertexVector.emplace_back(Vertex2 { imageMin.tlbr() }); zoomOutVertexVector.emplace_back(Vertex2 { imageMax.tlbr() }); } } } void updateVertexVector(std::size_t, std::size_t, const GeometryTileFeature&, const FeatureState&) override {} void upload(gfx::UploadPass& uploadPass) override { if (!patternToVertexVector.empty()) { assert(!zoomInVertexVector.empty()); assert(!zoomOutVertexVector.empty()); patternToVertexBuffer = uploadPass.createVertexBuffer(std::move(patternToVertexVector)); zoomInVertexBuffer = uploadPass.createVertexBuffer(std::move(zoomInVertexVector)); zoomOutVertexBuffer = uploadPass.createVertexBuffer(std::move(zoomOutVertexVector)); } } std::tuple, optional> attributeBinding(const PossiblyEvaluatedPropertyValue>& currentValue) const override { if (currentValue.isConstant()) { return {}; } else { if (patternToVertexBuffer) { assert(zoomInVertexBuffer); assert(zoomOutVertexBuffer); return std::tuple, optional>{ gfx::attributeBinding(*patternToVertexBuffer), gfx::attributeBinding(crossfade.fromScale == 2 ? *zoomInVertexBuffer : *zoomOutVertexBuffer) }; } return std::tuple, optional>{{}, {}}; } } std::tuple interpolationFactor(float) const override { return std::tuple { 0.0f, 0.0f }; } std::tuple, std::array> uniformValue(const PossiblyEvaluatedPropertyValue>& ) const override { // Uniform values for vertex attribute arrays are unused. return {}; } private: style::PropertyExpression expression; T defaultValue; Range zoomRange; gfx::VertexVector patternToVertexVector; gfx::VertexVector zoomInVertexVector; gfx::VertexVector zoomOutVertexVector; optional> patternToVertexBuffer; optional> zoomInVertexBuffer; optional> zoomOutVertexBuffer; CrossfadeParameters crossfade; }; template struct CreateBinder { template static std::unique_ptr> create(const PossiblyEvaluatedType& value, float zoom, T defaultValue) { return value.match( [&] (const T& constant) -> std::unique_ptr> { return std::make_unique>(constant); }, [&] (const style::PropertyExpression& expression) -> std::unique_ptr> { if (expression.isZoomConstant()) { return std::make_unique>(expression, defaultValue); } else { return std::make_unique>(expression, zoom, defaultValue); } } ); } }; template struct CreateBinder>> { template static std::unique_ptr, PossiblyEvaluatedPropertyValue>, A1, A2>> create(const PossiblyEvaluatedPropertyValue>& value, float zoom, T defaultValue) { return value.match( [&] (const Faded& constant) -> std::unique_ptr, PossiblyEvaluatedPropertyValue>, A1, A2>> { return std::make_unique>(constant); }, [&] (const style::PropertyExpression& expression) -> std::unique_ptr, PossiblyEvaluatedPropertyValue>, A1, A2>> { return std::make_unique>(expression, zoom, defaultValue); } ); } }; template std::unique_ptr> PaintPropertyBinder::create(const PossiblyEvaluatedType& value, float zoom, T defaultValue) { return CreateBinder::template create(value, zoom, defaultValue); } template struct ZoomInterpolatedAttribute : public Attr { using Type = ZoomInterpolatedAttributeType; }; template struct InterpolationUniform { using Value = float; static constexpr auto name() { return concat_literals<&Attr::name, &string_literal<'_', 't'>::value>::value(); } }; template class PaintPropertyBinders; template class PaintPropertyBinders> { private: template struct Detail; template struct Detail> { using Binder = PaintPropertyBinder; using ZoomInterpolatedAttributeList = TypeList...>; using InterpolationUniformList = TypeList...>; }; template using Property = Detail; public: template using Binder = typename Property

::Binder; using Binders = IndexedTuple< TypeList, TypeList>...>>; template PaintPropertyBinders(const EvaluatedProperties& properties, float z) : binders(Binder::create(properties.template get(), z, Ps::defaultValue())...) { (void)z; // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56958 } PaintPropertyBinders(PaintPropertyBinders&&) noexcept = default; PaintPropertyBinders(const PaintPropertyBinders&) = delete; void populateVertexVectors(const GeometryTileFeature& feature, std::size_t length, std::size_t index, const ImagePositions& patternPositions, const optional& patternDependencies, const CanonicalTileID& canonical, const style::expression::Value& formattedSection = {}) { util::ignore({(binders.template get()->populateVertexVector( feature, length, index, patternPositions, patternDependencies, canonical, formattedSection), 0)...}); } void updateVertexVectors(const FeatureStates& states, const GeometryTileLayer& layer, const ImagePositions& imagePositions) { util::ignore({(binders.template get()->updateVertexVectors(states, layer, imagePositions), 0)...}); } void setPatternParameters(const optional& posA, const optional& posB, const CrossfadeParameters& crossfade) const { util::ignore({ (binders.template get()->setPatternParameters(posA, posB, crossfade), 0)... }); } void upload(gfx::UploadPass& uploadPass) { util::ignore({ (binders.template get()->upload(uploadPass), 0)... }); } template using ZoomInterpolatedAttributeList = typename Property

::ZoomInterpolatedAttributeList; template using InterpolationUniformList = typename Property

::InterpolationUniformList; using AttributeList = TypeListConcat...>; using AttributeBindings = gfx::AttributeBindings; template AttributeBindings attributeBindings(const EvaluatedProperties& currentProperties) const { return AttributeBindings { std::tuple_cat( binders.template get()->attributeBinding(currentProperties.template get())... ) }; } using UniformList = TypeListConcat..., typename Ps::UniformList...>; using UniformValues = gfx::UniformValues; template UniformValues uniformValues(float currentZoom, EvaluatedProperties& currentProperties) const { (void)currentZoom; // Workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56958 return UniformValues ( std::tuple_cat( // interpolation uniform values binders.template get()->interpolationFactor(currentZoom)..., // uniform values binders.template get()->uniformValue(currentProperties.template get())...) ); } template const auto& statistics() const { return binders.template get

()->statistics; } private: Binders binders; }; } // namespace mbgl