diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index fc476fcd15f..d912392e156 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -97,6 +97,8 @@ set(MBGL_CORE_FILES src/mbgl/layout/symbol_instance.hpp src/mbgl/layout/symbol_layout.cpp src/mbgl/layout/symbol_layout.hpp + src/mbgl/layout/symbol_projection.cpp + src/mbgl/layout/symbol_projection.hpp # map include/mbgl/map/backend.hpp diff --git a/mapbox-gl-js b/mapbox-gl-js index 3dd41165546..4fa52b9e3a0 160000 --- a/mapbox-gl-js +++ b/mapbox-gl-js @@ -1 +1 @@ -Subproject commit 3dd4116554694559e3df2667ed91670438e8d1ae +Subproject commit 4fa52b9e3a0732c9a63f7d4221661a300c15c98f diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 1b4d6fbcb7e..3508b92a79d 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -164,15 +164,20 @@ void Context::verifyProgramLinkage(ProgramID program_) { throw std::runtime_error("program failed to link"); } -UniqueBuffer Context::createVertexBuffer(const void* data, std::size_t size) { +UniqueBuffer Context::createVertexBuffer(const void* data, std::size_t size, const BufferUsageType usage) { BufferID id = 0; MBGL_CHECK_ERROR(glGenBuffers(1, &id)); UniqueBuffer result { std::move(id), { this } }; vertexBuffer = result; - MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW)); + MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, size, data, static_cast(usage))); return result; } +void Context::updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size) { + vertexBuffer = buffer; + MBGL_CHECK_ERROR(glBufferSubData(GL_ARRAY_BUFFER, 0, size, data)); +} + UniqueBuffer Context::createIndexBuffer(const void* data, std::size_t size) { BufferID id = 0; MBGL_CHECK_ERROR(glGenBuffers(1, &id)); diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 4f5b4c797c4..f1f0ac7f8af 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -65,13 +65,19 @@ class Context : private util::noncopyable { optional> getBinaryProgram(ProgramID) const; template - VertexBuffer createVertexBuffer(VertexVector&& v) { + VertexBuffer createVertexBuffer(VertexVector&& v, const BufferUsageType usage=BufferUsageType::StaticDraw) { return VertexBuffer { v.vertexSize(), - createVertexBuffer(v.data(), v.byteSize()) + createVertexBuffer(v.data(), v.byteSize(), usage) }; } + template + void updateVertexBuffer(VertexBuffer& buffer, VertexVector&& v) { + assert(v.vertexSize() == buffer.vertexCount); + updateVertexBuffer(buffer.buffer, v.data(), v.byteSize()); + } + template IndexBuffer createIndexBuffer(IndexVector&& v) { return IndexBuffer { @@ -239,7 +245,8 @@ class Context : private util::noncopyable { State pointSize; #endif // MBGL_USE_GLES2 - UniqueBuffer createVertexBuffer(const void* data, std::size_t size); + UniqueBuffer createVertexBuffer(const void* data, std::size_t size, const BufferUsageType usage); + void updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); UniqueBuffer createIndexBuffer(const void* data, std::size_t size); UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit); void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit); diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index 74ce67fba6a..8997fcbf31f 100644 --- a/src/mbgl/gl/types.hpp +++ b/src/mbgl/gl/types.hpp @@ -96,5 +96,11 @@ enum class UniformDataType : uint32_t { SamplerCube = 0x8B60, }; +enum class BufferUsageType : uint32_t { + StreamDraw = 0x88E0, + StaticDraw = 0x88E4, + DynamicDraw = 0x88E8, +}; + } // namespace gl } // namespace mbgl diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index ffb70c7ca25..02fb800df6f 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -5,8 +5,8 @@ namespace mbgl { using namespace style; -SymbolInstance::SymbolInstance(Anchor& anchor, - const GeometryCoordinates& line, +SymbolInstance::SymbolInstance(Anchor& anchor_, + GeometryCoordinates line_, const std::pair& shapedTextOrientations, optional shapedIcon, const SymbolLayoutProperties::Evaluated& layout, @@ -16,33 +16,38 @@ SymbolInstance::SymbolInstance(Anchor& anchor, const float textBoxScale, const float textPadding, const SymbolPlacementType textPlacement, + const std::array textOffset_, const float iconBoxScale, const float iconPadding, const SymbolPlacementType iconPlacement, + const std::array iconOffset_, const GlyphPositionMap& positions, const IndexedSubfeature& indexedFeature, const std::size_t featureIndex_) : - point(anchor.point), + anchor(anchor_), + line(line_), index(index_), hasText(shapedTextOrientations.first || shapedTextOrientations.second), hasIcon(shapedIcon), // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature), - iconCollisionFeature(line, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature), - featureIndex(featureIndex_) { + textCollisionFeature(line_, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature), + iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature), + featureIndex(featureIndex_), + textOffset(textOffset_), + iconOffset(iconOffset_) { // Create the quads used for rendering the icon and glyphs. if (addToBuffers) { if (shapedIcon) { - iconQuad = getIconQuad(anchor, *shapedIcon, line, layout, layoutTextSize, iconPlacement, shapedTextOrientations.first); + iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); } if (shapedTextOrientations.first) { - auto quads = getGlyphQuads(anchor, shapedTextOrientations.first, textBoxScale, line, layout, textPlacement, positions); + auto quads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); } if (shapedTextOrientations.second) { - auto quads = getGlyphQuads(anchor, shapedTextOrientations.second, textBoxScale, line, layout, textPlacement, positions); + auto quads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); } } diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index f199d929df9..f1df416cd19 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -13,7 +13,7 @@ class IndexedSubfeature; class SymbolInstance { public: SymbolInstance(Anchor& anchor, - const GeometryCoordinates& line, + GeometryCoordinates line, const std::pair& shapedTextOrientations, optional shapedIcon, const style::SymbolLayoutProperties::Evaluated&, @@ -23,14 +23,17 @@ class SymbolInstance { const float textBoxScale, const float textPadding, style::SymbolPlacementType textPlacement, + const std::array textOffset, const float iconBoxScale, const float iconPadding, style::SymbolPlacementType iconPlacement, + const std::array iconOffset, const GlyphPositionMap&, const IndexedSubfeature&, const std::size_t featureIndex); - Point point; + Anchor anchor; + GeometryCoordinates line; uint32_t index; bool hasText; bool hasIcon; @@ -40,6 +43,8 @@ class SymbolInstance { CollisionFeature iconCollisionFeature; WritingModeType writingModes; std::size_t featureIndex; + std::array textOffset; + std::array iconOffset; }; } // namespace mbgl diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index a6649574893..e308da618ff 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -305,6 +305,8 @@ void SymbolLayout::addFeature(const std::size_t index, const float layoutTextSize = layout.evaluate(zoom + 1, feature); const float layoutIconSize = layout.evaluate(zoom + 1, feature); + const std::array textOffset = layout.evaluate(zoom, feature); + const std::array iconOffset = layout.evaluate(zoom, feature); // To reduce the number of labels that jump around when zooming we need // to use a text-size value that is the same for all zoom levels. @@ -355,8 +357,8 @@ void SymbolLayout::addFeature(const std::size_t index, symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, layout.evaluate(zoom, feature), layoutTextSize, addToBuffers, symbolInstances.size(), - textBoxScale, textPadding, textPlacement, - iconBoxScale, iconPadding, iconPlacement, + textBoxScale, textPadding, textPlacement, textOffset, + iconBoxScale, iconPadding, iconPlacement, iconOffset, glyphPositionMap, indexedFeature, index); }; @@ -455,8 +457,8 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) const float cos = std::cos(collisionTile.config.angle); std::sort(symbolInstances.begin(), symbolInstances.end(), [sin, cos](SymbolInstance &a, SymbolInstance &b) { - const int32_t aRotated = sin * a.point.x + cos * a.point.y; - const int32_t bRotated = sin * b.point.x + cos * b.point.y; + const int32_t aRotated = sin * a.anchor.point.x + cos * a.anchor.point.y; + const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y; return aRotated != bRotated ? aRotated < bRotated : a.index > b.index; @@ -501,10 +503,21 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) const float placementZoom = util::max(util::log2(glyphScale) + zoom, 0.0f); collisionTile.insertFeature(symbolInstance.textCollisionFeature, glyphScale, layout.get()); if (glyphScale < collisionTile.maxScale) { + + const float labelAngle = std::fmod((symbolInstance.anchor.angle + collisionTile.config.angle) + 2 * M_PI, 2 * M_PI); + const bool inVerticalRange = ( + (labelAngle > M_PI * 1.0 / 4.0 && labelAngle <= M_PI * 3.0 / 4) || + (labelAngle > M_PI * 5.0 / 4.0 && labelAngle <= M_PI * 7.0 / 4)); + const bool useVerticalMode = symbolInstance.writingModes & WritingModeType::Vertical && inVerticalRange; + + const Range sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, + symbolInstance.textOffset, placementZoom, useVerticalMode, symbolInstance.line); + for (const auto& symbol : symbolInstance.glyphQuads) { addSymbol( - bucket->text, *bucket->textSizeBinder, symbol, feature, placementZoom, - keepUpright, textPlacement, collisionTile.config.angle, symbolInstance.writingModes, symbolInstance.point); + bucket->text, sizeData, symbol, placementZoom, + keepUpright, textPlacement, symbolInstance.anchor, bucket->text.placedSymbols.back()); } } } @@ -513,9 +526,12 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) const float placementZoom = util::max(util::log2(iconScale) + zoom, 0.0f); collisionTile.insertFeature(symbolInstance.iconCollisionFeature, iconScale, layout.get()); if (iconScale < collisionTile.maxScale && symbolInstance.iconQuad) { + const Range sizeData = bucket->iconSizeBinder->getVertexSizeData(feature); + bucket->icon.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, + symbolInstance.iconOffset, placementZoom, false, symbolInstance.line); addSymbol( - bucket->icon, *bucket->iconSizeBinder, *symbolInstance.iconQuad, feature, placementZoom, - keepUpright, iconPlacement, collisionTile.config.angle, symbolInstance.writingModes, symbolInstance.point); + bucket->icon, sizeData, *symbolInstance.iconQuad, placementZoom, + keepUpright, iconPlacement, symbolInstance.anchor, bucket->icon.placedSymbols.back()); } } @@ -534,15 +550,13 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) template void SymbolLayout::addSymbol(Buffer& buffer, - SymbolSizeBinder& sizeBinder, + const Range sizeData, const SymbolQuad& symbol, - const SymbolFeature& feature, const float placementZoom, const bool keepUpright, const style::SymbolPlacementType placement, - const float placementAngle, - const WritingModeType writingModes, - const Point labelAnchor) { + const Anchor& labelAnchor, + PlacedSymbol& placedSymbol) { constexpr const uint16_t vertexLength = 4; const auto &tl = symbol.tl; @@ -551,30 +565,9 @@ void SymbolLayout::addSymbol(Buffer& buffer, const auto &br = symbol.br; const auto &tex = symbol.tex; - float minZoom = util::max(zoom + util::log2(symbol.minScale), placementZoom); - float maxZoom = util::min(zoom + util::log2(symbol.maxScale), util::MAX_ZOOM_F); - const auto &anchorPoint = symbol.anchorPoint; - - // drop incorrectly oriented glyphs - const float a = std::fmod(symbol.anchorAngle + placementAngle + M_PI, M_PI * 2); - if (writingModes & WritingModeType::Vertical) { - if (placement == style::SymbolPlacementType::Line && symbol.writingMode == WritingModeType::Vertical) { - if (keepUpright && placement == style::SymbolPlacementType::Line && (a <= (M_PI * 5 / 4) || a > (M_PI * 7 / 4))) - return; - } else if (keepUpright && placement == style::SymbolPlacementType::Line && (a <= (M_PI * 3 / 4) || a > (M_PI * 5 / 4))) - return; - } else if (keepUpright && placement == style::SymbolPlacementType::Line && - (a <= M_PI / 2 || a > M_PI * 3 / 2)) { - return; - } - - if (maxZoom <= minZoom) - return; - - // Lower min zoom so that while fading out the label - // it can be shown outside of collision-free zoom levels - if (minZoom == placementZoom) { - minZoom = 0; + if (placement == style::SymbolPlacementType::Line && keepUpright) { + // drop incorrectly oriented glyphs + if ((symbol.writingMode == WritingModeType::Vertical) != placedSymbol.useVerticalMode) return; } if (buffer.segments.empty() || buffer.segments.back().vertexLength + vertexLength > std::numeric_limits::max()) { @@ -587,20 +580,17 @@ void SymbolLayout::addSymbol(Buffer& buffer, assert(segment.vertexLength <= std::numeric_limits::max()); uint16_t index = segment.vertexLength; - // Encode angle of glyph - uint8_t glyphAngle = std::round((symbol.glyphAngle / (M_PI * 2)) * 256); - // coordinates (2 triangles) - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, tl, labelAnchor, tex.x, tex.y, - minZoom, maxZoom, placementZoom, glyphAngle)); - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, tr, labelAnchor, tex.x + tex.w, tex.y, - minZoom, maxZoom, placementZoom, glyphAngle)); - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, bl, labelAnchor, tex.x, tex.y + tex.h, - minZoom, maxZoom, placementZoom, glyphAngle)); - buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(anchorPoint, br, labelAnchor, tex.x + tex.w, tex.y + tex.h, - minZoom, maxZoom, placementZoom, glyphAngle)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, tl, symbol.glyphOffset.y, tex.x, tex.y, sizeData)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, tr, symbol.glyphOffset.y, tex.x + tex.w, tex.y, sizeData)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, bl, symbol.glyphOffset.y, tex.x, tex.y + tex.h, sizeData)); + buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, br, symbol.glyphOffset.y, tex.x + tex.w, tex.y + tex.h, sizeData)); - sizeBinder.populateVertexVector(feature); + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0, placementZoom); + buffer.dynamicVertices.emplace_back(dynamicVertex); + buffer.dynamicVertices.emplace_back(dynamicVertex); + buffer.dynamicVertices.emplace_back(dynamicVertex); + buffer.dynamicVertices.emplace_back(dynamicVertex); // add the two triangles, referencing the four coordinates we just inserted. buffer.triangles.emplace_back(index + 0, index + 1, index + 2); @@ -608,6 +598,8 @@ void SymbolLayout::addSymbol(Buffer& buffer, segment.vertexLength += vertexLength; segment.indexLength += 6; + + placedSymbol.glyphOffsets.push_back(symbol.glyphOffset.x); } void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& bucket) { @@ -647,10 +639,10 @@ void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& auto& segment = collisionBox.segments.back(); uint16_t index = segment.vertexLength; - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, tl, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, tr, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, br, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.point, bl, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br, maxZoom, placementZoom)); + collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl, maxZoom, placementZoom)); collisionBox.lines.emplace_back(index + 0, index + 1); collisionBox.lines.emplace_back(index + 1, index + 2); diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 5dc0f3eb76f..90f5b3c91d3 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -20,6 +20,7 @@ class CollisionTile; class SymbolBucket; class Anchor; class RenderLayer; +class PlacedSymbol; namespace style { class Filter; @@ -58,15 +59,13 @@ class SymbolLayout { // Adds placed items to the buffer. template void addSymbol(Buffer&, - SymbolSizeBinder& sizeBinder, + const Range sizeData, const SymbolQuad&, - const SymbolFeature& feature, float scale, const bool keepUpright, const style::SymbolPlacementType, - const float placementAngle, - WritingModeType writingModes, - const Point labelAnchor); + const Anchor& labelAnchor, + PlacedSymbol& placedSymbol); // Stores the layer so that we can hold on to GeometryTileFeature instances in SymbolFeature, // which may reference data from this object. diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp new file mode 100644 index 00000000000..99555f79971 --- /dev/null +++ b/src/mbgl/layout/symbol_projection.cpp @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + + /* + * # Overview of coordinate spaces + * + * ## Tile coordinate spaces + * Each label has an anchor. Some labels have corresponding line geometries. + * The points for both anchors and lines are stored in tile units. Each tile has it's own + * coordinate space going from (0, 0) at the top left to (EXTENT, EXTENT) at the bottom right. + * + * ## GL coordinate space + * At the end of everything, the vertex shader needs to produce a position in GL coordinate space, + * which is (-1, 1) at the top left and (1, -1) in the bottom right. + * + * ## Map pixel coordinate spaces + * Each tile has a pixel coordinate space. It's just the tile units scaled so that one unit is + * whatever counts as 1 pixel at the current zoom. + * This space is used for pitch-alignment=map, rotation-alignment=map + * + * ## Rotated map pixel coordinate spaces + * Like the above, but rotated so axis of the space are aligned with the viewport instead of the tile. + * This space is used for pitch-alignment=map, rotation-alignment=viewport + * + * ## Viewport pixel coordinate space + * (0, 0) is at the top left of the canvas and (pixelWidth, pixelHeight) is at the bottom right corner + * of the canvas. This space is used for pitch-alignment=viewport + * + * + * # Vertex projection + * It goes roughly like this: + * 1. project the anchor and line from tile units into the correct label coordinate space + * - map pixel space pitch-alignment=map rotation-alignment=map + * - rotated map pixel space pitch-alignment=map rotation-alignment=viewport + * - viewport pixel space pitch-alignment=viewport rotation-alignment=* + * 2. if the label follows a line, find the point along the line that is the correct distance from the anchor. + * 3. add the glyph's corner offset to the point from step 3 + * 4. convert from the label coordinate space to gl coordinates + * + * For horizontal labels we want to do step 1 in the shader for performance reasons (no cpu work). + * This is what `u_label_plane_matrix` is used for. + * For labels aligned with lines we have to steps 1 and 2 on the cpu since we need access to the line geometry. + * This is what `updateLineLabels(...)` does. + * Since the conversion is handled on the cpu we just set `u_label_plane_matrix` to an identity matrix. + * + * Steps 3 and 4 are done in the shaders for all labels. + */ + + /* + * Returns a matrix for converting from tile units to the correct label coordinate space. + */ + mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits) { + mat4 m; + matrix::identity(m); + if (pitchWithMap) { + matrix::scale(m, m, 1 / pixelsToTileUnits, 1 / pixelsToTileUnits, 1); + if (!rotateWithMap) { + matrix::rotate_z(m, m, state.getAngle()); + } + } else { + matrix::scale(m, m, state.getSize().width / 2.0, -(state.getSize().height / 2.0), 1.0); + matrix::translate(m, m, 1, -1, 0); + matrix::multiply(m, m, posMatrix); + } + return m; + } + + /* + * Returns a matrix for converting from the correct label coordinate space to gl coords. + */ + mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits) { + mat4 m; + matrix::identity(m); + if (pitchWithMap) { + matrix::multiply(m, m, posMatrix); + matrix::scale(m, m, pixelsToTileUnits, pixelsToTileUnits, 1); + if (!rotateWithMap) { + matrix::rotate_z(m, m, -state.getAngle()); + } + } else { + matrix::scale(m, m, 1, -1, 1); + matrix::translate(m, m, -1, -1, 0); + matrix::scale(m, m, 2.0 / state.getSize().width, 2.0 / state.getSize().height, 1.0); + } + return m; + } + + + Point project(const Point& point, const mat4& matrix) { + vec4 pos = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(pos, pos, matrix); + return { static_cast(pos[0] / pos[3]), static_cast(pos[1] / pos[3]) }; + } + + float evaluateSizeForFeature(const ZoomEvaluatedSize& zoomEvaluatedSize, const PlacedSymbol& placedSymbol) { + if (zoomEvaluatedSize.isFeatureConstant) { + return zoomEvaluatedSize.size; + } else { + if (zoomEvaluatedSize.isZoomConstant) { + return placedSymbol.lowerSize; + } else { + return placedSymbol.lowerSize + zoomEvaluatedSize.sizeT * (placedSymbol.upperSize - placedSymbol.lowerSize); + } + } + } + + bool isVisible(const vec4& anchorPos, const float placementZoom, const std::array& clippingBuffer, const FrameHistory& frameHistory) { + const float x = anchorPos[0] / anchorPos[3]; + const float y = anchorPos[1] / anchorPos[3]; + const bool inPaddedViewport = ( + x >= -clippingBuffer[0] && + x <= clippingBuffer[0] && + y >= -clippingBuffer[1] && + y <= clippingBuffer[1]); + return inPaddedViewport && frameHistory.isVisible(placementZoom); + } + + void addDynamicAttributes(const Point& anchorPoint, const float angle, const float placementZoom, + gl::VertexVector& dynamicVertexArray) { + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle, placementZoom); + dynamicVertexArray.emplace_back(dynamicVertex); + dynamicVertexArray.emplace_back(dynamicVertex); + dynamicVertexArray.emplace_back(dynamicVertex); + dynamicVertexArray.emplace_back(dynamicVertex); + } + + void hideGlyphs(size_t numGlyphs, gl::VertexVector& dynamicVertexArray) { + const Point offscreenPoint = { -INFINITY, -INFINITY }; + for (size_t i = 0; i < numGlyphs; i++) { + addDynamicAttributes(offscreenPoint, 0, 25, dynamicVertexArray); + } + } + + struct PlacedGlyph { + PlacedGlyph(Point point_, float angle_) : point(point_), angle(angle_) {} + Point point; + float angle; + }; + + optional placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip, + Point anchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const mat4& labelPlaneMatrix) { + + const float combinedOffsetX = flip ? + offsetX - lineOffsetX : + offsetX + lineOffsetX; + + int16_t dir = combinedOffsetX > 0 ? 1 : -1; + + float angle = 0.0; + if (flip) { + // The label needs to be flipped to keep text upright. + // Iterate in the reverse direction. + dir *= -1; + angle = M_PI; + } + + if (dir < 0) angle += M_PI; + + int32_t currentIndex = dir > 0 ? anchorSegment : anchorSegment + 1; + + Point current = anchorPoint; + Point prev = anchorPoint; + float distanceToPrev = 0.0; + float currentSegmentDistance = 0.0; + const float absOffsetX = std::abs(combinedOffsetX); + + while (distanceToPrev + currentSegmentDistance <= absOffsetX) { + currentIndex += dir; + + // offset does not fit on the projected line + if (currentIndex < 0 || currentIndex >= static_cast(line.size())) return {}; + + prev = current; + current = project(convertPoint(line.at(currentIndex)), labelPlaneMatrix); + + distanceToPrev += currentSegmentDistance; + currentSegmentDistance = util::dist(prev, current); + } + + // The point is on the current segment. Interpolate to find it. + const float segmentInterpolationT = (absOffsetX - distanceToPrev) / currentSegmentDistance; + const Point prevToCurrent = current - prev; + Point p = (prevToCurrent * segmentInterpolationT) + prev; + + // offset the point from the line to text-offset and icon-offset + p += util::perp(prevToCurrent) * static_cast(lineOffsetY * dir / util::mag(prevToCurrent)); + + const float segmentAngle = angle + std::atan2(current.y - prev.y, current.x - prev.x); + + return {{ p, segmentAngle }}; + } + + void placeGlyphsAlongLine(const PlacedSymbol& symbol, const float fontSize, const bool flip, const mat4& labelPlaneMatrix, + gl::VertexVector& dynamicVertexArray) { + const float fontScale = fontSize / 24.0; + const float lineOffsetX = symbol.lineOffset[0] * fontSize; + const float lineOffsetY = symbol.lineOffset[1] * fontSize; + + const Point anchorPoint = project(symbol.anchorPoint, labelPlaneMatrix); + + std::vector placedGlyphs; + for (auto glyphOffsetX : symbol.glyphOffsets) { + auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); + if (placedGlyph) { + placedGlyphs.push_back(*placedGlyph); + } else { + hideGlyphs(symbol.glyphOffsets.size(), dynamicVertexArray); + return; + } + } + + for (auto& placedGlyph : placedGlyphs) { + addDynamicAttributes(placedGlyph.point, placedGlyph.angle, symbol.placementZoom, dynamicVertexArray); + } + } + + void reprojectLineLabels(gl::VertexVector& dynamicVertexArray, const std::vector& placedSymbols, + const mat4& posMatrix, const style::SymbolPropertyValues& values, + const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state, const FrameHistory& frameHistory) { + + const ZoomEvaluatedSize partiallyEvaluatedSize = sizeBinder.evaluateForZoom(state.getZoom()); + + const std::array clippingBuffer = {{ 256.0 / state.getSize().width * 2.0 + 1.0, 256.0 / state.getSize().height * 2.0 + 1.0 }}; + + const mat4 labelPlaneMatrix = getLabelPlaneMatrix(posMatrix, values.pitchAlignment == style::AlignmentType::Map, + values.rotationAlignment == style::AlignmentType::Map, state, tile.id.pixelsToTileUnits(1, state.getZoom())); + + dynamicVertexArray.clear(); + + for (auto& placedSymbol : placedSymbols) { + vec4 anchorPos = {{ placedSymbol.anchorPoint.x, placedSymbol.anchorPoint.y, 0, 1 }}; + matrix::transformMat4(anchorPos, anchorPos, posMatrix); + + // Don't bother calculating the correct point for invisible labels. + if (!isVisible(anchorPos, placedSymbol.placementZoom, clippingBuffer, frameHistory)) { + hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); + continue; + } + + bool flip = false; + if (values.keepUpright) { + const Point a = project(convertPoint(placedSymbol.line.at(placedSymbol.segment)), posMatrix); + const Point b = project(convertPoint(placedSymbol.line.at(placedSymbol.segment + 1)), posMatrix); + flip = placedSymbol.useVerticalMode ? b.y > a.y : b.x < a.x; + } + + const float cameraToAnchorDistance = anchorPos[3]; + const float perspectiveRatio = 1 + 0.5 * ((cameraToAnchorDistance / state.getCameraToCenterDistance()) - 1.0); + + const float fontSize = evaluateSizeForFeature(partiallyEvaluatedSize, placedSymbol); + const float pitchScaledFontSize = values.pitchAlignment == style::AlignmentType::Map ? + fontSize * perspectiveRatio : + fontSize / perspectiveRatio; + + placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, flip, labelPlaneMatrix, dynamicVertexArray); + } + } +} // end namespace mbgl diff --git a/src/mbgl/layout/symbol_projection.hpp b/src/mbgl/layout/symbol_projection.hpp new file mode 100644 index 00000000000..2652fe7ace5 --- /dev/null +++ b/src/mbgl/layout/symbol_projection.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + + class TransformState; + class RenderTile; + class FrameHistory; + class SymbolSizeBinder; + class PlacedSymbol; + namespace style { + class SymbolPropertyValues; + } // end namespace style + + mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); + mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); + + void reprojectLineLabels(gl::VertexVector&, const std::vector&, + const mat4& posMatrix, const style::SymbolPropertyValues&, + const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&, const FrameHistory& frameHistory); + +} // end namespace mbgl diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index f39af2deec2..684d9d60994 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -23,6 +23,7 @@ inline uint16_t packUint8Pair(T a, T b) { MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_extrude); MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_pos_offset); +MBGL_DEFINE_ATTRIBUTE(float, 3, a_projected_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_label_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos); MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos); diff --git a/src/mbgl/programs/symbol_program.cpp b/src/mbgl/programs/symbol_program.cpp index bd43237b8f0..8790adcc63c 100644 --- a/src/mbgl/programs/symbol_program.cpp +++ b/src/mbgl/programs/symbol_program.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -10,7 +11,7 @@ namespace mbgl { using namespace style; -static_assert(sizeof(SymbolLayoutVertex) == 20, "expected SymbolLayoutVertex size"); +static_assert(sizeof(SymbolLayoutVertex) == 16, "expected SymbolLayoutVertex size"); std::unique_ptr SymbolSizeBinder::create(const float tileZoom, const style::DataDrivenPropertyValue& sizeProperty, @@ -33,6 +34,7 @@ Values makeValues(const bool isText, const style::SymbolPropertyValues& values, const Size& texsize, const std::array& pixelsToGLUnits, + const bool alongLine, const RenderTile& tile, const TransformState& state, Args&&... args) { @@ -46,21 +48,41 @@ Values makeValues(const bool isText, pixelsToGLUnits[1] * state.getCameraToCenterDistance() }}; } + + const float pixelsToTileUnits = tile.id.pixelsToTileUnits(1.0, state.getZoom()); + const bool pitchWithMap = values.pitchAlignment == style::AlignmentType::Map; + const bool rotateWithMap = values.rotationAlignment == style::AlignmentType::Map; + + mat4 labelPlaneMatrix; + if (alongLine) { + // For labels that follow lines the first part of the projection is handled on the cpu. + // Pass an identity matrix because no transformation needs to be done in the vertex shader. + matrix::identity(labelPlaneMatrix); + } else { + labelPlaneMatrix = getLabelPlaneMatrix(tile.matrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits); + } + + mat4 glCoordMatrix = getGlCoordMatrix(tile.matrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits); return Values { uniforms::u_matrix::Value{ tile.translatedMatrix(values.translate, values.translateAnchor, state) }, + uniforms::u_label_plane_matrix::Value{labelPlaneMatrix}, + uniforms::u_gl_coord_matrix::Value{ tile.translateVtxMatrix(glCoordMatrix, + values.translate, + values.translateAnchor, + state, + true) }, uniforms::u_extrude_scale::Value{ extrudeScale }, uniforms::u_texsize::Value{ texsize }, - uniforms::u_zoom::Value{ float(state.getZoom()) }, - uniforms::u_rotate_with_map::Value{ values.rotationAlignment == AlignmentType::Map }, uniforms::u_texture::Value{ 0 }, uniforms::u_fadetexture::Value{ 1 }, uniforms::u_is_text::Value{ isText }, uniforms::u_collision_y_stretch::Value{ tile.tile.yStretch() }, uniforms::u_camera_to_center_distance::Value{ state.getCameraToCenterDistance() }, uniforms::u_pitch::Value{ state.getPitch() }, + uniforms::u_pitch_with_map::Value{ pitchWithMap }, uniforms::u_max_camera_distance::Value{ values.maxCameraDistance }, std::forward(args)... }; @@ -71,6 +93,7 @@ SymbolIconProgram::uniformValues(const bool isText, const style::SymbolPropertyValues& values, const Size& texsize, const std::array& pixelsToGLUnits, + const bool alongLine, const RenderTile& tile, const TransformState& state) { @@ -79,6 +102,7 @@ SymbolIconProgram::uniformValues(const bool isText, values, texsize, pixelsToGLUnits, + alongLine, tile, state ); @@ -90,25 +114,24 @@ typename SymbolSDFProgram::UniformValues SymbolSDFProgram& pixelsToGLUnits, + const bool alongLine, const RenderTile& tile, const TransformState& state, const SymbolSDFPart part) { const float gammaScale = (values.pitchAlignment == AlignmentType::Map - ? std::cos(state.getPitch()) - : 1.0) * state.getCameraToCenterDistance(); + ? std::cos(state.getPitch()) * state.getCameraToCenterDistance() + : 1.0); return makeValues::UniformValues>( isText, values, texsize, pixelsToGLUnits, + alongLine, tile, state, uniforms::u_gamma_scale::Value{ gammaScale }, - uniforms::u_bearing::Value{ -1.0f * state.getAngle() }, - uniforms::u_aspect_ratio::Value{ (state.getSize().width * 1.0f) / (state.getSize().height * 1.0f) }, - uniforms::u_pitch_with_map::Value{ values.pitchAlignment == AlignmentType::Map }, uniforms::u_is_halo::Value{ part == SymbolSDFPart::Halo } ); } diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index 130e556b467..79a961ad218 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -29,9 +29,9 @@ class RenderTile; class TransformState; namespace uniforms { -MBGL_DEFINE_UNIFORM_SCALAR(bool, u_rotate_with_map); +MBGL_DEFINE_UNIFORM_MATRIX(double, 4, u_gl_coord_matrix); +MBGL_DEFINE_UNIFORM_MATRIX(double, 4, u_label_plane_matrix); MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_texture); -MBGL_DEFINE_UNIFORM_SCALAR(float, u_aspect_ratio); MBGL_DEFINE_UNIFORM_SCALAR(bool, u_is_halo); MBGL_DEFINE_UNIFORM_SCALAR(float, u_gamma_scale); @@ -40,57 +40,58 @@ MBGL_DEFINE_UNIFORM_SCALAR(bool, u_is_size_zoom_constant); MBGL_DEFINE_UNIFORM_SCALAR(bool, u_is_size_feature_constant); MBGL_DEFINE_UNIFORM_SCALAR(float, u_size_t); MBGL_DEFINE_UNIFORM_SCALAR(float, u_size); -MBGL_DEFINE_UNIFORM_SCALAR(float, u_layout_size); MBGL_DEFINE_UNIFORM_SCALAR(float, u_max_camera_distance); } // namespace uniforms struct SymbolLayoutAttributes : gl::Attributes< attributes::a_pos_offset, - attributes::a_label_pos, attributes::a_data> { - static Vertex vertex(Point a, + static Vertex vertex(Point labelAnchor, Point o, - Point labelAnchor, + float glyphOffsetY, uint16_t tx, uint16_t ty, - float minzoom, - float maxzoom, - float labelminzoom, - uint8_t labelangle) { + const Range& sizeData) { return Vertex { // combining pos and offset to reduce number of vertex attributes passed to shader (8 max for some devices) - {{ - static_cast(a.x), - static_cast(a.y), - static_cast(::round(o.x * 64)), // use 1/64 pixels for placement - static_cast(::round(o.y * 64)) - }}, {{ static_cast(labelAnchor.x), - static_cast(labelAnchor.y) + static_cast(labelAnchor.y), + static_cast(::round(o.x * 64)), // use 1/64 pixels for placement + static_cast(::round((o.y + glyphOffsetY) * 64)) }}, {{ tx, ty, - mbgl::attributes::packUint8Pair( - static_cast(labelminzoom * 10), // 1/10 zoom levels: z16 == 160 - static_cast(labelangle) - ), - mbgl::attributes::packUint8Pair( - static_cast(minzoom * 10), - static_cast(::fmin(maxzoom, 25) * 10) - ) + static_cast(sizeData.min * 10), + static_cast(sizeData.max * 10) }} }; } }; + +struct SymbolDynamicLayoutAttributes : gl::Attributes { + static Vertex vertex(Point anchorPoint, float labelAngle, float labelminzoom) { + return Vertex { + {{ + anchorPoint.x, + anchorPoint.y, + static_cast(mbgl::attributes::packUint8Pair( + static_cast(std::fmod(labelAngle + 2 * M_PI, 2 * M_PI) / (2 * M_PI) * 255), + static_cast(labelminzoom * 10))) + }} + }; + } +}; -class SymbolSizeAttributes : public gl::Attributes { -public: - using Attribute = attributes::a_size::Type; +struct ZoomEvaluatedSize { + bool isZoomConstant; + bool isFeatureConstant; + float sizeT; + float size; + float layoutSize; }; - // Mimic the PaintPropertyBinder technique specifically for the {text,icon}-size layout properties // in order to provide a 'custom' scheme for encoding the necessary attribute data. As with // PaintPropertyBinder, SymbolSizeBinder is an abstract class whose implementations handle the @@ -103,18 +104,25 @@ class SymbolSizeBinder { uniforms::u_is_size_zoom_constant, uniforms::u_is_size_feature_constant, uniforms::u_size_t, - uniforms::u_size, - uniforms::u_layout_size>; + uniforms::u_size>; using UniformValues = Uniforms::Values; static std::unique_ptr create(const float tileZoom, const style::DataDrivenPropertyValue& sizeProperty, const float defaultValue); - virtual SymbolSizeAttributes::Bindings attributeBindings() const = 0; - virtual void populateVertexVector(const GeometryTileFeature& feature) = 0; - virtual UniformValues uniformValues(float currentZoom) const = 0; - virtual void upload(gl::Context&) = 0; + virtual Range getVertexSizeData(const GeometryTileFeature& feature) = 0; + virtual ZoomEvaluatedSize evaluateForZoom(float currentZoom) const = 0; + + UniformValues uniformValues(float currentZoom) const { + const ZoomEvaluatedSize u = evaluateForZoom(currentZoom); + return UniformValues { + uniforms::u_is_size_zoom_constant::Value{ u.isZoomConstant }, + uniforms::u_is_size_feature_constant::Value{ u.isFeatureConstant}, + uniforms::u_size_t::Value{ u.sizeT }, + uniforms::u_size::Value{ u.size } + }; + } }; // Return the smallest range of stops that covers the interval [lowerZoom, upperZoom] @@ -160,14 +168,9 @@ class ConstantSymbolSizeBinder final : public SymbolSizeBinder { ); } - SymbolSizeAttributes::Bindings attributeBindings() const override { - return SymbolSizeAttributes::Bindings { gl::DisabledAttribute() }; - } - - void upload(gl::Context&) override {} - void populateVertexVector(const GeometryTileFeature&) override {}; + Range getVertexSizeData(const GeometryTileFeature&) override { return { 0.0f, 0.0f }; }; - UniformValues uniformValues(float currentZoom) const override { + ZoomEvaluatedSize evaluateForZoom(float currentZoom) const override { float size = layoutSize; bool isZoomConstant = !(coveringRanges || function); if (coveringRanges) { @@ -186,14 +189,9 @@ class ConstantSymbolSizeBinder final : public SymbolSizeBinder { } else if (function) { size = function->evaluate(currentZoom); } - - return UniformValues { - uniforms::u_is_size_zoom_constant::Value{ isZoomConstant }, - uniforms::u_is_size_feature_constant::Value{ true }, - uniforms::u_size_t::Value{ 0.0f }, // unused - uniforms::u_size::Value{ size }, - uniforms::u_layout_size::Value{ layoutSize } - }; + + const float unused = 0.0f; + return { isZoomConstant, true, unused, size, layoutSize }; } float layoutSize; @@ -215,49 +213,22 @@ class SourceFunctionSymbolSizeBinder final : public SymbolSizeBinder { defaultValue(defaultValue_) { } - SymbolSizeAttributes::Bindings attributeBindings() const override { - return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::binding(*buffer, 0, 1) }; - } - - void populateVertexVector(const GeometryTileFeature& feature) override { - const auto sizeVertex = Vertex { - {{ - static_cast(function.evaluate(feature, defaultValue) * 10) - }} - }; - - vertices.emplace_back(sizeVertex); - vertices.emplace_back(sizeVertex); - vertices.emplace_back(sizeVertex); - vertices.emplace_back(sizeVertex); + Range getVertexSizeData(const GeometryTileFeature& feature) override { + const float size = function.evaluate(feature, defaultValue); + return { size, size }; }; - UniformValues uniformValues(float) const override { - return UniformValues { - uniforms::u_is_size_zoom_constant::Value{ true }, - uniforms::u_is_size_feature_constant::Value{ false }, - uniforms::u_size_t::Value{ 0.0f }, // unused - uniforms::u_size::Value{ 0.0f }, // unused - uniforms::u_layout_size::Value{ 0.0f } // unused - }; - } - - void upload(gl::Context& context) override { - buffer = VertexBuffer { context.createVertexBuffer(std::move(vertices)) }; + ZoomEvaluatedSize evaluateForZoom(float) const override { + const float unused = 0.0f; + return { true, false, unused, unused, unused }; } const style::SourceFunction& function; const float defaultValue; - - VertexVector vertices; - optional buffer; }; class CompositeFunctionSymbolSizeBinder final : public SymbolSizeBinder { public: - using Vertex = SymbolSizeAttributes::Vertex; - using VertexVector = gl::VertexVector; - using VertexBuffer = gl::VertexBuffer; CompositeFunctionSymbolSizeBinder(const float tileZoom, const style::CompositeFunction& function_, const float defaultValue_) : function(function_), @@ -268,51 +239,27 @@ class CompositeFunctionSymbolSizeBinder final : public SymbolSizeBinder { return getCoveringStops(stops, tileZoom, tileZoom + 1); })) {} - SymbolSizeAttributes::Bindings attributeBindings() const override { - return SymbolSizeAttributes::Bindings { SymbolSizeAttributes::Attribute::binding(*buffer, 0) }; - } - - void populateVertexVector(const GeometryTileFeature& feature) override { - const auto sizeVertex = Vertex { - {{ - static_cast(function.evaluate(coveringZoomStops.min, feature, defaultValue) * 10), - static_cast(function.evaluate(coveringZoomStops.max, feature, defaultValue) * 10), - static_cast(function.evaluate(layoutZoom, feature, defaultValue) * 10) - }} + Range getVertexSizeData(const GeometryTileFeature& feature) override { + return { + function.evaluate(coveringZoomStops.min, feature, defaultValue), + function.evaluate(coveringZoomStops.max, feature, defaultValue) }; - - vertices.emplace_back(sizeVertex); - vertices.emplace_back(sizeVertex); - vertices.emplace_back(sizeVertex); - vertices.emplace_back(sizeVertex); }; - UniformValues uniformValues(float currentZoom) const override { + ZoomEvaluatedSize evaluateForZoom(float currentZoom) const override { float sizeInterpolationT = util::clamp( util::interpolationFactor(1.0f, coveringZoomStops, currentZoom), 0.0f, 1.0f ); - return UniformValues { - uniforms::u_is_size_zoom_constant::Value{ false }, - uniforms::u_is_size_feature_constant::Value{ false }, - uniforms::u_size_t::Value{ sizeInterpolationT }, - uniforms::u_size::Value{ 0.0f }, // unused - uniforms::u_layout_size::Value{ 0.0f } // unused - }; - } - - void upload(gl::Context& context) override { - buffer = VertexBuffer { context.createVertexBuffer(std::move(vertices)) }; + const float unused = 0.0f; + return { false, false, sizeInterpolationT, unused, unused }; } const style::CompositeFunction& function; const float defaultValue; float layoutZoom; Range coveringZoomStops; - - VertexVector vertices; - optional buffer; }; @@ -326,7 +273,7 @@ class SymbolProgram { using LayoutAttributes = LayoutAttrs; using LayoutVertex = typename LayoutAttributes::Vertex; - using LayoutAndSizeAttributes = gl::ConcatenateAttributes; + using LayoutAndSizeAttributes = gl::ConcatenateAttributes; using PaintProperties = PaintProps; using PaintPropertyBinders = typename PaintProperties::Binders; @@ -359,6 +306,7 @@ class SymbolProgram { gl::ColorMode colorMode, UniformValues&& uniformValues, const gl::VertexBuffer& layoutVertexBuffer, + const gl::VertexBuffer& dynamicLayoutVertexBuffer, const SymbolSizeBinder& symbolSizeBinder, const gl::IndexBuffer& indexBuffer, const gl::SegmentVector& segments, @@ -375,7 +323,7 @@ class SymbolProgram { .concat(symbolSizeBinder.uniformValues(currentZoom)) .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)), LayoutAttributes::bindings(layoutVertexBuffer) - .concat(symbolSizeBinder.attributeBindings()) + .concat(SymbolDynamicLayoutAttributes::bindings(dynamicLayoutVertexBuffer)) .concat(paintPropertyBinders.attributeBindings(currentProperties)), indexBuffer, segments @@ -389,16 +337,17 @@ class SymbolIconProgram : public SymbolProgram< SymbolLayoutAttributes, gl::Uniforms< uniforms::u_matrix, + uniforms::u_label_plane_matrix, + uniforms::u_gl_coord_matrix, uniforms::u_extrude_scale, uniforms::u_texsize, - uniforms::u_zoom, - uniforms::u_rotate_with_map, uniforms::u_texture, uniforms::u_fadetexture, uniforms::u_is_text, uniforms::u_collision_y_stretch, uniforms::u_camera_to_center_distance, uniforms::u_pitch, + uniforms::u_pitch_with_map, uniforms::u_max_camera_distance>, style::IconPaintProperties> { @@ -409,6 +358,7 @@ class SymbolIconProgram : public SymbolProgram< const style::SymbolPropertyValues&, const Size& texsize, const std::array& pixelsToGLUnits, + const bool alongLine, const RenderTile&, const TransformState&); }; @@ -425,21 +375,19 @@ class SymbolSDFProgram : public SymbolProgram< SymbolLayoutAttributes, gl::Uniforms< uniforms::u_matrix, + uniforms::u_label_plane_matrix, + uniforms::u_gl_coord_matrix, uniforms::u_extrude_scale, uniforms::u_texsize, - uniforms::u_zoom, - uniforms::u_rotate_with_map, uniforms::u_texture, uniforms::u_fadetexture, uniforms::u_is_text, uniforms::u_collision_y_stretch, uniforms::u_camera_to_center_distance, uniforms::u_pitch, + uniforms::u_pitch_with_map, uniforms::u_max_camera_distance, uniforms::u_gamma_scale, - uniforms::u_bearing, - uniforms::u_aspect_ratio, - uniforms::u_pitch_with_map, uniforms::u_is_halo>, PaintProperties> { @@ -449,21 +397,19 @@ class SymbolSDFProgram : public SymbolProgram< SymbolLayoutAttributes, gl::Uniforms< uniforms::u_matrix, + uniforms::u_label_plane_matrix, + uniforms::u_gl_coord_matrix, uniforms::u_extrude_scale, uniforms::u_texsize, - uniforms::u_zoom, - uniforms::u_rotate_with_map, uniforms::u_texture, uniforms::u_fadetexture, uniforms::u_is_text, uniforms::u_collision_y_stretch, uniforms::u_camera_to_center_distance, uniforms::u_pitch, + uniforms::u_pitch_with_map, uniforms::u_max_camera_distance, uniforms::u_gamma_scale, - uniforms::u_bearing, - uniforms::u_aspect_ratio, - uniforms::u_pitch_with_map, uniforms::u_is_halo>, PaintProperties>; @@ -477,6 +423,7 @@ class SymbolSDFProgram : public SymbolProgram< const style::SymbolPropertyValues&, const Size& texsize, const std::array& pixelsToGLUnits, + const bool alongLine, const RenderTile&, const TransformState&, const SymbolSDFPart); diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp index 861f3271c99..285d2432519 100644 --- a/src/mbgl/programs/uniforms.hpp +++ b/src/mbgl/programs/uniforms.hpp @@ -15,7 +15,6 @@ MBGL_DEFINE_UNIFORM_SCALAR(float, u_blur); MBGL_DEFINE_UNIFORM_SCALAR(float, u_zoom); MBGL_DEFINE_UNIFORM_SCALAR(float, u_collision_y_stretch); -MBGL_DEFINE_UNIFORM_SCALAR(float, u_camera_to_center_distance); MBGL_DEFINE_UNIFORM_SCALAR(float, u_pitch); MBGL_DEFINE_UNIFORM_SCALAR(float, u_bearing); MBGL_DEFINE_UNIFORM_SCALAR(float, u_radius); diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index 28e6a472503..194a5d6bd82 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -38,14 +38,14 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo void SymbolBucket::upload(gl::Context& context) { if (hasTextData()) { text.vertexBuffer = context.createVertexBuffer(std::move(text.vertices)); + text.dynamicVertexBuffer = context.createVertexBuffer(std::move(text.dynamicVertices), gl::BufferUsageType::StreamDraw); text.indexBuffer = context.createIndexBuffer(std::move(text.triangles)); - textSizeBinder->upload(context); } if (hasIconData()) { icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices)); + icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsageType::StreamDraw); icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles)); - iconSizeBinder->upload(context); } if (!collisionBox.vertices.empty()) { diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index 002b6e28b31..ffa22e90211 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -15,6 +15,23 @@ namespace mbgl { +class PlacedSymbol { +public: + PlacedSymbol(Point anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_, + std::array lineOffset_, float placementZoom_, bool useVerticalMode_, GeometryCoordinates line_) : + anchorPoint(anchorPoint_), segment(segment_), lowerSize(lowerSize_), upperSize(upperSize_), + lineOffset(lineOffset_), placementZoom(placementZoom_), useVerticalMode(useVerticalMode_), line(std::move(line_)) {} + Point anchorPoint; + uint16_t segment; + float lowerSize; + float upperSize; + std::array lineOffset; + float placementZoom; + bool useVerticalMode; + GeometryCoordinates line; + std::vector glyphOffsets; +}; + class SymbolBucket : public Bucket { public: SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated, @@ -44,10 +61,13 @@ class SymbolBucket : public Bucket { struct TextBuffer { gl::VertexVector vertices; + gl::VertexVector dynamicVertices; gl::IndexVector triangles; gl::SegmentVector segments; + std::vector placedSymbols; optional> vertexBuffer; + optional> dynamicVertexBuffer; optional> indexBuffer; } text; @@ -55,11 +75,14 @@ class SymbolBucket : public Bucket { struct IconBuffer { gl::VertexVector vertices; + gl::VertexVector dynamicVertices; gl::IndexVector triangles; gl::SegmentVector segments; + std::vector placedSymbols; PremultipliedImage atlasImage; optional> vertexBuffer; + optional> dynamicVertexBuffer; optional> indexBuffer; } icon; @@ -69,6 +92,7 @@ class SymbolBucket : public Bucket { gl::SegmentVector segments; optional> vertexBuffer; + optional> dynamicVertexBuffer; optional> indexBuffer; } collisionBox; }; diff --git a/src/mbgl/renderer/frame_history.cpp b/src/mbgl/renderer/frame_history.cpp index 869222b4eb9..de153b69630 100644 --- a/src/mbgl/renderer/frame_history.cpp +++ b/src/mbgl/renderer/frame_history.cpp @@ -74,4 +74,8 @@ void FrameHistory::bind(gl::Context& context, uint32_t unit) { context.bindTexture(*texture, unit); } +bool FrameHistory::isVisible(const float zoom) const { + return opacities.data[std::floor(zoom * 10)] != 0; +} + } // namespace mbgl diff --git a/src/mbgl/renderer/frame_history.hpp b/src/mbgl/renderer/frame_history.hpp index f2b11f5f415..75a8b60a716 100644 --- a/src/mbgl/renderer/frame_history.hpp +++ b/src/mbgl/renderer/frame_history.hpp @@ -22,6 +22,7 @@ class FrameHistory { bool needsAnimation(const Duration&) const; void bind(gl::Context&, uint32_t); void upload(gl::Context&, uint32_t); + bool isVisible(const float zoom) const; private: std::array changeTimes; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 573e9db72ea..2fe6dd971e9 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -84,6 +84,7 @@ style::SymbolPropertyValues RenderSymbolLayer::iconPropertyValues(const style::S return style::SymbolPropertyValues { layout_.get(), // icon-pitch-alignment is not yet implemented; inherit the rotation alignment layout_.get(), + layout_.get(), evaluated.get(), evaluated.get(), evaluated.get().constantOr(Color::black()).a > 0 && @@ -109,6 +110,7 @@ style::SymbolPropertyValues RenderSymbolLayer::textPropertyValues(const style::S return style::SymbolPropertyValues { layout_.get(), layout_.get(), + layout_.get(), evaluated.get(), evaluated.get(), evaluated.get().constantOr(Color::black()).a > 0 && diff --git a/src/mbgl/renderer/layers/render_symbol_layer.hpp b/src/mbgl/renderer/layers/render_symbol_layer.hpp index e788336cbdd..a201b6298f5 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.hpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.hpp @@ -40,6 +40,7 @@ class SymbolPropertyValues { // Layout AlignmentType pitchAlignment; AlignmentType rotationAlignment; + bool keepUpright; // Paint std::array translate; diff --git a/src/mbgl/renderer/painters/painter_symbol.cpp b/src/mbgl/renderer/painters/painter_symbol.cpp index dc80f096f4d..51c3967ae70 100644 --- a/src/mbgl/renderer/painters/painter_symbol.cpp +++ b/src/mbgl/renderer/painters/painter_symbol.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -52,6 +53,7 @@ void Painter::renderSymbol(PaintParameters& parameters, colorModeForRenderPass(), std::move(uniformValues), *buffers.vertexBuffer, + *buffers.dynamicVertexBuffer, *symbolSizeBinder, *buffers.indexBuffer, buffers.segments, @@ -64,10 +66,19 @@ void Painter::renderSymbol(PaintParameters& parameters, assert(dynamic_cast(&tile.tile)); GeometryTile& geometryTile = static_cast(tile.tile); + if (bucket.hasIconData()) { auto values = layer.iconPropertyValues(layout); auto paintPropertyValues = layer.iconPaintProperties(); + const bool alongLine = bucket.layout.get() == SymbolPlacementType::Line && + bucket.layout.get() == AlignmentType::Map; + + if (alongLine) { + reprojectLineLabels(bucket.icon.dynamicVertices, bucket.icon.placedSymbols, tile.matrix, values, tile, *(bucket.iconSizeBinder), state, frameHistory); + context.updateVertexBuffer(*bucket.icon.dynamicVertexBuffer, std::move(bucket.icon.dynamicVertices)); + } + const bool iconScaled = layout.get().constantOr(1.0) != 1.0 || bucket.iconsNeedLinear; const bool iconTransformed = values.rotationAlignment == AlignmentType::Map || state.getPitch() != 0; @@ -80,7 +91,7 @@ void Painter::renderSymbol(PaintParameters& parameters, if (bucket.sdfIcons) { if (values.hasHalo) { draw(parameters.programs.symbolIconSDF, - SymbolSDFIconProgram::uniformValues(false, values, texsize, pixelsToGLUnits, tile, state, SymbolSDFPart::Halo), + SymbolSDFIconProgram::uniformValues(false, values, texsize, pixelsToGLUnits, alongLine, tile, state, SymbolSDFPart::Halo), bucket.icon, bucket.iconSizeBinder, values, @@ -90,7 +101,7 @@ void Painter::renderSymbol(PaintParameters& parameters, if (values.hasFill) { draw(parameters.programs.symbolIconSDF, - SymbolSDFIconProgram::uniformValues(false, values, texsize, pixelsToGLUnits, tile, state, SymbolSDFPart::Fill), + SymbolSDFIconProgram::uniformValues(false, values, texsize, pixelsToGLUnits, alongLine, tile, state, SymbolSDFPart::Fill), bucket.icon, bucket.iconSizeBinder, values, @@ -99,7 +110,7 @@ void Painter::renderSymbol(PaintParameters& parameters, } } else { draw(parameters.programs.symbolIcon, - SymbolIconProgram::uniformValues(false, values, texsize, pixelsToGLUnits, tile, state), + SymbolIconProgram::uniformValues(false, values, texsize, pixelsToGLUnits, alongLine, tile, state), bucket.icon, bucket.iconSizeBinder, values, @@ -114,11 +125,19 @@ void Painter::renderSymbol(PaintParameters& parameters, auto values = layer.textPropertyValues(layout); auto paintPropertyValues = layer.textPaintProperties(); + const bool alongLine = bucket.layout.get() == SymbolPlacementType::Line && + bucket.layout.get() == AlignmentType::Map; + + if (alongLine) { + reprojectLineLabels(bucket.text.dynamicVertices, bucket.text.placedSymbols, tile.matrix, values, tile, *(bucket.textSizeBinder), state, frameHistory); + context.updateVertexBuffer(*bucket.text.dynamicVertexBuffer, std::move(bucket.text.dynamicVertices)); + } + const Size texsize = geometryTile.glyphAtlasTexture->size; if (values.hasHalo) { draw(parameters.programs.symbolGlyph, - SymbolSDFTextProgram::uniformValues(true, values, texsize, pixelsToGLUnits, tile, state, SymbolSDFPart::Halo), + SymbolSDFTextProgram::uniformValues(true, values, texsize, pixelsToGLUnits, alongLine, tile, state, SymbolSDFPart::Halo), bucket.text, bucket.textSizeBinder, values, @@ -128,7 +147,7 @@ void Painter::renderSymbol(PaintParameters& parameters, if (values.hasFill) { draw(parameters.programs.symbolGlyph, - SymbolSDFTextProgram::uniformValues(true, values, texsize, pixelsToGLUnits, tile, state, SymbolSDFPart::Fill), + SymbolSDFTextProgram::uniformValues(true, values, texsize, pixelsToGLUnits, alongLine, tile, state, SymbolSDFPart::Fill), bucket.text, bucket.textSizeBinder, values, diff --git a/src/mbgl/renderer/render_tile.cpp b/src/mbgl/renderer/render_tile.cpp index 59c3ea076b6..7e7e3e6d230 100644 --- a/src/mbgl/renderer/render_tile.cpp +++ b/src/mbgl/renderer/render_tile.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace mbgl { @@ -10,24 +11,26 @@ using namespace style; mat4 RenderTile::translateVtxMatrix(const mat4& tileMatrix, const std::array& translation, TranslateAnchorType anchor, - const TransformState& state) const { + const TransformState& state, + const bool inViewportPixelUnits) const { if (translation[0] == 0 && translation[1] == 0) { return tileMatrix; } mat4 vtxMatrix; - if (anchor == TranslateAnchorType::Viewport) { - const double sin_a = std::sin(-state.getAngle()); - const double cos_a = std::cos(-state.getAngle()); - matrix::translate(vtxMatrix, tileMatrix, - id.pixelsToTileUnits(translation[0] * cos_a - translation[1] * sin_a, state.getZoom()), - id.pixelsToTileUnits(translation[0] * sin_a + translation[1] * cos_a, state.getZoom()), - 0); + const float angle = inViewportPixelUnits ? + (anchor == TranslateAnchorType::Map ? state.getAngle() : 0) : + (anchor == TranslateAnchorType::Viewport ? -state.getAngle() : 0); + + Point translate = util::rotate(Point{ translation[0], translation[1] }, angle); + + if (inViewportPixelUnits) { + matrix::translate(vtxMatrix, tileMatrix, translate.x, translate.y, 0); } else { matrix::translate(vtxMatrix, tileMatrix, - id.pixelsToTileUnits(translation[0], state.getZoom()), - id.pixelsToTileUnits(translation[1], state.getZoom()), + id.pixelsToTileUnits(translate.x, state.getZoom()), + id.pixelsToTileUnits(translate.y, state.getZoom()), 0); } @@ -37,13 +40,13 @@ mat4 RenderTile::translateVtxMatrix(const mat4& tileMatrix, mat4 RenderTile::translatedMatrix(const std::array& translation, TranslateAnchorType anchor, const TransformState& state) const { - return translateVtxMatrix(matrix, translation, anchor, state); + return translateVtxMatrix(matrix, translation, anchor, state, false); } mat4 RenderTile::translatedClipMatrix(const std::array& translation, TranslateAnchorType anchor, const TransformState& state) const { - return translateVtxMatrix(nearClippedMatrix, translation, anchor, state); + return translateVtxMatrix(nearClippedMatrix, translation, anchor, state, false); } void RenderTile::startRender(Painter& painter) { diff --git a/src/mbgl/renderer/render_tile.hpp b/src/mbgl/renderer/render_tile.hpp index 07e2d699f73..6d374c29cb7 100644 --- a/src/mbgl/renderer/render_tile.hpp +++ b/src/mbgl/renderer/render_tile.hpp @@ -38,11 +38,11 @@ class RenderTile final { void startRender(Painter&); -private: mat4 translateVtxMatrix(const mat4& tileMatrix, const std::array& translation, style::TranslateAnchorType anchor, - const TransformState& state) const; + const TransformState& state, + const bool inViewportPixelUnits) const; }; } // namespace mbgl diff --git a/src/mbgl/shaders/collision_box.cpp b/src/mbgl/shaders/collision_box.cpp index 05f306ef657..07fa94e338a 100644 --- a/src/mbgl/shaders/collision_box.cpp +++ b/src/mbgl/shaders/collision_box.cpp @@ -36,7 +36,7 @@ void main() { v_max_zoom = a_data.x; v_placement_zoom = a_data.y; - v_perspective_zoom_adjust = log2(collision_perspective_ratio * collision_adjustment) * 10.0; + v_perspective_zoom_adjust = floor(log2(collision_perspective_ratio * collision_adjustment) * 10.0); v_fade_tex = vec2((v_placement_zoom + v_perspective_zoom_adjust) / 255.0, 0.0); } diff --git a/src/mbgl/shaders/symbol_icon.cpp b/src/mbgl/shaders/symbol_icon.cpp index 8960e02c28e..cb00cdad05c 100644 --- a/src/mbgl/shaders/symbol_icon.cpp +++ b/src/mbgl/shaders/symbol_icon.cpp @@ -7,17 +7,16 @@ namespace shaders { const char* symbol_icon::name = "symbol_icon"; const char* symbol_icon::vertexSource = R"MBGL_SHADER( +const float PI = 3.141592653589793; + attribute vec4 a_pos_offset; -attribute vec2 a_label_pos; attribute vec4 a_data; +attribute vec3 a_projected_pos; -// icon-size data (see symbol_sdf.vertex.glsl for more) -attribute vec3 a_size; uniform bool u_is_size_zoom_constant; uniform bool u_is_size_feature_constant; uniform highp float u_size_t; // used to interpolate between zoom stops when size is a composite function uniform highp float u_size; // used when size is both zoom and feature constant -uniform highp float u_layout_size; // used when size is feature constant uniform highp float u_camera_to_center_distance; uniform highp float u_pitch; uniform highp float u_collision_y_stretch; @@ -31,13 +30,12 @@ varying lowp float opacity; uniform lowp float u_opacity; #endif -// matrix is for the vertex position. uniform mat4 u_matrix; +uniform mat4 u_label_plane_matrix; +uniform mat4 u_gl_coord_matrix; uniform bool u_is_text; -uniform highp float u_zoom; -uniform bool u_rotate_with_map; -uniform vec2 u_extrude_scale; +uniform bool u_pitch_with_map; uniform vec2 u_texsize; @@ -56,61 +54,49 @@ void main() { vec2 a_offset = a_pos_offset.zw; vec2 a_tex = a_data.xy; - highp vec2 label_data = unpack_float(a_data[2]); - highp float a_labelminzoom = label_data[0]; - highp vec2 a_zoom = unpack_float(a_data[3]); - highp float a_minzoom = a_zoom[0]; - highp float a_maxzoom = a_zoom[1]; + vec2 a_size = a_data.zw; + + highp vec2 angle_labelminzoom = unpack_float(a_projected_pos[2]); + highp float segment_angle = -angle_labelminzoom[0] / 255.0 * 2.0 * PI; + mediump float a_labelminzoom = angle_labelminzoom[1]; float size; - // In order to accommodate placing labels around corners in - // symbol-placement: line, each glyph in a label could have multiple - // "quad"s only one of which should be shown at a given zoom level. - // The min/max zoom assigned to each quad is based on the font size at - // the vector tile's zoom level, which might be different than at the - // currently rendered zoom level if text-size is zoom-dependent. - // Thus, we compensate for this difference by calculating an adjustment - // based on the scale of rendered text size relative to layout text size. - highp float layoutSize; if (!u_is_size_zoom_constant && !u_is_size_feature_constant) { size = mix(a_size[0], a_size[1], u_size_t) / 10.0; - layoutSize = a_size[2] / 10.0; } else if (u_is_size_zoom_constant && !u_is_size_feature_constant) { size = a_size[0] / 10.0; - layoutSize = size; } else if (!u_is_size_zoom_constant && u_is_size_feature_constant) { size = u_size; - layoutSize = u_layout_size; } else { size = u_size; - layoutSize = u_size; } - float fontScale = u_is_text ? size / 24.0 : size; + vec4 projectedPoint = u_matrix * vec4(a_pos, 0, 1); + highp float camera_to_anchor_distance = projectedPoint.w; + // See comments in symbol_sdf.vertex + highp float distance_ratio = u_pitch_with_map ? + camera_to_anchor_distance / u_camera_to_center_distance : + u_camera_to_center_distance / camera_to_anchor_distance; + highp float perspective_ratio = 0.5 + 0.5 * distance_ratio; - highp float zoomAdjust = log2(size / layoutSize); - highp float adjustedZoom = (u_zoom - zoomAdjust) * 10.0; - // result: z = 0 if a_minzoom <= adjustedZoom < a_maxzoom, and 1 otherwise - highp float z = 2.0 - step(a_minzoom, adjustedZoom) - (1.0 - step(a_maxzoom, adjustedZoom)); + size *= perspective_ratio; - vec4 projectedPoint = u_matrix * vec4(a_label_pos, 0, 1); - highp float camera_to_anchor_distance = projectedPoint.w; - highp float perspective_ratio = 1.0 + 0.5*((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0); + float fontScale = u_is_text ? size / 24.0 : size; - vec2 extrude = fontScale * u_extrude_scale * perspective_ratio * (a_offset / 64.0); - if (u_rotate_with_map) { - gl_Position = u_matrix * vec4(a_pos + extrude, 0, 1); - gl_Position.z += z * gl_Position.w; - } else { - gl_Position = u_matrix * vec4(a_pos, 0, 1) + vec4(extrude, 0, 0); - } + highp float angle_sin = sin(segment_angle); + highp float angle_cos = cos(segment_angle); + mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); + + vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0); + gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 64.0 * fontScale), 0.0, 1.0); v_tex = a_tex / u_texsize; // See comments in symbol_sdf.vertex highp float incidence_stretch = camera_to_anchor_distance / (u_camera_to_center_distance * cos(u_pitch)); highp float collision_adjustment = max(1.0, incidence_stretch / u_collision_y_stretch); - highp float perspective_zoom_adjust = log2(perspective_ratio * collision_adjustment) * 10.0; + highp float collision_perspective_ratio = 1.0 + 0.5*((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0); + highp float perspective_zoom_adjust = floor(log2(collision_perspective_ratio * collision_adjustment) * 10.0); v_fade_tex = vec2((a_labelminzoom + perspective_zoom_adjust) / 255.0, 0.0); } diff --git a/src/mbgl/shaders/symbol_sdf.cpp b/src/mbgl/shaders/symbol_sdf.cpp index bae01a5b592..b4158bacc58 100644 --- a/src/mbgl/shaders/symbol_sdf.cpp +++ b/src/mbgl/shaders/symbol_sdf.cpp @@ -9,11 +9,9 @@ const char* symbol_sdf::name = "symbol_sdf"; const char* symbol_sdf::vertexSource = R"MBGL_SHADER( const float PI = 3.141592653589793; -// NOTE: the a_data attribute in this shader is manually bound (see https://github.com/mapbox/mapbox-gl-js/issues/4607). -// If removing or renaming a_data, revisit the manual binding in painter.js accordingly. attribute vec4 a_pos_offset; -attribute vec2 a_label_pos; attribute vec4 a_data; +attribute vec3 a_projected_pos; // contents of a_size vary based on the type of property value // used for {text,icon}-size. @@ -21,17 +19,14 @@ attribute vec4 a_data; // For source functions, we bind only one value per vertex: the value of {text,icon}-size evaluated for the current feature. // For composite functions: // [ text-size(lowerZoomStop, feature), -// text-size(upperZoomStop, feature), -// layoutSize == text-size(layoutZoomLevel, feature) ] -attribute vec3 a_size; +// text-size(upperZoomStop, feature) ] uniform bool u_is_size_zoom_constant; uniform bool u_is_size_feature_constant; uniform highp float u_size_t; // used to interpolate between zoom stops when size is a composite function uniform highp float u_size; // used when size is both zoom and feature constant -uniform highp float u_layout_size; // used when size is feature constant -#ifdef HAS_UNIFORM_u_fill_color +#ifndef HAS_UNIFORM_u_fill_color uniform lowp float a_fill_color_t; attribute highp vec4 a_fill_color; varying highp vec4 fill_color; @@ -39,8 +34,7 @@ varying highp vec4 fill_color; uniform highp vec4 u_fill_color; #endif - -#ifdef HAS_UNIFORM_u_halo_color +#ifndef HAS_UNIFORM_u_halo_color uniform lowp float a_halo_color_t; attribute highp vec4 a_halo_color; varying highp vec4 halo_color; @@ -48,8 +42,7 @@ varying highp vec4 halo_color; uniform highp vec4 u_halo_color; #endif - -#ifdef HAS_UNIFORM_u_opacity +#ifndef HAS_UNIFORM_u_opacity uniform lowp float a_opacity_t; attribute lowp vec2 a_opacity; varying lowp float opacity; @@ -57,8 +50,7 @@ varying lowp float opacity; uniform lowp float u_opacity; #endif - -#ifdef HAS_UNIFORM_u_halo_width +#ifndef HAS_UNIFORM_u_halo_width uniform lowp float a_halo_width_t; attribute lowp vec2 a_halo_width; varying lowp float halo_width; @@ -66,8 +58,7 @@ varying lowp float halo_width; uniform lowp float u_halo_width; #endif - -#ifdef HAS_UNIFORM_u_halo_blur +#ifndef HAS_UNIFORM_u_halo_blur uniform lowp float a_halo_blur_t; attribute lowp vec2 a_halo_blur; varying lowp float halo_blur; @@ -75,168 +66,100 @@ varying lowp float halo_blur; uniform lowp float u_halo_blur; #endif - -// matrix is for the vertex position. uniform mat4 u_matrix; +uniform mat4 u_label_plane_matrix; +uniform mat4 u_gl_coord_matrix; uniform bool u_is_text; -uniform highp float u_zoom; -uniform bool u_rotate_with_map; uniform bool u_pitch_with_map; uniform highp float u_pitch; -uniform highp float u_bearing; -uniform highp float u_aspect_ratio; uniform highp float u_camera_to_center_distance; -uniform highp float u_max_camera_distance; uniform highp float u_collision_y_stretch; -uniform vec2 u_extrude_scale; uniform vec2 u_texsize; -varying vec2 v_tex; -varying vec2 v_fade_tex; -varying float v_gamma_scale; -varying float v_size; - -// Used below to move the vertex out of the clip space for when the current -// zoom is out of the glyph's zoom range. -highp float clipUnusedGlyphAngles(const highp float render_size, - const highp float layout_size, - const highp float min_zoom, - const highp float max_zoom) { - highp float zoom_adjust = log2(render_size / layout_size); - highp float adjusted_zoom = (u_zoom - zoom_adjust) * 10.0; - // result: 0 if min_zoom <= adjusted_zoom < max_zoom, and 1 otherwise - return 2.0 - step(min_zoom, adjusted_zoom) - (1.0 - step(max_zoom, adjusted_zoom)); -} +varying vec4 v_data0; +varying vec2 v_data1; void main() { - + #ifndef HAS_UNIFORM_u_fill_color fill_color = unpack_mix_vec4(a_fill_color, a_fill_color_t); #else highp vec4 fill_color = u_fill_color; #endif - #ifndef HAS_UNIFORM_u_halo_color halo_color = unpack_mix_vec4(a_halo_color, a_halo_color_t); #else highp vec4 halo_color = u_halo_color; #endif - #ifndef HAS_UNIFORM_u_opacity opacity = unpack_mix_vec2(a_opacity, a_opacity_t); #else lowp float opacity = u_opacity; #endif - #ifndef HAS_UNIFORM_u_halo_width halo_width = unpack_mix_vec2(a_halo_width, a_halo_width_t); #else lowp float halo_width = u_halo_width; #endif - #ifndef HAS_UNIFORM_u_halo_blur halo_blur = unpack_mix_vec2(a_halo_blur, a_halo_blur_t); #else lowp float halo_blur = u_halo_blur; #endif - vec2 a_pos = a_pos_offset.xy; vec2 a_offset = a_pos_offset.zw; vec2 a_tex = a_data.xy; + vec2 a_size = a_data.zw; + + highp vec2 angle_labelminzoom = unpack_float(a_projected_pos[2]); + highp float segment_angle = -angle_labelminzoom[0] / 255.0 * 2.0 * PI; + mediump float a_labelminzoom = angle_labelminzoom[1]; + float size; - highp vec2 label_data = unpack_float(a_data[2]); - highp float a_labelminzoom = label_data[0]; - highp float a_lineangle = (label_data[1] / 256.0 * 2.0 * PI); - highp vec2 a_zoom = unpack_float(a_data[3]); - highp float a_minzoom = a_zoom[0]; - highp float a_maxzoom = a_zoom[1]; - - // In order to accommodate placing labels around corners in - // symbol-placement: line, each glyph in a label could have multiple - // "quad"s only one of which should be shown at a given zoom level. - // The min/max zoom assigned to each quad is based on the font size at - // the vector tile's zoom level, which might be different than at the - // currently rendered zoom level if text-size is zoom-dependent. - // Thus, we compensate for this difference by calculating an adjustment - // based on the scale of rendered text size relative to layout text size. - highp float layoutSize; if (!u_is_size_zoom_constant && !u_is_size_feature_constant) { - v_size = mix(a_size[0], a_size[1], u_size_t) / 10.0; - layoutSize = a_size[2] / 10.0; + size = mix(a_size[0], a_size[1], u_size_t) / 10.0; } else if (u_is_size_zoom_constant && !u_is_size_feature_constant) { - v_size = a_size[0] / 10.0; - layoutSize = v_size; + size = a_size[0] / 10.0; } else if (!u_is_size_zoom_constant && u_is_size_feature_constant) { - v_size = u_size; - layoutSize = u_layout_size; + size = u_size; } else { - v_size = u_size; - layoutSize = u_size; + size = u_size; } - float fontScale = u_is_text ? v_size / 24.0 : v_size; - - vec4 projectedPoint = u_matrix * vec4(a_label_pos, 0, 1); + vec4 projectedPoint = u_matrix * vec4(a_pos, 0, 1); highp float camera_to_anchor_distance = projectedPoint.w; - highp float perspective_ratio = 1.0 + 0.5*((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0); - - // pitch-alignment: map - // rotation-alignment: map | viewport - if (u_pitch_with_map) { - highp float angle = u_rotate_with_map ? a_lineangle : u_bearing; - highp float asin = sin(angle); - highp float acos = cos(angle); - mat2 RotationMatrix = mat2(acos, asin, -1.0 * asin, acos); - vec2 offset = RotationMatrix * a_offset; - vec2 extrude = fontScale * u_extrude_scale * perspective_ratio * (offset / 64.0); - - gl_Position = u_matrix * vec4(a_pos + extrude, 0, 1); - gl_Position.z += clipUnusedGlyphAngles(v_size*perspective_ratio, layoutSize, a_minzoom, a_maxzoom) * gl_Position.w; - // pitch-alignment: viewport - // rotation-alignment: map - } else if (u_rotate_with_map) { - // foreshortening factor to apply on pitched maps - // as a label goes from horizontal <=> vertical in angle - // it goes from 0% foreshortening to up to around 70% foreshortening - highp float pitchfactor = 1.0 - cos(u_pitch * sin(u_pitch * 0.75)); - - // use the lineangle to position points a,b along the line - // project the points and calculate the label angle in projected space - // this calculation allows labels to be rendered unskewed on pitched maps - vec4 a = u_matrix * vec4(a_pos, 0, 1); - vec4 b = u_matrix * vec4(a_pos + vec2(cos(a_lineangle), sin(a_lineangle)), 0, 1); - highp float angle = atan((b[1] / b[3] - a[1] / a[3]) / u_aspect_ratio, b[0] / b[3] - a[0] / a[3]); - highp float asin = sin(angle); - highp float acos = cos(angle); - mat2 RotationMatrix = mat2(acos, -1.0 * asin, asin, acos); - highp float foreshortening = (1.0 - pitchfactor) + (pitchfactor * cos(angle * 2.0)); - - vec2 offset = RotationMatrix * (vec2(foreshortening, 1.0) * a_offset); - vec2 extrude = fontScale * u_extrude_scale * perspective_ratio * (offset / 64.0); - - gl_Position = u_matrix * vec4(a_pos, 0, 1) + vec4(extrude, 0, 0); - gl_Position.z += clipUnusedGlyphAngles(v_size * perspective_ratio, layoutSize, a_minzoom, a_maxzoom) * gl_Position.w; - // pitch-alignment: viewport - // rotation-alignment: viewport - } else { - vec2 extrude = fontScale * u_extrude_scale * perspective_ratio * (a_offset / 64.0); - gl_Position = u_matrix * vec4(a_pos, 0, 1) + vec4(extrude, 0, 0); - } - - gl_Position.z += - step(u_max_camera_distance * u_camera_to_center_distance, camera_to_anchor_distance) * gl_Position.w; - - v_gamma_scale = gl_Position.w / perspective_ratio; - - v_tex = a_tex / u_texsize; + // If the label is pitched with the map, layout is done in pitched space, + // which makes labels in the distance smaller relative to viewport space. + // We counteract part of that effect by multiplying by the perspective ratio. + // If the label isn't pitched with the map, we do layout in viewport space, + // which makes labels in the distance larger relative to the features around + // them. We counteract part of that effect by dividing by the perspective ratio. + highp float distance_ratio = u_pitch_with_map ? + camera_to_anchor_distance / u_camera_to_center_distance : + u_camera_to_center_distance / camera_to_anchor_distance; + highp float perspective_ratio = 0.5 + 0.5 * distance_ratio; + + size *= perspective_ratio; + + float fontScale = u_is_text ? size / 24.0 : size; + + highp float angle_sin = sin(segment_angle); + highp float angle_cos = cos(segment_angle); + mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); + + vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0); + gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 64.0 * fontScale), 0.0, 1.0); + float gamma_scale = gl_Position.w; + + vec2 tex = a_tex / u_texsize; // incidence_stretch is the ratio of how much y space a label takes up on a tile while drawn perpendicular to the viewport vs // how much space it would take up if it were drawn flat on the tile // Using law of sines, camera_to_anchor/sin(ground_angle) = camera_to_center/sin(incidence_angle) @@ -256,8 +179,12 @@ void main() { highp float collision_adjustment = max(1.0, incidence_stretch / u_collision_y_stretch); // Floor to 1/10th zoom to dodge precision issues that can cause partially hidden labels - highp float perspective_zoom_adjust = floor(log2(perspective_ratio * collision_adjustment) * 10.0); - v_fade_tex = vec2((a_labelminzoom + perspective_zoom_adjust) / 255.0, 0.0); + highp float collision_perspective_ratio = 1.0 + 0.5*((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0); + highp float perspective_zoom_adjust = floor(log2(collision_perspective_ratio * collision_adjustment) * 10.0); + vec2 fade_tex = vec2((a_labelminzoom + perspective_zoom_adjust) / 255.0, 0.0); + + v_data0 = vec4(tex.x, tex.y, fade_tex.x, fade_tex.y); + v_data1 = vec2(gamma_scale, size); } )MBGL_SHADER"; @@ -273,73 +200,66 @@ varying highp vec4 fill_color; uniform highp vec4 u_fill_color; #endif - #ifndef HAS_UNIFORM_u_halo_color varying highp vec4 halo_color; #else uniform highp vec4 u_halo_color; #endif - #ifndef HAS_UNIFORM_u_opacity varying lowp float opacity; #else uniform lowp float u_opacity; #endif - #ifndef HAS_UNIFORM_u_halo_width varying lowp float halo_width; #else uniform lowp float u_halo_width; #endif - #ifndef HAS_UNIFORM_u_halo_blur varying lowp float halo_blur; #else uniform lowp float u_halo_blur; #endif - uniform sampler2D u_texture; uniform sampler2D u_fadetexture; uniform highp float u_gamma_scale; uniform bool u_is_text; -varying vec2 v_tex; -varying vec2 v_fade_tex; -varying float v_gamma_scale; -varying float v_size; +varying vec4 v_data0; +varying vec2 v_data1; void main() { - + #ifdef HAS_UNIFORM_u_fill_color highp vec4 fill_color = u_fill_color; #endif - #ifdef HAS_UNIFORM_u_halo_color highp vec4 halo_color = u_halo_color; #endif - #ifdef HAS_UNIFORM_u_opacity lowp float opacity = u_opacity; #endif - #ifdef HAS_UNIFORM_u_halo_width lowp float halo_width = u_halo_width; #endif - #ifdef HAS_UNIFORM_u_halo_blur lowp float halo_blur = u_halo_blur; #endif + vec2 tex = v_data0.xy; + vec2 fade_tex = v_data0.zw; + float gamma_scale = v_data1.x; + float size = v_data1.y; - float fontScale = u_is_text ? v_size / 24.0 : v_size; + float fontScale = u_is_text ? size / 24.0 : size; lowp vec4 color = fill_color; highp float gamma = EDGE_GAMMA / (fontScale * u_gamma_scale); @@ -350,9 +270,9 @@ void main() { buff = (6.0 - halo_width / fontScale) / SDF_PX; } - lowp float dist = texture2D(u_texture, v_tex).a; - lowp float fade_alpha = texture2D(u_fadetexture, v_fade_tex).a; - highp float gamma_scaled = gamma * v_gamma_scale; + lowp float dist = texture2D(u_texture, tex).a; + lowp float fade_alpha = texture2D(u_fadetexture, fade_tex).a; + highp float gamma_scaled = gamma * gamma_scale; highp float alpha = smoothstep(buff - gamma_scaled, buff + gamma_scaled, dist) * fade_alpha; gl_FragColor = color * (alpha * opacity); diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index ab10c5a6b75..7908ea4abc2 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -13,14 +13,9 @@ namespace mbgl { using namespace style; -const float globalMinScale = 0.5f; // underscale by 1 zoom level - -SymbolQuad getIconQuad(const Anchor& anchor, - const PositionedIcon& shapedIcon, - const GeometryCoordinates& line, +SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, const SymbolLayoutProperties::Evaluated& layout, const float layoutTextSize, - const style::SymbolPlacementType placement, const Shaping& shapedText) { const ImagePosition& image = shapedIcon.image(); @@ -71,18 +66,7 @@ SymbolQuad getIconQuad(const Anchor& anchor, bl = {left, bottom}; } - float angle = shapedIcon.angle(); - if (placement == style::SymbolPlacementType::Line) { - assert(static_cast(anchor.segment) < line.size()); - const GeometryCoordinate &prev= line[anchor.segment]; - if (anchor.point.y == prev.y && anchor.point.x == prev.x && - static_cast(anchor.segment + 1) < line.size()) { - const GeometryCoordinate &next= line[anchor.segment + 1]; - angle += std::atan2(anchor.point.y - next.y, anchor.point.x - next.x) + M_PI; - } else { - angle += std::atan2(anchor.point.y - prev.y, anchor.point.x - prev.x); - } - } + const float angle = shapedIcon.angle(); if (angle) { // Compute the transformation matrix. @@ -104,212 +88,19 @@ SymbolQuad getIconQuad(const Anchor& anchor, static_cast(image.textureRect.h + border * 2) }; - return SymbolQuad { tl, tr, bl, br, textureRect, 0, 0, anchor.point, globalMinScale, std::numeric_limits::infinity(), shapedText.writingMode }; -} - -struct GlyphInstance { - explicit GlyphInstance(Point anchorPoint_) : anchorPoint(std::move(anchorPoint_)) {} - explicit GlyphInstance(Point anchorPoint_, bool upsideDown_, float minScale_, float maxScale_, - float angle_) - : anchorPoint(std::move(anchorPoint_)), upsideDown(upsideDown_), minScale(minScale_), maxScale(maxScale_), angle(angle_) {} - - const Point anchorPoint; - const bool upsideDown = false; - const float minScale = globalMinScale; - const float maxScale = std::numeric_limits::infinity(); - const float angle = 0.0f; -}; - -using GlyphInstances = std::vector; - -struct VirtualSegment { - Point anchor; - Point end; - size_t index; - float minScale; - float maxScale; -}; - -inline void insertSegmentGlyph(std::back_insert_iterator glyphs, - const VirtualSegment& virtualSegment, - const bool glyphIsLogicallyForward, - const bool upsideDown) { - float segmentAngle = std::atan2(virtualSegment.end.y - virtualSegment.anchor.y, virtualSegment.end.x - virtualSegment.anchor.x); - // If !glyphIsLogicallyForward, we're iterating through the segments in reverse logical order as well, so we need to flip the segment angle - float glyphAngle = glyphIsLogicallyForward ? segmentAngle : segmentAngle + M_PI; - - // Insert a glyph rotated at this angle for display in the range from [scale, previous(larger) scale]. - glyphs = GlyphInstance{ - /* anchor */ virtualSegment.anchor, - /* upsideDown */ upsideDown, - /* minScale */ virtualSegment.minScale, - /* maxScale */ virtualSegment.maxScale, - /* angle */ static_cast(std::fmod((glyphAngle + 2.0 * M_PI), (2.0 * M_PI)))}; -} - -/** - Given the distance along the line from the label anchor to the beginning of the current segment, - project a "virtual anchor" point at the same distance along the line extending out from this segment. - - B <-- beginning of current segment -* . . . . . . . *--------* E <-- end of current segment -VA | - / VA = "virtual segment anchor" - / - ---*-----` - A = label anchor - - Distance _along line_ from A to B == straight-line distance from VA to B. - */ -inline Point getVirtualSegmentAnchor(const Point& segmentBegin, const Point& segmentEnd, float distanceFromAnchorToSegmentBegin) { - Point segmentDirectionUnitVector = util::normal(segmentBegin, segmentEnd); - return segmentBegin - (segmentDirectionUnitVector * distanceFromAnchorToSegmentBegin); -} - -/* - Given the segment joining `segmentAnchor` and `segmentEnd` and a desired offset - `glyphDistanceFromAnchor` at which a glyph is to be placed, calculate the minimum - "scale" at which the glyph will fall on the segment (i.e., not past the end) - - "Scale" here refers to the ratio between the *rendered* zoom level and the text-layout - zoom level, which is 1 + (source tile's zoom level). `glyphDistanceFromAnchor`, although - passed in units consistent with the text-layout zoom level, is based on text size. So - when the tile is being rendered at z < text-layout zoom, the glyph's actual distance from - the anchor is larger relative to the segment's length than at layout time: - - - GLYPH - z == layout-zoom, scale == 1: segmentAnchor *--------------^-------------* segmentEnd - z == layout-zoom - 1, scale == 0.5: segmentAnchor *--------------^* segmentEnd - - <--------------> - Anchor-to-glyph distance stays visually fixed, - so it changes relative to the segment. -*/ -inline float getMinScaleForSegment(const float glyphDistanceFromAnchor, - const Point& segmentAnchor, - const Point& segmentEnd) { - const auto distanceFromAnchorToEnd = util::dist(segmentAnchor, segmentEnd); - return glyphDistanceFromAnchor / distanceFromAnchorToEnd; -} - -inline Point getSegmentEnd(const bool glyphIsLogicallyForward, - const GeometryCoordinates& line, - const size_t segmentIndex) { - return convertPoint(glyphIsLogicallyForward ? line[segmentIndex+1] : line[segmentIndex]); -} - -optional getNextVirtualSegment(const VirtualSegment& previousVirtualSegment, - const GeometryCoordinates& line, - const float glyphDistanceFromAnchor, - const bool glyphIsLogicallyForward) { - auto nextSegmentBegin = previousVirtualSegment.end; - - auto end = nextSegmentBegin; - size_t index = previousVirtualSegment.index; - - // skip duplicate nodes - while (end == nextSegmentBegin) { - // look ahead by 2 points in the line because the segment index refers to the beginning - // of the segment, and we need an endpoint too - if (glyphIsLogicallyForward && (index + 2 < line.size())) { - index += 1; - } else if (!glyphIsLogicallyForward && index != 0) { - index -= 1; - } else { - return {}; - } - - end = getSegmentEnd(glyphIsLogicallyForward, line, index); - } - - const auto anchor = getVirtualSegmentAnchor(nextSegmentBegin, end, - util::dist(previousVirtualSegment.anchor, - previousVirtualSegment.end)); - return VirtualSegment { - anchor, - end, - index, - getMinScaleForSegment(glyphDistanceFromAnchor, anchor, end), - previousVirtualSegment.minScale - }; -} - -/* - Given (1) a glyph positioned relative to an anchor point and (2) a line to follow, - calculates which segment of the line the glyph will fall on for each possible - scale range, and for each range produces a "virtual" anchor point and an angle that will - place the glyph on the right segment and rotated to the correct angle. - - Because one glyph quad is made ahead of time for each possible orientation, the - symbol_sdf shader can quickly handle changing layout as we zoom in and out - - If the "keepUpright" property is set, we call getLineGlyphs twice (once upright and - once "upside down"). This will generate two sets of glyphs following the line in opposite - directions. Later, SymbolLayout::place will look at the glyphs and based on the placement - angle determine if their original anchor was "upright" or not -- based on that, it throws - away one set of glyphs or the other (this work has to be done in the CPU, but it's just a - filter so it's fast) - */ -void getLineGlyphs(std::back_insert_iterator glyphs, - Anchor& anchor, - float glyphHorizontalOffsetFromAnchor, - const GeometryCoordinates& line, - size_t anchorSegment, - bool upsideDown) { - assert(line.size() > anchorSegment+1); - - // This is true if the glyph is "logically forward" of the anchor point, based on the ordering of line segments - // The actual angle of the line is irrelevant - // If "upsideDown" is set, everything is flipped - const bool glyphIsLogicallyForward = (glyphHorizontalOffsetFromAnchor >= 0) ^ upsideDown; - const float glyphDistanceFromAnchor = std::fabs(glyphHorizontalOffsetFromAnchor); - - const auto initialSegmentEnd = getSegmentEnd(glyphIsLogicallyForward, line, anchorSegment); - VirtualSegment virtualSegment = { - anchor.point, - initialSegmentEnd, - anchorSegment, - getMinScaleForSegment(glyphDistanceFromAnchor, anchor.point, initialSegmentEnd), - std::numeric_limits::infinity() - }; - - while (true) { - insertSegmentGlyph(glyphs, - virtualSegment, - glyphIsLogicallyForward, - upsideDown); - - if (virtualSegment.minScale <= anchor.scale) { - // No need to calculate below the scale where the label starts showing - return; - } - - optional nextVirtualSegment = getNextVirtualSegment(virtualSegment, - line, - glyphDistanceFromAnchor, - glyphIsLogicallyForward); - if (!nextVirtualSegment) { - // There are no more segments, so we can't fit this glyph on the line at a lower scale - // This implies we can't show the label at all at lower scale, so we update the anchor's min scale - anchor.scale = virtualSegment.minScale; - return; - } else { - virtualSegment = *nextVirtualSegment; - } - } - + return SymbolQuad { tl, tr, bl, br, textureRect, shapedText.writingMode, { 0.0f, 0.0f } }; } -SymbolQuads getGlyphQuads(Anchor& anchor, - const Shaping& shapedText, - const float boxScale, - const GeometryCoordinates& line, +SymbolQuads getGlyphQuads(const Shaping& shapedText, const SymbolLayoutProperties::Evaluated& layout, const style::SymbolPlacementType placement, const GlyphPositionMap& positions) { const float textRotate = layout.get() * util::DEG2RAD; - const bool keepUpright = layout.get(); + + const float oneEm = 24.0; + std::array textOffset = layout.get(); + textOffset[0] *= oneEm; + textOffset[1] *= oneEm; SymbolQuads quads; @@ -320,67 +111,55 @@ SymbolQuads getGlyphQuads(Anchor& anchor, const GlyphPosition& glyph = positionsIt->second; const Rect& rect = glyph.rect; - const float centerX = (positionedGlyph.x + glyph.metrics.advance / 2.0f) * boxScale; - - GlyphInstances glyphInstances; - if (placement == style::SymbolPlacementType::Line) { - getLineGlyphs(std::back_inserter(glyphInstances), anchor, centerX, line, anchor.segment, false); - if (keepUpright) - getLineGlyphs(std::back_inserter(glyphInstances), anchor, centerX, line, anchor.segment, true); - } else { - glyphInstances.emplace_back(GlyphInstance{anchor.point}); - } // The rects have an addditional buffer that is not included in their size; const float glyphPadding = 1.0f; const float rectBuffer = 3.0f + glyphPadding; - const float x1 = positionedGlyph.x + glyph.metrics.left - rectBuffer; - const float y1 = positionedGlyph.y - glyph.metrics.top - rectBuffer; + const float halfAdvance = glyph.metrics.advance / 2.0; + const bool alongLine = layout.get() == AlignmentType::Map && placement == SymbolPlacementType::Line; + + const Point glyphOffset = alongLine ? + Point{ positionedGlyph.x + halfAdvance, positionedGlyph.y } : + Point{ 0.0f, 0.0f }; + + const Point builtInOffset = alongLine ? + Point{ 0.0f, 0.0f } : + Point{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] }; + + + const float x1 = glyph.metrics.left - rectBuffer - halfAdvance + builtInOffset.x; + const float y1 = -glyph.metrics.top - rectBuffer + builtInOffset.y; const float x2 = x1 + rect.w; const float y2 = y1 + rect.h; - const Point center{positionedGlyph.x, static_cast(static_cast(glyph.metrics.advance) / 2.0)}; + const Point center{builtInOffset.x - halfAdvance, static_cast(static_cast(glyph.metrics.advance) / 2.0)}; - Point otl{x1, y1}; - Point otr{x2, y1}; - Point obl{x1, y2}; - Point obr{x2, y2}; + Point tl{x1, y1}; + Point tr{x2, y1}; + Point bl{x1, y2}; + Point br{x2, y2}; if (positionedGlyph.angle != 0) { - otl = util::rotate(otl - center, positionedGlyph.angle) + center; - otr = util::rotate(otr - center, positionedGlyph.angle) + center; - obl = util::rotate(obl - center, positionedGlyph.angle) + center; - obr = util::rotate(obr - center, positionedGlyph.angle) + center; + tl = util::rotate(tl - center, positionedGlyph.angle) + center; + tr = util::rotate(tr - center, positionedGlyph.angle) + center; + bl = util::rotate(bl - center, positionedGlyph.angle) + center; + br = util::rotate(br - center, positionedGlyph.angle) + center; } - for (const GlyphInstance &instance : glyphInstances) { - Point tl = otl; - Point tr = otr; - Point bl = obl; - Point br = obr; + if (textRotate) { + // Compute the transformation matrix. + float angle_sin = std::sin(textRotate); + float angle_cos = std::cos(textRotate); + std::array matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}}; - if (textRotate) { - // Compute the transformation matrix. - float angle_sin = std::sin(textRotate); - float angle_cos = std::cos(textRotate); - std::array matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}}; - - tl = util::matrixMultiply(matrix, tl); - tr = util::matrixMultiply(matrix, tr); - bl = util::matrixMultiply(matrix, bl); - br = util::matrixMultiply(matrix, br); - } - - // Prevent label from extending past the end of the line - const float glyphMinScale = std::max(instance.minScale, anchor.scale); - - // All the glyphs for a label are tagged with either the "right side up" or "upside down" anchor angle, - // which is used at placement time to determine which set to show - const float anchorAngle = std::fmod((anchor.angle + (instance.upsideDown ? M_PI : 0.0) + 2 * M_PI), (2 * M_PI)); - const float glyphAngle = std::fmod((instance.angle + (instance.upsideDown ? M_PI : 0.0) + 2 * M_PI), (2 * M_PI)); - quads.emplace_back(tl, tr, bl, br, rect, anchorAngle, glyphAngle, instance.anchorPoint, glyphMinScale, instance.maxScale, shapedText.writingMode); + tl = util::matrixMultiply(matrix, tl); + tr = util::matrixMultiply(matrix, tr); + bl = util::matrixMultiply(matrix, bl); + br = util::matrixMultiply(matrix, br); } + + quads.emplace_back(tl, tr, bl, br, rect, shapedText.writingMode, glyphOffset); } return quads; diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index b29f6b0ad3b..33d003c9354 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -19,50 +19,33 @@ class SymbolQuad { Point bl_, Point br_, Rect tex_, - float anchorAngle_, - float glyphAngle_, - Point anchorPoint_, - float minScale_, - float maxScale_, - WritingModeType writingMode_) + WritingModeType writingMode_, + Point glyphOffset_) : tl(std::move(tl_)), tr(std::move(tr_)), bl(std::move(bl_)), br(std::move(br_)), tex(std::move(tex_)), - anchorAngle(anchorAngle_), - glyphAngle(glyphAngle_), - anchorPoint(std::move(anchorPoint_)), - minScale(minScale_), - maxScale(maxScale_), - writingMode(writingMode_) {} + writingMode(writingMode_), + glyphOffset(glyphOffset_) {} Point tl; Point tr; Point bl; Point br; Rect tex; - float anchorAngle, glyphAngle; - Point anchorPoint; - float minScale; - float maxScale; WritingModeType writingMode; + Point glyphOffset; }; using SymbolQuads = std::vector; -SymbolQuad getIconQuad(const Anchor& anchor, - const PositionedIcon& shapedIcon, - const GeometryCoordinates& line, +SymbolQuad getIconQuad(const PositionedIcon& shapedIcon, const style::SymbolLayoutProperties::Evaluated&, const float layoutTextSize, - style::SymbolPlacementType placement, const Shaping& shapedText); -SymbolQuads getGlyphQuads(Anchor& anchor, - const Shaping& shapedText, - const float boxScale, - const GeometryCoordinates& line, +SymbolQuads getGlyphQuads(const Shaping& shapedText, const style::SymbolLayoutProperties::Evaluated&, style::SymbolPlacementType placement, const GlyphPositionMap& positions); diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 338abe2e431..c81f25d4eb5 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -27,12 +27,9 @@ void align(Shaping& shaping, const float verticalAlign, const float maxLineLength, const float lineHeight, - const std::size_t lineCount, - const Point& translate) { - const float shiftX = - (justify - horizontalAlign) * maxLineLength + ::round(translate.x); - const float shiftY = - (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y); + const std::size_t lineCount) { + const float shiftX = (justify - horizontalAlign) * maxLineLength; + const float shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight; for (auto& glyph : shaping.positionedGlyphs) { glyph.x += shiftX; @@ -205,7 +202,6 @@ void shapeLines(Shaping& shaping, const float horizontalAlign, const float verticalAlign, const float justify, - const Point& translate, const float verticalHeight, const WritingModeType writingMode, const Glyphs& glyphs) { @@ -259,7 +255,7 @@ void shapeLines(Shaping& shaping, } align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, - lines.size(), translate); + lines.size()); const uint32_t height = lines.size() * lineHeight; // Calculate the bounding box @@ -288,7 +284,7 @@ const Shaping getShaping(const std::u16string& logicalInput, determineLineBreaks(logicalInput, spacing, maxWidth, writingMode, glyphs)); shapeLines(shaping, reorderedLines, spacing, lineHeight, horizontalAlign, verticalAlign, - justify, translate, verticalHeight, writingMode, glyphs); + justify, verticalHeight, writingMode, glyphs); return shaping; } diff --git a/test/programs/symbol_program.test.cpp b/test/programs/symbol_program.test.cpp index ef1e71c2698..62a2e58d7b3 100644 --- a/test/programs/symbol_program.test.cpp +++ b/test/programs/symbol_program.test.cpp @@ -10,7 +10,6 @@ TEST(SymbolProgram, SymbolSizeBinder) { EXPECT_EQ(uniformValues.get().t, true); EXPECT_EQ(uniformValues.get().t, true); EXPECT_EQ(uniformValues.get().t, 12.0f); - EXPECT_EQ(uniformValues.get().t, 12.0f); binder = SymbolSizeBinder::create(1.0f, style::CameraFunction(style::ExponentialStops({ {0.0f, 8.0f}, @@ -20,7 +19,6 @@ TEST(SymbolProgram, SymbolSizeBinder) { EXPECT_EQ(uniformValues.get().t, false); EXPECT_EQ(uniformValues.get().t, true); EXPECT_EQ(uniformValues.get().t, 9.5f); - EXPECT_EQ(uniformValues.get().t, 10.0f); binder = SymbolSizeBinder::create(0.0f, style::CameraFunction(style::ExponentialStops({ {1.0f, 8.0f}, @@ -30,7 +28,6 @@ TEST(SymbolProgram, SymbolSizeBinder) { EXPECT_EQ(uniformValues.get().t, false); EXPECT_EQ(uniformValues.get().t, true); EXPECT_EQ(uniformValues.get().t, 8.0f); - EXPECT_EQ(uniformValues.get().t, 8.0f); binder = SymbolSizeBinder::create(12.0f, style::CameraFunction(style::ExponentialStops({ {1.0f, 8.0f}, @@ -40,7 +37,6 @@ TEST(SymbolProgram, SymbolSizeBinder) { EXPECT_EQ(uniformValues.get().t, false); EXPECT_EQ(uniformValues.get().t, true); EXPECT_EQ(uniformValues.get().t, 18.0f); - EXPECT_EQ(uniformValues.get().t, 18.0f); binder = SymbolSizeBinder::create(0.0f, style::SourceFunction("x", style::ExponentialStops({ {1.0f, 8.0f}, diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp index efc3912aaac..f24b01ca875 100644 --- a/test/text/quads.test.cpp +++ b/test/text/quads.test.cpp @@ -23,10 +23,8 @@ TEST(getIconQuads, normal) { Shaping shapedText; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 16.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 16.0f, shapedText); - EXPECT_EQ(quad.anchorPoint.x, 2); - EXPECT_EQ(quad.anchorPoint.y, 3); EXPECT_EQ(quad.tl.x, -14); EXPECT_EQ(quad.tl.y, -10); EXPECT_EQ(quad.tr.x, 1); @@ -35,9 +33,6 @@ TEST(getIconQuads, normal) { EXPECT_EQ(quad.bl.y, 1); EXPECT_EQ(quad.br.x, 1); EXPECT_EQ(quad.br.y, 1); - EXPECT_EQ(quad.anchorAngle, 0.0f); - EXPECT_EQ(quad.glyphAngle, 0.0f); - EXPECT_EQ(quad.minScale, 0.5f); } TEST(getIconQuads, style) { @@ -61,10 +56,8 @@ TEST(getIconQuads, style) { { SymbolLayoutProperties::Evaluated layout; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); - EXPECT_EQ(quad.anchorPoint.x, 0); - EXPECT_EQ(quad.anchorPoint.y, 0); EXPECT_EQ(quad.tl.x, -19.5); EXPECT_EQ(quad.tl.y, -19.5); EXPECT_EQ(quad.tr.x, 0.5); @@ -73,9 +66,6 @@ TEST(getIconQuads, style) { EXPECT_EQ(quad.bl.y, 0.5); EXPECT_EQ(quad.br.x, 0.5); EXPECT_EQ(quad.br.y, 0.5); - EXPECT_EQ(quad.anchorAngle, 0.0f); - EXPECT_EQ(quad.glyphAngle, 0.0f); - EXPECT_EQ(quad.minScale, 0.5f); } // width @@ -84,7 +74,7 @@ TEST(getIconQuads, style) { layout.get() = 24.0f; layout.get() = IconTextFitType::Width; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 24.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 24.0f, shapedText); EXPECT_EQ(quad.tl.x, -60); EXPECT_EQ(quad.tl.y, 0); @@ -102,7 +92,7 @@ TEST(getIconQuads, style) { layout.get() = 12.0f; layout.get() = IconTextFitType::Width; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); EXPECT_EQ(quad.tl.x, -30); EXPECT_EQ(quad.tl.y, -5); @@ -124,7 +114,7 @@ TEST(getIconQuads, style) { layout.get()[2] = 5.0f; layout.get()[3] = 10.0f; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); EXPECT_EQ(quad.tl.x, -40); EXPECT_EQ(quad.tl.y, -10); @@ -142,7 +132,7 @@ TEST(getIconQuads, style) { layout.get() = 24.0f; layout.get() = IconTextFitType::Height; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 24.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 24.0f, shapedText); EXPECT_EQ(quad.tl.x, -30); EXPECT_EQ(quad.tl.y, -10); @@ -160,7 +150,7 @@ TEST(getIconQuads, style) { layout.get() = 12.0f; layout.get() = IconTextFitType::Height; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); EXPECT_EQ(quad.tl.x, -20); EXPECT_EQ(quad.tl.y, -5); @@ -182,7 +172,7 @@ TEST(getIconQuads, style) { layout.get()[2] = 5.0f; layout.get()[3] = 10.0f; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); EXPECT_EQ(quad.tl.x, -30); EXPECT_EQ(quad.tl.y, -10); @@ -200,7 +190,7 @@ TEST(getIconQuads, style) { layout.get() = 24.0f; layout.get() = IconTextFitType::Both; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 24.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 24.0f, shapedText); EXPECT_EQ(quad.tl.x, -60); EXPECT_EQ(quad.tl.y, -10); @@ -218,7 +208,7 @@ TEST(getIconQuads, style) { layout.get() = 12.0f; layout.get() = IconTextFitType::Both; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); EXPECT_EQ(quad.tl.x, -30); EXPECT_EQ(quad.tl.y, -5); @@ -240,7 +230,7 @@ TEST(getIconQuads, style) { layout.get()[2] = 5.0f; layout.get()[3] = 10.0f; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); EXPECT_EQ(quad.tl.x, -40); EXPECT_EQ(quad.tl.y, -10); @@ -262,7 +252,7 @@ TEST(getIconQuads, style) { layout.get()[2] = 10.0f; layout.get()[3] = 15.0f; SymbolQuad quad = - getIconQuad(anchor, shapedIcon, line, layout, 12.0f, SymbolPlacementType::Point, shapedText); + getIconQuad(shapedIcon, layout, 12.0f, shapedText); EXPECT_EQ(quad.tl.x, -45); EXPECT_EQ(quad.tl.y, -5);