diff options
Diffstat (limited to 'src/mbgl')
225 files changed, 19244 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..5be29d7543 --- /dev/null +++ b/src/mbgl/map/map.cpp @@ -0,0 +1,654 @@ +#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/util/mapbox.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 <algorithm> +#include <iostream> + +#define _USE_MATH_DEFINES +#include <cmath> + +#include <uv.h> + +// 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)), + 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() { + 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. + glyphStore.reset(); + style.reset(); + workers.reset(); + activeSources.clear(); + + // 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(); + }); + + 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(stop_callback cb, void *data) { + assert(std::this_thread::get_id() == mainThread); + assert(mainThread != mapThread); + assert(async); + + asyncTerminate->send(); + + if (cb) { + // 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) { + cb(data); + } + } + + // If a callback function was provided, this should return immediately because the thread has + // already finished executing. + thread.join(); + + async = false; +} + +void Map::run() { +#ifndef NDEBUG + if (!async) { + mapThread = mainThread; + } +#endif + assert(std::this_thread::get_id() == mapThread); + + setup(); + prepare(); + 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 + } +} + +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(); +} + +void Map::setReachability(bool reachable) { + // Note: This function may be called from *any* thread. + if (reachable) { + fileSource.prepare([&]() { + fileSource.retryAllPending(); + }); + } +} + +#pragma mark - Setup + +void Map::setup() { + assert(std::this_thread::get_id() == mapThread); + assert(painter); + view.make_active(); + painter->setup(); + view.make_inactive(); +} + +void Map::setStyleURL(const std::string &url) { + // TODO: Make threadsafe. + styleURL = url; +} + + +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()); + if (!fileSource.hasLoop()) { + fileSource.setLoop(**loop); + glyphStore = std::make_shared<GlyphStore>(fileSource); + } + fileSource.setBase(base); + glyphStore->setURL(util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken())); + update(); +} + +std::string Map::getStyleJSON() const { + return styleJSON; +} + +void Map::setAccessToken(std::string accessToken_) { + // TODO: Make threadsafe. + accessToken.swap(accessToken_); +} + +std::string Map::getAccessToken() const { + return accessToken; +} + +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 fb_width, uint16_t fb_height) { + if (transform.resize(width, height, ratio, fb_width, fb_height)) { + 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) { + style->setAppliedClasses(classes); + if (style->hasTransitions()) { + update(); + } +} + + +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) { + style->setDefaultTransitionDuration(milliseconds); +} + +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); + glyphStore = std::make_shared<GlyphStore>(fileSource); + } + + 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() { + view.make_active(); + + assert(painter); + painter->render(*style, activeSources, + state, animationTime); + // Schedule another rerender when we definitely need a next frame. + if (transform.needsTransition() || style->hasTransitions()) { + update(); + } + + view.make_inactive(); +} 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..8e67ffb86c --- /dev/null +++ b/src/mbgl/map/source.cpp @@ -0,0 +1,370 @@ +#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; + } + + std::string url = util::mapbox::normalizeSourceURL(info.url, map.getAccessToken()); + util::ptr<Source> source = shared_from_this(); + + fileSource.request(ResourceType::JSON, 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([¢er](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..c0c3bb1c74 --- /dev/null +++ b/src/mbgl/platform/gl.cpp @@ -0,0 +1,95 @@ +#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; + +} +} + +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..f350f2239b --- /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..f38470530b --- /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 + 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..dd35f11372 --- /dev/null +++ b/src/mbgl/renderer/prerendered_texture.cpp @@ -0,0 +1,154 @@ +#include <mbgl/renderer/prerendered_texture.hpp> + +#include <mbgl/renderer/painter.hpp> +#include <mbgl/style/style_bucket.hpp> + +using namespace mbgl; + +PrerenderedTexture::PrerenderedTexture(const StyleBucketRaster &properties_) + : properties(properties_) { +} + +PrerenderedTexture::~PrerenderedTexture() { + if (texture != 0) { + glDeleteTextures(1, &texture); + texture = 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, &previous_fbo); + + 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 (fbo_depth_stencil == 0) { + // Create depth/stencil buffer + glGenRenderbuffers(1, &fbo_depth_stencil); + glBindRenderbuffer(GL_RENDERBUFFER, fbo_depth_stencil); +#ifdef GL_ES_VERSION_2_0 + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, properties.size, properties.size); +#else + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, properties.size, properties.size); +#endif + 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); +#ifdef GL_ES_VERSION_2_0 + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fbo_depth_stencil); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo_depth_stencil); +#else + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo_depth_stencil); +#endif + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "Couldn't create framebuffer: "); + switch (status) { + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: fprintf(stderr, "incomplete attachment\n"); break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: fprintf(stderr, "incomplete missing attachment\n"); break; +#ifdef GL_ES_VERSION_2_0 + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: fprintf(stderr, "incomplete dimensions\n"); break; +#else + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: fprintf(stderr, "incomplete draw buffer\n"); break; +#endif + case GL_FRAMEBUFFER_UNSUPPORTED: fprintf(stderr, "unsupported\n"); break; + default: fprintf(stderr, "other\n"); break; + } + return; + } + } else { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + } +} + +void PrerenderedTexture::unbindFramebuffer() { + glBindFramebuffer(GL_FRAMEBUFFER, previous_fbo); + + if (fbo != 0) { + glDeleteFramebuffers(1, &fbo); + fbo = 0; + } +} + +void PrerenderedTexture::blur(Painter& painter, uint16_t passes) { + const GLuint original_texture = texture; + + // Create a secondary texture + GLuint secondary_texture; + glGenTextures(1, &secondary_texture); + glBindTexture(GL_TEXTURE_2D, secondary_texture); + 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, secondary_texture, 0); +#if GL_EXT_discard_framebuffer + 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, original_texture); + 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, original_texture, 0); +#if GL_EXT_discard_framebuffer + 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, secondary_texture); + painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index()); + } + + glDeleteTextures(1, &secondary_texture); +} diff --git a/src/mbgl/renderer/prerendered_texture.hpp b/src/mbgl/renderer/prerendered_texture.hpp new file mode 100644 index 0000000000..e4dc610418 --- /dev/null +++ b/src/mbgl/renderer/prerendered_texture.hpp @@ -0,0 +1,37 @@ +#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 previous_fbo = 0; + GLuint fbo = 0; + GLuint texture = 0; + GLuint fbo_depth_stencil = 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..84cb55eac4 --- /dev/null +++ b/src/mbgl/shader/shader.cpp @@ -0,0 +1,133 @@ +#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) { + GLchar *log = (GLchar *)malloc(logLength); + glGetProgramInfoLog(program, logLength, &logLength, log); + Log::Error(Event::Shader, "Program failed to link: %s", log); + free(log); + } + + 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) { + GLchar *log = (GLchar *)malloc(logLength); + glGetProgramInfoLog(program, logLength, &logLength, log); + Log::Error(Event::Shader, "Program failed to validate: %s", log); + free(log); + } + + 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) { + GLchar *log = (GLchar *)malloc(logLength); + glGetShaderInfoLog(*shader, logLength, &logLength, log); + Log::Error(Event::Shader, "Shader failed to compile: %s", log); + free(log); + } + + 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..5ce206996c --- /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_) : thread_id(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(thread_id == std::this_thread::get_id()); + notify(); +} + +void BaseRequest::retryImmediately() { + // no-op. override in child class. +} + +void BaseRequest::notify() { + assert(thread_id == std::this_thread::get_id()); + + // 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(thread_id == std::this_thread::get_id()); + 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(thread_id == std::this_thread::get_id()); + 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..2913c5eae5 --- /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 thread_id; + 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..70254b68da --- /dev/null +++ b/src/mbgl/storage/caching_http_file_source.cpp @@ -0,0 +1,115 @@ +#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/util/uv-messenger.h> +#include <mbgl/util/std.hpp> + +#include <uv.h> + +namespace mbgl { + +CachingHTTPFileSource::CachingHTTPFileSource(const std::string &path_) + : path(path_) {} + +CachingHTTPFileSource::~CachingHTTPFileSource() { + if (hasLoop()) { + assert(thread_id == std::this_thread::get_id()); + 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(); + } + } + } +} + +void CachingHTTPFileSource::setLoop(uv_loop_t* loop_) { + thread_id = 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::setBase(const std::string &value) { + assert(thread_id == std::this_thread::get_id()); + base = value; +} + +const std::string &CachingHTTPFileSource::getBase() const { + assert(thread_id == std::this_thread::get_id()); + return base; +} + +std::unique_ptr<Request> CachingHTTPFileSource::request(ResourceType type, const std::string &url) { + assert(thread_id == std::this_thread::get_id()); + + // Make URL absolute. + const std::string absoluteURL = [&]() -> std::string { + const size_t separator = url.find("://"); + if (separator == std::string::npos) { + // Relative URL. + return base + url; + } else { + return url; + } + }(); + + util::ptr<BaseRequest> req; + + // First, try to find an existing Request object. + auto it = pending.find(absoluteURL); + if (it != pending.end()) { + req = it->second.lock(); + } + + if (!req) { + if (absoluteURL.substr(0, 7) == "file://") { + req = std::make_shared<FileRequest>(absoluteURL.substr(7), loop); + } else { + req = std::make_shared<HTTPRequest>(type, absoluteURL, loop, store); + } + + pending.emplace(absoluteURL, req); + } + + return util::make_unique<Request>(req); +} + +void CachingHTTPFileSource::prepare(std::function<void()> fn) { + if (thread_id == std::this_thread::get_id()) { + fn(); + } else { + uv_messenger_send(queue, new std::function<void()>(std::move(fn))); + } +} + +void CachingHTTPFileSource::retryAllPending() { + assert(thread_id == std::this_thread::get_id()); + + 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..9f74c7b414 --- /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(thread_id == std::this_thread::get_id()); + + 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(thread_id == std::this_thread::get_id()); + cancel(); +} + +} diff --git a/src/mbgl/storage/file_request.hpp b/src/mbgl/storage/file_request.hpp new file mode 100644 index 0000000000..2f883728ff --- /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
\ No newline at end of file diff --git a/src/mbgl/storage/file_request_baton.cpp b/src/mbgl/storage/file_request_baton.cpp new file mode 100644 index 0000000000..0a9c5f6f55 --- /dev/null +++ b/src/mbgl/storage/file_request_baton.cpp @@ -0,0 +1,160 @@ +#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) + : thread_id(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 = (FileRequestBaton *)req->data; + assert(ptr->thread_id == std::this_thread::get_id()); + + 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 = (FileRequestBaton *)req->data; + assert(ptr->thread_id == std::this_thread::get_id()); + + 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 = (FileRequestBaton *)req->data; + assert(ptr->thread_id == std::this_thread::get_id()); + + 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 = (FileRequestBaton *)req->data; + assert(ptr->thread_id == std::this_thread::get_id()); + + 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); + } 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(((FileRequestBaton *)req->data)->thread_id == std::this_thread::get_id()); + + 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 = (FileRequestBaton *)req->data; + assert(ptr->thread_id == std::this_thread::get_id()); + + if (ptr->request) { + ptr->request->ptr = nullptr; + } + + uv_fs_req_cleanup(req); + delete ptr; +} + +} diff --git a/src/mbgl/storage/file_request_baton.hpp b/src/mbgl/storage/file_request_baton.hpp new file mode 100644 index 0000000000..897c88061d --- /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 thread_id; + 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..ebb9a84823 --- /dev/null +++ b/src/mbgl/storage/http_request.cpp @@ -0,0 +1,279 @@ +#include <mbgl/storage/http_request.hpp> +#include <mbgl/storage/sqlite_store.hpp> +#include <mbgl/storage/http_request_baton.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_), thread_id(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() == thread_id); + + cache_baton = new CacheRequestBaton; + cache_baton->request = this; + cache_baton->path = path; + cache_baton->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->cache_baton = nullptr; + baton->request->handleCacheResponse(std::move(response_)); + } + }, cache_baton); +} + +void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res) { + assert(std::this_thread::get_id() == thread_id); + + 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() == thread_id); + assert(!http_baton); + + http_baton = std::make_shared<HTTPRequestBaton>(path); + http_baton->request = this; + http_baton->async = new uv_async_t; + http_baton->response = std::move(res); + http_baton->async->data = new util::ptr<HTTPRequestBaton>(http_baton); + +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + uv_async_init(loop, http_baton->async, [](uv_async_t *async, int) { +#else + uv_async_init(loop, http_baton->async, [](uv_async_t *async) { +#endif + util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)async->data; + + if (baton->request) { + HTTPRequest *request = baton->request; + request->http_baton.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(http_baton); +} + + + +void HTTPRequest::handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&res) { + assert(std::this_thread::get_id() == thread_id); + assert(!http_baton); + 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() == thread_id); + assert(!backoff_timer); + backoff_timer = new uv_timer_t(); + uv_timer_init(loop, backoff_timer); + backoff_timer->data = new RetryBaton(this, std::move(res)); + +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + uv_timer_start(backoff_timer, [](uv_timer_t *timer, int) { +#else + uv_timer_start(backoff_timer, [](uv_timer_t *timer) { +#endif + std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(timer->data) }; + pair->first->startHTTPRequest(std::move(pair->second)); + pair->first->backoff_timer = 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() == thread_id); + if (http_baton) { + http_baton->request = nullptr; + HTTPRequestBaton::stop(http_baton); + http_baton.reset(); + } +} + +void HTTPRequest::removeCacheBaton() { + assert(std::this_thread::get_id() == thread_id); + if (cache_baton) { + // 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. + cache_baton->request = nullptr; + cache_baton = nullptr; + } +} + +void HTTPRequest::removeBackoffTimer() { + assert(std::this_thread::get_id() == thread_id); + if (backoff_timer) { + delete static_cast<RetryBaton *>(backoff_timer->data); + uv_timer_stop(backoff_timer); + uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + backoff_timer = nullptr; + } +} + +void HTTPRequest::retryImmediately() { + assert(std::this_thread::get_id() == thread_id); + if (!cache_baton && !http_baton) { + if (backoff_timer) { + // Retry immediately. + uv_timer_stop(backoff_timer); + std::unique_ptr<RetryBaton> pair { static_cast<RetryBaton *>(backoff_timer->data) }; + assert(pair->first == this); + startHTTPRequest(std::move(pair->second)); + uv_close((uv_handle_t *)backoff_timer, [](uv_handle_t *handle) { delete (uv_timer_t *)handle; }); + backoff_timer = nullptr; + } else { + assert(!"We should always have a backoff_timer when there are no batons"); + } + } +} + +void HTTPRequest::cancel() { + assert(std::this_thread::get_id() == thread_id); + removeCacheBaton(); + removeHTTPBaton(); + removeBackoffTimer(); + notify(); +} + + +HTTPRequest::~HTTPRequest() { + assert(std::this_thread::get_id() == thread_id); + 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..71d6e8814c --- /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 thread_id; + uv_loop_t *const loop; + CacheRequestBaton *cache_baton = nullptr; + util::ptr<HTTPRequestBaton> http_baton; + uv_timer_t *backoff_timer = nullptr; + util::ptr<SQLiteStore> store; + const ResourceType type; + uint8_t attempts = 0; + + friend struct HTTPRequestBaton; +}; + +} + +#endif
\ No newline at end of file diff --git a/src/mbgl/storage/http_request_baton.cpp b/src/mbgl/storage/http_request_baton.cpp new file mode 100644 index 0000000000..315708f4e0 --- /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_) : thread_id(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..15ca4e14fb --- /dev/null +++ b/src/mbgl/style/style.cpp @@ -0,0 +1,107 @@ +#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 <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()) { + 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 diff --git a/src/mbgl/util/vec.hpp b/src/mbgl/util/vec.hpp new file mode 100644 index 0000000000..60065b4fc3 --- /dev/null +++ b/src/mbgl/util/vec.hpp @@ -0,0 +1,15 @@ +#ifndef MBGL_UTIL_VEC +#define MBGL_UTIL_VEC + +#include <mbgl/util/vec.hpp> + +namespace mbgl { + +struct box { + vec2<double> tl, tr, bl, br; + vec2<double> center; +}; + +} + +#endif |