summaryrefslogtreecommitdiff
path: root/src/mbgl/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl/renderer')
-rw-r--r--src/mbgl/renderer/bucket.hpp24
-rw-r--r--src/mbgl/renderer/debug_bucket.cpp32
-rw-r--r--src/mbgl/renderer/debug_bucket.hpp35
-rw-r--r--src/mbgl/renderer/fill_bucket.cpp246
-rw-r--r--src/mbgl/renderer/fill_bucket.hpp88
-rw-r--r--src/mbgl/renderer/frame_history.cpp85
-rw-r--r--src/mbgl/renderer/frame_history.hpp40
-rw-r--r--src/mbgl/renderer/line_bucket.cpp403
-rw-r--r--src/mbgl/renderer/line_bucket.hpp62
-rw-r--r--src/mbgl/renderer/painter.cpp465
-rw-r--r--src/mbgl/renderer/painter.hpp259
-rw-r--r--src/mbgl/renderer/painter_clipping.cpp39
-rw-r--r--src/mbgl/renderer/painter_debug.cpp102
-rw-r--r--src/mbgl/renderer/painter_fill.cpp122
-rw-r--r--src/mbgl/renderer/painter_line.cpp101
-rw-r--r--src/mbgl/renderer/painter_prerender.cpp45
-rw-r--r--src/mbgl/renderer/painter_raster.cpp107
-rw-r--r--src/mbgl/renderer/painter_symbol.cpp208
-rw-r--r--src/mbgl/renderer/prerendered_texture.cpp186
-rw-r--r--src/mbgl/renderer/prerendered_texture.hpp38
-rw-r--r--src/mbgl/renderer/raster_bucket.cpp36
-rw-r--r--src/mbgl/renderer/raster_bucket.hpp38
-rw-r--r--src/mbgl/renderer/symbol_bucket.cpp427
-rw-r--r--src/mbgl/renderer/symbol_bucket.hpp114
24 files changed, 3302 insertions, 0 deletions
diff --git a/src/mbgl/renderer/bucket.hpp b/src/mbgl/renderer/bucket.hpp
new file mode 100644
index 0000000000..696bfb1110
--- /dev/null
+++ b/src/mbgl/renderer/bucket.hpp
@@ -0,0 +1,24 @@
+#ifndef MBGL_RENDERER_BUCKET
+#define MBGL_RENDERER_BUCKET
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <string>
+
+namespace mbgl {
+
+class Painter;
+class StyleLayer;
+
+class Bucket : private util::noncopyable {
+public:
+ virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) = 0;
+ virtual bool hasData() const = 0;
+ virtual ~Bucket() {}
+
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/debug_bucket.cpp b/src/mbgl/renderer/debug_bucket.cpp
new file mode 100644
index 0000000000..f089374564
--- /dev/null
+++ b/src/mbgl/renderer/debug_bucket.cpp
@@ -0,0 +1,32 @@
+#include <mbgl/renderer/debug_bucket.hpp>
+#include <mbgl/renderer/painter.hpp>
+
+#include <mbgl/platform/gl.hpp>
+
+#include <cassert>
+
+struct geometry_too_long_exception : std::exception {};
+
+using namespace mbgl;
+
+DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer_)
+ : fontBuffer(fontBuffer_) {
+}
+
+void DebugBucket::render(Painter& painter, util::ptr<StyleLayer> /*layer_desc*/, const Tile::ID& /*id*/, const mat4 &matrix) {
+ painter.renderDebugText(*this, matrix);
+}
+
+bool DebugBucket::hasData() const {
+ return fontBuffer.index() > 0;
+}
+
+void DebugBucket::drawLines(PlainShader& shader) {
+ array.bind(shader, fontBuffer, BUFFER_OFFSET(0));
+ glDrawArrays(GL_LINES, 0, (GLsizei)(fontBuffer.index()));
+}
+
+void DebugBucket::drawPoints(PlainShader& shader) {
+ array.bind(shader, fontBuffer, BUFFER_OFFSET(0));
+ glDrawArrays(GL_POINTS, 0, (GLsizei)(fontBuffer.index()));
+}
diff --git a/src/mbgl/renderer/debug_bucket.hpp b/src/mbgl/renderer/debug_bucket.hpp
new file mode 100644
index 0000000000..fb6cfb4cae
--- /dev/null
+++ b/src/mbgl/renderer/debug_bucket.hpp
@@ -0,0 +1,35 @@
+#ifndef MBGL_RENDERER_DEBUGBUCKET
+#define MBGL_RENDERER_DEBUGBUCKET
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/geometry/debug_font_buffer.hpp>
+#include <mbgl/geometry/vao.hpp>
+
+#include <vector>
+
+#ifndef BUFFER_OFFSET
+#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
+#endif
+
+namespace mbgl {
+
+class PlainShader;
+
+class DebugBucket : public Bucket {
+public:
+ DebugBucket(DebugFontBuffer& fontBuffer);
+
+ virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ virtual bool hasData() const;
+
+ void drawLines(PlainShader& shader);
+ void drawPoints(PlainShader& shader);
+
+private:
+ DebugFontBuffer& fontBuffer;
+ VertexArrayObject array;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/fill_bucket.cpp b/src/mbgl/renderer/fill_bucket.cpp
new file mode 100644
index 0000000000..0a7d77935d
--- /dev/null
+++ b/src/mbgl/renderer/fill_bucket.cpp
@@ -0,0 +1,246 @@
+#include <mbgl/renderer/fill_bucket.hpp>
+#include <mbgl/geometry/fill_buffer.hpp>
+#include <mbgl/geometry/elements_buffer.hpp>
+#include <mbgl/geometry/geometry.hpp>
+
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/map/vector_tile.hpp>
+
+#include <mbgl/platform/gl.hpp>
+
+
+#include <cassert>
+
+struct geometry_too_long_exception : std::exception {};
+
+using namespace mbgl;
+
+
+
+void *FillBucket::alloc(void *, unsigned int size) {
+ return ::malloc(size);
+}
+
+void *FillBucket::realloc(void *, void *ptr, unsigned int size) {
+ return ::realloc(ptr, size);
+}
+
+void FillBucket::free(void *, void *ptr) {
+ ::free(ptr);
+}
+
+FillBucket::FillBucket(FillVertexBuffer &vertexBuffer_,
+ TriangleElementsBuffer &triangleElementsBuffer_,
+ LineElementsBuffer &lineElementsBuffer_,
+ const StyleBucketFill &properties_)
+ : properties(properties_),
+ allocator(new TESSalloc{&alloc, &realloc, &free, nullptr, // userData
+ 64, // meshEdgeBucketSize
+ 64, // meshVertexBucketSize
+ 32, // meshFaceBucketSize
+ 64, // dictNodeBucketSize
+ 8, // regionBucketSize
+ 128, // extraVertices allocated for the priority queue.
+ }),
+ tesselator(tessNewTess(allocator)),
+ vertexBuffer(vertexBuffer_),
+ triangleElementsBuffer(triangleElementsBuffer_),
+ lineElementsBuffer(lineElementsBuffer_),
+ vertex_start(vertexBuffer_.index()),
+ triangle_elements_start(triangleElementsBuffer_.index()),
+ line_elements_start(lineElementsBuffer.index()) {
+ assert(tesselator);
+}
+
+FillBucket::~FillBucket() {
+ if (tesselator) {
+ tessDeleteTess(tesselator);
+ }
+ if (allocator) {
+ delete allocator;
+ }
+}
+
+void FillBucket::addGeometry(pbf& geom) {
+ Geometry::command cmd;
+
+ Coordinate coord;
+ Geometry geometry(geom);
+ int32_t x, y;
+ while ((cmd = geometry.next(x, y)) != Geometry::end) {
+ if (cmd == Geometry::move_to) {
+ if (line.size()) {
+ clipper.AddPath(line, ClipperLib::ptSubject, true);
+ line.clear();
+ hasVertices = true;
+ }
+ }
+ line.emplace_back(x, y);
+ }
+
+ if (line.size()) {
+ clipper.AddPath(line, ClipperLib::ptSubject, true);
+ line.clear();
+ hasVertices = true;
+ }
+
+ tessellate();
+}
+
+void FillBucket::tessellate() {
+ if (!hasVertices) {
+ return;
+ }
+ hasVertices = false;
+
+ std::vector<std::vector<ClipperLib::IntPoint>> polygons;
+ clipper.Execute(ClipperLib::ctUnion, polygons, ClipperLib::pftPositive);
+ clipper.Clear();
+
+ if (polygons.size() == 0) {
+ return;
+ }
+
+ size_t total_vertex_count = 0;
+ for (const std::vector<ClipperLib::IntPoint>& polygon : polygons) {
+ total_vertex_count += polygon.size();
+ }
+
+ if (total_vertex_count > 65536) {
+ throw geometry_too_long_exception();
+ }
+
+ if (!lineGroups.size() || (lineGroups.back().vertex_length + total_vertex_count > 65535)) {
+ // Move to a new group because the old one can't hold the geometry.
+ lineGroups.emplace_back();
+ }
+
+ line_group_type& lineGroup = lineGroups.back();
+ uint32_t lineIndex = lineGroup.vertex_length;
+
+ for (const std::vector<ClipperLib::IntPoint>& polygon : polygons) {
+ const size_t group_count = polygon.size();
+ assert(group_count >= 3);
+
+ std::vector<TESSreal> clipped_line;
+ for (const ClipperLib::IntPoint& pt : polygon) {
+ clipped_line.push_back(pt.X);
+ clipped_line.push_back(pt.Y);
+ vertexBuffer.add(pt.X, pt.Y);
+ }
+
+ for (size_t i = 0; i < group_count; i++) {
+ const size_t prev_i = (i == 0 ? group_count : i) - 1;
+ lineElementsBuffer.add(lineIndex + prev_i, lineIndex + i);
+ }
+
+ lineIndex += group_count;
+
+ tessAddContour(tesselator, vertexSize, clipped_line.data(), stride, (int)clipped_line.size() / vertexSize);
+ }
+
+ lineGroup.elements_length += total_vertex_count;
+
+ if (tessTesselate(tesselator, TESS_WINDING_POSITIVE, TESS_POLYGONS, vertices_per_group, vertexSize, 0)) {
+ const TESSreal *vertices = tessGetVertices(tesselator);
+ const size_t vertex_count = tessGetVertexCount(tesselator);
+ TESSindex *vertex_indices = const_cast<TESSindex *>(tessGetVertexIndices(tesselator));
+ const TESSindex *elements = tessGetElements(tesselator);
+ const int triangle_count = tessGetElementCount(tesselator);
+
+ for (size_t i = 0; i < vertex_count; ++i) {
+ if (vertex_indices[i] == TESS_UNDEF) {
+ vertexBuffer.add(std::round(vertices[i * 2]), std::round(vertices[i * 2 + 1]));
+ vertex_indices[i] = (TESSindex)total_vertex_count;
+ total_vertex_count++;
+ }
+ }
+
+ if (!triangleGroups.size() || (triangleGroups.back().vertex_length + total_vertex_count > 65535)) {
+ // Move to a new group because the old one can't hold the geometry.
+ triangleGroups.emplace_back();
+ }
+
+ // We're generating triangle fans, so we always start with the first
+ // coordinate in this polygon.
+ triangle_group_type& triangleGroup = triangleGroups.back();
+ uint32_t triangleIndex = triangleGroup.vertex_length;
+
+ for (int i = 0; i < triangle_count; ++i) {
+ const TESSindex *element_group = &elements[i * vertices_per_group];
+
+ if (element_group[0] != TESS_UNDEF && element_group[1] != TESS_UNDEF && element_group[2] != TESS_UNDEF) {
+ const TESSindex a = vertex_indices[element_group[0]];
+ const TESSindex b = vertex_indices[element_group[1]];
+ const TESSindex c = vertex_indices[element_group[2]];
+
+ if (a != TESS_UNDEF && b != TESS_UNDEF && c != TESS_UNDEF) {
+ triangleElementsBuffer.add(triangleIndex + a, triangleIndex + b, triangleIndex + c);
+ } else {
+#if defined(DEBUG)
+ // TODO: We're missing a vertex that was not part of the line.
+ fprintf(stderr, "undefined element buffer\n");
+#endif
+ }
+ } else {
+#if defined(DEBUG)
+ fprintf(stderr, "undefined element buffer\n");
+#endif
+ }
+ }
+
+ triangleGroup.vertex_length += total_vertex_count;
+ triangleGroup.elements_length += triangle_count;
+ } else {
+#if defined(DEBUG)
+ fprintf(stderr, "tessellation failed\n");
+#endif
+ }
+
+ // We're adding the total vertex count *after* we added additional vertices
+ // in the tessellation step. They won't be part of the actual lines, but
+ // we need to skip over them anyway if we draw the next group.
+ lineGroup.vertex_length += total_vertex_count;
+}
+
+void FillBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) {
+ painter.renderFill(*this, layer_desc, id, matrix);
+}
+
+bool FillBucket::hasData() const {
+ return !triangleGroups.empty() || !lineGroups.empty();
+}
+
+void FillBucket::drawElements(PlainShader& shader) {
+ char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
+ char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
+ for (triangle_group_type& group : triangleGroups) {
+ group.array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * vertexBuffer.itemSize;
+ elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ }
+}
+
+void FillBucket::drawElements(PatternShader& shader) {
+ char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
+ char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
+ for (triangle_group_type& group : triangleGroups) {
+ group.array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * vertexBuffer.itemSize;
+ elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ }
+}
+
+void FillBucket::drawVertices(OutlineShader& shader) {
+ char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
+ char *elements_index = BUFFER_OFFSET(line_elements_start * lineElementsBuffer.itemSize);
+ for (line_group_type& group : lineGroups) {
+ group.array[0].bind(shader, vertexBuffer, lineElementsBuffer, vertex_index);
+ glDrawElements(GL_LINES, group.elements_length * 2, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * vertexBuffer.itemSize;
+ elements_index += group.elements_length * lineElementsBuffer.itemSize;
+ }
+}
diff --git a/src/mbgl/renderer/fill_bucket.hpp b/src/mbgl/renderer/fill_bucket.hpp
new file mode 100644
index 0000000000..ae766ec28d
--- /dev/null
+++ b/src/mbgl/renderer/fill_bucket.hpp
@@ -0,0 +1,88 @@
+#ifndef MBGL_RENDERER_FILLBUCKET
+#define MBGL_RENDERER_FILLBUCKET
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/geometry/elements_buffer.hpp>
+#include <mbgl/geometry/fill_buffer.hpp>
+#include <mbgl/style/style_bucket.hpp>
+
+#include <clipper/clipper.hpp>
+#include <libtess2/tesselator.h>
+
+#include <vector>
+#include <memory>
+
+#ifndef BUFFER_OFFSET
+#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
+#endif
+
+namespace mbgl {
+
+class Style;
+class FillVertexBuffer;
+class TriangleElementsBuffer;
+class LineElementsBuffer;
+class BucketDescription;
+class OutlineShader;
+class PlainShader;
+class PatternShader;
+struct pbf;
+
+class FillBucket : public Bucket {
+
+ static void *alloc(void *data, unsigned int size);
+ static void *realloc(void *data, void *ptr, unsigned int size);
+ static void free(void *userData, void *ptr);
+
+ typedef ElementGroup<2> triangle_group_type;
+ typedef ElementGroup<1> line_group_type;
+
+public:
+ FillBucket(FillVertexBuffer& vertexBuffer,
+ TriangleElementsBuffer& triangleElementsBuffer,
+ LineElementsBuffer& lineElementsBuffer,
+ const StyleBucketFill& properties);
+ ~FillBucket();
+
+ virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ virtual bool hasData() const;
+
+ void addGeometry(pbf& data);
+ void tessellate();
+
+ void drawElements(PlainShader& shader);
+ void drawElements(PatternShader& shader);
+ void drawVertices(OutlineShader& shader);
+
+public:
+ const StyleBucketFill &properties;
+
+private:
+ TESSalloc *allocator;
+ TESStesselator *tesselator;
+ ClipperLib::Clipper clipper;
+
+ FillVertexBuffer& vertexBuffer;
+ TriangleElementsBuffer& triangleElementsBuffer;
+ LineElementsBuffer& lineElementsBuffer;
+
+ // hold information on where the vertices are located in the FillBuffer
+ const size_t vertex_start;
+ const size_t triangle_elements_start;
+ const size_t line_elements_start;
+ VertexArrayObject array;
+
+ std::vector<triangle_group_type> triangleGroups;
+ std::vector<line_group_type> lineGroups;
+
+ std::vector<ClipperLib::IntPoint> line;
+ bool hasVertices = false;
+
+ static const int vertexSize = 2;
+ static const int stride = sizeof(TESSreal) * vertexSize;
+ static const int vertices_per_group = 3;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/frame_history.cpp b/src/mbgl/renderer/frame_history.cpp
new file mode 100644
index 0000000000..8b69162a23
--- /dev/null
+++ b/src/mbgl/renderer/frame_history.cpp
@@ -0,0 +1,85 @@
+#include <mbgl/renderer/frame_history.hpp>
+
+using namespace mbgl;
+
+// Record frame history that will be used to calculate fading params
+void FrameHistory::record(timestamp now, float zoom) {
+ // first frame ever
+ if (!history.size()) {
+ history.emplace_back(FrameSnapshot{0, zoom});
+ history.emplace_back(FrameSnapshot{0, zoom});
+ }
+
+ if (history.size() > 0 || history.back().z != zoom) {
+ history.emplace_back(FrameSnapshot{now, zoom});
+ }
+}
+
+bool FrameHistory::needsAnimation(const timestamp duration) const {
+ if (!history.size()) {
+ return false;
+ }
+
+ // If we have a value that is older than duration and whose z value is the
+ // same as the most current z value, and if all values inbetween have the
+ // same z value, we don't need animation, otherwise we probably do.
+ const FrameSnapshot &pivot = history.back();
+
+ int i = -1;
+ while ((int)history.size() > i + 1 && history[i + 1].t + duration < pivot.t) {
+ i++;
+ }
+
+ if (i < 0) {
+ // There is no frame that is older than the duration time, so we need to
+ // check all frames.
+ i = 0;
+ }
+
+ // Make sure that all subsequent snapshots have the same zoom as the last
+ // pivot element.
+ for (; (int)history.size() > i; i++) {
+ if (history[i].z != pivot.z) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+FadeProperties FrameHistory::getFadeProperties(timestamp duration)
+{
+ const timestamp currentTime = util::now();
+
+ // Remove frames until only one is outside the duration, or until there are only three
+ while (history.size() > 3 && history[1].t + duration < currentTime) {
+ history.pop_front();
+ }
+
+ if (history[1].t + duration < currentTime) {
+ history[0].z = history[1].z;
+ }
+
+ // Find the range of zoom levels we want to fade between
+ float startingZ = history.front().z;
+ const FrameSnapshot lastFrame = history.back();
+ float endingZ = lastFrame.z;
+ float lowZ = std::fmin(startingZ, endingZ);
+ float highZ = std::fmax(startingZ, endingZ);
+
+ // Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one
+ // duration
+ float zoomDiff = endingZ - history[1].z, timeDiff = lastFrame.t - history[1].t;
+ float fadedist = zoomDiff / (timeDiff / duration);
+
+ // At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
+ // bump is how much farther it would have been if it had continued zooming at the same rate
+ float bump = (currentTime - lastFrame.t) / duration * fadedist;
+
+ return FadeProperties {
+ fadedist,
+ lowZ,
+ highZ,
+ bump
+ };
+}
diff --git a/src/mbgl/renderer/frame_history.hpp b/src/mbgl/renderer/frame_history.hpp
new file mode 100644
index 0000000000..61bb59da33
--- /dev/null
+++ b/src/mbgl/renderer/frame_history.hpp
@@ -0,0 +1,40 @@
+#ifndef MBGL_RENDERER_FRAME_HISTORY
+#define MBGL_RENDERER_FRAME_HISTORY
+
+#include <deque>
+#include <cassert>
+#include <cmath>
+
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/util/time.hpp>
+
+namespace mbgl {
+
+struct FrameSnapshot {
+ explicit inline FrameSnapshot(timestamp t_, float z_) : t(t_), z(z_) {}
+ float t;
+ float z;
+};
+
+struct FadeProperties {
+ float fadedist;
+ float minfadezoom;
+ float maxfadezoom;
+ float bump;
+};
+
+class FrameHistory {
+public:
+ // Record frame history that will be used to calculate fading params
+ void record(timestamp now, float zoom);
+
+ bool needsAnimation(timestamp duration) const;
+ FadeProperties getFadeProperties(timestamp duration);
+
+public:
+ std::deque<FrameSnapshot> history;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp
new file mode 100644
index 0000000000..8267cbaba2
--- /dev/null
+++ b/src/mbgl/renderer/line_bucket.cpp
@@ -0,0 +1,403 @@
+#include <mbgl/renderer/line_bucket.hpp>
+#include <mbgl/geometry/elements_buffer.hpp>
+#include <mbgl/geometry/geometry.hpp>
+
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/map/vector_tile.hpp>
+
+#include <mbgl/util/math.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
+
+#include <cassert>
+
+struct geometry_too_long_exception : std::exception {};
+
+using namespace mbgl;
+
+LineBucket::LineBucket(LineVertexBuffer& vertexBuffer_,
+ TriangleElementsBuffer& triangleElementsBuffer_,
+ PointElementsBuffer& pointElementsBuffer_,
+ const StyleBucketLine& properties_)
+ : properties(properties_),
+ vertexBuffer(vertexBuffer_),
+ triangleElementsBuffer(triangleElementsBuffer_),
+ pointElementsBuffer(pointElementsBuffer_),
+ vertex_start(vertexBuffer_.index()),
+ triangle_elements_start(triangleElementsBuffer_.index()),
+ point_elements_start(pointElementsBuffer_.index())
+{
+}
+
+void LineBucket::addGeometry(pbf& geom) {
+ std::vector<Coordinate> line;
+ Geometry::command cmd;
+
+ Coordinate coord;
+ Geometry geometry(geom);
+ int32_t x, y;
+ while ((cmd = geometry.next(x, y)) != Geometry::end) {
+ if (cmd == Geometry::move_to) {
+ if (!line.empty()) {
+ addGeometry(line);
+ line.clear();
+ }
+ }
+ line.emplace_back(x, y);
+ }
+ if (line.size()) {
+ addGeometry(line);
+ }
+}
+
+struct TriangleElement {
+ TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) : a(a_), b(b_), c(c_) {}
+ uint16_t a, b, c;
+};
+
+typedef uint16_t PointElement;
+
+void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
+ // TODO: use roundLimit
+ // const float roundLimit = geometry.round_limit;
+
+ if (vertices.size() < 2) {
+ // fprintf(stderr, "a line must have at least two vertices\n");
+ return;
+ }
+
+ Coordinate firstVertex = vertices.front();
+ Coordinate lastVertex = vertices.back();
+ bool closed = firstVertex.x == lastVertex.x && firstVertex.y == lastVertex.y;
+
+ if (vertices.size() == 2 && closed) {
+ // fprintf(stderr, "a line may not have coincident points\n");
+ return;
+ }
+
+ CapType beginCap = properties.cap;
+ CapType endCap = closed ? CapType::Butt : properties.cap;
+
+ JoinType currentJoin = JoinType::Miter;
+
+ Coordinate currentVertex = Coordinate::null(),
+ prevVertex = Coordinate::null(),
+ nextVertex = Coordinate::null();
+ vec2<double> prevNormal = vec2<double>::null(),
+ nextNormal = vec2<double>::null();
+
+ int32_t e1 = -1, e2 = -1, e3 = -1;
+
+ int8_t flip = 1;
+ double distance = 0;
+
+ if (closed) {
+ currentVertex = vertices[vertices.size() - 2];
+ nextNormal = util::normal<double>(currentVertex, lastVertex);
+ }
+
+ int32_t start_vertex = (int32_t)vertexBuffer.index();
+
+ std::vector<TriangleElement> triangle_store;
+ std::vector<PointElement> point_store;
+
+ for (size_t i = 0; i < vertices.size(); ++i) {
+ if (nextNormal) prevNormal = { -nextNormal.x, -nextNormal.y };
+ if (currentVertex) prevVertex = currentVertex;
+
+ currentVertex = vertices[i];
+ currentJoin = properties.join;
+
+ if (prevVertex) distance += util::dist<double>(currentVertex, prevVertex);
+
+ // Find the next vertex.
+ if (i + 1 < vertices.size()) {
+ nextVertex = vertices[i + 1];
+ } else {
+ nextVertex = Coordinate::null();
+ }
+
+ // If the line is closed, we treat the last vertex like the first vertex.
+ if (!nextVertex && closed) {
+ nextVertex = vertices[1];
+ }
+
+ if (nextVertex) {
+ // if two consecutive vertices exist, skip one
+ if (currentVertex.x == nextVertex.x && currentVertex.y == nextVertex.y) continue;
+ }
+
+ // 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 reversing the previous normal
+ if (nextVertex) {
+ nextNormal = util::normal<double>(currentVertex, nextVertex);
+ } else {
+ nextNormal = { -prevNormal.x, -prevNormal.y };
+ }
+
+ // 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.x, -nextNormal.y };
+ }
+
+ // Determine the normal of the join extrusion. It is the angle bisector
+ // of the segments between the previous line and the next line.
+ vec2<double> joinNormal = {
+ prevNormal.x + nextNormal.x,
+ prevNormal.y + nextNormal.y
+ };
+
+ // Cross product yields 0..1 depending on whether they are parallel
+ // or perpendicular.
+ double joinAngularity = nextNormal.x * joinNormal.y - nextNormal.y * joinNormal.x;
+ joinNormal.x /= joinAngularity;
+ joinNormal.y /= joinAngularity;
+ double roundness = std::fmax(std::abs(joinNormal.x), std::abs(joinNormal.y));
+
+
+ // Switch to miter joins if the angle is very low.
+ if (currentJoin != JoinType::Miter) {
+ if (std::fabs(joinAngularity) < 0.5 && roundness < properties.miter_limit) {
+ currentJoin = JoinType::Miter;
+ }
+ }
+
+ // Add offset square begin cap.
+ if (!prevVertex && beginCap == CapType::Square) {
+ // Add first vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal
+ 0, 0, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+
+ // Add second vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal
+ 0, 1, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+ }
+
+ // Add offset square end cap.
+ else if (!nextVertex && endCap == CapType::Square) {
+ // Add first vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal
+ 0, 0, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+
+ // Add second vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal
+ 0, 1, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+ }
+
+ else if (currentJoin == JoinType::Miter) {
+ // MITER JOIN
+ if (std::fabs(joinAngularity) < 0.01) {
+ // The two normals are almost parallel.
+ joinNormal.x = -nextNormal.y;
+ joinNormal.y = nextNormal.x;
+ } else if (roundness > properties.miter_limit) {
+ // If the miter grows too large, flip the direction to make a
+ // bevel join.
+ joinNormal.x = (prevNormal.x - nextNormal.x) / joinAngularity;
+ joinNormal.y = (prevNormal.y - nextNormal.y) / joinAngularity;
+ }
+
+ if (roundness > properties.miter_limit) {
+ flip = -flip;
+ }
+
+ // Add first vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ flip * joinNormal.x, flip * joinNormal.y, // extrude normal
+ 0, 0, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+
+ // Add second vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ -flip * joinNormal.x, -flip * joinNormal.y, // extrude normal
+ 0, 1, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+
+ if ((!prevVertex && beginCap == CapType::Round) ||
+ (!nextVertex && endCap == CapType::Round)) {
+ point_store.emplace_back(e1);
+ }
+ }
+
+ else {
+ // Close up the previous line
+ // Add first vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ flip * prevNormal.y, -flip * prevNormal.x, // extrude normal
+ 0, 0, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+
+ // Add second vertex.
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ -flip * prevNormal.y, flip * prevNormal.x, // extrude normal
+ 0, 1, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+
+ prevNormal = { -nextNormal.x, -nextNormal.y };
+ flip = 1;
+
+
+ // begin/end caps
+ if ((!prevVertex && beginCap == CapType::Round) ||
+ (!nextVertex && endCap == CapType::Round)) {
+ point_store.emplace_back(e1);
+ }
+
+
+ if (currentJoin == JoinType::Round) {
+ if (prevVertex && nextVertex && (!closed || i > 0)) {
+ point_store.emplace_back(e1);
+ }
+
+ // Reset the previous vertices so that we don't accidentally create
+ // any triangles.
+ e1 = -1; e2 = -1; e3 = -1;
+ }
+
+ // Start the new quad.
+ // Add first vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ -flip * nextNormal.y, flip * nextNormal.x, // extrude normal
+ 0, 0, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+
+ // Add second vertex
+ e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
+ flip * nextNormal.y, -flip * nextNormal.x, // extrude normal
+ 0, 1, distance) - start_vertex; // texture normal
+
+ if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3);
+ e1 = e2; e2 = e3;
+ }
+ }
+
+ size_t end_vertex = vertexBuffer.index();
+ size_t vertex_count = end_vertex - start_vertex;
+
+ // Store the triangle/line groups.
+ {
+ if (!triangleGroups.size() || (triangleGroups.back().vertex_length + vertex_count > 65535)) {
+ // Move to a new group because the old one can't hold the geometry.
+ triangleGroups.emplace_back();
+ }
+
+ triangle_group_type& group = triangleGroups.back();
+ for (const TriangleElement& triangle : triangle_store) {
+ triangleElementsBuffer.add(
+ group.vertex_length + triangle.a,
+ group.vertex_length + triangle.b,
+ group.vertex_length + triangle.c
+ );
+ }
+
+ group.vertex_length += vertex_count;
+ group.elements_length += triangle_store.size();
+ }
+
+ // Store the line join/cap groups.
+ {
+ if (!pointGroups.size() || (pointGroups.back().vertex_length + vertex_count > 65535)) {
+ // Move to a new group because the old one can't hold the geometry.
+ pointGroups.emplace_back();
+ }
+
+ point_group_type& group = pointGroups.back();
+ for (PointElement point : point_store) {
+ pointElementsBuffer.add(group.vertex_length + point);
+ }
+
+ group.vertex_length += vertex_count;
+ group.elements_length += point_store.size();
+ }
+}
+
+void LineBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) {
+ painter.renderLine(*this, layer_desc, id, matrix);
+}
+
+bool LineBucket::hasData() const {
+ return !triangleGroups.empty() || !pointGroups.empty();
+}
+
+bool LineBucket::hasPoints() const {
+ if (!pointGroups.empty()) {
+ for (const point_group_type& group : pointGroups) {
+ if (group.elements_length) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void LineBucket::drawLines(LineShader& shader) {
+ char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
+ char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
+ for (triangle_group_type& group : triangleGroups) {
+ if (!group.elements_length) {
+ continue;
+ }
+ group.array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * vertexBuffer.itemSize;
+ elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ }
+}
+
+void LineBucket::drawLinePatterns(LinepatternShader& shader) {
+ char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
+ char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
+ for (triangle_group_type& group : triangleGroups) {
+ if (!group.elements_length) {
+ continue;
+ }
+ group.array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
+ glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * vertexBuffer.itemSize;
+ elements_index += group.elements_length * triangleElementsBuffer.itemSize;
+ }
+}
+
+void LineBucket::drawPoints(LinejoinShader& shader) {
+ char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
+ char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer.itemSize);
+ for (point_group_type& group : pointGroups) {
+ if (!group.elements_length) {
+ continue;
+ }
+ group.array[0].bind(shader, vertexBuffer, pointElementsBuffer, vertex_index);
+ glDrawElements(GL_POINTS, group.elements_length, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * vertexBuffer.itemSize;
+ elements_index += group.elements_length * pointElementsBuffer.itemSize;
+ }
+}
diff --git a/src/mbgl/renderer/line_bucket.hpp b/src/mbgl/renderer/line_bucket.hpp
new file mode 100644
index 0000000000..7337ca80ad
--- /dev/null
+++ b/src/mbgl/renderer/line_bucket.hpp
@@ -0,0 +1,62 @@
+#ifndef MBGL_RENDERER_LINEBUCKET
+#define MBGL_RENDERER_LINEBUCKET
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/geometry/vao.hpp>
+#include <mbgl/geometry/elements_buffer.hpp>
+#include <mbgl/geometry/line_buffer.hpp>
+#include <mbgl/style/style_bucket.hpp>
+
+#include <vector>
+
+namespace mbgl {
+
+class Style;
+class LineVertexBuffer;
+class TriangleElementsBuffer;
+class LineShader;
+class LinejoinShader;
+class LinepatternShader;
+struct pbf;
+
+class LineBucket : public Bucket {
+ typedef ElementGroup<2> triangle_group_type;
+ typedef ElementGroup<1> point_group_type;
+
+public:
+ LineBucket(LineVertexBuffer& vertexBuffer,
+ TriangleElementsBuffer& triangleElementsBuffer,
+ PointElementsBuffer& pointElementsBuffer,
+ const StyleBucketLine& properties);
+
+ virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ virtual bool hasData() const;
+
+ void addGeometry(pbf& data);
+ void addGeometry(const std::vector<Coordinate>& line);
+
+ bool hasPoints() const;
+
+ void drawLines(LineShader& shader);
+ void drawLinePatterns(LinepatternShader& shader);
+ void drawPoints(LinejoinShader& shader);
+
+public:
+ const StyleBucketLine &properties;
+
+private:
+ LineVertexBuffer& vertexBuffer;
+ TriangleElementsBuffer& triangleElementsBuffer;
+ PointElementsBuffer& pointElementsBuffer;
+
+ const size_t vertex_start;
+ const size_t triangle_elements_start;
+ const size_t point_elements_start;
+
+ std::vector<triangle_group_type> triangleGroups;
+ std::vector<point_group_type> pointGroups;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp
new file mode 100644
index 0000000000..bfc59942e7
--- /dev/null
+++ b/src/mbgl/renderer/painter.cpp
@@ -0,0 +1,465 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/clip_ids.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/mat3.hpp>
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/map/source.hpp>
+
+#if defined(DEBUG)
+#include <mbgl/util/stopwatch.hpp>
+#endif
+
+#include <cassert>
+#include <algorithm>
+#include <iostream>
+
+using namespace mbgl;
+
+#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
+
+Painter::Painter(SpriteAtlas& spriteAtlas_, GlyphAtlas& glyphAtlas_)
+ : spriteAtlas(spriteAtlas_)
+ , glyphAtlas(glyphAtlas_)
+{
+}
+
+Painter::~Painter() {
+ cleanup();
+}
+
+bool Painter::needsAnimation() const {
+ return frameHistory.needsAnimation(300);
+}
+
+void Painter::setup() {
+#if defined(DEBUG)
+ util::stopwatch stopwatch("painter setup");
+#endif
+
+ // Enable GL debugging
+ if ((gl::DebugMessageControl != nullptr) && (gl::DebugMessageCallback != nullptr)) {
+ // This will enable all messages including performance hints
+ //gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
+
+ // This will only enable high and medium severity messages
+ gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE);
+ gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, GL_TRUE);
+
+ gl::DebugMessageCallback(gl::debug_callback, nullptr);
+ }
+
+ setupShaders();
+
+ assert(iconShader);
+ assert(plainShader);
+ assert(outlineShader);
+ assert(lineShader);
+ assert(linejoinShader);
+ assert(linepatternShader);
+ assert(patternShader);
+ assert(rasterShader);
+ assert(sdfGlyphShader);
+ assert(sdfIconShader);
+ assert(dotShader);
+ assert(gaussianShader);
+
+
+ // Blending
+ // We are blending new pixels on top of old pixels. Since we have depth testing
+ // and are drawing opaque fragments first front-to-back, then translucent
+ // fragments back-to-front, this shades the fewest fragments possible.
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ // Set clear values
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClearDepth(1.0f);
+ glClearStencil(0x0);
+
+ // Stencil test
+ glEnable(GL_STENCIL_TEST);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+}
+
+void Painter::setupShaders() {
+ if (!plainShader) plainShader = util::make_unique<PlainShader>();
+ if (!outlineShader) outlineShader = util::make_unique<OutlineShader>();
+ if (!lineShader) lineShader = util::make_unique<LineShader>();
+ if (!linejoinShader) linejoinShader = util::make_unique<LinejoinShader>();
+ if (!linepatternShader) linepatternShader = util::make_unique<LinepatternShader>();
+ if (!patternShader) patternShader = util::make_unique<PatternShader>();
+ if (!iconShader) iconShader = util::make_unique<IconShader>();
+ if (!rasterShader) rasterShader = util::make_unique<RasterShader>();
+ if (!sdfGlyphShader) sdfGlyphShader = util::make_unique<SDFGlyphShader>();
+ if (!sdfIconShader) sdfIconShader = util::make_unique<SDFIconShader>();
+ if (!dotShader) dotShader = util::make_unique<DotShader>();
+ if (!gaussianShader) gaussianShader = util::make_unique<GaussianShader>();
+}
+
+void Painter::deleteShaders() {
+ plainShader = nullptr;
+ outlineShader = nullptr;
+ lineShader = nullptr;
+ linejoinShader = nullptr;
+ linepatternShader = nullptr;
+ patternShader = nullptr;
+ iconShader = nullptr;
+ rasterShader = nullptr;
+ sdfGlyphShader = nullptr;
+ sdfIconShader = nullptr;
+ dotShader = nullptr;
+ gaussianShader = nullptr;
+}
+
+void Painter::cleanup() {
+}
+
+void Painter::terminate() {
+ cleanup();
+ deleteShaders();
+}
+
+void Painter::resize() {
+ if (gl_viewport != state.getFramebufferDimensions()) {
+ gl_viewport = state.getFramebufferDimensions();
+ assert(gl_viewport[0] > 0 && gl_viewport[1] > 0);
+ glViewport(0, 0, gl_viewport[0], gl_viewport[1]);
+ }
+}
+
+void Painter::setDebug(bool enabled) {
+ debug = enabled;
+}
+
+void Painter::useProgram(uint32_t program) {
+ if (gl_program != program) {
+ glUseProgram(program);
+ gl_program = program;
+ }
+}
+
+void Painter::lineWidth(float line_width) {
+ if (gl_lineWidth != line_width) {
+ glLineWidth(line_width);
+ gl_lineWidth = line_width;
+ }
+}
+
+void Painter::depthMask(bool value) {
+ if (gl_depthMask != value) {
+ glDepthMask(value ? GL_TRUE : GL_FALSE);
+ gl_depthMask = value;
+ }
+}
+
+void Painter::depthRange(const float near, const float far) {
+ if (gl_depthRange[0] != near || gl_depthRange[1] != far) {
+ glDepthRange(near, far);
+ gl_depthRange = {{ near, far }};
+ }
+}
+
+
+void Painter::changeMatrix() {
+ // Initialize projection matrix
+ matrix::ortho(projMatrix, 0, state.getWidth(), state.getHeight(), 0, 0, 1);
+
+ // The extrusion matrix.
+ matrix::identity(extrudeMatrix);
+ matrix::multiply(extrudeMatrix, projMatrix, extrudeMatrix);
+ matrix::rotate_z(extrudeMatrix, extrudeMatrix, state.getAngle());
+
+ // The native matrix is a 1:1 matrix that paints the coordinates at the
+ // same screen position as the vertex specifies.
+ matrix::identity(nativeMatrix);
+ matrix::multiply(nativeMatrix, projMatrix, nativeMatrix);
+}
+
+void Painter::clear() {
+ gl::group group("clear");
+ glStencilMask(0xFF);
+ depthMask(true);
+
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void Painter::setOpaque() {
+ if (pass != RenderPass::Opaque) {
+ pass = RenderPass::Opaque;
+ glDisable(GL_BLEND);
+ depthMask(true);
+ }
+}
+
+void Painter::setTranslucent() {
+ if (pass != RenderPass::Translucent) {
+ pass = RenderPass::Translucent;
+ glEnable(GL_BLEND);
+ depthMask(false);
+ }
+}
+
+void Painter::setStrata(float value) {
+ strata = value;
+}
+
+void Painter::prepareTile(const Tile& tile) {
+ const GLint ref = (GLint)tile.clip.reference.to_ulong();
+ const GLuint mask = (GLuint)tile.clip.mask.to_ulong();
+ glStencilFunc(GL_EQUAL, ref, mask);
+}
+
+void Painter::render(const Style& style, const std::set<util::ptr<StyleSource>>& sources,
+ TransformState state_, timestamp time) {
+ state = state_;
+
+ clear();
+ resize();
+ changeMatrix();
+
+ // Update all clipping IDs.
+ ClipIDGenerator generator;
+ for (const util::ptr<StyleSource> &source : sources) {
+ generator.update(source->source->getLoadedTiles());
+ source->source->updateMatrices(projMatrix, state);
+ }
+
+ drawClippingMasks(sources);
+
+ frameHistory.record(time, state.getNormalizedZoom());
+
+ // Actually render the layers
+ if (debug::renderTree) { std::cout << "{" << std::endl; indent++; }
+ renderLayers(style.layers);
+ if (debug::renderTree) { std::cout << "}" << std::endl; indent--; }
+
+ // Finalize the rendering, e.g. by calling debug render calls per tile.
+ // This guarantees that we have at least one function per tile called.
+ // When only rendering layers via the stylesheet, it's possible that we don't
+ // ever visit a tile during rendering.
+ for (const util::ptr<StyleSource> &source : sources) {
+ source->source->finishRender(*this);
+ }
+
+ glFlush();
+}
+
+void Painter::renderLayers(util::ptr<StyleLayerGroup> group) {
+ if (!group) {
+ // Make sure that we actually do have a layer group.
+ return;
+ }
+
+ // TODO: Correctly compute the number of layers recursively beforehand.
+ float strata_thickness = 1.0f / (group->layers.size() + 1);
+
+ // - FIRST PASS ------------------------------------------------------------
+ // Render everything top-to-bottom by using reverse iterators. Render opaque
+ // objects first.
+
+ if (debug::renderTree) {
+ std::cout << std::string(indent++ * 4, ' ') << "OPAQUE {" << std::endl;
+ }
+ int i = 0;
+ for (auto it = group->layers.rbegin(), end = group->layers.rend(); it != end; ++it, ++i) {
+ setOpaque();
+ setStrata(i * strata_thickness);
+ renderLayer(*it);
+ }
+ if (debug::renderTree) {
+ std::cout << std::string(--indent * 4, ' ') << "}" << std::endl;
+ }
+
+ // - SECOND PASS -----------------------------------------------------------
+ // Make a second pass, rendering translucent objects. This time, we render
+ // bottom-to-top.
+ if (debug::renderTree) {
+ std::cout << std::string(indent++ * 4, ' ') << "TRANSLUCENT {" << std::endl;
+ }
+ --i;
+ for (auto it = group->layers.begin(), end = group->layers.end(); it != end; ++it, --i) {
+ setTranslucent();
+ setStrata(i * strata_thickness);
+ renderLayer(*it);
+ }
+ if (debug::renderTree) {
+ std::cout << std::string(--indent * 4, ' ') << "}" << std::endl;
+ }
+}
+
+void Painter::renderLayer(util::ptr<StyleLayer> layer_desc, const Tile::ID* id, const mat4* matrix) {
+ if (layer_desc->type == StyleLayerType::Background) {
+ // This layer defines a background color/image.
+
+ if (debug::renderTree) {
+ std::cout << std::string(indent * 4, ' ') << "- " << layer_desc->id << " ("
+ << layer_desc->type << ")" << std::endl;
+ }
+
+ renderBackground(layer_desc);
+ } else {
+ // This is a singular layer.
+ if (!layer_desc->bucket) {
+ fprintf(stderr, "[WARNING] layer '%s' is missing bucket\n", layer_desc->id.c_str());
+ return;
+ }
+
+ if (!layer_desc->bucket->style_source) {
+ fprintf(stderr, "[WARNING] can't find source for layer '%s'\n", layer_desc->id.c_str());
+ return;
+ }
+
+ StyleSource const& style_source = *layer_desc->bucket->style_source;
+
+ // Skip this layer if there is no data.
+ if (!style_source.source) {
+ return;
+ }
+
+ // Skip this layer if it's outside the range of min/maxzoom.
+ // This may occur when there /is/ a bucket created for this layer, but the min/max-zoom
+ // is set to a fractional value, or value that is larger than the source maxzoom.
+ const double zoom = state.getZoom();
+ if (layer_desc->bucket->min_zoom > zoom ||
+ layer_desc->bucket->max_zoom <= zoom) {
+ return;
+ }
+
+ // Abort early if we can already deduce from the bucket type that
+ // we're not going to render anything anyway during this pass.
+ switch (layer_desc->type) {
+ case StyleLayerType::Fill:
+ if (!layer_desc->getProperties<FillProperties>().isVisible()) return;
+ break;
+ case StyleLayerType::Line:
+ if (pass == RenderPass::Opaque) return;
+ if (!layer_desc->getProperties<LineProperties>().isVisible()) return;
+ break;
+ case StyleLayerType::Symbol:
+ if (pass == RenderPass::Opaque) return;
+ if (!layer_desc->getProperties<SymbolProperties>().isVisible()) return;
+ break;
+ case StyleLayerType::Raster:
+ if (pass == RenderPass::Opaque) return;
+ if (!layer_desc->getProperties<RasterProperties>().isVisible()) return;
+ break;
+ default:
+ break;
+ }
+
+ if (debug::renderTree) {
+ std::cout << std::string(indent * 4, ' ') << "- " << layer_desc->id << " ("
+ << layer_desc->type << ")" << std::endl;
+ }
+ if (!id) {
+ style_source.source->render(*this, layer_desc);
+ } else {
+ style_source.source->render(*this, layer_desc, *id, *matrix);
+ }
+ }
+}
+
+void Painter::renderTileLayer(const Tile& tile, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) {
+ assert(tile.data);
+ if (tile.data->hasData(*layer_desc) || layer_desc->type == StyleLayerType::Raster) {
+ gl::group group(std::string { "render " } + tile.data->name);
+ prepareTile(tile);
+ tile.data->render(*this, layer_desc, matrix);
+ }
+}
+
+void Painter::renderBackground(util::ptr<StyleLayer> layer_desc) {
+ const BackgroundProperties& properties = layer_desc->getProperties<BackgroundProperties>();
+
+ if (properties.image.size()) {
+ if ((properties.opacity >= 1.0f) != (pass == RenderPass::Opaque))
+ return;
+
+ SpriteAtlasPosition imagePos = spriteAtlas.getPosition(properties.image, true);
+ float zoomFraction = state.getZoomFraction();
+
+ useProgram(patternShader->program);
+ patternShader->u_matrix = identityMatrix;
+ patternShader->u_pattern_tl = imagePos.tl;
+ patternShader->u_pattern_br = imagePos.br;
+ patternShader->u_mix = zoomFraction;
+ patternShader->u_opacity = properties.opacity;
+
+ std::array<float, 2> size = imagePos.size;
+ double lon, lat;
+ state.getLonLat(lon, lat);
+ std::array<float, 2> center = state.locationCoordinate(lon, lat);
+ float scale = 1 / std::pow(2, zoomFraction);
+
+ mat3 matrix;
+ matrix::identity(matrix);
+ matrix::scale(matrix, matrix,
+ 1.0f / size[0],
+ 1.0f / size[1]);
+ matrix::translate(matrix, matrix,
+ std::fmod(center[0] * 512, size[0]),
+ std::fmod(center[1] * 512, size[1]));
+ matrix::rotate(matrix, matrix, -state.getAngle());
+ matrix::scale(matrix, matrix,
+ scale * state.getWidth() / 2,
+ -scale * state.getHeight() / 2);
+ patternShader->u_patternmatrix = matrix;
+
+ backgroundBuffer.bind();
+ patternShader->bind(0);
+ spriteAtlas.bind(true);
+ } else {
+ Color color = properties.color;
+ color[0] *= properties.opacity;
+ color[1] *= properties.opacity;
+ color[2] *= properties.opacity;
+ color[3] *= properties.opacity;
+
+ if ((color[3] >= 1.0f) != (pass == RenderPass::Opaque))
+ return;
+
+ useProgram(plainShader->program);
+ plainShader->u_matrix = identityMatrix;
+ plainShader->u_color = color;
+ backgroundArray.bind(*plainShader, backgroundBuffer, BUFFER_OFFSET(0));
+ }
+
+ glDisable(GL_STENCIL_TEST);
+ depthRange(strata + strata_epsilon, 1.0f);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ glEnable(GL_STENCIL_TEST);
+}
+
+mat4 Painter::translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor) {
+ if (translation[0] == 0 && translation[1] == 0) {
+ return matrix;
+ } else {
+ // TODO: Get rid of the 8 (scaling from 4096 to tile size)
+ const double factor = ((double)(1 << id.z)) / state.getScale() * (4096.0 / util::tileSize);
+
+ 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, matrix,
+ factor * (translation[0] * cos_a - translation[1] * sin_a),
+ factor * (translation[0] * sin_a + translation[1] * cos_a),
+ 0);
+ } else {
+ matrix::translate(vtxMatrix, matrix,
+ factor * translation[0],
+ factor * translation[1],
+ 0);
+ }
+
+ return vtxMatrix;
+ }
+}
diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp
new file mode 100644
index 0000000000..be4bd12710
--- /dev/null
+++ b/src/mbgl/renderer/painter.hpp
@@ -0,0 +1,259 @@
+#ifndef MBGL_RENDERER_PAINTER
+#define MBGL_RENDERER_PAINTER
+
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/geometry/vao.hpp>
+#include <mbgl/geometry/static_vertex_buffer.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/renderer/frame_history.hpp>
+#include <mbgl/style/types.hpp>
+
+#include <mbgl/shader/plain_shader.hpp>
+#include <mbgl/shader/outline_shader.hpp>
+#include <mbgl/shader/pattern_shader.hpp>
+#include <mbgl/shader/line_shader.hpp>
+#include <mbgl/shader/linejoin_shader.hpp>
+#include <mbgl/shader/linepattern_shader.hpp>
+#include <mbgl/shader/icon_shader.hpp>
+#include <mbgl/shader/raster_shader.hpp>
+#include <mbgl/shader/sdf_shader.hpp>
+#include <mbgl/shader/dot_shader.hpp>
+#include <mbgl/shader/gaussian_shader.hpp>
+
+#include <mbgl/map/transform_state.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <map>
+#include <unordered_map>
+#include <set>
+
+namespace mbgl {
+
+enum class RenderPass : bool { Opaque, Translucent };
+
+class Transform;
+class Style;
+class Tile;
+class Sprite;
+class SpriteAtlas;
+class GlyphAtlas;
+class Source;
+class StyleSource;
+class StyleLayerGroup;
+
+class FillBucket;
+class LineBucket;
+class SymbolBucket;
+class RasterBucket;
+class PrerenderedTexture;
+
+struct FillProperties;
+struct RasterProperties;
+
+class LayerDescription;
+class RasterTileData;
+
+class Painter : private util::noncopyable {
+public:
+ Painter(SpriteAtlas&, GlyphAtlas&);
+ ~Painter();
+
+ void setup();
+
+ // Perform cleanup tasks that prepare shutting down the app. This doesn't mean that the
+ // app will be shut down. That means all operations must be automatically be reversed (e.g. through
+ // lazy initialization) in case rendering continues.
+ void cleanup();
+
+ void terminate();
+
+ // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any
+ // tiles whatsoever.
+ void clear();
+
+ // Updates the default matrices to the current viewport dimensions.
+ void changeMatrix();
+
+ void render(const Style& style,
+ const std::set<util::ptr<StyleSource>>& sources,
+ TransformState state,
+ timestamp time);
+
+ void renderLayers(util::ptr<StyleLayerGroup> group);
+ void renderLayer(util::ptr<StyleLayer> layer_desc, const Tile::ID* id = nullptr, const mat4* matrix = nullptr);
+
+ // Renders a particular layer from a tile.
+ void renderTileLayer(const Tile& tile, util::ptr<StyleLayer> layer_desc, const mat4 &matrix);
+
+ // Renders debug information for a tile.
+ void renderTileDebug(const Tile& tile);
+
+ // Renders the red debug frame around a tile, visualizing its perimeter.
+ void renderDebugFrame(const mat4 &matrix);
+
+ void renderDebugText(DebugBucket& bucket, const mat4 &matrix);
+ void renderDebugText(const std::vector<std::string> &strings);
+ void renderFill(FillBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ void renderLine(LineBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ void renderSymbol(SymbolBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ void renderRaster(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ void renderBackground(util::ptr<StyleLayer> layer_desc);
+
+ float saturationFactor(float saturation);
+ float contrastFactor(float contrast);
+ std::array<float, 3> spinWeights(float spin_value);
+
+ void preparePrerender(RasterBucket &bucket);
+
+ void renderPrerenderedTexture(RasterBucket &bucket, const mat4 &matrix, const RasterProperties& properties);
+
+ void createPrerendered(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id);
+
+ void resize();
+
+ // Changes whether debug information is drawn onto the map
+ void setDebug(bool enabled);
+
+ // Opaque/Translucent pass setting
+ void setOpaque();
+ void setTranslucent();
+
+ // Configures the painter strata that is used for early z-culling of fragments.
+ void setStrata(float strata);
+
+ void drawClippingMasks(const std::set<util::ptr<StyleSource>> &sources);
+ void drawClippingMask(const mat4& matrix, const ClipID& clip);
+
+ void resetFramebuffer();
+ void bindFramebuffer();
+ void pushFramebuffer();
+ GLuint popFramebuffer();
+ void discardFramebuffers();
+
+ bool needsAnimation() const;
+
+private:
+ void setupShaders();
+ void deleteShaders();
+ mat4 translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor);
+
+ void prepareTile(const Tile& tile);
+
+ template <typename BucketProperties, typename StyleProperties>
+ void renderSDF(SymbolBucket &bucket,
+ const Tile::ID &id,
+ const mat4 &matrixSymbol,
+ const BucketProperties& bucketProperties,
+ const StyleProperties& styleProperties,
+ float scaleDivisor,
+ std::array<float, 2> texsize,
+ SDFShader& sdfShader,
+ void (SymbolBucket::*drawSDF)(SDFShader&));
+
+public:
+ void useProgram(uint32_t program);
+ void lineWidth(float lineWidth);
+ void depthMask(bool value);
+ void depthRange(float near, float far);
+
+public:
+ mat4 projMatrix;
+ mat4 nativeMatrix;
+ mat4 extrudeMatrix;
+
+ // used to composite images and flips the geometry upside down
+ const mat4 flipMatrix = []{
+ mat4 flip;
+ matrix::ortho(flip, 0, 4096, -4096, 0, 0, 1);
+ matrix::translate(flip, flip, 0, -4096, 0);
+ return flip;
+ }();
+
+ const mat4 identityMatrix = []{
+ mat4 identity;
+ matrix::identity(identity);
+ return identity;
+ }();
+
+private:
+ TransformState state;
+
+ bool debug = false;
+ int indent = 0;
+
+ uint32_t gl_program = 0;
+ float gl_lineWidth = 0;
+ bool gl_depthMask = true;
+ std::array<uint16_t, 2> gl_viewport = {{ 0, 0 }};
+ std::array<float, 2> gl_depthRange = {{ 0, 1 }};
+ float strata = 0;
+ RenderPass pass = RenderPass::Opaque;
+ const float strata_epsilon = 1.0f / (1 << 16);
+
+public:
+ FrameHistory frameHistory;
+
+ SpriteAtlas& spriteAtlas;
+ GlyphAtlas& glyphAtlas;
+
+ std::unique_ptr<PlainShader> plainShader;
+ std::unique_ptr<OutlineShader> outlineShader;
+ std::unique_ptr<LineShader> lineShader;
+ std::unique_ptr<LinejoinShader> linejoinShader;
+ std::unique_ptr<LinepatternShader> linepatternShader;
+ std::unique_ptr<PatternShader> patternShader;
+ std::unique_ptr<IconShader> iconShader;
+ std::unique_ptr<RasterShader> rasterShader;
+ std::unique_ptr<SDFGlyphShader> sdfGlyphShader;
+ std::unique_ptr<SDFIconShader> sdfIconShader;
+ std::unique_ptr<DotShader> dotShader;
+ std::unique_ptr<GaussianShader> gaussianShader;
+
+ StaticVertexBuffer backgroundBuffer = {
+ { -1, -1 }, { 1, -1 },
+ { -1, 1 }, { 1, 1 }
+ };
+
+ VertexArrayObject backgroundArray;
+
+ // Set up the stencil quad we're using to generate the stencil mask.
+ StaticVertexBuffer tileStencilBuffer = {
+ // top left triangle
+ { 0, 0 },
+ { 4096, 0 },
+ { 0, 4096 },
+
+ // bottom right triangle
+ { 4096, 0 },
+ { 0, 4096 },
+ { 4096, 4096 },
+ };
+
+ VertexArrayObject coveringPlainArray;
+ VertexArrayObject coveringRasterArray;
+ VertexArrayObject coveringGaussianArray;
+
+ // Set up the tile boundary lines we're using to draw the tile outlines.
+ StaticVertexBuffer tileBorderBuffer = {
+ { 0, 0 },
+ { 4096, 0 },
+ { 4096, 4096 },
+ { 0, 4096 },
+ { 0, 0 },
+ };
+
+ VertexArrayObject tileBorderArray;
+
+ // Framebuffer management
+ std::vector<GLuint> fbos;
+ std::vector<GLuint> fbos_color;
+ GLuint fbo_depth_stencil;
+ int fbo_level = -1;
+ bool fbo_depth_stencil_valid = false;
+
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/painter_clipping.cpp b/src/mbgl/renderer/painter_clipping.cpp
new file mode 100644
index 0000000000..dc625ded4e
--- /dev/null
+++ b/src/mbgl/renderer/painter_clipping.cpp
@@ -0,0 +1,39 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/renderer/fill_bucket.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/source.hpp>
+#include <mbgl/util/clip_ids.hpp>
+
+using namespace mbgl;
+
+void Painter::drawClippingMasks(const std::set<util::ptr<StyleSource>> &sources) {
+ gl::group group("clipping masks");
+
+ useProgram(plainShader->program);
+ glDisable(GL_DEPTH_TEST);
+ depthMask(false);
+ glColorMask(false, false, false, false);
+ depthRange(1.0f, 1.0f);
+
+ coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
+
+ for (const util::ptr<StyleSource> &source : sources) {
+ source->source->drawClippingMasks(*this);
+ }
+
+ glEnable(GL_DEPTH_TEST);
+ glColorMask(true, true, true, true);
+ depthMask(true);
+ glStencilMask(0x0);
+}
+
+void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) {
+ plainShader->u_matrix = matrix;
+
+ const GLint ref = (GLint)(clip.reference.to_ulong());
+ const GLuint mask = (GLuint)(clip.mask.to_ulong());
+ glStencilFunc(GL_ALWAYS, ref, mask);
+ glStencilMask(mask);
+
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
+}
diff --git a/src/mbgl/renderer/painter_debug.cpp b/src/mbgl/renderer/painter_debug.cpp
new file mode 100644
index 0000000000..c4d273aa47
--- /dev/null
+++ b/src/mbgl/renderer/painter_debug.cpp
@@ -0,0 +1,102 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/renderer/debug_bucket.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/util/string.hpp>
+
+using namespace mbgl;
+
+void Painter::renderTileDebug(const Tile& tile) {
+ gl::group group(std::string { "debug " } + std::string(tile.id));
+ assert(tile.data);
+ if (debug) {
+ prepareTile(tile);
+ renderDebugText(tile.data->debugBucket, tile.matrix);
+ renderDebugFrame(tile.matrix);
+ }
+}
+
+void Painter::renderDebugText(DebugBucket& bucket, const mat4 &matrix) {
+ gl::group group("debug text");
+
+ glDisable(GL_DEPTH_TEST);
+
+ useProgram(plainShader->program);
+ plainShader->u_matrix = matrix;
+
+ // Draw white outline
+ plainShader->u_color = {{ 1.0f, 1.0f, 1.0f, 1.0f }};
+ lineWidth(4.0f * state.getPixelRatio());
+ bucket.drawLines(*plainShader);
+
+#ifndef GL_ES_VERSION_2_0
+ // Draw line "end caps"
+ glPointSize(2);
+ bucket.drawPoints(*plainShader);
+#endif
+
+ // Draw black text.
+ plainShader->u_color = {{ 0.0f, 0.0f, 0.0f, 1.0f }};
+ lineWidth(2.0f * state.getPixelRatio());
+ bucket.drawLines(*plainShader);
+
+ glEnable(GL_DEPTH_TEST);
+}
+
+void Painter::renderDebugFrame(const mat4 &matrix) {
+ gl::group group("debug frame");
+
+ // Disable depth test and don't count this towards the depth buffer,
+ // but *don't* disable stencil test, as we want to clip the red tile border
+ // to the tile viewport.
+ glDisable(GL_DEPTH_TEST);
+
+ useProgram(plainShader->program);
+ plainShader->u_matrix = matrix;
+
+ // draw tile outline
+ tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
+ plainShader->u_color = {{ 1.0f, 0.0f, 0.0f, 1.0f }};
+ lineWidth(4.0f * state.getPixelRatio());
+ glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index());
+
+ glEnable(GL_DEPTH_TEST);
+}
+
+void Painter::renderDebugText(const std::vector<std::string> &strings) {
+ if (strings.empty()) {
+ return;
+ }
+
+ gl::group group("debug text");
+
+ glDisable(GL_DEPTH_TEST);
+ glStencilFunc(GL_ALWAYS, 0xFF, 0xFF);
+
+ useProgram(plainShader->program);
+ plainShader->u_matrix = nativeMatrix;
+
+ DebugFontBuffer debugFontBuffer;
+ int line = 25;
+ for (const std::string &str : strings) {
+ debugFontBuffer.addText(str.c_str(), 10, line, 0.75);
+ line += 20;
+ }
+
+ if (!debugFontBuffer.empty()) {
+ // draw debug info
+ VertexArrayObject debugFontArray;
+ debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0));
+ plainShader->u_color = {{ 1.0f, 1.0f, 1.0f, 1.0f }};
+ lineWidth(4.0f * state.getPixelRatio());
+ glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
+ #ifndef GL_ES_VERSION_2_0
+ glPointSize(2);
+ glDrawArrays(GL_POINTS, 0, (GLsizei)debugFontBuffer.index());
+ #endif
+ plainShader->u_color = {{ 0.0f, 0.0f, 0.0f, 1.0f }};
+ lineWidth(2.0f * state.getPixelRatio());
+ glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
+ }
+
+ glEnable(GL_DEPTH_TEST);
+}
diff --git a/src/mbgl/renderer/painter_fill.cpp b/src/mbgl/renderer/painter_fill.cpp
new file mode 100644
index 0000000000..f2759ffd61
--- /dev/null
+++ b/src/mbgl/renderer/painter_fill.cpp
@@ -0,0 +1,122 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/renderer/fill_bucket.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/sprite.hpp>
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/mat3.hpp>
+
+using namespace mbgl;
+
+void Painter::renderFill(FillBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) {
+ // Abort early.
+ if (!bucket.hasData()) return;
+
+ const FillProperties &properties = layer_desc->getProperties<FillProperties>();
+ mat4 vtxMatrix = translatedMatrix(matrix, properties.translate, id, properties.translateAnchor);
+
+ Color fill_color = properties.fill_color;
+ fill_color[0] *= properties.opacity;
+ fill_color[1] *= properties.opacity;
+ fill_color[2] *= properties.opacity;
+ fill_color[3] *= properties.opacity;
+
+ Color stroke_color = properties.stroke_color;
+ if (stroke_color[3] < 0) {
+ stroke_color = fill_color;
+ } else {
+ stroke_color[0] *= properties.opacity;
+ stroke_color[1] *= properties.opacity;
+ stroke_color[2] *= properties.opacity;
+ stroke_color[3] *= properties.opacity;
+ }
+
+ const bool pattern = properties.image.size();
+
+ bool outline = properties.antialias && !pattern && properties.stroke_color != properties.fill_color;
+ bool fringeline = properties.antialias && !pattern && properties.stroke_color == properties.fill_color;
+
+ // Because we're drawing top-to-bottom, and we update the stencil mask
+ // below, we have to draw the outline first (!)
+ if (outline && pass == RenderPass::Translucent) {
+ useProgram(outlineShader->program);
+ outlineShader->u_matrix = vtxMatrix;
+ lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
+
+ outlineShader->u_color = stroke_color;
+
+ // Draw the entire line
+ outlineShader->u_world = {{
+ static_cast<float>(state.getFramebufferWidth()),
+ static_cast<float>(state.getFramebufferHeight())
+ }};
+ depthRange(strata, 1.0f);
+ bucket.drawVertices(*outlineShader);
+ }
+
+ if (pattern) {
+ // Image fill.
+ if (pass == RenderPass::Translucent) {
+ const SpriteAtlasPosition pos = spriteAtlas.getPosition(properties.image, true);
+ const float mix = std::fmod(float(state.getZoom()), 1.0f);
+ const float factor = 8.0 / std::pow(2, state.getIntegerZoom() - id.z);
+
+ mat3 patternMatrix;
+ matrix::identity(patternMatrix);
+ matrix::scale(patternMatrix, patternMatrix, 1.0f / (pos.size[0] * factor), 1.0f / (pos.size[1] * factor));
+
+ useProgram(patternShader->program);
+ patternShader->u_matrix = vtxMatrix;
+ patternShader->u_pattern_tl = pos.tl;
+ patternShader->u_pattern_br = pos.br;
+ patternShader->u_opacity = properties.opacity;
+ patternShader->u_image = 0;
+ patternShader->u_mix = mix;
+ patternShader->u_patternmatrix = patternMatrix;
+
+ glActiveTexture(GL_TEXTURE0);
+ spriteAtlas.bind(true);
+
+ // Draw the actual triangles into the color & stencil buffer.
+ depthRange(strata, 1.0f);
+ bucket.drawElements(*patternShader);
+ }
+ }
+ else {
+ // No image fill.
+ if ((fill_color[3] >= 1.0f) == (pass == RenderPass::Opaque)) {
+ // Only draw the fill when it's either opaque and we're drawing opaque
+ // fragments or when it's translucent and we're drawing translucent
+ // fragments
+ // Draw filling rectangle.
+ useProgram(plainShader->program);
+ plainShader->u_matrix = vtxMatrix;
+ plainShader->u_color = fill_color;
+
+ // Draw the actual triangles into the color & stencil buffer.
+ depthRange(strata + strata_epsilon, 1.0f);
+ bucket.drawElements(*plainShader);
+ }
+ }
+
+ // Because we're drawing top-to-bottom, and we update the stencil mask
+ // below, we have to draw the outline first (!)
+ if (fringeline && pass == RenderPass::Translucent) {
+ useProgram(outlineShader->program);
+ outlineShader->u_matrix = vtxMatrix;
+ lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
+
+ outlineShader->u_color = fill_color;
+
+ // Draw the entire line
+ outlineShader->u_world = {{
+ static_cast<float>(state.getFramebufferWidth()),
+ static_cast<float>(state.getFramebufferHeight())
+ }};
+
+ depthRange(strata + strata_epsilon, 1.0f);
+ bucket.drawVertices(*outlineShader);
+ }
+}
diff --git a/src/mbgl/renderer/painter_line.cpp b/src/mbgl/renderer/painter_line.cpp
new file mode 100644
index 0000000000..4bf50569ac
--- /dev/null
+++ b/src/mbgl/renderer/painter_line.cpp
@@ -0,0 +1,101 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/renderer/line_bucket.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/map/sprite.hpp>
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/map/map.hpp>
+
+using namespace mbgl;
+
+void Painter::renderLine(LineBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) {
+ // Abort early.
+ if (pass == RenderPass::Opaque) return;
+ if (!bucket.hasData()) return;
+
+ const LineProperties &properties = layer_desc->getProperties<LineProperties>();
+
+ float antialiasing = 1 / state.getPixelRatio();
+ float width = properties.width;
+ float offset = properties.gap_width == 0 ? 0 : (properties.gap_width + width) / 2;
+ float blur = properties.blur + antialiasing;
+
+ float inset = std::fmin((std::fmax(-1, offset - width / 2 - antialiasing / 2) + 1), 16.0f);
+ float outset = std::fmin(offset + width / 2 + antialiasing / 2, 16.0f);
+
+ Color color = properties.color;
+ color[0] *= properties.opacity;
+ color[1] *= properties.opacity;
+ color[2] *= properties.opacity;
+ color[3] *= properties.opacity;
+
+ float dash_length = properties.dash_array[0];
+ float dash_gap = properties.dash_array[1];
+
+ float ratio = state.getPixelRatio();
+ mat4 vtxMatrix = translatedMatrix(matrix, properties.translate, id, properties.translateAnchor);
+
+ depthRange(strata, 1.0f);
+
+ // We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway.
+ if (bucket.hasPoints() && outset > 1.0f) {
+ useProgram(linejoinShader->program);
+ linejoinShader->u_matrix = vtxMatrix;
+ linejoinShader->u_color = color;
+ linejoinShader->u_world = {{
+ state.getFramebufferWidth() * 0.5f,
+ state.getFramebufferHeight() * 0.5f
+ }};
+ linejoinShader->u_linewidth = {{
+ ((outset - 0.25f) * state.getPixelRatio()),
+ ((inset - 0.25f) * state.getPixelRatio())
+ }};
+
+ float pointSize = std::ceil(state.getPixelRatio() * outset * 2.0);
+#if defined(GL_ES_VERSION_2_0)
+ linejoinShader->u_size = pointSize;
+#else
+ glPointSize(pointSize);
+#endif
+ bucket.drawPoints(*linejoinShader);
+ }
+
+ if (properties.image.size()) {
+ SpriteAtlasPosition imagePos = spriteAtlas.getPosition(properties.image);
+
+ float factor = 8.0 / std::pow(2, state.getIntegerZoom() - id.z);
+ float fade = std::fmod(state.getZoom(), 1.0);
+
+ useProgram(linepatternShader->program);
+
+ linepatternShader->u_matrix = vtxMatrix;
+ linepatternShader->u_exmatrix = extrudeMatrix;
+ linepatternShader->u_linewidth = {{ outset, inset }};
+ linepatternShader->u_ratio = ratio;
+ linepatternShader->u_blur = blur;
+
+ linepatternShader->u_pattern_size = {{imagePos.size[0] * factor, imagePos.size[1]}};
+ linepatternShader->u_pattern_tl = imagePos.tl;
+ linepatternShader->u_pattern_br = imagePos.br;
+ linepatternShader->u_fade = fade;
+
+ spriteAtlas.bind(true);
+ glDepthRange(strata + strata_epsilon, 1.0f); // may or may not matter
+
+ bucket.drawLinePatterns(*linepatternShader);
+
+ } else {
+ useProgram(lineShader->program);
+
+ lineShader->u_matrix = vtxMatrix;
+ lineShader->u_exmatrix = extrudeMatrix;
+ lineShader->u_linewidth = {{ outset, inset }};
+ lineShader->u_ratio = ratio;
+ lineShader->u_blur = blur;
+
+ lineShader->u_color = color;
+ lineShader->u_dasharray = {{ dash_length, dash_gap }};
+
+ bucket.drawLines(*lineShader);
+ }
+}
diff --git a/src/mbgl/renderer/painter_prerender.cpp b/src/mbgl/renderer/painter_prerender.cpp
new file mode 100644
index 0000000000..22a2be9b64
--- /dev/null
+++ b/src/mbgl/renderer/painter_prerender.cpp
@@ -0,0 +1,45 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/style/style_properties.hpp>
+#include <mbgl/renderer/prerendered_texture.hpp>
+#include <mbgl/renderer/raster_bucket.hpp>
+
+using namespace mbgl;
+
+void Painter::preparePrerender(RasterBucket &bucket) {
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_STENCIL_TEST);
+
+// Render the actual tile.
+#if GL_EXT_discard_framebuffer && !__ANDROID__
+ const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
+ glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
+#endif
+ glClearColor(0.0, 0.0, 0.0, 0.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, bucket.properties.size, bucket.properties.size);
+}
+
+void Painter::renderPrerenderedTexture(RasterBucket &bucket, const mat4 &matrix, const RasterProperties& properties) {
+ const int buffer = bucket.properties.buffer * 4096.0f;
+
+ // draw the texture on a quad
+ useProgram(rasterShader->program);
+ rasterShader->u_matrix = matrix;
+ rasterShader->u_opacity = 1;
+
+ depthRange(strata, 1.0f);
+
+ glActiveTexture(GL_TEXTURE0);
+ rasterShader->u_image = 0;
+ rasterShader->u_buffer = buffer;
+ rasterShader->u_opacity = properties.opacity;
+ rasterShader->u_brightness_low = properties.brightness[0];
+ rasterShader->u_brightness_high = properties.brightness[1];
+ rasterShader->u_saturation_factor = saturationFactor(properties.saturation);
+ rasterShader->u_contrast_factor = contrastFactor(properties.contrast);
+ rasterShader->u_spin_weights = spinWeights(properties.hue_rotate);
+ bucket.texture.bindTexture();
+ coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0));
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
+}
diff --git a/src/mbgl/renderer/painter_raster.cpp b/src/mbgl/renderer/painter_raster.cpp
new file mode 100644
index 0000000000..df655cdae8
--- /dev/null
+++ b/src/mbgl/renderer/painter_raster.cpp
@@ -0,0 +1,107 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/renderer/raster_bucket.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/transform.hpp>
+
+using namespace mbgl;
+
+void Painter::renderRaster(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) {
+ if (pass != RenderPass::Translucent) return;
+
+ const RasterProperties &properties = layer_desc->getProperties<RasterProperties>();
+
+ if (layer_desc->layers) {
+
+ if (!bucket.texture.getTexture()) {
+
+ bucket.texture.bindFramebuffer();
+
+ preparePrerender(bucket);
+
+ const int buffer = bucket.properties.buffer * 4096.0f;
+
+ const mat4 preMatrix = [&]{
+ mat4 vtxMatrix;
+ matrix::ortho(vtxMatrix, -buffer, 4096 + buffer, -4096 - buffer, buffer, 0, 1);
+ matrix::translate(vtxMatrix, vtxMatrix, 0, -4096, 0);
+ return vtxMatrix;
+ }();
+
+ for (const util::ptr<StyleLayer> &layer : layer_desc->layers->layers) {
+ setOpaque();
+ renderLayer(layer, &id, &preMatrix);
+ setTranslucent();
+ renderLayer(layer, &id, &preMatrix);
+ }
+
+ if (bucket.properties.blur > 0) {
+ bucket.texture.blur(*this, bucket.properties.blur);
+ }
+
+ bucket.texture.unbindFramebuffer();
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_STENCIL_TEST);
+
+ glViewport(0, 0, gl_viewport[0], gl_viewport[1]);
+
+ }
+
+ renderPrerenderedTexture(bucket, matrix, properties);
+
+ }
+
+ // Only draw non-prerendered raster here
+ if (bucket.hasData()) {
+ depthMask(false);
+
+ useProgram(rasterShader->program);
+ rasterShader->u_matrix = matrix;
+ rasterShader->u_buffer = 0;
+ rasterShader->u_opacity = properties.opacity;
+ rasterShader->u_brightness_low = properties.brightness[0];
+ rasterShader->u_brightness_high = properties.brightness[1];
+ rasterShader->u_saturation_factor = saturationFactor(properties.saturation);
+ rasterShader->u_contrast_factor = contrastFactor(properties.contrast);
+ rasterShader->u_spin_weights = spinWeights(properties.hue_rotate);
+
+ depthRange(strata + strata_epsilon, 1.0f);
+
+ bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray);
+
+ depthMask(true);
+ }
+
+}
+
+float Painter::saturationFactor(float saturation) {
+ if (saturation > 0) {
+ return 1 - 1 / (1.001 - saturation);
+ } else {
+ return -saturation;
+ }
+}
+
+float Painter::contrastFactor(float contrast) {
+ if (contrast > 0) {
+ return 1 / (1 - contrast);
+ } else {
+ return 1 + contrast;
+ }
+}
+
+std::array<float, 3> Painter::spinWeights(float spin) {
+ spin *= M_PI / 180;
+ float s = std::sin(spin);
+ float c = std::cos(spin);
+ std::array<float, 3> spin_weights = {{
+ (2 * c + 1) / 3,
+ (-std::sqrt(3.0f) * s - c + 1) / 3,
+ (std::sqrt(3.0f) * s - c + 1) / 3
+ }};
+ return spin_weights;
+}
diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp
new file mode 100644
index 0000000000..79625f1681
--- /dev/null
+++ b/src/mbgl/renderer/painter_symbol.cpp
@@ -0,0 +1,208 @@
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/renderer/symbol_bucket.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/util/math.hpp>
+#include <cmath>
+
+using namespace mbgl;
+
+template <typename BucketProperties, typename StyleProperties>
+void Painter::renderSDF(SymbolBucket &bucket,
+ const Tile::ID &id,
+ const mat4 &matrix,
+ const BucketProperties& bucketProperties,
+ const StyleProperties& styleProperties,
+ float sdfFontSize,
+ std::array<float, 2> texsize,
+ SDFShader& sdfShader,
+ void (SymbolBucket::*drawSDF)(SDFShader&))
+{
+ mat4 vtxMatrix = translatedMatrix(matrix, styleProperties.translate, id, styleProperties.translate_anchor);
+
+ mat4 exMatrix;
+ matrix::copy(exMatrix, projMatrix);
+
+ bool aligned_with_map = (bucketProperties.rotation_alignment == RotationAlignmentType::Map);
+ const float angleOffset = aligned_with_map ? state.getAngle() : 0;
+
+ if (angleOffset) {
+ matrix::rotate_z(exMatrix, exMatrix, angleOffset);
+ }
+
+ // If layerStyle.size > bucket.info.fontSize then labels may collide
+ float fontSize = std::fmin(styleProperties.size, bucketProperties.max_size);
+ float fontScale = fontSize / sdfFontSize;
+ matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f);
+
+ useProgram(sdfShader.program);
+ sdfShader.u_matrix = vtxMatrix;
+ sdfShader.u_exmatrix = exMatrix;
+ sdfShader.u_texsize = texsize;
+
+ // Convert the -pi..pi to an int8 range.
+ float angle = std::round(state.getAngle() / M_PI * 128);
+
+ // adjust min/max zooms for variable font sies
+ float zoomAdjust = std::log(fontSize / bucketProperties.max_size) / std::log(2);
+
+ sdfShader.u_flip = (aligned_with_map && bucketProperties.keep_upright) ? 1 : 0;
+ sdfShader.u_angle = (int32_t)(angle + 256) % 256;
+ sdfShader.u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level
+
+ FadeProperties f = frameHistory.getFadeProperties(300_milliseconds);
+ sdfShader.u_fadedist = f.fadedist * 10;
+ sdfShader.u_minfadezoom = std::floor(f.minfadezoom * 10);
+ sdfShader.u_maxfadezoom = std::floor(f.maxfadezoom * 10);
+ sdfShader.u_fadezoom = (state.getNormalizedZoom() + f.bump) * 10;
+
+ // The default gamma value has to be adjust for the current pixelratio so that we're not
+ // drawing blurry font on retina screens.
+ const float gamma = 0.105 * sdfFontSize / fontSize / state.getPixelRatio();
+
+ const float sdfPx = 8.0f;
+ const float blurOffset = 1.19f;
+ const float haloOffset = 6.0f;
+
+ // We're drawing in the translucent pass which is bottom-to-top, so we need
+ // to draw the halo first.
+ if (styleProperties.halo_color[3] > 0.0f) {
+ sdfShader.u_gamma = styleProperties.halo_blur * blurOffset / fontScale / sdfPx + gamma;
+
+ if (styleProperties.opacity < 1.0f) {
+ Color color = styleProperties.halo_color;
+ color[0] *= styleProperties.opacity;
+ color[1] *= styleProperties.opacity;
+ color[2] *= styleProperties.opacity;
+ color[3] *= styleProperties.opacity;
+ sdfShader.u_color = color;
+ } else {
+ sdfShader.u_color = styleProperties.halo_color;
+ }
+
+ sdfShader.u_buffer = (haloOffset - styleProperties.halo_width / fontScale) / sdfPx;
+
+ depthRange(strata, 1.0f);
+ (bucket.*drawSDF)(sdfShader);
+ }
+
+ // Then, we draw the text/icon over the halo
+ if (styleProperties.color[3] > 0.0f) {
+ sdfShader.u_gamma = gamma;
+
+ if (styleProperties.opacity < 1.0f) {
+ Color color = styleProperties.color;
+ color[0] *= styleProperties.opacity;
+ color[1] *= styleProperties.opacity;
+ color[2] *= styleProperties.opacity;
+ color[3] *= styleProperties.opacity;
+ sdfShader.u_color = color;
+ } else {
+ sdfShader.u_color = styleProperties.color;
+ }
+
+ sdfShader.u_buffer = (256.0f - 64.0f) / 256.0f;
+
+ depthRange(strata + strata_epsilon, 1.0f);
+ (bucket.*drawSDF)(sdfShader);
+ }
+}
+
+void Painter::renderSymbol(SymbolBucket &bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) {
+ // Abort early.
+ if (pass == RenderPass::Opaque) {
+ return;
+ }
+
+ const SymbolProperties &properties = layer_desc->getProperties<SymbolProperties>();
+
+ glDisable(GL_STENCIL_TEST);
+
+ if (bucket.hasIconData()) {
+ bool sdf = bucket.sdfIcons;
+
+ const float angleOffset =
+ bucket.properties.icon.rotation_alignment == RotationAlignmentType::Map
+ ? state.getAngle()
+ : 0;
+
+ // If layerStyle.size > bucket.info.fontSize then labels may collide
+ const float fontSize = properties.icon.size != 0 ? properties.icon.size : bucket.properties.icon.max_size;
+ const float fontScale = fontSize / 1.0f;
+
+ spriteAtlas.bind(state.isChanging() || bucket.properties.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1 || sdf);
+
+ std::array<float, 2> texsize = {{
+ float(spriteAtlas.getWidth()),
+ float(spriteAtlas.getHeight())
+ }};
+
+ if (sdf) {
+ renderSDF(bucket,
+ id,
+ matrix,
+ bucket.properties.icon,
+ properties.icon,
+ 1.0f,
+ texsize,
+ *sdfIconShader,
+ &SymbolBucket::drawIcons);
+ } else {
+ mat4 vtxMatrix = translatedMatrix(matrix, properties.icon.translate, id, properties.icon.translate_anchor);
+
+ mat4 exMatrix;
+ matrix::copy(exMatrix, projMatrix);
+
+ if (angleOffset) {
+ matrix::rotate_z(exMatrix, exMatrix, angleOffset);
+ }
+
+ matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f);
+
+ useProgram(iconShader->program);
+ iconShader->u_matrix = vtxMatrix;
+ iconShader->u_exmatrix = exMatrix;
+ iconShader->u_texsize = texsize;
+
+ // Convert the -pi..pi to an int8 range.
+ const float angle = std::round(state.getAngle() / M_PI * 128);
+
+ // adjust min/max zooms for variable font sies
+ float zoomAdjust = std::log(fontSize / bucket.properties.icon.max_size) / std::log(2);
+
+ iconShader->u_angle = (int32_t)(angle + 256) % 256;
+
+ bool flip = (bucket.properties.icon.rotation_alignment == RotationAlignmentType::Map)
+ && bucket.properties.icon.keep_upright;
+ iconShader->u_flip = flip ? 1 : 0;
+ iconShader->u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level
+
+ iconShader->u_fadedist = 0 * 10;
+ iconShader->u_minfadezoom = state.getNormalizedZoom() * 10;
+ iconShader->u_maxfadezoom = state.getNormalizedZoom() * 10;
+ iconShader->u_fadezoom = state.getNormalizedZoom() * 10;
+ iconShader->u_opacity = properties.icon.opacity;
+
+ depthRange(strata, 1.0f);
+ bucket.drawIcons(*iconShader);
+ }
+ }
+
+ if (bucket.hasTextData()) {
+ glyphAtlas.bind();
+
+ renderSDF(bucket,
+ id,
+ matrix,
+ bucket.properties.text,
+ properties.text,
+ 24.0f,
+ {{ float(glyphAtlas.width) / 4, float(glyphAtlas.height) / 4 }},
+ *sdfGlyphShader,
+ &SymbolBucket::drawGlyphs);
+ }
+
+ glEnable(GL_STENCIL_TEST);
+}
diff --git a/src/mbgl/renderer/prerendered_texture.cpp b/src/mbgl/renderer/prerendered_texture.cpp
new file mode 100644
index 0000000000..f17fe3b82a
--- /dev/null
+++ b/src/mbgl/renderer/prerendered_texture.cpp
@@ -0,0 +1,186 @@
+#include <mbgl/renderer/prerendered_texture.hpp>
+
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/style/style_bucket.hpp>
+
+#include <mbgl/platform/log.hpp>
+
+using namespace mbgl;
+
+PrerenderedTexture::PrerenderedTexture(const StyleBucketRaster &properties_)
+ : properties(properties_) {
+}
+
+PrerenderedTexture::~PrerenderedTexture() {
+ if (texture != 0) {
+ glDeleteTextures(1, &texture);
+ texture = 0;
+ }
+
+ if (fboDepth != 0) {
+ glDeleteRenderbuffers(1, &fboDepth);
+ fboDepth = 0;
+ }
+
+ if (fboStencil != 0) {
+ glDeleteRenderbuffers(1, &fboStencil);
+ fboStencil = 0;
+ }
+
+ if (fbo != 0) {
+ glDeleteFramebuffers(1, &fbo);
+ fbo = 0;
+ }
+}
+
+
+void PrerenderedTexture::bindTexture() {
+ if (texture == 0) {
+ bindFramebuffer();
+ unbindFramebuffer();
+ }
+
+ glBindTexture(GL_TEXTURE_2D, texture);
+}
+
+void PrerenderedTexture::bindFramebuffer() {
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFbo);
+
+ if (texture == 0) {
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+#ifndef GL_ES_VERSION_2_0
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+#endif
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, properties.size, properties.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ if (fboDepth == 0) {
+ // Create depth buffer
+ glGenRenderbuffers(1, &fboDepth);
+ glBindRenderbuffer(GL_RENDERBUFFER, fboDepth);
+ if (gl::isPackedDepthStencilSupported) {
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, properties.size, properties.size);
+ } else {
+ if (gl::isDepth24Supported) {
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, properties.size, properties.size);
+ } else {
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, properties.size, properties.size);
+ }
+ }
+
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ }
+
+ if (!gl::isPackedDepthStencilSupported && (fboStencil == 0)) {
+ // Create stencil buffer
+ glGenRenderbuffers(1, &fboStencil);
+ glBindRenderbuffer(GL_RENDERBUFFER, fboStencil);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, properties.size, properties.size);
+
+ glBindRenderbuffer(GL_RENDERBUFFER, 0);
+ }
+
+ if (fbo == 0) {
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+
+ if (gl::isPackedDepthStencilSupported) {
+#ifdef GL_ES_VERSION_2_0
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
+#else
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
+#endif
+ } else {
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboStencil);
+ }
+
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "Couldn't create framebuffer: ");
+ switch (status) {
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete attachment\n"); break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete missing attachment\n"); break;
+#ifdef GL_ES_VERSION_2_0
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete dimensions\n"); break;
+#else
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete draw buffer\n"); break;
+#endif
+ case GL_FRAMEBUFFER_UNSUPPORTED: mbgl::Log::Error(mbgl::Event::OpenGL, "unsupported\n"); break;
+ default: mbgl::Log::Error(mbgl::Event::OpenGL, "other\n"); break;
+ }
+ return;
+ }
+ } else {
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ }
+}
+
+void PrerenderedTexture::unbindFramebuffer() {
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+ if (fbo != 0) {
+ glDeleteFramebuffers(1, &fbo);
+ fbo = 0;
+ }
+}
+
+void PrerenderedTexture::blur(Painter& painter, uint16_t passes) {
+ const GLuint originalTexture = texture;
+
+ // Create a secondary texture
+ GLuint secondaryTexture;
+ glGenTextures(1, &secondaryTexture);
+ glBindTexture(GL_TEXTURE_2D, secondaryTexture);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, properties.size, properties.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+
+ painter.useProgram(painter.gaussianShader->program);
+ painter.gaussianShader->u_matrix = painter.flipMatrix;
+ painter.gaussianShader->u_image = 0;
+ glActiveTexture(GL_TEXTURE0);
+
+ for (int i = 0; i < passes; i++) {
+ // Render horizontal
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, secondaryTexture, 0);
+#if GL_EXT_discard_framebuffer && !__ANDROID__
+ const GLenum discards[] = { GL_COLOR_ATTACHMENT0 };
+ glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
+#endif
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ painter.gaussianShader->u_offset = {{ 1.0f / float(properties.size), 0 }};
+ glBindTexture(GL_TEXTURE_2D, originalTexture);
+ painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0));
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index());
+
+
+
+ // Render vertical
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, originalTexture, 0);
+#if GL_EXT_discard_framebuffer && !__ANDROID__
+ glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards);
+#endif
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ painter.gaussianShader->u_offset = {{ 0, 1.0f / float(properties.size) }};
+ glBindTexture(GL_TEXTURE_2D, secondaryTexture);
+ painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0));
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index());
+ }
+
+ glDeleteTextures(1, &secondaryTexture);
+}
diff --git a/src/mbgl/renderer/prerendered_texture.hpp b/src/mbgl/renderer/prerendered_texture.hpp
new file mode 100644
index 0000000000..3ccd24038d
--- /dev/null
+++ b/src/mbgl/renderer/prerendered_texture.hpp
@@ -0,0 +1,38 @@
+#ifndef MBGL_RENDERER_PRERENDERED_TEXTURE
+#define MBGL_RENDERER_PRERENDERED_TEXTURE
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/platform/gl.hpp>
+
+namespace mbgl {
+
+class StyleBucketRaster;
+class Painter;
+
+class PrerenderedTexture : private util::noncopyable {
+public:
+ PrerenderedTexture(const StyleBucketRaster &properties);
+ ~PrerenderedTexture();
+
+ void bindTexture();
+ void bindFramebuffer();
+ void unbindFramebuffer();
+
+ inline GLuint getTexture() const { return texture; }
+
+ void blur(Painter& painter, uint16_t passes);
+
+public:
+ const StyleBucketRaster &properties;
+
+private:
+ GLint previousFbo = 0;
+ GLuint fbo = 0;
+ GLuint texture = 0;
+ GLuint fboDepth= 0;
+ GLuint fboStencil = 0;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/raster_bucket.cpp b/src/mbgl/renderer/raster_bucket.cpp
new file mode 100644
index 0000000000..85bb66970e
--- /dev/null
+++ b/src/mbgl/renderer/raster_bucket.cpp
@@ -0,0 +1,36 @@
+#include <mbgl/renderer/raster_bucket.hpp>
+#include <mbgl/renderer/painter.hpp>
+
+using namespace mbgl;
+
+RasterBucket::RasterBucket(TexturePool& texturePool, const StyleBucketRaster& properties_)
+: properties(properties_),
+ texture(properties_),
+ raster(texturePool) {
+}
+
+void RasterBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) {
+ painter.renderRaster(*this, layer_desc, id, matrix);
+}
+
+bool RasterBucket::setImage(const std::string &data) {
+ return raster.load(data);
+}
+
+void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array) {
+ raster.bind(true);
+ shader.u_image = 0;
+ array.bind(shader, vertices, BUFFER_OFFSET(0));
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index());
+}
+
+void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array, GLuint texture_) {
+ raster.bind(texture_);
+ shader.u_image = 0;
+ array.bind(shader, vertices, BUFFER_OFFSET(0));
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index());
+}
+
+bool RasterBucket::hasData() const {
+ return raster.isLoaded();
+}
diff --git a/src/mbgl/renderer/raster_bucket.hpp b/src/mbgl/renderer/raster_bucket.hpp
new file mode 100644
index 0000000000..0a7651d7cc
--- /dev/null
+++ b/src/mbgl/renderer/raster_bucket.hpp
@@ -0,0 +1,38 @@
+#ifndef MBGL_RENDERER_RASTERBUCKET
+#define MBGL_RENDERER_RASTERBUCKET
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/util/raster.hpp>
+#include <mbgl/renderer/prerendered_texture.hpp>
+#include <mbgl/style/style_bucket.hpp>
+
+
+
+namespace mbgl {
+
+class RasterShader;
+class StaticVertexBuffer;
+class VertexArrayObject;
+
+class RasterBucket : public Bucket {
+public:
+ RasterBucket(TexturePool&, const StyleBucketRaster&);
+
+ virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix);
+ virtual bool hasData() const;
+
+ bool setImage(const std::string &data);
+
+ const StyleBucketRaster &properties;
+ PrerenderedTexture texture;
+
+ void drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array);
+
+ void drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array, GLuint texture);
+
+ Raster raster;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp
new file mode 100644
index 0000000000..a005449628
--- /dev/null
+++ b/src/mbgl/renderer/symbol_bucket.cpp
@@ -0,0 +1,427 @@
+#include <mbgl/renderer/symbol_bucket.hpp>
+#include <mbgl/geometry/text_buffer.hpp>
+#include <mbgl/geometry/icon_buffer.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/geometry/geometry.hpp>
+#include <mbgl/geometry/anchor.hpp>
+#include <mbgl/geometry/resample.hpp>
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/text/glyph_store.hpp>
+#include <mbgl/text/placement.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/text/collision.hpp>
+#include <mbgl/map/sprite.hpp>
+
+#include <mbgl/util/utf.hpp>
+#include <mbgl/util/token.hpp>
+#include <mbgl/util/math.hpp>
+
+namespace mbgl {
+
+SymbolBucket::SymbolBucket(const StyleBucketSymbol &properties_, Collision &collision_)
+ : properties(properties_), collision(collision_) {}
+
+void SymbolBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc,
+ const Tile::ID &id, const mat4 &matrix) {
+ painter.renderSymbol(*this, layer_desc, id, matrix);
+}
+
+bool SymbolBucket::hasData() const { return hasTextData() || hasIconData(); }
+
+bool SymbolBucket::hasTextData() const { return !text.groups.empty(); }
+
+bool SymbolBucket::hasIconData() const { return !icon.groups.empty(); }
+
+void SymbolBucket::addGlyphsToAtlas(uint64_t tileid, const std::string stackname,
+ const std::u32string &text, const FontStack &fontStack,
+ GlyphAtlas &glyphAtlas, GlyphPositions &face) {
+ glyphAtlas.addGlyphs(tileid, text, stackname, fontStack,face);
+}
+
+std::vector<SymbolFeature> SymbolBucket::processFeatures(const VectorTileLayer &layer,
+ const FilterExpression &filter,
+ GlyphStore &glyphStore,
+ const Sprite &sprite) {
+ const bool has_text = properties.text.field.size();
+ const bool has_icon = properties.icon.image.size();
+
+ std::vector<SymbolFeature> features;
+
+ if (!has_text && !has_icon) {
+ return features;
+ }
+
+ // Determine and load glyph ranges
+ std::set<GlyphRange> ranges;
+
+ FilteredVectorTileLayer filtered_layer(layer, filter);
+ for (const pbf &feature_pbf : filtered_layer) {
+ const VectorTileFeature feature{feature_pbf, layer};
+
+ SymbolFeature ft;
+
+ if (has_text) {
+ std::string u8string = util::replaceTokens(properties.text.field, feature.properties);
+
+ if (properties.text.transform == TextTransformType::Uppercase) {
+ u8string = platform::uppercase(u8string);
+ } else if (properties.text.transform == TextTransformType::Lowercase) {
+ u8string = platform::lowercase(u8string);
+ }
+
+ ft.label = util::utf8_to_utf32::convert(u8string);
+
+ if (ft.label.size()) {
+ // Loop through all characters of this text and collect unique codepoints.
+ for (char32_t chr : ft.label) {
+ ranges.insert(getGlyphRange(chr));
+ }
+ }
+ }
+
+ if (has_icon) {
+ ft.sprite = util::replaceTokens(properties.icon.image, feature.properties);
+ }
+
+ if (ft.label.length() || ft.sprite.length()) {
+ ft.geometry = feature.geometry;
+ features.push_back(std::move(ft));
+ }
+ }
+
+ glyphStore.waitForGlyphRanges(properties.text.font, ranges);
+ sprite.waitUntilLoaded();
+
+ return features;
+}
+
+void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter,
+ const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite,
+ GlyphAtlas & glyphAtlas, GlyphStore &glyphStore) {
+
+ const std::vector<SymbolFeature> features = processFeatures(layer, filter, glyphStore, sprite);
+
+ float horizontalAlign = 0.5;
+ float verticalAlign = 0.5;
+
+ switch (properties.text.anchor) {
+ case TextAnchorType::Top:
+ case TextAnchorType::Bottom:
+ case TextAnchorType::Center:
+ break;
+ case TextAnchorType::Right:
+ case TextAnchorType::TopRight:
+ case TextAnchorType::BottomRight:
+ horizontalAlign = 1;
+ break;
+ case TextAnchorType::Left:
+ case TextAnchorType::TopLeft:
+ case TextAnchorType::BottomLeft:
+ horizontalAlign = 0;
+ break;
+ }
+
+ switch (properties.text.anchor) {
+ case TextAnchorType::Left:
+ case TextAnchorType::Right:
+ case TextAnchorType::Center:
+ break;
+ case TextAnchorType::Bottom:
+ case TextAnchorType::BottomLeft:
+ case TextAnchorType::BottomRight:
+ verticalAlign = 1;
+ break;
+ case TextAnchorType::Top:
+ case TextAnchorType::TopLeft:
+ case TextAnchorType::TopRight:
+ verticalAlign = 0;
+ break;
+ }
+
+ float justify = 0.5;
+ if (properties.text.justify == TextJustifyType::Right) justify = 1;
+ else if (properties.text.justify == TextJustifyType::Left) justify = 0;
+
+ const FontStack &fontStack = glyphStore.getFontStack(properties.text.font);
+
+ for (const SymbolFeature &feature : features) {
+ Shaping shaping;
+ Rect<uint16_t> image;
+ GlyphPositions face;
+
+ // if feature has text, shape the text
+ if (feature.label.length()) {
+ shaping = fontStack.getShaping(
+ /* string */ feature.label,
+ /* maxWidth */ properties.text.max_width,
+ /* lineHeight */ properties.text.line_height,
+ /* horizontalAlign */ horizontalAlign,
+ /* verticalAlign */ verticalAlign,
+ /* justify */ justify,
+ /* spacing */ properties.text.letter_spacing,
+ /* translate */ properties.text.offset);
+
+ // Add the glyphs we need for this label to the glyph atlas.
+ if (shaping.size()) {
+ SymbolBucket::addGlyphsToAtlas(id.to_uint64(), properties.text.font, feature.label, fontStack,
+ glyphAtlas, face);
+ }
+ }
+
+ // if feature has icon, get sprite atlas position
+ if (feature.sprite.length()) {
+ sprite.waitUntilLoaded();
+ image = spriteAtlas.getImage(feature.sprite);
+
+ if (sprite.getSpritePosition(feature.sprite).sdf) {
+ sdfIcons = true;
+ }
+ }
+
+ // if either shaping or icon position is present, add the feature
+ if (shaping.size() || image) {
+ addFeature(feature.geometry, shaping, face, image);
+ }
+ }
+}
+
+void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping,
+ const GlyphPositions &face, const Rect<uint16_t> &image) {
+ // Decode all lines.
+ std::vector<Coordinate> line;
+ Geometry::command cmd;
+
+ Coordinate coord;
+ pbf geom(geom_pbf);
+ Geometry geometry(geom);
+ int32_t x, y;
+ while ((cmd = geometry.next(x, y)) != Geometry::end) {
+ if (cmd == Geometry::move_to) {
+ if (!line.empty()) {
+ addFeature(line, shaping, face, image);
+ line.clear();
+ }
+ }
+ line.emplace_back(x, y);
+ }
+ if (line.size()) {
+ addFeature(line, shaping, face, image);
+ }
+}
+
+bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; }
+
+const PlacementRange fullRange{{2 * M_PI, 0}};
+
+void SymbolBucket::addFeature(const std::vector<Coordinate> &line, const Shaping &shaping,
+ const GlyphPositions &face, const Rect<uint16_t> &image) {
+ assert(line.size());
+
+ const float minScale = 0.5f;
+ const float glyphSize = 24.0f;
+
+ const bool horizontalText =
+ properties.text.rotation_alignment == RotationAlignmentType::Viewport;
+ const bool horizontalIcon =
+ properties.icon.rotation_alignment == RotationAlignmentType::Viewport;
+ const float fontScale = properties.text.max_size / glyphSize;
+ const float textBoxScale = collision.tilePixelRatio * fontScale;
+ const float iconBoxScale = collision.tilePixelRatio * properties.icon.max_size;
+ const bool iconWithoutText = properties.text.optional || !shaping.size();
+ const bool textWithoutIcon = properties.icon.optional || !image;
+ const bool avoidEdges = properties.avoid_edges && properties.placement != PlacementType::Line;
+
+ Anchors anchors;
+
+ if (properties.placement == PlacementType::Line) {
+ // Line labels
+ anchors = resample(line, properties.min_distance, minScale, collision.maxPlacementScale,
+ collision.tilePixelRatio);
+
+ // Sort anchors by segment so that we can start placement with the
+ // anchors that can be shown at the lowest zoom levels.
+ std::sort(anchors.begin(), anchors.end(), byScale);
+
+ } else {
+ // Point labels
+ anchors = {Anchor{float(line[0].x), float(line[0].y), 0, minScale}};
+ }
+
+ // TODO: figure out correct ascender height.
+ const vec2<float> origin = {0, -17};
+
+ for (Anchor &anchor : anchors) {
+
+ // Calculate the scales at which the text and icons can be first shown without overlap
+ Placement glyphPlacement;
+ Placement iconPlacement;
+ float glyphScale = 0;
+ float iconScale = 0;
+ const bool inside = !(anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096);
+
+ if (avoidEdges && !inside) continue;
+
+ if (shaping.size()) {
+ glyphPlacement = Placement::getGlyphs(anchor, origin, shaping, face, textBoxScale,
+ horizontalText, line, properties);
+ glyphScale =
+ properties.text.allow_overlap
+ ? glyphPlacement.minScale
+ : collision.getPlacementScale(glyphPlacement.boxes, glyphPlacement.minScale, avoidEdges);
+ if (!glyphScale && !iconWithoutText)
+ continue;
+ }
+
+ if (image) {
+ iconPlacement = Placement::getIcon(anchor, image, iconBoxScale, line, properties);
+ iconScale =
+ properties.icon.allow_overlap
+ ? iconPlacement.minScale
+ : collision.getPlacementScale(iconPlacement.boxes, iconPlacement.minScale, avoidEdges);
+ if (!iconScale && !textWithoutIcon)
+ continue;
+ }
+
+ if (!iconWithoutText && !textWithoutIcon) {
+ iconScale = glyphScale = util::max(iconScale, glyphScale);
+ } else if (!textWithoutIcon && glyphScale) {
+ glyphScale = util::max(iconScale, glyphScale);
+ } else if (!iconWithoutText && iconScale) {
+ iconScale = util::max(iconScale, glyphScale);
+ }
+
+ // Get the rotation ranges it is safe to show the glyphs
+ PlacementRange glyphRange =
+ (!glyphScale || properties.text.allow_overlap)
+ ? fullRange
+ : collision.getPlacementRange(glyphPlacement.boxes, glyphScale, horizontalText);
+ PlacementRange iconRange =
+ (!iconScale || properties.icon.allow_overlap)
+ ? fullRange
+ : collision.getPlacementRange(iconPlacement.boxes, iconScale, horizontalIcon);
+
+ const PlacementRange maxRange = {{
+ util::min(iconRange[0], glyphRange[0]), util::max(iconRange[1], glyphRange[1]),
+ }};
+
+ if (!iconWithoutText && !textWithoutIcon) {
+ iconRange = glyphRange = maxRange;
+ } else if (!textWithoutIcon) {
+ glyphRange = maxRange;
+ } else if (!iconWithoutText) {
+ iconRange = maxRange;
+ }
+
+ // Insert final placement into collision tree and add glyphs/icons to buffers
+ if (glyphScale && std::isfinite(glyphScale)) {
+ if (!properties.text.ignore_placement) {
+ collision.insert(glyphPlacement.boxes, anchor, glyphScale, glyphRange,
+ horizontalText);
+ }
+ if (inside) addSymbols(text, glyphPlacement.shapes, glyphScale, glyphRange);
+ }
+
+ if (iconScale && std::isfinite(iconScale)) {
+ if (!properties.icon.ignore_placement) {
+ collision.insert(iconPlacement.boxes, anchor, iconScale, iconRange, horizontalIcon);
+ }
+ if (inside) addSymbols(icon, iconPlacement.shapes, iconScale, iconRange);
+ }
+ }
+}
+
+template <typename Buffer>
+void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale,
+ PlacementRange placementRange) {
+ const float zoom = collision.zoom;
+
+ const float placementZoom = std::log(scale) / std::log(2) + zoom;
+
+ for (const PlacedGlyph &symbol : symbols) {
+ const auto &tl = symbol.tl;
+ const auto &tr = symbol.tr;
+ const auto &bl = symbol.bl;
+ const auto &br = symbol.br;
+ const auto &tex = symbol.tex;
+ const auto &angle = symbol.angle;
+
+ float minZoom =
+ util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)), placementZoom);
+ float maxZoom = util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f);
+ const auto &glyphAnchor = symbol.anchor;
+
+ if (maxZoom <= minZoom)
+ continue;
+
+ // 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;
+ }
+
+ const int glyph_vertex_length = 4;
+
+ if (!buffer.groups.size() ||
+ (buffer.groups.back().vertex_length + glyph_vertex_length > 65535)) {
+ // Move to a new group because the old one can't hold the geometry.
+ buffer.groups.emplace_back();
+ }
+
+ // We're generating triangle fans, so we always start with the first
+ // coordinate in this polygon.
+ auto &triangleGroup = buffer.groups.back();
+ uint32_t triangleIndex = triangleGroup.vertex_length;
+
+ // coordinates (2 triangles)
+ buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom,
+ placementRange, maxZoom, placementZoom);
+ buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle,
+ minZoom, placementRange, maxZoom, placementZoom);
+ buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle,
+ minZoom, placementRange, maxZoom, placementZoom);
+ buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h,
+ angle, minZoom, placementRange, maxZoom, placementZoom);
+
+ // add the two triangles, referencing the four coordinates we just inserted.
+ buffer.triangles.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2);
+ buffer.triangles.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3);
+
+ triangleGroup.vertex_length += glyph_vertex_length;
+ triangleGroup.elements_length += 2;
+ }
+}
+
+void SymbolBucket::drawGlyphs(SDFShader &shader) {
+ char *vertex_index = BUFFER_OFFSET(0);
+ char *elements_index = BUFFER_OFFSET(0);
+ for (TextElementGroup &group : text.groups) {
+ group.array[0].bind(shader, text.vertices, text.triangles, vertex_index);
+ glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * text.vertices.itemSize;
+ elements_index += group.elements_length * text.triangles.itemSize;
+ }
+}
+
+void SymbolBucket::drawIcons(SDFShader &shader) {
+ char *vertex_index = BUFFER_OFFSET(0);
+ char *elements_index = BUFFER_OFFSET(0);
+ for (IconElementGroup &group : icon.groups) {
+ group.array[0].bind(shader, icon.vertices, icon.triangles, vertex_index);
+ glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * icon.vertices.itemSize;
+ elements_index += group.elements_length * icon.triangles.itemSize;
+ }
+}
+
+void SymbolBucket::drawIcons(IconShader &shader) {
+ char *vertex_index = BUFFER_OFFSET(0);
+ char *elements_index = BUFFER_OFFSET(0);
+ for (IconElementGroup &group : icon.groups) {
+ group.array[1].bind(shader, icon.vertices, icon.triangles, vertex_index);
+ glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
+ vertex_index += group.vertex_length * icon.vertices.itemSize;
+ elements_index += group.elements_length * icon.triangles.itemSize;
+ }
+}
+}
diff --git a/src/mbgl/renderer/symbol_bucket.hpp b/src/mbgl/renderer/symbol_bucket.hpp
new file mode 100644
index 0000000000..dd596b1a00
--- /dev/null
+++ b/src/mbgl/renderer/symbol_bucket.hpp
@@ -0,0 +1,114 @@
+#ifndef MBGL_RENDERER_SYMBOLBUCKET
+#define MBGL_RENDERER_SYMBOLBUCKET
+
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/geometry/vao.hpp>
+#include <mbgl/geometry/elements_buffer.hpp>
+#include <mbgl/geometry/text_buffer.hpp>
+#include <mbgl/geometry/icon_buffer.hpp>
+#include <mbgl/map/vector_tile.hpp>
+#include <mbgl/text/types.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/style/style_bucket.hpp>
+
+#include <memory>
+#include <map>
+#include <vector>
+
+namespace mbgl {
+
+class Style;
+class SDFShader;
+class IconShader;
+class DotShader;
+class Collision;
+class SpriteAtlas;
+class Sprite;
+class GlyphAtlas;
+class GlyphStore;
+class FontStack;
+
+class SymbolFeature {
+public:
+ pbf geometry;
+ std::u32string label;
+ std::string sprite;
+};
+
+
+class Symbol {
+public:
+ vec2<float> tl, tr, bl, br;
+ Rect<uint16_t> tex;
+ float angle;
+ float minScale = 0.0f;
+ float maxScale = std::numeric_limits<float>::infinity();
+ CollisionAnchor anchor;
+};
+
+typedef std::vector<Symbol> Symbols;
+
+
+class SymbolBucket : public Bucket {
+ typedef ElementGroup<1> TextElementGroup;
+ typedef ElementGroup<2> IconElementGroup;
+
+public:
+ SymbolBucket(const StyleBucketSymbol &properties, Collision &collision);
+
+ virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix);
+ virtual bool hasData() const;
+ virtual bool hasTextData() const;
+ virtual bool hasIconData() const;
+
+ void addFeatures(const VectorTileLayer &layer, const FilterExpression &filter,
+ const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite,
+ GlyphAtlas &glyphAtlas, GlyphStore &glyphStore);
+
+ void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, PlacementRange placementRange,
+ float zoom);
+
+ void drawGlyphs(SDFShader& shader);
+ void drawIcons(SDFShader& shader);
+ void drawIcons(IconShader& shader);
+
+private:
+
+ std::vector<SymbolFeature> processFeatures(const VectorTileLayer &layer, const FilterExpression &filter, GlyphStore &glyphStore, const Sprite &sprite);
+
+
+ void addFeature(const pbf &geom_pbf, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image);
+ void addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image);
+
+
+ // Adds placed items to the buffer.
+ template <typename Buffer>
+ void addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, PlacementRange placementRange);
+
+ // Adds glyphs to the glyph atlas so that they have a left/top/width/height coordinates associated to them that we can use for writing to a buffer.
+ static void addGlyphsToAtlas(uint64_t tileid, const std::string stackname, const std::u32string &string,
+ const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face);
+
+public:
+ const StyleBucketSymbol &properties;
+ bool sdfIcons = false;
+
+private:
+ Collision &collision;
+
+ struct {
+ TextVertexBuffer vertices;
+ TriangleElementsBuffer triangles;
+ std::vector<TextElementGroup> groups;
+ } text;
+
+ struct {
+ IconVertexBuffer vertices;
+ TriangleElementsBuffer triangles;
+ std::vector<IconElementGroup> groups;
+ } icon;
+
+};
+}
+
+#endif