summaryrefslogtreecommitdiff
path: root/src/mbgl/renderer/buckets
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/renderer/buckets')
-rw-r--r--src/mbgl/renderer/buckets/circle_bucket.cpp115
-rw-r--r--src/mbgl/renderer/buckets/circle_bucket.hpp40
-rw-r--r--src/mbgl/renderer/buckets/debug_bucket.cpp83
-rw-r--r--src/mbgl/renderer/buckets/debug_bucket.hpp41
-rw-r--r--src/mbgl/renderer/buckets/fill_bucket.cpp137
-rw-r--r--src/mbgl/renderer/buckets/fill_bucket.hpp42
-rw-r--r--src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp169
-rw-r--r--src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp37
-rw-r--r--src/mbgl/renderer/buckets/line_bucket.cpp502
-rw-r--r--src/mbgl/renderer/buckets/line_bucket.hpp67
-rw-r--r--src/mbgl/renderer/buckets/raster_bucket.cpp110
-rw-r--r--src/mbgl/renderer/buckets/raster_bucket.hpp41
-rw-r--r--src/mbgl/renderer/buckets/symbol_bucket.cpp79
-rw-r--r--src/mbgl/renderer/buckets/symbol_bucket.hpp99
14 files changed, 1562 insertions, 0 deletions
diff --git a/src/mbgl/renderer/buckets/circle_bucket.cpp b/src/mbgl/renderer/buckets/circle_bucket.cpp
new file mode 100644
index 0000000000..04126990b3
--- /dev/null
+++ b/src/mbgl/renderer/buckets/circle_bucket.cpp
@@ -0,0 +1,115 @@
+#include <mbgl/renderer/buckets/circle_bucket.hpp>
+#include <mbgl/renderer/bucket_parameters.hpp>
+#include <mbgl/programs/circle_program.hpp>
+#include <mbgl/style/layers/circle_layer_impl.hpp>
+#include <mbgl/renderer/layers/render_circle_layer.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/math.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+CircleBucket::CircleBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers)
+ : mode(parameters.mode) {
+ for (const auto& layer : layers) {
+ paintPropertyBinders.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(layer->getID()),
+ std::forward_as_tuple(
+ layer->as<RenderCircleLayer>()->evaluated,
+ parameters.tileID.overscaledZ));
+ }
+}
+
+void CircleBucket::upload(gl::Context& context) {
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ indexBuffer = context.createIndexBuffer(std::move(triangles));
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.upload(context);
+ }
+
+ uploaded = true;
+}
+
+bool CircleBucket::hasData() const {
+ return !segments.empty();
+}
+
+void CircleBucket::addFeature(const GeometryTileFeature& feature,
+ const GeometryCollection& geometry) {
+ constexpr const uint16_t vertexLength = 4;
+
+ for (auto& circle : geometry) {
+ for(auto& point : circle) {
+ auto x = point.x;
+ auto y = point.y;
+
+ // Do not include points that are outside the tile boundaries.
+ // Include all points in Still mode. You need to include points from
+ // neighbouring tiles so that they are not clipped at tile boundaries.
+ if ((mode != MapMode::Still) &&
+ (x < 0 || x >= util::EXTENT || y < 0 || y >= util::EXTENT)) continue;
+
+ if (segments.empty() || segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
+ // Move to a new segments because the old one can't hold the geometry.
+ segments.emplace_back(vertices.vertexSize(), triangles.indexSize());
+ }
+
+ // this geometry will be of the Point type, and we'll derive
+ // two triangles from it.
+ //
+ // ┌─────────┐
+ // │ 4 3 │
+ // │ │
+ // │ 1 2 │
+ // └─────────┘
+ //
+ vertices.emplace_back(CircleProgram::vertex(point, -1, -1)); // 1
+ vertices.emplace_back(CircleProgram::vertex(point, 1, -1)); // 2
+ vertices.emplace_back(CircleProgram::vertex(point, 1, 1)); // 3
+ vertices.emplace_back(CircleProgram::vertex(point, -1, 1)); // 4
+
+ auto& segment = segments.back();
+ assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ uint16_t index = segment.vertexLength;
+
+ // 1, 2, 3
+ // 1, 4, 3
+ triangles.emplace_back(index, index + 1, index + 2);
+ triangles.emplace_back(index, index + 3, index + 2);
+
+ segment.vertexLength += vertexLength;
+ segment.indexLength += 6;
+ }
+ }
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.populateVertexVectors(feature, vertices.vertexSize());
+ }
+}
+
+template <class Property>
+static float get(const RenderCircleLayer& layer, const std::map<std::string, CircleProgram::PaintPropertyBinders>& paintPropertyBinders) {
+ auto it = paintPropertyBinders.find(layer.getID());
+ if (it == paintPropertyBinders.end() || !it->second.statistics<Property>().max()) {
+ return layer.evaluated.get<Property>().constantOr(Property::defaultValue());
+ } else {
+ return *it->second.statistics<Property>().max();
+ }
+}
+
+float CircleBucket::getQueryRadius(const RenderLayer& layer) const {
+ if (!layer.is<RenderCircleLayer>()) {
+ return 0;
+ }
+
+ auto circleLayer = layer.as<RenderCircleLayer>();
+
+ float radius = get<CircleRadius>(*circleLayer, paintPropertyBinders);
+ auto translate = circleLayer->evaluated.get<CircleTranslate>();
+ return radius + util::length(translate[0], translate[1]);
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/circle_bucket.hpp b/src/mbgl/renderer/buckets/circle_bucket.hpp
new file mode 100644
index 0000000000..78b6351bcb
--- /dev/null
+++ b/src/mbgl/renderer/buckets/circle_bucket.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/map/mode.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/programs/segment.hpp>
+#include <mbgl/programs/circle_program.hpp>
+#include <mbgl/style/layers/circle_layer_properties.hpp>
+
+namespace mbgl {
+
+class BucketParameters;
+
+class CircleBucket : public Bucket {
+public:
+ CircleBucket(const BucketParameters&, const std::vector<const RenderLayer*>&);
+
+ void addFeature(const GeometryTileFeature&,
+ const GeometryCollection&) override;
+ bool hasData() const override;
+
+ void upload(gl::Context&) override;
+
+ float getQueryRadius(const RenderLayer&) const override;
+
+ gl::VertexVector<CircleLayoutVertex> vertices;
+ gl::IndexVector<gl::Triangles> triangles;
+ SegmentVector<CircleAttributes> segments;
+
+ optional<gl::VertexBuffer<CircleLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+
+ std::map<std::string, CircleProgram::PaintPropertyBinders> paintPropertyBinders;
+
+ const MapMode mode;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/debug_bucket.cpp b/src/mbgl/renderer/buckets/debug_bucket.cpp
new file mode 100644
index 0000000000..53c751c443
--- /dev/null
+++ b/src/mbgl/renderer/buckets/debug_bucket.cpp
@@ -0,0 +1,83 @@
+#include <mbgl/renderer/buckets/debug_bucket.hpp>
+#include <mbgl/programs/fill_program.hpp>
+#include <mbgl/geometry/debug_font_data.hpp>
+#include <mbgl/tile/tile_id.hpp>
+#include <mbgl/util/string.hpp>
+
+#include <cmath>
+#include <string>
+#include <vector>
+
+namespace mbgl {
+
+DebugBucket::DebugBucket(const OverscaledTileID& id,
+ const bool renderable_,
+ const bool complete_,
+ optional<Timestamp> modified_,
+ optional<Timestamp> expires_,
+ MapDebugOptions debugMode_,
+ gl::Context& context)
+ : renderable(renderable_),
+ complete(complete_),
+ modified(std::move(modified_)),
+ expires(std::move(expires_)),
+ debugMode(debugMode_) {
+
+ gl::VertexVector<FillLayoutVertex> vertices;
+ gl::IndexVector<gl::Lines> indices;
+
+ auto addText = [&] (const std::string& text, double left, double baseline, double scale) {
+ for (uint8_t c : text) {
+ if (c < 32 || c >= 127)
+ continue;
+
+ optional<Point<int16_t>> prev;
+
+ const glyph& glyph = simplex[c - 32];
+ for (int32_t j = 0; j < glyph.length; j += 2) {
+ if (glyph.data[j] == -1 && glyph.data[j + 1] == -1) {
+ prev = {};
+ } else {
+ Point<int16_t> p {
+ int16_t(::round(left + glyph.data[j] * scale)),
+ int16_t(::round(baseline - glyph.data[j + 1] * scale))
+ };
+
+ vertices.emplace_back(FillProgram::layoutVertex(p));
+
+ if (prev) {
+ indices.emplace_back(vertices.vertexSize() - 2,
+ vertices.vertexSize() - 1);
+ }
+
+ prev = p;
+ }
+ }
+
+ left += glyph.width * scale;
+ }
+ };
+
+ double baseline = 200;
+ if (debugMode & MapDebugOptions::ParseStatus) {
+ const std::string text = util::toString(id) + " - " +
+ (complete ? "complete" : renderable ? "renderable" : "pending");
+ addText(text, 50, baseline, 5);
+ baseline += 200;
+ }
+
+ if (debugMode & MapDebugOptions::Timestamps && modified && expires) {
+ const std::string modifiedText = "modified: " + util::iso8601(*modified);
+ addText(modifiedText, 50, baseline, 5);
+
+ const std::string expiresText = "expires: " + util::iso8601(*expires);
+ addText(expiresText, 50, baseline + 200, 5);
+ }
+
+ segments.emplace_back(0, 0, vertices.vertexSize(), indices.indexSize());
+
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ indexBuffer = context.createIndexBuffer(std::move(indices));
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/debug_bucket.hpp b/src/mbgl/renderer/buckets/debug_bucket.hpp
new file mode 100644
index 0000000000..fc3128e944
--- /dev/null
+++ b/src/mbgl/renderer/buckets/debug_bucket.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <mbgl/map/mode.hpp>
+#include <mbgl/util/chrono.hpp>
+#include <mbgl/util/geometry.hpp>
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/programs/debug_program.hpp>
+
+namespace mbgl {
+
+class OverscaledTileID;
+
+namespace gl {
+class Context;
+} // namespace gl
+
+class DebugBucket : private util::noncopyable {
+public:
+ DebugBucket(const OverscaledTileID& id,
+ bool renderable,
+ bool complete,
+ optional<Timestamp> modified,
+ optional<Timestamp> expires,
+ MapDebugOptions,
+ gl::Context&);
+
+ const bool renderable;
+ const bool complete;
+ const optional<Timestamp> modified;
+ const optional<Timestamp> expires;
+ const MapDebugOptions debugMode;
+
+ SegmentVector<DebugAttributes> segments;
+ optional<gl::VertexBuffer<DebugLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Lines>> indexBuffer;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/fill_bucket.cpp b/src/mbgl/renderer/buckets/fill_bucket.cpp
new file mode 100644
index 0000000000..110db887a1
--- /dev/null
+++ b/src/mbgl/renderer/buckets/fill_bucket.cpp
@@ -0,0 +1,137 @@
+#include <mbgl/renderer/buckets/fill_bucket.hpp>
+#include <mbgl/programs/fill_program.hpp>
+#include <mbgl/renderer/bucket_parameters.hpp>
+#include <mbgl/style/layers/fill_layer_impl.hpp>
+#include <mbgl/renderer/layers/render_fill_layer.hpp>
+#include <mbgl/util/math.hpp>
+
+#include <mapbox/earcut.hpp>
+
+#include <cassert>
+
+namespace mapbox {
+namespace util {
+template <> struct nth<0, mbgl::GeometryCoordinate> {
+ static int64_t get(const mbgl::GeometryCoordinate& t) { return t.x; };
+};
+
+template <> struct nth<1, mbgl::GeometryCoordinate> {
+ static int64_t get(const mbgl::GeometryCoordinate& t) { return t.y; };
+};
+} // namespace util
+} // namespace mapbox
+
+namespace mbgl {
+
+using namespace style;
+
+struct GeometryTooLongException : std::exception {};
+
+FillBucket::FillBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers) {
+ for (const auto& layer : layers) {
+ paintPropertyBinders.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(layer->getID()),
+ std::forward_as_tuple(
+ layer->as<RenderFillLayer>()->evaluated,
+ parameters.tileID.overscaledZ));
+ }
+}
+
+void FillBucket::addFeature(const GeometryTileFeature& feature,
+ const GeometryCollection& geometry) {
+ for (auto& polygon : classifyRings(geometry)) {
+ // Optimize polygons with many interior rings for earcut tesselation.
+ limitHoles(polygon, 500);
+
+ std::size_t totalVertices = 0;
+
+ for (const auto& ring : polygon) {
+ totalVertices += ring.size();
+ if (totalVertices > std::numeric_limits<uint16_t>::max())
+ throw GeometryTooLongException();
+ }
+
+ std::size_t startVertices = vertices.vertexSize();
+
+ for (const auto& ring : polygon) {
+ std::size_t nVertices = ring.size();
+
+ if (nVertices == 0)
+ continue;
+
+ if (lineSegments.empty() || lineSegments.back().vertexLength + nVertices > std::numeric_limits<uint16_t>::max()) {
+ lineSegments.emplace_back(vertices.vertexSize(), lines.indexSize());
+ }
+
+ auto& lineSegment = lineSegments.back();
+ assert(lineSegment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ uint16_t lineIndex = lineSegment.vertexLength;
+
+ vertices.emplace_back(FillProgram::layoutVertex(ring[0]));
+ lines.emplace_back(lineIndex + nVertices - 1, lineIndex);
+
+ for (uint32_t i = 1; i < nVertices; i++) {
+ vertices.emplace_back(FillProgram::layoutVertex(ring[i]));
+ lines.emplace_back(lineIndex + i - 1, lineIndex + i);
+ }
+
+ lineSegment.vertexLength += nVertices;
+ lineSegment.indexLength += nVertices * 2;
+ }
+
+ std::vector<uint32_t> indices = mapbox::earcut(polygon);
+
+ std::size_t nIndicies = indices.size();
+ assert(nIndicies % 3 == 0);
+
+ if (triangleSegments.empty() || triangleSegments.back().vertexLength + totalVertices > std::numeric_limits<uint16_t>::max()) {
+ triangleSegments.emplace_back(startVertices, triangles.indexSize());
+ }
+
+ auto& triangleSegment = triangleSegments.back();
+ assert(triangleSegment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ uint16_t triangleIndex = triangleSegment.vertexLength;
+
+ for (uint32_t i = 0; i < nIndicies; i += 3) {
+ triangles.emplace_back(triangleIndex + indices[i],
+ triangleIndex + indices[i + 1],
+ triangleIndex + indices[i + 2]);
+ }
+
+ triangleSegment.vertexLength += totalVertices;
+ triangleSegment.indexLength += nIndicies;
+ }
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.populateVertexVectors(feature, vertices.vertexSize());
+ }
+}
+
+void FillBucket::upload(gl::Context& context) {
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ lineIndexBuffer = context.createIndexBuffer(std::move(lines));
+ triangleIndexBuffer = context.createIndexBuffer(std::move(triangles));
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.upload(context);
+ }
+
+ uploaded = true;
+}
+
+bool FillBucket::hasData() const {
+ return !triangleSegments.empty() || !lineSegments.empty();
+}
+
+float FillBucket::getQueryRadius(const RenderLayer& layer) const {
+ if (!layer.is<RenderFillLayer>()) {
+ return 0;
+ }
+
+ const std::array<float, 2>& translate = layer.as<RenderFillLayer>()->evaluated.get<FillTranslate>();
+ return util::length(translate[0], translate[1]);
+
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/fill_bucket.hpp b/src/mbgl/renderer/buckets/fill_bucket.hpp
new file mode 100644
index 0000000000..a50e1971f5
--- /dev/null
+++ b/src/mbgl/renderer/buckets/fill_bucket.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/programs/segment.hpp>
+#include <mbgl/programs/fill_program.hpp>
+#include <mbgl/style/layers/fill_layer_properties.hpp>
+
+#include <vector>
+
+namespace mbgl {
+
+class BucketParameters;
+
+class FillBucket : public Bucket {
+public:
+ FillBucket(const BucketParameters&, const std::vector<const RenderLayer*>&);
+
+ void addFeature(const GeometryTileFeature&,
+ const GeometryCollection&) override;
+ bool hasData() const override;
+
+ void upload(gl::Context&) override;
+
+ float getQueryRadius(const RenderLayer&) const override;
+
+ gl::VertexVector<FillLayoutVertex> vertices;
+ gl::IndexVector<gl::Lines> lines;
+ gl::IndexVector<gl::Triangles> triangles;
+ SegmentVector<FillAttributes> lineSegments;
+ SegmentVector<FillAttributes> triangleSegments;
+
+ optional<gl::VertexBuffer<FillLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Lines>> lineIndexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> triangleIndexBuffer;
+
+ std::map<std::string, FillProgram::PaintPropertyBinders> paintPropertyBinders;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp b/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp
new file mode 100644
index 0000000000..7f53326fe1
--- /dev/null
+++ b/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp
@@ -0,0 +1,169 @@
+#include <mbgl/renderer/buckets/fill_extrusion_bucket.hpp>
+#include <mbgl/programs/fill_extrusion_program.hpp>
+#include <mbgl/renderer/bucket_parameters.hpp>
+#include <mbgl/style/layers/fill_extrusion_layer_impl.hpp>
+#include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/constants.hpp>
+
+#include <mapbox/earcut.hpp>
+
+#include <cassert>
+
+namespace mapbox {
+namespace util {
+template <>
+struct nth<0, mbgl::GeometryCoordinate> {
+ static int64_t get(const mbgl::GeometryCoordinate& t) {
+ return t.x;
+ };
+};
+
+template <>
+struct nth<1, mbgl::GeometryCoordinate> {
+ static int64_t get(const mbgl::GeometryCoordinate& t) {
+ return t.y;
+ };
+};
+} // namespace util
+} // namespace mapbox
+
+namespace mbgl {
+
+using namespace style;
+
+struct GeometryTooLongException : std::exception {};
+
+FillExtrusionBucket::FillExtrusionBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers) {
+ for (const auto& layer : layers) {
+ paintPropertyBinders.emplace(std::piecewise_construct,
+ std::forward_as_tuple(layer->getID()),
+ std::forward_as_tuple(
+ layer->as<RenderFillExtrusionLayer>()->evaluated,
+ parameters.tileID.overscaledZ));
+ }
+}
+
+void FillExtrusionBucket::addFeature(const GeometryTileFeature& feature,
+ const GeometryCollection& geometry) {
+ for (auto& polygon : classifyRings(geometry)) {
+ // Optimize polygons with many interior rings for earcut tesselation.
+ limitHoles(polygon, 500);
+
+ std::size_t totalVertices = 0;
+
+ for (const auto& ring : polygon) {
+ totalVertices += ring.size();
+ if (totalVertices > std::numeric_limits<uint16_t>::max())
+ throw GeometryTooLongException();
+ }
+
+ if (totalVertices == 0) continue;
+
+ std::vector<uint32_t> flatIndices;
+ flatIndices.reserve(totalVertices);
+
+ std::size_t startVertices = vertices.vertexSize();
+
+ if (triangleSegments.empty() ||
+ triangleSegments.back().vertexLength + (5 * (totalVertices - 1) + 1) >
+ std::numeric_limits<uint16_t>::max()) {
+ triangleSegments.emplace_back(startVertices, triangles.indexSize());
+ }
+
+ auto& triangleSegment = triangleSegments.back();
+ assert(triangleSegment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ uint16_t triangleIndex = triangleSegment.vertexLength;
+
+ assert(triangleIndex + (5 * (totalVertices - 1) + 1) <=
+ std::numeric_limits<uint16_t>::max());
+
+ for (const auto& ring : polygon) {
+ std::size_t nVertices = ring.size();
+
+ if (nVertices == 0)
+ continue;
+
+ auto edgeDistance = 0;
+
+ for (uint32_t i = 0; i < nVertices; i++) {
+ const auto& p1 = ring[i];
+
+ vertices.emplace_back(
+ FillExtrusionProgram::layoutVertex(p1, 0, 0, 1, 1, edgeDistance));
+ flatIndices.emplace_back(triangleIndex);
+ triangleIndex++;
+
+ if (i != 0) {
+ const auto& p2 = ring[i - 1];
+
+ const auto d1 = convertPoint<double>(p1);
+ const auto d2 = convertPoint<double>(p2);
+
+ const Point<double> perp = util::unit(util::perp(d1 - d2));
+
+ vertices.emplace_back(
+ FillExtrusionProgram::layoutVertex(p1, perp.x, perp.y, 0, 0, edgeDistance));
+ vertices.emplace_back(
+ FillExtrusionProgram::layoutVertex(p1, perp.x, perp.y, 0, 1, edgeDistance));
+
+ edgeDistance += util::dist<int16_t>(d1, d2);
+
+ vertices.emplace_back(
+ FillExtrusionProgram::layoutVertex(p2, perp.x, perp.y, 0, 0, edgeDistance));
+ vertices.emplace_back(
+ FillExtrusionProgram::layoutVertex(p2, perp.x, perp.y, 0, 1, edgeDistance));
+
+ triangles.emplace_back(triangleIndex, triangleIndex + 1, triangleIndex + 2);
+ triangles.emplace_back(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3);
+ triangleIndex += 4;
+ triangleSegment.vertexLength += 4;
+ triangleSegment.indexLength += 6;
+ }
+ }
+ }
+
+ std::vector<uint32_t> indices = mapbox::earcut(polygon);
+
+ std::size_t nIndices = indices.size();
+ assert(nIndices % 3 == 0);
+
+ for (uint32_t i = 0; i < nIndices; i += 3) {
+ triangles.emplace_back(flatIndices[indices[i]], flatIndices[indices[i + 1]],
+ flatIndices[indices[i + 2]]);
+ }
+
+ triangleSegment.vertexLength += totalVertices;
+ triangleSegment.indexLength += nIndices;
+ }
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.populateVertexVectors(feature, vertices.vertexSize());
+ }
+}
+
+void FillExtrusionBucket::upload(gl::Context& context) {
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ indexBuffer = context.createIndexBuffer(std::move(triangles));
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.upload(context);
+ }
+
+ uploaded = true;
+}
+
+bool FillExtrusionBucket::hasData() const {
+ return !triangleSegments.empty();
+}
+
+float FillExtrusionBucket::getQueryRadius(const RenderLayer& layer) const {
+ if (!layer.is<RenderFillExtrusionLayer>()) {
+ return 0;
+ }
+
+ const std::array<float, 2>& translate = layer.as<RenderFillExtrusionLayer>()->evaluated.get<FillExtrusionTranslate>();
+ return util::length(translate[0], translate[1]);
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp b/src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp
new file mode 100644
index 0000000000..d57265ab16
--- /dev/null
+++ b/src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/programs/segment.hpp>
+#include <mbgl/programs/fill_extrusion_program.hpp>
+#include <mbgl/style/layers/fill_extrusion_layer_properties.hpp>
+
+namespace mbgl {
+
+class BucketParameters;
+
+class FillExtrusionBucket : public Bucket {
+public:
+ FillExtrusionBucket(const BucketParameters&, const std::vector<const RenderLayer*>&);
+
+ void addFeature(const GeometryTileFeature&,
+ const GeometryCollection&) override;
+ bool hasData() const override;
+
+ void upload(gl::Context&) override;
+
+ float getQueryRadius(const RenderLayer&) const override;
+
+ gl::VertexVector<FillExtrusionLayoutVertex> vertices;
+ gl::IndexVector<gl::Triangles> triangles;
+ SegmentVector<FillExtrusionAttributes> triangleSegments;
+
+ optional<gl::VertexBuffer<FillExtrusionLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+
+ std::unordered_map<std::string, FillExtrusionProgram::PaintPropertyBinders> paintPropertyBinders;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/line_bucket.cpp b/src/mbgl/renderer/buckets/line_bucket.cpp
new file mode 100644
index 0000000000..a96518df38
--- /dev/null
+++ b/src/mbgl/renderer/buckets/line_bucket.cpp
@@ -0,0 +1,502 @@
+#include <mbgl/renderer/buckets/line_bucket.hpp>
+#include <mbgl/renderer/layers/render_line_layer.hpp>
+#include <mbgl/renderer/bucket_parameters.hpp>
+#include <mbgl/style/layers/line_layer_impl.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/constants.hpp>
+
+#include <cassert>
+
+namespace mbgl {
+
+using namespace style;
+
+LineBucket::LineBucket(const BucketParameters& parameters,
+ const std::vector<const RenderLayer*>& layers,
+ const style::LineLayoutProperties::Unevaluated& layout_)
+ : layout(layout_.evaluate(PropertyEvaluationParameters(parameters.tileID.overscaledZ))),
+ overscaling(parameters.tileID.overscaleFactor()),
+ zoom(parameters.tileID.overscaledZ) {
+ for (const auto& layer : layers) {
+ paintPropertyBinders.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(layer->getID()),
+ std::forward_as_tuple(
+ layer->as<RenderLineLayer>()->evaluated,
+ parameters.tileID.overscaledZ));
+ }
+}
+
+void LineBucket::addFeature(const GeometryTileFeature& feature,
+ const GeometryCollection& geometryCollection) {
+ for (auto& line : geometryCollection) {
+ addGeometry(line, feature);
+ }
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.populateVertexVectors(feature, vertices.vertexSize());
+ }
+}
+
+/*
+ * Sharp corners cause dashed lines to tilt because the distance along the line
+ * is the same at both the inner and outer corners. To improve the appearance of
+ * dashed lines we add extra points near sharp corners so that a smaller part
+ * of the line is tilted.
+ *
+ * COS_HALF_SHARP_CORNER controls how sharp a corner has to be for us to add an
+ * extra vertex. The default is 75 degrees.
+ *
+ * The newly created vertices are placed SHARP_CORNER_OFFSET pixels from the corner.
+ */
+const float COS_HALF_SHARP_CORNER = std::cos(75.0 / 2.0 * (M_PI / 180.0));
+const float SHARP_CORNER_OFFSET = 15.0f;
+
+// The number of bits that is used to store the line distance in the buffer.
+const int LINE_DISTANCE_BUFFER_BITS = 14;
+
+// We don't have enough bits for the line distance as we'd like to have, so
+// use this value to scale the line distance (in tile units) down to a smaller
+// value. This lets us store longer distances while sacrificing precision.
+const float LINE_DISTANCE_SCALE = 1.0 / 2.0;
+
+// The maximum line distance, in tile units, that fits in the buffer.
+const float MAX_LINE_DISTANCE = std::pow(2, LINE_DISTANCE_BUFFER_BITS) / LINE_DISTANCE_SCALE;
+
+void LineBucket::addGeometry(const GeometryCoordinates& coordinates, const GeometryTileFeature& feature) {
+ const FeatureType type = feature.getType();
+ const std::size_t len = [&coordinates] {
+ std::size_t l = coordinates.size();
+ // If the line has duplicate vertices at the end, adjust length to remove them.
+ while (l >= 2 && coordinates[l - 1] == coordinates[l - 2]) {
+ l--;
+ }
+ return l;
+ }();
+
+ const std::size_t first = [&coordinates, &len] {
+ std::size_t i = 0;
+ // If the line has duplicate vertices at the start, adjust index to remove them.
+ while (i < len - 1 && coordinates[i] == coordinates[i + 1]) {
+ i++;
+ }
+ return i;
+ }();
+
+ // Ignore invalid geometry.
+ if (len < (type == FeatureType::Polygon ? 3 : 2)) {
+ return;
+ }
+
+ const LineJoinType joinType = layout.evaluate<LineJoin>(zoom, feature);
+
+ const float miterLimit = joinType == LineJoinType::Bevel ? 1.05f : float(layout.get<LineMiterLimit>());
+
+ const double sharpCornerOffset = SHARP_CORNER_OFFSET * (float(util::EXTENT) / (util::tileSize * overscaling));
+
+ const GeometryCoordinate firstCoordinate = coordinates[first];
+ const LineCapType beginCap = layout.get<LineCap>();
+ const LineCapType endCap = type == FeatureType::Polygon ? LineCapType::Butt : LineCapType(layout.get<LineCap>());
+
+ double distance = 0;
+ bool startOfLine = true;
+ optional<GeometryCoordinate> currentCoordinate;
+ optional<GeometryCoordinate> prevCoordinate;
+ optional<GeometryCoordinate> nextCoordinate;
+ optional<Point<double>> prevNormal;
+ optional<Point<double>> nextNormal;
+
+ // the last three vertices added
+ e1 = e2 = e3 = -1;
+
+ if (type == FeatureType::Polygon) {
+ currentCoordinate = coordinates[len - 2];
+ nextNormal = util::perp(util::unit(convertPoint<double>(firstCoordinate - *currentCoordinate)));
+ }
+
+ const std::size_t startVertex = vertices.vertexSize();
+ std::vector<TriangleElement> triangleStore;
+
+ for (std::size_t i = first; i < len; ++i) {
+ if (type == FeatureType::Polygon && i == len - 1) {
+ // if the line is closed, we treat the last vertex like the first
+ nextCoordinate = coordinates[first + 1];
+ } else if (i + 1 < len) {
+ // just the next vertex
+ nextCoordinate = coordinates[i + 1];
+ } else {
+ // there is no next vertex
+ nextCoordinate = {};
+ }
+
+ // if two consecutive vertices exist, skip the current one
+ if (nextCoordinate && coordinates[i] == *nextCoordinate) {
+ continue;
+ }
+
+ if (nextNormal) {
+ prevNormal = *nextNormal;
+ }
+ if (currentCoordinate) {
+ prevCoordinate = *currentCoordinate;
+ }
+
+ currentCoordinate = coordinates[i];
+
+ // Calculate the normal towards the next vertex in this line. In case
+ // there is no next vertex, pretend that the line is continuing straight,
+ // meaning that we are just using the previous normal.
+ nextNormal = nextCoordinate ? util::perp(util::unit(convertPoint<double>(*nextCoordinate - *currentCoordinate)))
+ : prevNormal;
+
+ // If we still don't have a previous normal, this is the beginning of a
+ // non-closed line, so we're doing a straight "join".
+ if (!prevNormal) {
+ prevNormal = *nextNormal;
+ }
+
+ // Determine the normal of the join extrusion. It is the angle bisector
+ // of the segments between the previous line and the next line.
+ // In the case of 180° angles, the prev and next normals cancel each other out:
+ // prevNormal + nextNormal = (0, 0), its magnitude is 0, so the unit vector would be
+ // undefined. In that case, we're keeping the joinNormal at (0, 0), so that the cosHalfAngle
+ // below will also become 0 and miterLength will become Infinity.
+ Point<double> joinNormal = *prevNormal + *nextNormal;
+ if (joinNormal.x != 0 || joinNormal.y != 0) {
+ joinNormal = util::unit(joinNormal);
+ }
+
+ /* joinNormal prevNormal
+ * ↖ ↑
+ * .________. prevVertex
+ * |
+ * nextNormal ← | currentVertex
+ * |
+ * nextVertex !
+ *
+ */
+
+ // Calculate the length of the miter (the ratio of the miter to the width).
+ // Find the cosine of the angle between the next and join normals
+ // using dot product. The inverse of that is the miter length.
+ const double cosHalfAngle = joinNormal.x * nextNormal->x + joinNormal.y * nextNormal->y;
+ const double miterLength =
+ cosHalfAngle != 0 ? 1 / cosHalfAngle : std::numeric_limits<double>::infinity();
+
+ const bool isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevCoordinate && nextCoordinate;
+
+ if (isSharpCorner && i > first) {
+ const auto prevSegmentLength = util::dist<double>(*currentCoordinate, *prevCoordinate);
+ if (prevSegmentLength > 2.0 * sharpCornerOffset) {
+ GeometryCoordinate newPrevVertex = *currentCoordinate - convertPoint<int16_t>(util::round(convertPoint<double>(*currentCoordinate - *prevCoordinate) * (sharpCornerOffset / prevSegmentLength)));
+ distance += util::dist<double>(newPrevVertex, *prevCoordinate);
+ addCurrentVertex(newPrevVertex, distance, *prevNormal, 0, 0, false, startVertex, triangleStore);
+ prevCoordinate = newPrevVertex;
+ }
+ }
+
+ // The join if a middle vertex, otherwise the cap
+ const bool middleVertex = prevCoordinate && nextCoordinate;
+ LineJoinType currentJoin = joinType;
+ const LineCapType currentCap = nextCoordinate ? beginCap : endCap;
+
+ if (middleVertex) {
+ if (currentJoin == LineJoinType::Round) {
+ if (miterLength < layout.get<LineRoundLimit>()) {
+ currentJoin = LineJoinType::Miter;
+ } else if (miterLength <= 2) {
+ currentJoin = LineJoinType::FakeRound;
+ }
+ }
+
+ if (currentJoin == LineJoinType::Miter && miterLength > miterLimit) {
+ currentJoin = LineJoinType::Bevel;
+ }
+
+ if (currentJoin == LineJoinType::Bevel) {
+ // The maximum extrude length is 128 / 63 = 2 times the width of the line
+ // so if miterLength >= 2 we need to draw a different type of bevel here.
+ if (miterLength > 2) {
+ currentJoin = LineJoinType::FlipBevel;
+ }
+
+ // If the miterLength is really small and the line bevel wouldn't be visible,
+ // just draw a miter join to save a triangle.
+ if (miterLength < miterLimit) {
+ currentJoin = LineJoinType::Miter;
+ }
+ }
+ }
+
+ // Calculate how far along the line the currentVertex is
+ if (prevCoordinate)
+ distance += util::dist<double>(*currentCoordinate, *prevCoordinate);
+
+ if (middleVertex && currentJoin == LineJoinType::Miter) {
+ joinNormal = joinNormal * miterLength;
+ addCurrentVertex(*currentCoordinate, distance, joinNormal, 0, 0, false, startVertex,
+ triangleStore);
+
+ } else if (middleVertex && currentJoin == LineJoinType::FlipBevel) {
+ // miter is too big, flip the direction to make a beveled join
+
+ if (miterLength > 100) {
+ // Almost parallel lines
+ joinNormal = *nextNormal * -1.0;
+ } else {
+ const double direction = prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x > 0 ? -1 : 1;
+ const double bevelLength = miterLength * util::mag(*prevNormal + *nextNormal) /
+ util::mag(*prevNormal - *nextNormal);
+ joinNormal = util::perp(joinNormal) * bevelLength * direction;
+ }
+
+ addCurrentVertex(*currentCoordinate, distance, joinNormal, 0, 0, false, startVertex,
+ triangleStore);
+
+ addCurrentVertex(*currentCoordinate, distance, joinNormal * -1.0, 0, 0, false, startVertex,
+ triangleStore);
+ } else if (middleVertex && (currentJoin == LineJoinType::Bevel || currentJoin == LineJoinType::FakeRound)) {
+ const bool lineTurnsLeft = (prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x) > 0;
+ const float offset = -std::sqrt(miterLength * miterLength - 1);
+ float offsetA;
+ float offsetB;
+
+ if (lineTurnsLeft) {
+ offsetB = 0;
+ offsetA = offset;
+ } else {
+ offsetA = 0;
+ offsetB = offset;
+ }
+
+ // Close previous segement with bevel
+ if (!startOfLine) {
+ addCurrentVertex(*currentCoordinate, distance, *prevNormal, offsetA, offsetB, false,
+ startVertex, triangleStore);
+ }
+
+ if (currentJoin == LineJoinType::FakeRound) {
+ // The join angle is sharp enough that a round join would be visible.
+ // Bevel joins fill the gap between segments with a single pie slice triangle.
+ // Create a round join by adding multiple pie slices. The join isn't actually round, but
+ // it looks like it is at the sizes we render lines at.
+
+ // Add more triangles for sharper angles.
+ // This math is just a good enough approximation. It isn't "correct".
+ const int n = std::floor((0.5 - (cosHalfAngle - 0.5)) * 8);
+
+ for (int m = 0; m < n; m++) {
+ auto approxFractionalJoinNormal = util::unit(*nextNormal * ((m + 1.0) / (n + 1.0)) + *prevNormal);
+ addPieSliceVertex(*currentCoordinate, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
+ }
+
+ addPieSliceVertex(*currentCoordinate, distance, joinNormal, lineTurnsLeft, startVertex, triangleStore);
+
+ for (int k = n - 1; k >= 0; k--) {
+ auto approxFractionalJoinNormal = util::unit(*prevNormal * ((k + 1.0) / (n + 1.0)) + *nextNormal);
+ addPieSliceVertex(*currentCoordinate, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
+ }
+ }
+
+ // Start next segment
+ if (nextCoordinate) {
+ addCurrentVertex(*currentCoordinate, distance, *nextNormal, -offsetA, -offsetB,
+ false, startVertex, triangleStore);
+ }
+
+ } else if (!middleVertex && currentCap == LineCapType::Butt) {
+ if (!startOfLine) {
+ // Close previous segment with a butt
+ addCurrentVertex(*currentCoordinate, distance, *prevNormal, 0, 0, false,
+ startVertex, triangleStore);
+ }
+
+ // Start next segment with a butt
+ if (nextCoordinate) {
+ addCurrentVertex(*currentCoordinate, distance, *nextNormal, 0, 0, false,
+ startVertex, triangleStore);
+ }
+
+ } else if (!middleVertex && currentCap == LineCapType::Square) {
+ if (!startOfLine) {
+ // Close previous segment with a square cap
+ addCurrentVertex(*currentCoordinate, distance, *prevNormal, 1, 1, false,
+ startVertex, triangleStore);
+
+ // The segment is done. Unset vertices to disconnect segments.
+ e1 = e2 = -1;
+ }
+
+ // Start next segment
+ if (nextCoordinate) {
+ addCurrentVertex(*currentCoordinate, distance, *nextNormal, -1, -1, false,
+ startVertex, triangleStore);
+ }
+
+ } else if (middleVertex ? currentJoin == LineJoinType::Round : currentCap == LineCapType::Round) {
+ if (!startOfLine) {
+ // Close previous segment with a butt
+ addCurrentVertex(*currentCoordinate, distance, *prevNormal, 0, 0, false,
+ startVertex, triangleStore);
+
+ // Add round cap or linejoin at end of segment
+ addCurrentVertex(*currentCoordinate, distance, *prevNormal, 1, 1, true, startVertex,
+ triangleStore);
+
+ // The segment is done. Unset vertices to disconnect segments.
+ e1 = e2 = -1;
+ }
+
+ // Start next segment with a butt
+ if (nextCoordinate) {
+ // Add round cap before first segment
+ addCurrentVertex(*currentCoordinate, distance, *nextNormal, -1, -1, true,
+ startVertex, triangleStore);
+
+ addCurrentVertex(*currentCoordinate, distance, *nextNormal, 0, 0, false,
+ startVertex, triangleStore);
+ }
+ }
+
+ if (isSharpCorner && i < len - 1) {
+ const auto nextSegmentLength = util::dist<double>(*currentCoordinate, *nextCoordinate);
+ if (nextSegmentLength > 2 * sharpCornerOffset) {
+ GeometryCoordinate newCurrentVertex = *currentCoordinate + convertPoint<int16_t>(util::round(convertPoint<double>(*nextCoordinate - *currentCoordinate) * (sharpCornerOffset / nextSegmentLength)));
+ distance += util::dist<double>(newCurrentVertex, *currentCoordinate);
+ addCurrentVertex(newCurrentVertex, distance, *nextNormal, 0, 0, false, startVertex, triangleStore);
+ currentCoordinate = newCurrentVertex;
+ }
+ }
+
+ startOfLine = false;
+ }
+
+ const std::size_t endVertex = vertices.vertexSize();
+ const std::size_t vertexCount = endVertex - startVertex;
+
+ if (segments.empty() || segments.back().vertexLength + vertexCount > std::numeric_limits<uint16_t>::max()) {
+ segments.emplace_back(startVertex, triangles.indexSize());
+ }
+
+ auto& segment = segments.back();
+ assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ uint16_t index = segment.vertexLength;
+
+ for (const auto& triangle : triangleStore) {
+ triangles.emplace_back(index + triangle.a, index + triangle.b, index + triangle.c);
+ }
+
+ segment.vertexLength += vertexCount;
+ segment.indexLength += triangleStore.size() * 3;
+}
+
+void LineBucket::addCurrentVertex(const GeometryCoordinate& currentCoordinate,
+ double &distance,
+ const Point<double>& normal,
+ double endLeft,
+ double endRight,
+ bool round,
+ std::size_t startVertex,
+ std::vector<TriangleElement>& triangleStore) {
+ Point<double> extrude = normal;
+ if (endLeft)
+ extrude = extrude - (util::perp(normal) * endLeft);
+ vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, round, false, endLeft, distance * LINE_DISTANCE_SCALE));
+ e3 = vertices.vertexSize() - 1 - startVertex;
+ if (e1 >= 0 && e2 >= 0) {
+ triangleStore.emplace_back(e1, e2, e3);
+ }
+ e1 = e2;
+ e2 = e3;
+
+ extrude = normal * -1.0;
+ if (endRight)
+ extrude = extrude - (util::perp(normal) * endRight);
+ vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, round, true, -endRight, distance * LINE_DISTANCE_SCALE));
+ e3 = vertices.vertexSize() - 1 - startVertex;
+ if (e1 >= 0 && e2 >= 0) {
+ triangleStore.emplace_back(e1, e2, e3);
+ }
+ e1 = e2;
+ e2 = e3;
+
+ // There is a maximum "distance along the line" that we can store in the buffers.
+ // When we get close to the distance, reset it to zero and add the vertex again with
+ // a distance of zero. The max distance is determined by the number of bits we allocate
+ // to `linesofar`.
+ if (distance > MAX_LINE_DISTANCE / 2.0f) {
+ distance = 0;
+ addCurrentVertex(currentCoordinate, distance, normal, endLeft, endRight, round, startVertex, triangleStore);
+ }
+}
+
+void LineBucket::addPieSliceVertex(const GeometryCoordinate& currentVertex,
+ double distance,
+ const Point<double>& extrude,
+ bool lineTurnsLeft,
+ std::size_t startVertex,
+ std::vector<TriangleElement>& triangleStore) {
+ Point<double> flippedExtrude = extrude * (lineTurnsLeft ? -1.0 : 1.0);
+ vertices.emplace_back(LineProgram::layoutVertex(currentVertex, flippedExtrude, false, lineTurnsLeft, 0, distance * LINE_DISTANCE_SCALE));
+ e3 = vertices.vertexSize() - 1 - startVertex;
+ if (e1 >= 0 && e2 >= 0) {
+ triangleStore.emplace_back(e1, e2, e3);
+ }
+
+ if (lineTurnsLeft) {
+ e2 = e3;
+ } else {
+ e1 = e3;
+ }
+}
+
+void LineBucket::upload(gl::Context& context) {
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ indexBuffer = context.createIndexBuffer(std::move(triangles));
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.upload(context);
+ }
+
+ uploaded = true;
+}
+
+bool LineBucket::hasData() const {
+ return !segments.empty();
+}
+
+template <class Property>
+static float get(const RenderLineLayer& layer, const std::map<std::string, LineProgram::PaintPropertyBinders>& paintPropertyBinders) {
+ auto it = paintPropertyBinders.find(layer.getID());
+ if (it == paintPropertyBinders.end() || !it->second.statistics<Property>().max()) {
+ return layer.evaluated.get<Property>().constantOr(Property::defaultValue());
+ } else {
+ return *it->second.statistics<Property>().max();
+ }
+}
+
+float LineBucket::getLineWidth(const RenderLineLayer& layer) const {
+ float lineWidth = get<LineWidth>(layer, paintPropertyBinders);
+ float gapWidth = get<LineGapWidth>(layer, paintPropertyBinders);
+
+ if (gapWidth) {
+ return gapWidth + 2 * lineWidth;
+ } else {
+ return lineWidth;
+ }
+}
+
+float LineBucket::getQueryRadius(const RenderLayer& layer) const {
+ if (!layer.is<RenderLineLayer>()) {
+ return 0;
+ }
+
+ auto lineLayer = layer.as<RenderLineLayer>();
+
+ const std::array<float, 2>& translate = lineLayer->evaluated.get<LineTranslate>();
+ float offset = get<LineOffset>(*lineLayer, paintPropertyBinders);
+ return getLineWidth(*lineLayer) / 2.0 + std::abs(offset) + util::length(translate[0], translate[1]);
+}
+
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/line_bucket.hpp b/src/mbgl/renderer/buckets/line_bucket.hpp
new file mode 100644
index 0000000000..4fb77c377e
--- /dev/null
+++ b/src/mbgl/renderer/buckets/line_bucket.hpp
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/tile/geometry_tile_data.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/programs/segment.hpp>
+#include <mbgl/programs/line_program.hpp>
+#include <mbgl/style/layers/line_layer_properties.hpp>
+
+#include <vector>
+
+namespace mbgl {
+
+class BucketParameters;
+class RenderLineLayer;
+
+class LineBucket : public Bucket {
+public:
+ LineBucket(const BucketParameters&,
+ const std::vector<const RenderLayer*>&,
+ const style::LineLayoutProperties::Unevaluated&);
+
+ void addFeature(const GeometryTileFeature&,
+ const GeometryCollection&) override;
+ bool hasData() const override;
+
+ void upload(gl::Context&) override;
+
+ float getQueryRadius(const RenderLayer&) const override;
+
+ style::LineLayoutProperties::PossiblyEvaluated layout;
+
+ gl::VertexVector<LineLayoutVertex> vertices;
+ gl::IndexVector<gl::Triangles> triangles;
+ SegmentVector<LineAttributes> segments;
+
+ optional<gl::VertexBuffer<LineLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+
+ std::map<std::string, LineProgram::PaintPropertyBinders> paintPropertyBinders;
+
+private:
+ void addGeometry(const GeometryCoordinates&, const GeometryTileFeature&);
+
+ struct TriangleElement {
+ TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) : a(a_), b(b_), c(c_) {}
+ uint16_t a, b, c;
+ };
+ void addCurrentVertex(const GeometryCoordinate& currentVertex, double& distance,
+ const Point<double>& normal, double endLeft, double endRight, bool round,
+ std::size_t startVertex, std::vector<LineBucket::TriangleElement>& triangleStore);
+ void addPieSliceVertex(const GeometryCoordinate& currentVertex, double distance,
+ const Point<double>& extrude, bool lineTurnsLeft, std::size_t startVertex,
+ std::vector<TriangleElement>& triangleStore);
+
+ std::ptrdiff_t e1;
+ std::ptrdiff_t e2;
+ std::ptrdiff_t e3;
+
+ const uint32_t overscaling;
+ const float zoom;
+
+ float getLineWidth(const RenderLineLayer& layer) const;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/raster_bucket.cpp b/src/mbgl/renderer/buckets/raster_bucket.cpp
new file mode 100644
index 0000000000..a66dd42d74
--- /dev/null
+++ b/src/mbgl/renderer/buckets/raster_bucket.cpp
@@ -0,0 +1,110 @@
+#include <mbgl/renderer/buckets/raster_bucket.hpp>
+#include <mbgl/renderer/layers/render_raster_layer.hpp>
+#include <mbgl/programs/raster_program.hpp>
+#include <mbgl/gl/context.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+RasterBucket::RasterBucket(PremultipliedImage&& image_) {
+ image = std::make_shared<PremultipliedImage>(std::move(image_));
+}
+
+RasterBucket::RasterBucket(std::shared_ptr<PremultipliedImage> image_): image(image_) {
+
+}
+
+void RasterBucket::upload(gl::Context& context) {
+ if (!hasData()) {
+ return;
+ }
+ if (!texture) {
+ texture = context.createTexture(*image);
+ }
+ if (!segments.empty()) {
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ indexBuffer = context.createIndexBuffer(std::move(indices));
+ }
+ uploaded = true;
+}
+
+void RasterBucket::clear() {
+ vertexBuffer = {};
+ indexBuffer = {};
+ segments.clear();
+ vertices.clear();
+ indices.clear();
+
+ uploaded = false;
+}
+
+void RasterBucket::setImage(std::shared_ptr<PremultipliedImage> image_) {
+ image = std::move(image_);
+ texture = {};
+ uploaded = false;
+}
+
+void RasterBucket::setMask(TileMask&& mask_) {
+ if (mask == mask_) {
+ return;
+ }
+
+ mask = std::move(mask_);
+ clear();
+
+ if (mask == TileMask{ { 0, 0, 0 } }) {
+ // We want to render the full tile, and keeping the segments/vertices/indices empty means
+ // using the global shared buffers for covering the entire tile.
+ return;
+ }
+
+ // Create a new segment so that we will upload (empty) buffers even when there is nothing to
+ // draw for this tile.
+ segments.emplace_back(0, 0);
+
+ constexpr const uint16_t vertexLength = 4;
+
+ // Create the vertex buffer for the specified tile mask.
+ for (const auto& id : mask) {
+ // Create a quad for every masked tile.
+ const int32_t vertexExtent = util::EXTENT >> id.z;
+
+ const Point<int16_t> tlVertex = { static_cast<int16_t>(id.x * vertexExtent),
+ static_cast<int16_t>(id.y * vertexExtent) };
+ const Point<int16_t> brVertex = { static_cast<int16_t>(tlVertex.x + vertexExtent),
+ static_cast<int16_t>(tlVertex.y + vertexExtent) };
+
+ if (segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
+ // Move to a new segments because the old one can't hold the geometry.
+ segments.emplace_back(vertices.vertexSize(), indices.indexSize());
+ }
+
+ vertices.emplace_back(
+ RasterProgram::layoutVertex({ tlVertex.x, tlVertex.y }, { static_cast<uint16_t>(tlVertex.x), static_cast<uint16_t>(tlVertex.y) }));
+ vertices.emplace_back(
+ RasterProgram::layoutVertex({ brVertex.x, tlVertex.y }, { static_cast<uint16_t>(brVertex.x), static_cast<uint16_t>(tlVertex.y) }));
+ vertices.emplace_back(
+ RasterProgram::layoutVertex({ tlVertex.x, brVertex.y }, { static_cast<uint16_t>(tlVertex.x), static_cast<uint16_t>(brVertex.y) }));
+ vertices.emplace_back(
+ RasterProgram::layoutVertex({ brVertex.x, brVertex.y }, { static_cast<uint16_t>(brVertex.x), static_cast<uint16_t>(brVertex.y) }));
+
+ auto& segment = segments.back();
+ assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ const uint16_t offset = segment.vertexLength;
+
+ // 0, 1, 2
+ // 1, 2, 3
+ indices.emplace_back(offset, offset + 1, offset + 2);
+ indices.emplace_back(offset + 1, offset + 2, offset + 3);
+
+ segment.vertexLength += vertexLength;
+ segment.indexLength += 6;
+ }
+}
+
+bool RasterBucket::hasData() const {
+ return !!image;
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/raster_bucket.hpp b/src/mbgl/renderer/buckets/raster_bucket.hpp
new file mode 100644
index 0000000000..3800eadec8
--- /dev/null
+++ b/src/mbgl/renderer/buckets/raster_bucket.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/gl/texture.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/programs/raster_program.hpp>
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/renderer/tile_mask.hpp>
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/optional.hpp>
+
+namespace mbgl {
+
+class RasterBucket : public Bucket {
+public:
+ RasterBucket(PremultipliedImage&&);
+ RasterBucket(std::shared_ptr<PremultipliedImage>);
+
+ void upload(gl::Context&) override;
+ bool hasData() const override;
+
+ void clear();
+ void setImage(std::shared_ptr<PremultipliedImage>);
+ void setMask(TileMask&&);
+
+ std::shared_ptr<PremultipliedImage> image;
+ optional<gl::Texture> texture;
+ TileMask mask{ { 0, 0, 0 } };
+
+ // Bucket specific vertices are used for Image Sources only
+ // Raster Tile Sources use the default buffers from Painter
+ gl::VertexVector<RasterLayoutVertex> vertices;
+ gl::IndexVector<gl::Triangles> indices;
+ SegmentVector<RasterAttributes> segments;
+
+ optional<gl::VertexBuffer<RasterLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp
new file mode 100644
index 0000000000..a3f71f1f6e
--- /dev/null
+++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp
@@ -0,0 +1,79 @@
+#include <mbgl/renderer/buckets/symbol_bucket.hpp>
+#include <mbgl/renderer/layers/render_symbol_layer.hpp>
+#include <mbgl/renderer/bucket_parameters.hpp>
+#include <mbgl/style/layers/symbol_layer_impl.hpp>
+#include <mbgl/text/glyph_atlas.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layout_,
+ const std::map<std::string, std::pair<
+ style::IconPaintProperties::PossiblyEvaluated,
+ style::TextPaintProperties::PossiblyEvaluated>>& layerPaintProperties,
+ const style::DataDrivenPropertyValue<float>& textSize,
+ const style::DataDrivenPropertyValue<float>& iconSize,
+ float zoom,
+ bool sdfIcons_,
+ bool iconsNeedLinear_)
+ : layout(std::move(layout_)),
+ sdfIcons(sdfIcons_),
+ iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()),
+ textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())),
+ iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())) {
+
+ for (const auto& pair : layerPaintProperties) {
+ paintPropertyBinders.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(pair.first),
+ std::forward_as_tuple(
+ std::piecewise_construct,
+ std::forward_as_tuple(pair.second.first, zoom),
+ std::forward_as_tuple(pair.second.second, zoom)));
+ }
+}
+
+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::BufferUsage::StreamDraw);
+ text.indexBuffer = context.createIndexBuffer(std::move(text.triangles));
+ }
+
+ if (hasIconData()) {
+ icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices));
+ icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsage::StreamDraw);
+ icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles));
+ }
+
+ if (!collisionBox.vertices.empty()) {
+ collisionBox.vertexBuffer = context.createVertexBuffer(std::move(collisionBox.vertices));
+ collisionBox.indexBuffer = context.createIndexBuffer(std::move(collisionBox.lines));
+ }
+
+ for (auto& pair : paintPropertyBinders) {
+ pair.second.first.upload(context);
+ pair.second.second.upload(context);
+ }
+
+ uploaded = true;
+}
+
+bool SymbolBucket::hasData() const {
+ return hasTextData() || hasIconData() || hasCollisionBoxData();
+}
+
+bool SymbolBucket::hasTextData() const {
+ return !text.segments.empty();
+}
+
+bool SymbolBucket::hasIconData() const {
+ return !icon.segments.empty();
+}
+
+bool SymbolBucket::hasCollisionBoxData() const {
+ return !collisionBox.segments.empty();
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp
new file mode 100644
index 0000000000..32f976bcb2
--- /dev/null
+++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/map/mode.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/programs/segment.hpp>
+#include <mbgl/programs/symbol_program.hpp>
+#include <mbgl/programs/collision_box_program.hpp>
+#include <mbgl/text/glyph_range.hpp>
+#include <mbgl/style/layers/symbol_layer_properties.hpp>
+#include <mbgl/layout/symbol_feature.hpp>
+
+#include <vector>
+
+namespace mbgl {
+
+class PlacedSymbol {
+public:
+ PlacedSymbol(Point<float> anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_,
+ std::array<float, 2> 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<float> anchorPoint;
+ uint16_t segment;
+ float lowerSize;
+ float upperSize;
+ std::array<float, 2> lineOffset;
+ float placementZoom;
+ bool useVerticalMode;
+ GeometryCoordinates line;
+ std::vector<float> glyphOffsets;
+};
+
+class SymbolBucket : public Bucket {
+public:
+ SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated,
+ const std::map<std::string, std::pair<style::IconPaintProperties::PossiblyEvaluated, style::TextPaintProperties::PossiblyEvaluated>>&,
+ const style::DataDrivenPropertyValue<float>& textSize,
+ const style::DataDrivenPropertyValue<float>& iconSize,
+ float zoom,
+ bool sdfIcons,
+ bool iconsNeedLinear);
+
+ void upload(gl::Context&) override;
+ bool hasData() const override;
+ bool hasTextData() const;
+ bool hasIconData() const;
+ bool hasCollisionBoxData() const;
+
+ const style::SymbolLayoutProperties::PossiblyEvaluated layout;
+ const bool sdfIcons;
+ const bool iconsNeedLinear;
+
+ std::map<std::string, std::pair<
+ SymbolIconProgram::PaintPropertyBinders,
+ SymbolSDFTextProgram::PaintPropertyBinders>> paintPropertyBinders;
+
+ std::unique_ptr<SymbolSizeBinder> textSizeBinder;
+
+ struct TextBuffer {
+ gl::VertexVector<SymbolLayoutVertex> vertices;
+ gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> dynamicVertices;
+ gl::IndexVector<gl::Triangles> triangles;
+ SegmentVector<SymbolTextAttributes> segments;
+ std::vector<PlacedSymbol> placedSymbols;
+
+ optional<gl::VertexBuffer<SymbolLayoutVertex>> vertexBuffer;
+ optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+ } text;
+
+ std::unique_ptr<SymbolSizeBinder> iconSizeBinder;
+
+ struct IconBuffer {
+ gl::VertexVector<SymbolLayoutVertex> vertices;
+ gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> dynamicVertices;
+ gl::IndexVector<gl::Triangles> triangles;
+ SegmentVector<SymbolIconAttributes> segments;
+ std::vector<PlacedSymbol> placedSymbols;
+ PremultipliedImage atlasImage;
+
+ optional<gl::VertexBuffer<SymbolLayoutVertex>> vertexBuffer;
+ optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+ } icon;
+
+ struct CollisionBoxBuffer {
+ gl::VertexVector<CollisionBoxVertex> vertices;
+ gl::IndexVector<gl::Lines> lines;
+ SegmentVector<CollisionBoxAttributes> segments;
+
+ optional<gl::VertexBuffer<CollisionBoxVertex>> vertexBuffer;
+ optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer;
+ optional<gl::IndexBuffer<gl::Lines>> indexBuffer;
+ } collisionBox;
+};
+
+} // namespace mbgl