summaryrefslogtreecommitdiff
path: root/src/mbgl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mbgl')
-rw-r--r--src/mbgl/geometry/anchor.hpp25
-rw-r--r--src/mbgl/geometry/binpack.hpp100
-rw-r--r--src/mbgl/geometry/buffer.hpp118
-rw-r--r--src/mbgl/geometry/debug_font_buffer.cpp42
-rw-r--r--src/mbgl/geometry/debug_font_buffer.hpp17
-rw-r--r--src/mbgl/geometry/debug_font_data.hpp206
-rw-r--r--src/mbgl/geometry/elements_buffer.cpp21
-rw-r--r--src/mbgl/geometry/elements_buffer.hpp64
-rw-r--r--src/mbgl/geometry/fill_buffer.cpp13
-rw-r--r--src/mbgl/geometry/fill_buffer.hpp21
-rw-r--r--src/mbgl/geometry/geometry.hpp77
-rw-r--r--src/mbgl/geometry/glyph_atlas.cpp167
-rw-r--r--src/mbgl/geometry/glyph_atlas.hpp54
-rw-r--r--src/mbgl/geometry/icon_buffer.cpp35
-rw-r--r--src/mbgl/geometry/icon_buffer.hpp22
-rw-r--r--src/mbgl/geometry/line_buffer.cpp23
-rw-r--r--src/mbgl/geometry/line_buffer.hpp39
-rw-r--r--src/mbgl/geometry/resample.cpp62
-rw-r--r--src/mbgl/geometry/resample.hpp13
-rw-r--r--src/mbgl/geometry/sprite_atlas.cpp261
-rw-r--r--src/mbgl/geometry/sprite_atlas.hpp82
-rw-r--r--src/mbgl/geometry/static_vertex_buffer.cpp14
-rw-r--r--src/mbgl/geometry/static_vertex_buffer.hpp26
-rw-r--r--src/mbgl/geometry/text_buffer.cpp33
-rw-r--r--src/mbgl/geometry/text_buffer.hpp25
-rw-r--r--src/mbgl/geometry/vao.cpp55
-rw-r--r--src/mbgl/geometry/vao.hpp73
-rw-r--r--src/mbgl/map/map.cpp745
-rw-r--r--src/mbgl/map/raster_tile_data.cpp34
-rw-r--r--src/mbgl/map/raster_tile_data.hpp33
-rw-r--r--src/mbgl/map/source.cpp369
-rw-r--r--src/mbgl/map/source.hpp86
-rw-r--r--src/mbgl/map/sprite.cpp152
-rw-r--r--src/mbgl/map/sprite.hpp79
-rw-r--r--src/mbgl/map/tile.cpp147
-rw-r--r--src/mbgl/map/tile_data.cpp104
-rw-r--r--src/mbgl/map/tile_data.hpp88
-rw-r--r--src/mbgl/map/tile_parser.cpp174
-rw-r--r--src/mbgl/map/tile_parser.hpp77
-rw-r--r--src/mbgl/map/transform.cpp472
-rw-r--r--src/mbgl/map/transform_state.cpp172
-rw-r--r--src/mbgl/map/vector_tile.cpp214
-rw-r--r--src/mbgl/map/vector_tile.hpp118
-rw-r--r--src/mbgl/map/vector_tile_data.cpp78
-rw-r--r--src/mbgl/map/vector_tile_data.hpp74
-rw-r--r--src/mbgl/platform/gl.cpp101
-rw-r--r--src/mbgl/platform/log.cpp7
-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
-rw-r--r--src/mbgl/shader/dot.fragment.glsl9
-rw-r--r--src/mbgl/shader/dot.vertex.glsl9
-rw-r--r--src/mbgl/shader/dot_shader.cpp26
-rw-r--r--src/mbgl/shader/dot_shader.hpp26
-rw-r--r--src/mbgl/shader/gaussian.fragment.glsl11
-rw-r--r--src/mbgl/shader/gaussian.vertex.glsl15
-rw-r--r--src/mbgl/shader/gaussian_shader.cpp28
-rw-r--r--src/mbgl/shader/gaussian_shader.hpp25
-rw-r--r--src/mbgl/shader/icon.fragment.glsl8
-rw-r--r--src/mbgl/shader/icon.vertex.glsl73
-rw-r--r--src/mbgl/shader/icon_shader.cpp60
-rw-r--r--src/mbgl/shader/icon_shader.hpp41
-rw-r--r--src/mbgl/shader/line.fragment.glsl25
-rw-r--r--src/mbgl/shader/line.vertex.glsl45
-rw-r--r--src/mbgl/shader/line_shader.cpp34
-rw-r--r--src/mbgl/shader/line_shader.hpp32
-rw-r--r--src/mbgl/shader/linejoin.fragment.glsl14
-rw-r--r--src/mbgl/shader/linejoin.vertex.glsl13
-rw-r--r--src/mbgl/shader/linejoin_shader.cpp27
-rw-r--r--src/mbgl/shader/linejoin_shader.hpp27
-rw-r--r--src/mbgl/shader/linepattern.fragment.glsl37
-rw-r--r--src/mbgl/shader/linepattern.vertex.glsl57
-rw-r--r--src/mbgl/shader/linepattern_shader.cpp35
-rw-r--r--src/mbgl/shader/linepattern_shader.hpp33
-rw-r--r--src/mbgl/shader/outline.fragment.glsl9
-rw-r--r--src/mbgl/shader/outline.vertex.glsl10
-rw-r--r--src/mbgl/shader/outline_shader.cpp26
-rw-r--r--src/mbgl/shader/outline_shader.hpp25
-rw-r--r--src/mbgl/shader/pattern.fragment.glsl21
-rw-r--r--src/mbgl/shader/pattern.vertex.glsl11
-rw-r--r--src/mbgl/shader/pattern_shader.cpp26
-rw-r--r--src/mbgl/shader/pattern_shader.hpp29
-rw-r--r--src/mbgl/shader/plain.fragment.glsl5
-rw-r--r--src/mbgl/shader/plain.vertex.glsl7
-rw-r--r--src/mbgl/shader/plain_shader.cpp26
-rw-r--r--src/mbgl/shader/plain_shader.hpp24
-rw-r--r--src/mbgl/shader/raster.fragment.glsl36
-rw-r--r--src/mbgl/shader/raster.vertex.glsl13
-rw-r--r--src/mbgl/shader/raster_shader.cpp28
-rw-r--r--src/mbgl/shader/raster_shader.hpp31
-rw-r--r--src/mbgl/shader/sdf.fragment.glsl13
-rw-r--r--src/mbgl/shader/sdf.vertex.glsl69
-rw-r--r--src/mbgl/shader/sdf_shader.cpp91
-rw-r--r--src/mbgl/shader/sdf_shader.hpp53
-rw-r--r--src/mbgl/shader/shader.cpp130
-rw-r--r--src/mbgl/shader/shader.hpp28
-rw-r--r--src/mbgl/shader/uniform.cpp47
-rw-r--r--src/mbgl/shader/uniform.hpp53
-rw-r--r--src/mbgl/storage/base_request.cpp87
-rw-r--r--src/mbgl/storage/base_request.hpp62
-rw-r--r--src/mbgl/storage/caching_http_file_source.cpp130
-rw-r--r--src/mbgl/storage/file_request.cpp37
-rw-r--r--src/mbgl/storage/file_request.hpp27
-rw-r--r--src/mbgl/storage/file_request_baton.cpp161
-rw-r--r--src/mbgl/storage/file_request_baton.hpp36
-rw-r--r--src/mbgl/storage/http_request.cpp280
-rw-r--r--src/mbgl/storage/http_request.hpp58
-rw-r--r--src/mbgl/storage/http_request_baton.cpp12
-rw-r--r--src/mbgl/storage/request.cpp49
-rw-r--r--src/mbgl/storage/response.cpp22
-rw-r--r--src/mbgl/storage/sqlite_store.cpp228
-rw-r--r--src/mbgl/storage/sqlite_store.hpp49
-rw-r--r--src/mbgl/style/applied_class_properties.cpp52
-rw-r--r--src/mbgl/style/applied_class_properties.hpp39
-rw-r--r--src/mbgl/style/class_dictionary.cpp51
-rw-r--r--src/mbgl/style/class_dictionary.hpp37
-rw-r--r--src/mbgl/style/class_properties.cpp14
-rw-r--r--src/mbgl/style/class_properties.hpp43
-rw-r--r--src/mbgl/style/filter_expression.cpp123
-rw-r--r--src/mbgl/style/filter_expression.hpp125
-rw-r--r--src/mbgl/style/filter_expression_private.hpp118
-rw-r--r--src/mbgl/style/function_properties.cpp68
-rw-r--r--src/mbgl/style/function_properties.hpp55
-rw-r--r--src/mbgl/style/property_fallback.cpp61
-rw-r--r--src/mbgl/style/property_fallback.hpp29
-rw-r--r--src/mbgl/style/property_key.hpp70
-rw-r--r--src/mbgl/style/property_transition.hpp15
-rw-r--r--src/mbgl/style/property_value.hpp21
-rw-r--r--src/mbgl/style/style.cpp109
-rw-r--r--src/mbgl/style/style.hpp68
-rw-r--r--src/mbgl/style/style_bucket.cpp15
-rw-r--r--src/mbgl/style/style_bucket.hpp112
-rw-r--r--src/mbgl/style/style_layer.cpp284
-rw-r--r--src/mbgl/style/style_layer.hpp89
-rw-r--r--src/mbgl/style/style_layer_group.cpp34
-rw-r--r--src/mbgl/style/style_layer_group.hpp23
-rw-r--r--src/mbgl/style/style_parser.cpp845
-rw-r--r--src/mbgl/style/style_parser.hpp113
-rw-r--r--src/mbgl/style/style_properties.cpp11
-rw-r--r--src/mbgl/style/style_properties.hpp114
-rw-r--r--src/mbgl/style/style_source.cpp77
-rw-r--r--src/mbgl/style/style_source.hpp41
-rw-r--r--src/mbgl/style/types.cpp0
-rw-r--r--src/mbgl/style/types.hpp196
-rw-r--r--src/mbgl/style/value.cpp60
-rw-r--r--src/mbgl/style/value.hpp45
-rw-r--r--src/mbgl/style/value_comparison.hpp109
-rw-r--r--src/mbgl/text/collision.cpp297
-rw-r--r--src/mbgl/text/collision.hpp58
-rw-r--r--src/mbgl/text/glyph.cpp14
-rw-r--r--src/mbgl/text/glyph.hpp60
-rw-r--r--src/mbgl/text/glyph_store.cpp294
-rw-r--r--src/mbgl/text/glyph_store.hpp99
-rw-r--r--src/mbgl/text/placement.cpp312
-rw-r--r--src/mbgl/text/placement.hpp31
-rw-r--r--src/mbgl/text/rotation_range.cpp263
-rw-r--r--src/mbgl/text/rotation_range.hpp54
-rw-r--r--src/mbgl/text/types.hpp113
-rw-r--r--src/mbgl/util/box.hpp15
-rw-r--r--src/mbgl/util/clip_ids.cpp96
-rw-r--r--src/mbgl/util/clip_ids.hpp38
-rw-r--r--src/mbgl/util/compression.cpp81
-rw-r--r--src/mbgl/util/compression.hpp15
-rw-r--r--src/mbgl/util/constants.cpp27
-rw-r--r--src/mbgl/util/constants.hpp31
-rw-r--r--src/mbgl/util/error.hpp20
-rw-r--r--src/mbgl/util/interpolate.hpp27
-rw-r--r--src/mbgl/util/io.cpp34
-rw-r--r--src/mbgl/util/io.hpp15
-rw-r--r--src/mbgl/util/mapbox.cpp44
-rw-r--r--src/mbgl/util/mapbox.hpp17
-rw-r--r--src/mbgl/util/mat3.cpp95
-rw-r--r--src/mbgl/util/mat3.hpp42
-rw-r--r--src/mbgl/util/mat4.cpp198
-rw-r--r--src/mbgl/util/math.cpp25
-rw-r--r--src/mbgl/util/optional.hpp69
-rw-r--r--src/mbgl/util/parsedate.c689
-rw-r--r--src/mbgl/util/pbf.hpp184
-rw-r--r--src/mbgl/util/queue.h92
-rw-r--r--src/mbgl/util/raster.cpp106
-rw-r--r--src/mbgl/util/raster.hpp74
-rw-r--r--src/mbgl/util/rect.hpp22
-rw-r--r--src/mbgl/util/sqlite3.cpp165
-rw-r--r--src/mbgl/util/sqlite3.hpp74
-rw-r--r--src/mbgl/util/stopwatch.cpp36
-rw-r--r--src/mbgl/util/stopwatch.hpp40
-rw-r--r--src/mbgl/util/texture_pool.cpp58
-rw-r--r--src/mbgl/util/texture_pool.hpp25
-rw-r--r--src/mbgl/util/time.cpp25
-rw-r--r--src/mbgl/util/token.hpp50
-rw-r--r--src/mbgl/util/transition.cpp26
-rw-r--r--src/mbgl/util/transition.hpp77
-rw-r--r--src/mbgl/util/unitbezier.hpp121
-rw-r--r--src/mbgl/util/url.cpp51
-rw-r--r--src/mbgl/util/url.hpp15
-rw-r--r--src/mbgl/util/utf.hpp24
-rw-r--r--src/mbgl/util/uv-channel.c69
-rw-r--r--src/mbgl/util/uv-channel.h29
-rw-r--r--src/mbgl/util/uv-messenger.c86
-rw-r--r--src/mbgl/util/uv-worker.c170
-rw-r--r--src/mbgl/util/uv-worker.h41
-rw-r--r--src/mbgl/util/uv.cpp25
-rw-r--r--src/mbgl/util/uv_detail.hpp177
224 files changed, 19374 insertions, 0 deletions
diff --git a/src/mbgl/geometry/anchor.hpp b/src/mbgl/geometry/anchor.hpp
new file mode 100644
index 0000000000..d30394f0b9
--- /dev/null
+++ b/src/mbgl/geometry/anchor.hpp
@@ -0,0 +1,25 @@
+#ifndef MBGL_GEOMETRY_ANCHOR
+#define MBGL_GEOMETRY_ANCHOR
+
+#include <vector>
+
+namespace mbgl {
+
+struct Anchor {
+ float x = 0.0f;
+ float y = 0.0f;
+ float angle = 0.0f;
+ float scale = 0.0f;
+ int segment = -1;
+
+ explicit Anchor(float x_, float y_, float angle_, float scale_)
+ : x(x_), y(y_), angle(angle_), scale(scale_) {}
+ explicit Anchor(float x_, float y_, float angle_, float scale_, int segment_)
+ : x(x_), y(y_), angle(angle_), scale(scale_), segment(segment_) {}
+};
+
+typedef std::vector<Anchor> Anchors;
+
+}
+
+#endif \ No newline at end of file
diff --git a/src/mbgl/geometry/binpack.hpp b/src/mbgl/geometry/binpack.hpp
new file mode 100644
index 0000000000..9aadaa202c
--- /dev/null
+++ b/src/mbgl/geometry/binpack.hpp
@@ -0,0 +1,100 @@
+#ifndef MBGL_GEOMETRY_BINPACK
+#define MBGL_GEOMETRY_BINPACK
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/rect.hpp>
+#include <cstdint>
+#include <list>
+
+namespace mbgl {
+
+template <typename T>
+class BinPack : private util::noncopyable {
+public:
+ BinPack(T width, T height)
+ : free(1, Rect<uint16_t>{ 0, 0, width, height }) {}
+public:
+ Rect<T> allocate(T width, T height) {
+ // Find the smallest free rect angle
+ auto smallest = free.end();
+ for (auto it = free.begin(); it != free.end(); ++it) {
+ const Rect<T>& ref = *it;
+ const Rect<T>& rect = *smallest;
+ if (width <= ref.w && height <= ref.h) {
+ if (smallest == free.end() || (ref.y <= rect.y && ref.x <= rect.x)) {
+ smallest = it;
+ }
+ }
+ }
+
+ if (smallest == free.end()) {
+ // There's no space left for this char.
+ return Rect<uint16_t>{ 0, 0, 0, 0 };
+ } else {
+ Rect<T> rect = *smallest;
+ free.erase(smallest);
+
+ // Shorter/Longer Axis Split Rule (SAS)
+ // http://clb.demon.fi/files/RectangleBinPack.pdf p. 15
+ // Ignore the dimension of R and just split long the shorter dimension
+ // See Also: http://www.cs.princeton.edu/~chazelle/pubs/blbinpacking.pdf
+ if (rect.w < rect.h) {
+ // split horizontally
+ // +--+---+
+ // |__|___| <-- b1
+ // +------+ <-- b2
+ if (rect.w > width) free.emplace_back(rect.x + width, rect.y, rect.w - width, height);
+ if (rect.h > height) free.emplace_back(rect.x, rect.y + height, rect.w, rect.h - height);
+ } else {
+ // split vertically
+ // +--+---+
+ // |__| | <-- b1
+ // +--|---+ <-- b2
+ if (rect.w > width) free.emplace_back(rect.x + width, rect.y, rect.w - width, rect.h);
+ if (rect.h > height) free.emplace_back(rect.x, rect.y + height, width, rect.h - height);
+ }
+
+ return Rect<uint16_t>{ rect.x, rect.y, width, height };
+ }
+ }
+
+
+ void release(Rect<T> rect) {
+ // Simple algorithm to recursively merge the newly released cell with its
+ // neighbor. This doesn't merge more than two cells at a time, and fails
+ // for complicated merges.
+ for (auto it = free.begin(); it != free.end(); ++it) {
+ Rect<T> ref = *it;
+ if (ref.y == rect.y && ref.h == rect.h && ref.x + ref.w == rect.x) {
+ ref.w += rect.w;
+ }
+ else if (ref.x == rect.x && ref.w == rect.w && ref.y + ref.h == rect.y) {
+ ref.h += rect.h;
+ }
+ else if (rect.y == ref.y && rect.h == ref.h && rect.x + rect.w == ref.x) {
+ ref.x = rect.x;
+ ref.w += rect.w;
+ }
+ else if (rect.x == ref.x && rect.w == ref.w && rect.y + rect.h == ref.y) {
+ ref.y = rect.y;
+ ref.h += rect.h;
+ } else {
+ continue;
+ }
+
+ free.erase(it);
+ release(ref);
+ return;
+
+ }
+
+ free.emplace_back(rect);
+ };
+
+private:
+ std::list<Rect<T>> free;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/buffer.hpp b/src/mbgl/geometry/buffer.hpp
new file mode 100644
index 0000000000..80cc6b9d1a
--- /dev/null
+++ b/src/mbgl/geometry/buffer.hpp
@@ -0,0 +1,118 @@
+#ifndef MBGL_GEOMETRY_BUFFER
+#define MBGL_GEOMETRY_BUFFER
+
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <cstdlib>
+#include <cassert>
+#include <stdexcept>
+
+namespace mbgl {
+
+template <
+ size_t item_size,
+ int bufferType = GL_ARRAY_BUFFER,
+ size_t defaultLength = 8192,
+ bool retainAfterUpload = false
+>
+class Buffer : private util::noncopyable {
+public:
+ ~Buffer() {
+ cleanup();
+ if (buffer != 0) {
+ glDeleteBuffers(1, &buffer);
+ buffer = 0;
+ }
+ }
+
+ // Returns the number of elements in this buffer. This is not the number of
+ // bytes, but rather the number of coordinates with associated information.
+ inline size_t index() const {
+ return pos / itemSize;
+ }
+
+ inline bool empty() const {
+ return pos == 0;
+ }
+
+ // Transfers this buffer to the GPU and binds the buffer to the GL context.
+ void bind(bool force = false) {
+ if (buffer == 0) {
+ glGenBuffers(1, &buffer);
+ force = true;
+ }
+ glBindBuffer(bufferType, buffer);
+ if (force) {
+ if (array == nullptr) {
+ throw std::runtime_error("Buffer was already deleted or doesn't contain elements");
+ }
+
+ glBufferData(bufferType, pos, array, GL_STATIC_DRAW);
+ if (!retainAfterUpload) {
+ cleanup();
+ }
+ }
+ }
+
+ void cleanup() {
+ if (array) {
+ free(array);
+ array = nullptr;
+ }
+ }
+
+ inline GLuint getID() const {
+ return buffer;
+ }
+
+protected:
+ // increase the buffer size by at least /required/ bytes.
+ inline void *addElement() {
+ if (buffer != 0) {
+ throw std::runtime_error("Can't add elements after buffer was bound to GPU");
+ }
+ if (length < pos + itemSize) {
+ while (length < pos + itemSize) length += defaultLength;
+ array = realloc(array, length);
+ if (array == nullptr) {
+ throw std::runtime_error("Buffer reallocation failed");
+ }
+ }
+ pos += itemSize;
+ return static_cast<char *>(array) + (pos - itemSize);
+ }
+
+ // Get a pointer to the item at a given index.
+ inline void *getElement(size_t i) {
+ if (array == nullptr) {
+ throw std::runtime_error("Buffer was already deleted or doesn't contain elements");
+ }
+
+ if (i * itemSize >= pos) {
+ throw new std::runtime_error("Can't get element after array bounds");
+ } else {
+ return static_cast<char *>(array) + (i * itemSize);
+ }
+ }
+
+public:
+ static const size_t itemSize = item_size;
+
+private:
+ // CPU buffer
+ void *array = nullptr;
+
+ // Byte position where we are writing.
+ size_t pos = 0;
+
+ // Number of bytes that are valid in this buffer.
+ size_t length = 0;
+
+ // GL buffer ID
+ GLuint buffer = 0;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/debug_font_buffer.cpp b/src/mbgl/geometry/debug_font_buffer.cpp
new file mode 100644
index 0000000000..1ec71463e5
--- /dev/null
+++ b/src/mbgl/geometry/debug_font_buffer.cpp
@@ -0,0 +1,42 @@
+#include <mbgl/geometry/debug_font_buffer.hpp>
+#include <mbgl/geometry/debug_font_data.hpp>
+
+#include <mbgl/platform/gl.hpp>
+#include <cmath>
+#include <cstring>
+
+using namespace mbgl;
+
+void DebugFontBuffer::addText(const char *text, double left, double baseline, double scale) {
+ uint16_t *coords = nullptr;
+
+ const size_t len = strlen(text);
+ for (size_t i = 0; i < len; ++i) {
+ if (text[i] < 32 || (unsigned char)(text[i]) >= 127) {
+ continue;
+ }
+
+ const glyph& glyph = simplex[text[i] - 32];
+
+ int16_t prev_x = -1, prev_y = -1, prev = false;
+ for (int32_t j = 0; j < glyph.length; j += 2) {
+ if (glyph.data[j] == -1 && glyph.data[j + 1] == -1) {
+ prev = false;
+ } else {
+ int16_t x = std::round(left + glyph.data[j] * scale);
+ int16_t y = std::round(baseline - glyph.data[j + 1] * scale);
+ if (prev) {
+ coords = static_cast<uint16_t *>(addElement());
+ coords[0] = prev_x;
+ coords[1] = prev_y;
+
+ coords = static_cast<uint16_t *>(addElement());
+ coords[0] = x;
+ coords[1] = y;
+ }
+ prev_x = x; prev_y = y; prev = true;
+ }
+ }
+ left += glyph.width * scale;
+ }
+}
diff --git a/src/mbgl/geometry/debug_font_buffer.hpp b/src/mbgl/geometry/debug_font_buffer.hpp
new file mode 100644
index 0000000000..802b5dbaac
--- /dev/null
+++ b/src/mbgl/geometry/debug_font_buffer.hpp
@@ -0,0 +1,17 @@
+#ifndef MBGL_GEOMETRY_DEBUG_FONT_BUFFER
+#define MBGL_GEOMETRY_DEBUG_FONT_BUFFER
+
+#include <mbgl/geometry/buffer.hpp>
+
+namespace mbgl {
+
+class DebugFontBuffer : public Buffer<
+ 4 // 2 bytes per coordinate, 2 coordinates
+> {
+public:
+ void addText(const char *text, double left, double baseline, double scale = 1);
+};
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/debug_font_data.hpp b/src/mbgl/geometry/debug_font_data.hpp
new file mode 100644
index 0000000000..26c54cb480
--- /dev/null
+++ b/src/mbgl/geometry/debug_font_data.hpp
@@ -0,0 +1,206 @@
+// This is an implementation file, so omit include guards.
+
+#include <cstdint>
+#include <map>
+
+const int8_t simplex_1[] = { 5, 21, 5, 7, -1, -1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2 };
+const int8_t simplex_2[] = { 4, 21, 4, 14, -1, -1, 12, 21, 12, 14 };
+const int8_t simplex_3[] = { 11, 25, 4, -7, -1, -1, 17, 25, 10, -7, -1, -1, 4, 12, 18, 12, -1, -1, 3, 6, 17, 6 };
+const int8_t simplex_4[] = { 8, 25, 8, -4, -1, -1, 12, 25, 12, -4, -1, -1, 17, 18, 15, 20, 12, 21, 8, 21, 5, 20, 3, 18, 3, 16, 4, 14, 5, 13, 7, 12, 13, 10, 15, 9, 16, 8, 17, 6, 17, 3, 15, 1, 12, 0, 8, 0, 5, 1, 3, 3 };
+const int8_t simplex_5[] = { 21, 21, 3, 0, -1, -1, 8, 21, 10, 19, 10, 17, 9, 15, 7, 14, 5, 14, 3, 16, 3, 18, 4, 20, 6, 21, 8, 21, 10, 20, 13, 19, 16, 19, 19, 20, 21, 21, -1, -1, 17, 7, 15, 6, 14, 4, 14, 2, 16, 0, 18, 0, 20, 1, 21, 3, 21, 5, 19, 7, 17, 7 };
+const int8_t simplex_6[] = { 23, 12, 23, 13, 22, 14, 21, 14, 20, 13, 19, 11, 17, 6, 15, 3, 13, 1, 11, 0, 7, 0, 5, 1, 4, 2, 3, 4, 3, 6, 4, 8, 5, 9, 12, 13, 13, 14, 14, 16, 14, 18, 13, 20, 11, 21, 9, 20, 8, 18, 8, 16, 9, 13, 11, 10, 16, 3, 18, 1, 20, 0, 22, 0, 23, 1, 23, 2 };
+const int8_t simplex_7[] = { 5, 19, 4, 20, 5, 21, 6, 20, 6, 18, 5, 16, 4, 15 };
+const int8_t simplex_8[] = { 11, 25, 9, 23, 7, 20, 5, 16, 4, 11, 4, 7, 5, 2, 7, -2, 9, -5, 11, -7 };
+const int8_t simplex_9[] = { 3, 25, 5, 23, 7, 20, 9, 16, 10, 11, 10, 7, 9, 2, 7, -2, 5, -5, 3, -7 };
+const int8_t simplex_10[] = { 8, 21, 8, 9, -1, -1, 3, 18, 13, 12, -1, -1, 13, 18, 3, 12 };
+const int8_t simplex_11[] = { 13, 18, 13, 0, -1, -1, 4, 9, 22, 9 };
+const int8_t simplex_12[] = { 6, 1, 5, 0, 4, 1, 5, 2, 6, 1, 6, -1, 5, -3, 4, -4 };
+const int8_t simplex_13[] = { 4, 9, 22, 9 };
+const int8_t simplex_14[] = { 5, 2, 4, 1, 5, 0, 6, 1, 5, 2 };
+const int8_t simplex_15[] = { 20, 25, 2, -7 };
+const int8_t simplex_16[] = { 9, 21, 6, 20, 4, 17, 3, 12, 3, 9, 4, 4, 6, 1, 9, 0, 11, 0, 14, 1, 16, 4, 17, 9, 17, 12, 16, 17, 14, 20, 11, 21, 9, 21 };
+const int8_t simplex_17[] = { 6, 17, 8, 18, 11, 21, 11, 0 };
+const int8_t simplex_18[] = { 4, 16, 4, 17, 5, 19, 6, 20, 8, 21, 12, 21, 14, 20, 15, 19, 16, 17, 16, 15, 15, 13, 13, 10, 3, 0, 17, 0 };
+const int8_t simplex_19[] = { 5, 21, 16, 21, 10, 13, 13, 13, 15, 12, 16, 11, 17, 8, 17, 6, 16, 3, 14, 1, 11, 0, 8, 0, 5, 1, 4, 2, 3, 4 };
+const int8_t simplex_20[] = { 13, 21, 3, 7, 18, 7, -1, -1, 13, 21, 13, 0 };
+const int8_t simplex_21[] = { 15, 21, 5, 21, 4, 12, 5, 13, 8, 14, 11, 14, 14, 13, 16, 11, 17, 8, 17, 6, 16, 3, 14, 1, 11, 0, 8, 0, 5, 1, 4, 2, 3, 4 };
+const int8_t simplex_22[] = { 16, 18, 15, 20, 12, 21, 10, 21, 7, 20, 5, 17, 4, 12, 4, 7, 5, 3, 7, 1, 10, 0, 11, 0, 14, 1, 16, 3, 17, 6, 17, 7, 16, 10, 14, 12, 11, 13, 10, 13, 7, 12, 5, 10, 4, 7 };
+const int8_t simplex_23[] = { 17, 21, 7, 0, -1, -1, 3, 21, 17, 21 };
+const int8_t simplex_24[] = { 8, 21, 5, 20, 4, 18, 4, 16, 5, 14, 7, 13, 11, 12, 14, 11, 16, 9, 17, 7, 17, 4, 16, 2, 15, 1, 12, 0, 8, 0, 5, 1, 4, 2, 3, 4, 3, 7, 4, 9, 6, 11, 9, 12, 13, 13, 15, 14, 16, 16, 16, 18, 15, 20, 12, 21, 8, 21 };
+const int8_t simplex_25[] = { 16, 14, 15, 11, 13, 9, 10, 8, 9, 8, 6, 9, 4, 11, 3, 14, 3, 15, 4, 18, 6, 20, 9, 21, 10, 21, 13, 20, 15, 18, 16, 14, 16, 9, 15, 4, 13, 1, 10, 0, 8, 0, 5, 1, 4, 3 };
+const int8_t simplex_26[] = { 5, 14, 4, 13, 5, 12, 6, 13, 5, 14, -1, -1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2 };
+const int8_t simplex_27[] = { 5, 14, 4, 13, 5, 12, 6, 13, 5, 14, -1, -1, 6, 1, 5, 0, 4, 1, 5, 2, 6, 1, 6, -1, 5, -3, 4, -4 };
+const int8_t simplex_28[] = { 20, 18, 4, 9, 20, 0 };
+const int8_t simplex_29[] = { 4, 12, 22, 12, -1, -1, 4, 6, 22, 6 };
+const int8_t simplex_30[] = { 4, 18, 20, 9, 4, 0 };
+const int8_t simplex_31[] = { 3, 16, 3, 17, 4, 19, 5, 20, 7, 21, 11, 21, 13, 20, 14, 19, 15, 17, 15, 15, 14, 13, 13, 12, 9, 10, 9, 7, -1, -1, 9, 2, 8, 1, 9, 0, 10, 1, 9, 2 };
+const int8_t simplex_32[] = { 18, 13, 17, 15, 15, 16, 12, 16, 10, 15, 9, 14, 8, 11, 8, 8, 9, 6, 11, 5, 14, 5, 16, 6, 17, 8, -1, -1, 12, 16, 10, 14, 9, 11, 9, 8, 10, 6, 11, 5, -1, -1, 18, 16, 17, 8, 17, 6, 19, 5, 21, 5, 23, 7, 24, 10, 24, 12, 23, 15, 22, 17, 20, 19, 18, 20, 15, 21, 12, 21, 9, 20, 7, 19, 5, 17, 4, 15, 3, 12, 3, 9, 4, 6, 5, 4, 7, 2, 9, 1, 12, 0, 15, 0, 18, 1, 20, 2, 21, 3, -1, -1, 19, 16, 18, 8, 18, 6, 19, 5 };
+const int8_t simplex_33[] = { 9, 21, 1, 0, -1, -1, 9, 21, 17, 0, -1, -1, 4, 7, 14, 7 };
+const int8_t simplex_34[] = { 4, 21, 4, 0, -1, -1, 4, 21, 13, 21, 16, 20, 17, 19, 18, 17, 18, 15, 17, 13, 16, 12, 13, 11, -1, -1, 4, 11, 13, 11, 16, 10, 17, 9, 18, 7, 18, 4, 17, 2, 16, 1, 13, 0, 4, 0 };
+const int8_t simplex_35[] = { 18, 16, 17, 18, 15, 20, 13, 21, 9, 21, 7, 20, 5, 18, 4, 16, 3, 13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0, 13, 0, 15, 1, 17, 3, 18, 5 };
+const int8_t simplex_36[] = { 4, 21, 4, 0, -1, -1, 4, 21, 11, 21, 14, 20, 16, 18, 17, 16, 18, 13, 18, 8, 17, 5, 16, 3, 14, 1, 11, 0, 4, 0 };
+const int8_t simplex_37[] = { 4, 21, 4, 0, -1, -1, 4, 21, 17, 21, -1, -1, 4, 11, 12, 11, -1, -1, 4, 0, 17, 0 };
+const int8_t simplex_38[] = { 4, 21, 4, 0, -1, -1, 4, 21, 17, 21, -1, -1, 4, 11, 12, 11 };
+const int8_t simplex_39[] = { 18, 16, 17, 18, 15, 20, 13, 21, 9, 21, 7, 20, 5, 18, 4, 16, 3, 13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0, 13, 0, 15, 1, 17, 3, 18, 5, 18, 8, -1, -1, 13, 8, 18, 8 };
+const int8_t simplex_40[] = { 4, 21, 4, 0, -1, -1, 18, 21, 18, 0, -1, -1, 4, 11, 18, 11 };
+const int8_t simplex_41[] = { 4, 21, 4, 0 };
+const int8_t simplex_42[] = { 12, 21, 12, 5, 11, 2, 10, 1, 8, 0, 6, 0, 4, 1, 3, 2, 2, 5, 2, 7 };
+const int8_t simplex_43[] = { 4, 21, 4, 0, -1, -1, 18, 21, 4, 7, -1, -1, 9, 12, 18, 0 };
+const int8_t simplex_44[] = { 4, 21, 4, 0, -1, -1, 4, 0, 16, 0 };
+const int8_t simplex_45[] = { 4, 21, 4, 0, -1, -1, 4, 21, 12, 0, -1, -1, 20, 21, 12, 0, -1, -1, 20, 21, 20, 0 };
+const int8_t simplex_46[] = { 4, 21, 4, 0, -1, -1, 4, 21, 18, 0, -1, -1, 18, 21, 18, 0 };
+const int8_t simplex_47[] = { 9, 21, 7, 20, 5, 18, 4, 16, 3, 13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0, 13, 0, 15, 1, 17, 3, 18, 5, 19, 8, 19, 13, 18, 16, 17, 18, 15, 20, 13, 21, 9, 21 };
+const int8_t simplex_48[] = { 4, 21, 4, 0, -1, -1, 4, 21, 13, 21, 16, 20, 17, 19, 18, 17, 18, 14, 17, 12, 16, 11, 13, 10, 4, 10 };
+const int8_t simplex_49[] = { 9, 21, 7, 20, 5, 18, 4, 16, 3, 13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0, 13, 0, 15, 1, 17, 3, 18, 5, 19, 8, 19, 13, 18, 16, 17, 18, 15, 20, 13, 21, 9, 21, -1, -1, 12, 4, 18, -2 };
+const int8_t simplex_50[] = { 4, 21, 4, 0, -1, -1, 4, 21, 13, 21, 16, 20, 17, 19, 18, 17, 18, 15, 17, 13, 16, 12, 13, 11, 4, 11, -1, -1, 11, 11, 18, 0 };
+const int8_t simplex_51[] = { 17, 18, 15, 20, 12, 21, 8, 21, 5, 20, 3, 18, 3, 16, 4, 14, 5, 13, 7, 12, 13, 10, 15, 9, 16, 8, 17, 6, 17, 3, 15, 1, 12, 0, 8, 0, 5, 1, 3, 3 };
+const int8_t simplex_52[] = { 8, 21, 8, 0, -1, -1, 1, 21, 15, 21 };
+const int8_t simplex_53[] = { 4, 21, 4, 6, 5, 3, 7, 1, 10, 0, 12, 0, 15, 1, 17, 3, 18, 6, 18, 21 };
+const int8_t simplex_54[] = { 1, 21, 9, 0, -1, -1, 17, 21, 9, 0 };
+const int8_t simplex_55[] = { 2, 21, 7, 0, -1, -1, 12, 21, 7, 0, -1, -1, 12, 21, 17, 0, -1, -1, 22, 21, 17, 0 };
+const int8_t simplex_56[] = { 3, 21, 17, 0, -1, -1, 17, 21, 3, 0 };
+const int8_t simplex_57[] = { 1, 21, 9, 11, 9, 0, -1, -1, 17, 21, 9, 11 };
+const int8_t simplex_58[] = { 17, 21, 3, 0, -1, -1, 3, 21, 17, 21, -1, -1, 3, 0, 17, 0 };
+const int8_t simplex_59[] = { 4, 25, 4, -7, -1, -1, 5, 25, 5, -7, -1, -1, 4, 25, 11, 25, -1, -1, 4, -7, 11, -7 };
+const int8_t simplex_60[] = { 0, 21, 14, -3 };
+const int8_t simplex_61[] = { 9, 25, 9, -7, -1, -1, 10, 25, 10, -7, -1, -1, 3, 25, 10, 25, -1, -1, 3, -7, 10, -7 };
+const int8_t simplex_62[] = { 6, 15, 8, 18, 10, 15, -1, -1, 3, 12, 8, 17, 13, 12, -1, -1, 8, 17, 8, 0 };
+const int8_t simplex_63[] = { 0, -2, 16, -2 };
+const int8_t simplex_64[] = { 6, 21, 5, 20, 4, 18, 4, 16, 5, 15, 6, 16, 5, 17 };
+const int8_t simplex_65[] = { 15, 14, 15, 0, -1, -1, 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3 };
+const int8_t simplex_66[] = { 4, 21, 4, 0, -1, -1, 4, 11, 6, 13, 8, 14, 11, 14, 13, 13, 15, 11, 16, 8, 16, 6, 15, 3, 13, 1, 11, 0, 8, 0, 6, 1, 4, 3 };
+const int8_t simplex_67[] = { 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3 };
+const int8_t simplex_68[] = { 15, 21, 15, 0, -1, -1, 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3 };
+const int8_t simplex_69[] = { 3, 8, 15, 8, 15, 10, 14, 12, 13, 13, 11, 14, 8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3 };
+const int8_t simplex_70[] = { 10, 21, 8, 21, 6, 20, 5, 17, 5, 0, -1, -1, 2, 14, 9, 14 };
+const int8_t simplex_71[] = { 15, 14, 15, -2, 14, -5, 13, -6, 11, -7, 8, -7, 6, -6, -1, -1, 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3 };
+const int8_t simplex_72[] = { 4, 21, 4, 0, -1, -1, 4, 10, 7, 13, 9, 14, 12, 14, 14, 13, 15, 10, 15, 0 };
+const int8_t simplex_73[] = { 3, 21, 4, 20, 5, 21, 4, 22, 3, 21, -1, -1, 4, 14, 4, 0 };
+const int8_t simplex_74[] = { 5, 21, 6, 20, 7, 21, 6, 22, 5, 21, -1, -1, 6, 14, 6, -3, 5, -6, 3, -7, 1, -7 };
+const int8_t simplex_75[] = { 4, 21, 4, 0, -1, -1, 14, 14, 4, 4, -1, -1, 8, 8, 15, 0 };
+const int8_t simplex_76[] = { 4, 21, 4, 0 };
+const int8_t simplex_77[] = { 4, 14, 4, 0, -1, -1, 4, 10, 7, 13, 9, 14, 12, 14, 14, 13, 15, 10, 15, 0, -1, -1, 15, 10, 18, 13, 20, 14, 23, 14, 25, 13, 26, 10, 26, 0 };
+const int8_t simplex_78[] = { 4, 14, 4, 0, -1, -1, 4, 10, 7, 13, 9, 14, 12, 14, 14, 13, 15, 10, 15, 0 };
+const int8_t simplex_79[] = { 8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3, 16, 6, 16, 8, 15, 11, 13, 13, 11, 14, 8, 14 };
+const int8_t simplex_80[] = { 4, 14, 4, -7, -1, -1, 4, 11, 6, 13, 8, 14, 11, 14, 13, 13, 15, 11, 16, 8, 16, 6, 15, 3, 13, 1, 11, 0, 8, 0, 6, 1, 4, 3 };
+const int8_t simplex_81[] = { 15, 14, 15, -7, -1, -1, 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3 };
+const int8_t simplex_82[] = { 4, 14, 4, 0, -1, -1, 4, 8, 5, 11, 7, 13, 9, 14, 12, 14 };
+const int8_t simplex_83[] = { 14, 11, 13, 13, 10, 14, 7, 14, 4, 13, 3, 11, 4, 9, 6, 8, 11, 7, 13, 6, 14, 4, 14, 3, 13, 1, 10, 0, 7, 0, 4, 1, 3, 3 };
+const int8_t simplex_84[] = { 5, 21, 5, 4, 6, 1, 8, 0, 10, 0, -1, -1, 2, 14, 9, 14 };
+const int8_t simplex_85[] = { 4, 14, 4, 4, 5, 1, 7, 0, 10, 0, 12, 1, 15, 4, -1, -1, 15, 14, 15, 0 };
+const int8_t simplex_86[] = { 2, 14, 8, 0, -1, -1, 14, 14, 8, 0 };
+const int8_t simplex_87[] = { 3, 14, 7, 0, -1, -1, 11, 14, 7, 0, -1, -1, 11, 14, 15, 0, -1, -1, 19, 14, 15, 0 };
+const int8_t simplex_88[] = { 3, 14, 14, 0, -1, -1, 14, 14, 3, 0 };
+const int8_t simplex_89[] = { 2, 14, 8, 0, -1, -1, 14, 14, 8, 0, 6, -4, 4, -6, 2, -7, 1, -7 };
+const int8_t simplex_90[] = { 14, 14, 3, 0, -1, -1, 3, 14, 14, 14, -1, -1, 3, 0, 14, 0 };
+const int8_t simplex_91[] = { 9, 25, 7, 24, 6, 23, 5, 21, 5, 19, 6, 17, 7, 16, 8, 14, 8, 12, 6, 10, -1, -1, 7, 24, 6, 22, 6, 20, 7, 18, 8, 17, 9, 15, 9, 13, 8, 11, 4, 9, 8, 7, 9, 5, 9, 3, 8, 1, 7, 0, 6, -2, 6, -4, 7, -6, -1, -1, 6, 8, 8, 6, 8, 4, 7, 2, 6, 1, 5, -1, 5, -3, 6, -5, 7, -6, 9, -7 };
+const int8_t simplex_92[] = { 4, 25, 4, -7 };
+const int8_t simplex_93[] = { 5, 25, 7, 24, 8, 23, 9, 21, 9, 19, 8, 17, 7, 16, 6, 14, 6, 12, 8, 10, -1, -1, 7, 24, 8, 22, 8, 20, 7, 18, 6, 17, 5, 15, 5, 13, 6, 11, 10, 9, 6, 7, 5, 5, 5, 3, 6, 1, 7, 0, 8, -2, 8, -4, 7, -6, -1, -1, 8, 8, 6, 6, 6, 4, 7, 2, 8, 1, 9, -1, 9, -3, 8, -5, 7, -6, 5, -7 };
+const int8_t simplex_94[] = { 3, 6, 3, 8, 4, 11, 6, 12, 8, 12, 10, 11, 14, 8, 16, 7, 18, 7, 20, 8, 21, 10, -1, -1, 3, 8, 4, 10, 6, 11, 8, 11, 10, 10, 14, 7, 16, 6, 18, 6, 20, 7, 21, 10, 21, 12 };
+
+struct glyph {
+ uint8_t width;
+ uint8_t length;
+ const int8_t *data;
+};
+
+// Font data From Hershey Simplex Font
+// http://paulbourke.net/dataformats/hershey/
+
+const glyph simplex[] = {
+ /* 32 */ { 16, 0, nullptr },
+ /* 33 ! */ { 10, sizeof(simplex_1), simplex_1 },
+ /* 34 " */ { 16, sizeof(simplex_2), simplex_2 },
+ /* 35 # */ { 21, sizeof(simplex_3), simplex_3 },
+ /* 36 $ */ { 20, sizeof(simplex_4), simplex_4 },
+ /* 37 % */ { 24, sizeof(simplex_5), simplex_5 },
+ /* 38 & */ { 26, sizeof(simplex_6), simplex_6 },
+ /* 39 ' */ { 10, sizeof(simplex_7), simplex_7 },
+ /* 40 ( */ { 14, sizeof(simplex_8), simplex_8 },
+ /* 41 ) */ { 14, sizeof(simplex_9), simplex_9 },
+ /* 42 * */ { 16, sizeof(simplex_10), simplex_10 },
+ /* 43 + */ { 26, sizeof(simplex_11), simplex_11 },
+ /* 44 , */ { 10, sizeof(simplex_12), simplex_12 },
+ /* 45 - */ { 26, sizeof(simplex_13), simplex_13 },
+ /* 46 . */ { 10, sizeof(simplex_14), simplex_14 },
+ /* 47 / */ { 22, sizeof(simplex_15), simplex_15 },
+ /* 48 0 */ { 20, sizeof(simplex_16), simplex_16 },
+ /* 49 1 */ { 20, sizeof(simplex_17), simplex_17 },
+ /* 50 2 */ { 20, sizeof(simplex_18), simplex_18 },
+ /* 51 3 */ { 20, sizeof(simplex_19), simplex_19 },
+ /* 52 4 */ { 20, sizeof(simplex_20), simplex_20 },
+ /* 53 5 */ { 20, sizeof(simplex_21), simplex_21 },
+ /* 54 6 */ { 20, sizeof(simplex_22), simplex_22 },
+ /* 55 7 */ { 20, sizeof(simplex_23), simplex_23 },
+ /* 56 8 */ { 20, sizeof(simplex_24), simplex_24 },
+ /* 57 9 */ { 20, sizeof(simplex_25), simplex_25 },
+ /* 58 : */ { 10, sizeof(simplex_26), simplex_26 },
+ /* 59 ; */ { 10, sizeof(simplex_27), simplex_27 },
+ /* 60 < */ { 24, sizeof(simplex_28), simplex_28 },
+ /* 61 = */ { 26, sizeof(simplex_29), simplex_29 },
+ /* 62 > */ { 24, sizeof(simplex_30), simplex_30 },
+ /* 63 ? */ { 18, sizeof(simplex_31), simplex_31 },
+ /* 64 @ */ { 27, sizeof(simplex_32), simplex_32 },
+ /* 65 A */ { 18, sizeof(simplex_33), simplex_33 },
+ /* 66 B */ { 21, sizeof(simplex_34), simplex_34 },
+ /* 67 C */ { 21, sizeof(simplex_35), simplex_35 },
+ /* 68 D */ { 21, sizeof(simplex_36), simplex_36 },
+ /* 69 E */ { 19, sizeof(simplex_37), simplex_37 },
+ /* 70 F */ { 18, sizeof(simplex_38), simplex_38 },
+ /* 71 G */ { 21, sizeof(simplex_39), simplex_39 },
+ /* 72 H */ { 22, sizeof(simplex_40), simplex_40 },
+ /* 73 I */ { 8, sizeof(simplex_41), simplex_41 },
+ /* 74 J */ { 16, sizeof(simplex_42), simplex_42 },
+ /* 75 K */ { 21, sizeof(simplex_43), simplex_43 },
+ /* 76 L */ { 17, sizeof(simplex_44), simplex_44 },
+ /* 77 M */ { 24, sizeof(simplex_45), simplex_45 },
+ /* 78 N */ { 22, sizeof(simplex_46), simplex_46 },
+ /* 79 O */ { 22, sizeof(simplex_47), simplex_47 },
+ /* 80 P */ { 21, sizeof(simplex_48), simplex_48 },
+ /* 81 Q */ { 22, sizeof(simplex_49), simplex_49 },
+ /* 82 R */ { 21, sizeof(simplex_50), simplex_50 },
+ /* 83 S */ { 20, sizeof(simplex_51), simplex_51 },
+ /* 84 T */ { 16, sizeof(simplex_52), simplex_52 },
+ /* 85 U */ { 22, sizeof(simplex_53), simplex_53 },
+ /* 86 V */ { 18, sizeof(simplex_54), simplex_54 },
+ /* 87 W */ { 24, sizeof(simplex_55), simplex_55 },
+ /* 88 X */ { 20, sizeof(simplex_56), simplex_56 },
+ /* 89 Y */ { 18, sizeof(simplex_57), simplex_57 },
+ /* 90 Z */ { 20, sizeof(simplex_58), simplex_58 },
+ /* 91 [ */ { 14, sizeof(simplex_59), simplex_59 },
+ /* 92 \ */ { 14, sizeof(simplex_60), simplex_60 },
+ /* 93 ] */ { 14, sizeof(simplex_61), simplex_61 },
+ /* 94 ^ */ { 16, sizeof(simplex_62), simplex_62 },
+ /* 95 _ */ { 16, sizeof(simplex_63), simplex_63 },
+ /* 96 ` */ { 10, sizeof(simplex_64), simplex_64 },
+ /* 97 a */ { 19, sizeof(simplex_65), simplex_65 },
+ /* 98 b */ { 19, sizeof(simplex_66), simplex_66 },
+ /* 99 c */ { 18, sizeof(simplex_67), simplex_67 },
+ /* 100 d */ { 19, sizeof(simplex_68), simplex_68 },
+ /* 101 e */ { 18, sizeof(simplex_69), simplex_69 },
+ /* 102 f */ { 12, sizeof(simplex_70), simplex_70 },
+ /* 103 g */ { 19, sizeof(simplex_71), simplex_71 },
+ /* 104 h */ { 19, sizeof(simplex_72), simplex_72 },
+ /* 105 i */ { 8, sizeof(simplex_73), simplex_73 },
+ /* 106 j */ { 10, sizeof(simplex_74), simplex_74 },
+ /* 107 k */ { 17, sizeof(simplex_75), simplex_75 },
+ /* 108 l */ { 8, sizeof(simplex_76), simplex_76 },
+ /* 109 m */ { 30, sizeof(simplex_77), simplex_77 },
+ /* 110 n */ { 19, sizeof(simplex_78), simplex_78 },
+ /* 111 o */ { 19, sizeof(simplex_79), simplex_79 },
+ /* 112 p */ { 19, sizeof(simplex_80), simplex_80 },
+ /* 113 q */ { 19, sizeof(simplex_81), simplex_81 },
+ /* 114 r */ { 13, sizeof(simplex_82), simplex_82 },
+ /* 115 s */ { 17, sizeof(simplex_83), simplex_83 },
+ /* 116 t */ { 12, sizeof(simplex_84), simplex_84 },
+ /* 117 u */ { 19, sizeof(simplex_85), simplex_85 },
+ /* 118 v */ { 16, sizeof(simplex_86), simplex_86 },
+ /* 119 w */ { 22, sizeof(simplex_87), simplex_87 },
+ /* 120 x */ { 17, sizeof(simplex_88), simplex_88 },
+ /* 121 y */ { 16, sizeof(simplex_89), simplex_89 },
+ /* 122 z */ { 17, sizeof(simplex_90), simplex_90 },
+ /* 123 { */ { 14, sizeof(simplex_91), simplex_91 },
+ /* 124 | */ { 8, sizeof(simplex_92), simplex_92 },
+ /* 125 } */ { 14, sizeof(simplex_93), simplex_93 },
+ /* 126 ~ */ { 24, sizeof(simplex_94), simplex_94 },
+};
diff --git a/src/mbgl/geometry/elements_buffer.cpp b/src/mbgl/geometry/elements_buffer.cpp
new file mode 100644
index 0000000000..79af1b7e35
--- /dev/null
+++ b/src/mbgl/geometry/elements_buffer.cpp
@@ -0,0 +1,21 @@
+#include <mbgl/geometry/elements_buffer.hpp>
+
+using namespace mbgl;
+
+void TriangleElementsBuffer::add(element_type a, element_type b, element_type c) {
+ element_type *elements = static_cast<element_type *>(addElement());
+ elements[0] = a;
+ elements[1] = b;
+ elements[2] = c;
+}
+
+void LineElementsBuffer::add(element_type a, element_type b) {
+ element_type *elements = static_cast<element_type *>(addElement());
+ elements[0] = a;
+ elements[1] = b;
+}
+
+void PointElementsBuffer::add(element_type a) {
+ uint16_t *data = static_cast<element_type *>(addElement());
+ data[0] = a;
+}
diff --git a/src/mbgl/geometry/elements_buffer.hpp b/src/mbgl/geometry/elements_buffer.hpp
new file mode 100644
index 0000000000..9255337cb5
--- /dev/null
+++ b/src/mbgl/geometry/elements_buffer.hpp
@@ -0,0 +1,64 @@
+#ifndef MBGL_GEOMETRY_TRIANGLE_ELEMENTS_BUFFER
+#define MBGL_GEOMETRY_TRIANGLE_ELEMENTS_BUFFER
+
+#include <mbgl/geometry/buffer.hpp>
+#include <mbgl/geometry/vao.hpp>
+
+#include <mbgl/util/noncopyable.hpp>
+
+#include <array>
+
+namespace mbgl {
+
+template <int count>
+struct ElementGroup : public util::noncopyable {
+ std::array<VertexArrayObject, count> array;
+ uint32_t vertex_length;
+ uint32_t elements_length;
+
+ ElementGroup() : vertex_length(0), elements_length(0) {}
+ ElementGroup(uint32_t vertex_length_, uint32_t elements_length_)
+ : vertex_length(vertex_length_),
+ elements_length(elements_length_) {
+ }
+
+ ElementGroup(ElementGroup &&rhs) noexcept
+ : array(std::move(rhs.array)),
+ vertex_length(rhs.vertex_length),
+ elements_length(rhs.elements_length) {};
+};
+
+class TriangleElementsBuffer : public Buffer<
+ 6, // bytes per triangle (3 * unsigned short == 6 bytes)
+ GL_ELEMENT_ARRAY_BUFFER
+> {
+public:
+ typedef uint16_t element_type;
+
+ void add(element_type a, element_type b, element_type c);
+};
+
+
+class LineElementsBuffer : public Buffer<
+ 4, // bytes per triangle (2 * unsigned short == 6 bytes)
+ GL_ELEMENT_ARRAY_BUFFER
+> {
+public:
+ typedef uint16_t element_type;
+
+ void add(element_type a, element_type b);
+};
+
+class PointElementsBuffer : public Buffer<
+ 2, // bytes per point (1 unsigned short)
+ GL_ELEMENT_ARRAY_BUFFER
+> {
+public:
+ typedef uint16_t element_type;
+
+ void add(element_type a);
+};
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/fill_buffer.cpp b/src/mbgl/geometry/fill_buffer.cpp
new file mode 100644
index 0000000000..3392699431
--- /dev/null
+++ b/src/mbgl/geometry/fill_buffer.cpp
@@ -0,0 +1,13 @@
+#include <mbgl/geometry/fill_buffer.hpp>
+
+#include <mbgl/platform/gl.hpp>
+
+#include <climits>
+
+using namespace mbgl;
+
+void FillVertexBuffer::add(vertex_type x, vertex_type y) {
+ vertex_type *vertices = static_cast<vertex_type *>(addElement());
+ vertices[0] = x;
+ vertices[1] = y;
+}
diff --git a/src/mbgl/geometry/fill_buffer.hpp b/src/mbgl/geometry/fill_buffer.hpp
new file mode 100644
index 0000000000..2cd1637fa1
--- /dev/null
+++ b/src/mbgl/geometry/fill_buffer.hpp
@@ -0,0 +1,21 @@
+#ifndef MBGL_GEOMETRY_FILL_BUFFER
+#define MBGL_GEOMETRY_FILL_BUFFER
+
+#include <mbgl/geometry/buffer.hpp>
+#include <vector>
+#include <cstdint>
+
+namespace mbgl {
+
+class FillVertexBuffer : public Buffer<
+ 4 // bytes per coordinates (2 * unsigned short == 4 bytes)
+> {
+public:
+ typedef int16_t vertex_type;
+
+ void add(vertex_type x, vertex_type y);
+};
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/geometry.hpp b/src/mbgl/geometry/geometry.hpp
new file mode 100644
index 0000000000..484d17b36d
--- /dev/null
+++ b/src/mbgl/geometry/geometry.hpp
@@ -0,0 +1,77 @@
+#ifndef MBGL_GEOMETRY_GEOMETRY
+#define MBGL_GEOMETRY_GEOMETRY
+
+#include <mbgl/util/pbf.hpp>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <cstdlib>
+
+namespace mbgl {
+
+class Geometry : private util::noncopyable {
+
+public:
+ inline explicit Geometry(pbf& data);
+
+ enum command : uint8_t {
+ end = 0,
+ move_to = 1,
+ line_to = 2,
+ close = 7
+ };
+
+ inline command next(int32_t &rx, int32_t &ry);
+
+private:
+ pbf& data;
+ uint8_t cmd;
+ uint32_t length;
+ int32_t x, y;
+ int32_t ox, oy;
+};
+
+Geometry::Geometry(pbf& data_)
+ : data(data_),
+ cmd(1),
+ length(0),
+ x(0), y(0),
+ ox(0), oy(0) {}
+
+Geometry::command Geometry::next(int32_t &rx, int32_t &ry) {
+ if (data.data < data.end) {
+ if (length == 0) {
+ uint32_t cmd_length = static_cast<uint32_t>(data.varint());
+ cmd = cmd_length & 0x7;
+ length = cmd_length >> 3;
+ }
+
+ --length;
+
+ if (cmd == move_to || cmd == line_to) {
+ rx = (x += data.svarint());
+ ry = (y += data.svarint());
+
+ if (cmd == move_to) {
+ ox = x;
+ oy = y;
+ return move_to;
+ } else {
+ return line_to;
+ }
+ } else if (cmd == close) {
+ rx = ox;
+ ry = oy;
+ return close;
+ } else {
+ fprintf(stderr, "unknown command: %d\n", cmd);
+ // TODO: gracefully handle geometry parse failures
+ return end;
+ }
+ } else {
+ return end;
+ }
+}
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/glyph_atlas.cpp b/src/mbgl/geometry/glyph_atlas.cpp
new file mode 100644
index 0000000000..bafcf2b000
--- /dev/null
+++ b/src/mbgl/geometry/glyph_atlas.cpp
@@ -0,0 +1,167 @@
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/map/vector_tile.hpp>
+
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/platform/platform.hpp>
+
+#include <cassert>
+#include <algorithm>
+
+
+using namespace mbgl;
+
+GlyphAtlas::GlyphAtlas(uint16_t width_, uint16_t height_)
+ : width(width_),
+ height(height_),
+ bin(width_, height_),
+ data(new char[width_ *height_]),
+ dirty(true) {
+}
+
+Rect<uint16_t> GlyphAtlas::addGlyph(uint64_t tile_id, const std::string& face_name,
+ const SDFGlyph& glyph)
+{
+ std::lock_guard<std::mutex> lock(mtx);
+ return addGlyph_impl(tile_id, face_name, glyph);
+}
+
+Rect<uint16_t> GlyphAtlas::addGlyph_impl(uint64_t tile_id, const std::string& face_name,
+ const SDFGlyph& glyph)
+{
+ // Use constant value for now.
+ const uint8_t buffer = 3;
+
+ std::map<uint32_t, GlyphValue>& face = index[face_name];
+ std::map<uint32_t, GlyphValue>::iterator it = face.find(glyph.id);
+
+ // The glyph is already in this texture.
+ if (it != face.end()) {
+ GlyphValue& value = it->second;
+ value.ids.insert(tile_id);
+ return value.rect;
+ }
+
+ // The glyph bitmap has zero width.
+ if (!glyph.bitmap.size()) {
+ return Rect<uint16_t>{ 0, 0, 0, 0 };
+ }
+
+ uint16_t buffered_width = glyph.metrics.width + buffer * 2;
+ uint16_t buffered_height = glyph.metrics.height + buffer * 2;
+
+ // Add a 1px border around every image.
+ uint16_t pack_width = buffered_width;
+ uint16_t pack_height = buffered_height;
+
+ // Increase to next number divisible by 4, but at least 1.
+ // This is so we can scale down the texture coordinates and pack them
+ // into 2 bytes rather than 4 bytes.
+ pack_width += (4 - pack_width % 4);
+ pack_height += (4 - pack_height % 4);
+
+ Rect<uint16_t> rect = bin.allocate(pack_width, pack_height);
+ if (rect.w == 0) {
+ fprintf(stderr, "glyph bitmap overflow");
+ return rect;
+ }
+
+ assert(rect.x + rect.w <= width);
+ assert(rect.y + rect.h <= height);
+
+ face.emplace(glyph.id, GlyphValue { rect, tile_id });
+
+ // Copy the bitmap
+ char *target = data.get();
+ const char *source = glyph.bitmap.data();
+ for (uint32_t y = 0; y < buffered_height; y++) {
+ uint32_t y1 = width * (rect.y + y) + rect.x;
+ uint32_t y2 = buffered_width * y;
+ for (uint32_t x = 0; x < buffered_width; x++) {
+ target[y1 + x] = source[y2 + x];
+ }
+ }
+
+ dirty = true;
+
+ return rect;
+}
+
+void GlyphAtlas::addGlyphs(uint64_t tileid, std::u32string const& text, std::string const& stackname, FontStack const& fontStack, GlyphPositions & face)
+{
+ std::lock_guard<std::mutex> lock(mtx);
+
+ std::map<uint32_t, SDFGlyph> const& sdfs = fontStack.getSDFs();
+ for (uint32_t chr : text)
+ {
+ auto sdf_it = sdfs.find(chr);
+ if (sdf_it != sdfs.end())
+ {
+ SDFGlyph const& sdf = sdf_it->second;
+ Rect<uint16_t> rect = addGlyph_impl(tileid, stackname, sdf);
+ face.emplace(chr, Glyph{rect, sdf.metrics});
+ }
+ }
+}
+
+void GlyphAtlas::removeGlyphs(uint64_t tile_id) {
+ std::lock_guard<std::mutex> lock(mtx);
+
+ for (auto& faces : index) {
+ std::map<uint32_t, GlyphValue>& face = faces.second;
+ for (auto it = face.begin(); it != face.end(); /* we advance in the body */) {
+ GlyphValue& value = it->second;
+ value.ids.erase(tile_id);
+
+ if (!value.ids.size()) {
+ const Rect<uint16_t>& rect = value.rect;
+
+ // Clear out the bitmap.
+ char *target = data.get();
+ for (uint32_t y = 0; y < rect.h; y++) {
+ uint32_t y1 = width * (rect.y + y) + rect.x;
+ for (uint32_t x = 0; x < rect.w; x++) {
+ target[y1 + x] = 0;
+ }
+ }
+
+ dirty = true;
+
+ bin.release(rect);
+
+ // Make sure to post-increment the iterator: This will return the
+ // current iterator, but will go to the next position before we
+ // erase the element from the map. That way, the iterator stays
+ // valid.
+ face.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ }
+}
+
+void GlyphAtlas::bind() {
+ if (!texture) {
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+#ifndef GL_ES_VERSION_2_0
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+#endif
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ } else {
+ glBindTexture(GL_TEXTURE_2D, texture);
+ }
+
+ if (dirty) {
+ std::lock_guard<std::mutex> lock(mtx);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data.get());
+ dirty = false;
+
+#if defined(DEBUG)
+ // platform::show_debug_image("Glyph Atlas", data, width, height);
+#endif
+ }
+};
diff --git a/src/mbgl/geometry/glyph_atlas.hpp b/src/mbgl/geometry/glyph_atlas.hpp
new file mode 100644
index 0000000000..7b3c223fe5
--- /dev/null
+++ b/src/mbgl/geometry/glyph_atlas.hpp
@@ -0,0 +1,54 @@
+#ifndef MBGL_GEOMETRY_GLYPH_ATLAS
+#define MBGL_GEOMETRY_GLYPH_ATLAS
+
+#include <mbgl/geometry/binpack.hpp>
+#include <mbgl/text/glyph_store.hpp>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <string>
+#include <set>
+#include <map>
+#include <mutex>
+#include <atomic>
+
+namespace mbgl {
+
+class GlyphAtlas : public util::noncopyable {
+public:
+
+private:
+ struct GlyphValue {
+ GlyphValue(const Rect<uint16_t>& rect_, uint64_t id)
+ : rect(rect_), ids({ id }) {}
+ Rect<uint16_t> rect;
+ std::set<uint64_t> ids;
+ };
+
+ Rect<uint16_t> addGlyph_impl(uint64_t tile_id, const std::string& face_name,
+ const SDFGlyph& glyph);
+public:
+ GlyphAtlas(uint16_t width, uint16_t height);
+
+ Rect<uint16_t> addGlyph(uint64_t tile_id, const std::string& face_name,
+ const SDFGlyph& glyph);
+ void addGlyphs(uint64_t tileid, std::u32string const& text, std::string const& stackname,
+ FontStack const& fontStack, GlyphPositions & face);
+ void removeGlyphs(uint64_t tile_id);
+ void bind();
+
+public:
+ const uint16_t width = 0;
+ const uint16_t height = 0;
+
+private:
+ std::mutex mtx;
+ BinPack<uint16_t> bin;
+ std::map<std::string, std::map<uint32_t, GlyphValue>> index;
+ std::unique_ptr<char[]> data;
+ std::atomic<bool> dirty;
+ uint32_t texture = 0;
+};
+
+};
+
+#endif
diff --git a/src/mbgl/geometry/icon_buffer.cpp b/src/mbgl/geometry/icon_buffer.cpp
new file mode 100644
index 0000000000..c571dfa69e
--- /dev/null
+++ b/src/mbgl/geometry/icon_buffer.cpp
@@ -0,0 +1,35 @@
+#include <mbgl/geometry/icon_buffer.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/util/math.hpp>
+
+#include <cmath>
+
+namespace mbgl {
+
+const double IconVertexBuffer::angleFactor = 128.0 / M_PI;
+
+size_t IconVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, int16_t tx, int16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom) {
+ const size_t idx = index();
+ void *data = addElement();
+
+ int16_t *shorts = static_cast<int16_t *>(data);
+ shorts[0] /* pos */ = x;
+ shorts[1] /* pos */ = y;
+ shorts[2] /* offset */ = std::round(ox * 64); // use 1/64 pixels for placement
+ shorts[3] /* offset */ = std::round(oy * 64);
+
+ uint8_t *ubytes = static_cast<uint8_t *>(data);
+ ubytes[8] /* labelminzoom */ = labelminzoom * 10;
+ ubytes[9] /* minzoom */ = minzoom * 10; // 1/10 zoom levels: z16 == 160.
+ ubytes[10] /* maxzoom */ = std::fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160.
+ ubytes[11] /* angle */ = (int16_t)std::round(angle * angleFactor) % 256;
+ ubytes[12] /* rangeend */ = util::max((int16_t)std::round(range[0] * angleFactor), (int16_t)0) % 256;
+ ubytes[13] /* rangestart */ = util::min((int16_t)std::round(range[1] * angleFactor), (int16_t)255) % 256;
+
+ shorts[8] /* tex */ = tx;
+ shorts[9] /* tex */ = ty;
+
+ return idx;
+}
+
+}
diff --git a/src/mbgl/geometry/icon_buffer.hpp b/src/mbgl/geometry/icon_buffer.hpp
new file mode 100644
index 0000000000..08c9687004
--- /dev/null
+++ b/src/mbgl/geometry/icon_buffer.hpp
@@ -0,0 +1,22 @@
+#ifndef MBGL_GEOMETRY_ICON_BUFFER
+#define MBGL_GEOMETRY_ICON_BUFFER
+
+#include <mbgl/geometry/buffer.hpp>
+
+#include <array>
+
+namespace mbgl {
+
+ class IconVertexBuffer : public Buffer<
+ 20
+ > {
+ public:
+ static const double angleFactor;
+
+ size_t add(int16_t x, int16_t y, float ox, float oy, int16_t tx, int16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom);
+
+ };
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/line_buffer.cpp b/src/mbgl/geometry/line_buffer.cpp
new file mode 100644
index 0000000000..50a6e66b93
--- /dev/null
+++ b/src/mbgl/geometry/line_buffer.cpp
@@ -0,0 +1,23 @@
+#include <mbgl/geometry/line_buffer.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cmath>
+
+using namespace mbgl;
+
+size_t LineVertexBuffer::add(vertex_type x, vertex_type y, float ex, float ey, int8_t tx, int8_t ty, int32_t linesofar) {
+ size_t idx = index();
+ void *data = addElement();
+
+ int16_t *coords = static_cast<int16_t *>(data);
+ coords[0] = (x * 2) | tx;
+ coords[1] = (y * 2) | ty;
+
+ int8_t *extrude = static_cast<int8_t *>(data);
+ extrude[4] = std::round(extrudeScale * ex);
+ extrude[5] = std::round(extrudeScale * ey);
+
+ coords[3] = linesofar;
+
+ return idx;
+}
diff --git a/src/mbgl/geometry/line_buffer.hpp b/src/mbgl/geometry/line_buffer.hpp
new file mode 100644
index 0000000000..1c217b59d2
--- /dev/null
+++ b/src/mbgl/geometry/line_buffer.hpp
@@ -0,0 +1,39 @@
+#ifndef MBGL_GEOMETRY_LINE_BUFFER
+#define MBGL_GEOMETRY_LINE_BUFFER
+
+#include <mbgl/geometry/buffer.hpp>
+
+namespace mbgl {
+
+class LineVertexBuffer : public Buffer<
+ 8 // 2 coordinates per vertex + 1 linesofar + 1 extrude coord pair == 4 (== 8 bytes)
+> {
+public:
+ typedef int16_t vertex_type;
+
+ /*
+ * Scale the extrusion vector so that the normal length is this value.
+ * Contains the "texture" normals (-1..1). This is distinct from the extrude
+ * normals for line joins, because the x-value remains 0 for the texture
+ * normal array, while the extrude normal actually moves the vertex to create
+ * the acute/bevelled line join.
+ */
+ static const int8_t extrudeScale = 63;
+
+ /*
+ * Add a vertex to this buffer
+ *
+ * @param {number} x vertex position
+ * @param {number} y vertex position
+ * @param {number} ex extrude normal
+ * @param {number} ey extrude normal
+ * @param {number} tx texture normal
+ * @param {number} ty texture normal
+ */
+ size_t add(vertex_type x, vertex_type y, float ex, float ey, int8_t tx, int8_t ty, int32_t linesofar = 0);
+};
+
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/resample.cpp b/src/mbgl/geometry/resample.cpp
new file mode 100644
index 0000000000..abb3ef1e3c
--- /dev/null
+++ b/src/mbgl/geometry/resample.cpp
@@ -0,0 +1,62 @@
+#include <mbgl/geometry/resample.hpp>
+
+#include <mbgl/util/interpolate.hpp>
+
+#include <cmath>
+
+namespace mbgl {
+
+const float minScale = 0.5f;
+const std::array<std::vector<float>, 4> minScaleArrays = {{
+ /*1:*/ { minScale },
+ /*2:*/ { minScale, 2 },
+ /*4:*/ { minScale, 4, 2, 4 },
+ /*8:*/ { minScale, 8, 4, 8, 2, 8, 4, 8 }
+}};
+
+
+Anchors resample(const std::vector<Coordinate> &vertices, float spacing,
+ const float /*minScale*/, float maxScale, const float tilePixelRatio,
+ const int start) {
+
+ maxScale = std::round(std::fmax(std::fmin(8.0f, maxScale / 2.0f), 1.0f));
+ spacing *= tilePixelRatio / maxScale;
+ const size_t index = util::clamp<size_t>(std::floor(std::log(maxScale) / std::log(2)), 0, minScaleArrays.size() - 1);
+ const std::vector<float> &minScales = minScaleArrays[index];
+ const size_t len = minScales.size();
+
+ float distance = 0.0f;
+ float markedDistance = 0.0f;
+ int added = start;
+
+ Anchors points;
+
+ auto end = vertices.end() - 1;
+ int i = 0;
+ for (auto it = vertices.begin(); it != end; it++, i++) {
+ const Coordinate &a = *(it), b = *(it + 1);
+
+ float segmentDist = util::dist<float>(a, b);
+ float angle = util::angle_to(b, a);
+
+ while (markedDistance + spacing < distance + segmentDist) {
+ markedDistance += spacing;
+
+ float t = (markedDistance - distance) / segmentDist,
+ x = util::interpolate(a.x, b.x, t),
+ y = util::interpolate(a.y, b.y, t),
+ s = minScales[added % len];
+
+ if (x >= 0 && x < 4096 && y >= 0 && y < 4096) {
+ points.emplace_back(x, y, angle, s, i);
+ }
+
+ added++;
+ }
+
+ distance += segmentDist;
+ }
+
+ return points;
+}
+}
diff --git a/src/mbgl/geometry/resample.hpp b/src/mbgl/geometry/resample.hpp
new file mode 100644
index 0000000000..bcfe4ca53d
--- /dev/null
+++ b/src/mbgl/geometry/resample.hpp
@@ -0,0 +1,13 @@
+#ifndef MBGL_GEOMETRY_INTERPOLATE
+#define MBGL_GEOMETRY_INTERPOLATE
+
+#include <mbgl/geometry/anchor.hpp>
+#include <mbgl/util/math.hpp>
+
+namespace mbgl {
+
+Anchors resample(const std::vector<Coordinate> &vertices, float spacing,
+ float minScale, float maxScale, float tilePixelRatio, int start = 0);
+}
+
+#endif
diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp
new file mode 100644
index 0000000000..7dc8f60ae6
--- /dev/null
+++ b/src/mbgl/geometry/sprite_atlas.cpp
@@ -0,0 +1,261 @@
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/constants.hpp>
+
+#include <mbgl/map/sprite.hpp>
+
+#include <cassert>
+#include <cmath>
+#include <algorithm>
+
+
+using namespace mbgl;
+
+SpriteAtlas::SpriteAtlas(dimension width_, dimension height_)
+ : width(width_),
+ height(height_),
+ bin(width_, height_),
+ dirty(true) {
+}
+
+bool SpriteAtlas::resize(const float newRatio) {
+ if (pixelRatio == newRatio) return false;
+
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ const float oldRatio = pixelRatio;
+ pixelRatio = newRatio;
+
+ if (data) {
+ uint32_t *old_data = data;
+
+ data = nullptr;
+ allocate();
+
+ const int old_w = width * oldRatio;
+ const int old_h = height * oldRatio;
+ const int new_w = width * newRatio;
+ const int new_h = height * newRatio;
+
+ // Basic image scaling. TODO: Replace this with better image scaling.
+ uint32_t *img_new = reinterpret_cast<uint32_t *>(data);
+ const uint32_t *img_old = reinterpret_cast<const uint32_t *>(old_data);
+
+ for (int y = 0; y < new_h; y++) {
+ const int old_yoffset = ((y * old_h) / new_h) * old_w;
+ const int new_yoffset = y * new_w;
+ for (int x = 0; x < new_w; x++) {
+ const int old_x = (x * old_w) / new_w;
+ img_new[new_yoffset + x] = img_old[old_yoffset + old_x];
+ }
+ }
+
+ ::operator delete(old_data);
+ dirty = true;
+
+ // Mark all sprite images as in need of update
+ for (const auto &pair : images) {
+ uninitialized.emplace(pair.first);
+ }
+ }
+
+ return dirty;
+}
+
+void copy_bitmap(const uint32_t *src, const int src_stride, const int src_x, const int src_y,
+ uint32_t *dst, const int dst_stride, const int dst_x, const int dst_y,
+ const int width, const int height) {
+ src += src_y * src_stride + src_x;
+ dst += dst_y * dst_stride + dst_x;
+ for (int y = 0; y < height; y++, src += src_stride, dst += dst_stride) {
+ for (int x = 0; x < width; x++) {
+ dst[x] = src[x];
+ }
+ }
+}
+
+Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(size_t pixel_width, size_t pixel_height) {
+ // We have to allocate a new area in the bin, and store an empty image in it.
+ // Add a 1px border around every image.
+ Rect<dimension> rect = bin.allocate(pixel_width + 2 * buffer, pixel_height + 2 * buffer);
+ if (rect.w == 0) {
+ return rect;
+ }
+
+ rect.x += buffer;
+ rect.y += buffer;
+ rect.w -= 2 * buffer;
+ rect.h -= 2 * buffer;
+
+ return rect;
+}
+
+Rect<SpriteAtlas::dimension> SpriteAtlas::getImage(const std::string& name) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ auto rect_it = images.find(name);
+ if (rect_it != images.end()) {
+ return rect_it->second;
+ }
+
+ const SpritePosition &pos = sprite->getSpritePosition(name);
+ if (!pos.width || !pos.height) {
+ return Rect<dimension> { 0, 0, 0, 0 };
+ }
+
+ Rect<dimension> rect = allocateImage(pos.width / pos.pixelRatio, pos.height / pos.pixelRatio);
+ if (rect.w == 0) {
+ if (debug::spriteWarnings) {
+ fprintf(stderr, "[WARNING] sprite atlas bitmap overflow\n");
+ }
+ return rect;
+ }
+
+ images.emplace(name, rect);
+
+ copy(rect, pos);
+
+ return rect;
+}
+
+SpriteAtlasPosition SpriteAtlas::getPosition(const std::string& name, bool repeating) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+ // `repeating` indicates that the image will be used in a repeating pattern
+ // repeating pattern images are assumed to have a 1px padding that mirrors the opposite edge
+ // positions for repeating images are adjusted to exclude the edge
+ Rect<dimension> rect = getImage(name);
+ const int r = repeating ? 1 : 0;
+ return SpriteAtlasPosition {
+ {{ float(rect.w) / pixelRatio, float(rect.h) / pixelRatio }},
+ {{ float(rect.x + r) / width, float(rect.y + r) / height }},
+ {{ float(rect.x + rect.w - 2*r) / width, float(rect.y + rect.h - 2*r) / height }}
+ };
+}
+
+void SpriteAtlas::allocate() {
+ if (!data) {
+ dimension w = static_cast<dimension>(width * pixelRatio);
+ dimension h = static_cast<dimension>(height * pixelRatio);
+ data = static_cast<uint32_t*>(::operator new(w * h * sizeof(uint32_t)));
+ std::fill(data, data + w * h, 0);
+ }
+}
+
+void SpriteAtlas::copy(const Rect<dimension>& dst, const SpritePosition& src) {
+ if (!sprite->raster) return;
+ const uint32_t *src_img = reinterpret_cast<const uint32_t *>(sprite->raster->getData());
+ if (!src_img) return;
+ allocate();
+ uint32_t *dst_img = reinterpret_cast<uint32_t *>(data);
+
+ copy_bitmap(
+ /* source buffer */ src_img,
+ /* source stride */ sprite->raster->getWidth(),
+ /* source x */ src.x,
+ /* source y */ src.y,
+ /* dest buffer */ dst_img,
+ /* dest stride */ width * pixelRatio,
+ /* dest x */ dst.x * pixelRatio,
+ /* dest y */ dst.y * pixelRatio,
+ /* icon dimension */ src.width,
+ /* icon dimension */ src.height
+ );
+
+ dirty = true;
+}
+
+void SpriteAtlas::setSprite(util::ptr<Sprite> sprite_) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ sprite = sprite_;
+
+ if (!sprite->isLoaded()) return;
+
+ util::erase_if(uninitialized, [this](const std::string &name) {
+ Rect<dimension> dst = getImage(name);
+ const SpritePosition& src = sprite->getSpritePosition(name);
+ if (!src) {
+ if (debug::spriteWarnings) {
+ fprintf(stderr, "[WARNING] sprite doesn't have image with name '%s'\n", name.c_str());
+ }
+ return true;
+ }
+
+ if (src.width == dst.w * pixelRatio && src.height == dst.h * pixelRatio && src.pixelRatio == pixelRatio) {
+ copy(dst, src);
+ return true;
+ } else {
+ if (debug::spriteWarnings) {
+ fprintf(stderr, "[WARNING] sprite icon dimension mismatch\n");
+ }
+ return false;
+ }
+ });
+}
+
+void SpriteAtlas::bind(bool linear) {
+ bool first = false;
+ if (!texture) {
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+#ifndef GL_ES_VERSION_2_0
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+#endif
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ first = true;
+ } else {
+ glBindTexture(GL_TEXTURE_2D, texture);
+ }
+
+ GLuint filter_val = linear ? GL_LINEAR : GL_NEAREST;
+ if (filter_val != filter) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_val);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_val);
+ filter = filter_val;
+ }
+
+ if (dirty) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+ allocate();
+
+ if (first) {
+ glTexImage2D(
+ GL_TEXTURE_2D, // GLenum target
+ 0, // GLint level
+ GL_RGBA, // GLint internalformat
+ width * pixelRatio, // GLsizei width
+ height * pixelRatio, // GLsizei height
+ 0, // GLint border
+ GL_RGBA, // GLenum format
+ GL_UNSIGNED_BYTE, // GLenum type
+ data // const GLvoid * data
+ );
+ } else {
+ glTexSubImage2D(
+ GL_TEXTURE_2D, // GLenum target
+ 0, // GLint level
+ 0, // GLint xoffset
+ 0, // GLint yoffset
+ width * pixelRatio, // GLsizei width
+ height * pixelRatio, // GLsizei height
+ GL_RGBA, // GLenum format
+ GL_UNSIGNED_BYTE, // GLenum type
+ data // const GLvoid *pixels
+ );
+ }
+
+ dirty = false;
+ }
+};
+
+SpriteAtlas::~SpriteAtlas() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ glDeleteTextures(1, &texture);
+ texture = 0;
+ ::operator delete(data), data = nullptr;
+}
diff --git a/src/mbgl/geometry/sprite_atlas.hpp b/src/mbgl/geometry/sprite_atlas.hpp
new file mode 100644
index 0000000000..9e0fe995bb
--- /dev/null
+++ b/src/mbgl/geometry/sprite_atlas.hpp
@@ -0,0 +1,82 @@
+#ifndef MBGL_GEOMETRY_SPRITE_ATLAS
+#define MBGL_GEOMETRY_SPRITE_ATLAS
+
+#include <mbgl/geometry/binpack.hpp>
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <string>
+#include <map>
+#include <mutex>
+#include <atomic>
+#include <set>
+#include <array>
+
+namespace mbgl {
+
+class Sprite;
+class SpritePosition;
+
+struct SpriteAtlasPosition {
+ std::array<float, 2> size;
+ std::array<float, 2> tl;
+ std::array<float, 2> br;
+};
+
+class SpriteAtlas : public util::noncopyable {
+public:
+ typedef uint16_t dimension;
+
+ // Add way to construct this from another SpriteAtlas (e.g. with another pixelRatio)
+ SpriteAtlas(dimension width, dimension height);
+ ~SpriteAtlas();
+
+ // Changes the pixel ratio.
+ bool resize(float newRatio);
+
+ // Changes the source sprite.
+ void setSprite(util::ptr<Sprite> sprite);
+
+ // Returns the coordinates of an image that is sourced from the sprite image.
+ // This getter attempts to read the image from the sprite if it is already loaded.
+ // In that case, it copies it into the sprite atlas and returns the dimensions.
+ // Otherwise, it returns a 0/0/0/0 rect.
+ Rect<dimension> getImage(const std::string& name);
+
+ SpriteAtlasPosition getPosition(const std::string& name, bool repeating = false);
+
+ // Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out
+ // of date.
+ void bind(bool linear = false);
+
+ inline float getWidth() const { return width; }
+ inline float getHeight() const { return height; }
+ inline float getTextureWidth() const { return width * pixelRatio; }
+ inline float getTextureHeight() const { return height * pixelRatio; }
+ inline float getPixelRatio() const { return pixelRatio; }
+
+ const dimension width = 0;
+ const dimension height = 0;
+
+private:
+ void allocate();
+ Rect<SpriteAtlas::dimension> allocateImage(size_t width, size_t height);
+ void copy(const Rect<dimension>& dst, const SpritePosition& src);
+
+ std::recursive_mutex mtx;
+ float pixelRatio = 1.0f;
+ BinPack<dimension> bin;
+ util::ptr<Sprite> sprite;
+ std::map<std::string, Rect<dimension>> images;
+ std::set<std::string> uninitialized;
+ uint32_t *data = nullptr;
+ std::atomic<bool> dirty;
+ uint32_t texture = 0;
+ uint32_t filter = 0;
+ static const int buffer = 1;
+};
+
+};
+
+#endif
diff --git a/src/mbgl/geometry/static_vertex_buffer.cpp b/src/mbgl/geometry/static_vertex_buffer.cpp
new file mode 100644
index 0000000000..c86211c50f
--- /dev/null
+++ b/src/mbgl/geometry/static_vertex_buffer.cpp
@@ -0,0 +1,14 @@
+#include <mbgl/geometry/static_vertex_buffer.hpp>
+#include <mbgl/platform/gl.hpp>
+
+namespace mbgl {
+
+StaticVertexBuffer::StaticVertexBuffer(std::initializer_list<std::pair<int16_t, int16_t>> init) {
+ for (const std::pair<int16_t, int16_t> &vertex : init) {
+ vertex_type *vertices = static_cast<vertex_type *>(addElement());
+ vertices[0] = vertex.first;
+ vertices[1] = vertex.second;
+ }
+}
+
+}
diff --git a/src/mbgl/geometry/static_vertex_buffer.hpp b/src/mbgl/geometry/static_vertex_buffer.hpp
new file mode 100644
index 0000000000..ce932269f0
--- /dev/null
+++ b/src/mbgl/geometry/static_vertex_buffer.hpp
@@ -0,0 +1,26 @@
+#ifndef MBGL_GEOMETRY_STATIC_VERTEX_BUFFER
+#define MBGL_GEOMETRY_STATIC_VERTEX_BUFFER
+
+#include <mbgl/geometry/buffer.hpp>
+
+#include <vector>
+#include <cstddef>
+#include <cstdint>
+#include <cmath>
+
+namespace mbgl {
+
+class StaticVertexBuffer : public Buffer<
+ 4, // bytes per vertex (2 * signed short == 4 bytes)
+ GL_ARRAY_BUFFER,
+ 32 // default length
+> {
+public:
+ typedef int16_t vertex_type;
+
+ StaticVertexBuffer(std::initializer_list<std::pair<int16_t, int16_t>> init);
+};
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/text_buffer.cpp b/src/mbgl/geometry/text_buffer.cpp
new file mode 100644
index 0000000000..295ff02efa
--- /dev/null
+++ b/src/mbgl/geometry/text_buffer.cpp
@@ -0,0 +1,33 @@
+#include <mbgl/geometry/text_buffer.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/util/math.hpp>
+
+#include <cmath>
+
+using namespace mbgl;
+
+const double TextVertexBuffer::angleFactor = 128.0 / M_PI;
+
+size_t TextVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, uint16_t tx, uint16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom) {
+ size_t idx = index();
+ void *data = addElement();
+
+ int16_t *shorts = static_cast<int16_t *>(data);
+ shorts[0] = x;
+ shorts[1] = y;
+ shorts[2] = std::round(ox * 64); // use 1/64 pixels for placement
+ shorts[3] = std::round(oy * 64);
+
+ uint8_t *ubytes = static_cast<uint8_t *>(data);
+ ubytes[8] = labelminzoom * 10;
+ ubytes[9] = minzoom * 10; // 1/10 zoom levels: z16 == 160.
+ ubytes[10] = fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160.
+ ubytes[11] = (int16_t)round(angle * angleFactor) % 256;
+ ubytes[12] = util::max((int16_t)std::round(range[0] * angleFactor), (int16_t)0) % 256;
+ ubytes[13] = util::min((int16_t)std::round(range[1] * angleFactor), (int16_t)255) % 256;
+
+ ubytes[14] = tx / 4;
+ ubytes[15] = ty / 4;
+
+ return idx;
+}
diff --git a/src/mbgl/geometry/text_buffer.hpp b/src/mbgl/geometry/text_buffer.hpp
new file mode 100644
index 0000000000..4687b32f97
--- /dev/null
+++ b/src/mbgl/geometry/text_buffer.hpp
@@ -0,0 +1,25 @@
+#ifndef MBGL_GEOMETRY_TEXT_BUFFER
+#define MBGL_GEOMETRY_TEXT_BUFFER
+
+#include <mbgl/geometry/buffer.hpp>
+#include <array>
+
+namespace mbgl {
+
+class TextVertexBuffer : public Buffer <
+ 16,
+ GL_ARRAY_BUFFER,
+ 32768
+> {
+public:
+ typedef int16_t vertex_type;
+
+ static const double angleFactor;
+
+ size_t add(int16_t x, int16_t y, float ox, float oy, uint16_t tx, uint16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom);
+};
+
+
+}
+
+#endif
diff --git a/src/mbgl/geometry/vao.cpp b/src/mbgl/geometry/vao.cpp
new file mode 100644
index 0000000000..66822ba5ce
--- /dev/null
+++ b/src/mbgl/geometry/vao.cpp
@@ -0,0 +1,55 @@
+#include <mbgl/geometry/vao.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/util/string.hpp>
+
+namespace mbgl {
+
+VertexArrayObject::~VertexArrayObject() {
+ if (!gl::DeleteVertexArrays) return;
+
+ if (vao) {
+ gl::DeleteVertexArrays(1, &vao);
+ }
+}
+
+void VertexArrayObject::bindVertexArrayObject() {
+ if (!gl::GenVertexArrays || !gl::BindVertexArray) {
+ static bool reported = false;
+ if (!reported) {
+ Log::Warning(Event::OpenGL, "Not using Vertex Array Objects");
+ reported = true;
+ }
+ return;
+ }
+
+ if (!vao) {
+ gl::GenVertexArrays(1, &vao);
+ }
+ gl::BindVertexArray(vao);
+}
+
+void VertexArrayObject::verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer,
+ char *offset) {
+ if (bound_shader != shader.getID()) {
+ throw std::runtime_error(std::string("trying to rebind VAO to another shader from " +
+ util::toString(bound_shader) + "(" + bound_shader_name + ") to " +
+ util::toString(shader.getID()) + "(" + shader.name + ")" ));
+ } else if (bound_offset != offset) {
+ throw std::runtime_error("trying to bind VAO to another offset");
+ } else if (bound_vertex_buffer != vertexBuffer) {
+ throw std::runtime_error("trying to bind VAO to another vertex buffer");
+ } else if (bound_elements_buffer != elementsBuffer) {
+ throw std::runtime_error("trying to bind VAO to another elements buffer");
+ }
+}
+
+void VertexArrayObject::storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer,
+ char *offset) {
+ bound_shader = shader.getID();
+ bound_shader_name = shader.name;
+ bound_offset = offset;
+ bound_vertex_buffer = vertexBuffer;
+ bound_elements_buffer = elementsBuffer;
+}
+
+}
diff --git a/src/mbgl/geometry/vao.hpp b/src/mbgl/geometry/vao.hpp
new file mode 100644
index 0000000000..2ecba731f7
--- /dev/null
+++ b/src/mbgl/geometry/vao.hpp
@@ -0,0 +1,73 @@
+#ifndef MBGL_GEOMETRY_VAO
+#define MBGL_GEOMETRY_VAO
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <stdexcept>
+
+namespace mbgl {
+
+class VertexArrayObject : public util::noncopyable {
+public:
+ inline VertexArrayObject() {};
+
+ inline VertexArrayObject(VertexArrayObject &&rhs) noexcept
+ : vao(rhs.vao),
+ bound_shader(rhs.bound_shader),
+ bound_shader_name(rhs.bound_shader_name),
+ bound_vertex_buffer(rhs.bound_vertex_buffer),
+ bound_elements_buffer(rhs.bound_elements_buffer),
+ bound_offset(rhs.bound_offset) {};
+
+ template <typename Shader, typename VertexBuffer>
+ inline void bind(Shader& shader, VertexBuffer &vertexBuffer, char *offset) {
+ bindVertexArrayObject();
+ if (bound_shader == 0) {
+ vertexBuffer.bind();
+ shader.bind(offset);
+ if (vao) {
+ storeBinding(shader, vertexBuffer.getID(), 0, offset);
+ }
+ } else {
+ verifyBinding(shader, vertexBuffer.getID(), 0, offset);
+ }
+ }
+
+ template <typename Shader, typename VertexBuffer, typename ElementsBuffer>
+ inline void bind(Shader& shader, VertexBuffer &vertexBuffer, ElementsBuffer &elementsBuffer, char *offset) {
+ bindVertexArrayObject();
+ if (bound_shader == 0) {
+ vertexBuffer.bind();
+ elementsBuffer.bind();
+ shader.bind(offset);
+ if (vao) {
+ storeBinding(shader, vertexBuffer.getID(), elementsBuffer.getID(), offset);
+ }
+ } else {
+ verifyBinding(shader, vertexBuffer.getID(), elementsBuffer.getID(), offset);
+ }
+ }
+
+ ~VertexArrayObject();
+
+private:
+ void bindVertexArrayObject();
+ void storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, char *offset);
+ void verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, char *offset);
+
+ GLuint vao = 0;
+
+ // For debug reasons, we're storing the bind information so that we can
+ // detect errors and report
+ GLuint bound_shader = 0;
+ const char *bound_shader_name = "";
+ GLuint bound_vertex_buffer = 0;
+ GLuint bound_elements_buffer = 0;
+ char *bound_offset = 0;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
new file mode 100644
index 0000000000..b0a06e8711
--- /dev/null
+++ b/src/mbgl/map/map.cpp
@@ -0,0 +1,745 @@
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/view.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/map/source.hpp>
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/map/sprite.hpp>
+#include <mbgl/util/transition.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/clip_ids.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/text/glyph_store.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/util/texture_pool.hpp>
+#include <mbgl/geometry/sprite_atlas.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/uv.hpp>
+
+#include <algorithm>
+#include <iostream>
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+
+#include <uv.h>
+
+#ifdef __ANDROID__
+ #include <coffeecatch/coffeecatch.h>
+#endif
+
+// Check libuv library version.
+const static bool uv_version_check = []() {
+ const unsigned int version = uv_version();
+ const unsigned int major = (version >> 16) & 0xFF;
+ const unsigned int minor = (version >> 8) & 0xFF;
+ const unsigned int patch = version & 0xFF;
+
+#ifndef UV_VERSION_PATCH
+ // 0.10 doesn't have UV_VERSION_PATCH defined, so we "fake" it by using the library patch level.
+ const unsigned int UV_VERSION_PATCH = version & 0xFF;
+#endif
+
+ if (major != UV_VERSION_MAJOR || minor != UV_VERSION_MINOR || patch != UV_VERSION_PATCH) {
+ throw std::runtime_error(mbgl::util::sprintf<96>(
+ "libuv version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d", UV_VERSION_MAJOR,
+ UV_VERSION_MINOR, UV_VERSION_PATCH, major, minor, patch));
+ }
+ return true;
+}();
+
+
+#include <zlib.h>
+// Check zlib library version.
+const static bool zlib_version_check = []() {
+ const char *const version = zlibVersion();
+ if (version[0] != ZLIB_VERSION[0]) {
+ throw std::runtime_error(mbgl::util::sprintf<96>(
+ "zlib version mismatch: headers report %s, but library reports %s", ZLIB_VERSION, version));
+ }
+
+ return true;
+}();
+
+
+#include <sqlite3.h>
+// Check sqlite3 library version.
+const static bool sqlite_version_check = []() {
+ if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER) {
+ throw std::runtime_error(mbgl::util::sprintf<96>(
+ "sqlite3 libversion mismatch: headers report %d, but library reports %d",
+ SQLITE_VERSION_NUMBER, sqlite3_libversion_number()));
+ }
+ if (strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) != 0) {
+ throw std::runtime_error(mbgl::util::sprintf<256>(
+ "sqlite3 sourceid mismatch: headers report \"%s\", but library reports \"%s\"",
+ SQLITE_SOURCE_ID, sqlite3_sourceid()));
+ }
+
+ return true;
+}();
+
+
+using namespace mbgl;
+
+Map::Map(View& view_, FileSource& fileSource_)
+ : loop(util::make_unique<uv::loop>()),
+ view(view_),
+#ifndef NDEBUG
+ mainThread(std::this_thread::get_id()),
+#endif
+ transform(view_),
+ fileSource(fileSource_),
+ glyphAtlas(util::make_unique<GlyphAtlas>(1024, 1024)),
+ glyphStore(std::make_shared<GlyphStore>(fileSource)),
+ spriteAtlas(util::make_unique<SpriteAtlas>(512, 512)),
+ texturePool(std::make_shared<TexturePool>()),
+ painter(util::make_unique<Painter>(*spriteAtlas, *glyphAtlas))
+{
+ view.initialize(this);
+ // Make sure that we're doing an initial drawing in all cases.
+ isClean.clear();
+ isRendered.clear();
+ isSwapped.test_and_set();
+}
+
+Map::~Map() {
+ if (async) {
+ stop();
+ }
+
+ // Explicitly reset all pointers.
+ activeSources.clear();
+ sprite.reset();
+ glyphStore.reset();
+ style.reset();
+ texturePool.reset();
+ workers.reset();
+
+ uv_run(**loop, UV_RUN_DEFAULT);
+}
+
+uv::worker &Map::getWorker() {
+ if (!workers) {
+ workers = util::make_unique<uv::worker>(**loop, 4, "Tile Worker");
+ }
+ return *workers;
+}
+
+void Map::start(bool startPaused) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(!async);
+
+ // When starting map rendering in another thread, we perform async/continuously
+ // updated rendering. Only in these cases, we attach the async handlers.
+ async = true;
+
+ // Reset the flag.
+ isStopped = false;
+
+ // Setup async notifications
+ asyncTerminate = util::make_unique<uv::async>(**loop, [this]() {
+ assert(std::this_thread::get_id() == mapThread);
+
+ // Remove all of these to make sure they are destructed in the correct thread.
+ style.reset();
+ workers.reset();
+ activeSources.clear();
+
+ fileSource.clearLoop();
+
+ terminating = true;
+
+ // Closes all open handles on the loop. This means that the loop will automatically terminate.
+ asyncCleanup.reset();
+ asyncRender.reset();
+ asyncTerminate.reset();
+ });
+
+ asyncRender = util::make_unique<uv::async>(**loop, [this]() {
+ assert(std::this_thread::get_id() == mapThread);
+
+ if (state.hasSize()) {
+ if (isRendered.test_and_set() == false) {
+ prepare();
+ if (isClean.test_and_set() == false) {
+ render();
+ isSwapped.clear();
+ view.swap();
+ } else {
+ // We set the rendered flag in the test above, so we have to reset it
+ // now that we're not actually rendering because the map is clean.
+ isRendered.clear();
+ }
+ }
+ }
+ });
+
+ asyncCleanup = util::make_unique<uv::async>(**loop, [this]() {
+ assert(painter);
+ painter->cleanup();
+ });
+
+ // Do we need to pause first?
+ if (startPaused) {
+ pause();
+ }
+
+ thread = std::thread([this]() {
+#ifndef NDEBUG
+ mapThread = std::this_thread::get_id();
+#endif
+
+#ifdef __APPLE__
+ pthread_setname_np("Map");
+#endif
+
+ run();
+
+#ifndef NDEBUG
+ mapThread = std::thread::id();
+#endif
+
+ // Make sure that the stop() function knows when to stop invoking the callback function.
+ isStopped = true;
+ view.notify();
+ });
+}
+
+void Map::stop(std::function<void ()> callback) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(mainThread != mapThread);
+ assert(async);
+
+ asyncTerminate->send();
+
+ resume();
+
+ if (callback) {
+ // Wait until the render thread stopped. We are using this construct instead of plainly
+ // relying on the thread_join because the system might need to run things in the current
+ // thread that is required for the render thread to terminate correctly. This is for example
+ // the case with Cocoa's NSURLRequest. Otherwise, we will eventually deadlock because this
+ // thread (== main thread) is blocked. The callback function should use an efficient waiting
+ // function to avoid a busy waiting loop.
+ while (!isStopped) {
+ callback();
+ }
+ }
+
+ // If a callback function was provided, this should return immediately because the thread has
+ // already finished executing.
+ thread.join();
+
+ async = false;
+}
+
+void Map::pause(bool waitForPause) {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(async);
+ mutexRun.lock();
+ pausing = true;
+ mutexRun.unlock();
+
+ uv_stop(**loop);
+ rerender(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause
+
+ if (waitForPause) {
+ std::unique_lock<std::mutex> lockPause (mutexPause);
+ while (!isPaused) {
+ condPause.wait(lockPause);
+ }
+ }
+}
+
+void Map::resume() {
+ assert(std::this_thread::get_id() == mainThread);
+ assert(async);
+
+ mutexRun.lock();
+ pausing = false;
+ condRun.notify_all();
+ mutexRun.unlock();
+}
+
+void Map::run() {
+#ifdef __ANDROID__
+ COFFEE_TRY() {
+#endif
+
+#ifndef NDEBUG
+ if (!async) {
+ mapThread = mainThread;
+ }
+#endif
+ assert(std::this_thread::get_id() == mapThread);
+
+ if (async) {
+ checkForPause();
+ }
+
+ setup();
+ prepare();
+
+ if (async) {
+ terminating = false;
+ while(!terminating) {
+ uv_run(**loop, UV_RUN_DEFAULT);
+ checkForPause();
+ }
+ } else {
+ uv_run(**loop, UV_RUN_DEFAULT);
+ }
+
+ // Run the event loop once more to make sure our async delete handlers are called.
+ uv_run(**loop, UV_RUN_ONCE);
+
+ // If the map rendering wasn't started asynchronously, we perform one render
+ // *after* all events have been processed.
+ if (!async) {
+ render();
+#ifndef NDEBUG
+ mapThread = std::thread::id();
+#endif
+ }
+#ifdef __ANDROID__
+ } COFFEE_CATCH() {
+ Log::Error(Event::Crash, "Map::run() crash:\n%s", coffeecatch_get_message());
+ abort();
+ }
+#endif
+}
+
+void Map::checkForPause() {
+ std::unique_lock<std::mutex> lockRun (mutexRun);
+ while (pausing) {
+ view.make_inactive();
+
+ mutexPause.lock();
+ isPaused = true;
+ condPause.notify_all();
+ mutexPause.unlock();
+
+ condRun.wait(lockRun);
+
+ view.make_active();
+ }
+
+ mutexPause.lock();
+ isPaused = false;
+ mutexPause.unlock();
+}
+
+void Map::rerender() {
+ // We only send render events if we want to continuously update the map
+ // (== async rendering).
+ if (async) {
+ asyncRender->send();
+ }
+}
+
+void Map::update() {
+ isClean.clear();
+ rerender();
+}
+
+bool Map::needsSwap() {
+ return isSwapped.test_and_set() == false;
+}
+
+void Map::swapped() {
+ isRendered.clear();
+ rerender();
+}
+
+void Map::cleanup() {
+ if (asyncCleanup != nullptr) {
+ asyncCleanup->send();
+ }
+}
+
+void Map::terminate() {
+ assert(painter);
+ painter->terminate();
+}
+
+#pragma mark - Setup
+
+void Map::setup() {
+ assert(std::this_thread::get_id() == mapThread);
+ assert(painter);
+ view.make_active();
+ painter->setup();
+}
+
+void Map::setStyleURL(const std::string &url) {
+ // TODO: Make threadsafe.
+
+ styleURL = url;
+ if (async) {
+ stop();
+ start();
+ }
+}
+
+
+void Map::setStyleJSON(std::string newStyleJSON, const std::string &base) {
+ // TODO: Make threadsafe.
+ styleJSON.swap(newStyleJSON);
+ sprite.reset();
+ if (!style) {
+ style = std::make_shared<Style>();
+ }
+
+ style->loadJSON((const uint8_t *)styleJSON.c_str());
+ fileSource.setBase(base);
+ glyphStore->setURL(style->glyph_url);
+
+ style->setDefaultTransitionDuration(defaultTransitionDuration);
+
+ // set applied classes if they were set while the style was loading
+ appliedClassesMutex.lock();
+ util::ptr<std::vector<std::string>> classes = appliedClasses;
+ if (appliedClasses) {
+ appliedClasses.reset();
+ }
+ appliedClassesMutex.unlock();
+ if (classes) {
+ style->setAppliedClasses(*classes);
+ }
+
+ update();
+}
+
+std::string Map::getStyleJSON() const {
+ return styleJSON;
+}
+
+util::ptr<Sprite> Map::getSprite() {
+ const float pixelRatio = state.getPixelRatio();
+ const std::string &sprite_url = style->getSpriteURL();
+ if (!sprite || sprite->pixelRatio != pixelRatio) {
+ sprite = Sprite::Create(sprite_url, pixelRatio, fileSource);
+ }
+
+ return sprite;
+}
+
+
+#pragma mark - Size
+
+void Map::resize(uint16_t width, uint16_t height, float ratio) {
+ resize(width, height, ratio, width * ratio, height * ratio);
+}
+
+void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight) {
+ if (transform.resize(width, height, ratio, fbWidth, fbHeight)) {
+ update();
+ }
+}
+
+#pragma mark - Transitions
+
+void Map::cancelTransitions() {
+ transform.cancelTransitions();
+
+ update();
+}
+
+
+#pragma mark - Position
+
+void Map::moveBy(double dx, double dy, double duration) {
+ transform.moveBy(dx, dy, duration * 1_second);
+ update();
+}
+
+void Map::setLonLat(double lon, double lat, double duration) {
+ transform.setLonLat(lon, lat, duration * 1_second);
+ update();
+}
+
+void Map::getLonLat(double& lon, double& lat) const {
+ transform.getLonLat(lon, lat);
+}
+
+void Map::startPanning() {
+ transform.startPanning();
+ update();
+}
+
+void Map::stopPanning() {
+ transform.stopPanning();
+ update();
+}
+
+void Map::resetPosition() {
+ transform.setAngle(0);
+ transform.setLonLat(0, 0);
+ transform.setZoom(0);
+ update();
+}
+
+
+#pragma mark - Scale
+
+void Map::scaleBy(double ds, double cx, double cy, double duration) {
+ transform.scaleBy(ds, cx, cy, duration * 1_second);
+ update();
+}
+
+void Map::setScale(double scale, double cx, double cy, double duration) {
+ transform.setScale(scale, cx, cy, duration * 1_second);
+ update();
+}
+
+double Map::getScale() const {
+ return transform.getScale();
+}
+
+void Map::setZoom(double zoom, double duration) {
+ transform.setZoom(zoom, duration * 1_second);
+ update();
+}
+
+double Map::getZoom() const {
+ return transform.getZoom();
+}
+
+void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) {
+ transform.setLonLatZoom(lon, lat, zoom, duration * 1_second);
+ update();
+}
+
+void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const {
+ transform.getLonLatZoom(lon, lat, zoom);
+}
+
+void Map::resetZoom() {
+ setZoom(0);
+}
+
+void Map::startScaling() {
+ transform.startScaling();
+ update();
+}
+
+void Map::stopScaling() {
+ transform.stopScaling();
+ update();
+}
+
+double Map::getMinZoom() const {
+ return transform.getMinZoom();
+}
+
+double Map::getMaxZoom() const {
+ return transform.getMaxZoom();
+}
+
+
+#pragma mark - Rotation
+
+void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) {
+ transform.rotateBy(sx, sy, ex, ey, duration * 1_second);
+ update();
+}
+
+void Map::setBearing(double degrees, double duration) {
+ transform.setAngle(-degrees * M_PI / 180, duration * 1_second);
+ update();
+}
+
+void Map::setBearing(double degrees, double cx, double cy) {
+ transform.setAngle(-degrees * M_PI / 180, cx, cy);
+ update();
+}
+
+double Map::getBearing() const {
+ return -transform.getAngle() / M_PI * 180;
+}
+
+void Map::resetNorth() {
+ transform.setAngle(0, 500_milliseconds);
+ update();
+}
+
+void Map::startRotating() {
+ transform.startRotating();
+ update();
+}
+
+void Map::stopRotating() {
+ transform.stopRotating();
+ update();
+}
+
+
+#pragma mark - Toggles
+
+void Map::setDebug(bool value) {
+ debug = value;
+ assert(painter);
+ painter->setDebug(debug);
+ update();
+}
+
+void Map::toggleDebug() {
+ setDebug(!debug);
+}
+
+bool Map::getDebug() const {
+ return debug;
+}
+
+void Map::setAppliedClasses(const std::vector<std::string> &classes) {
+ if (style) {
+ style->setAppliedClasses(classes);
+ if (style->hasTransitions()) {
+ update();
+ }
+ }
+ else {
+ std::lock_guard<std::mutex> lock(appliedClassesMutex);
+ appliedClasses = mbgl::util::make_unique<std::vector<std::string>>(classes);
+ }
+}
+
+
+void Map::toggleClass(const std::string &name) {
+ style->toggleClass(name);
+ if (style->hasTransitions()) {
+ update();
+ }
+}
+
+const std::vector<std::string> &Map::getAppliedClasses() const {
+ return style->getAppliedClasses();
+}
+
+void Map::setDefaultTransitionDuration(uint64_t milliseconds) {
+ defaultTransitionDuration = milliseconds;
+ if (style) {
+ style->setDefaultTransitionDuration(milliseconds);
+ }
+}
+
+uint64_t Map::getDefaultTransitionDuration() {
+ return defaultTransitionDuration;
+}
+
+void Map::updateSources() {
+ assert(std::this_thread::get_id() == mapThread);
+
+ // First, disable all existing sources.
+ for (const auto& source : activeSources) {
+ source->enabled = false;
+ }
+
+ // Then, reenable all of those that we actually use when drawing this layer.
+ updateSources(style->layers);
+
+ // Then, construct or destroy the actual source object, depending on enabled state.
+ for (const auto& source : activeSources) {
+ if (source->enabled) {
+ if (!source->source) {
+ source->source = std::make_shared<Source>(source->info);
+ source->source->load(*this, fileSource);
+ }
+ } else {
+ source->source.reset();
+ }
+ }
+
+ // Finally, remove all sources that are disabled.
+ util::erase_if(activeSources, [](util::ptr<StyleSource> source){
+ return !source->enabled;
+ });
+}
+
+void Map::updateSources(const util::ptr<StyleLayerGroup> &group) {
+ if (!group) {
+ return;
+ }
+ for (const util::ptr<StyleLayer> &layer : group->layers) {
+ if (!layer) continue;
+ if (layer->bucket) {
+ if (layer->bucket->style_source) {
+ (*activeSources.emplace(layer->bucket->style_source).first)->enabled = true;
+ }
+ } else if (layer->layers) {
+ updateSources(layer->layers);
+ }
+ }
+}
+
+void Map::updateTiles() {
+ for (const auto& source : activeSources) {
+ source->source->update(*this, getWorker(),
+ style, *glyphAtlas, *glyphStore,
+ *spriteAtlas, getSprite(),
+ *texturePool, fileSource, [this](){ update(); });
+ }
+}
+
+void Map::prepare() {
+ if (!fileSource.hasLoop()) {
+ fileSource.setLoop(**loop);
+ }
+
+ if (!style) {
+ style = std::make_shared<Style>();
+
+ fileSource.request(ResourceType::JSON, styleURL)->onload([&](const Response &res) {
+ if (res.code == 200) {
+ // Calculate the base
+ const size_t pos = styleURL.rfind('/');
+ std::string base = "";
+ if (pos != std::string::npos) {
+ base = styleURL.substr(0, pos + 1);
+ }
+
+ setStyleJSON(res.data, base);
+ } else {
+ Log::Error(Event::Setup, "loading style failed: %ld (%s)", res.code, res.message.c_str());
+ }
+ });
+ }
+
+ // Update transform transitions.
+ animationTime = util::now();
+ if (transform.needsTransition()) {
+ transform.updateTransitions(animationTime);
+ }
+
+ state = transform.currentState();
+
+ animationTime = util::now();
+ updateSources();
+ style->updateProperties(state.getNormalizedZoom(), animationTime);
+
+ // Allow the sprite atlas to potentially pull new sprite images if needed.
+ spriteAtlas->resize(state.getPixelRatio());
+ spriteAtlas->setSprite(getSprite());
+
+ updateTiles();
+}
+
+void Map::render() {
+ assert(painter);
+ painter->render(*style, activeSources,
+ state, animationTime);
+ // Schedule another rerender when we definitely need a next frame.
+ if (transform.needsTransition() || style->hasTransitions()) {
+ update();
+ }
+}
diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp
new file mode 100644
index 0000000000..6fac7862e7
--- /dev/null
+++ b/src/mbgl/map/raster_tile_data.cpp
@@ -0,0 +1,34 @@
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/raster_tile_data.hpp>
+#include <mbgl/style/style.hpp>
+
+using namespace mbgl;
+
+
+RasterTileData::RasterTileData(Tile::ID const& id_, TexturePool& texturePool, const SourceInfo& source_)
+ : TileData(id_, source_),
+ bucket(texturePool, properties) {
+}
+
+RasterTileData::~RasterTileData() {
+}
+
+void RasterTileData::parse() {
+ if (state != State::loaded) {
+ return;
+ }
+
+ if (bucket.setImage(data)) {
+ state = State::parsed;
+ } else {
+ state = State::invalid;
+ }
+}
+
+void RasterTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) {
+ bucket.render(painter, layer_desc, id, matrix);
+}
+
+bool RasterTileData::hasData(StyleLayer const& /*layer_desc*/) const {
+ return bucket.hasData();
+}
diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp
new file mode 100644
index 0000000000..42070d9c61
--- /dev/null
+++ b/src/mbgl/map/raster_tile_data.hpp
@@ -0,0 +1,33 @@
+#ifndef MBGL_MAP_RASTER_TILE_DATA
+#define MBGL_MAP_RASTER_TILE_DATA
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/renderer/raster_bucket.hpp>
+
+namespace mbgl {
+
+class Painter;
+class SourceInfo;
+class StyleLayer;
+class TexturePool;
+
+class RasterTileData : public TileData {
+ friend class TileParser;
+
+public:
+ RasterTileData(Tile::ID const& id, TexturePool&, const SourceInfo&);
+ ~RasterTileData();
+
+ virtual void parse();
+ virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix);
+ virtual bool hasData(StyleLayer const& layer_desc) const;
+
+protected:
+ StyleBucketRaster properties;
+ RasterBucket bucket;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp
new file mode 100644
index 0000000000..798cd41d1d
--- /dev/null
+++ b/src/mbgl/map/source.cpp
@@ -0,0 +1,369 @@
+#include <mbgl/map/source.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/map/transform.hpp>
+#include <mbgl/renderer/painter.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/raster.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/texture_pool.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/box.hpp>
+#include <mbgl/util/mapbox.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <mbgl/map/vector_tile_data.hpp>
+#include <mbgl/map/raster_tile_data.hpp>
+
+#include <algorithm>
+
+namespace mbgl {
+
+Source::Source(SourceInfo& info_)
+ : info(info_)
+{
+}
+
+// Note: This is a separate function that must be called exactly once after creation
+// The reason this isn't part of the constructor is that calling shared_from_this() in
+// the constructor fails.
+void Source::load(Map& map, FileSource& fileSource) {
+ if (info.url.empty()) {
+ loaded = true;
+ return;
+ }
+
+ util::ptr<Source> source = shared_from_this();
+
+ fileSource.request(ResourceType::JSON, info.url)->onload([source, &map](const Response &res) {
+ if (res.code != 200) {
+ Log::Warning(Event::General, "failed to load source TileJSON");
+ return;
+ }
+
+ rapidjson::Document d;
+ d.Parse<0>(res.data.c_str());
+
+ if (d.HasParseError()) {
+ Log::Warning(Event::General, "invalid source TileJSON");
+ return;
+ }
+
+ source->info.parseTileJSONProperties(d);
+ source->loaded = true;
+
+ map.update();
+
+ });
+}
+
+void Source::updateClipIDs(const std::map<Tile::ID, ClipID> &mapping) {
+ std::for_each(tiles.begin(), tiles.end(), [&mapping](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
+ Tile &tile = *pair.second;
+ auto it = mapping.find(tile.id);
+ if (it != mapping.end()) {
+ tile.clip = it->second;
+ } else {
+ tile.clip = ClipID {};
+ }
+ });
+}
+
+void Source::updateMatrices(const mat4 &projMatrix, const TransformState &transform) {
+ for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ transform.matrixFor(tile.matrix, tile.id);
+ matrix::multiply(tile.matrix, projMatrix, tile.matrix);
+ }
+}
+
+size_t Source::getTileCount() const {
+ return tiles.size();
+}
+
+void Source::drawClippingMasks(Painter &painter) {
+ for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ gl::group group(std::string { "mask: " } + std::string(tile.id));
+ painter.drawClippingMask(tile.matrix, tile.clip);
+ }
+}
+
+void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc) {
+ gl::group group(std::string { "layer: " } + layer_desc->id);
+ for (const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ if (tile.data && tile.data->state == TileData::State::parsed) {
+ painter.renderTileLayer(tile, layer_desc, tile.matrix);
+ }
+ }
+}
+
+void Source::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) {
+ auto it = tiles.find(id);
+ if (it != tiles.end() && it->second->data && it->second->data->state == TileData::State::parsed) {
+ painter.renderTileLayer(*it->second, layer_desc, matrix);
+ }
+}
+
+void Source::finishRender(Painter &painter) {
+ for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
+ Tile &tile = *pair.second;
+ painter.renderTileDebug(tile);
+ }
+}
+
+std::forward_list<Tile::ID> Source::getIDs() const {
+ std::forward_list<Tile::ID> ptrs;
+
+ std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
+ Tile &tile = *pair.second;
+ return tile.id;
+ });
+ return ptrs;
+}
+
+std::forward_list<Tile *> Source::getLoadedTiles() const {
+ std::forward_list<Tile *> ptrs;
+ auto it = ptrs.before_begin();
+ for (const auto &pair : tiles) {
+ if (pair.second->data->ready()) {
+ it = ptrs.insert_after(it, pair.second.get());
+ }
+ }
+ return ptrs;
+}
+
+
+TileData::State Source::hasTile(const Tile::ID& id) {
+ auto it = tiles.find(id);
+ if (it != tiles.end()) {
+ Tile &tile = *it->second;
+ if (tile.id == id && tile.data) {
+ return tile.data->state;
+ }
+ }
+
+ return TileData::State::invalid;
+}
+
+TileData::State Source::addTile(Map& map, uv::worker& worker,
+ util::ptr<Style> style,
+ GlyphAtlas& glyphAtlas, GlyphStore& glyphStore,
+ SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite,
+ FileSource& fileSource, TexturePool& texturePool,
+ const Tile::ID& id,
+ std::function<void ()> callback) {
+ const TileData::State state = hasTile(id);
+
+ if (state != TileData::State::invalid) {
+ return state;
+ }
+
+ auto pos = tiles.emplace(id, util::make_unique<Tile>(id));
+ Tile& new_tile = *pos.first->second;
+
+ // We couldn't find the tile in the list. Create a new one.
+ // Try to find the associated TileData object.
+ const Tile::ID normalized_id = id.normalized();
+
+ auto it = tile_data.find(normalized_id);
+ if (it != tile_data.end()) {
+ // Create a shared_ptr handle. Note that this might be empty!
+ new_tile.data = it->second.lock();
+ }
+
+ if (new_tile.data && new_tile.data->state == TileData::State::obsolete) {
+ // Do not consider the tile if it's already obsolete.
+ new_tile.data.reset();
+ }
+
+ if (!new_tile.data) {
+ // If we don't find working tile data, we're just going to load it.
+ if (info.type == SourceType::Vector) {
+ new_tile.data = std::make_shared<VectorTileData>(normalized_id, map.getMaxZoom(), style,
+ glyphAtlas, glyphStore,
+ spriteAtlas, sprite,
+ texturePool, info);
+ } else if (info.type == SourceType::Raster) {
+ new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info);
+ } else {
+ throw std::runtime_error("source type not implemented");
+ }
+
+ new_tile.data->request(worker, fileSource, map.getState().getPixelRatio(), callback);
+ tile_data.emplace(new_tile.data->id, new_tile.data);
+ }
+
+ return new_tile.data->state;
+}
+
+double Source::getZoom(const TransformState& state) const {
+ double offset = std::log(util::tileSize / info.tile_size) / std::log(2);
+ offset += (state.getPixelRatio() > 1.0 ? 1 :0);
+ return state.getZoom() + offset;
+}
+
+int32_t Source::coveringZoomLevel(const TransformState& state) const {
+ return std::floor(getZoom(state));
+}
+
+std::forward_list<Tile::ID> Source::coveringTiles(const TransformState& state) const {
+ int32_t z = coveringZoomLevel(state);
+
+ if (z < info.min_zoom) return {{}};
+ if (z > info.max_zoom) z = info.max_zoom;
+
+ // Map four viewport corners to pixel coordinates
+ box points = state.cornersToBox(z);
+ const vec2<double>& center = points.center;
+
+ std::forward_list<Tile::ID> covering_tiles = Tile::cover(z, points);
+
+ covering_tiles.sort([&center](const Tile::ID& a, const Tile::ID& b) {
+ // Sorts by distance from the box center
+ return std::fabs(a.x - center.x) + std::fabs(a.y - center.y) <
+ std::fabs(b.x - center.x) + std::fabs(b.y - center.y);
+ });
+
+ return covering_tiles;
+}
+
+/**
+ * Recursively find children of the given tile that are already loaded.
+ *
+ * @param id The tile ID that we should find children for.
+ * @param maxCoveringZoom The maximum zoom level of children to look for.
+ * @param retain An object that we add the found tiles to.
+ *
+ * @return boolean Whether the children found completely cover the tile.
+ */
+bool Source::findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain) {
+ bool complete = true;
+ int32_t z = id.z;
+ auto ids = id.children(z + 1);
+ for (const Tile::ID& child_id : ids) {
+ const TileData::State state = hasTile(child_id);
+ if (state == TileData::State::parsed) {
+ retain.emplace_front(child_id);
+ } else {
+ complete = false;
+ if (z < maxCoveringZoom) {
+ // Go further down the hierarchy to find more unloaded children.
+ findLoadedChildren(child_id, maxCoveringZoom, retain);
+ }
+ }
+ }
+ return complete;
+}
+
+/**
+ * Find a loaded parent of the given tile.
+ *
+ * @param id The tile ID that we should find children for.
+ * @param minCoveringZoom The minimum zoom level of parents to look for.
+ * @param retain An object that we add the found tiles to.
+ *
+ * @return boolean Whether a parent was found.
+ */
+bool Source::findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain) {
+ for (int32_t z = id.z - 1; z >= minCoveringZoom; --z) {
+ const Tile::ID parent_id = id.parent(z);
+ const TileData::State state = hasTile(parent_id);
+ if (state == TileData::State::parsed) {
+ retain.emplace_front(parent_id);
+ return true;
+ }
+ }
+ return false;
+}
+
+void Source::update(Map& map, uv::worker& worker,
+ util::ptr<Style> style,
+ GlyphAtlas& glyphAtlas, GlyphStore& glyphStore,
+ SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite,
+ TexturePool& texturePool, FileSource& fileSource,
+ std::function<void ()> callback) {
+ if (!loaded || map.getTime() <= updated)
+ return;
+
+ bool changed = false;
+
+ int32_t zoom = std::floor(getZoom(map.getState()));
+ std::forward_list<Tile::ID> required = coveringTiles(map.getState());
+
+ // Determine the overzooming/underzooming amounts.
+ int32_t minCoveringZoom = util::clamp<int32_t>(zoom - 10, info.min_zoom, info.max_zoom);
+ int32_t maxCoveringZoom = util::clamp<int32_t>(zoom + 1, info.min_zoom, info.max_zoom);
+
+ // Retain is a list of tiles that we shouldn't delete, even if they are not
+ // the most ideal tile for the current viewport. This may include tiles like
+ // parent or child tiles that are *already* loaded.
+ std::forward_list<Tile::ID> retain(required);
+
+ // Add existing child/parent tiles if the actual tile is not yet loaded
+ for (const Tile::ID& id : required) {
+ const TileData::State state = addTile(map, worker, style,
+ glyphAtlas, glyphStore,
+ spriteAtlas, sprite,
+ fileSource, texturePool,
+ id, callback);
+
+ if (state != TileData::State::parsed) {
+ // The tile we require is not yet loaded. Try to find a parent or
+ // child tile that we already have.
+
+ // First, try to find existing child tiles that completely cover the
+ // missing tile.
+ bool complete = findLoadedChildren(id, maxCoveringZoom, retain);
+
+ // Then, if there are no complete child tiles, try to find existing
+ // parent tiles that completely cover the missing tile.
+ if (!complete) {
+ findLoadedParent(id, minCoveringZoom, retain);
+ }
+ }
+
+ if (state == TileData::State::initial) {
+ changed = true;
+ }
+ }
+
+ // Remove tiles that we definitely don't need, i.e. tiles that are not on
+ // the required list.
+ std::set<Tile::ID> retain_data;
+ util::erase_if(tiles, [&retain, &retain_data, &changed](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
+ Tile &tile = *pair.second;
+ bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end();
+ if (obsolete) {
+ changed = true;
+ } else {
+ retain_data.insert(tile.data->id);
+ }
+ return obsolete;
+ });
+
+ // Remove all the expired pointers from the set.
+ util::erase_if(tile_data, [&retain_data](std::pair<const Tile::ID, std::weak_ptr<TileData>> &pair) {
+ const util::ptr<TileData> tile = pair.second.lock();
+ if (!tile) {
+ return true;
+ }
+
+ bool obsolete = retain_data.find(tile->id) == retain_data.end();
+ if (obsolete) {
+ tile->cancel();
+ return true;
+ } else {
+ return false;
+ }
+ });
+
+ updated = map.getTime();
+}
+
+}
diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp
new file mode 100644
index 0000000000..8976f67b05
--- /dev/null
+++ b/src/mbgl/map/source.hpp
@@ -0,0 +1,86 @@
+#ifndef MBGL_MAP_SOURCE
+#define MBGL_MAP_SOURCE
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/style/style_source.hpp>
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <cstdint>
+#include <forward_list>
+#include <iosfwd>
+#include <map>
+
+namespace mbgl {
+
+class Map;
+class GlyphAtlas;
+class GlyphStore;
+class SpriteAtlas;
+class Sprite;
+class FileSource;
+class TexturePool;
+class Style;
+class Painter;
+class StyleLayer;
+class TransformState;
+struct box;
+
+class Source : public std::enable_shared_from_this<Source>, private util::noncopyable {
+public:
+ Source(SourceInfo&);
+
+ void load(Map&, FileSource&);
+ void update(Map&, uv::worker&,
+ util::ptr<Style>,
+ GlyphAtlas&, GlyphStore&,
+ SpriteAtlas&, util::ptr<Sprite>,
+ TexturePool&, FileSource&,
+ std::function<void ()> callback);
+
+ void updateMatrices(const mat4 &projMatrix, const TransformState &transform);
+ void drawClippingMasks(Painter &painter);
+ size_t getTileCount() const;
+ void render(Painter &painter, util::ptr<StyleLayer> layer_desc);
+ void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix);
+ void finishRender(Painter &painter);
+
+ std::forward_list<Tile::ID> getIDs() const;
+ std::forward_list<Tile *> getLoadedTiles() const;
+ void updateClipIDs(const std::map<Tile::ID, ClipID> &mapping);
+
+private:
+ bool findLoadedChildren(const Tile::ID& id, int32_t maxCoveringZoom, std::forward_list<Tile::ID>& retain);
+ bool findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std::forward_list<Tile::ID>& retain);
+ int32_t coveringZoomLevel(const TransformState&) const;
+ std::forward_list<Tile::ID> coveringTiles(const TransformState&) const;
+
+ TileData::State addTile(Map&, uv::worker&,
+ util::ptr<Style>,
+ GlyphAtlas&, GlyphStore&,
+ SpriteAtlas&, util::ptr<Sprite>,
+ FileSource&, TexturePool&,
+ const Tile::ID&,
+ std::function<void ()> callback);
+
+ TileData::State hasTile(const Tile::ID& id);
+
+ double getZoom(const TransformState &state) const;
+
+ SourceInfo& info;
+ bool loaded = false;
+
+ // Stores the time when this source was most recently updated.
+ timestamp updated = 0;
+
+ std::map<Tile::ID, std::unique_ptr<Tile>> tiles;
+ std::map<Tile::ID, std::weak_ptr<TileData>> tile_data;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp
new file mode 100644
index 0000000000..c1f71e59d9
--- /dev/null
+++ b/src/mbgl/map/sprite.cpp
@@ -0,0 +1,152 @@
+#include <mbgl/map/sprite.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/util/raster.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <string>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <rapidjson/document.h>
+
+using namespace mbgl;
+
+SpritePosition::SpritePosition(uint16_t x_, uint16_t y_, uint16_t width_, uint16_t height_, float pixelRatio_, bool sdf_)
+ : x(x_),
+ y(y_),
+ width(width_),
+ height(height_),
+ pixelRatio(pixelRatio_),
+ sdf(sdf_) {
+}
+
+util::ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio, FileSource& fileSource) {
+ util::ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio));
+ sprite->load(fileSource);
+ return sprite;
+}
+
+Sprite::Sprite(const Key &, const std::string& base_url, float pixelRatio_)
+ : valid(base_url.length() > 0),
+ pixelRatio(pixelRatio_),
+ spriteURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".png"),
+ jsonURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".json"),
+ raster(),
+ loadedImage(false),
+ loadedJSON(false),
+ future(promise.get_future()) {
+}
+
+void Sprite::waitUntilLoaded() const {
+ future.wait();
+}
+
+Sprite::operator bool() const {
+ return valid && isLoaded() && !pos.empty();
+}
+
+
+// Note: This is a separate function that must be called exactly once after creation
+// The reason this isn't part of the constructor is that calling shared_from_this() in
+// the constructor fails.
+void Sprite::load(FileSource& fileSource) {
+ if (!valid) {
+ // Treat a non-existent sprite as a successfully loaded empty sprite.
+ loadedImage = true;
+ loadedJSON = true;
+ promise.set_value();
+ return;
+ }
+
+ util::ptr<Sprite> sprite = shared_from_this();
+
+ fileSource.request(ResourceType::JSON, jsonURL)->onload([sprite](const Response &res) {
+ if (res.code == 200) {
+ sprite->body = res.data;
+ sprite->parseJSON();
+ sprite->complete();
+ } else {
+ Log::Warning(Event::Sprite, "Failed to load sprite info: Error %d: %s", res.code, res.message.c_str());
+ if (!sprite->future.valid()) {
+ sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
+ }
+ }
+ });
+
+ fileSource.request(ResourceType::Image, spriteURL)->onload([sprite](const Response &res) {
+ if (res.code == 200) {
+ sprite->image = res.data;
+ sprite->parseImage();
+ sprite->complete();
+ } else {
+ Log::Warning(Event::Sprite, "Failed to load sprite image: Error %d: %s", res.code, res.message.c_str());
+ if (!sprite->future.valid()) {
+ sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message)));
+ }
+ }
+ });
+}
+
+void Sprite::complete() {
+ if (loadedImage && loadedJSON) {
+ Log::Info(Event::Sprite, "loaded %s", spriteURL.c_str());
+ promise.set_value();
+ }
+}
+
+bool Sprite::isLoaded() const {
+ return loadedImage && loadedJSON;
+}
+
+void Sprite::parseImage() {
+ raster = util::make_unique<util::Image>(image);
+ if (!*raster) {
+ raster.reset();
+ }
+ image.clear();
+ loadedImage = true;
+}
+
+void Sprite::parseJSON() {
+ rapidjson::Document d;
+ d.Parse<0>(body.c_str());
+ body.clear();
+
+ if (d.HasParseError()) {
+ Log::Warning(Event::Sprite, "sprite JSON is invalid");
+ } else if (d.IsObject()) {
+ for (rapidjson::Value::ConstMemberIterator itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) {
+ const std::string& name = itr->name.GetString();
+ const rapidjson::Value& value = itr->value;
+
+ if (value.IsObject()) {
+ uint16_t x = 0;
+ uint16_t y = 0;
+ uint16_t width = 0;
+ uint16_t height = 0;
+ float spritePixelRatio = 1.0f;
+ bool sdf = false;
+
+ if (value.HasMember("x")) x = value["x"].GetInt();
+ if (value.HasMember("y")) y = value["y"].GetInt();
+ if (value.HasMember("width")) width = value["width"].GetInt();
+ if (value.HasMember("height")) height = value["height"].GetInt();
+ if (value.HasMember("pixelRatio")) spritePixelRatio = value["pixelRatio"].GetInt();
+ if (value.HasMember("sdf")) sdf = value["sdf"].GetBool();
+ pos.emplace(name, SpritePosition { x, y, width, height, spritePixelRatio, sdf });
+ }
+ }
+ } else {
+ Log::Warning(Event::Sprite, "sprite JSON root is not an object");
+ }
+
+ loadedJSON = true;
+}
+
+const SpritePosition &Sprite::getSpritePosition(const std::string& name) const {
+ if (!isLoaded()) return empty;
+ auto it = pos.find(name);
+ return it == pos.end() ? empty : it->second;
+}
diff --git a/src/mbgl/map/sprite.hpp b/src/mbgl/map/sprite.hpp
new file mode 100644
index 0000000000..d4b54ba1b5
--- /dev/null
+++ b/src/mbgl/map/sprite.hpp
@@ -0,0 +1,79 @@
+#ifndef MBGL_STYLE_SPRITE
+#define MBGL_STYLE_SPRITE
+
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <cstdint>
+#include <atomic>
+#include <iosfwd>
+#include <string>
+#include <unordered_map>
+#include <future>
+
+namespace mbgl {
+
+class FileSource;
+
+class SpritePosition {
+public:
+ explicit SpritePosition() {}
+ explicit SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t height, float pixelRatio, bool sdf);
+
+ operator bool() const {
+ return !(width == 0 && height == 0 && x == 0 && y == 0);
+ }
+
+ uint16_t x = 0, y = 0;
+ uint16_t width = 0, height = 0;
+ float pixelRatio = 1.0f;
+ bool sdf = false;
+};
+
+class Sprite : public std::enable_shared_from_this<Sprite>, private util::noncopyable {
+private:
+ struct Key {};
+ void load(FileSource& fileSource);
+
+public:
+ Sprite(const Key &, const std::string& base_url, float pixelRatio);
+ static util::ptr<Sprite> Create(const std::string& base_url, float pixelRatio, FileSource& fileSource);
+
+ const SpritePosition &getSpritePosition(const std::string& name) const;
+
+ void waitUntilLoaded() const;
+ bool isLoaded() const;
+
+ operator bool() const;
+
+private:
+ const bool valid;
+
+public:
+ const float pixelRatio;
+ const std::string spriteURL;
+ const std::string jsonURL;
+ std::unique_ptr<util::Image> raster;
+
+private:
+ void parseJSON();
+ void parseImage();
+ void complete();
+
+private:
+ std::string body;
+ std::string image;
+ std::atomic<bool> loadedImage;
+ std::atomic<bool> loadedJSON;
+ std::unordered_map<std::string, SpritePosition> pos;
+ const SpritePosition empty;
+
+ std::promise<void> promise;
+ std::future<void> future;
+
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/tile.cpp b/src/mbgl/map/tile.cpp
new file mode 100644
index 0000000000..9f31048857
--- /dev/null
+++ b/src/mbgl/map/tile.cpp
@@ -0,0 +1,147 @@
+#include <mbgl/map/tile.hpp>
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/box.hpp>
+
+
+#include <cassert>
+
+using namespace mbgl;
+
+#include <iostream>
+
+Tile::Tile(const ID& id_)
+ : id(id_) {
+}
+
+Tile::ID Tile::ID::parent(int8_t parent_z) const {
+ assert(parent_z < z);
+ int32_t dim = std::pow(2, z - parent_z);
+ return Tile::ID{
+ parent_z,
+ (x >= 0 ? x : x - dim + 1) / dim,
+ y / dim
+ };
+}
+
+std::forward_list<Tile::ID> Tile::ID::children(int32_t child_z) const {
+ assert(child_z > z);
+ int32_t factor = std::pow(2, child_z - z);
+
+ std::forward_list<ID> child_ids;
+ for (int32_t ty = y * factor, y_max = (y + 1) * factor; ty < y_max; ++ty) {
+ for (int32_t tx = x * factor, x_max = (x + 1) * factor; tx < x_max; ++tx) {
+ child_ids.emplace_front(child_z, tx, ty);
+ }
+ }
+ return child_ids;
+}
+
+Tile::ID Tile::ID::normalized() const {
+ int32_t dim = std::pow(2, z);
+ int32_t nx = x, ny = y;
+ while (nx < 0) nx += dim;
+ while (nx >= dim) nx -= dim;
+ return ID { z, nx, ny };
+}
+
+bool Tile::ID::isChildOf(const Tile::ID &parent_id) const {
+ if (parent_id.z >= z || parent_id.w != w) {
+ return false;
+ }
+ int32_t scale = std::pow(2, z - parent_id.z);
+ return parent_id.x == ((x < 0 ? x - scale + 1 : x) / scale) &&
+ parent_id.y == y / scale;
+}
+
+
+Tile::ID::operator std::string() const {
+ return util::toString(z) + "/" + util::toString(x) + "/" + util::toString(y);
+}
+
+
+// Taken from polymaps src/Layer.js
+// https://github.com/simplegeo/polymaps/blob/master/src/Layer.js#L333-L383
+
+struct edge {
+ double x0 = 0, y0 = 0;
+ double x1 = 0, y1 = 0;
+ double dx = 0, dy = 0;
+
+ edge(vec2<double> a, vec2<double> b) {
+ if (a.y > b.y) { std::swap(a, b); }
+ x0 = a.x;
+ y0 = a.y;
+ x1 = b.x;
+ y1 = b.y;
+ dx = b.x - a.x;
+ dy = b.y - a.y;
+ }
+};
+
+typedef const std::function<void(int32_t x0, int32_t x1, int32_t y)> ScanLine;
+
+// scan-line conversion
+static void scanSpans(edge e0, edge e1, int32_t ymin, int32_t ymax, ScanLine scanLine) {
+ double y0 = std::fmax(ymin, std::floor(e1.y0));
+ double y1 = std::fmin(ymax, std::ceil(e1.y1));
+
+ // sort edges by x-coordinate
+ if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ?
+ (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) :
+ (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
+ std::swap(e0, e1);
+ }
+
+ // scan lines!
+ double m0 = e0.dx / e0.dy;
+ double m1 = e1.dx / e1.dy;
+ double d0 = e0.dx > 0; // use y + 1 to compute x0
+ double d1 = e1.dx < 0; // use y + 1 to compute x1
+ for (int32_t y = y0; y < y1; y++) {
+ double x0 = m0 * std::fmax(0, std::fmin(e0.dy, y + d0 - e0.y0)) + e0.x0;
+ double x1 = m1 * std::fmax(0, std::fmin(e1.dy, y + d1 - e1.y0)) + e1.x0;
+ scanLine(std::floor(x1), std::ceil(x0), y);
+ }
+}
+
+// scan-line conversion
+static void scanTriangle(const mbgl::vec2<double> a, const mbgl::vec2<double> b, const mbgl::vec2<double> c, int32_t ymin, int32_t ymax, ScanLine& scanLine) {
+ edge ab = edge(a, b);
+ edge bc = edge(b, c);
+ edge ca = edge(c, a);
+
+ // sort edges by y-length
+ if (ab.dy > bc.dy) { std::swap(ab, bc); }
+ if (ab.dy > ca.dy) { std::swap(ab, ca); }
+ if (bc.dy > ca.dy) { std::swap(bc, ca); }
+
+ // scan span! scan span!
+ if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine);
+ if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine);
+}
+
+std::forward_list<Tile::ID> Tile::cover(int8_t z, const mbgl::box &bounds) {
+ int32_t tiles = 1 << z;
+ std::forward_list<mbgl::Tile::ID> t;
+
+ auto scanLine = [&](int32_t x0, int32_t x1, int32_t y) {
+ int32_t x;
+ if (y >= 0 && y <= tiles) {
+ for (x = x0; x < x1; x++) {
+ t.emplace_front(z, x, y);
+ }
+ }
+ };
+
+ // Divide the screen up in two triangles and scan each of them:
+ // \---+
+ // | \ |
+ // +---\.
+ scanTriangle(bounds.tl, bounds.tr, bounds.br, 0, tiles, scanLine);
+ scanTriangle(bounds.br, bounds.bl, bounds.tl, 0, tiles, scanLine);
+
+ t.unique();
+
+ return t;
+}
diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp
new file mode 100644
index 0000000000..f89ff15baf
--- /dev/null
+++ b/src/mbgl/map/tile_data.cpp
@@ -0,0 +1,104 @@
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/style/style_source.hpp>
+
+#include <mbgl/util/token.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/uv_detail.hpp>
+
+using namespace mbgl;
+
+TileData::TileData(Tile::ID const& id_, const SourceInfo& source_)
+ : id(id_),
+ name(id),
+ state(State::initial),
+ source(source_),
+ debugBucket(debugFontBuffer) {
+ // Initialize tile debug coordinates
+ debugFontBuffer.addText(name.c_str(), 50, 200, 5);
+}
+
+TileData::~TileData() {
+ cancel();
+}
+
+const std::string TileData::toString() const {
+ return std::string { "[tile " } + name + "]";
+}
+
+void TileData::request(uv::worker& worker, FileSource& fileSource,
+ float pixelRatio, std::function<void ()> callback) {
+ if (source.tiles.empty())
+ return;
+
+ std::string url = source.tiles[(id.x + id.y) % source.tiles.size()];
+ url = util::replaceTokens(url, [&](const std::string &token) -> std::string {
+ if (token == "z") return util::toString(id.z);
+ if (token == "x") return util::toString(id.x);
+ if (token == "y") return util::toString(id.y);
+ if (token == "prefix") {
+ std::string prefix { 2 };
+ prefix[0] = "0123456789abcdef"[id.x % 16];
+ prefix[1] = "0123456789abcdef"[id.y % 16];
+ return prefix;
+ }
+ if (token == "ratio") return pixelRatio > 1.0 ? "@2x" : "";
+ return "";
+ });
+
+ state = State::loading;
+
+ // Note: Somehow this feels slower than the change to request_http()
+ std::weak_ptr<TileData> weak_tile = shared_from_this();
+ req = fileSource.request(ResourceType::Tile, url);
+ req->onload([weak_tile, url, callback, &worker](const Response &res) {
+ util::ptr<TileData> tile = weak_tile.lock();
+ if (!tile || tile->state == State::obsolete) {
+ // noop. Tile is obsolete and we're now just waiting for the refcount
+ // to drop to zero for destruction.
+ return;
+ }
+
+ // Clear the request object.
+ tile->req.reset();
+
+ if (res.code == 200) {
+ tile->state = State::loaded;
+
+ tile->data = res.data;
+
+ // Schedule tile parsing in another thread
+ tile->reparse(worker, callback);
+ } else {
+#if defined(DEBUG)
+ fprintf(stderr, "[%s] tile loading failed: %ld, %s\n", url.c_str(), res.code, res.message.c_str());
+#endif
+ }
+ });
+}
+
+void TileData::cancel() {
+ if (state != State::obsolete) {
+ state = State::obsolete;
+ if (req) {
+ req->cancel();
+ req.reset();
+ }
+ }
+}
+
+void TileData::reparse(uv::worker& worker, std::function<void()> callback)
+{
+ // We're creating a new work request. The work request deletes itself after it executed
+ // the after work handler
+ new uv::work<util::ptr<TileData>>(
+ worker,
+ [](util::ptr<TileData>& tile) {
+ tile->parse();
+ },
+ [callback](util::ptr<TileData>&) {
+ callback();
+ },
+ shared_from_this());
+}
diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp
new file mode 100644
index 0000000000..1ae215b204
--- /dev/null
+++ b/src/mbgl/map/tile_data.hpp
@@ -0,0 +1,88 @@
+#ifndef MBGL_MAP_TILE_DATA
+#define MBGL_MAP_TILE_DATA
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/renderer/debug_bucket.hpp>
+#include <mbgl/geometry/debug_font_buffer.hpp>
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <atomic>
+#include <exception>
+#include <iosfwd>
+#include <string>
+#include <functional>
+
+namespace uv {
+class worker;
+}
+
+namespace mbgl {
+
+class Map;
+class FileSource;
+class Painter;
+class SourceInfo;
+class StyleLayer;
+class Request;
+
+class TileData : public std::enable_shared_from_this<TileData>,
+ private util::noncopyable {
+public:
+ struct exception : std::exception {};
+ struct geometry_too_long_exception : exception {};
+
+public:
+ typedef util::ptr<TileData> Ptr;
+
+ enum class State {
+ invalid,
+ initial,
+ loading,
+ loaded,
+ parsed,
+ obsolete
+ };
+
+public:
+ TileData(Tile::ID const& id, const SourceInfo&);
+ ~TileData();
+
+ void request(uv::worker&, FileSource&, float pixelRatio, std::function<void ()> callback);
+ void reparse(uv::worker&, std::function<void ()> callback);
+ void cancel();
+ const std::string toString() const;
+
+ inline bool ready() const {
+ return state == State::parsed;
+ }
+
+ // Override this in the child class.
+ virtual void parse() = 0;
+ virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) = 0;
+ virtual bool hasData(StyleLayer const& layer_desc) const = 0;
+
+
+public:
+ const Tile::ID id;
+ const std::string name;
+ std::atomic<State> state;
+
+public:
+ const SourceInfo& source;
+
+protected:
+ std::unique_ptr<Request> req;
+ std::string data;
+
+ // Contains the tile ID string for painting debug information.
+ DebugFontBuffer debugFontBuffer;
+
+public:
+ DebugBucket debugBucket;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp
new file mode 100644
index 0000000000..1e12e5fc16
--- /dev/null
+++ b/src/mbgl/map/tile_parser.cpp
@@ -0,0 +1,174 @@
+#include <mbgl/map/tile_parser.hpp>
+#include <mbgl/map/vector_tile_data.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/renderer/fill_bucket.hpp>
+#include <mbgl/renderer/line_bucket.hpp>
+#include <mbgl/renderer/symbol_bucket.hpp>
+#include <mbgl/renderer/raster_bucket.hpp>
+#include <mbgl/util/raster.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/token.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+#include <mbgl/text/glyph_store.hpp>
+#include <mbgl/text/collision.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/map/map.hpp>
+
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/utf.hpp>
+
+#include <locale>
+
+namespace mbgl {
+
+// Note: This destructor is seemingly empty, but we need to declare it anyway
+// because this object has a std::unique_ptr<> of a forward-declare type in
+// its header file.
+TileParser::~TileParser() = default;
+
+TileParser::TileParser(const std::string &data, VectorTileData &tile_,
+ const util::ptr<const Style> &style_,
+ GlyphAtlas & glyphAtlas_,
+ GlyphStore & glyphStore_,
+ SpriteAtlas & spriteAtlas_,
+ const util::ptr<Sprite> &sprite_,
+ TexturePool& texturePool_)
+ : vector_data(pbf((const uint8_t *)data.data(), data.size())),
+ tile(tile_),
+ style(style_),
+ glyphAtlas(glyphAtlas_),
+ glyphStore(glyphStore_),
+ spriteAtlas(spriteAtlas_),
+ sprite(sprite_),
+ texturePool(texturePool_),
+ collision(util::make_unique<Collision>(tile.id.z, 4096, tile.source.tile_size, tile.depth)) {
+ assert(&tile != nullptr);
+ assert(style);
+ assert(sprite);
+ assert(collision);
+}
+
+void TileParser::parse() {
+ parseStyleLayers(style->layers);
+}
+
+bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; }
+
+void TileParser::parseStyleLayers(util::ptr<StyleLayerGroup> group) {
+ if (!group) {
+ return;
+ }
+
+ for (const util::ptr<StyleLayer> &layer_desc : group->layers) {
+ // Cancel early when parsing.
+ if (obsolete()) {
+ return;
+ }
+
+ if (layer_desc->isBackground()) {
+ // background is a special, fake bucket
+ continue;
+ } else if (layer_desc->layers) {
+ // This is a layer group.
+ parseStyleLayers(layer_desc->layers);
+ }
+ if (layer_desc->bucket) {
+ // This is a singular layer. Check if this bucket already exists. If not,
+ // parse this bucket.
+ auto bucket_it = tile.buckets.find(layer_desc->bucket->name);
+ if (bucket_it == tile.buckets.end()) {
+ // We need to create this bucket since it doesn't exist yet.
+ std::unique_ptr<Bucket> bucket = createBucket(layer_desc->bucket);
+ if (bucket) {
+ // Bucket creation might fail because the data tile may not
+ // contain any data that falls into this bucket.
+ tile.buckets[layer_desc->bucket->name] = std::move(bucket);
+ }
+ }
+ } else {
+ fprintf(stderr, "[WARNING] layer '%s' does not have child layers or buckets\n", layer_desc->id.c_str());
+ }
+ }
+}
+
+std::unique_ptr<Bucket> TileParser::createBucket(util::ptr<StyleBucket> bucket_desc) {
+ if (!bucket_desc) {
+ fprintf(stderr, "missing bucket desc\n");
+ return nullptr;
+ }
+
+ // Skip this bucket if we are to not render this
+ if (tile.id.z < std::floor(bucket_desc->min_zoom) && std::floor(bucket_desc->min_zoom) < tile.source.max_zoom) return nullptr;
+ if (tile.id.z >= std::ceil(bucket_desc->max_zoom)) return nullptr;
+
+ auto layer_it = vector_data.layers.find(bucket_desc->source_layer);
+ if (layer_it != vector_data.layers.end()) {
+ const VectorTileLayer &layer = layer_it->second;
+ if (bucket_desc->render.is<StyleBucketFill>()) {
+ return createFillBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketFill>());
+ } else if (bucket_desc->render.is<StyleBucketLine>()) {
+ return createLineBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketLine>());
+ } else if (bucket_desc->render.is<StyleBucketSymbol>()) {
+ return createSymbolBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketSymbol>());
+ } else if (bucket_desc->render.is<StyleBucketRaster>()) {
+ return nullptr;
+ } else {
+ fprintf(stderr, "[WARNING] unknown bucket render type for layer '%s' (source layer '%s')\n", bucket_desc->name.c_str(), bucket_desc->source_layer.c_str());
+ }
+ } else if (bucket_desc->render.is<StyleBucketRaster>() && bucket_desc->render.get<StyleBucketRaster>().prerendered == true) {
+ return createRasterBucket(bucket_desc->render.get<StyleBucketRaster>());
+ } else {
+ // The layer specified in the bucket does not exist. Do nothing.
+ if (debug::tileParseWarnings) {
+ fprintf(stderr, "[WARNING] layer '%s' does not exist in tile %d/%d/%d\n",
+ bucket_desc->source_layer.c_str(), tile.id.z, tile.id.x, tile.id.y);
+ }
+ }
+
+ return nullptr;
+}
+
+template <class Bucket>
+void TileParser::addBucketGeometries(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter) {
+ FilteredVectorTileLayer filtered_layer(layer, filter);
+ for (pbf feature : filtered_layer) {
+ if (obsolete())
+ return;
+
+ while (feature.next(4)) { // geometry
+ pbf geometry_pbf = feature.message();
+ if (geometry_pbf) {
+ bucket->addGeometry(geometry_pbf);
+ } else if (debug::tileParseWarnings) {
+ fprintf(stderr, "[WARNING] geometry is empty\n");
+ }
+ }
+ }
+}
+
+std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill) {
+ std::unique_ptr<FillBucket> bucket = util::make_unique<FillBucket>(tile.fillVertexBuffer, tile.triangleElementsBuffer, tile.lineElementsBuffer, fill);
+ addBucketGeometries(bucket, layer, filter);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+std::unique_ptr<Bucket> TileParser::createRasterBucket(const StyleBucketRaster &raster) {
+ std::unique_ptr<RasterBucket> bucket = util::make_unique<RasterBucket>(texturePool, raster);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line) {
+ std::unique_ptr<LineBucket> bucket = util::make_unique<LineBucket>(tile.lineVertexBuffer, tile.triangleElementsBuffer, tile.pointElementsBuffer, line);
+ addBucketGeometries(bucket, layer, filter);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+std::unique_ptr<Bucket> TileParser::createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol) {
+ std::unique_ptr<SymbolBucket> bucket = util::make_unique<SymbolBucket>(symbol, *collision);
+ bucket->addFeatures(layer, filter, tile.id, spriteAtlas, *sprite, glyphAtlas, glyphStore);
+ return obsolete() ? nullptr : std::move(bucket);
+}
+
+}
diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp
new file mode 100644
index 0000000000..beae3af831
--- /dev/null
+++ b/src/mbgl/map/tile_parser.hpp
@@ -0,0 +1,77 @@
+#ifndef MBGL_MAP_TILE_PARSER
+#define MBGL_MAP_TILE_PARSER
+
+#include <mbgl/map/vector_tile.hpp>
+#include <mbgl/style/filter_expression.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/util/ptr.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <cstdint>
+#include <iosfwd>
+#include <string>
+
+namespace mbgl {
+
+class Bucket;
+class TexturePool;
+class FontStack;
+class GlyphAtlas;
+class GlyphStore;
+class SpriteAtlas;
+class Sprite;
+class Style;
+class StyleBucket;
+class StyleBucketFill;
+class StyleBucketRaster;
+class StyleBucketLine;
+class StyleBucketSymbol;
+class StyleLayerGroup;
+class VectorTileData;
+class Collision;
+class TexturePool;
+
+class TileParser : private util::noncopyable
+{
+public:
+ TileParser(const std::string &data, VectorTileData &tile,
+ const util::ptr<const Style> &style,
+ GlyphAtlas & glyphAtlas,
+ GlyphStore & glyphStore,
+ SpriteAtlas & spriteAtlas,
+ const util::ptr<Sprite> &sprite,
+ TexturePool& texturePool);
+ ~TileParser();
+
+public:
+ void parse();
+
+private:
+ bool obsolete() const;
+ void parseStyleLayers(util::ptr<StyleLayerGroup> group);
+ std::unique_ptr<Bucket> createBucket(util::ptr<StyleBucket> bucket_desc);
+
+ std::unique_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill);
+ std::unique_ptr<Bucket> createRasterBucket(const StyleBucketRaster &raster);
+ std::unique_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line);
+ std::unique_ptr<Bucket> createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol);
+
+ template <class Bucket> void addBucketGeometries(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter);
+
+private:
+ const VectorTile vector_data;
+ VectorTileData& tile;
+
+ // Cross-thread shared data.
+ util::ptr<const Style> style;
+ GlyphAtlas & glyphAtlas;
+ GlyphStore & glyphStore;
+ SpriteAtlas & spriteAtlas;
+ util::ptr<Sprite> sprite;
+ TexturePool& texturePool;
+
+ std::unique_ptr<Collision> collision;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
new file mode 100644
index 0000000000..d05d1f7446
--- /dev/null
+++ b/src/mbgl/map/transform.cpp
@@ -0,0 +1,472 @@
+#include <mbgl/map/transform.hpp>
+#include <mbgl/map/view.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/transition.hpp>
+#include <mbgl/platform/platform.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+const double D2R = M_PI / 180.0;
+const double M2PI = 2 * M_PI;
+
+Transform::Transform(View &view_)
+ : view(view_)
+{
+}
+
+#pragma mark - Map View
+
+bool Transform::resize(const uint16_t w, const uint16_t h, const float ratio,
+ const uint16_t fb_w, const uint16_t fb_h) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ if (final.width != w || final.height != h || final.pixelRatio != ratio ||
+ final.framebuffer[0] != fb_w || final.framebuffer[1] != fb_h) {
+
+ view.notify_map_change(MapChangeRegionWillChange);
+
+ current.width = final.width = w;
+ current.height = final.height = h;
+ current.pixelRatio = final.pixelRatio = ratio;
+ current.framebuffer[0] = final.framebuffer[0] = fb_w;
+ current.framebuffer[1] = final.framebuffer[1] = fb_h;
+ constrain(current.scale, current.y);
+
+ view.notify_map_change(MapChangeRegionDidChange);
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+#pragma mark - Position
+
+void Transform::moveBy(const double dx, const double dy, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _moveBy(dx, dy, duration);
+}
+
+void Transform::_moveBy(const double dx, const double dy, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ final.x = current.x + std::cos(current.angle) * dx + std::sin(current.angle) * dy;
+ final.y = current.y + std::cos(current.angle) * dy + std::sin(-current.angle) * dx;
+
+ constrain(final.scale, final.y);
+
+ if (duration == 0) {
+ current.x = final.x;
+ current.y = final.y;
+ } else {
+ // Use a common start time for all of the transitions to avoid divergent transitions.
+ timestamp start = util::now();
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
+ }
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+void Transform::setLonLat(const double lon, const double lat, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ const double f = std::fmin(std::fmax(std::sin(D2R * lat), -0.9999), 0.9999);
+ double xn = -lon * Bc;
+ double yn = 0.5 * Cc * std::log((1 + f) / (1 - f));
+
+ _setScaleXY(current.scale, xn, yn, duration);
+}
+
+void Transform::setLonLatZoom(const double lon, const double lat, const double zoom,
+ const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double new_scale = std::pow(2.0, zoom);
+
+ const double s = new_scale * util::tileSize;
+ Bc = s / 360;
+ Cc = s / (2 * M_PI);
+
+ const double f = std::fmin(std::fmax(std::sin(D2R * lat), -0.9999), 0.9999);
+ double xn = -lon * Bc;
+ double yn = 0.5 * Cc * log((1 + f) / (1 - f));
+
+ _setScaleXY(new_scale, xn, yn, duration);
+}
+
+void Transform::getLonLat(double &lon, double &lat) const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ final.getLonLat(lon, lat);
+}
+
+void Transform::getLonLatZoom(double &lon, double &lat, double &zoom) const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ getLonLat(lon, lat);
+ zoom = getZoom();
+}
+
+void Transform::startPanning() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearPanning();
+
+ // Add a 200ms timeout for resetting this to false
+ current.panning = true;
+ timestamp start = util::now();
+ pan_timeout = std::make_shared<util::timeout<bool>>(false, current.panning, start, 200_milliseconds);
+ transitions.emplace_front(pan_timeout);
+}
+
+void Transform::stopPanning() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearPanning();
+}
+
+void Transform::_clearPanning() {
+ current.panning = false;
+ if (pan_timeout) {
+ transitions.remove(pan_timeout);
+ pan_timeout.reset();
+ }
+}
+
+#pragma mark - Zoom
+
+void Transform::scaleBy(const double ds, const double cx, const double cy, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ // clamp scale to min/max values
+ double new_scale = current.scale * ds;
+ if (new_scale < min_scale) {
+ new_scale = min_scale;
+ } else if (new_scale > max_scale) {
+ new_scale = max_scale;
+ }
+
+ _setScale(new_scale, cx, cy, duration);
+}
+
+void Transform::setScale(const double scale, const double cx, const double cy,
+ const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setScale(scale, cx, cy, duration);
+}
+
+void Transform::setZoom(const double zoom, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setScale(std::pow(2.0, zoom), -1, -1, duration);
+}
+
+double Transform::getZoom() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return std::log(final.scale) / M_LN2;
+}
+
+double Transform::getScale() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final.scale;
+}
+
+void Transform::startScaling() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearScaling();
+
+ // Add a 200ms timeout for resetting this to false
+ current.scaling = true;
+ timestamp start = util::now();
+ scale_timeout = std::make_shared<util::timeout<bool>>(false, current.scaling, start, 200_milliseconds);
+ transitions.emplace_front(scale_timeout);
+}
+
+void Transform::stopScaling() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearScaling();
+}
+
+double Transform::getMinZoom() const {
+ double test_scale = current.scale;
+ double test_y = current.y;
+ constrain(test_scale, test_y);
+
+ return std::log2(std::fmin(min_scale, test_scale));
+}
+
+double Transform::getMaxZoom() const {
+ return std::log2(max_scale);
+}
+
+void Transform::_clearScaling() {
+ // This is only called internally, so we don't need a lock here.
+
+ current.scaling = false;
+ if (scale_timeout) {
+ transitions.remove(scale_timeout);
+ scale_timeout.reset();
+ }
+}
+
+void Transform::_setScale(double new_scale, double cx, double cy, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ // Ensure that we don't zoom in further than the maximum allowed.
+ if (new_scale < min_scale) {
+ new_scale = min_scale;
+ } else if (new_scale > max_scale) {
+ new_scale = max_scale;
+ }
+
+ // Zoom in on the center if we don't have click or gesture anchor coordinates.
+ if (cx < 0 || cy < 0) {
+ cx = current.width / 2;
+ cy = current.height / 2;
+ }
+
+ // Account for the x/y offset from the center (= where the user clicked or pinched)
+ const double factor = new_scale / current.scale;
+ const double dx = (cx - current.width / 2) * (1.0 - factor);
+ const double dy = (cy - current.height / 2) * (1.0 - factor);
+
+ // Account for angle
+ const double angle_sin = std::sin(-current.angle);
+ const double angle_cos = std::cos(-current.angle);
+ const double ax = angle_cos * dx - angle_sin * dy;
+ const double ay = angle_sin * dx + angle_cos * dy;
+
+ const double xn = current.x * factor + ax;
+ const double yn = current.y * factor + ay;
+
+ _setScaleXY(new_scale, xn, yn, duration);
+}
+
+void Transform::_setScaleXY(const double new_scale, const double xn, const double yn,
+ const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ final.scale = new_scale;
+ final.x = xn;
+ final.y = yn;
+
+ constrain(final.scale, final.y);
+
+ if (duration == 0) {
+ current.scale = final.scale;
+ current.x = final.x;
+ current.y = final.y;
+ } else {
+ // Use a common start time for all of the transitions to avoid divergent transitions.
+ timestamp start = util::now();
+ transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
+ current.scale, final.scale, current.scale, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.x, final.x, current.x, start, duration));
+ transitions.emplace_front(
+ std::make_shared<util::ease_transition<double>>(current.y, final.y, current.y, start, duration));
+ }
+
+ const double s = final.scale * util::tileSize;
+ Bc = s / 360;
+ Cc = s / (2 * M_PI);
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+#pragma mark - Constraints
+
+void Transform::constrain(double& scale, double& y) const {
+ // Constrain minimum zoom to avoid zooming out far enough to show off-world areas.
+ if (scale < (current.height / util::tileSize)) scale = (current.height / util::tileSize);
+
+ // Constrain min/max vertical pan to avoid showing off-world areas.
+ double max_y = ((scale * util::tileSize) - current.height) / 2;
+
+ if (y > max_y) y = max_y;
+ if (y < -max_y) y = -max_y;
+}
+
+#pragma mark - Angle
+
+void Transform::rotateBy(const double start_x, const double start_y, const double end_x,
+ const double end_y, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double center_x = current.width / 2, center_y = current.height / 2;
+
+ const double begin_center_x = start_x - center_x;
+ const double begin_center_y = start_y - center_y;
+
+ const double beginning_center_dist =
+ std::sqrt(begin_center_x * begin_center_x + begin_center_y * begin_center_y);
+
+ // If the first click was too close to the center, move the center of rotation by 200 pixels
+ // in the direction of the click.
+ if (beginning_center_dist < 200) {
+ const double offset_x = -200, offset_y = 0;
+ const double rotate_angle = std::atan2(begin_center_y, begin_center_x);
+ const double rotate_angle_sin = std::sin(rotate_angle);
+ const double rotate_angle_cos = std::cos(rotate_angle);
+ center_x = start_x + rotate_angle_cos * offset_x - rotate_angle_sin * offset_y;
+ center_y = start_y + rotate_angle_sin * offset_x + rotate_angle_cos * offset_y;
+ }
+
+ const double first_x = start_x - center_x, first_y = start_y - center_y;
+ const double second_x = end_x - center_x, second_y = end_y - center_y;
+
+ const double ang = current.angle + util::angle_between(first_x, first_y, second_x, second_y);
+
+ _setAngle(ang, duration);
+}
+
+void Transform::setAngle(const double new_angle, const timestamp duration) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _setAngle(new_angle, duration);
+}
+
+void Transform::setAngle(const double new_angle, const double cx, const double cy) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ double dx = 0, dy = 0;
+
+ if (cx >= 0 && cy >= 0) {
+ dx = (final.width / 2) - cx;
+ dy = (final.height / 2) - cy;
+ _moveBy(dx, dy, 0);
+ }
+
+ _setAngle(new_angle, 0);
+
+ if (cx >= 0 && cy >= 0) {
+ _moveBy(-dx, -dy, 0);
+ }
+}
+
+void Transform::_setAngle(double new_angle, const timestamp duration) {
+ // This is only called internally, so we don't need a lock here.
+
+ view.notify_map_change(duration ?
+ MapChangeRegionWillChangeAnimated :
+ MapChangeRegionWillChange);
+
+ while (new_angle > M_PI)
+ new_angle -= M2PI;
+ while (new_angle <= -M_PI)
+ new_angle += M2PI;
+
+ final.angle = new_angle;
+
+ if (duration == 0) {
+ current.angle = final.angle;
+ } else {
+ timestamp start = util::now();
+ transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
+ current.angle, final.angle, current.angle, start, duration));
+ }
+
+ view.notify_map_change(duration ?
+ MapChangeRegionDidChangeAnimated :
+ MapChangeRegionDidChange,
+ duration);
+}
+
+double Transform::getAngle() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final.angle;
+}
+
+void Transform::startRotating() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearRotating();
+
+ // Add a 200ms timeout for resetting this to false
+ current.rotating = true;
+ timestamp start = util::now();
+ rotate_timeout = std::make_shared<util::timeout<bool>>(false, current.rotating, start, 200_milliseconds);
+ transitions.emplace_front(rotate_timeout);
+}
+
+void Transform::stopRotating() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ _clearRotating();
+}
+
+void Transform::_clearRotating() {
+ // This is only called internally, so we don't need a lock here.
+
+ current.rotating = false;
+ if (rotate_timeout) {
+ transitions.remove(rotate_timeout);
+ rotate_timeout.reset();
+ }
+}
+
+#pragma mark - Transition
+
+bool Transform::needsTransition() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return !transitions.empty();
+}
+
+void Transform::updateTransitions(const timestamp now) {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ transitions.remove_if([now](const util::ptr<util::transition> &transition) {
+ return transition->update(now) == util::transition::complete;
+ });
+}
+
+void Transform::cancelTransitions() {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ transitions.clear();
+}
+
+#pragma mark - Transform state
+
+const TransformState Transform::currentState() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return current;
+}
+
+const TransformState Transform::finalState() const {
+ std::lock_guard<std::recursive_mutex> lock(mtx);
+
+ return final;
+}
diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp
new file mode 100644
index 0000000000..a7da8ccab2
--- /dev/null
+++ b/src/mbgl/map/transform_state.cpp
@@ -0,0 +1,172 @@
+#include <mbgl/map/transform_state.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/box.hpp>
+
+using namespace mbgl;
+
+const double R2D = 180.0 / M_PI;
+
+#pragma mark - Matrix
+
+void TransformState::matrixFor(mat4& matrix, const Tile::ID& id) const {
+ const double tile_scale = std::pow(2, id.z);
+ const double tile_size = scale * util::tileSize / tile_scale;
+
+ matrix::identity(matrix);
+
+ matrix::translate(matrix, matrix, 0.5f * (float)width, 0.5f * (float)height, 0);
+ matrix::rotate_z(matrix, matrix, angle);
+ matrix::translate(matrix, matrix, -0.5f * (float)width, -0.5f * (float)height, 0);
+
+ matrix::translate(matrix, matrix, pixel_x() + id.x * tile_size, pixel_y() + id.y * tile_size, 0);
+
+ // TODO: Get rid of the 8 (scaling from 4096 to tile size);
+ float factor = scale / tile_scale / (4096.0f / util::tileSize);
+ matrix::scale(matrix, matrix, factor, factor, 1);
+}
+
+box TransformState::cornersToBox(uint32_t z) const {
+ const double ref_scale = std::pow(2, z);
+
+ const double angle_sin = std::sin(-angle);
+ const double angle_cos = std::cos(-angle);
+
+ const double w_2 = width / 2;
+ const double h_2 = height / 2;
+ const double ss_0 = scale * util::tileSize;
+ const double ss_1 = ref_scale / ss_0;
+ const double ss_2 = ss_0 / 2.0;
+
+ // Calculate the corners of the map view. The resulting coordinates will be
+ // in fractional tile coordinates.
+ box b;
+
+ b.tl.x = ((-w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.tl.y = ((-w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.tr.x = ((+w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.tr.y = ((+w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.bl.x = ((-w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.bl.y = ((-w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.br.x = ((+w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
+ b.br.y = ((+w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
+
+ b.center.x = (ss_2 - x) * ss_1;
+ b.center.y = (ss_2 - y) * ss_1;
+
+ return b;
+}
+
+
+#pragma mark - Dimensions
+
+bool TransformState::hasSize() const {
+ return width && height;
+}
+
+uint16_t TransformState::getWidth() const {
+ return width;
+}
+
+uint16_t TransformState::getHeight() const {
+ return height;
+}
+
+uint16_t TransformState::getFramebufferWidth() const {
+ return framebuffer[0];
+}
+
+uint16_t TransformState::getFramebufferHeight() const {
+ return framebuffer[1];
+}
+
+const std::array<uint16_t, 2> TransformState::getFramebufferDimensions() const {
+ return framebuffer;
+}
+
+float TransformState::getPixelRatio() const {
+ return pixelRatio;
+}
+
+float TransformState::worldSize() const {
+ return scale * util::tileSize;
+}
+
+float TransformState::lngX(float lon) const {
+ return (180 + lon) * worldSize() / 360;
+}
+
+float TransformState::latY(float lat) const {
+ float lat_y = 180 / M_PI * std::log(std::tan(M_PI / 4 + lat * M_PI / 360));
+ return (180 - lat_y) * worldSize() / 360;
+}
+
+std::array<float, 2> TransformState::locationCoordinate(float lon, float lat) const {
+ float k = std::pow(2, getIntegerZoom()) / worldSize();
+ return {{
+ lngX(lon) * k,
+ latY(lat) * k
+ }};
+}
+
+void TransformState::getLonLat(double &lon, double &lat) const {
+ const double s = scale * util::tileSize;
+ const double Bc = s / 360;
+ const double Cc = s / (2 * M_PI);
+
+ lon = -x / Bc;
+ lat = R2D * (2 * std::atan(std::exp(y / Cc)) - 0.5 * M_PI);
+}
+
+
+#pragma mark - Zoom
+
+float TransformState::getNormalizedZoom() const {
+ return std::log(scale * util::tileSize / 512.0f) / M_LN2;
+}
+
+double TransformState::getZoom() const {
+ return std::log(scale) / M_LN2;
+}
+
+int32_t TransformState::getIntegerZoom() const {
+ return std::floor(getZoom());
+}
+
+double TransformState::getZoomFraction() const {
+ return getZoom() - getIntegerZoom();
+}
+
+double TransformState::getScale() const {
+ return scale;
+}
+
+
+#pragma mark - Rotation
+
+float TransformState::getAngle() const {
+ return angle;
+}
+
+
+#pragma mark - Changing
+
+bool TransformState::isChanging() const {
+ return rotating || scaling || panning;
+}
+
+
+#pragma mark - (private helper functions)
+
+
+double TransformState::pixel_x() const {
+ const double center = (width - scale * util::tileSize) / 2;
+ return center + x;
+}
+
+double TransformState::pixel_y() const {
+ const double center = (height - scale * util::tileSize) / 2;
+ return center + y;
+}
diff --git a/src/mbgl/map/vector_tile.cpp b/src/mbgl/map/vector_tile.cpp
new file mode 100644
index 0000000000..ac7134fb0c
--- /dev/null
+++ b/src/mbgl/map/vector_tile.cpp
@@ -0,0 +1,214 @@
+#include <mbgl/map/vector_tile.hpp>
+#include <mbgl/style/filter_expression_private.hpp>
+
+#include <algorithm>
+#include <iostream>
+
+using namespace mbgl;
+
+
+std::ostream& mbgl::operator<<(std::ostream& os, const FeatureType& type) {
+ switch (type) {
+ case FeatureType::Unknown: return os << "Unknown";
+ case FeatureType::Point: return os << "Point";
+ case FeatureType::LineString: return os << "LineString";
+ case FeatureType::Polygon: return os << "Polygon";
+ default: return os << "Invalid";
+ }
+}
+
+VectorTileFeature::VectorTileFeature(pbf feature, const VectorTileLayer& layer) {
+ while (feature.next()) {
+ if (feature.tag == 1) { // id
+ id = feature.varint<uint64_t>();
+ } else if (feature.tag == 2) { // tags
+ // tags are packed varints. They should have an even length.
+ pbf tags = feature.message();
+ while (tags) {
+ uint32_t tag_key = tags.varint();
+
+ if (layer.keys.size() <= tag_key) {
+ throw std::runtime_error("feature referenced out of range key");
+ }
+
+ if (tags) {
+ uint32_t tag_val = tags.varint();
+ if (layer.values.size() <= tag_val) {
+ throw std::runtime_error("feature referenced out of range value");
+ }
+
+ properties.emplace(layer.keys[tag_key], layer.values[tag_val]);
+ } else {
+ throw std::runtime_error("uneven number of feature tag ids");
+ }
+ }
+ } else if (feature.tag == 3) { // type
+ type = (FeatureType)feature.varint();
+ } else if (feature.tag == 4) { // geometry
+ geometry = feature.message();
+ } else {
+ feature.skip();
+ }
+ }
+}
+
+
+std::ostream& mbgl::operator<<(std::ostream& os, const VectorTileFeature& feature) {
+ os << "Feature(" << feature.id << "): " << feature.type << std::endl;
+ for (const auto& prop : feature.properties) {
+ os << " - " << prop.first << ": " << prop.second << std::endl;
+ }
+ return os;
+}
+
+
+VectorTile::VectorTile() {}
+
+
+VectorTile::VectorTile(pbf tile) {
+ while (tile.next()) {
+ if (tile.tag == 3) { // layer
+ VectorTileLayer layer(tile.message());
+ layers.emplace(layer.name, std::forward<VectorTileLayer>(layer));
+ } else {
+ tile.skip();
+ }
+ }
+}
+
+VectorTile& VectorTile::operator=(VectorTile && other) {
+ if (this != &other) {
+ layers.swap(other.layers);
+ }
+ return *this;
+}
+
+VectorTileLayer::VectorTileLayer(pbf layer) : data(layer) {
+ std::vector<std::string> stacks;
+
+ while (layer.next()) {
+ if (layer.tag == 1) { // name
+ name = layer.string();
+ } else if (layer.tag == 3) { // keys
+ keys.emplace_back(layer.string());
+ key_index.emplace(keys.back(), keys.size() - 1);
+ } else if (layer.tag == 4) { // values
+ values.emplace_back(std::move(parseValue(layer.message())));
+ } else if (layer.tag == 5) { // extent
+ extent = layer.varint();
+ } else {
+ layer.skip();
+ }
+ }
+}
+
+FilteredVectorTileLayer::FilteredVectorTileLayer(const VectorTileLayer& layer_, const FilterExpression &filterExpression_)
+ : layer(layer_),
+ filterExpression(filterExpression_) {
+}
+
+FilteredVectorTileLayer::iterator FilteredVectorTileLayer::begin() const {
+ return iterator(*this, layer.data);
+}
+
+FilteredVectorTileLayer::iterator FilteredVectorTileLayer::end() const {
+ return iterator(*this, pbf(layer.data.end, 0));
+}
+
+FilteredVectorTileLayer::iterator::iterator(const FilteredVectorTileLayer& parent_, const pbf& data_)
+ : parent(parent_),
+ feature(pbf()),
+ data(data_) {
+ operator++();
+}
+
+VectorTileTagExtractor::VectorTileTagExtractor(const VectorTileLayer &layer) : layer_(layer) {}
+
+
+void VectorTileTagExtractor::setTags(const pbf &pbf) {
+ tags_ = pbf;
+}
+
+mapbox::util::optional<Value> VectorTileTagExtractor::getValue(const std::string &key) const {
+ if (key == "$type") {
+ return Value(uint64_t(type_));
+ }
+
+ mapbox::util::optional<Value> value;
+
+ auto field_it = layer_.key_index.find(key);
+ if (field_it != layer_.key_index.end()) {
+ const uint32_t filter_key = field_it->second;
+
+ // Now loop through all the key/value pair tags.
+ // tags are packed varints. They should have an even length.
+ pbf tags_pbf = tags_;
+ uint32_t tag_key, tag_val;
+ while (tags_pbf) {
+ tag_key = tags_pbf.varint();
+ if (!tags_pbf) {
+ // This should not happen; otherwise the vector tile is invalid.
+ fprintf(stderr, "[WARNING] uneven number of feature tag ids\n");
+ return value;
+ }
+ // Note: We need to run this command in all cases, even if the keys don't match.
+ tag_val = tags_pbf.varint();
+
+ if (tag_key == filter_key) {
+ if (layer_.values.size() > tag_val) {
+ value = layer_.values[tag_val];
+ } else {
+ fprintf(stderr, "[WARNING] feature references out of range value\n");
+ break;
+ }
+ }
+ }
+ }
+
+ return value;
+}
+
+void VectorTileTagExtractor::setType(FeatureType type) {
+ type_ = type;
+}
+
+template bool mbgl::evaluate(const FilterExpression&, const VectorTileTagExtractor&);
+
+void FilteredVectorTileLayer::iterator::operator++() {
+ valid = false;
+
+ const FilterExpression &expression = parent.filterExpression;
+
+ while (data.next(2)) { // feature
+ feature = data.message();
+ pbf feature_pbf = feature;
+
+ VectorTileTagExtractor extractor(parent.layer);
+
+ // Retrieve the basic information
+ while (feature_pbf.next()) {
+ if (feature_pbf.tag == 2) { // tags
+ extractor.setTags(feature_pbf.message());
+ } else if (feature_pbf.tag == 3) { // geometry type
+ extractor.setType(FeatureType(feature_pbf.varint()));
+ } else {
+ feature_pbf.skip();
+ }
+ }
+
+ if (evaluate(expression, extractor)) {
+ valid = true;
+ return; // data loop
+ } else {
+ valid = false;
+ }
+ }
+}
+
+bool FilteredVectorTileLayer::iterator::operator!=(const iterator& other) const {
+ return !(data.data == other.data.data && data.end == other.data.end && valid == other.valid);
+}
+
+const pbf& FilteredVectorTileLayer::iterator:: operator*() const {
+ return feature;
+}
diff --git a/src/mbgl/map/vector_tile.hpp b/src/mbgl/map/vector_tile.hpp
new file mode 100644
index 0000000000..2d02ba3a0b
--- /dev/null
+++ b/src/mbgl/map/vector_tile.hpp
@@ -0,0 +1,118 @@
+#ifndef MBGL_MAP_VECTOR_TILE
+#define MBGL_MAP_VECTOR_TILE
+
+#include <mbgl/style/filter_expression.hpp>
+#include <mbgl/style/value.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/util/pbf.hpp>
+#include <mbgl/util/optional.hpp>
+
+#include <cstdint>
+#include <iosfwd>
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace mbgl {
+
+class VectorTileLayer;
+
+enum class FeatureType {
+ Unknown = 0,
+ Point = 1,
+ LineString = 2,
+ Polygon = 3
+};
+
+std::ostream& operator<<(std::ostream&, const FeatureType& type);
+
+class VectorTileFeature {
+public:
+ VectorTileFeature(pbf feature, const VectorTileLayer& layer);
+
+ uint64_t id = 0;
+ FeatureType type = FeatureType::Unknown;
+ std::map<std::string, Value> properties;
+ pbf geometry;
+};
+
+std::ostream& operator<<(std::ostream&, const VectorTileFeature& feature);
+
+
+class VectorTileTagExtractor {
+public:
+ VectorTileTagExtractor(const VectorTileLayer &layer);
+
+ void setTags(const pbf &pbf);
+ mapbox::util::optional<Value> getValue(const std::string &key) const;
+ void setType(FeatureType type);
+ FeatureType getType() const;
+
+private:
+ const VectorTileLayer &layer_;
+ pbf tags_;
+ FeatureType type_ = FeatureType::Unknown;
+};
+
+/*
+ * Allows iterating over the features of a VectorTileLayer using a
+ * BucketDescription as filter. Only features matching the descriptions will
+ * be returned (as pbf).
+ */
+class FilteredVectorTileLayer {
+public:
+ class iterator {
+ public:
+ iterator(const FilteredVectorTileLayer& filter, const pbf& data);
+ void operator++();
+ bool operator!=(const iterator& other) const;
+ const pbf& operator*() const;
+
+ private:
+ const FilteredVectorTileLayer& parent;
+ bool valid = false;
+ pbf feature;
+ pbf data;
+ };
+
+public:
+ FilteredVectorTileLayer(const VectorTileLayer& layer, const FilterExpression &filterExpression);
+
+ iterator begin() const;
+ iterator end() const;
+
+private:
+ const VectorTileLayer& layer;
+ const FilterExpression& filterExpression;
+};
+
+std::ostream& operator<<(std::ostream&, const PositionedGlyph& placement);
+
+class VectorTileLayer {
+public:
+ VectorTileLayer(pbf data);
+
+ const pbf data;
+ std::string name;
+ uint32_t extent = 4096;
+ std::vector<std::string> keys;
+ std::unordered_map<std::string, uint32_t> key_index;
+ std::vector<Value> values;
+ std::map<std::string, std::map<Value, Shaping>> shaping;
+};
+
+class VectorTile {
+public:
+ VectorTile();
+ VectorTile(pbf data);
+ VectorTile& operator=(VectorTile&& other);
+
+ std::map<std::string, const VectorTileLayer> layers;
+};
+
+
+
+}
+
+#endif
diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp
new file mode 100644
index 0000000000..06782057f6
--- /dev/null
+++ b/src/mbgl/map/vector_tile_data.cpp
@@ -0,0 +1,78 @@
+#include <mbgl/map/vector_tile_data.hpp>
+#include <mbgl/map/tile_parser.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/geometry/glyph_atlas.hpp>
+
+using namespace mbgl;
+
+VectorTileData::VectorTileData(Tile::ID const& id_,
+ float mapMaxZoom, util::ptr<Style> style_,
+ GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_,
+ SpriteAtlas& spriteAtlas_, util::ptr<Sprite> sprite_,
+ TexturePool& texturePool_,
+ const SourceInfo& source_)
+ : TileData(id_, source_),
+ glyphAtlas(glyphAtlas_),
+ glyphStore(glyphStore_),
+ spriteAtlas(spriteAtlas_),
+ sprite(sprite_),
+ texturePool(texturePool_),
+ style(style_),
+ depth(id.z >= source.max_zoom ? mapMaxZoom - id.z : 1) {
+}
+
+VectorTileData::~VectorTileData() {
+ glyphAtlas.removeGlyphs(id.to_uint64());
+}
+
+
+void VectorTileData::parse() {
+ if (state != State::loaded) {
+ return;
+ }
+
+ try {
+ // Parsing creates state that is encapsulated in TileParser. While parsing,
+ // the TileParser object writes results into this objects. All other state
+ // is going to be discarded afterwards.
+ TileParser parser(data, *this, style,
+ glyphAtlas, glyphStore,
+ spriteAtlas, sprite,
+ texturePool);
+ parser.parse();
+ } catch (const std::exception& ex) {
+#if defined(DEBUG)
+ fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
+#endif
+ cancel();
+ return;
+ }
+
+ if (state != State::obsolete) {
+ state = State::parsed;
+ }
+}
+
+void VectorTileData::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) {
+ if (state == State::parsed && layer_desc->bucket) {
+ auto databucket_it = buckets.find(layer_desc->bucket->name);
+ if (databucket_it != buckets.end()) {
+ assert(databucket_it->second);
+ databucket_it->second->render(painter, layer_desc, id, matrix);
+ }
+ }
+}
+
+bool VectorTileData::hasData(StyleLayer const& layer_desc) const {
+ if (state == State::parsed && layer_desc.bucket) {
+ auto databucket_it = buckets.find(layer_desc.bucket->name);
+ if (databucket_it != buckets.end()) {
+ assert(databucket_it->second);
+ return databucket_it->second->hasData();
+ }
+ }
+ return false;
+}
diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp
new file mode 100644
index 0000000000..b9bf55a1b3
--- /dev/null
+++ b/src/mbgl/map/vector_tile_data.hpp
@@ -0,0 +1,74 @@
+#ifndef MBGL_MAP_VECTOR_TILE_DATA
+#define MBGL_MAP_VECTOR_TILE_DATA
+
+#include <mbgl/map/tile.hpp>
+#include <mbgl/map/tile_data.hpp>
+#include <mbgl/geometry/elements_buffer.hpp>
+#include <mbgl/geometry/fill_buffer.hpp>
+#include <mbgl/geometry/icon_buffer.hpp>
+#include <mbgl/geometry/line_buffer.hpp>
+#include <mbgl/geometry/text_buffer.hpp>
+
+#include <iosfwd>
+#include <memory>
+#include <unordered_map>
+
+namespace mbgl {
+
+class Bucket;
+class Painter;
+class SourceInfo;
+class StyleLayer;
+class TileParser;
+class GlyphAtlas;
+class GlyphStore;
+class SpriteAtlas;
+class Sprite;
+class TexturePool;
+class Style;
+
+class VectorTileData : public TileData {
+ friend class TileParser;
+
+public:
+ VectorTileData(Tile::ID const&,
+ float mapMaxZoom, util::ptr<Style>,
+ GlyphAtlas&, GlyphStore&,
+ SpriteAtlas&, util::ptr<Sprite>,
+ TexturePool&,
+ const SourceInfo&);
+ ~VectorTileData();
+
+ virtual void parse();
+ virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const mat4 &matrix);
+ virtual bool hasData(StyleLayer const& layer_desc) const;
+
+protected:
+ // Holds the actual geometries in this tile.
+ FillVertexBuffer fillVertexBuffer;
+ LineVertexBuffer lineVertexBuffer;
+ IconVertexBuffer iconVertexBuffer;
+ TextVertexBuffer textVertexBuffer;
+
+ TriangleElementsBuffer triangleElementsBuffer;
+ LineElementsBuffer lineElementsBuffer;
+ PointElementsBuffer pointElementsBuffer;
+
+ // Holds the buckets of this tile.
+ // They contain the location offsets in the buffers stored above
+ std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
+
+ GlyphAtlas& glyphAtlas;
+ GlyphStore& glyphStore;
+ SpriteAtlas& spriteAtlas;
+ util::ptr<Sprite> sprite;
+ TexturePool& texturePool;
+ util::ptr<Style> style;
+
+public:
+ const float depth;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/platform/gl.cpp b/src/mbgl/platform/gl.cpp
new file mode 100644
index 0000000000..155fd70d95
--- /dev/null
+++ b/src/mbgl/platform/gl.cpp
@@ -0,0 +1,101 @@
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <iostream>
+
+
+namespace mbgl {
+namespace gl {
+
+PFNGLDEBUGMESSAGECONTROLPROC DebugMessageControl = nullptr;
+PFNGLDEBUGMESSAGEINSERTPROC DebugMessageInsert = nullptr;
+PFNGLDEBUGMESSAGECALLBACKPROC DebugMessageCallback = nullptr;
+PFNGLGETDEBUGMESSAGELOGPROC GetDebugMessageLog = nullptr;
+PFNGLGETPOINTERVPROC GetPointerv = nullptr;
+PFNGLPUSHDEBUGGROUPPROC PushDebugGroup = nullptr;
+PFNGLPOPDEBUGGROUPPROC PopDebugGroup = nullptr;
+PFNGLOBJECTLABELPROC ObjectLabel = nullptr;
+PFNGLGETOBJECTLABELPROC GetObjectLabel = nullptr;
+PFNGLOBJECTPTRLABELPROC ObjectPtrLabel = nullptr;
+PFNGLGETOBJECTPTRLABELPROC GetObjectPtrLabel = nullptr;
+
+void debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei, const GLchar *message, const void *) {
+ std::string strSource;
+ switch (source) {
+ case GL_DEBUG_SOURCE_API: strSource = "DEBUG_SOURCE_API"; break;
+ case GL_DEBUG_SOURCE_WINDOW_SYSTEM: strSource = "DEBUG_SOURCE_WINDOW_SYSTEM"; break;
+ case GL_DEBUG_SOURCE_SHADER_COMPILER: strSource = "DEBUG_SOURCE_SHADER_COMPILER"; break;
+ case GL_DEBUG_SOURCE_THIRD_PARTY: strSource = "DEBUG_SOURCE_THIRD_PARTY"; break;
+ case GL_DEBUG_SOURCE_APPLICATION: strSource = "DEBUG_SOURCE_APPLICATION"; break;
+ case GL_DEBUG_SOURCE_OTHER: strSource = "DEBUG_SOURCE_OTHER"; break;
+ default: strSource = "(unknown)"; break;
+ }
+
+ std::string strType;
+ switch (type) {
+ case GL_DEBUG_TYPE_ERROR: strType = "DEBUG_TYPE_ERROR"; break;
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: strType = "DEBUG_TYPE_DEPRECATED_BEHAVIOR"; break;
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: strType = "DEBUG_TYPE_UNDEFINED_BEHAVIOR"; break;
+ case GL_DEBUG_TYPE_PERFORMANCE: strType = "DEBUG_TYPE_PERFORMANCE"; break;
+ case GL_DEBUG_TYPE_PORTABILITY: strType = "DEBUG_TYPE_PORTABILITY"; break;
+ case GL_DEBUG_TYPE_OTHER: strType = "DEBUG_TYPE_OTHER"; break;
+ case GL_DEBUG_TYPE_MARKER: strType = "DEBUG_TYPE_MARKER"; break;
+ case GL_DEBUG_TYPE_PUSH_GROUP: strType = "DEBUG_TYPE_OTHER"; break;
+ case GL_DEBUG_TYPE_POP_GROUP: strType = "DEBUG_TYPE_POP_GROUP"; break;
+ default: strSource = "(unknown)"; break;
+ }
+
+ std::string strSeverity;
+ mbgl::EventSeverity evtSeverity;
+ switch (severity) {
+ case GL_DEBUG_SEVERITY_HIGH: strSeverity = "DEBUG_SEVERITY_HIGH"; evtSeverity = mbgl::EventSeverity::Error; break;
+ case GL_DEBUG_SEVERITY_MEDIUM: strSeverity = "DEBUG_SEVERITY_MEDIUM"; evtSeverity = mbgl::EventSeverity::Warning; break;
+ case GL_DEBUG_SEVERITY_LOW: strSeverity = "DEBUG_SEVERITY_LOW"; evtSeverity = mbgl::EventSeverity::Info; break;
+ case GL_DEBUG_SEVERITY_NOTIFICATION: strSeverity = "DEBUG_SEVERITY_NOTIFICATION"; evtSeverity = mbgl::EventSeverity::Debug; break;
+ default: strSource = "(unknown)"; evtSeverity = mbgl::EventSeverity::Debug; break;
+ }
+
+ mbgl::Log::Record(evtSeverity, mbgl::Event::OpenGL, "GL_%s GL_%s %u GL_%s - %s", strSource.c_str(), strType.c_str(), id, strSeverity.c_str(), message);
+}
+
+PFNGLINSERTEVENTMARKEREXTPROC InsertEventMarkerEXT = nullptr;
+PFNGLPUSHGROUPMARKEREXTPROC PushGroupMarkerEXT = nullptr;
+PFNGLPOPGROUPMARKEREXTPROC PopGroupMarkerEXT = nullptr;
+
+PFNGLLABELOBJECTEXTPROC LabelObjectEXT = nullptr;
+PFNGLGETOBJECTLABELEXTPROC GetObjectLabelEXT = nullptr;
+
+PFNGLBINDVERTEXARRAYPROC BindVertexArray = nullptr;
+PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays = nullptr;
+PFNGLGENVERTEXARRAYSPROC GenVertexArrays = nullptr;
+PFNGLISVERTEXARRAYPROC IsVertexArray = nullptr;
+
+// GL_OES_packed_depth_stencil
+bool isPackedDepthStencilSupported = false;
+
+// GL_OES_depth24
+bool isDepth24Supported = false;
+
+}
+}
+
+void _CHECK_GL_ERROR(const char *cmd, const char *file, int line) {
+ std::cout << cmd << ";" << std::endl;
+
+ GLenum err;
+
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ std::string error;
+ switch (err) {
+ case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break;
+ case GL_INVALID_ENUM: error = "INVALID_ENUM"; break;
+ case GL_INVALID_VALUE: error = "INVALID_VALUE"; break;
+ case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break;
+ case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break;
+ default: error = "(unknown)"; break;
+ }
+
+ mbgl::Log::Error(mbgl::Event::OpenGL, "GL_%s (0x%04X) - %s:%i", error.c_str(), file, line, err);
+ exit(1);
+ }
+}
diff --git a/src/mbgl/platform/log.cpp b/src/mbgl/platform/log.cpp
new file mode 100644
index 0000000000..b83c7a9322
--- /dev/null
+++ b/src/mbgl/platform/log.cpp
@@ -0,0 +1,7 @@
+#include <mbgl/platform/log.hpp>
+
+namespace mbgl {
+
+std::unique_ptr<LogBackend> Log::Backend;
+
+}
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
diff --git a/src/mbgl/shader/dot.fragment.glsl b/src/mbgl/shader/dot.fragment.glsl
new file mode 100644
index 0000000000..6d998b5611
--- /dev/null
+++ b/src/mbgl/shader/dot.fragment.glsl
@@ -0,0 +1,9 @@
+uniform vec4 u_color;
+uniform float u_blur;
+
+void main() {
+ float dist = length(gl_PointCoord - 0.5);
+ float t = smoothstep(0.5, 0.5 - u_blur, dist);
+
+ gl_FragColor = u_color * t;
+}
diff --git a/src/mbgl/shader/dot.vertex.glsl b/src/mbgl/shader/dot.vertex.glsl
new file mode 100644
index 0000000000..5310ae745e
--- /dev/null
+++ b/src/mbgl/shader/dot.vertex.glsl
@@ -0,0 +1,9 @@
+uniform mat4 u_matrix;
+uniform float u_size;
+
+attribute vec2 a_pos;
+
+void main(void) {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ gl_PointSize = u_size;
+}
diff --git a/src/mbgl/shader/dot_shader.cpp b/src/mbgl/shader/dot_shader.cpp
new file mode 100644
index 0000000000..a897f410a7
--- /dev/null
+++ b/src/mbgl/shader/dot_shader.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/shader/dot_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+DotShader::DotShader()
+: Shader(
+ "dot",
+ shaders[DOT_SHADER].vertex,
+ shaders[DOT_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid dot shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+}
+
+void DotShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset);
+}
diff --git a/src/mbgl/shader/dot_shader.hpp b/src/mbgl/shader/dot_shader.hpp
new file mode 100644
index 0000000000..2c4176f364
--- /dev/null
+++ b/src/mbgl/shader/dot_shader.hpp
@@ -0,0 +1,26 @@
+#ifndef MBGL_SHADER_SHADER_DOT
+#define MBGL_SHADER_SHADER_DOT
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class DotShader : public Shader {
+public:
+ DotShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ Uniform<std::array<float, 4>> u_color = {"u_color", *this};
+ Uniform<float> u_size = {"u_size", *this};
+ Uniform<float> u_blur = {"u_blur", *this};
+
+private:
+ int32_t a_pos = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/gaussian.fragment.glsl b/src/mbgl/shader/gaussian.fragment.glsl
new file mode 100644
index 0000000000..ee8406e9e1
--- /dev/null
+++ b/src/mbgl/shader/gaussian.fragment.glsl
@@ -0,0 +1,11 @@
+uniform sampler2D u_image;
+
+varying vec2 v_coords[3];
+
+void main() {
+ vec4 sum = vec4(0.0);
+ sum += texture2D(u_image, v_coords[0]) * 0.40261994689424746;
+ sum += texture2D(u_image, v_coords[1]) * 0.2986900265528763;
+ sum += texture2D(u_image, v_coords[2]) * 0.2986900265528763;
+ gl_FragColor = sum;
+}
diff --git a/src/mbgl/shader/gaussian.vertex.glsl b/src/mbgl/shader/gaussian.vertex.glsl
new file mode 100644
index 0000000000..26a8394204
--- /dev/null
+++ b/src/mbgl/shader/gaussian.vertex.glsl
@@ -0,0 +1,15 @@
+attribute vec2 a_pos;
+
+uniform mat4 u_matrix;
+uniform vec2 u_offset;
+
+varying vec2 v_coords[3];
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+
+ vec2 tex = gl_Position.xy / 2.0 + 0.5;
+ v_coords[0] = tex;
+ v_coords[1] = tex + u_offset * 1.1824255238063563;
+ v_coords[2] = tex - u_offset * 1.1824255238063563;
+}
diff --git a/src/mbgl/shader/gaussian_shader.cpp b/src/mbgl/shader/gaussian_shader.cpp
new file mode 100644
index 0000000000..9060f0ee71
--- /dev/null
+++ b/src/mbgl/shader/gaussian_shader.cpp
@@ -0,0 +1,28 @@
+#include <mbgl/shader/gaussian_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+GaussianShader::GaussianShader()
+ : Shader(
+ "gaussian",
+ shaders[GAUSSIAN_SHADER].vertex,
+ shaders[GAUSSIAN_SHADER].fragment
+ ) {
+ if (!valid) {
+#if defined(DEBUG)
+ fprintf(stderr, "invalid raster shader\n");
+#endif
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+}
+
+void GaussianShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
+}
diff --git a/src/mbgl/shader/gaussian_shader.hpp b/src/mbgl/shader/gaussian_shader.hpp
new file mode 100644
index 0000000000..0f494f5c7e
--- /dev/null
+++ b/src/mbgl/shader/gaussian_shader.hpp
@@ -0,0 +1,25 @@
+#ifndef MBGL_RENDERER_SHADER_GAUSSIAN
+#define MBGL_RENDERER_SHADER_GAUSSIAN
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class GaussianShader : public Shader {
+public:
+ GaussianShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ Uniform<std::array<float, 2>> u_offset = {"u_offset", *this};
+ Uniform<int32_t> u_image = {"u_image", *this};
+
+private:
+ int32_t a_pos = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/icon.fragment.glsl b/src/mbgl/shader/icon.fragment.glsl
new file mode 100644
index 0000000000..45b56793eb
--- /dev/null
+++ b/src/mbgl/shader/icon.fragment.glsl
@@ -0,0 +1,8 @@
+uniform sampler2D u_texture;
+
+varying vec2 v_tex;
+varying float v_alpha;
+
+void main() {
+ gl_FragColor = texture2D(u_texture, v_tex) * v_alpha;
+}
diff --git a/src/mbgl/shader/icon.vertex.glsl b/src/mbgl/shader/icon.vertex.glsl
new file mode 100644
index 0000000000..8c69c40410
--- /dev/null
+++ b/src/mbgl/shader/icon.vertex.glsl
@@ -0,0 +1,73 @@
+attribute vec2 a_pos;
+attribute vec2 a_offset;
+attribute vec2 a_tex;
+attribute float a_angle;
+attribute float a_minzoom;
+attribute float a_maxzoom;
+attribute float a_rangeend;
+attribute float a_rangestart;
+attribute float a_labelminzoom;
+
+
+// matrix is for the vertex position, exmatrix is for rotating and projecting
+// the extrusion vector.
+uniform mat4 u_matrix;
+uniform mat4 u_exmatrix;
+uniform float u_angle;
+uniform float u_zoom;
+uniform float u_flip;
+uniform float u_fadedist;
+uniform float u_minfadezoom;
+uniform float u_maxfadezoom;
+uniform float u_fadezoom;
+uniform float u_opacity;
+
+uniform vec2 u_texsize;
+
+varying vec2 v_tex;
+varying float v_alpha;
+
+void main() {
+
+ float a_fadedist = 10.0;
+ float rev = 0.0;
+
+ // u_angle is angle of the map, -128..128 representing 0..2PI
+ // a_angle is angle of the label, 0..256 representing 0..2PI, where 0 is horizontal text
+ float rotated = mod(a_angle + u_angle, 256.0);
+ // if the label rotates with the map, and if the rotated label is upside down, hide it
+ if (u_flip > 0.0 && rotated >= 64.0 && rotated < 192.0) rev = 1.0;
+
+ // If the label should be invisible, we move the vertex outside
+ // of the view plane so that the triangle gets clipped. This makes it easier
+ // for us to create degenerate triangle strips.
+ // u_zoom is the current zoom level adjusted for the change in font size
+ float z = 2.0 - step(a_minzoom, u_zoom) - (1.0 - step(a_maxzoom, u_zoom)) + rev;
+
+ // fade out labels
+ float alpha = clamp((u_fadezoom - a_labelminzoom) / u_fadedist, 0.0, 1.0);
+
+ if (u_fadedist >= 0.0) {
+ v_alpha = alpha;
+ } else {
+ v_alpha = 1.0 - alpha;
+ }
+ if (u_maxfadezoom < a_labelminzoom) {
+ v_alpha = 0.0;
+ }
+ if (u_minfadezoom >= a_labelminzoom) {
+ v_alpha = 1.0;
+ }
+
+ // if label has been faded out, clip it
+ z += step(v_alpha, 0.0);
+
+ // all the angles are 0..256 representing 0..2PI
+ // hide if (angle >= a_rangeend && angle < rangestart)
+ z += step(a_rangeend, u_angle) * (1.0 - step(a_rangestart, u_angle));
+
+ gl_Position = u_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0);
+ v_tex = a_tex / u_texsize;
+
+ v_alpha *= u_opacity;
+}
diff --git a/src/mbgl/shader/icon_shader.cpp b/src/mbgl/shader/icon_shader.cpp
new file mode 100644
index 0000000000..5c54177eb6
--- /dev/null
+++ b/src/mbgl/shader/icon_shader.cpp
@@ -0,0 +1,60 @@
+#include <mbgl/shader/icon_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+IconShader::IconShader()
+ : Shader(
+ "icon",
+ shaders[ICON_SHADER].vertex,
+ shaders[ICON_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid icon shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+ a_offset = glGetAttribLocation(program, "a_offset");
+ a_tex = glGetAttribLocation(program, "a_tex");
+ a_angle = glGetAttribLocation(program, "a_angle");
+ a_minzoom = glGetAttribLocation(program, "a_minzoom");
+ a_maxzoom = glGetAttribLocation(program, "a_maxzoom");
+ a_rangeend = glGetAttribLocation(program, "a_rangeend");
+ a_rangestart = glGetAttribLocation(program, "a_rangestart");
+ a_labelminzoom = glGetAttribLocation(program, "a_labelminzoom");
+}
+
+void IconShader::bind(char *offset) {
+ const int stride = 20;
+
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, stride, offset + 0);
+
+ glEnableVertexAttribArray(a_offset);
+ glVertexAttribPointer(a_offset, 2, GL_SHORT, false, stride, offset + 4);
+
+ glEnableVertexAttribArray(a_labelminzoom);
+ glVertexAttribPointer(a_labelminzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 8);
+
+ glEnableVertexAttribArray(a_minzoom);
+ glVertexAttribPointer(a_minzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 9);
+
+ glEnableVertexAttribArray(a_maxzoom);
+ glVertexAttribPointer(a_maxzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 10);
+
+ glEnableVertexAttribArray(a_angle);
+ glVertexAttribPointer(a_angle, 1, GL_UNSIGNED_BYTE, false, stride, offset + 11);
+
+ glEnableVertexAttribArray(a_rangeend);
+ glVertexAttribPointer(a_rangeend, 1, GL_UNSIGNED_BYTE, false, stride, offset + 12);
+
+ glEnableVertexAttribArray(a_rangestart);
+ glVertexAttribPointer(a_rangestart, 1, GL_UNSIGNED_BYTE, false, stride, offset + 13);
+
+ glEnableVertexAttribArray(a_tex);
+ glVertexAttribPointer(a_tex, 2, GL_SHORT, false, stride, offset + 16);
+}
diff --git a/src/mbgl/shader/icon_shader.hpp b/src/mbgl/shader/icon_shader.hpp
new file mode 100644
index 0000000000..645d7e21b6
--- /dev/null
+++ b/src/mbgl/shader/icon_shader.hpp
@@ -0,0 +1,41 @@
+#ifndef MBGL_SHADER_SHADER_ICON
+#define MBGL_SHADER_SHADER_ICON
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class IconShader : public Shader {
+public:
+ IconShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this};
+ Uniform<float> u_angle = {"u_angle", *this};
+ Uniform<float> u_zoom = {"u_zoom", *this};
+ Uniform<float> u_flip = {"u_flip", *this};
+ Uniform<float> u_fadedist = {"u_fadedist", *this};
+ Uniform<float> u_minfadezoom = {"u_minfadezoom", *this};
+ Uniform<float> u_maxfadezoom = {"u_maxfadezoom", *this};
+ Uniform<float> u_fadezoom = {"u_fadezoom", *this};
+ Uniform<float> u_opacity = {"u_opacity", *this};
+ Uniform<std::array<float, 2>> u_texsize = {"u_texsize", *this};
+
+private:
+ int32_t a_pos = -1;
+ int32_t a_offset = -1;
+ int32_t a_tex = -1;
+ int32_t a_angle = -1;
+ int32_t a_minzoom = -1;
+ int32_t a_maxzoom = -1;
+ int32_t a_rangeend = -1;
+ int32_t a_rangestart = -1;
+ int32_t a_labelminzoom = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/line.fragment.glsl b/src/mbgl/shader/line.fragment.glsl
new file mode 100644
index 0000000000..f4ac1458b3
--- /dev/null
+++ b/src/mbgl/shader/line.fragment.glsl
@@ -0,0 +1,25 @@
+uniform vec2 u_linewidth;
+uniform vec4 u_color;
+uniform float u_blur;
+
+uniform vec2 u_dasharray;
+
+varying vec2 v_normal;
+varying float v_linesofar;
+
+void main() {
+ // Calculate the distance of the pixel from the line in pixels.
+ float dist = length(v_normal) * u_linewidth.s;
+
+ // Calculate the antialiasing fade factor. This is either when fading in
+ // the line in case of an offset line (v_linewidth.t) or when fading out
+ // (v_linewidth.s)
+ float alpha = clamp(min(dist - (u_linewidth.t - u_blur), u_linewidth.s - dist) / u_blur, 0.0, 1.0);
+
+ // Calculate the antialiasing fade factor based on distance to the dash.
+ // Only affects alpha when line is dashed
+ float pos = mod(v_linesofar, u_dasharray.x + u_dasharray.y);
+ alpha *= max(step(0.0, -u_dasharray.y), clamp(min(pos, u_dasharray.x - pos), 0.0, 1.0));
+
+ gl_FragColor = u_color * alpha;
+}
diff --git a/src/mbgl/shader/line.vertex.glsl b/src/mbgl/shader/line.vertex.glsl
new file mode 100644
index 0000000000..1d8e687c95
--- /dev/null
+++ b/src/mbgl/shader/line.vertex.glsl
@@ -0,0 +1,45 @@
+// floor(127 / 2) == 63.0
+// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is
+// stored in a byte (-128..127). we scale regular normals up to length 63, but
+// there are also "special" normals that have a bigger length (of up to 126 in
+// this case).
+// #define scale 63.0
+#define scale 0.015873016
+
+attribute vec2 a_pos;
+attribute vec2 a_extrude;
+attribute float a_linesofar;
+
+// matrix is for the vertex position, exmatrix is for rotating and projecting
+// the extrusion vector.
+uniform mat4 u_matrix;
+uniform mat4 u_exmatrix;
+
+// shared
+uniform float u_ratio;
+uniform vec2 u_linewidth;
+uniform vec4 u_color;
+
+varying vec2 v_normal;
+varying float v_linesofar;
+
+void main() {
+ // We store the texture normals in the most insignificant bit
+ // transform y so that 0 => -1 and 1 => 1
+ // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap
+ // y is 1 if the normal points up, and -1 if it points down
+ vec2 normal = mod(a_pos, 2.0);
+ normal.y = sign(normal.y - 0.5);
+ v_normal = normal;
+
+ // Scale the extrusion vector down to a normal and then up by the line width
+ // of this vertex.
+ vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0);
+
+ // Remove the texture normal bit of the position before scaling it with the
+ // model/view matrix. Add the extrusion vector *after* the model/view matrix
+ // because we're extruding the line in pixel space, regardless of the current
+ // tile's zoom level.
+ gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist;
+ v_linesofar = a_linesofar * u_ratio;
+}
diff --git a/src/mbgl/shader/line_shader.cpp b/src/mbgl/shader/line_shader.cpp
new file mode 100644
index 0000000000..8353f4c6ca
--- /dev/null
+++ b/src/mbgl/shader/line_shader.cpp
@@ -0,0 +1,34 @@
+#include <mbgl/shader/line_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+LineShader::LineShader()
+ : Shader(
+ "line",
+ shaders[LINE_SHADER].vertex,
+ shaders[LINE_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid line shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+ a_extrude = glGetAttribLocation(program, "a_extrude");
+ a_linesofar = glGetAttribLocation(program, "a_linesofar");
+}
+
+void LineShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset + 0);
+
+ glEnableVertexAttribArray(a_extrude);
+ glVertexAttribPointer(a_extrude, 2, GL_BYTE, false, 8, offset + 4);
+
+ glEnableVertexAttribArray(a_linesofar);
+ glVertexAttribPointer(a_linesofar, 1, GL_SHORT, false, 8, offset + 6);
+}
diff --git a/src/mbgl/shader/line_shader.hpp b/src/mbgl/shader/line_shader.hpp
new file mode 100644
index 0000000000..b789330882
--- /dev/null
+++ b/src/mbgl/shader/line_shader.hpp
@@ -0,0 +1,32 @@
+#ifndef MBGL_SHADER_SHADER_LINE
+#define MBGL_SHADER_SHADER_LINE
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class LineShader : public Shader {
+public:
+ LineShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this};
+ Uniform<std::array<float, 4>> u_color = {"u_color", *this};
+ Uniform<std::array<float, 2>> u_linewidth = {"u_linewidth", *this};
+ Uniform<std::array<float, 2>> u_dasharray = {"u_dasharray", *this};
+ Uniform<float> u_ratio = {"u_ratio", *this};
+ Uniform<float> u_blur = {"u_blur", *this};
+
+private:
+ int32_t a_pos = -1;
+ int32_t a_extrude = -1;
+ int32_t a_linesofar = -1;
+};
+
+
+}
+
+#endif
diff --git a/src/mbgl/shader/linejoin.fragment.glsl b/src/mbgl/shader/linejoin.fragment.glsl
new file mode 100644
index 0000000000..705a57766e
--- /dev/null
+++ b/src/mbgl/shader/linejoin.fragment.glsl
@@ -0,0 +1,14 @@
+uniform vec4 u_color;
+uniform vec2 u_linewidth;
+
+varying vec2 v_pos;
+
+void main() {
+ float dist = length(v_pos - gl_FragCoord.xy);
+
+ // Calculate the antialiasing fade factor. This is either when fading in
+ // the line in case of an offset line (v_linewidth.t) or when fading out
+ // (v_linewidth.s)
+ float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);
+ gl_FragColor = u_color * alpha;
+}
diff --git a/src/mbgl/shader/linejoin.vertex.glsl b/src/mbgl/shader/linejoin.vertex.glsl
new file mode 100644
index 0000000000..2e03561e5b
--- /dev/null
+++ b/src/mbgl/shader/linejoin.vertex.glsl
@@ -0,0 +1,13 @@
+attribute vec2 a_pos;
+
+uniform mat4 u_matrix;
+uniform vec2 u_world;
+uniform float u_size;
+
+varying vec2 v_pos;
+
+void main() {
+ gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0);
+ v_pos = (gl_Position.xy + 1.0) * u_world;
+ gl_PointSize = u_size;
+}
diff --git a/src/mbgl/shader/linejoin_shader.cpp b/src/mbgl/shader/linejoin_shader.cpp
new file mode 100644
index 0000000000..050e180e00
--- /dev/null
+++ b/src/mbgl/shader/linejoin_shader.cpp
@@ -0,0 +1,27 @@
+#include <mbgl/shader/linejoin_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+LinejoinShader::LinejoinShader()
+ : Shader(
+ "linejoin",
+ shaders[LINEJOIN_SHADER].vertex,
+ shaders[LINEJOIN_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid line shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+}
+
+void LinejoinShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ // Note: We're referring to the vertices in a line array, which are 8 bytes long!
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset);
+}
diff --git a/src/mbgl/shader/linejoin_shader.hpp b/src/mbgl/shader/linejoin_shader.hpp
new file mode 100644
index 0000000000..61406fd45c
--- /dev/null
+++ b/src/mbgl/shader/linejoin_shader.hpp
@@ -0,0 +1,27 @@
+#ifndef MBGL_SHADER_SHADER_LINEJOIN
+#define MBGL_SHADER_SHADER_LINEJOIN
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class LinejoinShader : public Shader {
+public:
+ LinejoinShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ Uniform<std::array<float, 4>> u_color = {"u_color", *this};
+ Uniform<std::array<float, 2>> u_world = {"u_world", *this};
+ Uniform<std::array<float, 2>> u_linewidth = {"u_linewidth", *this};
+ Uniform<float> u_size = {"u_size", *this};
+
+private:
+ int32_t a_pos = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/linepattern.fragment.glsl b/src/mbgl/shader/linepattern.fragment.glsl
new file mode 100644
index 0000000000..52ca823a3b
--- /dev/null
+++ b/src/mbgl/shader/linepattern.fragment.glsl
@@ -0,0 +1,37 @@
+uniform vec2 u_linewidth;
+uniform float u_point;
+uniform float u_blur;
+
+uniform vec2 u_pattern_size;
+uniform vec2 u_pattern_tl;
+uniform vec2 u_pattern_br;
+uniform float u_fade;
+
+uniform sampler2D u_image;
+
+varying vec2 v_normal;
+varying float v_linesofar;
+
+void main() {
+ // Calculate the distance of the pixel from the line in pixels.
+ float dist = length(v_normal) * (1.0 - u_point) + u_point * length(gl_PointCoord * 2.0 - 1.0);
+
+ dist *= u_linewidth.s;
+
+ // Calculate the antialiasing fade factor. This is either when fading in
+ // the line in case of an offset line (v_linewidth.t) or when fading out
+ // (v_linewidth.s)
+ float alpha = clamp(min(dist - (u_linewidth.t - u_blur), u_linewidth.s - dist) / u_blur, 0.0, 1.0);
+
+ float x = mod(v_linesofar / u_pattern_size.x, 1.0);
+ float y = 0.5 + (v_normal.y * u_linewidth.s / u_pattern_size.y);
+ vec2 pos = mix(u_pattern_tl, u_pattern_br, vec2(x, y));
+ float x2 = mod(x * 2.0, 1.0);
+ vec2 pos2 = mix(u_pattern_tl, u_pattern_br, vec2(x2, y));
+
+ vec4 color = texture2D(u_image, pos) * (1.0 - u_fade) + u_fade * texture2D(u_image, pos2);
+
+ color.rgb *= color.a; // premultiply
+
+ gl_FragColor = color * alpha;
+}
diff --git a/src/mbgl/shader/linepattern.vertex.glsl b/src/mbgl/shader/linepattern.vertex.glsl
new file mode 100644
index 0000000000..4600ebf65b
--- /dev/null
+++ b/src/mbgl/shader/linepattern.vertex.glsl
@@ -0,0 +1,57 @@
+// floor(127 / 2) == 63.0
+// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is
+// stored in a byte (-128..127). we scale regular normals up to length 63, but
+// there are also "special" normals that have a bigger length (of up to 126 in
+// this case).
+#define scale 63.0
+
+attribute vec2 a_pos;
+attribute vec2 a_extrude;
+attribute float a_linesofar;
+
+// matrix is for the vertex position, exmatrix is for rotating and projecting
+// the extrusion vector.
+uniform mat4 u_matrix;
+uniform mat4 u_exmatrix;
+
+// shared
+uniform float u_ratio;
+uniform vec2 u_linewidth;
+uniform vec4 u_color;
+uniform float u_point;
+
+varying vec2 v_normal;
+varying float v_linesofar;
+
+void main() {
+ // We store the texture normals in the most insignificant bit
+ // transform y so that 0 => -1 and 1 => 1
+ // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap
+ // y is 1 if the normal points up, and -1 if it points down
+ vec2 normal = mod(a_pos, 2.0);
+ normal.y = sign(normal.y - 0.5);
+ v_normal = normal;
+
+ // Scale the extrusion vector down to a normal and then up by the line width
+ // of this vertex.
+ vec2 extrude = a_extrude / scale;
+ vec2 dist = u_linewidth.s * extrude * (1.0 - u_point);
+
+ // If the x coordinate is the maximum integer, we move the z coordinates out
+ // of the view plane so that the triangle gets clipped. This makes it easier
+ // for us to create degenerate triangle strips.
+ float z = step(32767.0, a_pos.x);
+
+ // When drawing points, skip every other vertex
+ z += u_point * step(1.0, v_normal.y);
+
+ // Remove the texture normal bit of the position before scaling it with the
+ // model/view matrix. Add the extrusion vector *after* the model/view matrix
+ // because we're extruding the line in pixel space, regardless of the current
+ // tile's zoom level.
+ gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0) + u_exmatrix * vec4(dist, z, 0.0);
+ v_linesofar = a_linesofar;// * u_ratio;
+
+
+ gl_PointSize = 2.0 * u_linewidth.s - 1.0;
+}
diff --git a/src/mbgl/shader/linepattern_shader.cpp b/src/mbgl/shader/linepattern_shader.cpp
new file mode 100644
index 0000000000..954dbd2b3f
--- /dev/null
+++ b/src/mbgl/shader/linepattern_shader.cpp
@@ -0,0 +1,35 @@
+#include <mbgl/shader/linepattern_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <iostream>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+LinepatternShader::LinepatternShader()
+ : Shader(
+ "linepattern",
+ shaders[LINEPATTERN_SHADER].vertex,
+ shaders[LINEPATTERN_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid line pattern shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+ a_extrude = glGetAttribLocation(program, "a_extrude");
+ a_linesofar = glGetAttribLocation(program, "a_linesofar");
+}
+
+void LinepatternShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset + 0);
+
+ glEnableVertexAttribArray(a_extrude);
+ glVertexAttribPointer(a_extrude, 2, GL_BYTE, false, 8, offset + 4);
+
+ glEnableVertexAttribArray(a_linesofar);
+ glVertexAttribPointer(a_linesofar, 1, GL_SHORT, false, 8, offset + 6);
+}
diff --git a/src/mbgl/shader/linepattern_shader.hpp b/src/mbgl/shader/linepattern_shader.hpp
new file mode 100644
index 0000000000..bf85940b8a
--- /dev/null
+++ b/src/mbgl/shader/linepattern_shader.hpp
@@ -0,0 +1,33 @@
+#ifndef MBGL_SHADER_SHADER_LINEPATTERN
+#define MBGL_SHADER_SHADER_LINEPATTERN
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class LinepatternShader : public Shader {
+public:
+ LinepatternShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this};
+ Uniform<std::array<float, 2>> u_linewidth = {"u_linewidth", *this};
+ Uniform<std::array<float, 2>> u_pattern_size = {"u_pattern_size", *this};
+ Uniform<std::array<float, 2>> u_pattern_tl = {"u_pattern_tl", *this};
+ Uniform<std::array<float, 2>> u_pattern_br = {"u_pattern_br", *this};
+ Uniform<float> u_ratio = {"u_ratio", *this};
+ Uniform<float> u_point = {"u_point", *this};
+ Uniform<float> u_blur = {"u_blur", *this};
+ Uniform<float> u_fade = {"u_fade", *this};
+
+private:
+ int32_t a_pos = -1;
+ int32_t a_extrude = -1;
+ int32_t a_linesofar = -1;
+};
+}
+
+#endif
diff --git a/src/mbgl/shader/outline.fragment.glsl b/src/mbgl/shader/outline.fragment.glsl
new file mode 100644
index 0000000000..eccda714e5
--- /dev/null
+++ b/src/mbgl/shader/outline.fragment.glsl
@@ -0,0 +1,9 @@
+uniform vec4 u_color;
+
+varying vec2 v_pos;
+
+void main() {
+ float dist = length(v_pos - gl_FragCoord.xy);
+ float alpha = smoothstep(1.0, 0.0, dist);
+ gl_FragColor = u_color * alpha;
+}
diff --git a/src/mbgl/shader/outline.vertex.glsl b/src/mbgl/shader/outline.vertex.glsl
new file mode 100644
index 0000000000..29c16e3ded
--- /dev/null
+++ b/src/mbgl/shader/outline.vertex.glsl
@@ -0,0 +1,10 @@
+attribute vec2 a_pos;
+uniform mat4 u_matrix;
+uniform vec2 u_world;
+
+varying vec2 v_pos;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ v_pos = (gl_Position.xy + 1.0) / 2.0 * u_world;
+}
diff --git a/src/mbgl/shader/outline_shader.cpp b/src/mbgl/shader/outline_shader.cpp
new file mode 100644
index 0000000000..ddabfa5d0d
--- /dev/null
+++ b/src/mbgl/shader/outline_shader.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/shader/outline_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+OutlineShader::OutlineShader()
+ : Shader(
+ "outline",
+ shaders[OUTLINE_SHADER].vertex,
+ shaders[OUTLINE_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid outline shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+}
+
+void OutlineShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
+}
diff --git a/src/mbgl/shader/outline_shader.hpp b/src/mbgl/shader/outline_shader.hpp
new file mode 100644
index 0000000000..f3e8175fd7
--- /dev/null
+++ b/src/mbgl/shader/outline_shader.hpp
@@ -0,0 +1,25 @@
+#ifndef MBGL_SHADER_SHADER_OUTLINE
+#define MBGL_SHADER_SHADER_OUTLINE
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class OutlineShader : public Shader {
+public:
+ OutlineShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ Uniform<std::array<float, 4>> u_color = {"u_color", *this};
+ Uniform<std::array<float, 2>> u_world = {"u_world", *this};
+
+private:
+ int32_t a_pos = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/pattern.fragment.glsl b/src/mbgl/shader/pattern.fragment.glsl
new file mode 100644
index 0000000000..ba6aed3023
--- /dev/null
+++ b/src/mbgl/shader/pattern.fragment.glsl
@@ -0,0 +1,21 @@
+uniform float u_opacity;
+uniform vec2 u_pattern_tl;
+uniform vec2 u_pattern_br;
+uniform float u_mix;
+
+uniform sampler2D u_image;
+
+varying vec2 v_pos;
+
+void main() {
+
+ vec2 imagecoord = mod(v_pos, 1.0);
+ vec2 pos = mix(u_pattern_tl, u_pattern_br, imagecoord);
+ vec4 color1 = texture2D(u_image, pos);
+
+ vec2 imagecoord2 = mod(imagecoord * 2.0, 1.0);
+ vec2 pos2 = mix(u_pattern_tl, u_pattern_br, imagecoord2);
+ vec4 color2 = texture2D(u_image, pos2);
+
+ gl_FragColor = mix(color1, color2, u_mix) * u_opacity;
+}
diff --git a/src/mbgl/shader/pattern.vertex.glsl b/src/mbgl/shader/pattern.vertex.glsl
new file mode 100644
index 0000000000..f2de884ead
--- /dev/null
+++ b/src/mbgl/shader/pattern.vertex.glsl
@@ -0,0 +1,11 @@
+uniform mat4 u_matrix;
+uniform mat3 u_patternmatrix;
+
+attribute vec2 a_pos;
+
+varying vec2 v_pos;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ v_pos = (u_patternmatrix * vec3(a_pos, 1)).xy;
+}
diff --git a/src/mbgl/shader/pattern_shader.cpp b/src/mbgl/shader/pattern_shader.cpp
new file mode 100644
index 0000000000..31374bc3e8
--- /dev/null
+++ b/src/mbgl/shader/pattern_shader.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/shader/pattern_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+PatternShader::PatternShader()
+ : Shader(
+ "pattern",
+ shaders[PATTERN_SHADER].vertex,
+ shaders[PATTERN_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid pattern shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+}
+
+void PatternShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
+}
diff --git a/src/mbgl/shader/pattern_shader.hpp b/src/mbgl/shader/pattern_shader.hpp
new file mode 100644
index 0000000000..9fabd8e18a
--- /dev/null
+++ b/src/mbgl/shader/pattern_shader.hpp
@@ -0,0 +1,29 @@
+#ifndef MBGL_SHADER_SHADER_PATTERN
+#define MBGL_SHADER_SHADER_PATTERN
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class PatternShader : public Shader {
+public:
+ PatternShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ Uniform<std::array<float, 2>> u_pattern_tl = {"u_pattern_tl", *this};
+ Uniform<std::array<float, 2>> u_pattern_br = {"u_pattern_br", *this};
+ Uniform<float> u_opacity = {"u_opacity", *this};
+ Uniform<float> u_mix = {"u_mix", *this};
+ Uniform<int32_t> u_image = {"u_image", *this};
+ UniformMatrix<3> u_patternmatrix = {"u_patternmatrix", *this};
+
+private:
+ int32_t a_pos = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/plain.fragment.glsl b/src/mbgl/shader/plain.fragment.glsl
new file mode 100644
index 0000000000..8df552c171
--- /dev/null
+++ b/src/mbgl/shader/plain.fragment.glsl
@@ -0,0 +1,5 @@
+uniform vec4 u_color;
+
+void main() {
+ gl_FragColor = u_color;
+}
diff --git a/src/mbgl/shader/plain.vertex.glsl b/src/mbgl/shader/plain.vertex.glsl
new file mode 100644
index 0000000000..866c3cd2f3
--- /dev/null
+++ b/src/mbgl/shader/plain.vertex.glsl
@@ -0,0 +1,7 @@
+attribute vec2 a_pos;
+
+uniform mat4 u_matrix;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+}
diff --git a/src/mbgl/shader/plain_shader.cpp b/src/mbgl/shader/plain_shader.cpp
new file mode 100644
index 0000000000..8a37837b30
--- /dev/null
+++ b/src/mbgl/shader/plain_shader.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/shader/plain_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+PlainShader::PlainShader()
+ : Shader(
+ "plain",
+ shaders[PLAIN_SHADER].vertex,
+ shaders[PLAIN_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid plain shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+}
+
+void PlainShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
+}
diff --git a/src/mbgl/shader/plain_shader.hpp b/src/mbgl/shader/plain_shader.hpp
new file mode 100644
index 0000000000..051501c3c9
--- /dev/null
+++ b/src/mbgl/shader/plain_shader.hpp
@@ -0,0 +1,24 @@
+#ifndef MBGL_SHADER_SHADER_PLAIN
+#define MBGL_SHADER_SHADER_PLAIN
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class PlainShader : public Shader {
+public:
+ PlainShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ Uniform<std::array<float, 4>> u_color = {"u_color", *this};
+
+private:
+ int32_t a_pos = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/raster.fragment.glsl b/src/mbgl/shader/raster.fragment.glsl
new file mode 100644
index 0000000000..333de76dc1
--- /dev/null
+++ b/src/mbgl/shader/raster.fragment.glsl
@@ -0,0 +1,36 @@
+uniform sampler2D u_image;
+uniform float u_opacity;
+
+varying vec2 v_pos;
+
+uniform float u_brightness_low;
+uniform float u_brightness_high;
+
+uniform float u_saturation_factor;
+uniform float u_contrast_factor;
+uniform vec3 u_spin_weights;
+
+void main() {
+
+ vec4 color = texture2D(u_image, v_pos) * u_opacity;
+ vec3 rgb = color.rgb;
+
+ // spin
+ rgb = vec3(
+ dot(rgb, u_spin_weights.xyz),
+ dot(rgb, u_spin_weights.zxy),
+ dot(rgb, u_spin_weights.yzx));
+
+ // saturation
+ float average = (color.r + color.g + color.b) / 3.0;
+ rgb += (average - rgb) * u_saturation_factor;
+
+ // contrast
+ rgb = (rgb - 0.5) * u_contrast_factor + 0.5;
+
+ // brightness
+ vec3 u_high_vec = vec3(u_brightness_low, u_brightness_low, u_brightness_low);
+ vec3 u_low_vec = vec3(u_brightness_high, u_brightness_high, u_brightness_high);
+
+ gl_FragColor = vec4(mix(u_high_vec, u_low_vec, rgb), color.a);
+}
diff --git a/src/mbgl/shader/raster.vertex.glsl b/src/mbgl/shader/raster.vertex.glsl
new file mode 100644
index 0000000000..97e563f585
--- /dev/null
+++ b/src/mbgl/shader/raster.vertex.glsl
@@ -0,0 +1,13 @@
+uniform mat4 u_matrix;
+uniform float u_buffer;
+
+attribute vec2 a_pos;
+
+varying vec2 v_pos;
+
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ float dimension = (4096.0 + 2.0 * u_buffer);
+ v_pos = (a_pos / dimension) + (u_buffer / dimension);
+}
diff --git a/src/mbgl/shader/raster_shader.cpp b/src/mbgl/shader/raster_shader.cpp
new file mode 100644
index 0000000000..7351f7d0c4
--- /dev/null
+++ b/src/mbgl/shader/raster_shader.cpp
@@ -0,0 +1,28 @@
+#include <mbgl/shader/raster_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+RasterShader::RasterShader()
+ : Shader(
+ "raster",
+ shaders[RASTER_SHADER].vertex,
+ shaders[RASTER_SHADER].fragment
+ ) {
+ if (!valid) {
+#if defined(DEBUG)
+ fprintf(stderr, "invalid raster shader\n");
+#endif
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+}
+
+void RasterShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
+}
diff --git a/src/mbgl/shader/raster_shader.hpp b/src/mbgl/shader/raster_shader.hpp
new file mode 100644
index 0000000000..8cf97055a2
--- /dev/null
+++ b/src/mbgl/shader/raster_shader.hpp
@@ -0,0 +1,31 @@
+#ifndef MBGL_RENDERER_SHADER_RASTER
+#define MBGL_RENDERER_SHADER_RASTER
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class RasterShader : public Shader {
+public:
+ RasterShader();
+
+ void bind(char *offset);
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ Uniform<int32_t> u_image = {"u_image", *this};
+ Uniform<float> u_opacity = {"u_opacity", *this};
+ Uniform<float> u_buffer = {"u_buffer", *this};
+ Uniform<float> u_brightness_low = {"u_brightness_low", *this};
+ Uniform<float> u_brightness_high = {"u_brightness_high", *this};
+ Uniform<float> u_saturation_factor = {"u_saturation_factor", *this};
+ Uniform<float> u_contrast_factor = {"u_contrast_factor", *this};
+ Uniform<std::array<float, 3>> u_spin_weights = {"u_spin_weights", *this};
+
+private:
+ int32_t a_pos = -1;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/sdf.fragment.glsl b/src/mbgl/shader/sdf.fragment.glsl
new file mode 100644
index 0000000000..d72d61dab1
--- /dev/null
+++ b/src/mbgl/shader/sdf.fragment.glsl
@@ -0,0 +1,13 @@
+uniform sampler2D u_texture;
+uniform vec4 u_color;
+uniform float u_buffer;
+uniform float u_gamma;
+
+varying vec2 v_tex;
+varying float v_alpha;
+
+void main() {
+ float dist = texture2D(u_texture, v_tex).a;
+ float alpha = smoothstep(u_buffer - u_gamma, u_buffer + u_gamma, dist) * v_alpha;
+ gl_FragColor = u_color * alpha;
+}
diff --git a/src/mbgl/shader/sdf.vertex.glsl b/src/mbgl/shader/sdf.vertex.glsl
new file mode 100644
index 0000000000..c5166ae46f
--- /dev/null
+++ b/src/mbgl/shader/sdf.vertex.glsl
@@ -0,0 +1,69 @@
+attribute vec2 a_pos;
+attribute vec2 a_offset;
+attribute vec2 a_tex;
+attribute float a_angle;
+attribute float a_minzoom;
+attribute float a_maxzoom;
+attribute float a_rangeend;
+attribute float a_rangestart;
+attribute float a_labelminzoom;
+
+
+// matrix is for the vertex position, exmatrix is for rotating and projecting
+// the extrusion vector.
+uniform mat4 u_matrix;
+uniform mat4 u_exmatrix;
+uniform float u_angle;
+uniform float u_zoom;
+uniform float u_flip;
+uniform float u_fadedist;
+uniform float u_minfadezoom;
+uniform float u_maxfadezoom;
+uniform float u_fadezoom;
+
+uniform vec2 u_texsize;
+
+varying vec2 v_tex;
+varying float v_alpha;
+
+void main() {
+
+ float rev = 0.0;
+
+ // u_angle is angle of the map, -128..128 representing 0..2PI
+ // a_angle is angle of the label, 0..256 representing 0..2PI, where 0 is horizontal text
+ float rotated = mod(a_angle + u_angle, 256.0);
+ // if the label rotates with the map, and if the rotated label is upside down, hide it
+ if (u_flip > 0.0 && rotated >= 64.0 && rotated < 192.0) rev = 1.0;
+
+ // If the label should be invisible, we move the vertex outside
+ // of the view plane so that the triangle gets clipped. This makes it easier
+ // for us to create degenerate triangle strips.
+ // u_zoom is the current zoom level adjusted for the change in font size
+ float z = 2.0 - step(a_minzoom, u_zoom) - (1.0 - step(a_maxzoom, u_zoom)) + rev;
+
+ // fade out labels
+ float alpha = clamp((u_fadezoom - a_labelminzoom) / u_fadedist, 0.0, 1.0);
+
+ if (u_fadedist >= 0.0) {
+ v_alpha = alpha;
+ } else {
+ v_alpha = 1.0 - alpha;
+ }
+ if (u_maxfadezoom < a_labelminzoom) {
+ v_alpha = 0.0;
+ }
+ if (u_minfadezoom >= a_labelminzoom) {
+ v_alpha = 1.0;
+ }
+
+ // if label has been faded out, clip it
+ z += step(v_alpha, 0.0);
+
+ // all the angles are 0..256 representing 0..2PI
+ // hide if (angle >= a_rangeend && angle < rangestart)
+ z += step(a_rangeend, u_angle) * (1.0 - step(a_rangestart, u_angle));
+
+ gl_Position = u_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0);
+ v_tex = a_tex / u_texsize;
+}
diff --git a/src/mbgl/shader/sdf_shader.cpp b/src/mbgl/shader/sdf_shader.cpp
new file mode 100644
index 0000000000..b86733c0e4
--- /dev/null
+++ b/src/mbgl/shader/sdf_shader.cpp
@@ -0,0 +1,91 @@
+#include <mbgl/shader/sdf_shader.hpp>
+#include <mbgl/shader/shaders.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace mbgl;
+
+SDFShader::SDFShader()
+ : Shader(
+ "sdf",
+ shaders[SDF_SHADER].vertex,
+ shaders[SDF_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid sdf shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+ a_offset = glGetAttribLocation(program, "a_offset");
+ a_tex = glGetAttribLocation(program, "a_tex");
+ a_angle = glGetAttribLocation(program, "a_angle");
+ a_minzoom = glGetAttribLocation(program, "a_minzoom");
+ a_maxzoom = glGetAttribLocation(program, "a_maxzoom");
+ a_rangeend = glGetAttribLocation(program, "a_rangeend");
+ a_rangestart = glGetAttribLocation(program, "a_rangestart");
+ a_labelminzoom = glGetAttribLocation(program, "a_labelminzoom");
+}
+
+void SDFGlyphShader::bind(char *offset) {
+ const int stride = 16;
+
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, stride, offset + 0);
+
+ glEnableVertexAttribArray(a_offset);
+ glVertexAttribPointer(a_offset, 2, GL_SHORT, false, stride, offset + 4);
+
+ glEnableVertexAttribArray(a_labelminzoom);
+ glVertexAttribPointer(a_labelminzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 8);
+
+ glEnableVertexAttribArray(a_minzoom);
+ glVertexAttribPointer(a_minzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 9);
+
+ glEnableVertexAttribArray(a_maxzoom);
+ glVertexAttribPointer(a_maxzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 10);
+
+ glEnableVertexAttribArray(a_angle);
+ glVertexAttribPointer(a_angle, 1, GL_UNSIGNED_BYTE, false, stride, offset + 11);
+
+ glEnableVertexAttribArray(a_rangeend);
+ glVertexAttribPointer(a_rangeend, 1, GL_UNSIGNED_BYTE, false, stride, offset + 12);
+
+ glEnableVertexAttribArray(a_rangestart);
+ glVertexAttribPointer(a_rangestart, 1, GL_UNSIGNED_BYTE, false, stride, offset + 13);
+
+ glEnableVertexAttribArray(a_tex);
+ glVertexAttribPointer(a_tex, 2, GL_UNSIGNED_BYTE, false, stride, offset + 14);
+}
+
+void SDFIconShader::bind(char *offset) {
+ const int stride = 20;
+
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, stride, offset + 0);
+
+ glEnableVertexAttribArray(a_offset);
+ glVertexAttribPointer(a_offset, 2, GL_SHORT, false, stride, offset + 4);
+
+ glEnableVertexAttribArray(a_labelminzoom);
+ glVertexAttribPointer(a_labelminzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 8);
+
+ glEnableVertexAttribArray(a_minzoom);
+ glVertexAttribPointer(a_minzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 9);
+
+ glEnableVertexAttribArray(a_maxzoom);
+ glVertexAttribPointer(a_maxzoom, 1, GL_UNSIGNED_BYTE, false, stride, offset + 10);
+
+ glEnableVertexAttribArray(a_angle);
+ glVertexAttribPointer(a_angle, 1, GL_UNSIGNED_BYTE, false, stride, offset + 11);
+
+ glEnableVertexAttribArray(a_rangeend);
+ glVertexAttribPointer(a_rangeend, 1, GL_UNSIGNED_BYTE, false, stride, offset + 12);
+
+ glEnableVertexAttribArray(a_rangestart);
+ glVertexAttribPointer(a_rangestart, 1, GL_UNSIGNED_BYTE, false, stride, offset + 13);
+
+ glEnableVertexAttribArray(a_tex);
+ glVertexAttribPointer(a_tex, 2, GL_SHORT, false, stride, offset + 16);
+}
diff --git a/src/mbgl/shader/sdf_shader.hpp b/src/mbgl/shader/sdf_shader.hpp
new file mode 100644
index 0000000000..0737c25ee1
--- /dev/null
+++ b/src/mbgl/shader/sdf_shader.hpp
@@ -0,0 +1,53 @@
+#ifndef MBGL_SHADER_SDF_SHADER
+#define MBGL_SHADER_SDF_SHADER
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+class SDFShader : public Shader {
+public:
+ SDFShader();
+
+ virtual void bind(char *offset) = 0;
+
+ UniformMatrix<4> u_matrix = {"u_matrix", *this};
+ UniformMatrix<4> u_exmatrix = {"u_exmatrix", *this};
+ Uniform<std::array<float, 4>> u_color = {"u_color", *this};
+ Uniform<std::array<float, 2>> u_texsize = {"u_texsize", *this};
+ Uniform<float> u_buffer = {"u_buffer", *this};
+ Uniform<float> u_gamma = {"u_gamma", *this};
+ Uniform<float> u_angle = {"u_angle", *this};
+ Uniform<float> u_zoom = {"u_zoom", *this};
+ Uniform<float> u_flip = {"u_flip", *this};
+ Uniform<float> u_fadedist = {"u_fadedist", *this};
+ Uniform<float> u_minfadezoom = {"u_minfadezoom", *this};
+ Uniform<float> u_maxfadezoom = {"u_maxfadezoom", *this};
+ Uniform<float> u_fadezoom = {"u_fadezoom", *this};
+
+protected:
+ int32_t a_pos = -1;
+ int32_t a_offset = -1;
+ int32_t a_tex = -1;
+ int32_t a_angle = -1;
+ int32_t a_minzoom = -1;
+ int32_t a_maxzoom = -1;
+ int32_t a_rangeend = -1;
+ int32_t a_rangestart = -1;
+ int32_t a_labelminzoom = -1;
+};
+
+class SDFGlyphShader : public SDFShader {
+public:
+ void bind(char *offset);
+};
+
+class SDFIconShader : public SDFShader {
+public:
+ void bind(char *offset);
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/shader.cpp b/src/mbgl/shader/shader.cpp
new file mode 100644
index 0000000000..f083beaa48
--- /dev/null
+++ b/src/mbgl/shader/shader.cpp
@@ -0,0 +1,130 @@
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/platform/gl.hpp>
+#include <mbgl/util/stopwatch.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <cstring>
+#include <cstdlib>
+
+using namespace mbgl;
+
+Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSource)
+ : name(name_),
+ valid(false),
+ program(0) {
+ util::stopwatch stopwatch("shader compilation", Event::Shader);
+
+ GLuint vertShader;
+ if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertSource)) {
+ Log::Error(Event::Shader, "Vertex shader failed to compile: %s", vertSource);
+ return;
+ }
+
+ GLuint fragShader;
+ if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fragSource)) {
+ Log::Error(Event::Shader, "Fragment shader failed to compile: %s", fragSource);
+ return;
+ }
+
+ program = glCreateProgram();
+
+ // Attach shaders
+ glAttachShader(program, vertShader);
+ glAttachShader(program, fragShader);
+
+
+ {
+ // Link program
+ GLint status;
+ glLinkProgram(program);
+
+ glGetProgramiv(program, GL_LINK_STATUS, &status);
+ if (status == 0) {
+ GLint logLength;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ std::unique_ptr<GLchar[]> log = mbgl::util::make_unique<GLchar[]>(logLength);
+ glGetProgramInfoLog(program, logLength, &logLength, log.get());
+ Log::Error(Event::Shader, "Program failed to link: %s", log.get());
+ }
+
+ glDeleteShader(vertShader);
+ vertShader = 0;
+ glDeleteShader(fragShader);
+ fragShader = 0;
+ glDeleteProgram(program);
+ program = 0;
+ return;
+ }
+ }
+
+ {
+ // Validate program
+ GLint status;
+ glValidateProgram(program);
+
+ glGetProgramiv(program, GL_VALIDATE_STATUS, &status);
+ if (status == 0) {
+ GLint logLength;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ std::unique_ptr<GLchar[]> log = mbgl::util::make_unique<GLchar[]>(logLength);
+ glGetProgramInfoLog(program, logLength, &logLength, log.get());
+ Log::Error(Event::Shader, "Program failed to validate: %s", log.get());
+ }
+
+ glDeleteShader(vertShader);
+ vertShader = 0;
+ glDeleteShader(fragShader);
+ fragShader = 0;
+ glDeleteProgram(program);
+ program = 0;
+ }
+
+ }
+
+ // Remove the compiled shaders; they are now part of the program.
+ glDetachShader(program, vertShader);
+ glDeleteShader(vertShader);
+ glDetachShader(program, fragShader);
+ glDeleteShader(fragShader);
+
+ valid = true;
+}
+
+
+bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) {
+ GLint status;
+
+ *shader = glCreateShader(type);
+ const GLchar *strings[] = { source };
+ const GLint lengths[] = { (GLint)strlen(source) };
+ glShaderSource(*shader, 1, strings, lengths);
+
+ glCompileShader(*shader);
+
+ glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
+ if (status == 0) {
+ GLint logLength;
+ glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
+ if (logLength > 0) {
+ std::unique_ptr<GLchar[]> log = mbgl::util::make_unique<GLchar[]>(logLength);
+ glGetShaderInfoLog(*shader, logLength, &logLength, log.get());
+ Log::Error(Event::Shader, "Shader failed to compile: %s", log.get());
+ }
+
+ glDeleteShader(*shader);
+ *shader = 0;
+ return false;
+ }
+
+ return true;
+}
+
+Shader::~Shader() {
+ if (program) {
+ glDeleteProgram(program);
+ program = 0;
+ valid = false;
+ }
+}
diff --git a/src/mbgl/shader/shader.hpp b/src/mbgl/shader/shader.hpp
new file mode 100644
index 0000000000..27e831a510
--- /dev/null
+++ b/src/mbgl/shader/shader.hpp
@@ -0,0 +1,28 @@
+#ifndef MBGL_RENDERER_SHADER
+#define MBGL_RENDERER_SHADER
+
+#include <cstdint>
+#include <array>
+#include <mbgl/util/noncopyable.hpp>
+
+namespace mbgl {
+
+class Shader : private util::noncopyable {
+public:
+ Shader(const char *name, const char *vertex, const char *fragment);
+ ~Shader();
+ const char *name;
+ bool valid;
+ uint32_t program;
+
+ inline uint32_t getID() const {
+ return program;
+ }
+
+private:
+ bool compileShader(uint32_t *shader, uint32_t type, const char *source);
+};
+
+}
+
+#endif
diff --git a/src/mbgl/shader/uniform.cpp b/src/mbgl/shader/uniform.cpp
new file mode 100644
index 0000000000..24f179baf1
--- /dev/null
+++ b/src/mbgl/shader/uniform.cpp
@@ -0,0 +1,47 @@
+#include <mbgl/shader/uniform.hpp>
+
+namespace mbgl {
+
+template <>
+void Uniform<float>::bind(const float& t) {
+ glUniform1f(location, t);
+}
+
+template <>
+void Uniform<int32_t>::bind(const int32_t& t) {
+ glUniform1i(location, t);
+}
+
+template <>
+void Uniform<std::array<float, 2>>::bind(const std::array<float, 2>& t) {
+ glUniform2fv(location, 1, t.data());
+}
+
+template <>
+void Uniform<std::array<float, 3>>::bind(const std::array<float, 3>& t) {
+ glUniform3fv(location, 1, t.data());
+}
+
+template <>
+void Uniform<std::array<float, 4>>::bind(const std::array<float, 4>& t) {
+ glUniform4fv(location, 1, t.data());
+}
+
+template <>
+void UniformMatrix<2>::bind(const std::array<float, 4>& t) {
+ glUniformMatrix2fv(location, 1, GL_FALSE, t.data());
+}
+
+template <>
+void UniformMatrix<3>::bind(const std::array<float, 9>& t) {
+ glUniformMatrix3fv(location, 1, GL_FALSE, t.data());
+}
+
+template <>
+void UniformMatrix<4>::bind(const std::array<float, 16>& t) {
+ glUniformMatrix4fv(location, 1, GL_FALSE, t.data());
+}
+
+// Add more as needed.
+
+}
diff --git a/src/mbgl/shader/uniform.hpp b/src/mbgl/shader/uniform.hpp
new file mode 100644
index 0000000000..8579ae22c7
--- /dev/null
+++ b/src/mbgl/shader/uniform.hpp
@@ -0,0 +1,53 @@
+#ifndef MBGL_SHADER_UNIFORM
+#define MBGL_SHADER_UNIFORM
+
+#include <mbgl/shader/shader.hpp>
+#include <mbgl/platform/gl.hpp>
+
+namespace mbgl {
+
+template <typename T>
+class Uniform {
+public:
+ Uniform(const GLchar* name, const Shader& shader)
+ : location(glGetUniformLocation(shader.program, name)) {}
+
+ void operator=(const T& t) {
+ if (current != t) {
+ current = t;
+ bind(t);
+ }
+ }
+
+private:
+ void bind(const T&);
+
+ T current;
+ GLint location;
+};
+
+template <size_t C, size_t R = C>
+class UniformMatrix {
+public:
+ typedef std::array<float, C*R> T;
+
+ UniformMatrix(const GLchar* name, const Shader& shader)
+ : location(glGetUniformLocation(shader.program, name)) {}
+
+ void operator=(const T& t) {
+ if (current != t) {
+ current = t;
+ bind(t);
+ }
+ }
+
+private:
+ void bind(const T&);
+
+ T current;
+ GLint location;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/storage/base_request.cpp b/src/mbgl/storage/base_request.cpp
new file mode 100644
index 0000000000..510bd7bf1c
--- /dev/null
+++ b/src/mbgl/storage/base_request.cpp
@@ -0,0 +1,87 @@
+#include <mbgl/storage/base_request.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/storage/request.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+
+namespace mbgl {
+
+template <typename T, typename ...Args>
+void invoke(const std::forward_list<std::unique_ptr<Callback>> &list, Args&& ...args) {
+ for (const std::unique_ptr<Callback> &callback : list) {
+ assert(callback);
+ if (callback->is<T>()) {
+ callback->get<T>()(::std::forward<Args>(args)...);
+ }
+ }
+}
+
+BaseRequest::BaseRequest(const std::string &path_) : threadId(std::this_thread::get_id()), path(path_) {
+}
+
+// A base request can only be "canceled" by destroying the object. In that case, we'll have to
+// notify all cancel callbacks.
+BaseRequest::~BaseRequest() {
+ assert(std::this_thread::get_id() == threadId);
+ notify();
+}
+
+void BaseRequest::retryImmediately() {
+ // no-op. override in child class.
+}
+
+void BaseRequest::notify() {
+ assert(std::this_thread::get_id() == threadId);
+
+ // The parameter exists solely so that any calls to ->remove()
+ // are not going to cause deallocation of this object while this call is in progress.
+ util::ptr<BaseRequest> retain = self;
+
+ // Swap the lists so that it's safe for callbacks to call ->cancel()
+ // on the request object, which would modify the list.
+ const std::forward_list<std::unique_ptr<Callback>> list = std::move(callbacks);
+ callbacks.clear();
+
+ if (response) {
+ invoke<CompletedCallback>(list, *response);
+ } else {
+ invoke<AbortedCallback>(list);
+ }
+
+ self.reset();
+}
+
+Callback *BaseRequest::add(Callback &&callback, const util::ptr<BaseRequest> &request) {
+ assert(std::this_thread::get_id() == threadId);
+ assert(this == request.get());
+
+ if (response) {
+ // We already have a response. Notify right away.
+ if (callback.is<CompletedCallback>()) {
+ callback.get<CompletedCallback>()(*response);
+ } else {
+ // We already know that this request was successful. The AbortedCallback will be discarded
+ // here since it would never be called.
+ }
+ return nullptr;
+ } else {
+ self = request;
+ callbacks.push_front(util::make_unique<Callback>(std::move(callback)));
+ return callbacks.front().get();
+ }
+}
+
+void BaseRequest::remove(Callback *callback) {
+ assert(std::this_thread::get_id() == threadId);
+ callbacks.remove_if([=](const std::unique_ptr<Callback> &cb) {
+ return cb.get() == callback;
+ });
+ if (callbacks.empty()) {
+ self.reset();
+ }
+}
+
+}
diff --git a/src/mbgl/storage/base_request.hpp b/src/mbgl/storage/base_request.hpp
new file mode 100644
index 0000000000..5119c343e9
--- /dev/null
+++ b/src/mbgl/storage/base_request.hpp
@@ -0,0 +1,62 @@
+#ifndef MBGL_STORAGE_BASE_REQUEST
+#define MBGL_STORAGE_BASE_REQUEST
+
+#include <mbgl/storage/request_callback.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <string>
+#include <forward_list>
+#include <functional>
+#include <thread>
+
+typedef struct uv_loop_s uv_loop_t;
+typedef struct uv_async_s uv_async_t;
+
+namespace mbgl {
+
+class Response;
+class Request;
+
+class BaseRequest {
+private:
+ // Make noncopyable and immovable
+ BaseRequest(const BaseRequest &) = delete;
+ BaseRequest(BaseRequest &&) = delete;
+ BaseRequest& operator=(const BaseRequest &) = delete;
+ BaseRequest& operator=(BaseRequest &&) = delete;
+
+public:
+ BaseRequest(const std::string &path);
+ virtual ~BaseRequest();
+
+ Callback *add(Callback &&callback, const util::ptr<BaseRequest> &request);
+ void remove(Callback *callback);
+
+ // Must be called by subclasses when a valid Response object is available. It will notify
+ // all listeners.
+ void notify();
+
+ // This function is called when the request ought to be stopped. Any subclass must make sure this
+ // is also called in its destructor. Calling this function repeatedly must be safe.
+ // This function must call notify().
+ virtual void cancel() = 0;
+
+ // This function is called when the request should be reattempted immediately. This is typically
+ // reaction to a network status change.
+ virtual void retryImmediately();
+
+public:
+ const std::thread::id threadId;
+ const std::string path;
+ std::unique_ptr<Response> response;
+
+protected:
+ // This object may hold a shared_ptr to itself. It does this to prevent destruction of this object
+ // while a request is in progress.
+ util::ptr<BaseRequest> self;
+ std::forward_list<std::unique_ptr<Callback>> callbacks;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/storage/caching_http_file_source.cpp b/src/mbgl/storage/caching_http_file_source.cpp
new file mode 100644
index 0000000000..cf09c58113
--- /dev/null
+++ b/src/mbgl/storage/caching_http_file_source.cpp
@@ -0,0 +1,130 @@
+#include <mbgl/storage/caching_http_file_source.hpp>
+#include <mbgl/storage/file_request.hpp>
+#include <mbgl/storage/http_request.hpp>
+#include <mbgl/storage/sqlite_store.hpp>
+#include <mbgl/storage/asset_request.hpp>
+#include <mbgl/util/uv-messenger.h>
+#include <mbgl/util/mapbox.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <uv.h>
+
+namespace mbgl {
+
+CachingHTTPFileSource::CachingHTTPFileSource(const std::string &path_)
+ : path(path_) {}
+
+CachingHTTPFileSource::~CachingHTTPFileSource() {
+}
+
+void CachingHTTPFileSource::setLoop(uv_loop_t* loop_) {
+ threadId = std::this_thread::get_id();
+ store = !path.empty() ? util::ptr<SQLiteStore>(new SQLiteStore(loop_, path)) : nullptr;
+ loop = loop_;
+ queue = new uv_messenger_t;
+
+ uv_messenger_init(loop, queue, [](void *ptr) {
+ std::unique_ptr<std::function<void()>> fn { reinterpret_cast<std::function<void()> *>(ptr) };
+ (*fn)();
+ });
+ uv_unref((uv_handle_t *)&queue->async);
+}
+
+bool CachingHTTPFileSource::hasLoop() {
+ return loop;
+}
+
+void CachingHTTPFileSource::clearLoop() {
+ assert(std::this_thread::get_id() == threadId);
+ assert(loop);
+
+ uv_messenger_stop(queue, [](uv_messenger_t *msgr) {
+ delete msgr;
+ });
+
+ util::ptr<BaseRequest> req;
+
+ // Send a cancel() message to all requests that we are still holding.
+ for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) {
+ if ((req = pair.second.lock())) {
+ req->cancel();
+ }
+ }
+
+ store.reset();
+}
+
+void CachingHTTPFileSource::setBase(std::string value) {
+ // TODO: Make threadsafe.
+ base.swap(value);
+}
+
+void CachingHTTPFileSource::setAccessToken(std::string value) {
+ // TODO: Make threadsafe.
+ accessToken.swap(value);
+}
+
+std::unique_ptr<Request> CachingHTTPFileSource::request(ResourceType type, const std::string& url_) {
+ assert(tstd::this_thread::get_id() == threadId);
+
+ std::string url = url_;
+
+ // Make URL absolute.
+ const size_t separator = url.find("://");
+ if (separator == std::string::npos) {
+ url = base + url;
+ }
+
+ // Normalize mapbox:// URLs.
+ switch (type) {
+ case ResourceType::Glyphs:
+ url = util::mapbox::normalizeGlyphsURL(url, accessToken);
+ default:
+ url = util::mapbox::normalizeSourceURL(url, accessToken);
+ }
+
+ util::ptr<BaseRequest> req;
+
+ // First, try to find an existing Request object.
+ auto it = pending.find(url);
+ if (it != pending.end()) {
+ req = it->second.lock();
+ }
+
+ if (!req) {
+ if (url.substr(0, 7) == "file://") {
+ req = std::make_shared<FileRequest>(url.substr(7), loop);
+ } else if (url.substr(0, 8) == "asset://") {
+ req = std::make_shared<AssetRequest>(url.substr(8), loop);
+ } else {
+ req = std::make_shared<HTTPRequest>(type, url, loop, store);
+ }
+
+ pending.emplace(url, req);
+ }
+
+ return util::make_unique<Request>(req);
+}
+
+void CachingHTTPFileSource::prepare(std::function<void()> fn) {
+ if (std::this_thread::get_id() == threadId) {
+ fn();
+ } else {
+ uv_messenger_send(queue, new std::function<void()>(std::move(fn)));
+ }
+}
+
+void CachingHTTPFileSource::setReachability(bool reachable) {
+ if (reachable && loop) {
+ prepare([this]() {
+ util::ptr<BaseRequest> req;
+ for (const std::pair<std::string, std::weak_ptr<BaseRequest>> &pair : pending) {
+ if ((req = pair.second.lock())) {
+ req->retryImmediately();
+ }
+ }
+ });
+ }
+}
+
+}
diff --git a/src/mbgl/storage/file_request.cpp b/src/mbgl/storage/file_request.cpp
new file mode 100644
index 0000000000..6cb882101d
--- /dev/null
+++ b/src/mbgl/storage/file_request.cpp
@@ -0,0 +1,37 @@
+#include <mbgl/storage/file_request.hpp>
+#include <mbgl/storage/file_request_baton.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+
+#include <unistd.h>
+
+namespace mbgl {
+
+FileRequest::FileRequest(const std::string &path_, uv_loop_t *loop)
+ : BaseRequest(path_), ptr(new FileRequestBaton(this, path, loop)) {
+}
+
+void FileRequest::cancel() {
+ assert(std::this_thread::get_id() == threadId);
+
+ if (ptr) {
+ ptr->cancel();
+
+ // When deleting a FileRequest object with a uv_fs_* call is in progress, we are making sure
+ // that the callback doesn't accidentally reference this object again.
+ ptr->request = nullptr;
+ ptr = nullptr;
+ }
+
+ notify();
+}
+
+FileRequest::~FileRequest() {
+ assert(std::this_thread::get_id() == threadId);
+ cancel();
+}
+
+}
diff --git a/src/mbgl/storage/file_request.hpp b/src/mbgl/storage/file_request.hpp
new file mode 100644
index 0000000000..3de2d5b60d
--- /dev/null
+++ b/src/mbgl/storage/file_request.hpp
@@ -0,0 +1,27 @@
+#ifndef MBGL_STORAGE_FILE_REQUEST
+#define MBGL_STORAGE_FILE_REQUEST
+
+#include <mbgl/storage/base_request.hpp>
+
+namespace mbgl {
+
+typedef struct uv_loop_s uv_loop_t;
+
+struct FileRequestBaton;
+
+class FileRequest : public BaseRequest {
+public:
+ FileRequest(const std::string &path, uv_loop_t *loop);
+ ~FileRequest();
+
+ void cancel();
+
+private:
+ FileRequestBaton *ptr = nullptr;
+
+ friend struct FileRequestBaton;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/storage/file_request_baton.cpp b/src/mbgl/storage/file_request_baton.cpp
new file mode 100644
index 0000000000..c661cd347a
--- /dev/null
+++ b/src/mbgl/storage/file_request_baton.cpp
@@ -0,0 +1,161 @@
+#include <mbgl/storage/file_request_baton.hpp>
+#include <mbgl/storage/file_request.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <limits>
+
+namespace mbgl {
+
+FileRequestBaton::FileRequestBaton(FileRequest *request_, const std::string &path, uv_loop_t *loop)
+ : threadId(std::this_thread::get_id()), request(request_) {
+ req.data = this;
+ uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, file_opened);
+}
+
+FileRequestBaton::~FileRequestBaton() {
+}
+
+void FileRequestBaton::cancel() {
+ canceled = true;
+
+ // uv_cancel fails frequently when the request has already been started.
+ // In that case, we have to let it complete and check the canceled bool
+ // instead.
+ uv_cancel((uv_req_t *)&req);
+}
+
+void FileRequestBaton::notify_error(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) {
+ ptr->request->response = util::make_unique<Response>();
+ ptr->request->response->code = req->result == UV_ENOENT ? 404 : 500;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ ptr->request->response->message = uv_strerror(uv_last_error(req->loop));
+#else
+ ptr->request->response->message = uv_strerror(int(req->result));
+#endif
+ ptr->request->notify();
+ }
+}
+
+void FileRequestBaton::file_opened(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (req->result < 0) {
+ // Opening failed or was canceled. There isn't much left we can do.
+ notify_error(req);
+ cleanup(req);
+ } else {
+ const uv_file fd = uv_file(req->result);
+
+ // We're going to reuse this handle, so we need to cleanup first.
+ uv_fs_req_cleanup(req);
+
+ if (ptr->canceled || !ptr->request) {
+ // Either the FileRequest object has been destructed, or the
+ // request was canceled.
+ uv_fs_close(req->loop, req, fd, file_closed);
+ } else {
+ ptr->fd = fd;
+ uv_fs_fstat(req->loop, req, fd, file_stated);
+ }
+ }
+}
+
+void FileRequestBaton::file_stated(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (req->result != 0 || ptr->canceled || !ptr->request) {
+ // Stating failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notify_error(req);
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, ptr->fd, file_closed);
+ } else {
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ const uv_statbuf_t *stat = static_cast<const uv_statbuf_t *>(req->ptr);
+#else
+ const uv_stat_t *stat = static_cast<const uv_stat_t *>(req->ptr);
+#endif
+ if (stat->st_size > std::numeric_limits<int>::max()) {
+ // File is too large for us to open this way because uv_buf's only support unsigned
+ // ints as maximum size.
+ if (ptr->request) {
+ ptr->request->response = util::make_unique<Response>();
+ ptr->request->response->code = UV_EFBIG;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ ptr->request->response->message = uv_strerror(uv_err_t {UV_EFBIG, 0});
+#else
+ ptr->request->response->message = uv_strerror(UV_EFBIG);
+#endif
+ ptr->request->notify();
+ }
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, ptr->fd, file_closed);
+ } else {
+ const unsigned int size = (unsigned int)(stat->st_size);
+ ptr->body.resize(size);
+ ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size);
+ uv_fs_req_cleanup(req);
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_fs_read(req->loop, req, ptr->fd, ptr->buffer.base, ptr->buffer.len, -1, file_read);
+#else
+ uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, file_read);
+#endif
+ }
+ }
+}
+
+void FileRequestBaton::file_read(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (req->result < 0 || ptr->canceled || !ptr->request) {
+ // Reading failed or was canceled. We already have an open file handle
+ // though, which we'll have to close.
+ notify_error(req);
+ } else {
+ // File was successfully read.
+ if (ptr->request) {
+ ptr->request->response = util::make_unique<Response>();
+ ptr->request->response->code = 200;
+ ptr->request->response->data = std::move(ptr->body);
+ ptr->request->notify();
+ }
+ }
+
+ uv_fs_req_cleanup(req);
+ uv_fs_close(req->loop, req, ptr->fd, file_closed);
+}
+
+void FileRequestBaton::file_closed(uv_fs_t *req) {
+ assert(std::this_thread::get_id() == reinterpret_cast<FileRequestBaton *>(req->data)->threadId);
+
+ if (req->result < 0) {
+ // Closing the file failed. But there isn't anything we can do.
+ }
+
+ cleanup(req);
+}
+
+void FileRequestBaton::cleanup(uv_fs_t *req) {
+ FileRequestBaton *ptr = reinterpret_cast<FileRequestBaton *>(req->data);
+ assert(std::this_thread::get_id() == ptr->threadId);
+
+ if (ptr->request) {
+ ptr->request->ptr = nullptr;
+ }
+
+ uv_fs_req_cleanup(req);
+ delete ptr;
+ ptr = nullptr;
+}
+
+}
diff --git a/src/mbgl/storage/file_request_baton.hpp b/src/mbgl/storage/file_request_baton.hpp
new file mode 100644
index 0000000000..0c3fa16da3
--- /dev/null
+++ b/src/mbgl/storage/file_request_baton.hpp
@@ -0,0 +1,36 @@
+#ifndef MBGL_STORAGE_FILE_REQUEST_BATON
+#define MBGL_STORAGE_FILE_REQUEST_BATON
+
+#include <mbgl/storage/file_request.hpp>
+#include <thread>
+
+#include <uv.h>
+
+namespace mbgl {
+
+struct FileRequestBaton {
+ FileRequestBaton(FileRequest *request_, const std::string &path, uv_loop_t *loop);
+ ~FileRequestBaton();
+
+ void cancel();
+ static void file_opened(uv_fs_t *req);
+ static void file_stated(uv_fs_t *req);
+ static void file_read(uv_fs_t *req);
+ static void file_closed(uv_fs_t *req);
+ static void notify_error(uv_fs_t *req);
+ static void cleanup(uv_fs_t *req);
+
+ const std::thread::id threadId;
+ FileRequest *request = nullptr;
+ uv_fs_t req;
+ uv_file fd = -1;
+ bool canceled = false;
+ std::string body;
+ uv_buf_t buffer;
+};
+
+
+}
+
+
+#endif
diff --git a/src/mbgl/storage/http_request.cpp b/src/mbgl/storage/http_request.cpp
new file mode 100644
index 0000000000..57e6c260ef
--- /dev/null
+++ b/src/mbgl/storage/http_request.cpp
@@ -0,0 +1,280 @@
+#include <mbgl/storage/http_request.hpp>
+#include <mbgl/storage/sqlite_store.hpp>
+#include <mbgl/storage/http_request_baton.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+#include <chrono>
+
+namespace mbgl {
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#pragma clang diagnostic ignored "-Wexit-time-destructors"
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+
+struct CacheRequestBaton {
+ HTTPRequest *request = nullptr;
+ std::string path;
+ util::ptr<SQLiteStore> store;
+};
+
+HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path_, uv_loop_t *loop_, util::ptr<SQLiteStore> store_)
+ : BaseRequest(path_), threadId(std::this_thread::get_id()), loop(loop_), store(store_), type(type_) {
+ if (store) {
+ startCacheRequest();
+ } else {
+ startHTTPRequest(nullptr);
+ }
+}
+
+void HTTPRequest::startCacheRequest() {
+ assert(std::this_thread::get_id() == threadId);
+
+ cacheBaton = new CacheRequestBaton;
+ cacheBaton->request = this;
+ cacheBaton->path = path;
+ cacheBaton->store = store;
+ store->get(path, [](std::unique_ptr<Response> &&response_, void *ptr) {
+ // Wrap in a unique_ptr, so it'll always get auto-destructed.
+ std::unique_ptr<CacheRequestBaton> baton((CacheRequestBaton *)ptr);
+ if (baton->request) {
+ baton->request->cacheBaton = nullptr;
+ baton->request->handleCacheResponse(std::move(response_));
+ }
+ }, cacheBaton);
+}
+
+void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res) {
+ assert(std::this_thread::get_id() == threadId);
+
+ if (res) {
+ // This entry was stored in the cache. Now determine if we need to revalidate.
+ const int64_t now = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count();
+ if (res->expires > now) {
+ response = std::move(res);
+ notify();
+ // Note: after calling notify(), the request object may cease to exist.
+ // This HTTPRequest is completed.
+ return;
+ } else {
+ // TODO: notify with preliminary results.
+ }
+ }
+
+ startHTTPRequest(std::move(res));
+}
+
+void HTTPRequest::startHTTPRequest(std::unique_ptr<Response> &&res) {
+ assert(std::this_thread::get_id() == threadId);
+ assert(!httpBaton);
+
+ httpBaton = std::make_shared<HTTPRequestBaton>(path);
+ httpBaton->request = this;
+ httpBaton->async = new uv_async_t;
+ httpBaton->response = std::move(res);
+ httpBaton->async->data = new util::ptr<HTTPRequestBaton>(httpBaton);
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_async_init(loop, httpBaton->async, [](uv_async_t *async, int) {
+#else
+ uv_async_init(loop, httpBaton->async, [](uv_async_t *async) {
+#endif
+ util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)async->data;
+
+ if (baton->request) {
+ HTTPRequest *request = baton->request;
+ request->httpBaton.reset();
+ baton->request = nullptr;
+ request->handleHTTPResponse(baton->type, std::move(baton->response));
+ }
+
+ delete (util::ptr<HTTPRequestBaton> *)async->data;
+ uv_close((uv_handle_t *)async, [](uv_handle_t *handle) {
+ uv_async_t *async_handle = (uv_async_t *)handle;
+ delete async_handle;
+ });
+ });
+ attempts++;
+ HTTPRequestBaton::start(httpBaton);
+}
+
+
+
+void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&res) {
+ assert(std::this_thread::get_id() == threadId);
+ assert(!httpBaton);
+ assert(!response);
+
+ switch (responseType) {
+ // This error was caused by a temporary error and it is likely that it will be resolved
+ // immediately. We are going to try again right away. This is like the TemporaryError,
+ // except that we will not perform exponential back-off.
+ case HTTPResponseType::SingularError:
+ if (attempts >= 4) {
+ // Report as error after 4 attempts.
+ response = std::move(res);
+ notify();
+ } else if (attempts >= 2) {
+ // Switch to the back-off algorithm after the second failure.
+ retryHTTPRequest(std::move(res), (1 << attempts) * 1000);
+ return;
+ } else {
+ startHTTPRequest(std::move(res));
+ }
+ break;
+
+ // This error might be resolved by waiting some time (e.g. server issues).
+ // We are going to do an exponential back-off and will try again in a few seconds.
+ case HTTPResponseType::TemporaryError:
+ if (attempts >= 4) {
+ // Report error back after it failed completely.
+ response = std::move(res);
+ notify();
+ } else {
+ retryHTTPRequest(std::move(res), (1 << attempts) * 1000);
+ }
+ break;
+
+ // This error might be resolved once the network reachability status changes.
+ // We are going to watch the network status for changes and will retry as soon as the
+ // operating system notifies us of a network status change.
+ case HTTPResponseType::ConnectionError:
+
+ if (attempts >= 4) {
+ // Report error back after it failed completely.
+ response = std::move(res);
+ notify();
+ } else {
+ // By default, we will retry every 60 seconds.
+ retryHTTPRequest(std::move(res), 60000);
+ }
+ break;
+
+ // The request was canceled programatically.
+ case HTTPResponseType::Canceled:
+ response.reset();
+ notify();
+ break;
+
+ // This error probably won't be resolved by retrying anytime soon. We are giving up.
+ case HTTPResponseType::PermanentError:
+ response = std::move(res);
+ notify();
+ break;
+
+ // The request returned data successfully. We retrieved and decoded the data successfully.
+ case HTTPResponseType::Successful:
+ if (store) {
+ store->put(path, type, *res);
+ }
+ response = std::move(res);
+ notify();
+ break;
+
+ // The request confirmed that the data wasn't changed. We already have the data.
+ case HTTPResponseType::NotModified:
+ if (store) {
+ store->updateExpiration(path, res->expires);
+ }
+ response = std::move(res);
+ notify();
+ break;
+
+ default:
+ assert(!"Response wasn't set");
+ break;
+ }
+}
+
+using RetryBaton = std::pair<HTTPRequest *, std::unique_ptr<Response>>;
+
+void HTTPRequest::retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout) {
+ assert(std::this_thread::get_id() == threadId);
+ assert(!backoffTimer);
+ backoffTimer = new uv_timer_t();
+ uv_timer_init(loop, backoffTimer);
+ backoffTimer->data = new RetryBaton(this, std::move(res));
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_timer_start(backoffTimer, [](uv_timer_t *timer, int) {
+#else
+ uv_timer_start(backoffTimer, [](uv_timer_t *timer) {
+#endif
+ std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(timer->data) };
+ pair->first->startHTTPRequest(std::move(pair->second));
+ pair->first->backoffTimer = nullptr;
+ uv_timer_stop(timer);
+ uv_close((uv_handle_t *)timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
+ }, timeout, 0);
+}
+
+void HTTPRequest::removeHTTPBaton() {
+ assert(std::this_thread::get_id() == threadId);
+ if (httpBaton) {
+ httpBaton->request = nullptr;
+ HTTPRequestBaton::stop(httpBaton);
+ httpBaton.reset();
+ }
+}
+
+void HTTPRequest::removeCacheBaton() {
+ assert(std::this_thread::get_id() == threadId);
+ if (cacheBaton) {
+ // Make sre that this object doesn't accidentally get accessed when it is destructed before
+ // the callback returned. They are being run in the same thread, so just setting it to
+ // null is sufficient.
+ // Note: We don't manually delete the CacheRequestBaton since it'll be deleted by the
+ // callback.
+ cacheBaton->request = nullptr;
+ cacheBaton = nullptr;
+ }
+}
+
+void HTTPRequest::removeBackoffTimer() {
+ assert(std::this_thread::get_id() == threadId);
+ if (backoffTimer) {
+ delete static_cast<RetryBaton *>(backoffTimer->data);
+ uv_timer_stop(backoffTimer);
+ uv_close((uv_handle_t *)backoffTimer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
+ backoffTimer = nullptr;
+ }
+}
+
+void HTTPRequest::retryImmediately() {
+ assert(std::this_thread::get_id() == threadId);
+ if (!cacheBaton && !httpBaton) {
+ if (backoffTimer) {
+ // Retry immediately.
+ uv_timer_stop(backoffTimer);
+ std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(backoffTimer->data) };
+ assert(pair->first == this);
+ startHTTPRequest(std::move(pair->second));
+ uv_close((uv_handle_t *)backoffTimer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; });
+ backoffTimer = nullptr;
+ } else {
+ assert(!"We should always have a backoffTimer when there are no batons");
+ }
+ }
+}
+
+void HTTPRequest::cancel() {
+ assert(std::this_thread::get_id() == threadId);
+ removeCacheBaton();
+ removeHTTPBaton();
+ removeBackoffTimer();
+ notify();
+}
+
+
+HTTPRequest::~HTTPRequest() {
+ assert(std::this_thread::get_id() == threadId);
+ cancel();
+}
+
+#pragma clang diagnostic pop
+
+}
diff --git a/src/mbgl/storage/http_request.hpp b/src/mbgl/storage/http_request.hpp
new file mode 100644
index 0000000000..7cc72101d5
--- /dev/null
+++ b/src/mbgl/storage/http_request.hpp
@@ -0,0 +1,58 @@
+#ifndef MBGL_STORAGE_HTTP_REQUEST
+#define MBGL_STORAGE_HTTP_REQUEST
+
+#include <mbgl/storage/resource_type.hpp>
+#include <mbgl/storage/base_request.hpp>
+#include <mbgl/storage/http_request_baton.hpp>
+
+#include <string>
+#include <memory>
+#include <cassert>
+#include <thread>
+
+typedef struct uv_loop_s uv_loop_t;
+typedef struct uv_timer_s uv_timer_t;
+
+namespace mbgl {
+
+struct CacheRequestBaton;
+struct HTTPRequestBaton;
+struct CacheEntry;
+class SQLiteStore;
+
+class HTTPRequest : public BaseRequest {
+public:
+ HTTPRequest(ResourceType type, const std::string &path, uv_loop_t *loop, util::ptr<SQLiteStore> store);
+ ~HTTPRequest();
+
+ void cancel();
+ void retryImmediately();
+
+private:
+ void startCacheRequest();
+ void handleCacheResponse(std::unique_ptr<Response> &&response);
+ void startHTTPRequest(std::unique_ptr<Response> &&res);
+ void handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&response);
+
+ void retryHTTPRequest(std::unique_ptr<Response> &&res, uint64_t timeout);
+
+ void removeCacheBaton();
+ void removeHTTPBaton();
+ void removeBackoffTimer();
+
+private:
+ const std::thread::id threadId;
+ uv_loop_t *const loop;
+ CacheRequestBaton *cacheBaton = nullptr;
+ util::ptr<HTTPRequestBaton> httpBaton;
+ uv_timer_t *backoffTimer = nullptr;
+ util::ptr<SQLiteStore> store;
+ const ResourceType type;
+ uint8_t attempts = 0;
+
+ friend struct HTTPRequestBaton;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/storage/http_request_baton.cpp b/src/mbgl/storage/http_request_baton.cpp
new file mode 100644
index 0000000000..d781a3bdf4
--- /dev/null
+++ b/src/mbgl/storage/http_request_baton.cpp
@@ -0,0 +1,12 @@
+#include <mbgl/storage/http_request_baton.hpp>
+#include <uv.h>
+
+namespace mbgl {
+
+HTTPRequestBaton::HTTPRequestBaton(const std::string &path_) : threadId(std::this_thread::get_id()), path(path_) {
+}
+
+HTTPRequestBaton::~HTTPRequestBaton() {
+}
+
+}
diff --git a/src/mbgl/storage/request.cpp b/src/mbgl/storage/request.cpp
new file mode 100644
index 0000000000..39fbd36789
--- /dev/null
+++ b/src/mbgl/storage/request.cpp
@@ -0,0 +1,49 @@
+#include <mbgl/storage/request.hpp>
+#include <mbgl/storage/base_request.hpp>
+
+#include <uv.h>
+
+#include <cassert>
+
+namespace mbgl {
+
+Request::Request(const util::ptr<BaseRequest> &base_)
+ : thread_id(std::this_thread::get_id()), base(base_) {
+}
+
+Request::~Request() {
+ assert(thread_id == std::this_thread::get_id());
+}
+
+void Request::onload(CompletedCallback cb) {
+ assert(thread_id == std::this_thread::get_id());
+ if (base) {
+ Callback *callback = base->add(std::move(cb), base);
+ if (callback) {
+ callbacks.push_front(callback);
+ }
+ }
+}
+
+void Request::oncancel(AbortedCallback cb) {
+ assert(thread_id == std::this_thread::get_id());
+ if (base) {
+ Callback *callback = base->add(std::move(cb), base);
+ if (callback) {
+ callbacks.push_front(callback);
+ }
+ }
+}
+
+void Request::cancel() {
+ assert(thread_id == std::this_thread::get_id());
+ if (base) {
+ for (Callback *callback : callbacks) {
+ base->remove(callback);
+ }
+ base.reset();
+ }
+ callbacks.clear();
+}
+
+}
diff --git a/src/mbgl/storage/response.cpp b/src/mbgl/storage/response.cpp
new file mode 100644
index 0000000000..a08a6d31ce
--- /dev/null
+++ b/src/mbgl/storage/response.cpp
@@ -0,0 +1,22 @@
+#include <mbgl/storage/response.hpp>
+
+#include <chrono>
+
+namespace mbgl {
+
+int64_t Response::parseCacheControl(const char *value) {
+ if (value) {
+ unsigned long long seconds = 0;
+ // TODO: cache-control may contain other information as well:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ if (std::sscanf(value, "max-age=%llu", &seconds) == 1) {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count() +
+ seconds;
+ }
+ }
+
+ return -1;
+}
+
+}
diff --git a/src/mbgl/storage/sqlite_store.cpp b/src/mbgl/storage/sqlite_store.cpp
new file mode 100644
index 0000000000..d382921dec
--- /dev/null
+++ b/src/mbgl/storage/sqlite_store.cpp
@@ -0,0 +1,228 @@
+#include <mbgl/storage/sqlite_store.hpp>
+#include <mbgl/util/compression.hpp>
+#include <mbgl/util/sqlite3.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <mbgl/util/uv-worker.h>
+
+#include <cassert>
+
+using namespace mapbox::sqlite;
+
+std::string removeAccessTokenFromURL(const std::string &url) {
+ const size_t token_start = url.find("access_token=");
+ // Ensure that token exists, isn't at the front and is preceded by either & or ?.
+ if (token_start == std::string::npos || token_start == 0 || !(url[token_start - 1] == '&' || url[token_start - 1] == '?')) {
+ return url;
+ }
+
+ const size_t token_end = url.find_first_of('&', token_start);
+ if (token_end == std::string::npos) {
+ // The token is the last query argument. We slice away the "&access_token=..." part
+ return url.substr(0, token_start - 1);
+ } else {
+ // We slice away the "access_token=...&" part.
+ return url.substr(0, token_start) + url.substr(token_end + 1);
+ }
+}
+
+std::string convertMapboxDomainsToProtocol(const std::string &url) {
+ const size_t protocol_separator = url.find("://");
+ if (protocol_separator == std::string::npos) {
+ return url;
+ }
+
+ const std::string protocol = url.substr(0, protocol_separator);
+ if (!(protocol == "http" || protocol == "https")) {
+ return url;
+ }
+
+ const size_t domain_begin = protocol_separator + 3;
+ const size_t path_separator = url.find("/", domain_begin);
+ if (path_separator == std::string::npos) {
+ return url;
+ }
+
+ const std::string domain = url.substr(domain_begin, path_separator - domain_begin);
+ if (domain.find(".tiles.mapbox.com") != std::string::npos) {
+ return "mapbox://" + url.substr(path_separator + 1);
+ } else {
+ return url;
+ }
+}
+
+std::string unifyMapboxURLs(const std::string &url) {
+ return removeAccessTokenFromURL(convertMapboxDomainsToProtocol(url));
+}
+
+namespace mbgl {
+
+SQLiteStore::SQLiteStore(uv_loop_t *loop, const std::string &path)
+ : thread_id(std::this_thread::get_id()),
+ db(std::make_shared<Database>(path.c_str(), ReadWrite | Create)) {
+ createSchema();
+ worker = new uv_worker_t;
+ uv_worker_init(worker, loop, 1, "SQLite");
+}
+
+SQLiteStore::~SQLiteStore() {
+ // Nothing to do. This function needs to be here because we're forward-declaring
+ // Database, so we need the actual definition here to be able to properly destruct it.
+ if (worker) {
+ uv_worker_close(worker, [](uv_worker_t *worker_handle) {
+ delete worker_handle;
+ });
+ }
+}
+
+void SQLiteStore::createSchema() {
+ if (!db || !*db) {
+ return;
+ }
+
+ db->exec("CREATE TABLE IF NOT EXISTS `http_cache` ("
+ " `url` TEXT PRIMARY KEY NOT NULL,"
+ " `code` INTEGER NOT NULL,"
+ " `type` INTEGER NOT NULL,"
+ " `modified` INTEGER,"
+ " `etag` TEXT,"
+ " `expires` INTEGER,"
+ " `data` BLOB,"
+ " `compressed` INTEGER NOT NULL DEFAULT 0"
+ ");"
+ "CREATE INDEX IF NOT EXISTS `http_cache_type_idx` ON `http_cache` (`type`);");
+}
+
+struct GetBaton {
+ util::ptr<Database> db;
+ std::string path;
+ ResourceType type;
+ void *ptr = nullptr;
+ SQLiteStore::GetCallback callback = nullptr;
+ std::unique_ptr<Response> response;
+};
+
+void SQLiteStore::get(const std::string &path, GetCallback callback, void *ptr) {
+ assert(std::this_thread::get_id() == thread_id);
+ if (!db || !*db) {
+ if (callback) {
+ callback(nullptr, ptr);
+ }
+ return;
+ }
+
+ GetBaton *get_baton = new GetBaton;
+ get_baton->db = db;
+ get_baton->path = path;
+ get_baton->ptr = ptr;
+ get_baton->callback = callback;
+
+ uv_worker_send(worker, get_baton, [](void *data) {
+ GetBaton *baton = (GetBaton *)data;
+ const std::string url = unifyMapboxURLs(baton->path);
+ // 0 1 2
+ Statement stmt = baton->db->prepare("SELECT `code`, `type`, `modified`, "
+ // 3 4 5 6
+ "`etag`, `expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?");
+
+ stmt.bind(1, url.c_str());
+ if (stmt.run()) {
+ // There is data.
+ baton->response = util::make_unique<Response>();
+
+ baton->response->code = stmt.get<int>(0);
+ baton->type = ResourceType(stmt.get<int>(1));
+ baton->response->modified = stmt.get<int64_t>(2);
+ baton->response->etag = stmt.get<std::string>(3);
+ baton->response->expires = stmt.get<int64_t>(4);
+ baton->response->data = stmt.get<std::string>(5);
+ if (stmt.get<int>(6)) { // == compressed
+ baton->response->data = util::decompress(baton->response->data);
+ }
+ } else {
+ // There is no data.
+ // This is a noop.
+ }
+ }, [](void *data) {
+ std::unique_ptr<GetBaton> baton { (GetBaton *)data };
+ if (baton->callback) {
+ baton->callback(std::move(baton->response), baton->ptr);
+ }
+ });
+}
+
+
+struct PutBaton {
+ util::ptr<Database> db;
+ std::string path;
+ ResourceType type;
+ Response response;
+};
+
+void SQLiteStore::put(const std::string &path, ResourceType type, const Response &response) {
+ assert(std::this_thread::get_id() == thread_id);
+ if (!db) return;
+
+ PutBaton *put_baton = new PutBaton;
+ put_baton->db = db;
+ put_baton->path = path;
+ put_baton->type = type;
+ put_baton->response = response;
+
+ uv_worker_send(worker, put_baton, [](void *data) {
+ PutBaton *baton = (PutBaton *)data;
+ const std::string url = unifyMapboxURLs(baton->path);
+ Statement stmt = baton->db->prepare("REPLACE INTO `http_cache` ("
+ // 1 2 3 4 5 6 7 8
+ "`url`, `code`, `type`, `modified`, `etag`, `expires`, `data`, `compressed`"
+ ") VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
+ stmt.bind(1, url.c_str());
+ stmt.bind(2, int(baton->response.code));
+ stmt.bind(3, int(baton->type));
+ stmt.bind(4, baton->response.modified);
+ stmt.bind(5, baton->response.etag.c_str());
+ stmt.bind(6, baton->response.expires);
+
+ if (baton->type == ResourceType::Image) {
+ stmt.bind(7, baton->response.data, false); // do not retain the string internally.
+ stmt.bind(8, false);
+ } else {
+ stmt.bind(7, util::compress(baton->response.data), true); // retain the string internally.
+ stmt.bind(8, true);
+ }
+
+ stmt.run();
+ }, [](void *data) {
+ delete (PutBaton *)data;
+ });
+}
+
+struct ExpirationBaton {
+ util::ptr<Database> db;
+ std::string path;
+ int64_t expires;
+};
+
+void SQLiteStore::updateExpiration(const std::string &path, int64_t expires) {
+ assert(std::this_thread::get_id() == thread_id);
+ if (!db || !*db) return;
+
+ ExpirationBaton *expiration_baton = new ExpirationBaton;
+ expiration_baton->db = db;
+ expiration_baton->path = path;
+ expiration_baton->expires = expires;
+
+ uv_worker_send(worker, expiration_baton, [](void *data) {
+ ExpirationBaton *baton = (ExpirationBaton *)data;
+ const std::string url = unifyMapboxURLs(baton->path);
+ Statement stmt = // 1 2
+ baton->db->prepare("UPDATE `http_cache` SET `expires` = ? WHERE `url` = ?");
+ stmt.bind<int64_t>(1, baton->expires);
+ stmt.bind(2, url.c_str());
+ stmt.run();
+ }, [](void *data) {
+ delete (ExpirationBaton *)data;
+ });
+}
+
+}
diff --git a/src/mbgl/storage/sqlite_store.hpp b/src/mbgl/storage/sqlite_store.hpp
new file mode 100644
index 0000000000..988eca2597
--- /dev/null
+++ b/src/mbgl/storage/sqlite_store.hpp
@@ -0,0 +1,49 @@
+#ifndef MBGL_STORAGE_SQLITE_STORE
+#define MBGL_STORAGE_SQLITE_STORE
+
+#include <mbgl/storage/response.hpp>
+#include <mbgl/storage/resource_type.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <uv.h>
+
+#include <string>
+#include <thread>
+
+typedef struct uv_worker_s uv_worker_t;
+
+namespace mapbox {
+namespace sqlite {
+class Database;
+}
+}
+
+namespace mbgl {
+
+class SQLiteStore {
+public:
+ SQLiteStore(uv_loop_t *loop, const std::string &path);
+ ~SQLiteStore();
+
+ typedef void (*GetCallback)(std::unique_ptr<Response> &&entry, void *ptr);
+
+ void get(const std::string &path, GetCallback cb, void *ptr);
+ void put(const std::string &path, ResourceType type, const Response &entry);
+ void updateExpiration(const std::string &path, int64_t expires);
+
+private:
+ void createSchema();
+ void closeDatabase();
+ static void runGet(uv_work_t *req);
+ static void runPut(uv_work_t *req);
+ static void deliverResult(uv_work_t *req, int status);
+
+private:
+ const std::thread::id thread_id;
+ util::ptr<mapbox::sqlite::Database> db;
+ uv_worker_t *worker = nullptr;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/applied_class_properties.cpp b/src/mbgl/style/applied_class_properties.cpp
new file mode 100644
index 0000000000..9037c6ad5d
--- /dev/null
+++ b/src/mbgl/style/applied_class_properties.cpp
@@ -0,0 +1,52 @@
+#include <mbgl/style/applied_class_properties.hpp>
+
+namespace mbgl {
+
+AppliedClassProperty::AppliedClassProperty(ClassID class_id, timestamp begin_, timestamp end_, const PropertyValue &value_)
+ : name(class_id),
+ begin(begin_),
+ end(end_),
+ value(value_) {}
+
+// Returns thie ID of the most recent
+ClassID AppliedClassProperties::mostRecent() const {
+ return properties.size() ? properties.back().name : ClassID::Fallback;
+}
+
+void AppliedClassProperties::add(ClassID class_id, timestamp begin, timestamp end, const PropertyValue &value) {
+ properties.emplace_back(class_id, begin, end, value);
+}
+
+bool AppliedClassProperties::hasTransitions() const {
+ return properties.size() > 1;
+}
+
+// Erase all items in the property list that are before a completed transition.
+// Then, if the only remaining property is a Fallback value, remove it too.
+void AppliedClassProperties::cleanup(timestamp now) {
+ // Iterate backwards, but without using the rbegin/rend interface since we need forward
+ // iterators to use .erase().
+ for (auto it = properties.end(), begin = properties.begin(); it != begin;) {
+ // If the property is finished, break iteration and delete all remaining items.
+ if ((--it)->end <= now) {
+ // Removes all items that precede the current iterator, but *not* the element currently
+ // pointed to by the iterator. This preserves the last completed transition as the
+ // first element in the property list.
+ properties.erase(begin, it);
+
+ // Also erase the pivot element if it's a fallback value. This means we can remove the
+ // entire applied properties object as well, because we already have the fallback
+ // value set as the default.
+ if (it->name == ClassID::Fallback) {
+ properties.erase(it);
+ }
+ break;
+ }
+ }
+}
+
+bool AppliedClassProperties::empty() const {
+ return properties.empty();
+}
+
+} \ No newline at end of file
diff --git a/src/mbgl/style/applied_class_properties.hpp b/src/mbgl/style/applied_class_properties.hpp
new file mode 100644
index 0000000000..827f15a2a1
--- /dev/null
+++ b/src/mbgl/style/applied_class_properties.hpp
@@ -0,0 +1,39 @@
+#ifndef MBGL_STYLE_APPLIED_CLASS_PROPERTIES
+#define MBGL_STYLE_APPLIED_CLASS_PROPERTIES
+
+#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/class_dictionary.hpp>
+#include <mbgl/util/time.hpp>
+
+#include <list>
+
+namespace mbgl {
+
+class AppliedClassProperty {
+public:
+ AppliedClassProperty(ClassID class_id, timestamp begin, timestamp end, const PropertyValue &value);
+
+public:
+ const ClassID name;
+ const timestamp begin;
+ const timestamp end;
+ const PropertyValue value;
+};
+
+
+class AppliedClassProperties {
+public:
+ std::list<AppliedClassProperty> properties;
+
+public:
+ // Returns thie ID of the most recent
+ ClassID mostRecent() const;
+ void add(ClassID class_id, timestamp begin, timestamp end, const PropertyValue &value);
+ bool hasTransitions() const;
+ void cleanup(timestamp now);
+ bool empty() const;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/class_dictionary.cpp b/src/mbgl/style/class_dictionary.cpp
new file mode 100644
index 0000000000..ba7c0d55be
--- /dev/null
+++ b/src/mbgl/style/class_dictionary.cpp
@@ -0,0 +1,51 @@
+#include <mbgl/style/class_dictionary.hpp>
+
+#include <uv.h>
+
+namespace mbgl {
+
+ClassDictionary::ClassDictionary() {}
+
+ClassDictionary &ClassDictionary::Get() {
+ // Note: We should eventually switch to uv_key_* functions, but libuv 0.10 doesn't have these
+ // yet. Instead, we're using the pthread functions directly for now.
+ static pthread_once_t store_once = PTHREAD_ONCE_INIT;
+ static pthread_key_t store_key;
+
+ // Create the key.
+ pthread_once(&store_once, []() {
+ pthread_key_create(&store_key, [](void *ptr) {
+ delete reinterpret_cast<ClassDictionary *>(ptr);
+ });
+ });
+
+ ClassDictionary *ptr = reinterpret_cast<ClassDictionary *>(pthread_getspecific(store_key));
+ if (ptr == nullptr) {
+ ptr = new ClassDictionary();
+ pthread_setspecific(store_key, ptr);
+ }
+
+ return *ptr;
+}
+
+ClassID ClassDictionary::lookup(const std::string &class_name) {
+ auto it = store.find(class_name);
+ if (it == store.end()) {
+ // Insert the class name into the store.
+ ClassID id = ClassID(uint32_t(ClassID::Named) + offset++);
+ store.emplace(class_name, id);
+ return id;
+ } else {
+ return it->second;
+ }
+}
+
+ClassID ClassDictionary::normalize(ClassID id) {
+ if (id >= ClassID::Named) {
+ return ClassID::Named;
+ } else {
+ return id;
+ }
+}
+
+}
diff --git a/src/mbgl/style/class_dictionary.hpp b/src/mbgl/style/class_dictionary.hpp
new file mode 100644
index 0000000000..ecf80be3e3
--- /dev/null
+++ b/src/mbgl/style/class_dictionary.hpp
@@ -0,0 +1,37 @@
+#ifndef MBGL_STYLE_CLASS_DICTIONARY
+#define MBGL_STYLE_CLASS_DICTIONARY
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+
+namespace mbgl {
+
+enum class ClassID : uint32_t {
+ Fallback = 0, // These values are from the fallback properties
+ Default = 1, // These values are from the default style for a layer
+ Named = 2 // These values (and all subsequent IDs) are from a named style from the layer
+};
+
+class ClassDictionary {
+private:
+ ClassDictionary();
+
+public:
+ static ClassDictionary &Get();
+
+ // Returns an ID for a class name. If the class name does not yet have an ID, one is
+ // auto-generated and stored for future reference.
+ ClassID lookup(const std::string &class_name);
+
+ // Returns either Fallback, Default or Named, depending on the type of the class id.
+ ClassID normalize(ClassID id);
+
+private:
+ std::unordered_map<std::string, ClassID> store = { { "", ClassID::Default } };
+ uint32_t offset = 0;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/class_properties.cpp b/src/mbgl/style/class_properties.cpp
new file mode 100644
index 0000000000..e7bf855bfc
--- /dev/null
+++ b/src/mbgl/style/class_properties.cpp
@@ -0,0 +1,14 @@
+#include <mbgl/style/class_properties.hpp>
+
+namespace mbgl {
+
+const PropertyTransition &ClassProperties::getTransition(PropertyKey key, const PropertyTransition &defaultTransition) const {
+ auto it = transitions.find(key);
+ if (it == transitions.end()) {
+ return defaultTransition;
+ } else {
+ return it->second;
+ }
+}
+
+}
diff --git a/src/mbgl/style/class_properties.hpp b/src/mbgl/style/class_properties.hpp
new file mode 100644
index 0000000000..888a90c5d7
--- /dev/null
+++ b/src/mbgl/style/class_properties.hpp
@@ -0,0 +1,43 @@
+#ifndef MBGL_STYLE_CLASS_PROPERTIES
+#define MBGL_STYLE_CLASS_PROPERTIES
+
+#include <mbgl/style/property_key.hpp>
+#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/property_transition.hpp>
+
+#include <map>
+
+namespace mbgl {
+
+class ClassProperties {
+public:
+ inline ClassProperties() {}
+ inline ClassProperties(ClassProperties &&properties_)
+ : properties(std::move(properties_.properties)) {}
+
+ inline void set(PropertyKey key, const PropertyValue &value) {
+ properties.emplace(key, value);
+ }
+
+ inline void set(PropertyKey key, const PropertyTransition &transition) {
+ transitions.emplace(key, transition);
+ }
+
+ const PropertyTransition &getTransition(PropertyKey key, const PropertyTransition &defaultTransition) const;
+
+ // Route-through iterable interface so that you can iterate on the object as is.
+ inline std::map<PropertyKey, PropertyValue>::const_iterator begin() const {
+ return properties.begin();
+ }
+ inline std::map<PropertyKey, PropertyValue>::const_iterator end() const {
+ return properties.end();
+ }
+
+public:
+ std::map<PropertyKey, PropertyValue> properties;
+ std::map<PropertyKey, PropertyTransition> transitions;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/filter_expression.cpp b/src/mbgl/style/filter_expression.cpp
new file mode 100644
index 0000000000..7d4f60b3ed
--- /dev/null
+++ b/src/mbgl/style/filter_expression.cpp
@@ -0,0 +1,123 @@
+#include <mbgl/map/vector_tile.hpp>
+#include <mbgl/platform/log.hpp>
+
+namespace mbgl {
+
+Value parseFeatureType(const Value& value) {
+ if (value == std::string("Point")) {
+ return Value(uint64_t(FeatureType::Point));
+ } else if (value == std::string("LineString")) {
+ return Value(uint64_t(FeatureType::LineString));
+ } else if (value == std::string("Polygon")) {
+ return Value(uint64_t(FeatureType::Polygon));
+ } else {
+ Log::Warning(Event::ParseStyle, "value for $type filter must be Point, LineString, or Polygon");
+ return Value(uint64_t(FeatureType::Unknown));
+ }
+}
+
+template <class Expression>
+FilterExpression parseBinaryFilter(const rapidjson::Value& value) {
+ FilterExpression empty;
+
+ if (value.Size() < 3) {
+ Log::Warning(Event::ParseStyle, "filter expression must have 3 elements");
+ return empty;
+ }
+
+ if (!value[1u].IsString()) {
+ Log::Warning(Event::ParseStyle, "filter expression key must be a string");
+ return empty;
+ }
+
+ Expression expression;
+ expression.key = { value[1u].GetString(), value[1u].GetStringLength() };
+ expression.value = parseValue(value[2u]);
+
+ if (expression.key == "$type") {
+ expression.value = parseFeatureType(expression.value);
+ }
+
+ return expression;
+}
+
+template <class Expression>
+FilterExpression parseSetFilter(const rapidjson::Value& value) {
+ FilterExpression empty;
+
+ if (value.Size() < 2) {
+ Log::Warning(Event::ParseStyle, "filter expression must at least 2 elements");
+ return empty;
+ }
+
+ if (!value[1u].IsString()) {
+ Log::Warning(Event::ParseStyle, "filter expression key must be a string");
+ return empty;
+ }
+
+ Expression expression;
+ expression.key = { value[1u].GetString(), value[1u].GetStringLength() };
+ for (rapidjson::SizeType i = 2; i < value.Size(); ++i) {
+ expression.values.push_back(parseValue(value[i]));
+ }
+ return expression;
+}
+
+template <class Expression>
+FilterExpression parseCompoundFilter(const rapidjson::Value& value) {
+ Expression expression;
+ for (rapidjson::SizeType i = 1; i < value.Size(); ++i) {
+ expression.expressions.push_back(parseFilterExpression(value[i]));
+ }
+ return expression;
+}
+
+FilterExpression parseFilterExpression(const rapidjson::Value& value) {
+ FilterExpression empty;
+
+ if (!value.IsArray()) {
+ Log::Warning(Event::ParseStyle, "filter expression must be an array");
+ return empty;
+ }
+
+ if (value.Size() < 1) {
+ Log::Warning(Event::ParseStyle, "filter expression must have at least 1 element");
+ return empty;
+ }
+
+ if (!value[0u].IsString()) {
+ Log::Warning(Event::ParseStyle, "filter operator must be a string");
+ return empty;
+ }
+
+ std::string op = { value[0u].GetString(), value[0u].GetStringLength() };
+
+ if (op == "==") {
+ return parseBinaryFilter<EqualsExpression>(value);
+ } else if (op == "!=") {
+ return parseBinaryFilter<NotEqualsExpression>(value);
+ } else if (op == ">") {
+ return parseBinaryFilter<GreaterThanExpression>(value);
+ } else if (op == ">=") {
+ return parseBinaryFilter<GreaterThanEqualsExpression>(value);
+ } else if (op == "<") {
+ return parseBinaryFilter<LessThanExpression>(value);
+ } else if (op == "<=") {
+ return parseBinaryFilter<LessThanEqualsExpression>(value);
+ } else if (op == "in") {
+ return parseSetFilter<InExpression>(value);
+ } else if (op == "!in") {
+ return parseSetFilter<NotInExpression>(value);
+ } else if (op == "all") {
+ return parseCompoundFilter<AllExpression>(value);
+ } else if (op == "any") {
+ return parseCompoundFilter<AnyExpression>(value);
+ } else if (op == "none") {
+ return parseCompoundFilter<NoneExpression>(value);
+ } else {
+ Log::Warning(Event::ParseStyle, "filter operator must be one of \"==\", \"!=\", \">\", \">=\", \"<\", \"<=\", \"in\", \"!in\", \"all\", \"any\", \"none\"");
+ return empty;
+ }
+}
+
+}
diff --git a/src/mbgl/style/filter_expression.hpp b/src/mbgl/style/filter_expression.hpp
new file mode 100644
index 0000000000..8c6f447770
--- /dev/null
+++ b/src/mbgl/style/filter_expression.hpp
@@ -0,0 +1,125 @@
+#ifndef MBGL_STYLE_FILTER_EXPRESSION
+#define MBGL_STYLE_FILTER_EXPRESSION
+
+#include <mbgl/style/value.hpp>
+
+#include <rapidjson/document.h>
+
+#include <string>
+#include <vector>
+
+namespace mbgl {
+
+typedef mapbox::util::variant<
+ struct NullExpression,
+ struct EqualsExpression,
+ struct NotEqualsExpression,
+ struct LessThanExpression,
+ struct LessThanEqualsExpression,
+ struct GreaterThanExpression,
+ struct GreaterThanEqualsExpression,
+ struct InExpression,
+ struct NotInExpression,
+ struct AnyExpression,
+ struct AllExpression,
+ struct NoneExpression
+ > FilterExpression;
+
+FilterExpression parseFilterExpression(const rapidjson::Value&);
+
+template <class Extractor>
+bool evaluate(const FilterExpression&, const Extractor&);
+
+struct NullExpression {
+ template <class Extractor>
+ bool evaluate(const Extractor&) const { return true; }
+};
+
+struct EqualsExpression {
+ std::string key;
+ Value value;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct NotEqualsExpression {
+ std::string key;
+ Value value;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct LessThanExpression {
+ std::string key;
+ Value value;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct LessThanEqualsExpression {
+ std::string key;
+ Value value;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct GreaterThanExpression {
+ std::string key;
+ Value value;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct GreaterThanEqualsExpression {
+ std::string key;
+ Value value;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct InExpression {
+ std::string key;
+ std::vector<Value> values;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct NotInExpression {
+ std::string key;
+ std::vector<Value> values;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct AnyExpression {
+ std::vector<FilterExpression> expressions;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct AllExpression {
+ std::vector<FilterExpression> expressions;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+struct NoneExpression {
+ std::vector<FilterExpression> expressions;
+
+ template <class Extractor>
+ bool evaluate(const Extractor&) const;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/filter_expression_private.hpp b/src/mbgl/style/filter_expression_private.hpp
new file mode 100644
index 0000000000..381f8f617c
--- /dev/null
+++ b/src/mbgl/style/filter_expression_private.hpp
@@ -0,0 +1,118 @@
+#include <mbgl/util/optional.hpp>
+#include <mbgl/style/value_comparison.hpp>
+
+namespace mbgl {
+
+template <class Extractor>
+struct Evaluator : public mapbox::util::static_visitor<bool>
+{
+ const Extractor& extractor;
+
+ Evaluator(const Extractor& extractor_)
+ : extractor(extractor_) {}
+
+ template <class E>
+ bool operator()(const E& e) const { return e.evaluate(extractor); }
+};
+
+template <class Extractor>
+bool evaluate(const FilterExpression& expression, const Extractor& extractor) {
+ return mapbox::util::apply_visitor(Evaluator<Extractor>(extractor), expression);
+};
+
+template <class Extractor>
+bool EqualsExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ return actual && util::relaxed_equal(*actual, value);
+}
+
+template <class Extractor>
+bool NotEqualsExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ return !actual || util::relaxed_not_equal(*actual, value);
+}
+
+template <class Extractor>
+bool LessThanExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ return actual && util::relaxed_less(*actual, value);
+}
+
+template <class Extractor>
+bool LessThanEqualsExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ return actual && util::relaxed_less_equal(*actual, value);
+}
+
+template <class Extractor>
+bool GreaterThanExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ return actual && util::relaxed_greater(*actual, value);
+}
+
+template <class Extractor>
+bool GreaterThanEqualsExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ return actual && util::relaxed_greater_equal(*actual, value);
+}
+
+template <class Extractor>
+bool InExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ if (!actual)
+ return false;
+ for (const auto& v: values) {
+ if (util::relaxed_equal(*actual, v)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <class Extractor>
+bool NotInExpression::evaluate(const Extractor& extractor) const {
+ mapbox::util::optional<Value> actual = extractor.getValue(key);
+ if (!actual)
+ return true;
+ for (const auto& v: values) {
+ if (util::relaxed_equal(*actual, v)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <class Extractor>
+bool AnyExpression::evaluate(const Extractor& extractor) const {
+ Evaluator<Extractor> evaluator(extractor);
+ for (const auto& e: expressions) {
+ if (mapbox::util::apply_visitor(evaluator, e)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <class Extractor>
+bool AllExpression::evaluate(const Extractor& extractor) const {
+ Evaluator<Extractor> evaluator(extractor);
+ for (const auto& e: expressions) {
+ if (!mapbox::util::apply_visitor(evaluator, e)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <class Extractor>
+bool NoneExpression::evaluate(const Extractor& extractor) const {
+ Evaluator<Extractor> evaluator(extractor);
+ for (const auto& e: expressions) {
+ if (mapbox::util::apply_visitor(evaluator, e)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+}
diff --git a/src/mbgl/style/function_properties.cpp b/src/mbgl/style/function_properties.cpp
new file mode 100644
index 0000000000..69466c1f64
--- /dev/null
+++ b/src/mbgl/style/function_properties.cpp
@@ -0,0 +1,68 @@
+#include <mbgl/style/function_properties.hpp>
+#include <mbgl/style/types.hpp>
+#include <mbgl/util/interpolate.hpp>
+
+#include <cmath>
+
+namespace mbgl {
+
+template <typename T>
+inline T defaultStopsValue();
+
+template <> inline bool defaultStopsValue() { return true; }
+template <> inline float defaultStopsValue() { return 1.0f; }
+template <> inline Color defaultStopsValue() { return {{ 0, 0, 0, 1 }}; }
+
+
+template <typename T>
+T StopsFunction<T>::evaluate(float z) const {
+ bool smaller = false;
+ float smaller_z = 0.0f;
+ T smaller_val = T();
+ bool larger = false;
+ float larger_z = 0.0f;
+ T larger_val = T();
+
+ for (uint32_t i = 0; i < values.size(); i++) {
+ float stop_z = values[i].first;
+ T stop_val = values[i].second;
+ if (stop_z <= z && (!smaller || smaller_z < stop_z)) {
+ smaller = true;
+ smaller_z = stop_z;
+ smaller_val = stop_val;
+ }
+ if (stop_z >= z && (!larger || larger_z > stop_z)) {
+ larger = true;
+ larger_z = stop_z;
+ larger_val = stop_val;
+ }
+ }
+
+ if (smaller && larger) {
+ if (larger_z == smaller_z || larger_val == smaller_val) {
+ return smaller_val;
+ }
+ const float zoomDiff = larger_z - smaller_z;
+ const float zoomProgress = z - smaller_z;
+ if (base == 1.0f) {
+ const float t = zoomProgress / zoomDiff;
+ return util::interpolate(smaller_val, larger_val, t);
+ } else {
+ const float t = (std::pow(base, zoomProgress) - 1) / (std::pow(base, zoomDiff) - 1);
+ return util::interpolate(smaller_val, larger_val, t);
+ }
+ } else if (larger) {
+ return larger_val;
+ } else if (smaller) {
+ return smaller_val;
+ } else {
+ // No stop defined.
+ return defaultStopsValue<T>();
+ }
+}
+
+template bool StopsFunction<bool>::evaluate(float z) const;
+template float StopsFunction<float>::evaluate(float z) const;
+template Color StopsFunction<Color>::evaluate(float z) const;
+
+}
diff --git a/src/mbgl/style/function_properties.hpp b/src/mbgl/style/function_properties.hpp
new file mode 100644
index 0000000000..924f192330
--- /dev/null
+++ b/src/mbgl/style/function_properties.hpp
@@ -0,0 +1,55 @@
+#ifndef MBGL_STYLE_FUNCTION_PROPERTIES
+#define MBGL_STYLE_FUNCTION_PROPERTIES
+
+#include <mbgl/util/variant.hpp>
+
+#include <vector>
+
+namespace mbgl {
+
+template <typename T>
+struct ConstantFunction {
+ inline ConstantFunction(const T &value_) : value(value_) {}
+ inline T evaluate(float) const { return value; }
+
+private:
+ const T value;
+};
+
+template <typename T>
+struct StopsFunction {
+ inline StopsFunction(const std::vector<std::pair<float, T>> &values_, float base_) : values(values_), base(base_) {}
+ T evaluate(float z) const;
+
+private:
+ const std::vector<std::pair<float, T>> values;
+ const float base;
+};
+
+template <typename T>
+using Function = mapbox::util::variant<
+ std::false_type,
+ ConstantFunction<T>,
+ StopsFunction<T>
+>;
+
+template <typename T>
+struct FunctionEvaluator {
+ typedef T result_type;
+ inline FunctionEvaluator(float z_) : z(z_) {}
+
+ inline result_type operator()(const std::false_type &) {
+ return result_type();
+ }
+
+ template <template <typename> class Fn>
+ inline result_type operator()(const Fn<T>& fn) {
+ return fn.evaluate(z);
+ }
+private:
+ float z;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/property_fallback.cpp b/src/mbgl/style/property_fallback.cpp
new file mode 100644
index 0000000000..965baf6c4b
--- /dev/null
+++ b/src/mbgl/style/property_fallback.cpp
@@ -0,0 +1,61 @@
+#include <mbgl/style/property_fallback.hpp>
+#include <mbgl/style/style_properties.hpp>
+
+namespace mbgl {
+
+const std::map<PropertyKey, PropertyValue> PropertyFallbackValue::properties = {
+ { PropertyKey::FillAntialias, defaultStyleProperties<FillProperties>().antialias },
+ { PropertyKey::FillOpacity, defaultStyleProperties<FillProperties>().opacity },
+ { PropertyKey::FillColor, defaultStyleProperties<FillProperties>().fill_color },
+ // no FillOutlineColor on purpose.
+ { PropertyKey::FillTranslateX, defaultStyleProperties<FillProperties>().translate[0] },
+ { PropertyKey::FillTranslateY, defaultStyleProperties<FillProperties>().translate[1] },
+ { PropertyKey::FillTranslateAnchor, defaultStyleProperties<FillProperties>().translateAnchor },
+
+ { PropertyKey::LineOpacity, defaultStyleProperties<LineProperties>().opacity },
+ { PropertyKey::LineColor, defaultStyleProperties<LineProperties>().color },
+ { PropertyKey::LineTranslateX, defaultStyleProperties<LineProperties>().translate[0] },
+ { PropertyKey::LineTranslateY, defaultStyleProperties<LineProperties>().translate[1] },
+ { PropertyKey::LineTranslateAnchor, defaultStyleProperties<LineProperties>().translateAnchor },
+ { PropertyKey::LineWidth, defaultStyleProperties<LineProperties>().width },
+ { PropertyKey::LineGapWidth, defaultStyleProperties<LineProperties>().gap_width },
+ { PropertyKey::LineBlur, defaultStyleProperties<LineProperties>().blur },
+ { PropertyKey::LineDashLand, defaultStyleProperties<LineProperties>().dash_array[0] },
+ { PropertyKey::LineDashGap, defaultStyleProperties<LineProperties>().dash_array[1] },
+
+ { PropertyKey::IconOpacity, defaultStyleProperties<SymbolProperties>().icon.opacity },
+ { PropertyKey::IconRotate, defaultStyleProperties<SymbolProperties>().icon.rotate },
+ { PropertyKey::IconSize, defaultStyleProperties<SymbolProperties>().icon.size },
+ { PropertyKey::IconColor, defaultStyleProperties<SymbolProperties>().icon.color },
+ { PropertyKey::IconHaloColor, defaultStyleProperties<SymbolProperties>().icon.halo_color },
+ { PropertyKey::IconHaloWidth, defaultStyleProperties<SymbolProperties>().icon.halo_width },
+ { PropertyKey::IconHaloBlur, defaultStyleProperties<SymbolProperties>().icon.halo_blur },
+ { PropertyKey::IconTranslateX, defaultStyleProperties<SymbolProperties>().icon.translate[0] },
+ { PropertyKey::IconTranslateY, defaultStyleProperties<SymbolProperties>().icon.translate[1] },
+ { PropertyKey::IconTranslateAnchor, defaultStyleProperties<SymbolProperties>().icon.translate_anchor },
+
+ { PropertyKey::TextOpacity, defaultStyleProperties<SymbolProperties>().text.opacity },
+ { PropertyKey::TextSize, defaultStyleProperties<SymbolProperties>().text.size },
+ { PropertyKey::TextColor, defaultStyleProperties<SymbolProperties>().text.color },
+ { PropertyKey::TextHaloColor, defaultStyleProperties<SymbolProperties>().text.halo_color },
+ { PropertyKey::TextHaloWidth, defaultStyleProperties<SymbolProperties>().text.halo_width },
+ { PropertyKey::TextHaloBlur, defaultStyleProperties<SymbolProperties>().text.halo_blur },
+ { PropertyKey::TextTranslateX, defaultStyleProperties<SymbolProperties>().text.translate[0] },
+ { PropertyKey::TextTranslateY, defaultStyleProperties<SymbolProperties>().text.translate[1] },
+ { PropertyKey::TextTranslateAnchor, defaultStyleProperties<SymbolProperties>().text.translate_anchor },
+
+ { PropertyKey::RasterOpacity, defaultStyleProperties<RasterProperties>().opacity },
+ { PropertyKey::RasterHueRotate, defaultStyleProperties<RasterProperties>().hue_rotate },
+ { PropertyKey::RasterBrightnessLow, defaultStyleProperties<RasterProperties>().brightness[0] },
+ { PropertyKey::RasterBrightnessHigh, defaultStyleProperties<RasterProperties>().brightness[1] },
+ { PropertyKey::RasterSaturation, defaultStyleProperties<RasterProperties>().saturation },
+ { PropertyKey::RasterContrast, defaultStyleProperties<RasterProperties>().contrast },
+ { PropertyKey::RasterFade, defaultStyleProperties<RasterProperties>().fade },
+
+ { PropertyKey::BackgroundOpacity, defaultStyleProperties<BackgroundProperties>().opacity },
+ { PropertyKey::BackgroundColor, defaultStyleProperties<BackgroundProperties>().color },
+};
+
+const PropertyValue PropertyFallbackValue::defaultProperty = false;
+
+}
diff --git a/src/mbgl/style/property_fallback.hpp b/src/mbgl/style/property_fallback.hpp
new file mode 100644
index 0000000000..5c5eae0cd6
--- /dev/null
+++ b/src/mbgl/style/property_fallback.hpp
@@ -0,0 +1,29 @@
+#ifndef MBGL_STYLE_PROPERTY_FALLBACK
+#define MBGL_STYLE_PROPERTY_FALLBACK
+
+#include <mbgl/style/property_key.hpp>
+#include <mbgl/style/property_value.hpp>
+
+#include <map>
+
+namespace mbgl {
+
+class PropertyFallbackValue {
+public:
+ static const PropertyValue &Get(PropertyKey key) {
+ auto it = properties.find(key);
+ if (it != properties.end()) {
+ return it->second;
+ } else {
+ return defaultProperty;
+ }
+ }
+
+private:
+ static const std::map<PropertyKey, PropertyValue> properties;
+ static const PropertyValue defaultProperty;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/property_key.hpp b/src/mbgl/style/property_key.hpp
new file mode 100644
index 0000000000..efeebf0242
--- /dev/null
+++ b/src/mbgl/style/property_key.hpp
@@ -0,0 +1,70 @@
+#ifndef MBGL_STYLE_PROPERTY_KEY
+#define MBGL_STYLE_PROPERTY_KEY
+
+namespace mbgl {
+
+enum class PropertyKey {
+ FillAntialias,
+ FillOpacity,
+ FillColor,
+ FillOutlineColor,
+ FillTranslate, // for transitions only
+ FillTranslateX,
+ FillTranslateY,
+ FillTranslateAnchor,
+ FillImage,
+
+ LineOpacity,
+ LineColor,
+ LineTranslate, // for transitions only
+ LineTranslateX,
+ LineTranslateY,
+ LineTranslateAnchor,
+ LineWidth,
+ LineGapWidth,
+ LineBlur,
+ LineDashArray, // for transitions only
+ LineDashLand,
+ LineDashGap,
+ LineImage,
+
+ IconOpacity,
+ IconRotate,
+ IconSize,
+ IconColor,
+ IconHaloColor,
+ IconHaloWidth,
+ IconHaloBlur,
+ IconTranslate, // for transitions only
+ IconTranslateX,
+ IconTranslateY,
+ IconTranslateAnchor,
+
+ TextOpacity,
+ TextSize,
+ TextColor,
+ TextHaloColor,
+ TextHaloWidth,
+ TextHaloBlur,
+ TextTranslate, // for transitions only
+ TextTranslateX,
+ TextTranslateY,
+ TextTranslateAnchor,
+
+ RasterOpacity,
+ RasterHueRotate,
+ RasterBrightness, // for transitions only
+ RasterBrightnessLow,
+ RasterBrightnessHigh,
+ RasterSaturation,
+ RasterContrast,
+ RasterFade,
+
+ BackgroundOpacity,
+ BackgroundColor,
+ BackgroundImage
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/property_transition.hpp b/src/mbgl/style/property_transition.hpp
new file mode 100644
index 0000000000..07b7cfe288
--- /dev/null
+++ b/src/mbgl/style/property_transition.hpp
@@ -0,0 +1,15 @@
+#ifndef MBGL_STYLE_PROPERTY_TRANSITION
+#define MBGL_STYLE_PROPERTY_TRANSITION
+
+#include <cstdint>
+
+namespace mbgl {
+
+struct PropertyTransition {
+ uint16_t duration = 0;
+ uint16_t delay = 0;
+};
+
+}
+
+#endif \ No newline at end of file
diff --git a/src/mbgl/style/property_value.hpp b/src/mbgl/style/property_value.hpp
new file mode 100644
index 0000000000..1b22b31177
--- /dev/null
+++ b/src/mbgl/style/property_value.hpp
@@ -0,0 +1,21 @@
+#ifndef MBGL_STYLE_PROPERTY_VALUE
+#define MBGL_STYLE_PROPERTY_VALUE
+
+#include <mbgl/util/variant.hpp>
+#include <mbgl/style/function_properties.hpp>
+#include <mbgl/style/types.hpp>
+
+namespace mbgl {
+
+typedef mapbox::util::variant<
+ std::string,
+ TranslateAnchorType,
+ RotateAnchorType,
+ Function<bool>,
+ Function<float>,
+ Function<Color>
+> PropertyValue;
+
+}
+
+#endif
diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp
new file mode 100644
index 0000000000..9669e0e4a7
--- /dev/null
+++ b/src/mbgl/style/style.cpp
@@ -0,0 +1,109 @@
+#include <mbgl/style/style.hpp>
+#include <mbgl/map/sprite.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/style/style_parser.hpp>
+#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/error.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/platform/log.hpp>
+#include <csscolorparser/csscolorparser.hpp>
+
+#include <rapidjson/document.h>
+
+#include <algorithm>
+
+namespace mbgl {
+
+Style::Style()
+ : mtx(util::make_unique<uv::rwlock>()) {
+}
+
+// Note: This constructor is seemingly empty, but we need to declare it anyway
+// because this file includes uv_detail.hpp, which has the declarations necessary
+// for deleting the std::unique_ptr<uv::rwlock>.
+Style::~Style() {}
+
+void Style::updateProperties(float z, timestamp now) {
+ uv::writelock lock(mtx);
+
+ if (layers) {
+ layers->updateProperties(z, now);
+ }
+
+ // Apply transitions after the first time.
+ if (!initial_render_complete) {
+ initial_render_complete = true;
+ return;
+ }
+}
+
+const std::string &Style::getSpriteURL() const {
+ return sprite_url;
+}
+
+void Style::setDefaultTransitionDuration(uint16_t duration_milliseconds) {
+ defaultTransition.duration = duration_milliseconds;
+}
+
+const std::vector<std::string> &Style::getAppliedClasses() const {
+ return appliedClasses;
+}
+
+void Style::setAppliedClasses(const std::vector<std::string> &class_names) {
+ appliedClasses = class_names;
+ updateClasses();
+}
+
+void Style::toggleClass(const std::string &name) {
+ if (name.length()) {
+ auto it = std::find(appliedClasses.begin(), appliedClasses.end(), name);
+ if (it == appliedClasses.end()) {
+ appliedClasses.push_back(name);
+ } else {
+ appliedClasses.erase(it);
+ }
+ }
+
+ updateClasses();
+}
+
+void Style::updateClasses() {
+ if (layers) {
+ layers->setClasses(appliedClasses, util::now(), defaultTransition);
+ }
+}
+
+bool Style::hasTransitions() const {
+ if (layers) {
+ if (layers->hasTransitions()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void Style::loadJSON(const uint8_t *const data) {
+ uv::writelock lock(mtx);
+
+ rapidjson::Document doc;
+ doc.Parse<0>((const char *const)data);
+ if (doc.HasParseError()) {
+ Log::Error(Event::ParseStyle, "Error parsing style JSON at %i: %s", doc.GetErrorOffset(), doc.GetParseError());
+ throw error::style_parse(doc.GetErrorOffset(), doc.GetParseError());
+ }
+
+ StyleParser parser;
+ parser.parse(doc);
+
+ layers = parser.getLayers();
+ sprite_url = parser.getSprite();
+ glyph_url = parser.getGlyphURL();
+
+ updateClasses();
+}
+
+}
diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp
new file mode 100644
index 0000000000..56f318ecbb
--- /dev/null
+++ b/src/mbgl/style/style.hpp
@@ -0,0 +1,68 @@
+#ifndef MBGL_STYLE_STYLE
+#define MBGL_STYLE_STYLE
+
+#include <mbgl/style/property_transition.hpp>
+#include <mbgl/style/style_source.hpp>
+
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/uv.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <cstdint>
+#include <map>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include <set>
+
+namespace mbgl {
+
+class Sprite;
+class StyleLayer;
+class StyleLayerGroup;
+
+class Style {
+public:
+ struct exception : std::runtime_error { exception(const char *msg) : std::runtime_error(msg) {} };
+
+public:
+ Style();
+ ~Style();
+
+ void loadJSON(const uint8_t *const data);
+
+ size_t layerCount() const;
+ void updateProperties(float z, timestamp t);
+
+ void setDefaultTransitionDuration(uint16_t duration_milliseconds = 0);
+
+ void setAppliedClasses(const std::vector<std::string> &classes);
+ const std::vector<std::string> &getAppliedClasses() const;
+ void toggleClass(const std::string &name);
+
+ // Updates the styling information to reflect the current array
+ // of applied classes.
+ void updateClasses();
+
+ bool hasTransitions() const;
+
+ const std::string &getSpriteURL() const;
+
+public:
+ util::ptr<StyleLayerGroup> layers;
+ std::vector<std::string> appliedClasses;
+ std::string glyph_url;
+
+private:
+ std::string sprite_url;
+
+private:
+ PropertyTransition defaultTransition;
+ bool initial_render_complete = false;
+
+ std::unique_ptr<uv::rwlock> mtx;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/style_bucket.cpp b/src/mbgl/style/style_bucket.cpp
new file mode 100644
index 0000000000..9a40c2386b
--- /dev/null
+++ b/src/mbgl/style/style_bucket.cpp
@@ -0,0 +1,15 @@
+#include <mbgl/style/style_bucket.hpp>
+
+namespace mbgl {
+
+StyleBucket::StyleBucket(StyleLayerType type) {
+ switch (type) {
+ case StyleLayerType::Fill: render = StyleBucketFill{}; break;
+ case StyleLayerType::Line: render = StyleBucketLine{}; break;
+ case StyleLayerType::Symbol: render = StyleBucketSymbol{}; break;
+ case StyleLayerType::Raster: render = StyleBucketRaster{}; break;
+ default: break;
+ }
+}
+
+} \ No newline at end of file
diff --git a/src/mbgl/style/style_bucket.hpp b/src/mbgl/style/style_bucket.hpp
new file mode 100644
index 0000000000..d84d35d5b2
--- /dev/null
+++ b/src/mbgl/style/style_bucket.hpp
@@ -0,0 +1,112 @@
+#ifndef MBGL_STYLE_STYLE_BUCKET
+#define MBGL_STYLE_STYLE_BUCKET
+
+#include <mbgl/style/types.hpp>
+#include <mbgl/style/filter_expression.hpp>
+#include <mbgl/style/style_source.hpp>
+
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/variant.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <forward_list>
+
+namespace mbgl {
+
+class Source;
+
+class StyleBucketFill {
+public:
+ WindingType winding = WindingType::NonZero;
+};
+
+class StyleBucketLine {
+public:
+ CapType cap = CapType::Butt;
+ JoinType join = JoinType::Miter;
+ float miter_limit = 2.0f;
+ float round_limit = 1.0f;
+};
+
+class StyleBucketSymbol {
+public:
+ // Make movable only.
+ inline StyleBucketSymbol() = default;
+ inline StyleBucketSymbol(StyleBucketSymbol &&) = default;
+ inline StyleBucketSymbol& operator=(StyleBucketSymbol &&) = default;
+ inline StyleBucketSymbol(const StyleBucketSymbol &) = delete;
+ inline StyleBucketSymbol& operator=(const StyleBucketSymbol &) = delete;
+
+ PlacementType placement = PlacementType::Point;
+ float min_distance = 250.0f;
+ bool avoid_edges = false;
+
+ struct {
+ bool allow_overlap = false;
+ bool ignore_placement = false;
+ bool optional = false;
+ RotationAlignmentType rotation_alignment = RotationAlignmentType::Viewport;
+ float max_size = 1.0f;
+ std::string image;
+ float rotate = 0.0f;
+ float padding = 2.0f;
+ bool keep_upright = false;
+ vec2<float> offset = {0, 0};
+ } icon;
+
+ struct {
+ RotationAlignmentType rotation_alignment = RotationAlignmentType::Viewport;
+ std::string field;
+ std::string font;
+ float max_size = 16.0f;
+ float max_width = 15.0f * 24 /* em */;
+ float line_height = 1.2f * 24 /* em */;
+ float letter_spacing = 0.0f * 24 /* em */;
+ TextJustifyType justify = TextJustifyType::Center;
+ TextAnchorType anchor = TextAnchorType::Center;
+ float max_angle = 45.0f /* degrees */;
+ float rotate = 0.0f;
+ float slant = 0.0f;
+ float padding = 2.0f;
+ bool keep_upright = true;
+ TextTransformType transform = TextTransformType::None;
+ vec2<float> offset = {0, 0};
+ bool allow_overlap = false;
+ bool ignore_placement = false;
+ bool optional = false;
+ } text;
+};
+
+class StyleBucketRaster {
+public:
+ bool prerendered = false;
+ uint16_t size = 256;
+ float blur = 0.0f;
+ float buffer = 0.03125f;
+};
+
+typedef mapbox::util::variant<StyleBucketFill, StyleBucketLine, StyleBucketSymbol,
+ StyleBucketRaster, std::false_type> StyleBucketRender;
+
+
+class StyleBucket {
+public:
+ typedef util::ptr<StyleBucket> Ptr;
+
+ StyleBucket(StyleLayerType type);
+
+ std::string name;
+ util::ptr<StyleSource> style_source;
+ std::string source_layer;
+ FilterExpression filter;
+ StyleBucketRender render = std::false_type();
+ float min_zoom = -std::numeric_limits<float>::infinity();
+ float max_zoom = std::numeric_limits<float>::infinity();
+};
+
+
+
+};
+
+#endif
diff --git a/src/mbgl/style/style_layer.cpp b/src/mbgl/style/style_layer.cpp
new file mode 100644
index 0000000000..e58756afa4
--- /dev/null
+++ b/src/mbgl/style/style_layer.cpp
@@ -0,0 +1,284 @@
+#include <mbgl/style/style_layer.hpp>
+#include <mbgl/style/style_bucket.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/style/property_fallback.hpp>
+
+#include <mbgl/util/interpolate.hpp>
+
+namespace mbgl {
+
+StyleLayer::StyleLayer(const std::string &id_, std::map<ClassID, ClassProperties> &&styles_)
+ : id(id_), styles(std::move(styles_)) {}
+
+bool StyleLayer::isBackground() const {
+ return type == StyleLayerType::Background;
+}
+
+void StyleLayer::setClasses(const std::vector<std::string> &class_names, const timestamp now,
+ const PropertyTransition &defaultTransition) {
+ // Stores all keys that we have already added transitions for.
+ std::set<PropertyKey> already_applied;
+
+ // Reverse iterate through all class names and apply them last to first.
+ for (auto it = class_names.rbegin(); it != class_names.rend(); ++it) {
+ const std::string &class_name = *it;
+ // From here on, we're only dealing with IDs to avoid comparing strings all the time.
+ const ClassID class_id = ClassDictionary::Get().lookup(class_name);
+ applyClassProperties(class_id, already_applied, now, defaultTransition);
+ }
+
+ // As the last class, apply the default class.
+ applyClassProperties(ClassID::Default, already_applied, now, defaultTransition);
+
+ // Make sure that we also transition to the fallback value for keys that aren't changed by
+ // any applied classes.
+ for (std::pair<const PropertyKey, AppliedClassProperties> &property_pair : appliedStyle) {
+ const PropertyKey key = property_pair.first;
+ if (already_applied.find(key) != already_applied.end()) {
+ // This property has already been set by a previous class, so we don't need to
+ // transition to the fallback.
+ continue;
+ }
+
+ AppliedClassProperties &appliedProperties = property_pair.second;
+ // Make sure that we don't do double transitions to the fallback value.
+ if (appliedProperties.mostRecent() != ClassID::Fallback) {
+ // This property key hasn't been set by a previous class, so we need to add a transition
+ // to the fallback value for that key.
+ const timestamp begin = now + defaultTransition.delay * 1_millisecond;
+ const timestamp end = begin + defaultTransition.duration * 1_millisecond;
+ const PropertyValue &value = PropertyFallbackValue::Get(key);
+ appliedProperties.add(ClassID::Fallback, begin, end, value);
+ }
+ }
+
+ // Update all child layers as well.
+ if (layers) {
+ layers->setClasses(class_names, now, defaultTransition);
+ }
+}
+
+// Helper function for applying all properties of a a single class that haven't been applied yet.
+void StyleLayer::applyClassProperties(const ClassID class_id,
+ std::set<PropertyKey> &already_applied, timestamp now,
+ const PropertyTransition &defaultTransition) {
+ auto style_it = styles.find(class_id);
+ if (style_it == styles.end()) {
+ // There is no class in this layer with this class_name.
+ return;
+ }
+
+ // Loop through all the properties in this style, and add transitions to them, if they're
+ // not already the most recent transition.
+ const ClassProperties &class_properties = style_it->second;
+ for (const std::pair<PropertyKey, PropertyValue> &property_pair : class_properties) {
+ PropertyKey key = property_pair.first;
+ if (already_applied.find(key) != already_applied.end()) {
+ // This property has already been set by a previous class.
+ continue;
+ }
+
+ // Mark this property as written by a previous class, so that subsequent
+ // classes won't override this.
+ already_applied.insert(key);
+
+ // If the most recent transition is not the one with the highest priority, create
+ // a transition.
+ AppliedClassProperties &appliedProperties = appliedStyle[key];
+ if (appliedProperties.mostRecent() != class_id) {
+ const PropertyTransition &transition =
+ class_properties.getTransition(key, defaultTransition);
+ const timestamp begin = now + transition.delay * 1_millisecond;
+ const timestamp end = begin + transition.duration * 1_millisecond;
+ const PropertyValue &value = property_pair.second;
+ appliedProperties.add(class_id, begin, end, value);
+ }
+ }
+}
+
+template <typename T>
+struct PropertyEvaluator {
+ typedef T result_type;
+ PropertyEvaluator(float z_) : z(z_) {}
+
+ template <typename P, typename std::enable_if<std::is_convertible<P, T>::value, int>::type = 0>
+ T operator()(const P &value) const {
+ return value;
+ }
+
+ T operator()(const Function<T> &value) const {
+ return mapbox::util::apply_visitor(FunctionEvaluator<T>(z), value);
+ }
+
+ template <typename P, typename std::enable_if<!std::is_convertible<P, T>::value, int>::type = 0>
+ T operator()(const P &) const {
+ return T();
+ }
+
+private:
+ const float z;
+};
+
+template <typename T>
+void StyleLayer::applyStyleProperty(PropertyKey key, T &target, const float z, const timestamp now) {
+ auto it = appliedStyle.find(key);
+ if (it != appliedStyle.end()) {
+ AppliedClassProperties &applied = it->second;
+ // Iterate through all properties that we need to apply in order.
+ const PropertyEvaluator<T> evaluator(z);
+ for (AppliedClassProperty &property : applied.properties) {
+ if (now >= property.begin) {
+ // We overwrite the current property with the new value.
+ target = mapbox::util::apply_visitor(evaluator, property.value);
+ } else {
+ // Do not apply this property because its transition hasn't begun yet.
+ }
+ }
+ }
+}
+
+template <typename T>
+void StyleLayer::applyTransitionedStyleProperty(PropertyKey key, T &target, const float z, const timestamp now) {
+ auto it = appliedStyle.find(key);
+ if (it != appliedStyle.end()) {
+ AppliedClassProperties &applied = it->second;
+ // Iterate through all properties that we need to apply in order.
+ const PropertyEvaluator<T> evaluator(z);
+ for (AppliedClassProperty &property : applied.properties) {
+ if (now >= property.end) {
+ // We overwrite the current property with the new value.
+ target = mapbox::util::apply_visitor(evaluator, property.value);
+ } else if (now >= property.begin) {
+ // We overwrite the current property partially with the new value.
+ float progress = float(now - property.begin) / float(property.end - property.begin);
+ target = util::interpolate(target, mapbox::util::apply_visitor(evaluator, property.value), progress);
+ } else {
+ // Do not apply this property because its transition hasn't begun yet.
+ }
+ }
+ }
+}
+
+template <>
+void StyleLayer::applyStyleProperties<FillProperties>(const float z, const timestamp now) {
+ properties.set<FillProperties>();
+ FillProperties &fill = properties.get<FillProperties>();
+ applyStyleProperty(PropertyKey::FillAntialias, fill.antialias, z, now);
+ applyTransitionedStyleProperty(PropertyKey::FillOpacity, fill.opacity, z, now);
+ applyTransitionedStyleProperty(PropertyKey::FillColor, fill.fill_color, z, now);
+ applyTransitionedStyleProperty(PropertyKey::FillOutlineColor, fill.stroke_color, z, now);
+ applyTransitionedStyleProperty(PropertyKey::FillTranslateX, fill.translate[0], z, now);
+ applyTransitionedStyleProperty(PropertyKey::FillTranslateY, fill.translate[1], z, now);
+ applyStyleProperty(PropertyKey::FillTranslateAnchor, fill.translateAnchor, z, now);
+ applyStyleProperty(PropertyKey::FillImage, fill.image, z, now);
+}
+
+template <>
+void StyleLayer::applyStyleProperties<LineProperties>(const float z, const timestamp now) {
+ properties.set<LineProperties>();
+ LineProperties &line = properties.get<LineProperties>();
+ applyTransitionedStyleProperty(PropertyKey::LineOpacity, line.opacity, z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineColor, line.color, z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineTranslateX, line.translate[0], z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineTranslateY, line.translate[1], z, now);
+ applyStyleProperty(PropertyKey::LineTranslateAnchor, line.translateAnchor, z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineWidth, line.width, z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineGapWidth, line.gap_width, z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineBlur, line.blur, z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineDashLand, line.dash_array[0], z, now);
+ applyTransitionedStyleProperty(PropertyKey::LineDashGap, line.dash_array[1], z, now);
+ applyStyleProperty(PropertyKey::LineImage, line.image, z, now);
+}
+
+template <>
+void StyleLayer::applyStyleProperties<SymbolProperties>(const float z, const timestamp now) {
+ properties.set<SymbolProperties>();
+ SymbolProperties &symbol = properties.get<SymbolProperties>();
+ applyTransitionedStyleProperty(PropertyKey::IconOpacity, symbol.icon.opacity, z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconRotate, symbol.icon.rotate, z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconSize, symbol.icon.size, z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconColor, symbol.icon.color, z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconHaloColor, symbol.icon.halo_color, z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconHaloWidth, symbol.icon.halo_width, z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconHaloBlur, symbol.icon.halo_blur, z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconTranslateX, symbol.icon.translate[0], z, now);
+ applyTransitionedStyleProperty(PropertyKey::IconTranslateY, symbol.icon.translate[1], z, now);
+ applyStyleProperty(PropertyKey::IconTranslateAnchor, symbol.icon.translate_anchor, z, now);
+
+ applyTransitionedStyleProperty(PropertyKey::TextOpacity, symbol.text.opacity, z, now);
+ applyTransitionedStyleProperty(PropertyKey::TextSize, symbol.text.size, z, now);
+ applyTransitionedStyleProperty(PropertyKey::TextColor, symbol.text.color, z, now);
+ applyTransitionedStyleProperty(PropertyKey::TextHaloColor, symbol.text.halo_color, z, now);
+ applyTransitionedStyleProperty(PropertyKey::TextHaloWidth, symbol.text.halo_width, z, now);
+ applyTransitionedStyleProperty(PropertyKey::TextHaloBlur, symbol.text.halo_blur, z, now);
+ applyTransitionedStyleProperty(PropertyKey::TextTranslateX, symbol.text.translate[0], z, now);
+ applyTransitionedStyleProperty(PropertyKey::TextTranslateY, symbol.text.translate[1], z, now);
+ applyStyleProperty(PropertyKey::TextTranslateAnchor, symbol.text.translate_anchor, z, now);
+}
+
+template <>
+void StyleLayer::applyStyleProperties<RasterProperties>(const float z, const timestamp now) {
+ properties.set<RasterProperties>();
+ RasterProperties &raster = properties.get<RasterProperties>();
+ applyTransitionedStyleProperty(PropertyKey::RasterOpacity, raster.opacity, z, now);
+ applyTransitionedStyleProperty(PropertyKey::RasterHueRotate, raster.hue_rotate, z, now);
+ applyTransitionedStyleProperty(PropertyKey::RasterBrightnessLow, raster.brightness[0], z, now);
+ applyTransitionedStyleProperty(PropertyKey::RasterBrightnessHigh, raster.brightness[1], z, now);
+ applyTransitionedStyleProperty(PropertyKey::RasterSaturation, raster.saturation, z, now);
+ applyTransitionedStyleProperty(PropertyKey::RasterContrast, raster.contrast, z, now);
+ applyTransitionedStyleProperty(PropertyKey::RasterFade, raster.fade, z, now);
+}
+
+template <>
+void StyleLayer::applyStyleProperties<BackgroundProperties>(const float z, const timestamp now) {
+ properties.set<BackgroundProperties>();
+ BackgroundProperties &background = properties.get<BackgroundProperties>();
+ applyTransitionedStyleProperty(PropertyKey::BackgroundOpacity, background.opacity, z, now);
+ applyTransitionedStyleProperty(PropertyKey::BackgroundColor, background.color, z, now);
+ applyStyleProperty(PropertyKey::BackgroundImage, background.image, z, now);
+}
+
+void StyleLayer::updateProperties(float z, const timestamp now) {
+ if (layers) {
+ layers->updateProperties(z, now);
+ }
+
+ cleanupAppliedStyleProperties(now);
+
+ switch (type) {
+ case StyleLayerType::Fill: applyStyleProperties<FillProperties>(z, now); break;
+ case StyleLayerType::Line: applyStyleProperties<LineProperties>(z, now); break;
+ case StyleLayerType::Symbol: applyStyleProperties<SymbolProperties>(z, now); break;
+ case StyleLayerType::Raster: applyStyleProperties<RasterProperties>(z, now); break;
+ case StyleLayerType::Background: applyStyleProperties<BackgroundProperties>(z, now); break;
+ default: properties.set<std::false_type>(); break;
+ }
+}
+
+bool StyleLayer::hasTransitions() const {
+ for (const std::pair<PropertyKey, AppliedClassProperties> &pair : appliedStyle) {
+ if (pair.second.hasTransitions()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void StyleLayer::cleanupAppliedStyleProperties(timestamp now) {
+ auto it = appliedStyle.begin();
+ const auto end = appliedStyle.end();
+ while (it != end) {
+ AppliedClassProperties &applied_properties = it->second;
+ applied_properties.cleanup(now);
+
+ // If the current properties object is empty, remove it from the map entirely.
+ if (applied_properties.empty()) {
+ appliedStyle.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+}
+
+}
diff --git a/src/mbgl/style/style_layer.hpp b/src/mbgl/style/style_layer.hpp
new file mode 100644
index 0000000000..641dc1e71c
--- /dev/null
+++ b/src/mbgl/style/style_layer.hpp
@@ -0,0 +1,89 @@
+#ifndef MBGL_STYLE_STYLE_LAYER
+#define MBGL_STYLE_STYLE_LAYER
+
+#include <mbgl/style/class_dictionary.hpp>
+#include <mbgl/style/class_properties.hpp>
+#include <mbgl/style/style_properties.hpp>
+#include <mbgl/style/applied_class_properties.hpp>
+
+#include <mbgl/util/ptr.hpp>
+
+#include <vector>
+#include <string>
+#include <map>
+#include <set>
+
+namespace mbgl {
+
+class StyleBucket;
+class StyleLayerGroup;
+
+class StyleLayer {
+public:
+ StyleLayer(const std::string &id, std::map<ClassID, ClassProperties> &&styles);
+
+ template <typename T> const T &getProperties() {
+ if (properties.is<T>()) {
+ return properties.get<T>();
+ } else {
+ return defaultStyleProperties<T>();
+ }
+ }
+
+ // Determines whether this layer is the background layer.
+ bool isBackground() const;
+
+ // Updates the StyleProperties information in this layer by evaluating all
+ // pending transitions and applied classes in order.
+ void updateProperties(float z, timestamp now);
+
+ // Sets the list of classes and creates transitions to the currently applied values.
+ void setClasses(const std::vector<std::string> &class_names, timestamp now,
+ const PropertyTransition &defaultTransition);
+
+ bool hasTransitions() const;
+
+private:
+ // Applies all properties from a class, if they haven't been applied already.
+ void applyClassProperties(ClassID class_id, std::set<PropertyKey> &already_applied,
+ timestamp now, const PropertyTransition &defaultTransition);
+
+ // Sets the properties of this object by evaluating all pending transitions and
+ // aplied classes in order.
+ template <typename T> void applyStyleProperties(float z, timestamp now);
+ template <typename T> void applyStyleProperty(PropertyKey key, T &, float z, timestamp now);
+ template <typename T> void applyTransitionedStyleProperty(PropertyKey key, T &, float z, timestamp now);
+
+ // Removes all expired style transitions.
+ void cleanupAppliedStyleProperties(timestamp now);
+
+public:
+ // The name of this layer.
+ const std::string id;
+
+ StyleLayerType type = StyleLayerType::Unknown;
+
+ // Bucket information, telling the renderer how to generate the geometries
+ // for this layer (feature property filters, tessellation instructions, ...).
+ util::ptr<StyleBucket> bucket;
+
+ // Contains all style classes that can be applied to this layer.
+ const std::map<ClassID, ClassProperties> styles;
+
+private:
+ // For every property, stores a list of applied property values, with
+ // optional transition times.
+ std::map<PropertyKey, AppliedClassProperties> appliedStyle;
+
+public:
+ // Stores the evaluated, and cascaded styling information, specific to this
+ // layer's type.
+ StyleProperties properties;
+
+ // Child layer array (if this layer has child layers).
+ util::ptr<StyleLayerGroup> layers;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/style_layer_group.cpp b/src/mbgl/style/style_layer_group.cpp
new file mode 100644
index 0000000000..0ca0fa0cce
--- /dev/null
+++ b/src/mbgl/style/style_layer_group.cpp
@@ -0,0 +1,34 @@
+#include <mbgl/style/style_layer_group.hpp>
+
+namespace mbgl {
+
+void StyleLayerGroup::setClasses(const std::vector<std::string> &class_names, timestamp now,
+ const PropertyTransition &defaultTransition) {
+ for (const util::ptr<StyleLayer> &layer : layers) {
+ if (layer) {
+ layer->setClasses(class_names, now, defaultTransition);
+ }
+ }
+}
+
+void StyleLayerGroup::updateProperties(float z, timestamp t) {
+ for (const util::ptr<StyleLayer> &layer: layers) {
+ if (layer) {
+ layer->updateProperties(z, t);
+ }
+ }
+}
+
+bool StyleLayerGroup::hasTransitions() const {
+ for (const util::ptr<const StyleLayer> &layer: layers) {
+ if (layer) {
+ if (layer->hasTransitions()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+}
diff --git a/src/mbgl/style/style_layer_group.hpp b/src/mbgl/style/style_layer_group.hpp
new file mode 100644
index 0000000000..1af6e23bd7
--- /dev/null
+++ b/src/mbgl/style/style_layer_group.hpp
@@ -0,0 +1,23 @@
+#ifndef MBGL_STYLE_STYLE_LAYER_GROUP
+#define MBGL_STYLE_STYLE_LAYER_GROUP
+
+#include <mbgl/style/style_layer.hpp>
+
+#include <vector>
+
+namespace mbgl {
+
+class StyleLayerGroup {
+public:
+ void setClasses(const std::vector<std::string> &class_names, timestamp now,
+ const PropertyTransition &defaultTransition);
+ void updateProperties(float z, timestamp t);
+
+ bool hasTransitions() const;
+public:
+ std::vector<util::ptr<StyleLayer>> layers;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp
new file mode 100644
index 0000000000..2dec648aff
--- /dev/null
+++ b/src/mbgl/style/style_parser.cpp
@@ -0,0 +1,845 @@
+#include <mbgl/style/style_source.hpp>
+#include <mbgl/style/style_parser.hpp>
+#include <mbgl/style/style_layer_group.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/platform/log.hpp>
+#include <csscolorparser/csscolorparser.hpp>
+
+#include <algorithm>
+
+namespace mbgl {
+
+using JSVal = const rapidjson::Value&;
+
+StyleParser::StyleParser() {
+}
+
+void StyleParser::parse(JSVal document) {
+ if (document.HasMember("constants")) {
+ parseConstants(document["constants"]);
+ }
+
+ if (document.HasMember("sources")) {
+ parseSources(document["sources"]);
+ }
+
+ if (document.HasMember("layers")) {
+ root = createLayers(document["layers"]);
+ parseLayers();
+ }
+
+ if (document.HasMember("sprite")) {
+ parseSprite(document["sprite"]);
+ }
+
+ if (document.HasMember("glyphs")) {
+ parseGlyphURL(document["glyphs"]);
+ }
+}
+
+void StyleParser::parseConstants(JSVal value) {
+ if (value.IsObject()) {
+ rapidjson::Value::ConstMemberIterator itr = value.MemberBegin();
+ for (; itr != value.MemberEnd(); ++itr) {
+ std::string name { itr->name.GetString(), itr->name.GetStringLength() };
+ // Discard constants that don't start with an @ sign.
+ if (name.length() && name[0] == '@') {
+ constants.emplace(std::move(name), &itr->value);
+ }
+ }
+ } else {
+ Log::Warning(Event::ParseStyle, "constants must be an object");
+ }
+}
+
+JSVal StyleParser::replaceConstant(JSVal value) {
+ if (value.IsString()) {
+ auto it = constants.find({ value.GetString(), value.GetStringLength() });
+ if (it != constants.end()) {
+ return *it->second;
+ }
+ }
+
+ return value;
+}
+
+#pragma mark - Parse Render Properties
+
+template<> bool StyleParser::parseRenderProperty(JSVal value, bool &target, const char *name) {
+ if (value.HasMember(name)) {
+ JSVal property = replaceConstant(value[name]);
+ if (property.IsBool()) {
+ target = property.GetBool();
+ return true;
+ } else {
+ fprintf(stderr, "[WARNING] '%s' must be a boolean\n", name);
+ }
+ }
+ return false;
+}
+
+
+template<> bool StyleParser::parseRenderProperty(JSVal value, std::string &target, const char *name) {
+ if (value.HasMember(name)) {
+ JSVal property = replaceConstant(value[name]);
+ if (property.IsString()) {
+ target = { property.GetString(), property.GetStringLength() };
+ return true;
+ } else {
+ Log::Warning(Event::ParseStyle, "'%s' must be a string", name);
+ }
+ }
+ return false;
+}
+
+template<> bool StyleParser::parseRenderProperty(JSVal value, float &target, const char *name) {
+ if (value.HasMember(name)) {
+ JSVal property = replaceConstant(value[name]);
+ if (property.IsNumber()) {
+ target = property.GetDouble();
+ return true;
+ } else {
+ Log::Warning(Event::ParseStyle, "'%s' must be a number", name);
+ }
+ }
+ return false;
+}
+
+template<> bool StyleParser::parseRenderProperty(JSVal value, uint16_t &target, const char *name) {
+ if (value.HasMember(name)) {
+ JSVal property = replaceConstant(value[name]);
+ if (property.IsUint()) {
+ unsigned int int_value = property.GetUint();
+ if (int_value > std::numeric_limits<uint16_t>::max()) {
+ Log::Warning(Event::ParseStyle, "values for %s that are larger than %d are not supported", name, std::numeric_limits<uint16_t>::max());
+ return false;
+ }
+
+ target = int_value;
+ return true;
+ } else {
+ Log::Warning(Event::ParseStyle, "%s must be an unsigned integer", name);
+ }
+ }
+ return false;
+}
+
+template<> bool StyleParser::parseRenderProperty(JSVal value, int32_t &target, const char *name) {
+ if (value.HasMember(name)) {
+ JSVal property = replaceConstant(value[name]);
+ if (property.IsInt()) {
+ target = property.GetInt();
+ return true;
+ } else {
+ Log::Warning(Event::ParseStyle, "%s must be an integer", name);
+ }
+ }
+ return false;
+}
+
+template<> bool StyleParser::parseRenderProperty(JSVal value, vec2<float> &target, const char *name) {
+ if (value.HasMember(name)) {
+ JSVal property = replaceConstant(value[name]);
+ if (property.IsArray()) {
+ if (property.Size() >= 2) {
+ target.x = property[(rapidjson::SizeType)0].GetDouble();
+ target.y = property[(rapidjson::SizeType)1].GetDouble();
+ return true;
+ } else {
+ Log::Warning(Event::ParseStyle, "%s must have at least two members", name);
+ }
+ } else {
+ Log::Warning(Event::ParseStyle, "%s must be an array of numbers", name);
+ }
+ }
+ return false;
+}
+
+template<typename Parser, typename T>
+bool StyleParser::parseRenderProperty(JSVal value, T &target, const char *name) {
+ if (value.HasMember(name)) {
+ JSVal property = replaceConstant(value[name]);
+ if (property.IsString()) {
+ target = Parser({ property.GetString(), property.GetStringLength() });
+ return true;
+ } else {
+ Log::Warning(Event::ParseStyle, "%s must have one of the enum values", name);
+ }
+ }
+ return false;
+}
+
+
+#pragma mark - Parse Sources
+
+void StyleParser::parseSources(JSVal value) {
+ if (value.IsObject()) {
+ rapidjson::Value::ConstMemberIterator itr = value.MemberBegin();
+ for (; itr != value.MemberEnd(); ++itr) {
+ std::string name { itr->name.GetString(), itr->name.GetStringLength() };
+ SourceInfo& info = sources.emplace(name, std::make_shared<StyleSource>()).first->second->info;
+
+ parseRenderProperty<SourceTypeClass>(itr->value, info.type, "type");
+ parseRenderProperty(itr->value, info.url, "url");
+ parseRenderProperty(itr->value, info.tile_size, "tileSize");
+ info.parseTileJSONProperties(itr->value);
+ }
+ } else {
+ Log::Warning(Event::ParseStyle, "sources must be an object");
+ }
+}
+
+#pragma mark - Parse Style Properties
+
+Color parseColor(JSVal value) {
+ if (!value.IsString()) {
+ Log::Warning(Event::ParseStyle, "color value must be a string");
+ return Color{{ 0, 0, 0, 0 }};
+ }
+
+ CSSColorParser::Color css_color = CSSColorParser::parse({ value.GetString(), value.GetStringLength() });
+
+ // Premultiply the color.
+ const float factor = css_color.a / 255;
+
+ return Color{{(float)css_color.r * factor,
+ (float)css_color.g * factor,
+ (float)css_color.b * factor,
+ css_color.a}};
+}
+
+template <>
+bool StyleParser::parseFunctionArgument(JSVal value) {
+ JSVal rvalue = replaceConstant(value);
+ if (rvalue.IsBool()) {
+ return rvalue.GetBool();
+ } else if (rvalue.IsNumber()) {
+ return rvalue.GetDouble();
+ } else {
+ Log::Warning(Event::ParseStyle, "function argument must be a boolean or numeric value");
+ return false;
+ }
+}
+
+template <>
+float StyleParser::parseFunctionArgument(JSVal value) {
+ JSVal rvalue = replaceConstant(value);
+ if (rvalue.IsNumber()) {
+ return rvalue.GetDouble();
+ } else {
+ Log::Warning(Event::ParseStyle, "function argument must be a numeric value");
+ return 0.0f;
+ }
+}
+
+template <>
+Color StyleParser::parseFunctionArgument(JSVal value) {
+ JSVal rvalue = replaceConstant(value);
+ return parseColor(rvalue);
+}
+
+template <typename T> inline float defaultBaseValue() { return 1.75; }
+template <> inline float defaultBaseValue<Color>() { return 1.0; }
+
+template <typename T>
+std::tuple<bool, Function<T>> StyleParser::parseFunction(JSVal value) {
+ if (!value.HasMember("stops")) {
+ Log::Warning(Event::ParseStyle, "function must specify a function type");
+ return std::tuple<bool, Function<T>> { false, ConstantFunction<T>(T()) };
+ }
+
+ float base = defaultBaseValue<T>();
+
+ if (value.HasMember("base")) {
+ JSVal value_base = value["base"];
+ if (value_base.IsNumber()) {
+ base = value_base.GetDouble();
+ } else {
+ Log::Warning(Event::ParseStyle, "base must be numeric");
+ }
+ }
+
+ JSVal value_stops = value["stops"];
+ if (!value_stops.IsArray()) {
+ Log::Warning(Event::ParseStyle, "stops function must specify a stops array");
+ return std::tuple<bool, Function<T>> { false, ConstantFunction<T>(T()) };
+ }
+
+ std::vector<std::pair<float, T>> stops;
+ for (rapidjson::SizeType i = 0; i < value_stops.Size(); ++i) {
+ JSVal stop = value_stops[i];
+ if (stop.IsArray()) {
+ if (stop.Size() != 2) {
+ Log::Warning(Event::ParseStyle, "stop must have zoom level and value specification");
+ return std::tuple<bool, Function<T>> { false, ConstantFunction<T>(T()) };
+ }
+
+ JSVal z = stop[rapidjson::SizeType(0)];
+ if (!z.IsNumber()) {
+ Log::Warning(Event::ParseStyle, "zoom level in stop must be a number");
+ return std::tuple<bool, Function<T>> { false, ConstantFunction<T>(T()) };
+ }
+
+ stops.emplace_back(z.GetDouble(), parseFunctionArgument<T>(stop[rapidjson::SizeType(1)]));
+ } else {
+ Log::Warning(Event::ParseStyle, "function argument must be a numeric value");
+ return std::tuple<bool, Function<T>> { false, ConstantFunction<T>(T()) };
+ }
+ }
+
+ return std::tuple<bool, Function<T>> { true, StopsFunction<T>(stops, base) };
+}
+
+
+template <typename T>
+bool StyleParser::parseFunction(PropertyKey key, ClassProperties &klass, JSVal value) {
+ bool parsed;
+ Function<T> function;
+ std::tie(parsed, function) = parseFunction<T>(value);
+ if (parsed) {
+ klass.set(key, function);
+ }
+ return parsed;
+}
+
+template <typename T>
+bool StyleParser::setProperty(JSVal value, const char *property_name, PropertyKey key, ClassProperties &klass) {
+ bool parsed;
+ T result;
+ std::tie(parsed, result) = parseProperty<T>(value, property_name);
+ if (parsed) {
+ klass.set(key, result);
+ }
+ return parsed;
+}
+
+template <typename T>
+bool StyleParser::setProperty(JSVal value, const char *property_name, T &target) {
+ bool parsed;
+ T result;
+ std::tie(parsed, result) = parseProperty<T>(value, property_name);
+ if (parsed) {
+ target = std::move(result);
+ }
+ return parsed;
+}
+
+
+template<typename T>
+bool StyleParser::parseOptionalProperty(const char *property_name, PropertyKey key, ClassProperties &klass, JSVal value) {
+ if (!value.HasMember(property_name)) {
+ return false;
+ } else {
+ return setProperty<T>(replaceConstant(value[property_name]), property_name, key, klass);
+ }
+}
+
+template <typename T>
+bool StyleParser::parseOptionalProperty(const char *property_name, T &target, JSVal value) {
+ if (!value.HasMember(property_name)) {
+ return false;
+ } else {
+ return setProperty<T>(replaceConstant(value[property_name]), property_name, target);
+ }
+}
+
+template<> std::tuple<bool, std::string> StyleParser::parseProperty(JSVal value, const char *property_name) {
+ if (!value.IsString()) {
+ Log::Warning(Event::ParseStyle, "value of '%s' must be a string", property_name);
+ return std::tuple<bool, std::string> { false, std::string() };
+ }
+
+ return std::tuple<bool, std::string> { true, { value.GetString(), value.GetStringLength() } };
+}
+
+template<> std::tuple<bool, TranslateAnchorType> StyleParser::parseProperty(JSVal value, const char *property_name) {
+ if (!value.IsString()) {
+ Log::Warning(Event::ParseStyle, "value of '%s' must be a string", property_name);
+ return std::tuple<bool, TranslateAnchorType> { false, TranslateAnchorType::Map };
+ }
+
+ return std::tuple<bool, TranslateAnchorType> { true, TranslateAnchorTypeClass({ value.GetString(), value.GetStringLength() }) };
+}
+
+template<> std::tuple<bool, RotateAnchorType> StyleParser::parseProperty<RotateAnchorType>(JSVal value, const char *property_name) {
+ if (!value.IsString()) {
+ Log::Warning(Event::ParseStyle, "value of '%s' must be a string", property_name);
+ return std::tuple<bool, RotateAnchorType> { false, RotateAnchorType::Map };
+ }
+
+ return std::tuple<bool, RotateAnchorType> { true, RotateAnchorTypeClass({ value.GetString(), value.GetStringLength() }) };
+}
+
+template<> std::tuple<bool, PropertyTransition> StyleParser::parseProperty(JSVal value, const char */*property_name*/) {
+ PropertyTransition transition;
+ if (value.IsObject()) {
+ if (value.HasMember("duration") && value["duration"].IsNumber()) {
+ transition.duration = value["duration"].GetUint();
+ }
+ if (value.HasMember("delay") && value["delay"].IsNumber()) {
+ transition.delay = value["delay"].GetUint();
+ }
+ }
+
+ if (transition.duration == 0 && transition.delay == 0) {
+ return std::tuple<bool, PropertyTransition> { false, std::move(transition) };
+ }
+
+ return std::tuple<bool, PropertyTransition> { true, std::move(transition) };
+}
+
+template<> std::tuple<bool, Function<bool>> StyleParser::parseProperty(JSVal value, const char *property_name) {
+ if (value.IsObject()) {
+ return parseFunction<bool>(value);
+ } else if (value.IsNumber()) {
+ return std::tuple<bool, Function<bool>> { true, ConstantFunction<bool>(value.GetDouble()) };
+ } else if (value.IsBool()) {
+ return std::tuple<bool, Function<bool>> { true, ConstantFunction<bool>(value.GetBool()) };
+ } else {
+ Log::Warning(Event::ParseStyle, "value of '%s' must be convertible to boolean, or a boolean function", property_name);
+ return std::tuple<bool, Function<bool>> { false, ConstantFunction<bool>(false) };
+ }
+}
+
+template<> std::tuple<bool, Function<float>> StyleParser::parseProperty(JSVal value, const char *property_name) {
+ if (value.IsObject()) {
+ return parseFunction<float>(value);
+ } else if (value.IsNumber()) {
+ return std::tuple<bool, Function<float>> { true, ConstantFunction<float>(value.GetDouble()) };
+ } else if (value.IsBool()) {
+ return std::tuple<bool, Function<float>> { true, ConstantFunction<float>(value.GetBool()) };
+ } else {
+ Log::Warning(Event::ParseStyle, "value of '%s' must be a number, or a number function", property_name);
+ return std::tuple<bool, Function<float>> { false, ConstantFunction<float>(0) };
+ }
+}
+
+template<> std::tuple<bool, Function<Color>> StyleParser::parseProperty(JSVal value, const char *property_name) {
+ if (value.IsObject()) {
+ return parseFunction<Color>(value);
+ } else if (value.IsString()) {
+ return std::tuple<bool, Function<Color>> { true, ConstantFunction<Color>(parseColor(value)) };
+ } else {
+ Log::Warning(Event::ParseStyle, "value of '%s' must be a color, or a color function", property_name);
+ return std::tuple<bool, Function<Color>> { false, ConstantFunction<Color>(Color {{ 0, 0, 0, 0 }}) };
+ }
+}
+
+template <typename T>
+bool StyleParser::parseOptionalProperty(const char *property_name, const std::vector<PropertyKey> &keys, ClassProperties &klass, JSVal value) {
+ if (value.HasMember(property_name)) {
+ JSVal rvalue = replaceConstant(value[property_name]);
+ if (!rvalue.IsArray()) {
+ Log::Warning(Event::ParseStyle, "array value must be an array");
+ }
+
+ if (rvalue.Size() != keys.size()) {
+ Log::Warning(Event::ParseStyle, "array value has unexpected number of elements");
+ }
+
+ for (uint16_t i = 0; i < keys.size(); i++) {
+ setProperty<T>(rvalue[(rapidjson::SizeType)i], property_name, keys[i], klass);
+ }
+ }
+ return true;
+}
+
+#pragma mark - Parse Layers
+
+std::unique_ptr<StyleLayerGroup> StyleParser::createLayers(JSVal value) {
+ if (value.IsArray()) {
+ std::unique_ptr<StyleLayerGroup> group = util::make_unique<StyleLayerGroup>();
+ for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
+ util::ptr<StyleLayer> layer = createLayer(value[i]);
+ if (layer) {
+ group->layers.emplace_back(layer);
+ }
+ }
+ return group;
+ } else {
+ Log::Warning(Event::ParseStyle, "layers must be an array");
+ return nullptr;
+ }
+}
+
+util::ptr<StyleLayer> StyleParser::createLayer(JSVal value) {
+ if (value.IsObject()) {
+ if (!value.HasMember("id")) {
+ Log::Warning(Event::ParseStyle, "layer must have an id");
+ return nullptr;
+ }
+
+ JSVal id = value["id"];
+ if (!id.IsString()) {
+ Log::Warning(Event::ParseStyle, "layer id must be a string");
+ return nullptr;
+ }
+
+ const std::string layer_id = { id.GetString(), id.GetStringLength() };
+
+ if (layers.find(layer_id) != layers.end()) {
+ Log::Warning(Event::ParseStyle, "duplicate layer id %s", layer_id.c_str());
+ return nullptr;
+ }
+
+ // Parse paints already, as they can't be inherited anyway.
+ std::map<ClassID, ClassProperties> paints;
+ parsePaints(value, paints);
+
+ util::ptr<StyleLayer> layer = std::make_shared<StyleLayer>(
+ layer_id, std::move(paints));
+
+ if (value.HasMember("layers")) {
+ layer->layers = createLayers(value["layers"]);
+ }
+
+ // Store the layer ID so we can reference it later.
+ layers.emplace(layer_id, std::pair<JSVal, util::ptr<StyleLayer>> { value, layer });
+
+ return layer;
+ } else {
+ Log::Warning(Event::ParseStyle, "layer must be an object");
+ return nullptr;
+ }
+}
+
+void StyleParser::parseLayers() {
+ for (std::pair<const std::string, std::pair<JSVal, util::ptr<StyleLayer>>> &pair : layers) {
+ parseLayer(pair.second);
+ }
+}
+
+void StyleParser::parseLayer(std::pair<JSVal, util::ptr<StyleLayer>> &pair) {
+ JSVal value = pair.first;
+ util::ptr<StyleLayer> &layer = pair.second;
+
+ if (value.HasMember("type")) {
+ JSVal type = value["type"];
+ if (!type.IsString()) {
+ Log::Warning(Event::ParseStyle, "layer type of '%s' must be a string", layer->id.c_str());
+ } else {
+ layer->type = StyleLayerTypeClass(std::string { type.GetString(), type.GetStringLength() });
+ }
+ }
+
+ if (layer->bucket || (layer->layers && layer->type != StyleLayerType::Raster)) {
+ // Skip parsing this again. We already have a valid layer definition.
+ return;
+ }
+
+ // Make sure we have not previously attempted to parse this layer.
+ if (std::find(stack.begin(), stack.end(), layer.get()) != stack.end()) {
+ Log::Warning(Event::ParseStyle, "layer reference of '%s' is circular", layer->id.c_str());
+ return;
+ }
+
+ if (value.HasMember("ref")) {
+ // This layer is referencing another layer. Inherit the bucket from that layer, if we
+ // already parsed it.
+ parseReference(replaceConstant(value["ref"]), layer);
+ } else {
+ // Otherwise, parse the source/source-layer/filter/render keys to form the bucket.
+ parseBucket(value, layer);
+ }
+}
+
+#pragma mark - Parse Styles
+
+void StyleParser::parsePaints(JSVal value, std::map<ClassID, ClassProperties> &paints) {
+ rapidjson::Value::ConstMemberIterator itr = value.MemberBegin();
+ for (; itr != value.MemberEnd(); ++itr) {
+ const std::string name { itr->name.GetString(), itr->name.GetStringLength() };
+
+ if (name == "paint") {
+ parsePaint(replaceConstant(itr->value), paints[ClassID::Default]);
+ } else if (name.compare(0, 6, "paint.") == 0 && name.length() > 6) {
+ const ClassID class_id = ClassDictionary::Get().lookup(name.substr(6));
+ parsePaint(replaceConstant(itr->value), paints[class_id]);
+ }
+ }
+}
+
+void StyleParser::parsePaint(JSVal value, ClassProperties &klass) {
+ using Key = PropertyKey;
+
+ parseOptionalProperty<Function<bool>>("fill-antialias", Key::FillAntialias, klass, value);
+ parseOptionalProperty<Function<float>>("fill-opacity", Key::FillOpacity, klass, value);
+ parseOptionalProperty<PropertyTransition>("fill-opacity-transition", Key::FillOpacity, klass, value);
+ parseOptionalProperty<Function<Color>>("fill-color", Key::FillColor, klass, value);
+ parseOptionalProperty<PropertyTransition>("fill-color-transition", Key::FillColor, klass, value);
+ parseOptionalProperty<Function<Color>>("fill-outline-color", Key::FillOutlineColor, klass, value);
+ parseOptionalProperty<PropertyTransition>("fill-outline-color-transition", Key::FillOutlineColor, klass, value);
+ parseOptionalProperty<Function<float>>("fill-translate", { Key::FillTranslateX, Key::FillTranslateY }, klass, value);
+ parseOptionalProperty<PropertyTransition>("fill-translate-transition", Key::FillTranslate, klass, value);
+ parseOptionalProperty<TranslateAnchorType>("fill-translate-anchor", Key::FillTranslateAnchor, klass, value);
+ parseOptionalProperty<std::string>("fill-image", Key::FillImage, klass, value);
+
+ parseOptionalProperty<Function<float>>("line-opacity", Key::LineOpacity, klass, value);
+ parseOptionalProperty<PropertyTransition>("line-opacity-transition", Key::LineOpacity, klass, value);
+ parseOptionalProperty<Function<Color>>("line-color", Key::LineColor, klass, value);
+ parseOptionalProperty<PropertyTransition>("line-color-transition", Key::LineColor, klass, value);
+ parseOptionalProperty<Function<float>>("line-translate", { Key::LineTranslateX, Key::LineTranslateY }, klass, value);
+ parseOptionalProperty<PropertyTransition>("line-translate-transition", Key::LineTranslate, klass, value);
+ parseOptionalProperty<TranslateAnchorType>("line-translate-anchor", Key::LineTranslateAnchor, klass, value);
+ parseOptionalProperty<Function<float>>("line-width", Key::LineWidth, klass, value);
+ parseOptionalProperty<PropertyTransition>("line-width-transition", Key::LineWidth, klass, value);
+ parseOptionalProperty<Function<float>>("line-gap-width", Key::LineGapWidth, klass, value);
+ parseOptionalProperty<PropertyTransition>("line-gap-width-transition", Key::LineGapWidth, klass, value);
+ parseOptionalProperty<Function<float>>("line-blur", Key::LineBlur, klass, value);
+ parseOptionalProperty<PropertyTransition>("line-blur-transition", Key::LineBlur, klass, value);
+ parseOptionalProperty<Function<float>>("line-dasharray", { Key::LineDashLand, Key::LineDashGap }, klass, value);
+ parseOptionalProperty<PropertyTransition>("line-dasharray-transition", Key::LineDashArray, klass, value);
+ parseOptionalProperty<std::string>("line-image", Key::LineImage, klass, value);
+
+ parseOptionalProperty<Function<float>>("icon-opacity", Key::IconOpacity, klass, value);
+ parseOptionalProperty<PropertyTransition>("icon-opacity-transition", Key::IconOpacity, klass, value);
+ parseOptionalProperty<Function<float>>("icon-rotate", Key::IconRotate, klass, value);
+ parseOptionalProperty<Function<float>>("icon-size", Key::IconSize, klass, value);
+ parseOptionalProperty<PropertyTransition>("icon-size-transition", Key::IconSize, klass, value);
+ parseOptionalProperty<Function<Color>>("icon-color", Key::IconColor, klass, value);
+ parseOptionalProperty<PropertyTransition>("icon-color-transition", Key::IconColor, klass, value);
+ parseOptionalProperty<Function<Color>>("icon-halo-color", Key::IconHaloColor, klass, value);
+ parseOptionalProperty<PropertyTransition>("icon-halo-color-transition", Key::IconHaloColor, klass, value);
+ parseOptionalProperty<Function<float>>("icon-halo-width", Key::IconHaloWidth, klass, value);
+ parseOptionalProperty<PropertyTransition>("icon-halo-width-transition", Key::IconHaloWidth, klass, value);
+ parseOptionalProperty<Function<float>>("icon-halo-blur", Key::IconHaloBlur, klass, value);
+ parseOptionalProperty<PropertyTransition>("icon-halo-blur-transition", Key::IconHaloBlur, klass, value);
+ parseOptionalProperty<Function<float>>("icon-translate", { Key::IconTranslateX, Key::IconTranslateY }, klass, value);
+ parseOptionalProperty<PropertyTransition>("icon-translate-transition", Key::IconTranslate, klass, value);
+ parseOptionalProperty<TranslateAnchorType>("icon-translate-anchor", Key::IconTranslateAnchor, klass, value);
+
+ parseOptionalProperty<Function<float>>("text-opacity", Key::TextOpacity, klass, value);
+ parseOptionalProperty<PropertyTransition>("text-opacity-transition", Key::TextOpacity, klass, value);
+ parseOptionalProperty<Function<float>>("text-size", Key::TextSize, klass, value);
+ parseOptionalProperty<PropertyTransition>("text-size-transition", Key::TextSize, klass, value);
+ parseOptionalProperty<Function<Color>>("text-color", Key::TextColor, klass, value);
+ parseOptionalProperty<PropertyTransition>("text-color-transition", Key::TextColor, klass, value);
+ parseOptionalProperty<Function<Color>>("text-halo-color", Key::TextHaloColor, klass, value);
+ parseOptionalProperty<PropertyTransition>("text-halo-color-transition", Key::TextHaloColor, klass, value);
+ parseOptionalProperty<Function<float>>("text-halo-width", Key::TextHaloWidth, klass, value);
+ parseOptionalProperty<PropertyTransition>("text-halo-width-transition", Key::TextHaloWidth, klass, value);
+ parseOptionalProperty<Function<float>>("text-halo-blur", Key::TextHaloBlur, klass, value);
+ parseOptionalProperty<PropertyTransition>("text-halo-blur-transition", Key::TextHaloBlur, klass, value);
+ parseOptionalProperty<Function<float>>("text-translate", { Key::TextTranslateX, Key::TextTranslateY }, klass, value);
+ parseOptionalProperty<PropertyTransition>("text-translate-transition", Key::TextTranslate, klass, value);
+ parseOptionalProperty<TranslateAnchorType>("text-translate-anchor", Key::TextTranslateAnchor, klass, value);
+
+ parseOptionalProperty<Function<float>>("raster-opacity", Key::RasterOpacity, klass, value);
+ parseOptionalProperty<PropertyTransition>("raster-opacity-transition", Key::RasterOpacity, klass, value);
+ parseOptionalProperty<Function<float>>("raster-hue-rotate", Key::RasterHueRotate, klass, value);
+ parseOptionalProperty<PropertyTransition>("raster-hue-rotate-transition", Key::RasterHueRotate, klass, value);
+ parseOptionalProperty<Function<float>>("raster-brightness", { Key::RasterBrightnessLow, Key::RasterBrightnessHigh }, klass, value);
+ parseOptionalProperty<PropertyTransition>("raster-brightness-transition", Key::RasterBrightness, klass, value);
+ parseOptionalProperty<Function<float>>("raster-saturation", Key::RasterSaturation, klass, value);
+ parseOptionalProperty<PropertyTransition>("raster-saturation-transition", Key::RasterSaturation, klass, value);
+ parseOptionalProperty<Function<float>>("raster-contrast", Key::RasterContrast, klass, value);
+ parseOptionalProperty<PropertyTransition>("raster-contrast-transition", Key::RasterContrast, klass, value);
+ parseOptionalProperty<Function<float>>("raster-fade-duration", Key::RasterFade, klass, value);
+ parseOptionalProperty<PropertyTransition>("raster-fade-duration-transition", Key::RasterFade, klass, value);
+
+ parseOptionalProperty<Function<float>>("background-opacity", Key::BackgroundOpacity, klass, value);
+ parseOptionalProperty<Function<Color>>("background-color", Key::BackgroundColor, klass, value);
+ parseOptionalProperty<std::string>("background-image", Key::BackgroundImage, klass, value);
+}
+
+void StyleParser::parseReference(JSVal value, util::ptr<StyleLayer> &layer) {
+ if (!value.IsString()) {
+ Log::Warning(Event::ParseStyle, "layer ref of '%s' must be a string", layer->id.c_str());
+ return;
+ }
+ const std::string ref { value.GetString(), value.GetStringLength() };
+ auto it = layers.find(ref);
+ if (it == layers.end()) {
+ Log::Warning(Event::ParseStyle, "layer '%s' references unknown layer %s", layer->id.c_str(), ref.c_str());
+ // We cannot parse this layer further.
+ return;
+ }
+
+ // Recursively parse the referenced layer.
+ stack.push_front(layer.get());
+ parseLayer(it->second);
+ stack.pop_front();
+
+
+ util::ptr<StyleLayer> reference = it->second.second;
+
+ layer->type = reference->type;
+
+ if (reference->layers) {
+ Log::Warning(Event::ParseStyle, "layer '%s' references composite layer", layer->id.c_str());
+ // We cannot parse this layer further.
+ return;
+ } else {
+ layer->bucket = reference->bucket;
+ }
+}
+
+#pragma mark - Parse Bucket
+
+void StyleParser::parseBucket(JSVal value, util::ptr<StyleLayer> &layer) {
+ layer->bucket = std::make_shared<StyleBucket>(layer->type);
+
+ // We name the buckets according to the layer that defined it.
+ layer->bucket->name = layer->id;
+
+ if (value.HasMember("source")) {
+ JSVal value_source = replaceConstant(value["source"]);
+ if (value_source.IsString()) {
+ const std::string source_name = { value_source.GetString(), value_source.GetStringLength() };
+ auto source_it = sources.find(source_name);
+ if (source_it != sources.end()) {
+ layer->bucket->style_source = source_it->second;
+ } else {
+ Log::Warning(Event::ParseStyle, "can't find source '%s' required for layer '%s'", source_name.c_str(), layer->id.c_str());
+ }
+ } else {
+ Log::Warning(Event::ParseStyle, "source of layer '%s' must be a string", layer->id.c_str());
+ }
+ }
+
+ if (value.HasMember("source-layer")) {
+ JSVal value_source_layer = replaceConstant(value["source-layer"]);
+ if (value_source_layer.IsString()) {
+ layer->bucket->source_layer = { value_source_layer.GetString(), value_source_layer.GetStringLength() };
+ } else {
+ Log::Warning(Event::ParseStyle, "source-layer of layer '%s' must be a string", layer->id.c_str());
+ }
+ }
+
+ if (value.HasMember("filter")) {
+ JSVal value_filter = replaceConstant(value["filter"]);
+ layer->bucket->filter = parseFilterExpression(value_filter);
+ }
+
+ if (value.HasMember("layout")) {
+ JSVal value_render = replaceConstant(value["layout"]);
+ parseLayout(value_render, layer);
+ }
+
+ if (value.HasMember("minzoom")) {
+ JSVal min_zoom = value["minzoom"];
+ if (min_zoom.IsNumber()) {
+ layer->bucket->min_zoom = min_zoom.GetDouble();
+ } else {
+ Log::Warning(Event::ParseStyle, "minzoom of layer %s must be numeric", layer->id.c_str());
+ }
+ }
+
+ if (value.HasMember("maxzoom")) {
+ JSVal max_zoom = value["maxzoom"];
+ if (max_zoom.IsNumber()) {
+ layer->bucket->min_zoom = max_zoom.GetDouble();
+ } else {
+ Log::Warning(Event::ParseStyle, "maxzoom of layer %s must be numeric", layer->id.c_str());
+ }
+ }
+}
+
+void StyleParser::parseLayout(JSVal value, util::ptr<StyleLayer> &layer) {
+ if (!value.IsObject()) {
+ Log::Warning(Event::ParseStyle, "layout property of layer '%s' must be an object", layer->id.c_str());
+ return;
+ }
+
+ StyleBucket &bucket = *layer->bucket;
+
+ switch (layer->type) {
+ case StyleLayerType::Fill: {
+ StyleBucketFill &render = bucket.render.get<StyleBucketFill>();
+
+ parseRenderProperty<WindingTypeClass>(value, render.winding, "fill-winding");
+ } break;
+
+ case StyleLayerType::Line: {
+ StyleBucketLine &render = bucket.render.get<StyleBucketLine>();
+
+ parseRenderProperty<CapTypeClass>(value, render.cap, "line-cap");
+ parseRenderProperty<JoinTypeClass>(value, render.join, "line-join");
+ parseRenderProperty(value, render.miter_limit, "line-miter-limit");
+ parseRenderProperty(value, render.round_limit, "line-round-limit");
+ } break;
+
+ case StyleLayerType::Symbol: {
+ StyleBucketSymbol &render = bucket.render.get<StyleBucketSymbol>();
+
+ parseRenderProperty<PlacementTypeClass>(value, render.placement, "symbol-placement");
+ if (render.placement == PlacementType::Line) {
+ // Change the default value in case of line placement.
+ render.text.rotation_alignment = RotationAlignmentType::Map;
+ render.icon.rotation_alignment = RotationAlignmentType::Map;
+ }
+
+ parseRenderProperty(value, render.min_distance, "symbol-min-distance");
+ parseRenderProperty(value, render.avoid_edges, "symbol-avoid-edges");
+
+ parseRenderProperty(value, render.icon.allow_overlap, "icon-allow-overlap");
+ parseRenderProperty(value, render.icon.ignore_placement, "icon-ignore-placement");
+ parseRenderProperty(value, render.icon.optional, "icon-optional");
+ parseRenderProperty<RotationAlignmentTypeClass>(value, render.icon.rotation_alignment, "icon-rotation-alignment");
+ parseRenderProperty(value, render.icon.max_size, "icon-max-size");
+ parseRenderProperty(value, render.icon.image, "icon-image");
+ parseRenderProperty(value, render.icon.rotate, "icon-rotate");
+ parseRenderProperty(value, render.icon.padding, "icon-padding");
+ parseRenderProperty(value, render.icon.keep_upright, "icon-keep-upright");
+ parseRenderProperty(value, render.icon.offset, "icon-offset");
+
+
+ parseRenderProperty<RotationAlignmentTypeClass>(value, render.text.rotation_alignment, "text-rotation-alignment");
+ parseRenderProperty(value, render.text.field, "text-field");
+ parseRenderProperty(value, render.text.font, "text-font");
+ parseRenderProperty(value, render.text.max_size, "text-max-size");
+ if (parseRenderProperty(value, render.text.max_width, "text-max-width")) {
+ render.text.max_width *= 24; // em
+ }
+ if (parseRenderProperty(value, render.text.line_height, "text-line-height")) {
+ render.text.line_height *= 24; // em
+ }
+ if (parseRenderProperty(value, render.text.letter_spacing, "text-letter-spacing")) {
+ render.text.letter_spacing *= 24; // em
+ }
+ parseRenderProperty<TextJustifyTypeClass>(value, render.text.justify, "text-justify");
+ parseRenderProperty<TextAnchorTypeClass>(value, render.text.anchor, "text-anchor");
+ parseRenderProperty(value, render.text.max_angle, "text-max-angle");
+ parseRenderProperty(value, render.text.rotate, "text-rotate");
+ parseRenderProperty(value, render.text.slant, "text-slant");
+ parseRenderProperty(value, render.text.padding, "text-padding");
+ parseRenderProperty(value, render.text.keep_upright, "text-keep-upright");
+ parseRenderProperty<TextTransformTypeClass>(value, render.text.transform, "text-transform");
+ parseRenderProperty(value, render.text.offset, "text-offset");
+ parseRenderProperty(value, render.text.allow_overlap, "text-allow-overlap");
+ parseRenderProperty(value, render.text.ignore_placement, "text-ignore-placement");
+ parseRenderProperty(value, render.text.optional, "text-optional");
+ } break;
+
+ case StyleLayerType::Raster: {
+ StyleBucketRaster &render = bucket.render.get<StyleBucketRaster>();
+
+ parseRenderProperty(value, render.size, "raster-size");
+ parseRenderProperty(value, render.blur, "raster-blur");
+ parseRenderProperty(value, render.buffer, "raster-buffer");
+ if (layer->layers) {
+ render.prerendered = true;
+ }
+ } break;
+
+ default:
+ // There are no render properties for these layer types.
+ break;
+ }
+}
+
+void StyleParser::parseSprite(JSVal value) {
+ if (value.IsString()) {
+ sprite = { value.GetString(), value.GetStringLength() };
+ }
+}
+
+void StyleParser::parseGlyphURL(JSVal value) {
+ if (value.IsString()) {
+ glyph_url = { value.GetString(), value.GetStringLength() };
+ }
+}
+
+
+}
diff --git a/src/mbgl/style/style_parser.hpp b/src/mbgl/style/style_parser.hpp
new file mode 100644
index 0000000000..c37e856034
--- /dev/null
+++ b/src/mbgl/style/style_parser.hpp
@@ -0,0 +1,113 @@
+#ifndef MBGL_STYLE_STYLE_PARSER
+#define MBGL_STYLE_STYLE_PARSER
+
+#include <rapidjson/document.h>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/style_source.hpp>
+#include <mbgl/style/filter_expression.hpp>
+#include <mbgl/style/class_properties.hpp>
+#include <mbgl/style/style_bucket.hpp>
+
+#include <unordered_map>
+#include <forward_list>
+#include <tuple>
+
+namespace mbgl {
+
+enum class ClassID : uint32_t;
+
+class StyleLayer;
+class StyleLayerGroup;
+
+class StyleParser {
+public:
+ using JSVal = const rapidjson::Value&;
+
+ StyleParser();
+
+ void parse(JSVal document);
+
+ util::ptr<StyleLayerGroup> getLayers() {
+ return root;
+ }
+
+ std::string getSprite() const {
+ return sprite;
+ }
+
+ std::string getGlyphURL() const {
+ return glyph_url;
+ }
+
+private:
+ void parseConstants(JSVal value);
+ JSVal replaceConstant(JSVal value);
+
+ void parseSources(JSVal value);
+
+ std::unique_ptr<StyleLayerGroup> createLayers(JSVal value);
+ util::ptr<StyleLayer> createLayer(JSVal value);
+ void parseLayers();
+ void parseLayer(std::pair<JSVal, util::ptr<StyleLayer>> &pair);
+ void parsePaints(JSVal value, std::map<ClassID, ClassProperties> &paints);
+ void parsePaint(JSVal, ClassProperties &properties);
+ void parseReference(JSVal value, util::ptr<StyleLayer> &layer);
+ void parseBucket(JSVal value, util::ptr<StyleLayer> &layer);
+ void parseLayout(JSVal value, util::ptr<StyleLayer> &layer);
+ void parseSprite(JSVal value);
+ void parseGlyphURL(JSVal value);
+
+ // Parses optional properties into a render bucket.
+ template<typename T>
+ bool parseRenderProperty(JSVal value, T &target, const char *name);
+ template <typename Parser, typename T>
+ bool parseRenderProperty(JSVal value, T &target, const char *name);
+
+ // Parses optional properties into style class properties.
+ template <typename T>
+ bool parseOptionalProperty(const char *property_name, PropertyKey key, ClassProperties &klass, JSVal value);
+ template <typename T>
+ bool parseOptionalProperty(const char *property_name, const std::vector<PropertyKey> &keys, ClassProperties &klass, JSVal value);
+ template <typename T>
+ bool parseOptionalProperty(const char *property_name, T &target, JSVal value);
+ template <typename T>
+ bool setProperty(JSVal value, const char *property_name, PropertyKey key, ClassProperties &klass);
+ template <typename T>
+ bool setProperty(JSVal value, const char *property_name, T &target);
+
+ template <typename T>
+ std::tuple<bool, T> parseProperty(JSVal value, const char *property_name);
+
+ template <typename T>
+ bool parseFunction(PropertyKey key, ClassProperties &klass, JSVal value);
+ template <typename T>
+ std::tuple<bool, Function<T>> parseFunction(JSVal value);
+ template <typename T>
+ T parseFunctionArgument(JSVal value);
+
+ FilterExpression parseFilter(JSVal);
+
+private:
+ std::unordered_map<std::string, const rapidjson::Value *> constants;
+
+ std::unordered_map<std::string, const util::ptr<StyleSource>> sources;
+
+ // This stores the root layer.
+ util::ptr<StyleLayerGroup> root;
+
+ // This maps ids to Layer objects, with all items being at the root level.
+ std::unordered_map<std::string, std::pair<JSVal, util::ptr<StyleLayer>>> layers;
+
+ // Store a stack of layers we're parsing right now. This is to prevent reference cycles.
+ std::forward_list<StyleLayer *> stack;
+
+ // Base URL of the sprite image.
+ std::string sprite;
+
+ // URL template for glyph PBFs.
+ std::string glyph_url;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/style_properties.cpp b/src/mbgl/style/style_properties.cpp
new file mode 100644
index 0000000000..29730fb85b
--- /dev/null
+++ b/src/mbgl/style/style_properties.cpp
@@ -0,0 +1,11 @@
+#include <mbgl/style/style_properties.hpp>
+
+namespace mbgl {
+
+template<> const FillProperties &defaultStyleProperties() { static const FillProperties p; return p; }
+template<> const LineProperties &defaultStyleProperties() { static const LineProperties p; return p; }
+template<> const SymbolProperties &defaultStyleProperties() { static const SymbolProperties p; return p; }
+template<> const RasterProperties &defaultStyleProperties() { static const RasterProperties p; return p; }
+template<> const BackgroundProperties &defaultStyleProperties() { static const BackgroundProperties p; return p; }
+
+}
diff --git a/src/mbgl/style/style_properties.hpp b/src/mbgl/style/style_properties.hpp
new file mode 100644
index 0000000000..c44b7c34c8
--- /dev/null
+++ b/src/mbgl/style/style_properties.hpp
@@ -0,0 +1,114 @@
+#ifndef MBGL_STYLE_STYLE_PROPERTIES
+#define MBGL_STYLE_STYLE_PROPERTIES
+
+#include <mbgl/util/variant.hpp>
+#include <mbgl/style/types.hpp>
+#include <mbgl/style/function_properties.hpp>
+
+#include <array>
+#include <string>
+#include <type_traits>
+#include <memory>
+
+namespace mbgl {
+
+struct FillProperties {
+ FillProperties() {}
+ bool antialias = true;
+ float opacity = 1.0f;
+ Color fill_color = {{ 0, 0, 0, 1 }};
+ Color stroke_color = {{ 0, 0, 0, -1 }};
+ std::array<float, 2> translate = {{ 0, 0 }};
+ TranslateAnchorType translateAnchor = TranslateAnchorType::Map;
+ std::string image;
+
+ inline bool isVisible() const {
+ return opacity > 0 && (fill_color[3] > 0 || stroke_color[3] > 0);
+ }
+};
+
+struct LineProperties {
+ inline LineProperties() {}
+ float opacity = 1.0f;
+ Color color = {{ 0, 0, 0, 1 }};
+ std::array<float, 2> translate = {{ 0, 0 }};
+ TranslateAnchorType translateAnchor = TranslateAnchorType::Map;
+ float width = 1;
+ float gap_width = 0;
+ float blur = 0;
+ std::array<float, 2> dash_array = {{ 1, -1 }};
+ std::string image;
+
+ inline bool isVisible() const {
+ return opacity > 0 && color[3] > 0 && width > 0;
+ }
+};
+
+struct SymbolProperties {
+ inline SymbolProperties() {}
+
+ struct {
+ float opacity = 1.0f;
+ float rotate = 0.0f;
+ float size = 1.0f;
+ Color color = {{ 0, 0, 0, 1 }};
+ Color halo_color = {{ 0, 0, 0, 0 }};
+ float halo_width = 0.0f;
+ float halo_blur = 0.0f;
+ std::array<float, 2> translate = {{ 0, 0 }};
+ TranslateAnchorType translate_anchor = TranslateAnchorType::Map;
+ } icon;
+
+ struct {
+ float opacity = 1.0f;
+ float size = 16.0f;
+ Color color = {{ 0, 0, 0, 1 }};
+ Color halo_color = {{ 0, 0, 0, 0 }};
+ float halo_width = 0.0f;
+ float halo_blur = 0.0f;
+ std::array<float, 2> translate = {{ 0, 0 }};
+ TranslateAnchorType translate_anchor = TranslateAnchorType::Map;
+ } text;
+
+ inline bool isVisible() const {
+ return (icon.opacity > 0 && (icon.color[3] > 0 || icon.halo_color[3] > 0) && icon.size > 0) ||
+ (text.opacity > 0 && (text.color[3] > 0 || text.halo_color[3] > 0) && text.size > 0);
+ }
+};
+
+struct RasterProperties {
+ inline RasterProperties() {}
+ float opacity = 1.0f;
+ float hue_rotate = 0.0f;
+ std::array<float, 2> brightness = {{ 0, 1 }};
+ float saturation = 0.0f;
+ float contrast = 0.0f;
+ float fade = 0.0f;
+
+ inline bool isVisible() const {
+ return opacity > 0;
+ }
+};
+
+struct BackgroundProperties {
+ inline BackgroundProperties() {}
+ float opacity = 1.0f;
+ Color color = {{ 0, 0, 0, 1 }};
+ std::string image;
+};
+
+typedef mapbox::util::variant<
+ FillProperties,
+ LineProperties,
+ SymbolProperties,
+ RasterProperties,
+ BackgroundProperties,
+ std::false_type
+> StyleProperties;
+
+template <typename T>
+const T &defaultStyleProperties();
+
+}
+
+#endif
diff --git a/src/mbgl/style/style_source.cpp b/src/mbgl/style/style_source.cpp
new file mode 100644
index 0000000000..f46a6fb09b
--- /dev/null
+++ b/src/mbgl/style/style_source.cpp
@@ -0,0 +1,77 @@
+#include <mbgl/style/style_source.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <limits>
+
+namespace mbgl {
+
+void parse(const rapidjson::Value& value, std::vector<std::string>& target, const char *name) {
+ if (!value.HasMember(name))
+ return;
+
+ const rapidjson::Value& property = value[name];
+ if (!property.IsArray())
+ return;
+
+ for (rapidjson::SizeType i = 0; i < property.Size(); i++)
+ if (!property[i].IsString())
+ return;
+
+ for (rapidjson::SizeType i = 0; i < property.Size(); i++)
+ target.emplace_back(std::string(property[i].GetString(), property[i].GetStringLength()));
+}
+
+void parse(const rapidjson::Value& value, std::string& target, const char* name) {
+ if (!value.HasMember(name))
+ return;
+
+ const rapidjson::Value& property = value[name];
+ if (!property.IsString())
+ return;
+
+ target = { property.GetString(), property.GetStringLength() };
+}
+
+void parse(const rapidjson::Value& value, uint16_t& target, const char* name) {
+ if (!value.HasMember(name))
+ return;
+
+ const rapidjson::Value& property = value[name];
+ if (!property.IsUint())
+ return;
+
+ unsigned int uint = property.GetUint();
+ if (uint > std::numeric_limits<uint16_t>::max())
+ return;
+
+ target = uint;
+}
+
+template <size_t N>
+void parse(const rapidjson::Value& value, std::array<float, N>& target, const char* name) {
+ if (!value.HasMember(name))
+ return;
+
+ const rapidjson::Value& property = value[name];
+ if (!property.IsArray() || property.Size() != N)
+ return;
+
+ for (rapidjson::SizeType i = 0; i < property.Size(); i++)
+ if (!property[i].IsNumber())
+ return;
+
+ for (rapidjson::SizeType i = 0; i < property.Size(); i++)
+ target[i] = property[i].GetDouble();
+}
+
+void SourceInfo::parseTileJSONProperties(const rapidjson::Value& value) {
+ parse(value, tiles, "tiles");
+ parse(value, min_zoom, "minzoom");
+ parse(value, max_zoom, "maxzoom");
+ parse(value, attribution, "attribution");
+ parse(value, center, "center");
+ parse(value, bounds, "bounds");
+}
+
+}
diff --git a/src/mbgl/style/style_source.hpp b/src/mbgl/style/style_source.hpp
new file mode 100644
index 0000000000..8c7d028880
--- /dev/null
+++ b/src/mbgl/style/style_source.hpp
@@ -0,0 +1,41 @@
+#ifndef MBGL_STYLE_STYLE_SOURCE
+#define MBGL_STYLE_STYLE_SOURCE
+
+#include <mbgl/style/types.hpp>
+#include <mbgl/util/ptr.hpp>
+#include <mbgl/util/noncopyable.hpp>
+#include <rapidjson/document.h>
+
+#include <vector>
+#include <string>
+
+namespace mbgl {
+
+class Source;
+
+class SourceInfo : private util::noncopyable {
+public:
+ SourceType type = SourceType::Vector;
+ std::string url;
+ std::vector<std::string> tiles;
+ uint16_t tile_size = 512;
+ uint16_t min_zoom = 0;
+ uint16_t max_zoom = 22;
+ std::string attribution;
+ std::array<float, 3> center = {{0, 0, 0}};
+ std::array<float, 4> bounds = {{-180, -90, 180, 90}};
+
+ void parseTileJSONProperties(const rapidjson::Value&);
+};
+
+
+class StyleSource : private util::noncopyable {
+public:
+ SourceInfo info;
+ bool enabled = false;
+ util::ptr<Source> source;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/src/mbgl/style/types.cpp
diff --git a/src/mbgl/style/types.hpp b/src/mbgl/style/types.hpp
new file mode 100644
index 0000000000..2f7ca7683d
--- /dev/null
+++ b/src/mbgl/style/types.hpp
@@ -0,0 +1,196 @@
+#ifndef MBGL_STYLE_TYPES
+#define MBGL_STYLE_TYPES
+
+#include <mbgl/util/enum.hpp>
+
+#include <string>
+#include <array>
+
+namespace mbgl {
+
+// Stores a premultiplied color, with all four channels ranging from 0..1
+typedef std::array<float, 4> Color;
+
+// -------------------------------------------------------------------------------------------------
+
+enum class StyleLayerType : uint8_t {
+ Unknown,
+ Fill,
+ Line,
+ Symbol,
+ Raster,
+ Background
+};
+
+MBGL_DEFINE_ENUM_CLASS(StyleLayerTypeClass, StyleLayerType, {
+ { StyleLayerType::Unknown, "unknown" },
+ { StyleLayerType::Fill, "fill" },
+ { StyleLayerType::Line, "line" },
+ { StyleLayerType::Symbol, "symbol" },
+ { StyleLayerType::Raster, "raster" },
+ { StyleLayerType::Background, "background" },
+ { StyleLayerType(-1), "unknown" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class SourceType : uint8_t {
+ Vector,
+ Raster,
+ GeoJSON,
+ Video
+};
+
+MBGL_DEFINE_ENUM_CLASS(SourceTypeClass, SourceType, {
+ { SourceType::Vector, "vector" },
+ { SourceType::Raster, "raster" },
+ { SourceType::GeoJSON, "geojson" },
+ { SourceType::Video, "video" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class WindingType : bool {
+ EvenOdd,
+ NonZero,
+};
+
+MBGL_DEFINE_ENUM_CLASS(WindingTypeClass, WindingType, {
+ { WindingType::EvenOdd, "even-odd" },
+ { WindingType::NonZero, "non-zero" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class CapType : uint8_t {
+ Round,
+ Butt,
+ Square,
+};
+
+MBGL_DEFINE_ENUM_CLASS(CapTypeClass, CapType, {
+ { CapType::Round, "round" },
+ { CapType::Butt, "butt" },
+ { CapType::Square, "square" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class JoinType : uint8_t {
+ Miter,
+ Bevel,
+ Round
+};
+
+MBGL_DEFINE_ENUM_CLASS(JoinTypeClass, JoinType, {
+ { JoinType::Miter, "miter" },
+ { JoinType::Bevel, "bevel" },
+ { JoinType::Round, "round" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class TranslateAnchorType : bool {
+ Map,
+ Viewport
+};
+
+MBGL_DEFINE_ENUM_CLASS(TranslateAnchorTypeClass, TranslateAnchorType, {
+ { TranslateAnchorType::Map, "map" },
+ { TranslateAnchorType::Viewport, "viewport" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class RotateAnchorType : bool {
+ Map,
+ Viewport,
+};
+
+MBGL_DEFINE_ENUM_CLASS(RotateAnchorTypeClass, RotateAnchorType, {
+ { RotateAnchorType::Map, "map" },
+ { RotateAnchorType::Viewport, "viewport" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class PlacementType : bool {
+ Point,
+ Line,
+};
+
+MBGL_DEFINE_ENUM_CLASS(PlacementTypeClass, PlacementType, {
+ { PlacementType::Point, "point" },
+ { PlacementType::Line, "line" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class RotationAlignmentType : bool {
+ Map,
+ Viewport,
+};
+
+MBGL_DEFINE_ENUM_CLASS(RotationAlignmentTypeClass, RotationAlignmentType, {
+ { RotationAlignmentType::Map, "map" },
+ { RotationAlignmentType::Viewport, "viewport" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class TextJustifyType : uint8_t {
+ Center,
+ Left,
+ Right
+};
+
+MBGL_DEFINE_ENUM_CLASS(TextJustifyTypeClass, TextJustifyType, {
+ { TextJustifyType::Center, "center" },
+ { TextJustifyType::Left, "left" },
+ { TextJustifyType::Right, "right" },
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class TextAnchorType : uint8_t {
+ Center,
+ Left,
+ Right,
+ Top,
+ Bottom,
+ TopLeft,
+ TopRight,
+ BottomLeft,
+ BottomRight
+};
+
+MBGL_DEFINE_ENUM_CLASS(TextAnchorTypeClass, TextAnchorType, {
+ { TextAnchorType::Center, "center" },
+ { TextAnchorType::Left, "left" },
+ { TextAnchorType::Right, "right" },
+ { TextAnchorType::Top, "top" },
+ { TextAnchorType::Bottom, "bottom" },
+ { TextAnchorType::TopLeft, "top-left" },
+ { TextAnchorType::TopRight, "top-right" },
+ { TextAnchorType::BottomLeft, "bottom-left" },
+ { TextAnchorType::BottomRight, "bottom-right" }
+});
+
+// -------------------------------------------------------------------------------------------------
+
+enum class TextTransformType : uint8_t {
+ None,
+ Uppercase,
+ Lowercase,
+};
+
+MBGL_DEFINE_ENUM_CLASS(TextTransformTypeClass, TextTransformType, {
+ { TextTransformType::None, "none" },
+ { TextTransformType::Uppercase, "uppercase" },
+ { TextTransformType::Lowercase, "lowercase" },
+});
+
+}
+
+#endif
+
diff --git a/src/mbgl/style/value.cpp b/src/mbgl/style/value.cpp
new file mode 100644
index 0000000000..ae51ce3783
--- /dev/null
+++ b/src/mbgl/style/value.cpp
@@ -0,0 +1,60 @@
+#include <mbgl/style/value.hpp>
+#include <mbgl/util/string.hpp>
+
+mbgl::Value mbgl::parseValue(pbf data) {
+ while (data.next())
+ {
+ switch (data.tag)
+ {
+ case 1: // string_value
+ return data.string();
+ case 2: // float_value
+ return static_cast<double>(data.float32());
+ case 3: // double_value
+ return data.float64();
+ case 4: // int_value
+ return data.varint<int64_t>();
+ case 5: // uint_value
+ return data.varint<uint64_t>();
+ case 6: // sint_value
+ return data.svarint<int64_t>();
+ case 7: // bool_value
+ return data.boolean();
+ default:
+ data.skip();
+ break;
+ }
+ }
+ return false;
+}
+
+std::string mbgl::toString(const mbgl::Value& value) {
+ if (value.is<std::string>()) return value.get<std::string>();
+ else if (value.is<bool>()) return value.get<bool>() ? "true" : "false";
+ else if (value.is<int64_t>()) return util::toString(value.get<int64_t>());
+ else if (value.is<uint64_t>()) return util::toString(value.get<uint64_t>());
+ else if (value.is<double>()) return util::toString(value.get<double>());
+ else return "null";
+}
+
+mbgl::Value mbgl::parseValue(const rapidjson::Value& value) {
+ switch (value.GetType()) {
+ case rapidjson::kNullType:
+ case rapidjson::kFalseType:
+ return false;
+
+ case rapidjson::kTrueType:
+ return true;
+
+ case rapidjson::kStringType:
+ return std::string { value.GetString(), value.GetStringLength() };
+
+ case rapidjson::kNumberType:
+ if (value.IsUint64()) return value.GetUint64();
+ if (value.IsInt64()) return value.GetInt64();
+ return value.GetDouble();
+
+ default:
+ return false;
+ }
+}
diff --git a/src/mbgl/style/value.hpp b/src/mbgl/style/value.hpp
new file mode 100644
index 0000000000..87d6f4cda3
--- /dev/null
+++ b/src/mbgl/style/value.hpp
@@ -0,0 +1,45 @@
+#ifndef MBGL_STYLE_VALUE
+#define MBGL_STYLE_VALUE
+
+#include <mbgl/util/variant.hpp>
+#include <mbgl/util/pbf.hpp>
+#include <rapidjson/document.h>
+
+#include <cstdlib>
+#include <cerrno>
+
+namespace mbgl {
+
+typedef mapbox::util::variant<bool, int64_t, uint64_t, double, std::string> Value;
+
+std::string toString(const Value &value);
+
+Value parseValue(pbf data);
+Value parseValue(const rapidjson::Value&);
+
+namespace util {
+inline bool parseNumericString(const std::string &str, double &result) {
+ char *end = nullptr;
+ const char *begin = str.c_str();
+ result = std::strtod(begin, &end);
+ while (*end != '\0' && isspace(*end)) end++; // eat whitespace after the end
+ return errno == 0 && end - begin == long(str.size());
+}
+}
+
+template <typename T>
+T toNumber(const Value &value) {
+ if (value.is<std::string>()) {
+ double val;
+ return util::parseNumericString(value.get<std::string>(), val) ? val : 0;
+ }
+ else if (value.is<bool>()) return value.get<bool>();
+ else if (value.is<int64_t>()) return value.get<int64_t>();
+ else if (value.is<uint64_t>()) return value.get<uint64_t>();
+ else if (value.is<double>()) return value.get<double>();
+ else return 0;
+}
+
+}
+
+#endif
diff --git a/src/mbgl/style/value_comparison.hpp b/src/mbgl/style/value_comparison.hpp
new file mode 100644
index 0000000000..895c8a0869
--- /dev/null
+++ b/src/mbgl/style/value_comparison.hpp
@@ -0,0 +1,109 @@
+#ifndef MBGL_STYLE_VALUE_COMPARISON
+#define MBGL_STYLE_VALUE_COMPARISON
+
+#include <mbgl/style/value.hpp>
+#include <cstdlib>
+#include <cerrno>
+
+namespace mbgl {
+
+namespace util {
+
+namespace detail {
+
+template <typename Operator>
+struct relaxed_operator_visitor {
+ typedef bool result_type;
+
+ template <typename T0, typename T1>
+ inline bool operator()(T0, T1) const { return false; }
+
+ template <typename T>
+ inline bool operator()(T lhs, T rhs) const { return Operator()(lhs, rhs); }
+
+ inline bool operator()(int64_t lhs, uint64_t rhs) const {
+ return Operator()(double(lhs), double(rhs));
+ }
+
+ inline bool operator()(int64_t lhs, double rhs) const {
+ return Operator()(double(lhs), rhs);
+ }
+
+ inline bool operator()(uint64_t lhs, int64_t rhs) const {
+ return Operator()(double(lhs), double(rhs));
+ }
+
+ inline bool operator()(uint64_t lhs, double rhs) const {
+ return Operator()(double(lhs), rhs);
+ }
+
+ inline bool operator()(double lhs, uint64_t rhs) const {
+ return Operator()(lhs, double(rhs));
+ }
+
+ inline bool operator()(double lhs, int64_t rhs) const {
+ return Operator()(lhs, double(rhs));
+ }
+};
+
+struct relaxed_equal_operator {
+ template <typename T0, typename T1>
+ inline bool operator()(T0 lhs, T1 rhs) const { return lhs == rhs; }
+};
+
+struct relaxed_not_equal_operator {
+ template <typename T0, typename T1>
+ inline bool operator()(T0 lhs, T1 rhs) const { return lhs != rhs; }
+};
+
+struct relaxed_greater_operator {
+ template <typename T0, typename T1>
+ inline bool operator()(T0 lhs, T1 rhs) const { return lhs > rhs; }
+};
+
+struct relaxed_greater_equal_operator {
+ template <typename T0, typename T1>
+ inline bool operator()(T0 lhs, T1 rhs) const { return lhs >= rhs; }
+};
+
+struct relaxed_less_operator {
+ template <typename T0, typename T1>
+ inline bool operator()(T0 lhs, T1 rhs) const { return lhs < rhs; }
+};
+
+struct relaxed_less_equal_operator {
+ template <typename T0, typename T1>
+ inline bool operator()(T0 lhs, T1 rhs) const { return lhs <= rhs; }
+};
+
+} // end namespace detail
+
+inline bool relaxed_equal(Value const &lhs, Value const &rhs) {
+ return apply_visitor(detail::relaxed_operator_visitor<detail::relaxed_equal_operator>(), lhs, rhs);
+}
+
+inline bool relaxed_not_equal(Value const &lhs, Value const &rhs) {
+ return apply_visitor(detail::relaxed_operator_visitor<detail::relaxed_not_equal_operator>(), lhs, rhs);
+}
+
+inline bool relaxed_greater(Value const &lhs, Value const &rhs) {
+ return apply_visitor(detail::relaxed_operator_visitor<detail::relaxed_greater_operator>(), lhs, rhs);
+}
+
+inline bool relaxed_greater_equal(Value const &lhs, Value const &rhs) {
+ return apply_visitor(detail::relaxed_operator_visitor<detail::relaxed_greater_equal_operator>(), lhs, rhs);
+}
+
+inline bool relaxed_less(Value const &lhs, Value const &rhs) {
+ return apply_visitor(detail::relaxed_operator_visitor<detail::relaxed_less_operator>(), lhs, rhs);
+}
+
+inline bool relaxed_less_equal(Value const &lhs, Value const &rhs) {
+ return apply_visitor(detail::relaxed_operator_visitor<detail::relaxed_less_equal_operator>(), lhs, rhs);
+}
+
+}
+
+}
+
+#endif
diff --git a/src/mbgl/text/collision.cpp b/src/mbgl/text/collision.cpp
new file mode 100644
index 0000000000..2e0ec6dce2
--- /dev/null
+++ b/src/mbgl/text/collision.cpp
@@ -0,0 +1,297 @@
+#include <mbgl/text/collision.hpp>
+#include <mbgl/text/rotation_range.hpp>
+#include <mbgl/util/math.hpp>
+
+
+using namespace mbgl;
+
+Box getBox(const CollisionAnchor &anchor, const CollisionRect &bbox, float minScale,
+ float maxScale) {
+ return Box{
+ Point{
+ anchor.x + util::min(bbox.tl.x / minScale, bbox.tl.x / maxScale),
+ anchor.y + util::min(bbox.tl.y / minScale, bbox.tl.y / maxScale),
+ },
+ Point{
+ anchor.x + util::max(bbox.br.x / minScale, bbox.br.x / maxScale),
+ anchor.y + util::max(bbox.br.y / minScale, bbox.br.y / maxScale),
+ },
+ };
+};
+
+Collision::Collision(float zoom_, float tileExtent, float tileSize, float placementDepth)
+ // tile pixels per screen pixels at the tile's zoom level
+ : tilePixelRatio(tileExtent / tileSize),
+
+ zoom(zoom_),
+
+ // Calculate the maximum scale we can go down in our fake-3d rtree so that
+ // placement still makes sense. This is calculated so that the minimum
+ // placement zoom can be at most 25.5 (we use an unsigned integer x10 to
+ // store the minimum zoom).
+ //
+ // We don't want to place labels all the way to 25.5. This lets too many
+ // glyphs be placed, slowing down collision checking. Only place labels if
+ // they will show up within the intended zoom range of the tile.
+ maxPlacementScale(std::exp(std::log(2) * util::min(3.0f, placementDepth, 25.5f - zoom_))) {
+ const float m = 4096;
+ const float edge = m * tilePixelRatio * 2;
+
+
+ PlacementBox left;
+ left.anchor = CollisionAnchor{ 0.0f, 0.0f };
+ left.box = CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{0, edge}};
+ left.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ left.placementScale = 0.5f;
+ left.maxScale = std::numeric_limits<float>::infinity();
+ left.padding = 0.0f;
+ leftEdge = PlacementValue{
+ getBox(left.anchor, left.box, left.placementScale, left.maxScale),
+ left};
+
+ PlacementBox top;
+ top.anchor = CollisionAnchor{ 0.0f, 0.0f };
+ top.box = CollisionRect{CollisionPoint{-edge, -edge}, CollisionPoint{edge, 0}};
+ top.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ top.placementScale = 0.5f;
+ top.maxScale = std::numeric_limits<float>::infinity();
+ top.padding = 0.0f;
+ topEdge = PlacementValue{
+ getBox(top.anchor, top.box, top.placementScale, top.maxScale),
+ top};
+
+ PlacementBox bottom;
+ bottom.anchor = CollisionAnchor{ m, m };
+ bottom.box = CollisionRect{CollisionPoint{-edge, 0}, CollisionPoint{edge, edge}};
+ bottom.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ bottom.placementScale = 0.5f;
+ bottom.maxScale = std::numeric_limits<float>::infinity();
+ bottom.padding = 0.0f;
+ bottomEdge = PlacementValue{
+ getBox(bottom.anchor, bottom.box, bottom.placementScale, bottom.maxScale),
+ bottom};
+
+ PlacementBox right;
+ right.anchor = CollisionAnchor{ m, m };
+ right.box = CollisionRect{CollisionPoint{0, -edge}, CollisionPoint{edge, edge}};
+ right.placementRange = {{ 2.0f * M_PI, 0.0f }};
+ right.placementScale = 0.5f;
+ right.maxScale = std::numeric_limits<float>::infinity();
+ right.padding = 0.0f;
+ rightEdge = PlacementValue{
+ getBox(right.anchor, right.box, right.placementScale, right.maxScale),
+ right};
+
+}
+
+GlyphBox getMergedGlyphs(const GlyphBoxes &boxes, const CollisionAnchor &anchor) {
+ GlyphBox mergedGlyphs;
+ const float inf = std::numeric_limits<float>::infinity();
+ mergedGlyphs.box = CollisionRect{{inf, inf}, {-inf, -inf}};
+ mergedGlyphs.anchor = anchor;
+
+ CollisionRect &box = mergedGlyphs.box;
+ for (const GlyphBox &glyph : boxes) {
+ const CollisionRect &gbox = glyph.box;
+ box.tl.x = util::min(box.tl.x, gbox.tl.x);
+ box.tl.y = util::min(box.tl.y, gbox.tl.y);
+ box.br.x = util::max(box.br.x, gbox.br.x);
+ box.br.y = util::max(box.br.y, gbox.br.y);
+ mergedGlyphs.minScale = util::max(mergedGlyphs.minScale, glyph.minScale);
+ }
+
+ return mergedGlyphs;
+}
+
+float Collision::getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale, bool avoidEdges) {
+
+ for (const GlyphBox &glyph : glyphs) {
+ const CollisionRect &box = glyph.box;
+ const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box;
+ const CollisionAnchor &anchor = glyph.anchor;
+ const float pad = glyph.padding;
+
+
+ if (anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096) {
+ return 0;
+ }
+
+ float minScale = std::fmax(minPlacementScale, glyph.minScale);
+ float maxScale = glyph.maxScale != 0 ? glyph.maxScale : std::numeric_limits<float>::infinity();
+
+ if (minScale >= maxScale) {
+ continue;
+ }
+
+ // Compute the scaled bounding box of the unrotated glyph
+ const Box searchBox = getBox(anchor, bbox, minScale, maxScale);
+
+ std::vector<PlacementValue> blocking;
+ hTree.query(bgi::intersects(searchBox), std::back_inserter(blocking));
+ cTree.query(bgi::intersects(searchBox), std::back_inserter(blocking));
+
+ if (avoidEdges) {
+ if (searchBox.min_corner().get<0>() < 0) blocking.emplace_back(leftEdge);
+ if (searchBox.min_corner().get<1>() < 0) blocking.emplace_back(topEdge);
+ if (searchBox.max_corner().get<0>() >= 4096) blocking.emplace_back(rightEdge);
+ if (searchBox.max_corner().get<1>() >= 4096) blocking.emplace_back(bottomEdge);
+ }
+
+ if (blocking.size()) {
+ const CollisionAnchor &na = anchor; // new anchor
+ const CollisionRect &nb = box; // new box
+
+ for (const PlacementValue &value : blocking) {
+ const PlacementBox &placement = std::get<1>(value);
+ const CollisionAnchor &oa = placement.anchor; // old anchor
+ const CollisionRect &ob = placement.box; // old box
+
+ // If anchors are identical, we're going to skip the label.
+ // NOTE: this isn't right because there can be glyphs with
+ // the same anchor but differing box offsets.
+ if (na == oa) {
+ return 0;
+ }
+
+ // todo: unhardcode the 8 = tileExtent/tileSize
+ float padding = std::fmax(pad, placement.padding) * 8.0f;
+
+ // Original algorithm:
+ float s1 = (ob.tl.x - nb.br.x - padding) /
+ (na.x - oa.x); // scale at which new box is to the left of old box
+ float s2 = (ob.br.x - nb.tl.x + padding) /
+ (na.x - oa.x); // scale at which new box is to the right of old box
+ float s3 = (ob.tl.y - nb.br.y - padding) /
+ (na.y - oa.y); // scale at which new box is to the top of old box
+ float s4 = (ob.br.y - nb.tl.y + padding) /
+ (na.y - oa.y); // scale at which new box is to the bottom of old box
+
+ if (std::isnan(s1) || std::isnan(s2)) {
+ s1 = s2 = 1;
+ }
+ if (std::isnan(s3) || std::isnan(s4)) {
+ s3 = s4 = 1;
+ }
+
+ const float collisionFreeScale = std::fmin(std::fmax(s1, s2), std::fmax(s3, s4));
+
+ // Only update label's min scale if the glyph was
+ // restricted by a collision
+ if (collisionFreeScale > minPlacementScale &&
+ collisionFreeScale > minScale &&
+ collisionFreeScale < maxScale &&
+ collisionFreeScale < placement.maxScale) {
+ minPlacementScale = collisionFreeScale;
+ }
+
+ if (minPlacementScale > maxPlacementScale) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return minPlacementScale;
+}
+
+PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs, float placementScale,
+ bool horizontal) {
+ PlacementRange placementRange = {{2.0f * M_PI, 0}};
+
+ for (const GlyphBox &glyph : glyphs) {
+ const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box;
+ const CollisionAnchor &anchor = glyph.anchor;
+
+ float minPlacedX = anchor.x + bbox.tl.x / placementScale;
+ float minPlacedY = anchor.y + bbox.tl.y / placementScale;
+ float maxPlacedX = anchor.x + bbox.br.x / placementScale;
+ float maxPlacedY = anchor.y + bbox.br.y / placementScale;
+
+ Box query_box{Point{minPlacedX, minPlacedY}, Point{maxPlacedX, maxPlacedY}};
+
+ std::vector<PlacementValue> blocking;
+ hTree.query(bgi::intersects(query_box), std::back_inserter(blocking));
+
+ if (horizontal) {
+ cTree.query(bgi::intersects(query_box), std::back_inserter(blocking));
+ }
+
+ for (const PlacementValue &value : blocking) {
+ const Box &s = std::get<0>(value);
+ const PlacementBox &b = std::get<1>(value);
+ const CollisionRect &bbox2 = b.hBox ? b.hBox.get() : b.box;
+
+ float x1, x2, y1, y2, intersectX, intersectY;
+
+ // Adjust and compare bboxes to see if the glyphs might intersect
+ if (placementScale > b.placementScale) {
+ x1 = b.anchor.x + bbox2.tl.x / placementScale;
+ y1 = b.anchor.y + bbox2.tl.y / placementScale;
+ x2 = b.anchor.x + bbox2.br.x / placementScale;
+ y2 = b.anchor.y + bbox2.br.y / placementScale;
+ intersectX = x1 < maxPlacedX && x2 > minPlacedX;
+ intersectY = y1 < maxPlacedY && y2 > minPlacedY;
+ } else {
+ x1 = anchor.x + bbox.tl.x / b.placementScale;
+ y1 = anchor.y + bbox.tl.y / b.placementScale;
+ x2 = anchor.x + bbox.br.x / b.placementScale;
+ y2 = anchor.y + bbox.br.y / b.placementScale;
+
+ intersectX = x1 < s.max_corner().get<0>() && x2 > s.min_corner().get<0>();
+ intersectY = y1 < s.max_corner().get<1>() && y2 > s.min_corner().get<1>();
+ }
+
+ // If they can't intersect, skip more expensive rotation calculation
+ if (!(intersectX && intersectY))
+ continue;
+
+ float scale = std::fmax(placementScale, b.placementScale);
+ // TODO? glyph.box or glyph.bbox?
+ CollisionRange range = rotationRange(glyph, b, scale);
+
+ placementRange[0] = std::fmin(placementRange[0], range[0]);
+ placementRange[1] = std::fmax(placementRange[1], range[1]);
+ }
+ }
+
+ return placementRange;
+};
+
+void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor,
+ float placementScale, const PlacementRange &placementRange,
+ bool horizontal) {
+ assert(placementScale != std::numeric_limits<float>::infinity());
+
+ std::vector<PlacementValue> allBounds;
+ allBounds.reserve(glyphs.size());
+
+ for (const GlyphBox &glyph : glyphs) {
+ const CollisionRect &box = glyph.box;
+ const CollisionRect &bbox = glyph.hBox ? glyph.hBox.get() : glyph.box;
+
+ const float minScale = util::max(placementScale, glyph.minScale);
+ const float maxScale = glyph.maxScale != 0 ? glyph.maxScale : std::numeric_limits<float>::infinity();
+
+ const Box bounds = getBox(anchor, bbox, minScale, maxScale);
+
+ PlacementBox placement;
+ placement.anchor = anchor;
+ placement.box = box;
+ if (glyph.hBox) {
+ placement.hBox = bbox;
+ }
+ placement.placementRange = placementRange;
+ placement.placementScale = minScale;
+ placement.maxScale = maxScale;
+ placement.padding = glyph.padding;
+
+ allBounds.emplace_back(bounds, placement);
+ }
+
+ // Bulk-insert all glyph boxes
+ if (horizontal) {
+ hTree.insert(allBounds.begin(), allBounds.end());
+ } else {
+ cTree.insert(allBounds.begin(), allBounds.end());
+ }
+}
diff --git a/src/mbgl/text/collision.hpp b/src/mbgl/text/collision.hpp
new file mode 100644
index 0000000000..3bf37a6a12
--- /dev/null
+++ b/src/mbgl/text/collision.hpp
@@ -0,0 +1,58 @@
+#ifndef MBGL_TEXT_COLLISION
+#define MBGL_TEXT_COLLISION
+
+#include <mbgl/text/types.hpp>
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wshadow"
+#ifdef __clang__
+#pragma GCC diagnostic ignored "-Wdeprecated-register"
+#else
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+#include <boost/geometry.hpp>
+#include <boost/geometry/geometries/point.hpp>
+#include <boost/geometry/geometries/box.hpp>
+#include <boost/geometry/index/rtree.hpp>
+#pragma GCC diagnostic pop
+
+namespace mbgl {
+
+namespace bg = boost::geometry;
+namespace bgm = bg::model;
+namespace bgi = bg::index;
+typedef bgm::point<float, 2, bg::cs::cartesian> Point;
+typedef bgm::box<Point> Box;
+typedef std::pair<Box, PlacementBox> PlacementValue;
+typedef bgi::rtree<PlacementValue, bgi::linear<16,4>> Tree;
+
+class Collision {
+
+public:
+ Collision(float zoom, float tileExtent, float tileSize, float placementDepth = 1);
+
+ float getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale, bool avoidEdges);
+ PlacementRange getPlacementRange(const GlyphBoxes &glyphs, float placementScale,
+ bool horizontal);
+ void insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor, float placementScale,
+ const PlacementRange &placementRange, bool horizontal);
+
+private:
+ Tree hTree;
+ Tree cTree;
+ PlacementValue leftEdge;
+ PlacementValue topEdge;
+ PlacementValue rightEdge;
+ PlacementValue bottomEdge;
+
+public:
+ const float tilePixelRatio;
+ const float zoom;
+ const float maxPlacementScale;
+};
+}
+
+#endif
diff --git a/src/mbgl/text/glyph.cpp b/src/mbgl/text/glyph.cpp
new file mode 100644
index 0000000000..f02c710db2
--- /dev/null
+++ b/src/mbgl/text/glyph.cpp
@@ -0,0 +1,14 @@
+#include <mbgl/text/glyph.hpp>
+
+namespace mbgl {
+
+// Note: this only works for the BMP
+GlyphRange getGlyphRange(char32_t glyph) {
+ unsigned start = (glyph/256) * 256;
+ unsigned end = (start + 255);
+ if (start > 65280) start = 65280;
+ if (end > 65533) end = 65533;
+ return { start, end };
+}
+
+}
diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp
new file mode 100644
index 0000000000..4fbb75fc1e
--- /dev/null
+++ b/src/mbgl/text/glyph.hpp
@@ -0,0 +1,60 @@
+#ifndef MBGL_TEXT_GLYPH
+#define MBGL_TEXT_GLYPH
+
+#include <mbgl/util/rect.hpp>
+
+#include <cstdint>
+#include <vector>
+#include <map>
+
+namespace mbgl {
+
+typedef std::pair<uint16_t, uint16_t> GlyphRange;
+
+// Note: this only works for the BMP
+GlyphRange getGlyphRange(char32_t glyph);
+
+struct GlyphMetrics {
+ operator bool() const {
+ return !(width == 0 && height == 0 && advance == 0);
+ }
+
+ // Glyph metrics.
+ uint32_t width = 0;
+ uint32_t height = 0;
+ int32_t left = 0;
+ int32_t top = 0;
+ uint32_t advance = 0;
+
+};
+
+struct Glyph {
+ inline explicit Glyph() : rect(0, 0, 0, 0), metrics() {}
+ inline explicit Glyph(const Rect<uint16_t> &rect_,
+ const GlyphMetrics &metrics_)
+ : rect(rect_), metrics(metrics_) {}
+
+ operator bool() const {
+ return metrics || rect;
+ }
+
+ const Rect<uint16_t> rect;
+ const GlyphMetrics metrics;
+};
+
+typedef std::map<uint32_t, Glyph> GlyphPositions;
+
+class PositionedGlyph {
+public:
+ inline explicit PositionedGlyph(uint32_t glyph_, float x_, float y_)
+ : glyph(glyph_), x(x_), y(y_) {}
+
+ uint32_t glyph = 0;
+ float x = 0;
+ float y = 0;
+};
+
+typedef std::vector<PositionedGlyph> Shaping;
+}
+
+#endif
diff --git a/src/mbgl/text/glyph_store.cpp b/src/mbgl/text/glyph_store.cpp
new file mode 100644
index 0000000000..2f5db180fd
--- /dev/null
+++ b/src/mbgl/text/glyph_store.cpp
@@ -0,0 +1,294 @@
+#include <mbgl/text/glyph_store.hpp>
+
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/utf.hpp>
+#include <mbgl/util/pbf.hpp>
+#include <mbgl/util/url.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/util/token.hpp>
+#include <mbgl/util/math.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/util/uv_detail.hpp>
+#include <algorithm>
+
+namespace mbgl {
+
+
+void FontStack::insert(uint32_t id, const SDFGlyph &glyph) {
+ std::lock_guard<std::mutex> lock(mtx);
+ metrics.emplace(id, glyph.metrics);
+ bitmaps.emplace(id, glyph.bitmap);
+ sdfs.emplace(id, glyph);
+}
+
+const std::map<uint32_t, GlyphMetrics> &FontStack::getMetrics() const {
+ std::lock_guard<std::mutex> lock(mtx);
+ return metrics;
+}
+
+const std::map<uint32_t, SDFGlyph> &FontStack::getSDFs() const {
+ std::lock_guard<std::mutex> lock(mtx);
+ return sdfs;
+}
+
+const Shaping FontStack::getShaping(const std::u32string &string, const float maxWidth,
+ const float lineHeight, const float horizontalAlign,
+ const float verticalAlign, const float justify,
+ const float spacing, const vec2<float> &translate) const {
+ std::lock_guard<std::mutex> lock(mtx);
+
+ Shaping shaping;
+
+ int32_t x = std::round(translate.x * 24); // one em
+ const int32_t y = std::round(translate.y * 24); // one em
+
+ // Loop through all characters of this label and shape.
+ for (uint32_t chr : string) {
+ shaping.emplace_back(chr, x, y);
+ auto metric = metrics.find(chr);
+ if (metric != metrics.end()) {
+ x += metric->second.advance + spacing;
+ }
+ }
+
+ if (!shaping.size())
+ return shaping;
+
+ lineWrap(shaping, lineHeight, maxWidth, horizontalAlign, verticalAlign, justify);
+
+ return shaping;
+}
+
+void align(Shaping &shaping, const float justify, const float horizontalAlign,
+ const float verticalAlign, const uint32_t maxLineLength, const float lineHeight,
+ const uint32_t line) {
+ const float shiftX = (justify - horizontalAlign) * maxLineLength;
+ const float shiftY = (-verticalAlign * (line + 1) + 0.5) * lineHeight;
+
+ for (PositionedGlyph &glyph : shaping) {
+ glyph.x += shiftX;
+ glyph.y += shiftY;
+ }
+}
+
+void justifyLine(Shaping &shaping, const std::map<uint32_t, GlyphMetrics> &metrics, uint32_t start,
+ uint32_t end, float justify) {
+ PositionedGlyph &glyph = shaping[end];
+ auto metric = metrics.find(glyph.glyph);
+ if (metric != metrics.end()) {
+ const uint32_t lastAdvance = metric->second.advance;
+ const float lineIndent = float(glyph.x + lastAdvance) * justify;
+
+ for (uint32_t j = start; j <= end; j++) {
+ shaping[j].x -= lineIndent;
+ }
+ }
+}
+
+void FontStack::lineWrap(Shaping &shaping, const float lineHeight, const float maxWidth,
+ const float horizontalAlign, const float verticalAlign,
+ const float justify) const {
+ uint32_t lastSafeBreak = 0;
+
+ uint32_t lengthBeforeCurrentLine = 0;
+ uint32_t lineStartIndex = 0;
+ uint32_t line = 0;
+
+ uint32_t maxLineLength = 0;
+
+ if (maxWidth) {
+ for (uint32_t i = 0; i < shaping.size(); i++) {
+ PositionedGlyph &shape = shaping[i];
+
+ shape.x -= lengthBeforeCurrentLine;
+ shape.y += lineHeight * line;
+
+ if (shape.x > maxWidth && lastSafeBreak > 0) {
+
+ uint32_t lineLength = shaping[lastSafeBreak + 1].x;
+ maxLineLength = util::max(lineLength, maxLineLength);
+
+ for (uint32_t k = lastSafeBreak + 1; k <= i; k++) {
+ shaping[k].y += lineHeight;
+ shaping[k].x -= lineLength;
+ }
+
+ if (justify) {
+ justifyLine(shaping, metrics, lineStartIndex, lastSafeBreak - 1, justify);
+ }
+
+ lineStartIndex = lastSafeBreak + 1;
+ lastSafeBreak = 0;
+ lengthBeforeCurrentLine += lineLength;
+ line++;
+ }
+
+ if (shape.glyph == 32) {
+ lastSafeBreak = i;
+ }
+ }
+ }
+
+ if (!maxLineLength) maxLineLength = shaping.back().x;
+
+ justifyLine(shaping, metrics, lineStartIndex, uint32_t(shaping.size()) - 1, justify);
+ align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line);
+}
+
+GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, FileSource& fileSource)
+ : future(promise.get_future().share())
+{
+ // Load the glyph set URL
+ std::string url = util::replaceTokens(glyphURL, [&](const std::string &name) -> std::string {
+ if (name == "fontstack") return util::percentEncode(fontStack);
+ if (name == "range") return util::toString(glyphRange.first) + "-" + util::toString(glyphRange.second);
+ return "";
+ });
+
+ // The prepare call jumps back to the main thread.
+ fileSource.prepare([&, url] {
+ auto request = fileSource.request(ResourceType::Glyphs, url);
+ request->onload([&, url](const Response &res) {
+ if (res.code != 200) {
+ // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners.
+ const std::string msg = std::string { "[ERROR] failed to load glyphs (" } + util::toString(res.code) + "): " + res.message;
+ promise.set_exception(std::make_exception_ptr(std::runtime_error(msg)));
+ } else {
+ // Transfer the data to the GlyphSet and signal its availability.
+ // Once it is available, the caller will need to call parse() to actually
+ // parse the data we received. We are not doing this here since this callback is being
+ // called from another (unknown) thread.
+ data = res.data;
+ promise.set_value(*this);
+ }
+ });
+ request->oncancel([&]() {
+ promise.set_exception(std::make_exception_ptr(std::runtime_error("Loading glyphs was canceled")));
+ });
+ });
+}
+
+std::shared_future<GlyphPBF &> GlyphPBF::getFuture() {
+ return future;
+}
+
+void GlyphPBF::parse(FontStack &stack) {
+ std::lock_guard<std::mutex> lock(mtx);
+
+ if (!data.size()) {
+ // If there is no data, this means we either haven't received any data, or
+ // we have already parsed the data.
+ return;
+ }
+
+ // Parse the glyph PBF
+ pbf glyphs_pbf(reinterpret_cast<const uint8_t *>(data.data()), data.size());
+
+ while (glyphs_pbf.next()) {
+ if (glyphs_pbf.tag == 1) { // stacks
+ pbf fontstack_pbf = glyphs_pbf.message();
+ while (fontstack_pbf.next()) {
+ if (fontstack_pbf.tag == 3) { // glyphs
+ pbf glyph_pbf = fontstack_pbf.message();
+
+ SDFGlyph glyph;
+
+ while (glyph_pbf.next()) {
+ if (glyph_pbf.tag == 1) { // id
+ glyph.id = glyph_pbf.varint();
+ } else if (glyph_pbf.tag == 2) { // bitmap
+ glyph.bitmap = glyph_pbf.string();
+ } else if (glyph_pbf.tag == 3) { // width
+ glyph.metrics.width = glyph_pbf.varint();
+ } else if (glyph_pbf.tag == 4) { // height
+ glyph.metrics.height = glyph_pbf.varint();
+ } else if (glyph_pbf.tag == 5) { // left
+ glyph.metrics.left = glyph_pbf.svarint();
+ } else if (glyph_pbf.tag == 6) { // top
+ glyph.metrics.top = glyph_pbf.svarint();
+ } else if (glyph_pbf.tag == 7) { // advance
+ glyph.metrics.advance = glyph_pbf.varint();
+ } else {
+ glyph_pbf.skip();
+ }
+ }
+
+ stack.insert(glyph.id, glyph);
+ } else {
+ fontstack_pbf.skip();
+ }
+ }
+ } else {
+ glyphs_pbf.skip();
+ }
+ }
+
+ data.clear();
+}
+
+GlyphStore::GlyphStore(FileSource& fileSource_) : fileSource(fileSource_) {}
+
+void GlyphStore::setURL(const std::string &url) {
+ glyphURL = url;
+}
+
+
+void GlyphStore::waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges) {
+ // We are implementing a blocking wait with futures: Every GlyphSet has a future that we are
+ // waiting for until it is loaded.
+ if (glyphRanges.empty()) {
+ return;
+ }
+
+ FontStack *stack = nullptr;
+
+ std::vector<std::shared_future<GlyphPBF &>> futures;
+ futures.reserve(glyphRanges.size());
+ {
+ std::lock_guard<std::mutex> lock(mtx);
+ auto &rangeSets = ranges[fontStack];
+
+ stack = &createFontStack(fontStack);
+
+ // Attempt to load the glyph range. If the GlyphSet already exists, we are getting back
+ // the same shared_future.
+ for (GlyphRange range : glyphRanges) {
+ futures.emplace_back(loadGlyphRange(fontStack, rangeSets, range));
+ }
+ }
+
+ // Now that we potentially created all GlyphSets, we are waiting for the results, one by one.
+ // When we get a result (or the GlyphSet is aready loaded), we are attempting to parse the
+ // GlyphSet.
+ for (std::shared_future<GlyphPBF &> &future : futures) {
+ future.get().parse(*stack);
+ }
+}
+
+std::shared_future<GlyphPBF &> GlyphStore::loadGlyphRange(const std::string &fontStack, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, const GlyphRange range) {
+ auto range_it = rangeSets.find(range);
+ if (range_it == rangeSets.end()) {
+ // We don't have this glyph set yet for this font stack.
+ range_it = rangeSets.emplace(range, util::make_unique<GlyphPBF>(glyphURL, fontStack, range, fileSource)).first;
+ }
+
+ return range_it->second->getFuture();
+}
+
+FontStack &GlyphStore::createFontStack(const std::string &fontStack) {
+ auto stack_it = stacks.find(fontStack);
+ if (stack_it == stacks.end()) {
+ stack_it = stacks.emplace(fontStack, util::make_unique<FontStack>()).first;
+ }
+ return *stack_it->second.get();
+}
+
+FontStack &GlyphStore::getFontStack(const std::string &fontStack) {
+ std::lock_guard<std::mutex> lock(mtx);
+ return createFontStack(fontStack);
+}
+
+
+}
diff --git a/src/mbgl/text/glyph_store.hpp b/src/mbgl/text/glyph_store.hpp
new file mode 100644
index 0000000000..95ab92f307
--- /dev/null
+++ b/src/mbgl/text/glyph_store.hpp
@@ -0,0 +1,99 @@
+#ifndef MBGL_TEXT_GLYPH_STORE
+#define MBGL_TEXT_GLYPH_STORE
+
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/util/pbf.hpp>
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/ptr.hpp>
+
+#include <cstdint>
+#include <vector>
+#include <future>
+#include <map>
+#include <set>
+#include <unordered_map>
+
+namespace mbgl {
+
+class FileSource;
+
+class SDFGlyph {
+public:
+ uint32_t id = 0;
+
+ // A signed distance field of the glyph with a border of 3 pixels.
+ std::string bitmap;
+
+ // Glyph metrics
+ GlyphMetrics metrics;
+};
+
+class FontStack {
+public:
+ void insert(uint32_t id, const SDFGlyph &glyph);
+ const std::map<uint32_t, GlyphMetrics> &getMetrics() const;
+ const std::map<uint32_t, SDFGlyph> &getSDFs() const;
+ const Shaping getShaping(const std::u32string &string, float maxWidth, float lineHeight,
+ float horizontalAlign, float verticalAlign, float justify,
+ float spacing, const vec2<float> &translate) const;
+ void lineWrap(Shaping &shaping, float lineHeight, float maxWidth, float horizontalAlign,
+ float verticalAlign, float justify) const;
+
+private:
+ std::map<uint32_t, std::string> bitmaps;
+ std::map<uint32_t, GlyphMetrics> metrics;
+ std::map<uint32_t, SDFGlyph> sdfs;
+ mutable std::mutex mtx;
+};
+
+class GlyphPBF {
+public:
+ GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, FileSource& fileSource);
+
+private:
+ GlyphPBF(const GlyphPBF &) = delete;
+ GlyphPBF(GlyphPBF &&) = delete;
+ GlyphPBF &operator=(const GlyphPBF &) = delete;
+ GlyphPBF &operator=(GlyphPBF &&) = delete;
+
+public:
+ void parse(FontStack &stack);
+
+ std::shared_future<GlyphPBF &> getFuture();
+
+private:
+ std::string data;
+ std::promise<GlyphPBF &> promise;
+ std::shared_future<GlyphPBF &> future;
+ std::mutex mtx;
+};
+
+// Manages Glyphrange PBF loading.
+class GlyphStore {
+public:
+ GlyphStore(FileSource& fileSource);
+
+ // Block until all specified GlyphRanges of the specified font stack are loaded.
+ void waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges);
+
+ FontStack &getFontStack(const std::string &fontStack);
+
+ void setURL(const std::string &url);
+
+private:
+ // Loads an individual glyph range from the font stack and adds it to rangeSets
+ std::shared_future<GlyphPBF &> loadGlyphRange(const std::string &fontStack, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, GlyphRange range);
+
+ FontStack &createFontStack(const std::string &fontStack);
+
+ std::string glyphURL;
+ FileSource& fileSource;
+ std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges;
+ std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks;
+ std::mutex mtx;
+};
+
+
+}
+
+#endif
diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp
new file mode 100644
index 0000000000..84d4e20b2f
--- /dev/null
+++ b/src/mbgl/text/placement.cpp
@@ -0,0 +1,312 @@
+#include <mbgl/text/placement.hpp>
+#include <mbgl/geometry/anchor.hpp>
+#include <mbgl/text/glyph.hpp>
+#include <mbgl/text/placement.hpp>
+#include <mbgl/text/glyph_store.hpp>
+#include <mbgl/style/style_bucket.hpp>
+
+#include <mbgl/util/math.hpp>
+
+namespace mbgl {
+
+const float Placement::globalMinScale = 0.5; // underscale by 1 zoom level
+
+struct GlyphInstance {
+ explicit GlyphInstance(const vec2<float> &anchor_) : anchor(anchor_) {}
+ explicit GlyphInstance(const vec2<float> &anchor_, float offset_, float minScale_, float maxScale_,
+ float angle_)
+ : anchor(anchor_), offset(offset_), minScale(minScale_), maxScale(maxScale_), angle(angle_) {}
+
+ const vec2<float> anchor;
+ const float offset = 0.0f;
+ const float minScale = Placement::globalMinScale;
+ const float maxScale = std::numeric_limits<float>::infinity();
+ const float angle = 0.0f;
+};
+
+typedef std::vector<GlyphInstance> GlyphInstances;
+
+void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, Anchor &anchor,
+ float offset, const std::vector<Coordinate> &line, int segment,
+ int8_t direction, float maxAngle) {
+ const bool upsideDown = direction < 0;
+
+ if (offset < 0)
+ direction *= -1;
+
+ if (direction > 0)
+ segment++;
+
+ vec2<float> newAnchor = anchor;
+
+ if ((int)line.size() <= segment) {
+ return;
+ }
+ vec2<float> end = line[segment];
+ float prevscale = std::numeric_limits<float>::infinity();
+ float prevAngle = 0.0f;
+
+ offset = std::fabs(offset);
+
+ const float placementScale = anchor.scale;
+
+ while (true) {
+ const float dist = util::dist<float>(newAnchor, end);
+ const float scale = offset / dist;
+ float angle =
+ -std::atan2(end.x - newAnchor.x, end.y - newAnchor.y) + direction * M_PI / 2.0f;
+ if (upsideDown)
+ angle += M_PI;
+
+ // Don't place around sharp corners
+ float angleDiff = std::fmod((angle - prevAngle), (2.0f * M_PI));
+ if (prevAngle && std::fabs(angleDiff) > maxAngle) {
+ anchor.scale = prevscale;
+ break;
+ }
+
+ glyphs = GlyphInstance{
+ /* anchor */ newAnchor,
+ /* offset */ static_cast<float>(upsideDown ? M_PI : 0.0),
+ /* minScale */ scale,
+ /* maxScale */ prevscale,
+ /* angle */ static_cast<float>(std::fmod((angle + 2.0 * M_PI), (2.0 * M_PI)))};
+
+ if (scale <= placementScale)
+ break;
+
+ newAnchor = end;
+
+ // skip duplicate nodes
+ while (newAnchor == end) {
+ segment += direction;
+ if ((int)line.size() <= segment || segment < 0) {
+ anchor.scale = scale;
+ return;
+ }
+ end = line[segment];
+ }
+
+ vec2<float> normal = util::normal<float>(newAnchor, end) * dist;
+ newAnchor = newAnchor - normal;
+
+ prevscale = scale;
+ prevAngle = angle;
+ }
+}
+
+GlyphBox getMergedBoxes(const GlyphBoxes &glyphs, const Anchor &anchor) {
+ // Collision checks between rotating and fixed labels are relatively expensive,
+ // so we use one box per label, not per glyph for horizontal labels.
+
+ const float inf = std::numeric_limits<float>::infinity();
+
+ GlyphBox mergedglyphs{/* box */ CollisionRect{inf, inf, -inf, -inf},
+ /* anchor */ anchor,
+ /* minScale */ 0,
+ /* maxScale */ inf,
+ /* padding */ -inf};
+
+ CollisionRect &box = mergedglyphs.box;
+
+ for (const GlyphBox &glyph : glyphs) {
+ const CollisionRect &gbox = glyph.box;
+ box.tl.x = util::min(box.tl.x, gbox.tl.x);
+ box.tl.y = util::min(box.tl.y, gbox.tl.y);
+ box.br.x = util::max(box.br.x, gbox.br.x);
+ box.br.y = util::max(box.br.y, gbox.br.y);
+ mergedglyphs.minScale = util::max(mergedglyphs.minScale, glyph.minScale);
+ mergedglyphs.padding = util::max(mergedglyphs.padding, glyph.padding);
+ }
+ // for all horizontal labels, calculate bbox covering all rotated positions
+ float x12 = box.tl.x * box.tl.x, y12 = box.tl.y * box.tl.y, x22 = box.br.x * box.br.x,
+ y22 = box.br.y * box.br.y,
+ diag = std::sqrt(util::max(x12 + y12, x12 + y22, x22 + y12, x22 + y22));
+
+ mergedglyphs.hBox = CollisionRect{-diag, -diag, diag, diag};
+
+ return mergedglyphs;
+}
+
+Placement Placement::getIcon(Anchor &anchor, const Rect<uint16_t> &image, float boxScale,
+ const std::vector<Coordinate> &line, const StyleBucketSymbol &props) {
+ const float x = image.w / 2.0f; // No need to divide by image.pixelRatio here?
+ const float y = image.h / 2.0f; // image.pixelRatio;
+
+ const float dx = props.icon.offset.x;
+ const float dy = props.icon.offset.y;
+ float x1 = (dx - x);
+ float x2 = (dx + x);
+ float y1 = (dy - y);
+ float y2 = (dy + y);
+
+ vec2<float> tl{x1, y1};
+ vec2<float> tr{x2, y1};
+ vec2<float> br{x2, y2};
+ vec2<float> bl{x1, y2};
+
+ float angle = props.icon.rotate * M_PI / 180.0f;
+ if (anchor.segment >= 0 && props.icon.rotation_alignment != RotationAlignmentType::Viewport) {
+ const Coordinate &next = line[anchor.segment];
+ angle += -std::atan2(next.x - anchor.x, next.y - anchor.y) + M_PI / 2;
+ }
+
+ if (angle) {
+ // Compute the transformation matrix.
+ float angle_sin = std::sin(angle);
+ float angle_cos = std::cos(angle);
+ std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
+
+ tl = tl.matMul(matrix);
+ tr = tr.matMul(matrix);
+ bl = bl.matMul(matrix);
+ br = br.matMul(matrix);
+
+ x1 = util::min(tl.x, tr.x, bl.x, br.x);
+ x2 = util::max(tl.x, tr.x, bl.x, br.x);
+ y1 = util::min(tl.y, tr.y, bl.y, br.y);
+ y2 = util::max(tl.y, tr.y, bl.y, br.y);
+ }
+
+ const CollisionRect box{/* x1 */ x1 * boxScale,
+ /* y1 */ y1 * boxScale,
+ /* x2 */ x2 * boxScale,
+ /* y2 */ y2 * boxScale};
+
+ Placement placement;
+
+ placement.boxes.emplace_back(
+ /* box */ box,
+ /* anchor */ anchor,
+ /* minScale */ Placement::globalMinScale,
+ /* maxScale */ std::numeric_limits<float>::infinity(),
+ /* padding */ props.icon.padding);
+
+ placement.shapes.emplace_back(
+ /* tl */ tl,
+ /* tr */ tr,
+ /* bl */ bl,
+ /* br */ br,
+ /* image */ image,
+ /* angle */ 0,
+ /* anchors */ anchor,
+ /* minScale */ Placement::globalMinScale,
+ /* maxScale */ std::numeric_limits<float>::infinity());
+
+ placement.minScale = anchor.scale;
+
+ return placement;
+}
+
+Placement Placement::getGlyphs(Anchor &anchor, const vec2<float> &origin, const Shaping &shaping,
+ const GlyphPositions &face, float boxScale, bool horizontal,
+ const std::vector<Coordinate> &line,
+ const StyleBucketSymbol &props) {
+ const float maxAngle = props.text.max_angle * M_PI / 180;
+ const float rotate = props.text.rotate * M_PI / 180;
+ const float padding = props.text.padding;
+ const bool alongLine = props.text.rotation_alignment != RotationAlignmentType::Viewport;
+ const bool keepUpright = props.text.keep_upright;
+
+ Placement placement;
+
+ const uint32_t buffer = 3;
+
+ for (const PositionedGlyph &shape : shaping) {
+ auto face_it = face.find(shape.glyph);
+ if (face_it == face.end())
+ continue;
+ const Glyph &glyph = face_it->second;
+ const Rect<uint16_t> &rect = glyph.rect;
+
+ if (!glyph)
+ continue;
+
+ if (!rect)
+ continue;
+
+ const float x = (origin.x + shape.x + glyph.metrics.left - buffer + rect.w / 2) * boxScale;
+
+ GlyphInstances glyphInstances;
+ if (anchor.segment >= 0 && alongLine) {
+ getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x, line, anchor.segment, 1,
+ maxAngle);
+ if (keepUpright)
+ getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x, line,
+ anchor.segment, -1, maxAngle);
+
+ } else {
+ glyphInstances.emplace_back(GlyphInstance{anchor});
+ }
+
+ const float x1 = origin.x + shape.x + glyph.metrics.left - buffer;
+ const float y1 = origin.y + shape.y - glyph.metrics.top - buffer;
+ const float x2 = x1 + glyph.rect.w;
+ const float y2 = y1 + glyph.rect.h;
+
+ const vec2<float> otl{x1, y1};
+ const vec2<float> otr{x2, y1};
+ const vec2<float> obl{x1, y2};
+ const vec2<float> obr{x2, y2};
+
+ const CollisionRect obox{boxScale * x1, boxScale * y1, boxScale * x2, boxScale * y2};
+
+ for (const GlyphInstance &instance : glyphInstances) {
+ vec2<float> tl = otl;
+ vec2<float> tr = otr;
+ vec2<float> bl = obl;
+ vec2<float> br = obr;
+
+ CollisionRect box = obox;
+
+ // Clamp to -90/+90 degrees
+ const float angle = instance.angle + rotate;
+
+ if (angle) {
+ // Compute the transformation matrix.
+ float angle_sin = std::sin(angle);
+ float angle_cos = std::cos(angle);
+ std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};
+
+ tl = tl.matMul(matrix);
+ tr = tr.matMul(matrix);
+ bl = bl.matMul(matrix);
+ br = br.matMul(matrix);
+ }
+
+ // Prevent label from extending past the end of the line
+ const float glyphMinScale = std::max(instance.minScale, anchor.scale);
+
+ // Remember the glyph for later insertion.
+ placement.shapes.emplace_back(
+ tl, tr, bl, br, rect,
+ float(std::fmod((anchor.angle + rotate + instance.offset + 2 * M_PI), (2 * M_PI))),
+ instance.anchor, glyphMinScale, instance.maxScale);
+
+ if (!instance.offset) { // not a flipped glyph
+ if (angle) {
+ // Calculate the rotated glyph's bounding box offsets from the anchor point.
+ box = CollisionRect{boxScale * util::min(tl.x, tr.x, bl.x, br.x),
+ boxScale * util::min(tl.y, tr.y, bl.y, br.y),
+ boxScale * util::max(tl.x, tr.x, bl.x, br.x),
+ boxScale * util::max(tl.y, tr.y, bl.y, br.y)};
+ }
+ placement.boxes.emplace_back(box, instance.anchor, glyphMinScale, instance.maxScale, padding);
+ }
+ }
+ }
+
+ // TODO avoid creating the boxes in the first place?
+ if (horizontal)
+ placement.boxes = {getMergedBoxes(placement.boxes, anchor)};
+
+ const float minPlacementScale = anchor.scale;
+ placement.minScale = std::numeric_limits<float>::infinity();
+ for (const GlyphBox &box : placement.boxes) {
+ placement.minScale = util::min(placement.minScale, box.minScale);
+ }
+ placement.minScale = util::max(minPlacementScale, Placement::globalMinScale);
+
+ return placement;
+}
+}
diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp
new file mode 100644
index 0000000000..28eb8d5317
--- /dev/null
+++ b/src/mbgl/text/placement.hpp
@@ -0,0 +1,31 @@
+#ifndef MBGL_TEXT_PLACEMENT
+#define MBGL_TEXT_PLACEMENT
+
+#include <mbgl/text/types.hpp>
+#include <mbgl/text/glyph.hpp>
+
+#include <mbgl/util/vec.hpp>
+
+namespace mbgl {
+
+struct Anchor;
+class StyleBucketSymbol;
+
+class Placement {
+public:
+ static Placement getIcon(Anchor &anchor, const Rect<uint16_t> &image, float iconBoxScale,
+ const std::vector<Coordinate> &line, const StyleBucketSymbol &props);
+
+ static Placement getGlyphs(Anchor &anchor, const vec2<float> &origin, const Shaping &shaping,
+ const GlyphPositions &face, float boxScale, bool horizontal,
+ const std::vector<Coordinate> &line, const StyleBucketSymbol &props);
+
+ static const float globalMinScale;
+
+ GlyphBoxes boxes;
+ PlacedGlyphs shapes;
+ float minScale;
+};
+}
+
+#endif
diff --git a/src/mbgl/text/rotation_range.cpp b/src/mbgl/text/rotation_range.cpp
new file mode 100644
index 0000000000..664ea9c709
--- /dev/null
+++ b/src/mbgl/text/rotation_range.cpp
@@ -0,0 +1,263 @@
+#include <mbgl/text/rotation_range.hpp>
+
+#include <mbgl/util/interpolate.hpp>
+
+#include <cassert>
+#include <algorithm>
+
+namespace mbgl {
+
+/*
+ * Combine an array of collision ranges to form a continuous
+ * range that includes 0. Collisions within the ignoreRange are ignored
+ */
+CollisionRange mergeCollisions(const CollisionList &collisions,
+ PlacementRange ignoreRange) {
+ // find continuous interval including 0 that doesn't have any collisions
+ float min = 2.0f * M_PI;
+ float max = 0.0f;
+
+ for (CollisionRange collision : collisions) {
+ bool entryOutside =
+ ignoreRange[0] <= collision[0] && collision[0] <= ignoreRange[1];
+ bool exitOutside =
+ ignoreRange[0] <= collision[1] && collision[1] <= ignoreRange[1];
+
+ if (entryOutside && exitOutside) {
+ // no collision, since blocker is out of range
+ } else if (entryOutside) {
+ min = util::min(min, ignoreRange[1]);
+ max = util::max(max, collision[1]);
+ } else if (exitOutside) {
+ min = util::min(min, collision[0]);
+ max = util::max(max, ignoreRange[0]);
+ } else {
+ min = util::min(min, collision[0]);
+ max = util::max(max, collision[1]);
+ }
+ }
+
+ return {{min, max}};
+}
+
+/*
+ * Calculate collision ranges for two rotating boxes.
+ */
+CollisionList
+rotatingRotatingCollisions(const CollisionRect &a, const CollisionRect &b,
+ const CollisionAnchor &anchorToAnchor) {
+ const float d = util::mag<float>(anchorToAnchor);
+ const float d_sq = d * d;
+
+ const CollisionAnchor horizontal = {1, 0};
+ const float angleBetweenAnchors =
+ util::angle_between<float>(anchorToAnchor, horizontal);
+
+ // Calculate angles at which collisions may occur
+ const std::array<float, 8> c = {{
+ // top/bottom
+ /*[0]*/ static_cast<float>(std::asin((float)(a.br.y - b.tl.y) / d)),
+ /*[1]*/ static_cast<float>(std::asin((float)(a.br.y - b.tl.y) / d) + M_PI),
+ /*[2]*/ static_cast<float>(2 * M_PI -
+ std::asin((float)(-a.tl.y + b.br.y) / d)),
+ /*[3]*/ static_cast<float>(M_PI - std::asin((float)(-a.tl.y + b.br.y) / d)),
+
+ // left/right
+ /*[4]*/ static_cast<float>(2 * M_PI -
+ std::acos((float)(a.br.x - b.tl.x) / d)),
+ /*[5]*/ static_cast<float>(std::acos((float)(a.br.x - b.tl.x) / d)),
+ /*[6]*/ static_cast<float>(M_PI - std::acos((float)(-a.tl.x + b.br.x) / d)),
+ /*[7]*/ static_cast<float>(M_PI +
+ std::acos((float)(-a.tl.x + b.br.x) / d))}};
+
+ const float rl = a.br.x - b.tl.x;
+ const float lr = -a.tl.x + b.br.x;
+ const float tb = a.br.y - b.tl.y;
+ const float bt = -a.tl.y + b.br.y;
+
+ // Calculate the distance squared of the diagonal which will be used
+ // to check if the boxes are close enough for collisions to occur at each
+ // angle
+ // todo, triple check these
+ const std::array<float, 8> e = {{
+ // top/bottom
+ /*[0]*/ static_cast<float>(rl * rl + tb * tb),
+ /*[1]*/ static_cast<float>(lr * lr + tb * tb),
+ /*[2]*/ static_cast<float>(rl * rl + bt * bt),
+ /*[3]*/ static_cast<float>(lr * lr + bt * bt),
+
+ // left/right
+ /*[4]*/ static_cast<float>(rl * rl + tb * tb),
+ /*[5]*/ static_cast<float>(rl * rl + bt * bt),
+ /*[6]*/ static_cast<float>(lr * lr + bt * bt),
+ /*[7]*/ static_cast<float>(lr * lr + tb * tb)}};
+
+ std::vector<float> f;
+ for (size_t i = 0; i < c.size(); i++) {
+ // Check if they are close enough to collide
+ if (!std::isnan(c[i]) && d_sq <= e[i]) {
+ // So far, angles have been calulated as relative to the vector
+ // between anchors.
+ // Convert the angles to angles from north.
+ f.push_back(
+ std::fmod((c[i] + angleBetweenAnchors + 2 * M_PI), (2 * M_PI)));
+ }
+ }
+
+ assert(f.size() % 2 == 0);
+
+ // Group the collision angles by two
+ // each group represents a range where the two boxes collide
+ CollisionList collisions;
+ std::sort(f.begin(), f.end());
+ for (size_t k = 0; k < f.size(); k += 2) {
+ collisions.push_back({{f[k], f[k + 1]}});
+ }
+
+ return collisions;
+}
+
+double getAngle(const CollisionPoint &p1, const CollisionPoint &p2,
+ CollisionAngle d, const CollisionPoint &corner) {
+ return std::fmod(util::angle_between(util::interpolate(p1.x, p2.x, d),
+ util::interpolate(p1.y, p2.y, d), corner.x,
+ corner.y) +
+ 2 * M_PI,
+ 2 * M_PI);
+}
+
+/*
+ * Return the intersection points of a circle and a line segment;
+ */
+void circleEdgeCollisions(std::back_insert_iterator<CollisionAngles> angles,
+ const CollisionPoint &corner, float radius,
+ const CollisionPoint &p1, const CollisionPoint &p2) {
+ const CollisionPoint::Type edgeX = p2.x - p1.x;
+ const CollisionPoint::Type edgeY = p2.y - p1.y;
+
+ const CollisionAngle a = edgeX * edgeX + edgeY * edgeY;
+ const CollisionAngle b = (edgeX * p1.x + edgeY * p1.y) * 2;
+ const CollisionAngle c = p1.x * p1.x + p1.y * p1.y - radius * radius;
+
+ const CollisionAngle discriminant = b * b - 4 * a * c;
+
+ // a collision exists only if line intersects circle at two points
+ if (discriminant > 0) {
+ CollisionAngle x1 = (-b - std::sqrt(discriminant)) / (2 * a);
+ CollisionAngle x2 = (-b + std::sqrt(discriminant)) / (2 * a);
+
+ // only add points if within line segment
+ // hack to handle floating point representations of 0 and 1
+ if (0 < x1 && x1 < 1) {
+ angles = getAngle(p1, p2, x1, corner);
+ }
+
+ if (0 < x2 && x2 < 1) {
+ angles = getAngle(p1, p2, x2, corner);
+ }
+ }
+}
+
+/*
+ * Calculate the ranges for which the corner,
+ * rotatated around the anchor, is within the box;
+ */
+void cornerBoxCollisions(std::back_insert_iterator<CollisionList> collisions,
+ const CollisionPoint &corner,
+ const CollisionCorners &boxCorners, bool flip) {
+ float radius = util::mag<float>(corner);
+
+ CollisionAngles angles;
+
+ // Calculate the points at which the corners intersect with the edges
+ for (size_t i = 0, j = 3; i < 4; j = i++) {
+ circleEdgeCollisions(std::back_inserter(angles), corner, radius,
+ boxCorners[j], boxCorners[i]);
+ }
+
+ if (angles.size() % 2 != 0) {
+ // TODO fix
+ // This could get hit when a point intersects very close to a corner
+ // and floating point issues cause only one of the entry or exit to be
+ // counted
+ throw std::runtime_error("expecting an even number of intersections");
+ }
+
+ std::sort(angles.begin(), angles.end());
+
+ // Group by pairs, where each represents a range where a collision occurs
+ for (size_t k = 0; k < angles.size(); k += 2) {
+ CollisionRange range = {{angles[k], angles[k + 1]}};
+ if (flip) {
+ range = util::flip(range);
+ }
+ collisions = range;
+ }
+}
+
+CollisionCorners getCorners(const CollisionRect &a) {
+ return {{{a.tl.x, a.tl.y},
+ {a.tl.x, a.br.y},
+ {a.br.x, a.br.y},
+ {a.br.x, a.tl.y}}};
+}
+
+/*
+ * Calculate collision ranges for a rotating box and a fixed box;
+ */
+CollisionList rotatingFixedCollisions(const CollisionRect &rotating,
+ const CollisionRect &fixed) {
+ const auto cornersR = getCorners(rotating);
+ const auto cornersF = getCorners(fixed);
+
+ // A collision occurs when, and only at least one corner from one of the
+ // boxes is within the other box. Calculate these ranges for each corner.
+
+ CollisionList collisions;
+
+ for (size_t i = 0; i < 4; i++) {
+ cornerBoxCollisions(std::back_inserter(collisions), cornersR[i],
+ cornersF);
+ cornerBoxCollisions(std::back_inserter(collisions), cornersF[i],
+ cornersR, true);
+ }
+
+ return collisions;
+}
+
+/*
+ * Calculate the range a box conflicts with a second box
+ */
+CollisionRange rotationRange(const GlyphBox &inserting,
+ const PlacementBox &blocker, float scale) {
+ CollisionList collisions;
+
+ const GlyphBox &a = inserting;
+ const PlacementBox &b = blocker;
+
+ // Instead of scaling the boxes, we move the anchors
+ CollisionAnchor relativeAnchor{
+ static_cast<float>((b.anchor.x - a.anchor.x) * scale),
+ static_cast<float>((b.anchor.y - a.anchor.y) * scale)};
+
+ // Generate a list of collision interval
+ if (a.hBox && b.hBox) {
+ collisions = rotatingRotatingCollisions(a.box, b.box, relativeAnchor);
+ } else if (a.hBox) {
+ const CollisionRect box {
+ b.box.tl.x + relativeAnchor.x, b.box.tl.y + relativeAnchor.y,
+ b.box.br.x + relativeAnchor.x, b.box.br.y + relativeAnchor.y};
+ collisions = rotatingFixedCollisions(a.box, box);
+ } else if (b.hBox) {
+ const CollisionRect box {
+ a.box.tl.x - relativeAnchor.x, a.box.tl.y - relativeAnchor.y,
+ a.box.br.x - relativeAnchor.x, a.box.br.y - relativeAnchor.y};
+ collisions = rotatingFixedCollisions(b.box, box);
+ } else {
+ // collisions remains empty
+ }
+
+ // Find and return the continous are around 0 where there are no collisions
+ return mergeCollisions(collisions, blocker.placementRange);
+}
+}
diff --git a/src/mbgl/text/rotation_range.hpp b/src/mbgl/text/rotation_range.hpp
new file mode 100644
index 0000000000..4968fda164
--- /dev/null
+++ b/src/mbgl/text/rotation_range.hpp
@@ -0,0 +1,54 @@
+#ifndef MBGL_TEXT_ROTATION_RANGE
+#define MBGL_TEXT_ROTATION_RANGE
+
+#include <mbgl/util/math.hpp>
+#include <mbgl/text/types.hpp>
+
+#include <vector>
+#include <cassert>
+
+namespace mbgl {
+
+/*
+ * Combine an array of collision ranges to form a continuous
+ * range that includes 0. Collisions within the ignoreRange are ignored
+ */
+CollisionRange mergeCollisions(const CollisionList &collisions,
+ PlacementRange ignoreRange);
+
+/*
+ * Calculate collision ranges for two rotating boxes.e
+ */
+CollisionList rotatingRotatingCollisions(const CollisionRect &a,
+ const CollisionRect &b,
+ const CollisionAnchor &anchorToAnchor);
+
+/*
+ * Return the intersection points of a circle and a line segment;
+ */
+void circleEdgeCollisions(std::back_insert_iterator<CollisionAngles> angles,
+ const CollisionPoint &corner, float radius,
+ const CollisionPoint &p1, const CollisionPoint &p2);
+
+/*
+ * Calculate the ranges for which the corner,
+ * rotatated around the anchor, is within the box;
+ */
+void cornerBoxCollisions(std::back_insert_iterator<CollisionList> collisions,
+ const CollisionPoint &corner,
+ const CollisionCorners &boxCorners, bool flip = false);
+
+/*
+ * Calculate collision ranges for a rotating box and a fixed box;
+ */
+CollisionList rotatingFixedCollisions(const CollisionRect &rotating,
+ const CollisionRect &fixed);
+
+/*
+ * Calculate the range a box conflicts with a second box
+ */
+CollisionRange rotationRange(const GlyphBox &inserting,
+ const PlacementBox &blocker, float scale);
+}
+
+#endif
diff --git a/src/mbgl/text/types.hpp b/src/mbgl/text/types.hpp
new file mode 100644
index 0000000000..23f49aa748
--- /dev/null
+++ b/src/mbgl/text/types.hpp
@@ -0,0 +1,113 @@
+#ifndef MBGL_TEXT_TYPES
+#define MBGL_TEXT_TYPES
+
+#include <mbgl/util/vec.hpp>
+#include <mbgl/util/rect.hpp>
+#include <mbgl/util/optional.hpp>
+#include <array>
+#include <vector>
+
+namespace mbgl {
+
+typedef vec2<float> CollisionPoint;
+typedef vec2<float> CollisionAnchor;
+
+typedef std::array<float, 2> PlacementRange;
+typedef float CollisionAngle;
+typedef std::vector<CollisionAngle> CollisionAngles;
+typedef std::array<CollisionAngle, 2> CollisionRange;
+typedef std::vector<CollisionRange> CollisionList;
+typedef std::array<CollisionPoint, 4> CollisionCorners;
+
+struct CollisionRect {
+ CollisionPoint tl;
+ CollisionPoint br;
+ inline explicit CollisionRect() {}
+ inline explicit CollisionRect(CollisionPoint::Type ax,
+ CollisionPoint::Type ay,
+ CollisionPoint::Type bx,
+ CollisionPoint::Type by)
+ : tl(ax, ay), br(bx, by) {}
+ inline explicit CollisionRect(const CollisionPoint &tl_,
+ const CollisionPoint &br_)
+ : tl(tl_), br(br_) {}
+};
+
+// These are the glyph boxes that we want to have placed.
+struct GlyphBox {
+ explicit GlyphBox() {}
+ explicit GlyphBox(const CollisionRect &box_,
+ const CollisionAnchor &anchor_,
+ float minScale_,
+ float maxScale_,
+ float padding_)
+ : box(box_), anchor(anchor_), minScale(minScale_), maxScale(maxScale_), padding(padding_) {}
+ explicit GlyphBox(const CollisionRect &box_,
+ float minScale_,
+ float padding_)
+ : box(box_), minScale(minScale_), padding(padding_) {}
+
+ CollisionRect box;
+ CollisionAnchor anchor;
+ float minScale = 0.0f;
+ float maxScale = std::numeric_limits<float>::infinity();
+ float padding = 0.0f;
+ mapbox::util::optional<CollisionRect> hBox;
+};
+
+typedef std::vector<GlyphBox> GlyphBoxes;
+
+
+// TODO: Transform the vec2<float>s to vec2<int16_t> to save bytes
+struct PlacedGlyph {
+ explicit PlacedGlyph(const vec2<float> &tl_, const vec2<float> &tr_,
+ const vec2<float> &bl_, const vec2<float> &br_,
+ const Rect<uint16_t> &tex_, float angle_, const vec2<float> &anchor_,
+ float minScale_, float maxScale_)
+ : tl(tl_),
+ tr(tr_),
+ bl(bl_),
+ br(br_),
+ tex(tex_),
+ angle(angle_),
+ anchor(anchor_),
+ minScale(minScale_),
+ maxScale(maxScale_) {}
+
+ vec2<float> tl, tr, bl, br;
+ Rect<uint16_t> tex;
+ float angle;
+ vec2<float> anchor;
+ float minScale, maxScale;
+};
+
+typedef std::vector<PlacedGlyph> PlacedGlyphs;
+
+// These are the placed boxes contained in the rtree.
+struct PlacementBox {
+ CollisionAnchor anchor;
+ CollisionRect box;
+ mapbox::util::optional<CollisionRect> hBox;
+ PlacementRange placementRange = {{0.0f, 0.0f}};
+ float placementScale = 0.0f;
+ float maxScale = std::numeric_limits<float>::infinity();
+ float padding = 0.0f;
+};
+
+struct PlacementProperty {
+ explicit PlacementProperty() {}
+ explicit PlacementProperty(float zoom_, const PlacementRange &rotationRange_)
+ : zoom(zoom_), rotationRange(rotationRange_) {}
+
+ inline operator bool() const {
+ return !std::isnan(zoom) && zoom != std::numeric_limits<float>::infinity() &&
+ rotationRange[0] != rotationRange[1];
+ }
+
+ float zoom = std::numeric_limits<float>::infinity();
+ PlacementRange rotationRange = {{0.0f, 0.0f}};
+};
+
+}
+
+#endif
diff --git a/src/mbgl/util/box.hpp b/src/mbgl/util/box.hpp
new file mode 100644
index 0000000000..55a5d46fbc
--- /dev/null
+++ b/src/mbgl/util/box.hpp
@@ -0,0 +1,15 @@
+#ifndef MBGL_UTIL_BOX
+#define MBGL_UTIL_BOX
+
+#include <mbgl/util/vec.hpp>
+
+namespace mbgl {
+
+struct box {
+ vec2<double> tl, tr, bl, br;
+ vec2<double> center;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/util/clip_ids.cpp b/src/mbgl/util/clip_ids.cpp
new file mode 100644
index 0000000000..9c391c38ad
--- /dev/null
+++ b/src/mbgl/util/clip_ids.cpp
@@ -0,0 +1,96 @@
+#include <mbgl/util/clip_ids.hpp>
+#include <mbgl/map/tile.hpp>
+
+#include <mbgl/util/math.hpp>
+
+#include <list>
+#include <vector>
+#include <bitset>
+#include <cassert>
+#include <iostream>
+#include <algorithm>
+
+namespace mbgl {
+
+ClipIDGenerator::Leaf::Leaf(Tile &tile_) : tile(tile_) {}
+
+void ClipIDGenerator::Leaf::add(const Tile::ID &p) {
+ if (p.isChildOf(tile.id)) {
+ // Ensure that no already present child is a parent of the new p.
+ for (const Tile::ID &child : children) {
+ if (p.isChildOf(child))
+ return;
+ }
+ children.push_front(p);
+ }
+}
+
+bool ClipIDGenerator::Leaf::operator==(const Leaf &other) const {
+ return tile.id == other.tile.id && children == other.children;
+}
+
+bool ClipIDGenerator::reuseExisting(Leaf &leaf) {
+ for (const std::vector<Leaf> &pool : pools) {
+ auto existing = std::find(pool.begin(), pool.end(), leaf);
+ if (existing != pool.end()) {
+ leaf.tile.clip = existing->tile.clip;
+ return true;
+ }
+ }
+ return false;
+}
+
+void ClipIDGenerator::update(std::forward_list<Tile *> tiles) {
+ Pool pool;
+
+ tiles.sort([](const Tile *a, const Tile *b) {
+ return a->id < b->id;
+ });
+
+ const auto end = tiles.end();
+ for (auto it = tiles.begin(); it != end; it++) {
+ if (!*it) {
+ // Handle null pointers.
+ continue;
+ }
+
+ Tile &tile = **it;
+ Leaf clip { tile };
+
+ // Try to add all remaining ids as children. We sorted the tile list
+ // by z earlier, so all preceding items cannot be children of the current
+ // tile.
+ for (auto child_it = std::next(it); child_it != end; child_it++) {
+ clip.add((*child_it)->id);
+ }
+ clip.children.sort();
+
+ // Loop through all existing pools and try to find a matching ClipID.
+ if (!reuseExisting(clip)) {
+ // We haven't found an existing clip ID
+ pool.push_back(std::move(clip));
+ }
+ }
+
+ if (pool.size()) {
+ const uint32_t bit_count = util::ceil_log2(pool.size() + 1);
+ const std::bitset<8> mask = uint64_t(((1 << bit_count) - 1) << bit_offset);
+
+ // We are starting our count with 1 since we need at least 1 bit set to distinguish between
+ // areas without any tiles whatsoever and the current area.
+ uint8_t count = 1;
+ for (Leaf &leaf : pool) {
+ leaf.tile.clip.mask = mask;
+ leaf.tile.clip.reference = count++ << bit_offset;
+ }
+
+ bit_offset += bit_count;
+ pools.push_front(std::move(pool));
+ }
+
+ if (bit_offset > 8) {
+ fprintf(stderr, "stencil mask overflow\n");
+ }
+}
+
+}
diff --git a/src/mbgl/util/clip_ids.hpp b/src/mbgl/util/clip_ids.hpp
new file mode 100644
index 0000000000..5855b16af7
--- /dev/null
+++ b/src/mbgl/util/clip_ids.hpp
@@ -0,0 +1,38 @@
+#ifndef MBGL_UTIL_CLIP_IDS
+#define MBGL_UTIL_CLIP_IDS
+
+#include <mbgl/map/tile.hpp>
+#include <list>
+#include <set>
+#include <vector>
+#include <forward_list>
+#include <map>
+
+namespace mbgl {
+
+class ClipIDGenerator {
+private:
+ struct Leaf {
+ Leaf(Tile &tile);
+ void add(const Tile::ID &p);
+ bool operator==(const Leaf &other) const;
+
+ Tile &tile;
+ std::forward_list<Tile::ID> children;
+ };
+
+ typedef std::vector<Leaf> Pool;
+ std::forward_list<Pool> pools;
+ uint8_t bit_offset = 0;
+
+private:
+ bool reuseExisting(Leaf &leaf);
+
+public:
+ void update(std::forward_list<Tile *> tiles);
+};
+
+
+}
+
+#endif
diff --git a/src/mbgl/util/compression.cpp b/src/mbgl/util/compression.cpp
new file mode 100644
index 0000000000..d6d6370546
--- /dev/null
+++ b/src/mbgl/util/compression.cpp
@@ -0,0 +1,81 @@
+#include <mbgl/util/compression.hpp>
+
+#include <zlib.h>
+
+#include <cstring>
+#include <stdexcept>
+
+namespace mbgl {
+namespace util {
+
+std::string compress(const std::string &raw) {
+ z_stream deflate_stream;
+ memset(&deflate_stream, 0, sizeof(deflate_stream));
+
+ // TODO: reuse z_streams
+ if (deflateInit(&deflate_stream, Z_DEFAULT_COMPRESSION) != Z_OK) {
+ throw std::runtime_error("failed to initialize deflate");
+ }
+
+ deflate_stream.next_in = (Bytef *)raw.data();
+ deflate_stream.avail_in = uInt(raw.size());
+
+ std::string result;
+ char out[16384];
+
+ int code;
+ do {
+ deflate_stream.next_out = reinterpret_cast<Bytef *>(out);
+ deflate_stream.avail_out = sizeof(out);
+ code = deflate(&deflate_stream, Z_FINISH);
+ if (result.size() < deflate_stream.total_out) {
+ // append the block to the output string
+ result.append(out, deflate_stream.total_out - result.size());
+ }
+ } while (code == Z_OK);
+
+ deflateEnd(&deflate_stream);
+
+ if (code != Z_STREAM_END) {
+ throw std::runtime_error(deflate_stream.msg);
+ }
+
+ return result;
+}
+
+std::string decompress(const std::string &raw) {
+ z_stream inflate_stream;
+ memset(&inflate_stream, 0, sizeof(inflate_stream));
+
+ // TODO: reuse z_streams
+ if (inflateInit(&inflate_stream) != Z_OK) {
+ throw std::runtime_error("failed to initialize inflate");
+ }
+
+ inflate_stream.next_in = (Bytef *)raw.data();
+ inflate_stream.avail_in = uInt(raw.size());
+
+ std::string result;
+ char out[15384];
+
+ int code;
+ do {
+ inflate_stream.next_out = reinterpret_cast<Bytef *>(out);
+ inflate_stream.avail_out = sizeof(out);
+ code = inflate(&inflate_stream, 0);
+ // result.append(out, sizeof(out) - inflate_stream.avail_out);
+ if (result.size() < inflate_stream.total_out) {
+ result.append(out, inflate_stream.total_out - result.size());
+ }
+ } while (code == Z_OK);
+
+ inflateEnd(&inflate_stream);
+
+ if (code != Z_STREAM_END) {
+ throw std::runtime_error(inflate_stream.msg);
+ }
+
+ return result;
+}
+}
+}
diff --git a/src/mbgl/util/compression.hpp b/src/mbgl/util/compression.hpp
new file mode 100644
index 0000000000..a33b2476a7
--- /dev/null
+++ b/src/mbgl/util/compression.hpp
@@ -0,0 +1,15 @@
+#ifndef MBGL_UTIL_COMPRESSION
+#define MBGL_UTIL_COMPRESSION
+
+#include <string>
+
+namespace mbgl {
+namespace util {
+
+std::string compress(const std::string &raw);
+std::string decompress(const std::string &raw);
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/constants.cpp b/src/mbgl/util/constants.cpp
new file mode 100644
index 0000000000..3d1422e6c7
--- /dev/null
+++ b/src/mbgl/util/constants.cpp
@@ -0,0 +1,27 @@
+#include <mbgl/util/constants.hpp>
+
+const float mbgl::util::tileSize = 512.0f;
+
+#if defined(DEBUG)
+const bool mbgl::debug::tileParseWarnings = false;
+const bool mbgl::debug::styleParseWarnings = false;
+const bool mbgl::debug::spriteWarnings = false;
+const bool mbgl::debug::renderWarnings = false;
+const bool mbgl::debug::renderTree = false;
+const bool mbgl::debug::labelTextMissingWarning = true;
+const bool mbgl::debug::missingFontStackWarning = true;
+const bool mbgl::debug::missingFontFaceWarning = true;
+const bool mbgl::debug::glyphWarning = true;
+const bool mbgl::debug::shapingWarning = true;
+#else
+const bool mbgl::debug::tileParseWarnings = false;
+const bool mbgl::debug::styleParseWarnings = false;
+const bool mbgl::debug::spriteWarnings = false;
+const bool mbgl::debug::renderWarnings = false;
+const bool mbgl::debug::renderTree = false;
+const bool mbgl::debug::labelTextMissingWarning = false;
+const bool mbgl::debug::missingFontStackWarning = false;
+const bool mbgl::debug::missingFontFaceWarning = false;
+const bool mbgl::debug::glyphWarning = false;
+const bool mbgl::debug::shapingWarning = false;
+#endif
diff --git a/src/mbgl/util/constants.hpp b/src/mbgl/util/constants.hpp
new file mode 100644
index 0000000000..98365e0f32
--- /dev/null
+++ b/src/mbgl/util/constants.hpp
@@ -0,0 +1,31 @@
+#ifndef MBGL_UTIL_CONSTANTS
+#define MBGL_UTIL_CONSTANTS
+
+#include <cmath>
+
+namespace mbgl {
+
+namespace util {
+
+extern const float tileSize;
+
+}
+
+namespace debug {
+
+extern const bool tileParseWarnings;
+extern const bool styleParseWarnings;
+extern const bool spriteWarnings;
+extern const bool renderWarnings;
+extern const bool renderTree;
+extern const bool labelTextMissingWarning;
+extern const bool missingFontStackWarning;
+extern const bool missingFontFaceWarning;
+extern const bool glyphWarning;
+extern const bool shapingWarning;
+
+}
+
+}
+
+#endif
diff --git a/src/mbgl/util/error.hpp b/src/mbgl/util/error.hpp
new file mode 100644
index 0000000000..09fa8d3e21
--- /dev/null
+++ b/src/mbgl/util/error.hpp
@@ -0,0 +1,20 @@
+#ifndef MBGL_UTIL_ERROR
+#define MBGL_UTIL_ERROR
+
+#include <stdexcept>
+#include <string>
+
+namespace mbgl {
+namespace error {
+
+struct style_parse : std::exception {
+ inline style_parse(size_t offset_, const char *msg_) : offset(offset_), msg(msg_) {}
+ inline const char* what() const noexcept { return msg.c_str(); }
+ const size_t offset;
+ const std::string msg;
+};
+}
+
+}
+
+#endif \ No newline at end of file
diff --git a/src/mbgl/util/interpolate.hpp b/src/mbgl/util/interpolate.hpp
new file mode 100644
index 0000000000..c9232db4eb
--- /dev/null
+++ b/src/mbgl/util/interpolate.hpp
@@ -0,0 +1,27 @@
+#ifndef MBGL_UTIL_INTERPOLATE
+#define MBGL_UTIL_INTERPOLATE
+
+#include <array>
+
+namespace mbgl {
+namespace util {
+
+template <typename T>
+T interpolate(const T a, const T b, const double t) {
+ return a * (1.0 - t) + b * t;
+}
+
+template <typename T>
+inline std::array<T, 4> interpolate(const std::array<T, 4>& a, const std::array<T, 4>& b, const double t) {
+ return {{
+ interpolate(a[0], b[0], t),
+ interpolate(a[1], b[1], t),
+ interpolate(a[2], b[2], t),
+ interpolate(a[3], b[3], t)
+ }};
+}
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/io.cpp b/src/mbgl/util/io.cpp
new file mode 100644
index 0000000000..76f7c35ade
--- /dev/null
+++ b/src/mbgl/util/io.cpp
@@ -0,0 +1,34 @@
+#include <mbgl/util/io.hpp>
+
+#include <cstdio>
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <stdexcept>
+
+namespace mbgl {
+namespace util {
+
+void write_file(const std::string &filename, const std::string &data) {
+ FILE *fd = fopen(filename.c_str(), "wb");
+ if (fd) {
+ fwrite(data.data(), sizeof(std::string::value_type), data.size(), fd);
+ fclose(fd);
+ } else {
+ throw std::runtime_error(std::string("Failed to open file ") + filename);
+ }
+}
+
+std::string read_file(const std::string &filename) {
+ std::ifstream file(filename);
+ if (file.good()) {
+ std::stringstream data;
+ data << file.rdbuf();
+ return data.str();
+ } else {
+ throw std::runtime_error(std::string("Cannot read file ") + filename);
+ }
+}
+
+}
+}
diff --git a/src/mbgl/util/io.hpp b/src/mbgl/util/io.hpp
new file mode 100644
index 0000000000..e95fc16d9d
--- /dev/null
+++ b/src/mbgl/util/io.hpp
@@ -0,0 +1,15 @@
+#ifndef MBGL_UTIL_IO
+#define MBGL_UTIL_IO
+
+#include <string>
+
+namespace mbgl {
+namespace util {
+
+void write_file(const std::string &filename, const std::string &data);
+std::string read_file(const std::string &filename);
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/mapbox.cpp b/src/mbgl/util/mapbox.cpp
new file mode 100644
index 0000000000..277b647f34
--- /dev/null
+++ b/src/mbgl/util/mapbox.cpp
@@ -0,0 +1,44 @@
+#include <mbgl/util/mapbox.hpp>
+
+#include <stdexcept>
+
+namespace mbgl {
+namespace util {
+namespace mapbox {
+
+const std::string mapbox = "mapbox://";
+
+std::string normalizeURL(const std::string& url, const std::string& accessToken) {
+ if (accessToken.empty())
+ throw std::runtime_error("You must provide a Mapbox API access token for Mapbox tile sources");
+
+ return std::string("https://api.tiles.mapbox.com/v4/")
+ + url.substr(mapbox.length())
+ + "?access_token="
+ + accessToken;
+}
+
+std::string normalizeSourceURL(const std::string& url, const std::string& accessToken) {
+ if (url.compare(0, mapbox.length(), mapbox) != 0)
+ return url;
+
+ std::string result = normalizeURL(url + ".json", accessToken);
+
+ // TileJSON requests need a secure flag appended to their URLs so
+ // that the server knows to send SSL-ified resource references.
+ if (url.compare(0, 5, "https") == 0)
+ result += "&secure";
+
+ return result;
+}
+
+std::string normalizeGlyphsURL(const std::string& url, const std::string& accessToken) {
+ if (url.compare(0, mapbox.length(), mapbox) != 0)
+ return url;
+
+ return normalizeURL(url, accessToken);
+}
+
+}
+}
+}
diff --git a/src/mbgl/util/mapbox.hpp b/src/mbgl/util/mapbox.hpp
new file mode 100644
index 0000000000..0fbb9a91ed
--- /dev/null
+++ b/src/mbgl/util/mapbox.hpp
@@ -0,0 +1,17 @@
+#ifndef MBGL_UTIL_MAPBOX
+#define MBGL_UTIL_MAPBOX
+
+#include <string>
+
+namespace mbgl {
+namespace util {
+namespace mapbox {
+
+std::string normalizeSourceURL(const std::string& url, const std::string& accessToken);
+std::string normalizeGlyphsURL(const std::string& url, const std::string& accessToken);
+
+}
+}
+}
+
+#endif
diff --git a/src/mbgl/util/mat3.cpp b/src/mbgl/util/mat3.cpp
new file mode 100644
index 0000000000..263768ee41
--- /dev/null
+++ b/src/mbgl/util/mat3.cpp
@@ -0,0 +1,95 @@
+// This is an incomplete port of http://glmatrix.net/
+//
+// Copyright (c) 2013 Brandon Jones, Colin MacKenzie IV
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not claim
+// that you wrote the original software. If you use this software in a
+// product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+
+#include <mbgl/util/mat3.hpp>
+
+#include <cmath>
+
+using namespace mbgl;
+
+void matrix::identity(mat3& out) {
+ out[0] = 1.0f;
+ out[1] = 0.0f;
+ out[2] = 0.0f;
+ out[3] = 0.0f;
+ out[4] = 1.0f;
+ out[5] = 0.0f;
+ out[6] = 0.0f;
+ out[7] = 0.0f;
+ out[8] = 1.0f;
+}
+
+void matrix::translate(mat3& out, const mat3& a, float x, float y) {
+ float a00 = a[0], a01 = a[1], a02 = a[2],
+ a10 = a[3], a11 = a[4], a12 = a[5],
+ a20 = a[6], a21 = a[7], a22 = a[8];
+
+ out[0] = a00;
+ out[1] = a01;
+ out[2] = a02;
+
+ out[3] = a10;
+ out[4] = a11;
+ out[5] = a12;
+
+ out[6] = x * a00 + y * a10 + a20;
+ out[7] = x * a01 + y * a11 + a21;
+ out[8] = x * a02 + y * a12 + a22;
+}
+
+void matrix::rotate(mat3& out, const mat3& a, float rad) {
+ float s = std::sin(rad),
+ c = std::cos(rad),
+ a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a10 = a[3],
+ a11 = a[4],
+ a12 = a[5],
+ a20 = a[6],
+ a21 = a[7],
+ a22 = a[8];
+
+ out[0] = c * a00 + s * a10;
+ out[1] = c * a01 + s * a11;
+ out[2] = c * a02 + s * a12;
+
+ out[3] = c * a10 - s * a00;
+ out[4] = c * a11 - s * a01;
+ out[5] = c * a12 - s * a02;
+
+ out[6] = a20;
+ out[7] = a21;
+ out[8] = a22;
+};
+
+void matrix::scale(mat3& out, const mat3& a, float x, float y) {
+ out[0] = x * a[0];
+ out[1] = x * a[1];
+ out[2] = x * a[2];
+ out[3] = y * a[3];
+ out[4] = y * a[4];
+ out[5] = y * a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+}
diff --git a/src/mbgl/util/mat3.hpp b/src/mbgl/util/mat3.hpp
new file mode 100644
index 0000000000..fa40751764
--- /dev/null
+++ b/src/mbgl/util/mat3.hpp
@@ -0,0 +1,42 @@
+// This is an incomplete port of http://glmatrix.net/
+//
+// Copyright (c) 2013 Brandon Jones, Colin MacKenzie IV
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not claim
+// that you wrote the original software. If you use this software in a
+// product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+
+#ifndef MBGL_UTIL_MAT3
+#define MBGL_UTIL_MAT3
+
+#include <array>
+
+namespace mbgl {
+
+typedef std::array<float, 9> mat3;
+
+namespace matrix {
+
+void identity(mat3& out);
+void translate(mat3& out, const mat3& a, float x, float y);
+void rotate(mat3& out, const mat3& a, float rad);
+void scale(mat3& out, const mat3& a, float x, float y);
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/mat4.cpp b/src/mbgl/util/mat4.cpp
new file mode 100644
index 0000000000..50270d9217
--- /dev/null
+++ b/src/mbgl/util/mat4.cpp
@@ -0,0 +1,198 @@
+// This is an incomplete port of http://glmatrix.net/
+//
+// Copyright (c) 2013 Brandon Jones, Colin MacKenzie IV
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the
+// use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not claim
+// that you wrote the original software. If you use this software in a
+// product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+
+#include <mbgl/util/mat4.hpp>
+
+#include <cmath>
+
+using namespace mbgl;
+
+void matrix::identity(mat4& out) {
+ out[0] = 1.0f;
+ out[1] = 0.0f;
+ out[2] = 0.0f;
+ out[3] = 0.0f;
+ out[4] = 0.0f;
+ out[5] = 1.0f;
+ out[6] = 0.0f;
+ out[7] = 0.0f;
+ out[8] = 0.0f;
+ out[9] = 0.0f;
+ out[10] = 1.0f;
+ out[11] = 0.0f;
+ out[12] = 0.0f;
+ out[13] = 0.0f;
+ out[14] = 0.0f;
+ out[15] = 1.0f;
+}
+
+void matrix::ortho(mat4& out, float left, float right, float bottom, float top, float near, float far) {
+ float lr = 1.0f / (left - right),
+ bt = 1.0f / (bottom - top),
+ nf = 1.0f / (near - far);
+ out[0] = -2.0f * lr;
+ out[1] = 0.0f;
+ out[2] = 0.0f;
+ out[3] = 0.0f;
+ out[4] = 0.0f;
+ out[5] = -2.0f * bt;
+ out[6] = 0.0f;
+ out[7] = 0.0f;
+ out[8] = 0.0f;
+ out[9] = 0.0f;
+ out[10] = 2.0f * nf;
+ out[11] = 0.0f;
+ out[12] = (left + right) * lr;
+ out[13] = (top + bottom) * bt;
+ out[14] = (far + near) * nf;
+ out[15] = 1.0f;
+}
+
+void matrix::copy(mat4& out, const mat4& a) {
+ out[0] = a[0];
+ out[1] = a[1];
+ out[2] = a[2];
+ out[3] = a[3];
+ out[4] = a[4];
+ out[5] = a[5];
+ out[6] = a[6];
+ out[7] = a[7];
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+}
+
+void matrix::translate(mat4& out, const mat4& a, float x, float y, float z) {
+ if (a == out) {
+ out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
+ out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
+ out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
+ out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
+ } else {
+ float a00, a01, a02, a03,
+ a10, a11, a12, a13,
+ a20, a21, a22, a23;
+
+ a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
+ a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
+ a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];
+
+ out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
+ out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
+ out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;
+
+ out[12] = a00 * x + a10 * y + a20 * z + a[12];
+ out[13] = a01 * x + a11 * y + a21 * z + a[13];
+ out[14] = a02 * x + a12 * y + a22 * z + a[14];
+ out[15] = a03 * x + a13 * y + a23 * z + a[15];
+ }
+}
+
+void matrix::rotate_z(mat4& out, const mat4& a, float rad) {
+ float s = std::sin(rad),
+ c = std::cos(rad),
+ a00 = a[0],
+ a01 = a[1],
+ a02 = a[2],
+ a03 = a[3],
+ a10 = a[4],
+ a11 = a[5],
+ a12 = a[6],
+ a13 = a[7];
+
+ if (a != out) { // If the source and destination differ, copy the unchanged last row
+ out[8] = a[8];
+ out[9] = a[9];
+ out[10] = a[10];
+ out[11] = a[11];
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+ }
+
+ // Perform axis-specific matrix multiplication
+ out[0] = a00 * c + a10 * s;
+ out[1] = a01 * c + a11 * s;
+ out[2] = a02 * c + a12 * s;
+ out[3] = a03 * c + a13 * s;
+ out[4] = a10 * c - a00 * s;
+ out[5] = a11 * c - a01 * s;
+ out[6] = a12 * c - a02 * s;
+ out[7] = a13 * c - a03 * s;
+}
+
+void matrix::scale(mat4& out, const mat4& a, float x, float y, float z) {
+ out[0] = a[0] * x;
+ out[1] = a[1] * x;
+ out[2] = a[2] * x;
+ out[3] = a[3] * x;
+ out[4] = a[4] * y;
+ out[5] = a[5] * y;
+ out[6] = a[6] * y;
+ out[7] = a[7] * y;
+ out[8] = a[8] * z;
+ out[9] = a[9] * z;
+ out[10] = a[10] * z;
+ out[11] = a[11] * z;
+ out[12] = a[12];
+ out[13] = a[13];
+ out[14] = a[14];
+ out[15] = a[15];
+}
+
+void matrix::multiply(mat4& out, const mat4& a, const mat4& b) {
+ float a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
+ a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
+ a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
+ a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
+
+ // Cache only the current line of the second matrix
+ float b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
+ out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+
+ b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
+ out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+
+ b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
+ out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+
+ b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
+ out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
+ out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
+ out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
+ out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
+}
diff --git a/src/mbgl/util/math.cpp b/src/mbgl/util/math.cpp
new file mode 100644
index 0000000000..a7eab2d771
--- /dev/null
+++ b/src/mbgl/util/math.cpp
@@ -0,0 +1,25 @@
+#include <mbgl/util/math.hpp>
+
+namespace mbgl {
+namespace util {
+
+// From http://stackoverflow.com/questions/3272424/compute-fast-log-base-2-ceiling
+uint32_t ceil_log2(uint64_t x) {
+ static const uint64_t t[6] = {0xFFFFFFFF00000000, 0x00000000FFFF0000,
+ 0x000000000000FF00, 0x00000000000000F0,
+ 0x000000000000000C, 0x0000000000000002};
+ uint32_t y = (((x & (x - 1)) == 0) ? 0 : 1);
+ uint32_t j = 32;
+
+ for (int32_t i = 0; i < 6; i++) {
+ const uint32_t k = (((x & t[i]) == 0) ? 0 : j);
+ y += k;
+ x >>= k;
+ j >>= 1;
+ }
+
+ return y;
+}
+
+}
+} \ No newline at end of file
diff --git a/src/mbgl/util/optional.hpp b/src/mbgl/util/optional.hpp
new file mode 100644
index 0000000000..8d46eae857
--- /dev/null
+++ b/src/mbgl/util/optional.hpp
@@ -0,0 +1,69 @@
+#ifndef MAPBOX_UTIL_OPTIONAL_HPP
+#define MAPBOX_UTIL_OPTIONAL_HPP
+
+#include <type_traits>
+
+#include <mbgl/util/variant.hpp>
+
+namespace mapbox
+{
+namespace util
+{
+
+template <typename T> class optional
+{
+ static_assert(!std::is_reference<T>::value, "optional doesn't support references");
+
+ struct none_type
+ {
+ };
+
+ variant<none_type, T> variant_;
+
+ public:
+ optional() = default;
+
+ optional(optional const &rhs)
+ {
+ if (this != &rhs)
+ { // protect against invalid self-assignment
+ variant_ = rhs.variant_;
+ }
+ }
+
+ optional(T const &v) { variant_ = v; }
+
+ explicit operator bool() const noexcept { return variant_.template is<T>(); }
+
+ T const &get() const { return variant_.template get<T>(); }
+ T &get() { return variant_.template get<T>(); }
+
+ T const &operator*() const { return this->get(); }
+ T operator*() { return this->get(); }
+
+ optional &operator=(T const &v)
+ {
+ variant_ = v;
+ return *this;
+ }
+
+ optional &operator=(optional const &rhs)
+ {
+ if (this != &rhs)
+ {
+ variant_ = rhs.variant_;
+ }
+ return *this;
+ }
+
+ template <typename... Args> void emplace(Args &&... args)
+ {
+ variant_ = T{std::forward<Args>(args)...};
+ }
+
+ void reset() { variant_ = none_type{}; }
+};
+}
+}
+
+#endif
diff --git a/src/mbgl/util/parsedate.c b/src/mbgl/util/parsedate.c
new file mode 100644
index 0000000000..f888def853
--- /dev/null
+++ b/src/mbgl/util/parsedate.c
@@ -0,0 +1,689 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at http://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+/*
+ A brief summary of the date string formats this parser groks:
+
+ RFC 2616 3.3.1
+
+ Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
+ Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+ Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+
+ we support dates without week day name:
+
+ 06 Nov 1994 08:49:37 GMT
+ 06-Nov-94 08:49:37 GMT
+ Nov 6 08:49:37 1994
+
+ without the time zone:
+
+ 06 Nov 1994 08:49:37
+ 06-Nov-94 08:49:37
+
+ weird order:
+
+ 1994 Nov 6 08:49:37 (GNU date fails)
+ GMT 08:49:37 06-Nov-94 Sunday
+ 94 6 Nov 08:49:37 (GNU date fails)
+
+ time left out:
+
+ 1994 Nov 6
+ 06-Nov-94
+ Sun Nov 6 94
+
+ unusual separators:
+
+ 1994.Nov.6
+ Sun/Nov/6/94/GMT
+
+ commonly used time zone names:
+
+ Sun, 06 Nov 1994 08:49:37 CET
+ 06 Nov 1994 08:49:37 EST
+
+ time zones specified using RFC822 style:
+
+ Sun, 12 Sep 2004 15:05:58 -0700
+ Sat, 11 Sep 2004 21:32:11 +0200
+
+ compact numerical date strings:
+
+ 20040912 15:05:58 -0700
+ 20040911 +0200
+
+*/
+
+#include <mbgl/util/parsedate.h>
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <limits.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+
+#define ERRNO (errno)
+#define SET_ERRNO(x) (errno = (x))
+
+
+/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because
+ its behavior is altered by the current locale. */
+char raw_toupper(char in)
+{
+ switch (in) {
+ case 'a':
+ return 'A';
+ case 'b':
+ return 'B';
+ case 'c':
+ return 'C';
+ case 'd':
+ return 'D';
+ case 'e':
+ return 'E';
+ case 'f':
+ return 'F';
+ case 'g':
+ return 'G';
+ case 'h':
+ return 'H';
+ case 'i':
+ return 'I';
+ case 'j':
+ return 'J';
+ case 'k':
+ return 'K';
+ case 'l':
+ return 'L';
+ case 'm':
+ return 'M';
+ case 'n':
+ return 'N';
+ case 'o':
+ return 'O';
+ case 'p':
+ return 'P';
+ case 'q':
+ return 'Q';
+ case 'r':
+ return 'R';
+ case 's':
+ return 'S';
+ case 't':
+ return 'T';
+ case 'u':
+ return 'U';
+ case 'v':
+ return 'V';
+ case 'w':
+ return 'W';
+ case 'x':
+ return 'X';
+ case 'y':
+ return 'Y';
+ case 'z':
+ return 'Z';
+ }
+ return in;
+}
+
+/*
+ * raw_equal() is for doing "raw" case insensitive strings. This is meant
+ * to be locale independent and only compare strings we know are safe for
+ * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for
+ * some further explanation to why this function is necessary.
+ *
+ * The function is capable of comparing a-z case insensitively even for
+ * non-ascii.
+ */
+
+int raw_equal(const char *first, const char *second)
+{
+ while(*first && *second) {
+ if(raw_toupper(*first) != raw_toupper(*second))
+ /* get out of the loop as soon as they don't match */
+ break;
+ first++;
+ second++;
+ }
+ /* we do the comparison here (possibly again), just to make sure that if the
+ loop above is skipped because one of the strings reached zero, we must not
+ return this as a successful match */
+ return (raw_toupper(*first) == raw_toupper(*second));
+}
+
+#define ISSPACE(x) (isspace((int) ((unsigned char)x)))
+#define ISDIGIT(x) (isdigit((int) ((unsigned char)x)))
+#define ISALNUM(x) (isalnum((int) ((unsigned char)x)))
+#define ISALPHA(x) (isalpha((int) ((unsigned char)x)))
+
+
+/*
+ * Redefine TRUE and FALSE too, to catch current use. With this
+ * change, 'bool found = 1' will give a warning on MIPSPro, but
+ * 'bool found = TRUE' will not. Change tested on IRIX/MIPSPro,
+ * AIX 5.1/Xlc, Tru64 5.1/cc, w/make test too.
+ */
+
+#ifndef TRUE
+#define TRUE true
+#endif
+#ifndef FALSE
+#define FALSE false
+#endif
+
+
+
+/*
+** signed long to signed int
+*/
+
+int clamp_to_int(long slnum)
+{
+ return slnum > INT_MAX ? INT_MAX : slnum < INT_MIN ? INT_MIN : (int)slnum;
+}
+
+
+const char * const wkday[] =
+{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
+static const char * const weekday[] =
+{ "Monday", "Tuesday", "Wednesday", "Thursday",
+ "Friday", "Saturday", "Sunday" };
+const char * const month[]=
+{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+struct tzinfo {
+ char name[5];
+ int offset; /* +/- in minutes */
+};
+
+/*
+ * parsedate()
+ *
+ * Returns:
+ *
+ * PARSEDATE_OK - a fine conversion
+ * PARSEDATE_FAIL - failed to convert
+ * PARSEDATE_LATER - time overflow at the far end of time_t
+ * PARSEDATE_SOONER - time underflow at the low end of time_t
+ */
+
+static int parsedate(const char *date, time_t *output);
+
+#define PARSEDATE_OK 0
+#define PARSEDATE_FAIL -1
+#define PARSEDATE_LATER 1
+#define PARSEDATE_SOONER 2
+
+/* Here's a bunch of frequently used time zone names. These were supported
+ by the old getdate parser. */
+#define tDAYZONE -60 /* offset for daylight savings time */
+static const struct tzinfo tz[]= {
+ {"GMT", 0}, /* Greenwich Mean */
+ {"UTC", 0}, /* Universal (Coordinated) */
+ {"WET", 0}, /* Western European */
+ {"BST", 0 tDAYZONE}, /* British Summer */
+ {"WAT", 60}, /* West Africa */
+ {"AST", 240}, /* Atlantic Standard */
+ {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */
+ {"EST", 300}, /* Eastern Standard */
+ {"EDT", 300 tDAYZONE}, /* Eastern Daylight */
+ {"CST", 360}, /* Central Standard */
+ {"CDT", 360 tDAYZONE}, /* Central Daylight */
+ {"MST", 420}, /* Mountain Standard */
+ {"MDT", 420 tDAYZONE}, /* Mountain Daylight */
+ {"PST", 480}, /* Pacific Standard */
+ {"PDT", 480 tDAYZONE}, /* Pacific Daylight */
+ {"YST", 540}, /* Yukon Standard */
+ {"YDT", 540 tDAYZONE}, /* Yukon Daylight */
+ {"HST", 600}, /* Hawaii Standard */
+ {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */
+ {"CAT", 600}, /* Central Alaska */
+ {"AHST", 600}, /* Alaska-Hawaii Standard */
+ {"NT", 660}, /* Nome */
+ {"IDLW", 720}, /* International Date Line West */
+ {"CET", -60}, /* Central European */
+ {"MET", -60}, /* Middle European */
+ {"MEWT", -60}, /* Middle European Winter */
+ {"MEST", -60 tDAYZONE}, /* Middle European Summer */
+ {"CEST", -60 tDAYZONE}, /* Central European Summer */
+ {"MESZ", -60 tDAYZONE}, /* Middle European Summer */
+ {"FWT", -60}, /* French Winter */
+ {"FST", -60 tDAYZONE}, /* French Summer */
+ {"EET", -120}, /* Eastern Europe, USSR Zone 1 */
+ {"WAST", -420}, /* West Australian Standard */
+ {"WADT", -420 tDAYZONE}, /* West Australian Daylight */
+ {"CCT", -480}, /* China Coast, USSR Zone 7 */
+ {"JST", -540}, /* Japan Standard, USSR Zone 8 */
+ {"EAST", -600}, /* Eastern Australian Standard */
+ {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */
+ {"GST", -600}, /* Guam Standard, USSR Zone 9 */
+ {"NZT", -720}, /* New Zealand */
+ {"NZST", -720}, /* New Zealand Standard */
+ {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */
+ {"IDLE", -720}, /* International Date Line East */
+ /* Next up: Military timezone names. RFC822 allowed these, but (as noted in
+ RFC 1123) had their signs wrong. Here we use the correct signs to match
+ actual military usage.
+ */
+ {"A", +1 * 60}, /* Alpha */
+ {"B", +2 * 60}, /* Bravo */
+ {"C", +3 * 60}, /* Charlie */
+ {"D", +4 * 60}, /* Delta */
+ {"E", +5 * 60}, /* Echo */
+ {"F", +6 * 60}, /* Foxtrot */
+ {"G", +7 * 60}, /* Golf */
+ {"H", +8 * 60}, /* Hotel */
+ {"I", +9 * 60}, /* India */
+ /* "J", Juliet is not used as a timezone, to indicate the observer's local
+ time */
+ {"K", +10 * 60}, /* Kilo */
+ {"L", +11 * 60}, /* Lima */
+ {"M", +12 * 60}, /* Mike */
+ {"N", -1 * 60}, /* November */
+ {"O", -2 * 60}, /* Oscar */
+ {"P", -3 * 60}, /* Papa */
+ {"Q", -4 * 60}, /* Quebec */
+ {"R", -5 * 60}, /* Romeo */
+ {"S", -6 * 60}, /* Sierra */
+ {"T", -7 * 60}, /* Tango */
+ {"U", -8 * 60}, /* Uniform */
+ {"V", -9 * 60}, /* Victor */
+ {"W", -10 * 60}, /* Whiskey */
+ {"X", -11 * 60}, /* X-ray */
+ {"Y", -12 * 60}, /* Yankee */
+ {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */
+};
+
+/* returns:
+ -1 no day
+ 0 monday - 6 sunday
+*/
+
+static int checkday(const char *check, size_t len)
+{
+ int i;
+ const char * const *what;
+ bool found= FALSE;
+ if(len > 3)
+ what = &weekday[0];
+ else
+ what = &wkday[0];
+ for(i=0; i<7; i++) {
+ if(raw_equal(check, what[0])) {
+ found=TRUE;
+ break;
+ }
+ what++;
+ }
+ return found?i:-1;
+}
+
+static int checkmonth(const char *check)
+{
+ int i;
+ const char * const *what;
+ bool found= FALSE;
+
+ what = &month[0];
+ for(i=0; i<12; i++) {
+ if(raw_equal(check, what[0])) {
+ found=TRUE;
+ break;
+ }
+ what++;
+ }
+ return found?i:-1; /* return the offset or -1, no real offset is -1 */
+}
+
+/* return the time zone offset between GMT and the input one, in number
+ of seconds or -1 if the timezone wasn't found/legal */
+
+static int checktz(const char *check)
+{
+ unsigned int i;
+ const struct tzinfo *what;
+ bool found= FALSE;
+
+ what = tz;
+ for(i=0; i< sizeof(tz)/sizeof(tz[0]); i++) {
+ if(raw_equal(check, what->name)) {
+ found=TRUE;
+ break;
+ }
+ what++;
+ }
+ return found?what->offset*60:-1;
+}
+
+static void skip(const char **date)
+{
+ /* skip everything that aren't letters or digits */
+ while(**date && !ISALNUM(**date))
+ (*date)++;
+}
+
+enum assume {
+ DATE_MDAY,
+ DATE_YEAR,
+ DATE_TIME
+};
+
+/* this is a clone of 'struct tm' but with all fields we don't need or use
+ cut out */
+struct my_tm {
+ int tm_sec;
+ int tm_min;
+ int tm_hour;
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+};
+
+/* struct tm to time since epoch in GMT time zone.
+ * This is similar to the standard mktime function but for GMT only, and
+ * doesn't suffer from the various bugs and portability problems that
+ * some systems' implementations have.
+ */
+static time_t my_timegm(struct my_tm *tm)
+{
+ static const int month_days_cumulative [12] =
+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
+ int month, year, leap_days;
+
+ if(tm->tm_year < 70)
+ /* we don't support years before 1970 as they will cause this function
+ to return a negative value */
+ return -1;
+
+ year = tm->tm_year + 1900;
+ month = tm->tm_mon;
+ if(month < 0) {
+ year += (11 - month) / 12;
+ month = 11 - (11 - month) % 12;
+ }
+ else if(month >= 12) {
+ year -= month / 12;
+ month = month % 12;
+ }
+
+ leap_days = year - (tm->tm_mon <= 1);
+ leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400)
+ - (1969 / 4) + (1969 / 100) - (1969 / 400));
+
+ return ((((time_t) (year - 1970) * 365
+ + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24
+ + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;
+}
+
+/*
+ * parsedate()
+ *
+ * Returns:
+ *
+ * PARSEDATE_OK - a fine conversion
+ * PARSEDATE_FAIL - failed to convert
+ * PARSEDATE_LATER - time overflow at the far end of time_t
+ * PARSEDATE_SOONER - time underflow at the low end of time_t
+ */
+
+static int parsedate(const char *date, time_t *output)
+{
+ time_t t = 0;
+ int wdaynum=-1; /* day of the week number, 0-6 (mon-sun) */
+ int monnum=-1; /* month of the year number, 0-11 */
+ int mdaynum=-1; /* day of month, 1 - 31 */
+ int hournum=-1;
+ int minnum=-1;
+ int secnum=-1;
+ int yearnum=-1;
+ int tzoff=-1;
+ struct my_tm tm;
+ enum assume dignext = DATE_MDAY;
+ const char *indate = date; /* save the original pointer */
+ int part = 0; /* max 6 parts */
+
+ while(*date && (part < 6)) {
+ bool found=FALSE;
+
+ skip(&date);
+
+ if(ISALPHA(*date)) {
+ /* a name coming up */
+ char buf[32]="";
+ size_t len;
+ if(sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz]", buf))
+ len = strlen(buf);
+ else
+ len = 0;
+
+ if(wdaynum == -1) {
+ wdaynum = checkday(buf, len);
+ if(wdaynum != -1)
+ found = TRUE;
+ }
+ if(!found && (monnum == -1)) {
+ monnum = checkmonth(buf);
+ if(monnum != -1)
+ found = TRUE;
+ }
+
+ if(!found && (tzoff == -1)) {
+ /* this just must be a time zone string */
+ tzoff = checktz(buf);
+ if(tzoff != -1)
+ found = TRUE;
+ }
+
+ if(!found)
+ return PARSEDATE_FAIL; /* bad string */
+
+ date += len;
+ }
+ else if(ISDIGIT(*date)) {
+ /* a digit */
+ int val;
+ char *end;
+ if((secnum == -1) &&
+ (3 == sscanf(date, "%02d:%02d:%02d", &hournum, &minnum, &secnum))) {
+ /* time stamp! */
+ date += 8;
+ }
+ else if((secnum == -1) &&
+ (2 == sscanf(date, "%02d:%02d", &hournum, &minnum))) {
+ /* time stamp without seconds */
+ date += 5;
+ secnum = 0;
+ }
+ else {
+ long lval;
+ int error;
+ int old_errno;
+
+ old_errno = ERRNO;
+ SET_ERRNO(0);
+ lval = strtol(date, &end, 10);
+ error = ERRNO;
+ if(error != old_errno)
+ SET_ERRNO(old_errno);
+
+ if(error)
+ return PARSEDATE_FAIL;
+
+#if LONG_MAX != INT_MAX
+ if((lval > (long)INT_MAX) || (lval < (long)INT_MIN))
+ return PARSEDATE_FAIL;
+#endif
+
+ val = clamp_to_int(lval);
+
+ if((tzoff == -1) &&
+ ((end - date) == 4) &&
+ (val <= 1400) &&
+ (indate< date) &&
+ ((date[-1] == '+' || date[-1] == '-'))) {
+ /* four digits and a value less than or equal to 1400 (to take into
+ account all sorts of funny time zone diffs) and it is preceded
+ with a plus or minus. This is a time zone indication. 1400 is
+ picked since +1300 is frequently used and +1400 is mentioned as
+ an edge number in the document "ISO C 200X Proposal: Timezone
+ Functions" at http://david.tribble.com/text/c0xtimezone.html If
+ anyone has a more authoritative source for the exact maximum time
+ zone offsets, please speak up! */
+ found = TRUE;
+ tzoff = (val/100 * 60 + val%100)*60;
+
+ /* the + and - prefix indicates the local time compared to GMT,
+ this we need ther reversed math to get what we want */
+ tzoff = date[-1]=='+'?-tzoff:tzoff;
+ }
+
+ if(((end - date) == 8) &&
+ (yearnum == -1) &&
+ (monnum == -1) &&
+ (mdaynum == -1)) {
+ /* 8 digits, no year, month or day yet. This is YYYYMMDD */
+ found = TRUE;
+ yearnum = val/10000;
+ monnum = (val%10000)/100-1; /* month is 0 - 11 */
+ mdaynum = val%100;
+ }
+
+ if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) {
+ if((val > 0) && (val<32)) {
+ mdaynum = val;
+ found = TRUE;
+ }
+ dignext = DATE_YEAR;
+ }
+
+ if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) {
+ yearnum = val;
+ found = TRUE;
+ if(yearnum < 1900) {
+ if(yearnum > 70)
+ yearnum += 1900;
+ else
+ yearnum += 2000;
+ }
+ if(mdaynum == -1)
+ dignext = DATE_MDAY;
+ }
+
+ if(!found)
+ return PARSEDATE_FAIL;
+
+ date = end;
+ }
+ }
+
+ part++;
+ }
+
+ if(-1 == secnum)
+ secnum = minnum = hournum = 0; /* no time, make it zero */
+
+ if((-1 == mdaynum) ||
+ (-1 == monnum) ||
+ (-1 == yearnum))
+ /* lacks vital info, fail */
+ return PARSEDATE_FAIL;
+
+#if SIZEOF_TIME_T < 5
+ /* 32 bit time_t can only hold dates to the beginning of 2038 */
+ if(yearnum > 2037) {
+ *output = 0x7fffffff;
+ return PARSEDATE_LATER;
+ }
+#endif
+
+ if(yearnum < 1970) {
+ *output = 0;
+ return PARSEDATE_SOONER;
+ }
+
+ if((mdaynum > 31) || (monnum > 11) ||
+ (hournum > 23) || (minnum > 59) || (secnum > 60))
+ return PARSEDATE_FAIL; /* clearly an illegal date */
+
+ tm.tm_sec = secnum;
+ tm.tm_min = minnum;
+ tm.tm_hour = hournum;
+ tm.tm_mday = mdaynum;
+ tm.tm_mon = monnum;
+ tm.tm_year = yearnum - 1900;
+
+ /* my_timegm() returns a time_t. time_t is often 32 bits, even on many
+ architectures that feature 64 bit 'long'.
+
+ Some systems have 64 bit time_t and deal with years beyond 2038. However,
+ even on some of the systems with 64 bit time_t mktime() returns -1 for
+ dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06)
+ */
+ t = my_timegm(&tm);
+
+ /* time zone adjust (cast t to int to compare to negative one) */
+ if(-1 != (int)t) {
+
+ /* Add the time zone diff between local time zone and GMT. */
+ long delta = (long)(tzoff!=-1?tzoff:0);
+
+ if((delta>0) && (t > LONG_MAX - delta))
+ return -1; /* time_t overflow */
+
+ t += delta;
+ }
+
+ *output = t;
+
+ return PARSEDATE_OK;
+}
+
+time_t parse_date(const char *p)
+{
+ time_t parsed;
+ int rc = parsedate(p, &parsed);
+
+ switch(rc) {
+ case PARSEDATE_OK:
+ case PARSEDATE_LATER:
+ case PARSEDATE_SOONER:
+ return parsed;
+ }
+ /* everything else is fail */
+ return -1;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/mbgl/util/pbf.hpp b/src/mbgl/util/pbf.hpp
new file mode 100644
index 0000000000..d017219a52
--- /dev/null
+++ b/src/mbgl/util/pbf.hpp
@@ -0,0 +1,184 @@
+#ifndef MBGL_UTIL_PBF
+#define MBGL_UTIL_PBF
+
+/*
+ * Some parts are from upb - a minimalist implementation of protocol buffers.
+ *
+ * Copyright (c) 2008-2011 Google Inc. See LICENSE for details.
+ * Author: Josh Haberman <jhaberman@gmail.com>
+ */
+
+#include <string>
+#include <cstring>
+
+namespace mbgl {
+
+struct pbf {
+ struct exception : std::exception { const char *what() const noexcept { return "pbf exception"; } };
+ struct unterminated_varint_exception : exception { const char *what() const noexcept { return "pbf unterminated varint exception"; } };
+ struct varint_too_long_exception : exception { const char *what() const noexcept { return "pbf varint too long exception"; } };
+ struct unknown_field_type_exception : exception { const char *what() const noexcept { return "pbf unknown field type exception"; } };
+ struct end_of_buffer_exception : exception { const char *what() const noexcept { return "pbf end of buffer exception"; } };
+
+ inline pbf(const unsigned char *data, size_t length);
+ inline pbf();
+
+ inline operator bool() const;
+
+ inline bool next();
+ inline bool next(uint32_t tag);
+ template <typename T = uint32_t> inline T varint();
+ template <typename T = uint32_t> inline T svarint();
+
+ template <typename T = uint32_t, int bytes = 4> inline T fixed();
+ inline float float32();
+ inline double float64();
+
+ inline std::string string();
+ inline bool boolean();
+
+ inline pbf message();
+
+ inline void skip();
+ inline void skipValue(uint32_t val);
+ inline void skipBytes(uint32_t bytes);
+
+ const uint8_t *data = nullptr;
+ const uint8_t *end = nullptr;
+ uint32_t value = 0;
+ uint32_t tag = 0;
+};
+
+pbf::pbf(const unsigned char *data_, size_t length)
+ : data(data_),
+ end(data_ + length),
+ value(0),
+ tag(0) {
+}
+
+pbf::pbf()
+ : data(nullptr),
+ end(nullptr),
+ value(0),
+ tag(0) {
+}
+
+
+pbf::operator bool() const {
+ return data < end;
+}
+
+bool pbf::next() {
+ if (data < end) {
+ value = static_cast<uint32_t>(varint());
+ tag = value >> 3;
+ return true;
+ }
+ return false;
+}
+
+bool pbf::next(uint32_t requested_tag) {
+ while (next()) {
+ if (tag == requested_tag) {
+ return true;
+ } else {
+ skip();
+ }
+ }
+ return false;
+}
+
+template <typename T>
+T pbf::varint() {
+ uint8_t byte = 0x80;
+ T result = 0;
+ int bitpos;
+ for (bitpos = 0; bitpos < 70 && (byte & 0x80); bitpos += 7) {
+ if (data >= end) {
+ throw unterminated_varint_exception();
+ }
+ result |= ((T)(byte = *data) & 0x7F) << bitpos;
+
+ data++;
+ }
+ if (bitpos == 70 && (byte & 0x80)) {
+ throw varint_too_long_exception();
+ }
+
+ return result;
+}
+
+template <typename T>
+T pbf::svarint() {
+ T n = varint<T>();
+ return (n >> 1) ^ -(T)(n & 1);
+}
+
+template <typename T, int bytes>
+T pbf::fixed() {
+ skipBytes(bytes);
+ T result;
+ memcpy(&result, data - bytes, bytes);
+ return result;
+}
+
+float pbf::float32() {
+ return fixed<float, 4>();
+}
+
+double pbf::float64() {
+ return fixed<double, 8>();
+}
+
+std::string pbf::string() {
+ uint32_t bytes = static_cast<uint32_t>(varint());
+ const char *string_data = reinterpret_cast<const char*>(data);
+ skipBytes(bytes);
+ return std::string(string_data, bytes);
+}
+
+bool pbf::boolean() {
+ skipBytes(1);
+ return *(bool *)(data - 1);
+}
+
+pbf pbf::message() {
+ uint32_t bytes = static_cast<uint32_t>(varint());
+ const uint8_t *pos = data;
+ skipBytes(bytes);
+ return pbf(pos, bytes);
+}
+
+void pbf::skip() {
+ skipValue(value);
+}
+
+void pbf::skipValue(uint32_t val) {
+ switch (val & 0x7) {
+ case 0: // varint
+ varint();
+ break;
+ case 1: // 64 bit
+ skipBytes(8);
+ break;
+ case 2: // string/message
+ skipBytes(static_cast<uint32_t>(varint()));
+ break;
+ case 5: // 32 bit
+ skipBytes(4);
+ break;
+ default:
+ throw unknown_field_type_exception();
+ }
+}
+
+void pbf::skipBytes(uint32_t bytes) {
+ if (data + bytes > end) {
+ throw end_of_buffer_exception();
+ }
+ data += bytes;
+}
+
+} // end namespace mbgl
+
+#endif
diff --git a/src/mbgl/util/queue.h b/src/mbgl/util/queue.h
new file mode 100644
index 0000000000..fe02b454ea
--- /dev/null
+++ b/src/mbgl/util/queue.h
@@ -0,0 +1,92 @@
+/* Copyright (c) 2013, Ben Noordhuis <info@bnoordhuis.nl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef QUEUE_H_
+#define QUEUE_H_
+
+typedef void *QUEUE[2];
+
+/* Private macros. */
+#define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0]))
+#define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1]))
+#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q)))
+#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q)))
+
+/* Public macros. */
+#define QUEUE_DATA(ptr, type, field) \
+ ((type *) ((char *) (ptr) - ((char *) &((type *) 0)->field)))
+
+#define QUEUE_FOREACH(q, h) \
+ for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q))
+
+#define QUEUE_EMPTY(q) \
+ ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q))
+
+#define QUEUE_HEAD(q) \
+ (QUEUE_NEXT(q))
+
+#define QUEUE_INIT(q) \
+ do { \
+ QUEUE_NEXT(q) = (q); \
+ QUEUE_PREV(q) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_ADD(h, n) \
+ do { \
+ QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \
+ QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \
+ QUEUE_PREV(h) = QUEUE_PREV(n); \
+ QUEUE_PREV_NEXT(h) = (h); \
+ } \
+ while (0)
+
+#define QUEUE_SPLIT(h, q, n) \
+ do { \
+ QUEUE_PREV(n) = QUEUE_PREV(h); \
+ QUEUE_PREV_NEXT(n) = (n); \
+ QUEUE_NEXT(n) = (q); \
+ QUEUE_PREV(h) = QUEUE_PREV(q); \
+ QUEUE_PREV_NEXT(h) = (h); \
+ QUEUE_PREV(q) = (n); \
+ } \
+ while (0)
+
+#define QUEUE_INSERT_HEAD(h, q) \
+ do { \
+ QUEUE_NEXT(q) = QUEUE_NEXT(h); \
+ QUEUE_PREV(q) = (h); \
+ QUEUE_NEXT_PREV(q) = (q); \
+ QUEUE_NEXT(h) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_INSERT_TAIL(h, q) \
+ do { \
+ QUEUE_NEXT(q) = (h); \
+ QUEUE_PREV(q) = QUEUE_PREV(h); \
+ QUEUE_PREV_NEXT(q) = (q); \
+ QUEUE_PREV(h) = (q); \
+ } \
+ while (0)
+
+#define QUEUE_REMOVE(q) \
+ do { \
+ QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \
+ QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \
+ } \
+ while (0)
+
+#endif /* QUEUE_H_ */
diff --git a/src/mbgl/util/raster.cpp b/src/mbgl/util/raster.cpp
new file mode 100644
index 0000000000..56461aec5f
--- /dev/null
+++ b/src/mbgl/util/raster.cpp
@@ -0,0 +1,106 @@
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <mbgl/util/raster.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/uv_detail.hpp>
+#include <mbgl/util/std.hpp>
+
+#include <cassert>
+#include <cstring>
+
+using namespace mbgl;
+
+Raster::Raster(TexturePool& texturePool_)
+ : texturePool(texturePool_)
+{}
+
+Raster::~Raster() {
+ if (textured) {
+ texturePool.removeTextureID(texture);
+ }
+}
+
+bool Raster::isLoaded() const {
+ std::lock_guard<std::mutex> lock(mtx);
+ return loaded;
+}
+
+bool Raster::load(const std::string &data) {
+ img = util::make_unique<util::Image>(data);
+ width = img->getWidth();
+ height = img->getHeight();
+
+ std::lock_guard<std::mutex> lock(mtx);
+ if (img->getData()) {
+ loaded = true;
+ }
+ return loaded;
+}
+
+
+void Raster::bind(bool linear) {
+ if (!width || !height) {
+ fprintf(stderr, "trying to bind texture without dimension\n");
+ return;
+ }
+
+ if (img && !textured) {
+ texture = texturePool.getTextureID();
+ glBindTexture(GL_TEXTURE_2D, texture);
+#ifndef GL_ES_VERSION_2_0
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+#endif
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getData());
+ img.reset();
+ textured = true;
+ } else if (textured) {
+ glBindTexture(GL_TEXTURE_2D, texture);
+ }
+
+ GLuint new_filter = linear ? GL_LINEAR : GL_NEAREST;
+ if (new_filter != this->filter) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, new_filter);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, new_filter);
+ filter = new_filter;
+ }
+}
+
+// overload ::bind for prerendered raster textures
+void Raster::bind(const GLuint custom_texture) {
+ if (img && !textured) {
+ glBindTexture(GL_TEXTURE_2D, custom_texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getData());
+ img.reset();
+ textured = true;
+ } else if (textured) {
+ glBindTexture(GL_TEXTURE_2D, custom_texture);
+ }
+
+ GLuint new_filter = GL_LINEAR;
+ if (new_filter != this->filter) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, new_filter);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, new_filter);
+ filter = new_filter;
+ }
+
+}
+
+void Raster::beginFadeInTransition() {
+ timestamp start = util::now();
+ fade_transition = std::make_shared<util::ease_transition<double>>(opacity, 1.0, opacity, start, 250_milliseconds);
+}
+
+bool Raster::needsTransition() const {
+ return fade_transition != nullptr;
+}
+
+void Raster::updateTransitions(timestamp now) {
+ if (fade_transition->update(now) == util::transition::complete) {
+ fade_transition = nullptr;
+ }
+}
diff --git a/src/mbgl/util/raster.hpp b/src/mbgl/util/raster.hpp
new file mode 100644
index 0000000000..ff27f509f4
--- /dev/null
+++ b/src/mbgl/util/raster.hpp
@@ -0,0 +1,74 @@
+#ifndef MBGL_UTIL_RASTER
+#define MBGL_UTIL_RASTER
+
+#include <mbgl/util/transition.hpp>
+#include <mbgl/util/texture_pool.hpp>
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/ptr.hpp>
+#include <mbgl/renderer/prerendered_texture.hpp>
+
+#include <string>
+#include <mutex>
+
+typedef struct uv_loop_s uv_loop_t;
+
+namespace mbgl {
+
+class Raster : public std::enable_shared_from_this<Raster> {
+
+public:
+ Raster(TexturePool&);
+ ~Raster();
+
+ // load image data
+ bool load(const std::string &img);
+
+ // bind current texture
+ void bind(bool linear = false);
+
+ // bind prerendered texture
+ void bind(const GLuint texture);
+
+ // loaded status
+ bool isLoaded() const;
+
+ // transitions
+ void beginFadeInTransition();
+ bool needsTransition() const;
+ void updateTransitions(timestamp now);
+
+public:
+ // loaded image dimensions
+ uint32_t width = 0, height = 0;
+
+ // has been uploaded to texture
+ bool textured = false;
+
+ // the uploaded texture
+ uint32_t texture = 0;
+
+ // texture opacity
+ double opacity = 0;
+
+private:
+ mutable std::mutex mtx;
+
+ // raw pixels have been loaded
+ bool loaded = false;
+
+ // shared texture pool
+ TexturePool& texturePool;
+
+ // min/mag filter
+ uint32_t filter = 0;
+
+ // the raw pixels
+ std::unique_ptr<util::Image> img;
+
+ // fade in transition
+ util::ptr<util::transition> fade_transition = nullptr;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/util/rect.hpp b/src/mbgl/util/rect.hpp
new file mode 100644
index 0000000000..f5c77f93d1
--- /dev/null
+++ b/src/mbgl/util/rect.hpp
@@ -0,0 +1,22 @@
+#ifndef MBGL_UTIL_RECT
+#define MBGL_UTIL_RECT
+
+namespace mbgl {
+
+template <typename T>
+struct Rect {
+ inline Rect() {}
+ inline Rect(T x_, T y_, T w_, T h_) : x(x_), y(y_), w(w_), h(h_) {}
+ T x = 0, y = 0;
+ T w = 0, h = 0;
+
+ template <typename Number>
+ Rect operator *(Number value) const {
+ return Rect(x * value, y * value, w * value, h * value);
+ }
+
+ operator bool() const { return w != 0 && h != 0; }
+};
+}
+
+#endif
diff --git a/src/mbgl/util/sqlite3.cpp b/src/mbgl/util/sqlite3.cpp
new file mode 100644
index 0000000000..787db83992
--- /dev/null
+++ b/src/mbgl/util/sqlite3.cpp
@@ -0,0 +1,165 @@
+#include <mbgl/util/sqlite3.hpp>
+#include <sqlite3.h>
+
+#include <cassert>
+
+namespace mapbox {
+namespace sqlite {
+
+Database::Database(const std::string &filename, int flags) {
+ const int err = sqlite3_open_v2(filename.c_str(), &db, flags, nullptr);
+ if (err != SQLITE_OK) {
+ Exception ex { err, sqlite3_errmsg(db) };
+ db = nullptr;
+ throw ex;
+ }
+}
+
+Database::Database(Database &&other)
+ : db(std::move(other.db)) {}
+
+Database &Database::operator=(Database &&other) {
+ std::swap(db, other.db);
+ return *this;
+}
+
+Database::~Database() {
+ if (db) {
+ const int err = sqlite3_close(db);
+ if (err != SQLITE_OK) {
+ throw Exception { err, sqlite3_errmsg(db) };
+ }
+ }
+}
+
+Database::operator bool() const {
+ return db != nullptr;
+}
+
+void Database::exec(const std::string &sql) {
+ assert(db);
+ char *msg = nullptr;
+ const int err = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &msg);
+ if (msg) {
+ Exception ex { err, msg };
+ sqlite3_free(msg);
+ throw ex;
+ } else if (err != SQLITE_OK) {
+ throw Exception { err, sqlite3_errmsg(db) };
+ }
+}
+
+Statement Database::prepare(const char *query) {
+ assert(db);
+ return std::move(Statement(db, query));
+}
+
+Statement::Statement(sqlite3 *db, const char *sql) {
+ const int err = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
+ if (err != SQLITE_OK) {
+ stmt = nullptr;
+ throw Exception { err, sqlite3_errmsg(db) };
+ }
+}
+
+#define CHECK_SQLITE_OK(err) \
+ if (err != SQLITE_OK) { \
+ throw Exception { err, sqlite3_errmsg(sqlite3_db_handle(stmt)) }; \
+ }
+
+Statement::Statement(Statement &&other) {
+ *this = std::move(other);
+}
+
+Statement &Statement::operator=(Statement &&other) {
+ std::swap(stmt, other.stmt);
+ return *this;
+}
+
+Statement::~Statement() {
+ if (stmt) {
+ const int err = sqlite3_finalize(stmt);
+ CHECK_SQLITE_OK(err)
+ }
+}
+
+Statement::operator bool() const {
+ return stmt != nullptr;
+}
+
+#define BIND_3(type, value) \
+ assert(stmt); \
+ const int err = sqlite3_bind_##type(stmt, offset, value); \
+ CHECK_SQLITE_OK(err)
+
+#define BIND_5(type, value, length, param) \
+ assert(stmt); \
+ const int err = sqlite3_bind_##type(stmt, offset, value, length, param); \
+ CHECK_SQLITE_OK(err)
+
+template <> void Statement::bind(int offset, int value) {
+ BIND_3(int, value)
+}
+
+template <> void Statement::bind(int offset, int64_t value) {
+ BIND_3(int64, value)
+}
+
+template <> void Statement::bind(int offset, double value) {
+ BIND_3(double, value)
+}
+
+template <> void Statement::bind(int offset, bool value) {
+ BIND_3(int, value)
+}
+
+template <> void Statement::bind(int offset, const char *value) {
+ BIND_5(text, value, -1, nullptr)
+}
+
+void Statement::bind(int offset, const std::string &value, bool retain) {
+ BIND_5(blob, value.data(), int(value.size()), retain ? SQLITE_TRANSIENT : SQLITE_STATIC)
+}
+
+bool Statement::run() {
+ assert(stmt);
+ const int err = sqlite3_step(stmt);
+ if (err == SQLITE_DONE) {
+ return false;
+ } else if (err == SQLITE_ROW) {
+ return true;
+ } else {
+ throw std::runtime_error("failed to run statement");
+ }
+}
+
+template <> int Statement::get(int offset) {
+ assert(stmt);
+ return sqlite3_column_int(stmt, offset);
+}
+
+template <> int64_t Statement::get(int offset) {
+ assert(stmt);
+ return sqlite3_column_int64(stmt, offset);
+}
+
+template <> double Statement::get(int offset) {
+ assert(stmt);
+ return sqlite3_column_double(stmt, offset);
+}
+
+template <> std::string Statement::get(int offset) {
+ assert(stmt);
+ return {
+ reinterpret_cast<const char *>(sqlite3_column_blob(stmt, offset)),
+ size_t(sqlite3_column_bytes(stmt, offset))
+ };
+}
+
+void Statement::reset() {
+ assert(stmt);
+ sqlite3_reset(stmt);
+}
+
+}
+}
diff --git a/src/mbgl/util/sqlite3.hpp b/src/mbgl/util/sqlite3.hpp
new file mode 100644
index 0000000000..3e324f7ce1
--- /dev/null
+++ b/src/mbgl/util/sqlite3.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+#include <string>
+#include <stdexcept>
+
+typedef struct sqlite3 sqlite3;
+typedef struct sqlite3_stmt sqlite3_stmt;
+
+namespace mapbox {
+namespace sqlite {
+
+enum OpenFlag : int {
+ ReadOnly = 0x00000001,
+ ReadWrite = 0x00000002,
+ Create = 0x00000004,
+ NoMutex = 0x00008000,
+ FullMutex = 0x00010000,
+ SharedCache = 0x00020000,
+ PrivateCache = 0x00040000,
+};
+
+struct Exception : std::runtime_error {
+ inline Exception(int err, const char *msg) : std::runtime_error(msg), code(err) {}
+ const int code = 0;
+};
+
+class Statement;
+
+class Database {
+private:
+ Database(const Database &) = delete;
+ Database &operator=(const Database &) = delete;
+
+public:
+ Database(const std::string &filename, int flags = 0);
+ Database(Database &&);
+ ~Database();
+ Database &operator=(Database &&);
+
+ operator bool() const;
+
+ void exec(const std::string &sql);
+ Statement prepare(const char *query);
+
+private:
+ sqlite3 *db = nullptr;
+};
+
+class Statement {
+private:
+ Statement(const Statement &) = delete;
+ Statement &operator=(const Statement &) = delete;
+
+public:
+ Statement(sqlite3 *db, const char *sql);
+ Statement(Statement &&);
+ ~Statement();
+ Statement &operator=(Statement &&);
+
+ operator bool() const;
+
+ template <typename T> void bind(int offset, T value);
+ void bind(int offset, const std::string &value, bool retain = true);
+ template <typename T> T get(int offset);
+
+ bool run();
+ void reset();
+
+private:
+ sqlite3_stmt *stmt = nullptr;
+};
+
+}
+}
diff --git a/src/mbgl/util/stopwatch.cpp b/src/mbgl/util/stopwatch.cpp
new file mode 100644
index 0000000000..14b4f4018b
--- /dev/null
+++ b/src/mbgl/util/stopwatch.cpp
@@ -0,0 +1,36 @@
+#ifndef DISABLE_STOPWATCH
+#include <mbgl/util/stopwatch.hpp>
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <iostream>
+#include <atomic>
+
+using namespace mbgl::util;
+
+stopwatch::stopwatch(Event event_)
+ : event(event_), start(now()) {}
+
+stopwatch::stopwatch(EventSeverity severity_, Event event_)
+ : severity(severity_), event(event_), start(now()) {}
+
+stopwatch::stopwatch(const std::string &name_, Event event_)
+ : name(name_), event(event_), start(now()) {}
+
+stopwatch::stopwatch(const std::string &name_, EventSeverity severity_, Event event_)
+ : name(name_), severity(severity_), event(event_), start(now()) {}
+
+void stopwatch::report(const std::string &name_) {
+ timestamp duration = now() - start;
+
+ Log::Record(severity, event, name_ + ": " + util::toString(double(duration) / 1_millisecond) + "ms");
+ start += duration;
+}
+
+stopwatch::~stopwatch() {
+ if (name.size()) {
+ report(name);
+ }
+}
+#endif
diff --git a/src/mbgl/util/stopwatch.hpp b/src/mbgl/util/stopwatch.hpp
new file mode 100644
index 0000000000..663bbb6fc7
--- /dev/null
+++ b/src/mbgl/util/stopwatch.hpp
@@ -0,0 +1,40 @@
+#ifndef MBGL_UTIL_STOPWATCH
+#define MBGL_UTIL_STOPWATCH
+
+#include <mbgl/platform/event.hpp>
+
+#include <string>
+
+namespace mbgl {
+namespace util {
+
+#ifndef DISABLE_STOPWATCH
+class stopwatch {
+public:
+ stopwatch(Event event = Event::General);
+ stopwatch(EventSeverity severity, Event event = Event::General);
+ stopwatch(const std::string &name, Event event = Event::General);
+ stopwatch(const std::string &name, EventSeverity severity, Event event = Event::General);
+ void report(const std::string &name);
+ ~stopwatch();
+
+private:
+ const std::string name;
+ EventSeverity severity = EventSeverity::Debug;
+ Event event = Event::General;
+ uint64_t start;
+};
+#else
+class stopwatch {
+ inline stopwatch(Event event = Event::General);
+ inline stopwatch(EventSeverity severity, Event event = Event::General);
+ inline stopwatch(const std::string &name, Event event = Event::General);
+ inline stopwatch(const std::string &name, EventSeverity severity, Event event = Event::General);
+ inline void report(const std::string &name) {}
+ inline ~stopwatch() {}
+};
+#endif
+}
+}
+
+#endif
diff --git a/src/mbgl/util/texture_pool.cpp b/src/mbgl/util/texture_pool.cpp
new file mode 100644
index 0000000000..9c8c24b085
--- /dev/null
+++ b/src/mbgl/util/texture_pool.cpp
@@ -0,0 +1,58 @@
+#include <mbgl/util/texture_pool.hpp>
+
+#include <vector>
+
+const int TextureMax = 64;
+
+using namespace mbgl;
+
+GLuint TexturePool::getTextureID() {
+ if (texture_ids.empty()) {
+ GLuint new_texture_ids[TextureMax];
+ glGenTextures(TextureMax, new_texture_ids);
+ for (uint32_t id = 0; id < TextureMax; id++) {
+ texture_ids.insert(new_texture_ids[id]);
+ }
+ }
+
+ GLuint id = 0;
+
+ if (!texture_ids.empty()) {
+ std::set<GLuint>::iterator id_iterator = texture_ids.begin();
+ id = *id_iterator;
+ texture_ids.erase(id_iterator);
+ }
+
+ return id;
+}
+
+void TexturePool::removeTextureID(GLuint texture_id) {
+ bool needs_clear = false;
+
+ texture_ids.insert(texture_id);
+
+ if (texture_ids.size() > TextureMax) {
+ needs_clear = true;
+ }
+
+ if (needs_clear) {
+ // TODO: We need to find a better way to deal with texture pool cleanup
+// clearTextureIDs();
+ }
+}
+
+void TexturePool::clearTextureIDs() {
+ std::vector<GLuint> ids_to_remove;
+ ids_to_remove.reserve(texture_ids.size());
+
+ for (std::set<GLuint>::iterator id_iterator = texture_ids.begin();
+ id_iterator != texture_ids.end(); ++id_iterator) {
+ ids_to_remove.push_back(*id_iterator);
+ }
+
+ if (!ids_to_remove.empty()) {
+ glDeleteTextures((GLsizei)ids_to_remove.size(), &ids_to_remove[0]);
+ }
+
+ texture_ids.clear();
+}
diff --git a/src/mbgl/util/texture_pool.hpp b/src/mbgl/util/texture_pool.hpp
new file mode 100644
index 0000000000..95d918c237
--- /dev/null
+++ b/src/mbgl/util/texture_pool.hpp
@@ -0,0 +1,25 @@
+#ifndef MBGL_UTIL_TEXTUREPOOL
+#define MBGL_UTIL_TEXTUREPOOL
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/platform/gl.hpp>
+
+#include <set>
+#include <mutex>
+
+namespace mbgl {
+
+class TexturePool : private util::noncopyable {
+
+public:
+ GLuint getTextureID();
+ void removeTextureID(GLuint texture_id);
+ void clearTextureIDs();
+
+private:
+ std::set<GLuint> texture_ids;
+};
+
+}
+
+#endif
diff --git a/src/mbgl/util/time.cpp b/src/mbgl/util/time.cpp
new file mode 100644
index 0000000000..1953975b18
--- /dev/null
+++ b/src/mbgl/util/time.cpp
@@ -0,0 +1,25 @@
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/uv_detail.hpp>
+
+namespace mbgl {
+namespace util {
+
+timestamp now() {
+ return uv_hrtime();
+}
+
+static const char *week[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+std::string rfc1123(std::time_t time) {
+ std::tm info;
+ gmtime_r(&time, &info);
+ char buffer[30];
+ snprintf(buffer, 30, "%s, %02d %s %4d %02d:%02d:%02d GMT", week[info.tm_wday], info.tm_mday,
+ months[info.tm_mon], 1900 + info.tm_year, info.tm_hour, info.tm_min, info.tm_sec);
+ return buffer;
+}
+
+}
+}
diff --git a/src/mbgl/util/token.hpp b/src/mbgl/util/token.hpp
new file mode 100644
index 0000000000..64192a99f9
--- /dev/null
+++ b/src/mbgl/util/token.hpp
@@ -0,0 +1,50 @@
+#ifndef MBGL_UTIL_TOKEN
+#define MBGL_UTIL_TOKEN
+
+#include <map>
+#include <string>
+#include <algorithm>
+
+namespace mbgl {
+namespace util {
+
+// Replaces {tokens} in a string by calling the lookup function.
+template <typename Lookup>
+std::string replaceTokens(const std::string &source, const Lookup &lookup) {
+ std::string result;
+ result.reserve(source.size());
+
+ auto pos = source.begin();
+ const auto end = source.end();
+
+ while (pos != end) {
+ auto brace = std::find(pos, end, '{');
+ result.append(pos, brace);
+ pos = brace;
+ if (pos != end) {
+ for (brace++; brace != end && (std::isalnum(*brace) || *brace == '_'); brace++);
+ if (brace != end && *brace == '}') {
+ result.append(lookup({ pos + 1, brace }));
+ pos = brace + 1;
+ } else {
+ result.append(pos, brace);
+ pos = brace;
+ }
+ }
+ }
+
+ return result;
+}
+
+template <typename T>
+inline std::string replaceTokens(const std::string &source, const std::map<std::string, T> &properties) {
+ return replaceTokens(source, [&properties](const std::string &token) -> std::string {
+ const auto it_prop = properties.find(token);
+ return it_prop != properties.end() ? toString(it_prop->second) : "";
+ });
+}
+
+} // end namespace util
+} // end namespace mbgl
+
+#endif
diff --git a/src/mbgl/util/transition.cpp b/src/mbgl/util/transition.cpp
new file mode 100644
index 0000000000..e63a5c311f
--- /dev/null
+++ b/src/mbgl/util/transition.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/util/transition.hpp>
+#include <mbgl/util/unitbezier.hpp>
+#include <mbgl/util/interpolate.hpp>
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl { namespace util {
+
+UnitBezier ease(0, 0, 0.25, 1);
+
+transition::~transition() {}
+
+template <typename T>
+transition::state ease_transition<T>::update(timestamp now) const {
+ float t = progress(now);
+ if (t >= 1) {
+ value = to;
+ return complete;
+ } else {
+ value = interpolate(from, to, ease.solve(t, 0.001));
+ return running;
+ }
+}
+
+template class ease_transition<double>;
+
+}}
diff --git a/src/mbgl/util/transition.hpp b/src/mbgl/util/transition.hpp
new file mode 100644
index 0000000000..d6bbc9eba0
--- /dev/null
+++ b/src/mbgl/util/transition.hpp
@@ -0,0 +1,77 @@
+#ifndef MBGL_UTIL_TRANSITION
+#define MBGL_UTIL_TRANSITION
+
+#include <mbgl/util/noncopyable.hpp>
+#include <mbgl/util/time.hpp>
+
+namespace mbgl {
+namespace util {
+
+class transition : private noncopyable {
+public:
+ enum state {
+ running,
+ complete
+ };
+
+ inline transition(timestamp start_, timestamp duration_)
+ : start(start_),
+ duration(duration_) {}
+
+ inline float progress(timestamp now) const {
+ if (duration == 0) return 1;
+ if (start > now) return 0;
+
+ return (float)(now - start) / duration;
+ }
+
+ virtual state update(timestamp now) const = 0;
+ virtual ~transition();
+
+protected:
+ const timestamp start, duration;
+};
+
+template <typename T>
+class ease_transition : public transition {
+public:
+ ease_transition(T from_, T to_, T& value_, timestamp start_, timestamp duration_)
+ : transition(start_, duration_),
+ from(from_),
+ to(to_),
+ value(value_) {}
+
+ state update(timestamp now) const;
+
+private:
+ const T from, to;
+ T& value;
+
+};
+
+template <typename T>
+class timeout : public transition {
+public:
+ timeout(T final_value_, T& value_, timestamp start_, timestamp duration_)
+ : transition(start_, duration_),
+ final_value(final_value_),
+ value(value_) {}
+
+ state update(timestamp now) const {
+ if (progress(now) >= 1) {
+ value = final_value;
+ return complete;
+ } else {
+ return running;
+ }
+ }
+
+private:
+ const T final_value;
+ T& value;
+};
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/unitbezier.hpp b/src/mbgl/util/unitbezier.hpp
new file mode 100644
index 0000000000..095e15f809
--- /dev/null
+++ b/src/mbgl/util/unitbezier.hpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef MBGL_UTIL_UNITBEZIER
+#define MBGL_UTIL_UNITBEZIER
+
+#include <cmath>
+
+namespace mbgl {
+namespace util {
+
+struct UnitBezier {
+ UnitBezier(double p1x, double p1y, double p2x, double p2y) {
+ // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
+ cx = 3.0 * p1x;
+ bx = 3.0 * (p2x - p1x) - cx;
+ ax = 1.0 - cx - bx;
+
+ cy = 3.0 * p1y;
+ by = 3.0 * (p2y - p1y) - cy;
+ ay = 1.0 - cy - by;
+ }
+
+ double sampleCurveX(double t) {
+ // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
+ return ((ax * t + bx) * t + cx) * t;
+ }
+
+ double sampleCurveY(double t) {
+ return ((ay * t + by) * t + cy) * t;
+ }
+
+ double sampleCurveDerivativeX(double t) {
+ return (3.0 * ax * t + 2.0 * bx) * t + cx;
+ }
+
+ // Given an x value, find a parametric value it came from.
+ double solveCurveX(double x, double epsilon) {
+ double t0;
+ double t1;
+ double t2;
+ double x2;
+ double d2;
+ int i;
+
+ // First try a few iterations of Newton's method -- normally very fast.
+ for (t2 = x, i = 0; i < 8; ++i) {
+ x2 = sampleCurveX(t2) - x;
+ if (fabs (x2) < epsilon)
+ return t2;
+ d2 = sampleCurveDerivativeX(t2);
+ if (fabs(d2) < 1e-6)
+ break;
+ t2 = t2 - x2 / d2;
+ }
+
+ // Fall back to the bisection method for reliability.
+ t0 = 0.0;
+ t1 = 1.0;
+ t2 = x;
+
+ if (t2 < t0)
+ return t0;
+ if (t2 > t1)
+ return t1;
+
+ while (t0 < t1) {
+ x2 = sampleCurveX(t2);
+ if (fabs(x2 - x) < epsilon)
+ return t2;
+ if (x > x2)
+ t0 = t2;
+ else
+ t1 = t2;
+ t2 = (t1 - t0) * .5 + t0;
+ }
+
+ // Failure.
+ return t2;
+ }
+
+ double solve(double x, double epsilon) {
+ return sampleCurveY(solveCurveX(x, epsilon));
+ }
+
+private:
+ double ax;
+ double bx;
+ double cx;
+
+ double ay;
+ double by;
+ double cy;
+};
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/url.cpp b/src/mbgl/util/url.cpp
new file mode 100644
index 0000000000..e9b9672109
--- /dev/null
+++ b/src/mbgl/util/url.cpp
@@ -0,0 +1,51 @@
+#include <mbgl/util/url.hpp>
+
+#include <cctype>
+#include <iomanip>
+#include <sstream>
+#include <string>
+#include <cstdlib>
+#include <algorithm>
+
+namespace mbgl {
+namespace util {
+
+std::string percentEncode(const std::string& input) {
+ std::ostringstream encoded;
+
+ encoded.fill('0');
+ encoded << std::hex;
+
+ for (auto c : input) {
+ if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
+ encoded << c;
+ } else {
+ encoded << '%' << std::setw(2) << int(c);
+ }
+ }
+
+ return encoded.str();
+}
+
+std::string percentDecode(const std::string& input) {
+ std::string decoded;
+
+ auto it = input.begin();
+ const auto end = input.end();
+ char hex[3] = "00";
+
+ while (it != end) {
+ auto cur = std::find(it, end, '%');
+ decoded.append(it, cur);
+ it = cur;
+ if (cur != end) {
+ it += input.copy(hex, 2, cur - input.begin() + 1) + 1;
+ decoded += static_cast<char>(std::strtoul(hex, nullptr, 16));
+ }
+ }
+
+ return decoded;
+}
+
+}
+}
diff --git a/src/mbgl/util/url.hpp b/src/mbgl/util/url.hpp
new file mode 100644
index 0000000000..a7e5291ec5
--- /dev/null
+++ b/src/mbgl/util/url.hpp
@@ -0,0 +1,15 @@
+#ifndef MBGL_UTIL_URL
+#define MBGL_UTIL_URL
+
+#include <string>
+
+namespace mbgl {
+namespace util {
+
+std::string percentEncode(const std::string&);
+std::string percentDecode(const std::string&);
+
+}
+}
+
+#endif
diff --git a/src/mbgl/util/utf.hpp b/src/mbgl/util/utf.hpp
new file mode 100644
index 0000000000..d6ba2a1f2f
--- /dev/null
+++ b/src/mbgl/util/utf.hpp
@@ -0,0 +1,24 @@
+#ifndef MBGL_UTIL_UTF
+#define MBGL_UTIL_UTF
+
+#include <memory>
+
+#include <boost/regex/pending/unicode_iterator.hpp>
+
+namespace mbgl {
+
+namespace util {
+
+class utf8_to_utf32 {
+ public:
+ static std::u32string convert(std::string const& utf8)
+ {
+ boost::u8_to_u32_iterator<std::string::const_iterator> begin(utf8.begin());
+ boost::u8_to_u32_iterator<std::string::const_iterator> end(utf8.end());
+ return std::u32string(begin,end);
+ }
+};
+
+}}
+
+#endif
diff --git a/src/mbgl/util/uv-channel.c b/src/mbgl/util/uv-channel.c
new file mode 100644
index 0000000000..4e3b9fa5ff
--- /dev/null
+++ b/src/mbgl/util/uv-channel.c
@@ -0,0 +1,69 @@
+#include <mbgl/util/uv-channel.h>
+#include <mbgl/util/queue.h>
+
+#include <stdlib.h>
+
+// Taken from http://navaneeth.github.io/blog/2013/08/02/channels-in-libuv/
+
+typedef struct {
+ void *data;
+ void *active_queue[2];
+} uv__chan_item_t;
+
+int uv_chan_init(uv_chan_t *chan) {
+ int r = uv_mutex_init(&chan->mutex);
+ if (r == -1)
+ return r;
+
+ QUEUE_INIT(&chan->q);
+
+ return uv_cond_init(&chan->cond);
+}
+
+void uv_chan_send(uv_chan_t *chan, void *data) {
+ uv__chan_item_t *item = (uv__chan_item_t *)malloc(sizeof(uv__chan_item_t));
+ item->data = data;
+
+ uv_mutex_lock(&chan->mutex);
+ QUEUE_INSERT_TAIL(&chan->q, &item->active_queue);
+ uv_cond_signal(&chan->cond);
+ uv_mutex_unlock(&chan->mutex);
+}
+
+void *uv_chan_receive(uv_chan_t *chan) {
+ uv__chan_item_t *item;
+ QUEUE *head;
+ void *data = NULL;
+
+ uv_mutex_lock(&chan->mutex);
+ while (QUEUE_EMPTY(&chan->q)) {
+ uv_cond_wait(&chan->cond, &chan->mutex);
+ }
+
+ head = QUEUE_HEAD(&chan->q);
+ item = QUEUE_DATA(head, uv__chan_item_t, active_queue);
+ data = item->data;
+ QUEUE_REMOVE(head);
+ free(item);
+ uv_mutex_unlock(&chan->mutex);
+ return data;
+}
+
+void uv_chan_clear(uv_chan_t *chan) {
+ uv_mutex_lock(&chan->mutex);
+ uv__chan_item_t *item = NULL;
+ QUEUE *head = NULL;
+ while (!QUEUE_EMPTY(&chan->q)) {
+ head = QUEUE_HEAD(&chan->q);
+ item = QUEUE_DATA(head, uv__chan_item_t, active_queue);
+ QUEUE_REMOVE(head);
+ free(item);
+ }
+ uv_mutex_unlock(&chan->mutex);
+}
+
+void uv_chan_destroy(uv_chan_t *chan) {
+ uv_chan_clear(chan);
+ uv_cond_destroy(&chan->cond);
+ uv_mutex_destroy(&chan->mutex);
+}
diff --git a/src/mbgl/util/uv-channel.h b/src/mbgl/util/uv-channel.h
new file mode 100644
index 0000000000..ea5c279f65
--- /dev/null
+++ b/src/mbgl/util/uv-channel.h
@@ -0,0 +1,29 @@
+#ifndef MBGL_UTIL_UV_CHANNEL
+#define MBGL_UTIL_UV_CHANNEL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <uv.h>
+
+// Taken from http://navaneeth.github.io/blog/2013/08/02/channels-in-libuv/
+
+typedef struct uv_chan_s uv_chan_t;
+
+struct uv_chan_s {
+ uv_mutex_t mutex;
+ uv_cond_t cond;
+ void *q[2];
+};
+
+int uv_chan_init(uv_chan_t *chan);
+void uv_chan_send(uv_chan_t *chan, void *data);
+void *uv_chan_receive(uv_chan_t *chan);
+void uv_chan_destroy(uv_chan_t *chan);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/mbgl/util/uv-messenger.c b/src/mbgl/util/uv-messenger.c
new file mode 100644
index 0000000000..935b6f1c41
--- /dev/null
+++ b/src/mbgl/util/uv-messenger.c
@@ -0,0 +1,86 @@
+#include <mbgl/util/uv-messenger.h>
+#include <mbgl/util/queue.h>
+
+#include <stdlib.h>
+#include <assert.h>
+
+typedef struct {
+ void *data;
+ void *queue[2];
+} uv__messenger_item_t;
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-parameter"
+void uv__messenger_callback(uv_async_t *async, int status) {
+#pragma clang diagnostic pop
+#else
+void uv__messenger_callback(uv_async_t *async) {
+#endif
+ uv_messenger_t *msgr = (uv_messenger_t *)async->data;
+
+ uv__messenger_item_t *item;
+ QUEUE *head;
+
+ while (1) {
+ uv_mutex_lock(&msgr->mutex);
+ if (QUEUE_EMPTY(&msgr->queue)) {
+ uv_mutex_unlock(&msgr->mutex);
+ break;
+ }
+
+ head = QUEUE_HEAD(&msgr->queue);
+ item = QUEUE_DATA(head, uv__messenger_item_t, queue);
+ QUEUE_REMOVE(head);
+ uv_mutex_unlock(&msgr->mutex);
+
+ msgr->callback(item->data);
+
+ free(item);
+ }
+}
+
+int uv_messenger_init(uv_loop_t *loop, uv_messenger_t *msgr, uv_messenger_cb callback) {
+ int ret = uv_mutex_init(&msgr->mutex);
+ if (ret < 0) {
+ return ret;
+ }
+
+ msgr->callback = callback;
+ msgr->stop_callback = NULL;
+
+ QUEUE_INIT(&msgr->queue);
+
+ msgr->async.data = msgr;
+ return uv_async_init(loop, &msgr->async, uv__messenger_callback);
+}
+
+void uv_messenger_send(uv_messenger_t *msgr, void *data) {
+ uv__messenger_item_t *item = (uv__messenger_item_t *)malloc(sizeof(uv__messenger_item_t));
+ item->data = data;
+
+ uv_mutex_lock(&msgr->mutex);
+ QUEUE_INSERT_TAIL(&msgr->queue, &item->queue);
+ uv_mutex_unlock(&msgr->mutex);
+
+ uv_async_send(&msgr->async);
+}
+
+void uv_messenger_ref(uv_messenger_t *msgr) {
+ uv_ref((uv_handle_t *)&msgr->async);
+}
+
+void uv_messenger_unref(uv_messenger_t *msgr) {
+ uv_unref((uv_handle_t *)&msgr->async);
+}
+
+void uv__messenger_stop_callback(uv_handle_t *handle) {
+ uv_messenger_t *msgr = (uv_messenger_t *)handle->data;
+ msgr->stop_callback(msgr);
+}
+
+void uv_messenger_stop(uv_messenger_t *msgr, uv_messenger_stop_cb stop_callback) {
+ assert(!msgr->stop_callback);
+ msgr->stop_callback = stop_callback;
+ uv_close((uv_handle_t *)&msgr->async, uv__messenger_stop_callback);
+}
diff --git a/src/mbgl/util/uv-worker.c b/src/mbgl/util/uv-worker.c
new file mode 100644
index 0000000000..d2aa908019
--- /dev/null
+++ b/src/mbgl/util/uv-worker.c
@@ -0,0 +1,170 @@
+#include <mbgl/util/uv-worker.h>
+#include <mbgl/util/uv-messenger.h>
+
+#include <stdio.h>
+#include <assert.h>
+
+typedef struct uv__worker_item_s uv__worker_item_t;
+struct uv__worker_item_s {
+ uv_worker_t *worker;
+ void *data;
+ uv_worker_cb work_cb;
+ uv_worker_after_cb after_work_cb;
+};
+
+typedef struct uv__worker_thread_s uv__worker_thread_t;
+struct uv__worker_thread_s {
+ uv_worker_t *worker;
+ uv_thread_t thread;
+};
+
+void uv__worker_free_messenger(uv_messenger_t *msgr) {
+ free(msgr);
+}
+
+void uv__worker_thread_finished(uv__worker_thread_t *worker_thread) {
+ uv_worker_t *worker = worker_thread->worker;
+
+#ifndef NDEBUG
+ assert(uv_thread_self() == worker->thread_id);
+#endif
+
+ // This should at most block very briefly. We are sending the termination
+ // notification as the last thing in the worker thread, so by now the thread
+ // has probably terminated already. If not, the waiting time should be
+ // extremely short.
+ uv_thread_join(&worker_thread->thread);
+
+ assert(worker->count > 0);
+ worker->count--;
+ if (worker->count == 0) {
+ uv_chan_destroy(&worker->chan);
+ uv_messenger_stop(worker->msgr, uv__worker_free_messenger);
+ if (worker->close_cb) {
+ worker->close_cb(worker);
+ }
+ }
+}
+
+void uv__worker_after(void *ptr) {
+ uv__worker_item_t *item = (uv__worker_item_t *)ptr;
+
+ if (item->work_cb) {
+ // We are finishing a regular work request.
+ if (item->after_work_cb) {
+ assert(item->after_work_cb);
+ item->after_work_cb(item->data);
+ }
+ uv_worker_t *worker = item->worker;
+ assert(worker->active_items > 0);
+ if (--worker->active_items == 0) {
+ uv_messenger_unref(worker->msgr);
+ }
+ } else {
+ // This is a worker thread termination.
+ uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)item->data;
+ uv__worker_thread_finished(worker_thread);
+ free(worker_thread);
+ }
+
+ free(item);
+}
+
+void uv__worker_thread_loop(void *ptr) {
+ uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)ptr;
+ uv_worker_t *worker = worker_thread->worker;
+
+#ifdef __APPLE__
+ if (worker->name) {
+ pthread_setname_np(worker->name);
+ }
+#endif
+
+ uv__worker_item_t *item = NULL;
+ while ((item = (uv__worker_item_t *)uv_chan_receive(&worker->chan)) != NULL) {
+ assert(item->work_cb);
+ item->work_cb(item->data);
+
+ // Trigger the after callback in the main thread.
+ uv_messenger_send(worker->msgr, item);
+ }
+
+ // Make sure to close all other workers too.
+ uv_chan_send(&worker->chan, NULL);
+
+ // Create a new worker item that acts as a terminate flag for this thread.
+ item = (uv__worker_item_t *)malloc(sizeof(uv__worker_item_t));
+ item->data = worker_thread;
+ item->work_cb = NULL;
+ item->after_work_cb = NULL;
+ uv_messenger_send(worker->msgr, item);
+}
+
+int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop, int count, const char *name) {
+#ifndef NDEBUG
+ worker->thread_id = uv_thread_self();
+#endif
+ worker->loop = loop;
+ worker->name = name;
+ worker->count = 0;
+ worker->close_cb = NULL;
+ worker->active_items = 0;
+ worker->msgr = (uv_messenger_t *)malloc(sizeof(uv_messenger_t));
+ int ret = uv_messenger_init(loop, worker->msgr, uv__worker_after);
+ if (ret < 0) {
+ free(worker->msgr);
+ return ret;
+ }
+ uv_messenger_unref(worker->msgr);
+ ret = uv_chan_init(&worker->chan);
+ if (ret < 0) return ret;
+
+ // Initialize all worker threads.
+ int i;
+ for (i = 0; i < count; i++) {
+ uv__worker_thread_t *worker_thread = (uv__worker_thread_t *)malloc(sizeof(uv__worker_thread_t));
+ worker_thread->worker = worker;
+ ret = uv_thread_create(&worker_thread->thread, uv__worker_thread_loop, worker_thread);
+ if (ret < 0) return ret;
+ worker->count++;
+ }
+
+ return 0;
+}
+
+void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb,
+ uv_worker_after_cb after_work_cb) {
+#ifndef NDEBUG
+ assert(uv_thread_self() == worker->thread_id);
+#endif
+
+ // It doesn't make sense to not provide a work callback. On the other hand, the after_work_cb
+ // may be NULL. In that case, there will be no callback called in the current thread and the
+ // worker item will instead be freed in the worker thread.
+ assert(work_cb);
+
+ uv__worker_item_t *item = (uv__worker_item_t *)malloc(sizeof(uv__worker_item_t));
+ item->worker = worker;
+ item->work_cb = work_cb;
+ item->after_work_cb = after_work_cb;
+ item->data = data;
+ uv_chan_send(&worker->chan, item);
+ if (worker->active_items++ == 0) {
+ uv_messenger_ref(worker->msgr);
+ }
+}
+
+void uv_worker_close(uv_worker_t *worker, uv_worker_close_cb close_cb) {
+#ifndef NDEBUG
+ assert(uv_thread_self() == worker->thread_id);
+#endif
+
+ // Prevent double calling.
+ assert(worker->close_cb == NULL);
+
+ worker->close_cb = close_cb;
+ uv_chan_send(&worker->chan, NULL);
+ if (worker->active_items++ == 0) {
+ uv_messenger_ref(worker->msgr);
+ }
+}
diff --git a/src/mbgl/util/uv-worker.h b/src/mbgl/util/uv-worker.h
new file mode 100644
index 0000000000..cb2075d1c3
--- /dev/null
+++ b/src/mbgl/util/uv-worker.h
@@ -0,0 +1,41 @@
+#ifndef MBGL_UTIL_UV_WORKER
+#define MBGL_UTIL_UV_WORKER
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <mbgl/util/uv-messenger.h>
+#include <mbgl/util/uv-channel.h>
+
+#include <stdlib.h>
+
+typedef struct uv_worker_s uv_worker_t;
+
+typedef void (*uv_worker_cb)(void *data);
+typedef void (*uv_worker_after_cb)(void *data);
+typedef void (*uv_worker_close_cb)(uv_worker_t *worker);
+
+struct uv_worker_s {
+#ifndef NDEBUG
+ unsigned long thread_id;
+#endif
+ uv_loop_t *loop;
+ uv_messenger_t *msgr;
+ uv_chan_t chan;
+ const char *name;
+ int count;
+ uv_worker_close_cb close_cb;
+ unsigned int active_items;
+};
+
+int uv_worker_init(uv_worker_t *worker, uv_loop_t *loop, int count, const char *name);
+void uv_worker_send(uv_worker_t *worker, void *data, uv_worker_cb work_cb,
+ uv_worker_after_cb after_work_cb);
+void uv_worker_close(uv_worker_t *worker, uv_worker_close_cb close_cb);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/mbgl/util/uv.cpp b/src/mbgl/util/uv.cpp
new file mode 100644
index 0000000000..7aa5bad0cf
--- /dev/null
+++ b/src/mbgl/util/uv.cpp
@@ -0,0 +1,25 @@
+#include <mbgl/util/uv.hpp>
+
+#include <uv.h>
+
+namespace uv {
+
+std::string cwd() {
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ char dir[512];
+ uv_cwd(dir, 512);
+ return dir;
+#else
+ size_t max = 0;
+ std::string dir;
+ do {
+ max += 256;
+ dir.resize(max);
+ uv_cwd(const_cast<char *>(dir.data()), &max);
+ } while (max == dir.size());
+ dir.resize(max - 1);
+ return dir;
+#endif
+}
+
+}
diff --git a/src/mbgl/util/uv_detail.hpp b/src/mbgl/util/uv_detail.hpp
new file mode 100644
index 0000000000..99f5edc145
--- /dev/null
+++ b/src/mbgl/util/uv_detail.hpp
@@ -0,0 +1,177 @@
+#ifndef MBGL_UTIL_UV_DETAIL
+#define MBGL_UTIL_UV_DETAIL
+
+#include <mbgl/util/uv-worker.h>
+#include <mbgl/util/noncopyable.hpp>
+
+#include <uv.h>
+
+#include <functional>
+#include <cassert>
+#include <memory>
+#include <string>
+
+namespace uv {
+
+template <class T>
+void close(std::unique_ptr<T> ptr) {
+ uv_close(reinterpret_cast<uv_handle_t*>(ptr.release()), [](uv_handle_t* handle) {
+ delete reinterpret_cast<T*>(handle);
+ });
+}
+
+class loop : public mbgl::util::noncopyable {
+public:
+ inline loop() {
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ l = uv_loop_new();
+ if (l == nullptr) {
+#else
+ l = new uv_loop_t;
+ if (uv_loop_init(l) != 0) {
+#endif
+ throw std::runtime_error("failed to initialize loop");
+ }
+ }
+
+ inline ~loop() {
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ uv_loop_delete(l);
+#else
+ uv_loop_close(l);
+ delete l;
+#endif
+
+ }
+
+ inline uv_loop_t *operator*() { return l; }
+
+private:
+ uv_loop_t *l = nullptr;
+};
+
+class async : public mbgl::util::noncopyable {
+public:
+ inline async(uv_loop_t* loop, std::function<void ()> fn_)
+ : a(new uv_async_t)
+ , fn(fn_)
+ {
+ a->data = this;
+ if (uv_async_init(loop, a.get(), async_cb) != 0) {
+ throw std::runtime_error("failed to initialize async");
+ }
+ }
+
+ inline ~async() {
+ close(std::move(a));
+ }
+
+ inline void send() {
+ if (uv_async_send(a.get()) != 0) {
+ throw std::runtime_error("failed to async send");
+ }
+ }
+
+private:
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ static void async_cb(uv_async_t* a, int) {
+#else
+ static void async_cb(uv_async_t* a) {
+#endif
+ reinterpret_cast<async*>(a->data)->fn();
+ }
+
+ std::unique_ptr<uv_async_t> a;
+ std::function<void ()> fn;
+};
+
+class rwlock : public mbgl::util::noncopyable {
+public:
+ inline rwlock() {
+ if (uv_rwlock_init(&mtx) != 0) {
+ throw std::runtime_error("failed to initialize read-write lock");
+ }
+ }
+ inline ~rwlock() { uv_rwlock_destroy(&mtx); }
+ inline void rdlock() { uv_rwlock_rdlock(&mtx); }
+ inline void wrlock() { uv_rwlock_wrlock(&mtx); }
+ inline void rdunlock() { uv_rwlock_rdunlock(&mtx); }
+ inline void wrunlock() { uv_rwlock_wrunlock(&mtx); }
+
+private:
+ uv_rwlock_t mtx;
+};
+
+class readlock : public mbgl::util::noncopyable {
+public:
+ inline readlock(rwlock &mtx_) : mtx(mtx_) { mtx.rdlock(); }
+ inline readlock(const std::unique_ptr<rwlock> &mtx_) : mtx(*mtx_) { mtx.rdlock(); }
+ inline ~readlock() { mtx.rdunlock(); }
+
+private:
+ rwlock &mtx;
+};
+
+class writelock : public mbgl::util::noncopyable {
+public:
+ inline writelock(rwlock &mtx_) : mtx(mtx_) { mtx.wrlock(); }
+ inline writelock(const std::unique_ptr<rwlock> &mtx_) : mtx(*mtx_) { mtx.wrlock(); }
+ inline ~writelock() { mtx.wrunlock(); }
+
+private:
+ rwlock &mtx;
+};
+
+class worker : public mbgl::util::noncopyable {
+public:
+ inline worker(uv_loop_t *loop, unsigned int count, const char *name = nullptr) : w(new uv_worker_t) {
+ uv_worker_init(w, loop, count, name);
+ }
+ inline ~worker() {
+ uv_worker_close(w, [](uv_worker_t *worker_) {
+ delete worker_;
+ });
+ }
+ inline void add(void *data, uv_worker_cb work_cb, uv_worker_after_cb after_work_cb) {
+ uv_worker_send(w, data, work_cb, after_work_cb);
+ }
+
+private:
+ uv_worker_t *w;
+};
+
+template <typename T>
+class work : public mbgl::util::noncopyable {
+public:
+ typedef std::function<void (T&)> work_callback;
+ typedef std::function<void (T&)> after_work_callback;
+
+ template<typename... Args>
+ work(worker &worker, work_callback work_cb_, after_work_callback after_work_cb_, Args&&... args)
+ : data(std::forward<Args>(args)...),
+ work_cb(work_cb_),
+ after_work_cb(after_work_cb_) {
+ worker.add(this, do_work, after_work);
+ }
+
+private:
+ static void do_work(void *data) {
+ work<T> *w = reinterpret_cast<work<T> *>(data);
+ w->work_cb(w->data);
+ }
+
+ static void after_work(void *data) {
+ work<T> *w = reinterpret_cast<work<T> *>(data);
+ w->after_work_cb(w->data);
+ delete w;
+ }
+
+private:
+ T data;
+ work_callback work_cb;
+ after_work_callback after_work_cb;
+};
+
+}
+
+#endif