From 895977a538d95e9a158f21320db88fb9a63cfa3a Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 8 May 2014 14:42:09 -0700 Subject: [PATCH 01/21] setup-libraries: improve dependency checking --- setup-libraries.sh | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/setup-libraries.sh b/setup-libraries.sh index 44ad91fc6..994ee9ed2 100755 --- a/setup-libraries.sh +++ b/setup-libraries.sh @@ -3,16 +3,33 @@ set -e -u UNAME=$(uname -s); +MISSING_DEPS=false + +function ensure_dep { + if [[ ! `which $1` ]]; then + MISSING_DEPS="$MISSING_DEPS $1" + fi +} + +ensure_dep aclocal +ensure_dep cmake +ensure_dep automake +ensure_dep autoconf if [ ${UNAME} = 'Darwin' ]; then - if [ ! `which aclocal` ] || [ ! `which automake` ] || [ ! `which autoconf` ] || [ ! `which glibtool` ]; then - echo 'autotools commands not found: run "brew install autoconf automake libtool" before continuing' - exit 1 - fi + ensure_dep makedepend + ensure_dep glibtool elif [ ${UNAME} = 'Linux' ]; then - if [ ! `which aclocal` ] || [ ! `which automake` ] || [ ! `which autoconf` ] || [ ! `which libtool` ]; then - echo 'autotools commands not found: run "sudo apt-get install automake libtool xutils-dev" before continuing' - exit 1 + ensure_dep libtool + ensure_dep gccmakedep +fi + +if [[ $MISSING_DEPS != false ]]; then + if [ ${UNAME} = 'Darwin' ]; then + echo 'Please run "brew install autoconf automake libtool makedependc cmake" and then re-run ./setup-libraries.sh' + elif [ ${UNAME} = 'Linux' ]; then + echo 'Please run "sudo apt-get install automake libtool xutils-dev cmake" and then re-run ./setup-libraries.sh' fi + exit 1 fi if [ ! -d 'mapnik-packaging/.git' ]; then From 70a7ba81f33d88991d21534b68600d470ad2b798 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 8 May 2014 15:11:34 -0700 Subject: [PATCH 02/21] ensure resources.cpp is included in xcode project - closes #175 --- Makefile | 4 ++++ llmr.gyp | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/Makefile b/Makefile index c16c3f87c..1b262cc91 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,10 @@ lproj: config.gypi linux/llmr-app.gyp clean: -rm -rf ./build/Release -rm -rf ./build/Debug + -rm -f include/llmr/shader/shaders.hpp + -rm -f include/llmr/style/resources.hpp + -rm -f src/style/resources.cpp + -rm -rf ~/Library/Developer/Xcode/DerivedData/llmr* distclean: clean -rm -rf ./build diff --git a/llmr.gyp b/llmr.gyp index aae6630e1..369a21658 100644 --- a/llmr.gyp +++ b/llmr.gyp @@ -7,6 +7,7 @@ { 'target_name': 'shaders', 'type': 'none', + 'hard_dependency': 1, 'actions': [ { 'action_name': 'Build Shaders', @@ -25,6 +26,7 @@ { 'target_name': 'build_stylesheet', 'type': 'none', + 'hard_dependency': 1, 'actions': [ { 'action_name': 'Build Stylesheet', @@ -38,6 +40,11 @@ 'action': ['<@(node)', 'bin/build-style.js'] } ], + 'direct_dependent_settings': { + 'sources': [ + 'src/style/resources.cpp' + ] + } }, { 'target_name': 'llmr-x86', From 0bfd7b0f430d51dd5661d69876ac5294616d128f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 9 May 2014 18:10:28 +0200 Subject: [PATCH 03/21] Calculate clip IDs with a huffman prefix tree This changes the clip IDs from sequential to a huffman prefix tree, meaning that parent tiles share the same prefix with child tiles (but are shorter). This allows us to use the same checkerboard pattern to draw all tiles. TODO: - make sure we're not overflowing the 8 bit limit of the stencil buffer - draw all checkerboard fields at the same time, with one draw call - fix raster drawing (fading, clipping) refs #132 --- bin/style.js | 13 +- include/llmr/map/map.hpp | 2 + include/llmr/map/raster_tile_data.hpp | 27 +++ include/llmr/map/source.hpp | 12 +- include/llmr/map/tile.hpp | 43 +++-- include/llmr/map/tile_data.hpp | 69 ++----- include/llmr/map/tile_parser.hpp | 12 +- include/llmr/map/vector_tile_data.hpp | 50 +++++ include/llmr/renderer/debug_bucket.hpp | 36 ++++ include/llmr/renderer/painter.hpp | 15 +- include/llmr/renderer/raster_bucket.hpp | 14 +- include/llmr/style/sprite.hpp | 6 +- include/llmr/util/clip_ids.hpp | 19 ++ include/llmr/util/raster.hpp | 13 +- src/map/map.cpp | 55 ++++-- src/map/raster_tile_data.cpp | 30 +++ src/map/source.cpp | 68 ++++--- src/map/tile.cpp | 21 ++- src/map/tile_data.cpp | 42 +---- src/map/tile_parser.cpp | 57 +++--- src/map/vector_tile_data.cpp | 43 +++++ src/renderer/debug_bucket.cpp | 29 +++ src/renderer/painter.cpp | 148 ++++++++++----- src/renderer/raster_bucket.cpp | 21 ++- src/renderer/text_bucket.cpp | 2 - src/style/sprite.cpp | 32 ++-- src/util/clip_ids.cpp | 194 ++++++++++++++++++++ src/util/raster.cpp | 26 ++- src/util/texturepool.cpp | 2 +- test/clip_ids.cpp | 234 ++++++++++++++++++++++++ test/test.gyp | 34 ++++ test/tile.cpp | 50 +++++ 32 files changed, 1129 insertions(+), 290 deletions(-) create mode 100644 include/llmr/map/raster_tile_data.hpp create mode 100644 include/llmr/map/vector_tile_data.hpp create mode 100644 include/llmr/renderer/debug_bucket.hpp create mode 100644 include/llmr/util/clip_ids.hpp create mode 100644 src/map/raster_tile_data.cpp create mode 100644 src/map/vector_tile_data.cpp create mode 100644 src/renderer/debug_bucket.cpp create mode 100644 src/util/clip_ids.cpp create mode 100644 test/clip_ids.cpp create mode 100644 test/tile.cpp diff --git a/bin/style.js b/bin/style.js index c89ff81e8..47cdcc1f9 100644 --- a/bin/style.js +++ b/bin/style.js @@ -2,6 +2,10 @@ module.exports = { "buckets": { + "satellite": { + "source": "satellite", + "type": "raster" + }, "admin_maritime": { "source": "mapbox streets", "layer": "admin", @@ -590,6 +594,9 @@ module.exports = { "structure": [{ "name": "background", "bucket": "background" + }, { + "name": "satellite", + "bucket": "satellite" }, { "name": "waterway_other", "bucket": "waterway_other" @@ -2054,7 +2061,11 @@ module.exports = { "color": "maki", "image": "beer-12", "translate": "point_translate", - } + }, + "satellite": { + "type": "raster", + "opacity": 0.25 + }, } }], "sprite": "/img/maki-sprite" diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index 72e955a93..a9bdf53e5 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -94,6 +94,7 @@ public: inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; } inline uv_loop_t *getLoop() { return loop; } inline time getAnimationTime() const { return animationTime; } + inline Texturepool &getTexturepool() { return texturepool; } private: // uv async callbacks @@ -106,6 +107,7 @@ private: void loadStyle(const uint8_t *const data, uint32_t bytes); void updateTiles(); + void updateClippingIDs(); // Prepares a map render by updating the tiles we need for the current view, as well as updating // the stylesheet. diff --git a/include/llmr/map/raster_tile_data.hpp b/include/llmr/map/raster_tile_data.hpp new file mode 100644 index 000000000..33243334b --- /dev/null +++ b/include/llmr/map/raster_tile_data.hpp @@ -0,0 +1,27 @@ +#ifndef LLMR_MAP_RASTER_TILE_DATA +#define LLMR_MAP_RASTER_TILE_DATA + +#include + +#include + + +namespace llmr { + +class RasterTileData : public TileData { + friend class TileParser; + +public: + RasterTileData(Tile::ID id, Map &map, const std::string url); + ~RasterTileData(); + + virtual void parse(); + virtual void render(Painter &painter, const LayerDescription& layer_desc); + +protected: + RasterBucket bucket; +}; + +} + +#endif diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index 14770d9d2..4da8e6149 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -6,10 +6,12 @@ #include #include +#include #include #include #include #include +#include namespace llmr { @@ -26,17 +28,18 @@ public: }; public: - Source(Map &map, Painter &painter, Texturepool &texturepool, + Source(Map &map, Painter &painter, const char *url = "", Type type = Type::vector, std::vector zooms = {0}, uint32_t tile_size = 512, uint32_t min_zoom = 0, uint32_t max_zoom = 14, bool enabled = true); - bool update(); - void prepare_render(const TransformState &transform, bool is_baselayer = false); - void render(time animationTime); + size_t prepareRender(const TransformState &transform); + void render(); + std::forward_list getIDs() const; + void updateClipIDs(const std::map &mapping); public: bool enabled; @@ -56,7 +59,6 @@ private: private: Map& map; Painter& painter; - Texturepool& texturepool; const Type type; const std::vector zooms; diff --git a/include/llmr/map/tile.hpp b/include/llmr/map/tile.hpp index 0857cf17b..76a01e04e 100644 --- a/include/llmr/map/tile.hpp +++ b/include/llmr/map/tile.hpp @@ -7,45 +7,64 @@ #include #include +#include +#include namespace llmr { class TileData; +struct ClipID { + explicit ClipID() {} + explicit ClipID(const std::bitset<8> &mask, uint8_t length) : mask(mask), length(length) {} + explicit ClipID(const std::string &mask, uint8_t length) : mask(mask), length(length) {} + std::bitset<8> mask; + uint8_t length = 0; +}; + class Tile : private util::noncopyable { public: struct ID { - int8_t z = 0; - int32_t x = 0, y = 0; + const int16_t w = 0; + const int8_t z = 0; + const int32_t x = 0, y = 0; - inline ID(int8_t z, int32_t x, int32_t y) - : z(z), x(x), y(y) { + inline explicit ID(int8_t z, int32_t x, int32_t y) + : w((x < 0 ? x - (1 << z) + 1 : x) / (1 << z)), z(z), x(x), y(y) {} + + inline uint64_t to_uint64() const { + return ((pow(2, z) * y + x) * 32) + z; } - inline operator uint64_t() const { - uint32_t dim = 1 << z; - return ((dim * y + x) * 32) + z; + inline operator std::string() const { + return std::to_string(z) + "/" + std::to_string(x) + "/" + std::to_string(y); } inline bool operator==(const ID& rhs) const { - return z == rhs.z && x == rhs.x && y == rhs.y; + return w == rhs.w && z == rhs.z && x == rhs.x && y == rhs.y; + } + + inline bool operator<(const ID &rhs) const { + if (w != rhs.w) return w < rhs.w; + if (z != rhs.z) return z < rhs.z; + if (x != rhs.x) return x < rhs.x; + return y < rhs.y; } ID parent(int8_t z) const; - void normalize(); ID normalized() const; std::forward_list children(int32_t z) const; + bool isChildOf(const Tile::ID &id) const; }; public: - Tile(const ID& id); + explicit Tile(const ID& id); public: const Tile::ID id; - uint8_t clip_id; + ClipID clip; mat4 matrix; std::shared_ptr data; - }; } diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index fb9eceedf..dde48e9c4 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -2,39 +2,21 @@ #define LLMR_MAP_TILE_DATA #include -#include -#include #include -#include -#include -#include #include - -#include - -#include -#include -#include -#include -#include -#include +#include +#include #include -#include #include -#include -#include #include #include namespace llmr { class Map; -class Raster; +class Painter; class LayerDescription; -class BucketDescription; - -class PlainShader; class TileData : public std::enable_shared_from_this, private util::noncopyable { @@ -55,47 +37,36 @@ public: }; public: - TileData(Tile::ID id, Map &map, const std::string url, const bool is_raster); + TileData(Tile::ID id, Map &map, const std::string url); ~TileData(); void request(); - void scheduleParsing(); - void parse(); void cancel(); - + void reparse(); const std::string toString() const; + // Override this in the child class. + virtual void parse() = 0; + virtual void render(Painter &painter, const LayerDescription& layer_desc) = 0; + + public: const Tile::ID id; std::atomic state; - std::shared_ptr raster; - - // Holds the actual geometries in this tile. - DebugFontBuffer debugFontBuffer; - VertexArrayObject debugFontArray; - - FillVertexBuffer fillVertexBuffer; - LineVertexBuffer lineVertexBuffer; - PointVertexBuffer pointVertexBuffer; - 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::map> buckets; - -private: - const std::string url; - const bool is_raster = false; - std::string data; +protected: Map ↦ - // Stores a request that is in progress. + // Request-related information. + const std::string url; std::weak_ptr req; + std::string data; + + // Contains the tile ID string for painting debug information. + DebugFontBuffer debugFontBuffer; + +public: + DebugBucket debugBucket; }; } diff --git a/include/llmr/map/tile_parser.hpp b/include/llmr/map/tile_parser.hpp index 013d650de..269ab3ba9 100644 --- a/include/llmr/map/tile_parser.hpp +++ b/include/llmr/map/tile_parser.hpp @@ -1,7 +1,8 @@ #ifndef LLMR_MAP_TILE_PARSER #define LLMR_MAP_TILE_PARSER -#include +#include +#include #include namespace llmr { @@ -9,9 +10,12 @@ namespace llmr { class Style; class GlyphAtlas; +class LayerDescription; +class Bucket; + class TileParser { public: - TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas, bool is_raster = false); + TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas); private: bool obsolete() const; @@ -22,13 +26,11 @@ private: std::unique_ptr createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::unique_ptr createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::unique_ptr createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); - std::unique_ptr createRasterBucket(const BucketDescription& bucket_desc); template void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc); private: const VectorTile vector_data; - const std::string raster_data; - TileData& tile; + VectorTileData& tile; const Style& style; GlyphAtlas& glyphAtlas; Faces faces; diff --git a/include/llmr/map/vector_tile_data.hpp b/include/llmr/map/vector_tile_data.hpp new file mode 100644 index 000000000..6deae7bd7 --- /dev/null +++ b/include/llmr/map/vector_tile_data.hpp @@ -0,0 +1,50 @@ +#ifndef LLMR_MAP_VECTOR_TILE_DATA +#define LLMR_MAP_VECTOR_TILE_DATA + +#include + + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace llmr { + + +class VectorTileData : public TileData { + friend class TileParser; + +public: + VectorTileData(Tile::ID id, Map &map, const std::string url); + ~VectorTileData(); + + virtual void parse(); + virtual void render(Painter &painter, const LayerDescription& layer_desc); + +protected: + // Holds the actual geometries in this tile. + FillVertexBuffer fillVertexBuffer; + LineVertexBuffer lineVertexBuffer; + PointVertexBuffer pointVertexBuffer; + 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::map> buckets; + +}; + +} + +#endif diff --git a/include/llmr/renderer/debug_bucket.hpp b/include/llmr/renderer/debug_bucket.hpp new file mode 100644 index 000000000..f1a7d151f --- /dev/null +++ b/include/llmr/renderer/debug_bucket.hpp @@ -0,0 +1,36 @@ +#ifndef LLMR_RENDERER_DEBUGBUCKET +#define LLMR_RENDERER_DEBUGBUCKET + +#include +#include +#include +#include + +#include +#include + +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + +namespace llmr { + +class PlainShader; + +class DebugBucket : public Bucket { +public: + DebugBucket(DebugFontBuffer& fontBuffer); + + virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + + void drawLines(PlainShader& shader); + void drawPoints(PlainShader& shader); + +private: + DebugFontBuffer& fontBuffer; + VertexArrayObject array; +}; + +} + +#endif diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index 03d894597..a5455ba62 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -33,6 +33,9 @@ class PointBucket; class TextBucket; class RasterBucket; +class LayerDescription; +class RasterTileData; + class Painter : private util::noncopyable { public: Painter(Map &map); @@ -43,11 +46,15 @@ public: void changeMatrix(); void render(const Tile& tile); void renderMatte(); + + void renderDebugFrame(); + void renderDebugText(DebugBucket& bucket); + void renderDebugText(const std::vector &strings); void renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id); void renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& id); void renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& id); void renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& id); - void renderRaster(const std::string& layer_name, const std::shared_ptr& tile_data); + void renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& id); void resize(); @@ -55,16 +62,17 @@ public: void setDebug(bool enabled); void prepareClippingMask(); - void drawClippingMask(const mat4& matrix, uint8_t clip_id); + void drawClippingMask(const mat4& matrix, const ClipID& clip); void finishClippingMask(); + void setCurrentSourceName(const std::string &name); + bool needsAnimation() const; private: void setupShaders(); void renderLayers(const std::shared_ptr& tile, const std::vector& layers); void renderLayer(const std::shared_ptr& tile_data, const LayerDescription& layer_desc); void translateLayer(std::array translation, bool reverse = false); - void renderDebug(const std::shared_ptr& tile); void useProgram(uint32_t program); void lineWidth(float lineWidth); @@ -90,6 +98,7 @@ private: float strata = 0; const float strata_epsilon = 1.0f / (1 << 16); enum { Opaque, Translucent } pass = Opaque; + std::string currentSourceName; std::unique_ptr plainShader; std::unique_ptr outlineShader; diff --git a/include/llmr/renderer/raster_bucket.hpp b/include/llmr/renderer/raster_bucket.hpp index 88bb3bd84..bac87cc72 100644 --- a/include/llmr/renderer/raster_bucket.hpp +++ b/include/llmr/renderer/raster_bucket.hpp @@ -3,16 +3,26 @@ #include #include +#include namespace llmr { -class BucketDescription; +class RasterShader; +class VertexBuffer; +class VertexArrayObject; class RasterBucket : public Bucket { public: - RasterBucket(const BucketDescription& bucket_desc); + RasterBucket(Texturepool &texturepool); virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + + bool setImage(const std::string &data); + + void drawRaster(RasterShader& shader, VertexBuffer &vertices, VertexArrayObject &array); + +private: + Raster raster; }; } diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index ea1a29dff..40da8c4a8 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -45,7 +45,7 @@ public: public: const float pixelRatio; - std::shared_ptr raster; + Raster raster; private: void asyncParseJSON(); @@ -57,9 +57,9 @@ private: private: Map ↦ - mutable std::mutex mtx; std::string body; - bool loaded = false; + std::string image; + std::atomic loaded; std::map pos; }; diff --git a/include/llmr/util/clip_ids.hpp b/include/llmr/util/clip_ids.hpp new file mode 100644 index 000000000..4e4d0abe3 --- /dev/null +++ b/include/llmr/util/clip_ids.hpp @@ -0,0 +1,19 @@ +#ifndef LLMR_UTIL_CLIP_IDS +#define LLMR_UTIL_CLIP_IDS + +#include +#include +#include +#include + +namespace llmr { + +static constexpr uint8_t clipMask[9] { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF }; + +void updateClipIDs(const std::list &array); + +std::map computeClipIDs(std::forward_list array); + +} + +#endif diff --git a/include/llmr/util/raster.hpp b/include/llmr/util/raster.hpp index 0bca3a736..ccdffeef3 100644 --- a/include/llmr/util/raster.hpp +++ b/include/llmr/util/raster.hpp @@ -15,13 +15,11 @@ namespace llmr { class Raster : public std::enable_shared_from_this { public: + Raster(Texturepool &texturepool); ~Raster(); // load image data - void load(); - - // set shared texture pool - void setTexturepool(Texturepool* texturepool); + bool load(const std::string &img); // bind current texture void bind(bool linear = false); @@ -34,8 +32,6 @@ public: bool needsTransition() const; void updateTransitions(time now); - inline void setData(const std::string &img) { data = img; } - public: // loaded image dimensions uint32_t width = 0, height = 0; @@ -60,14 +56,11 @@ private: bool loaded = false; // shared texture pool - Texturepool* texturepool = nullptr; + Texturepool &texturepool; // min/mag filter uint32_t filter = 0; - // the encoded image data. - std::string data; - // the raw pixels char *img = nullptr; diff --git a/src/map/map.cpp b/src/map/map.cpp index 35d845858..dc707ee8f 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #include #include @@ -138,7 +141,6 @@ void Map::setup() { sources.emplace("mapbox streets", std::unique_ptr(new Source(*this, painter, - texturepool, "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf", Source::Type::vector, {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }}, @@ -150,7 +152,6 @@ void Map::setup() { sources.emplace("satellite", std::unique_ptr(new Source(*this, painter, - texturepool, "https://a.tiles.mapbox.com/v3/justin.hh0gkdfm/%d/%d/%d%s.png256", Source::Type::raster, {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 }}, @@ -331,7 +332,11 @@ void Map::toggleRaster() { if (satellite_source.enabled) { satellite_source.enabled = false; - style.appliedClasses.erase(style.appliedClasses.find("satellite")); + + auto style_class = style.appliedClasses.find("satellite"); + if (style_class != style.appliedClasses.end()) { + style.appliedClasses.erase(style_class); + } } else { satellite_source.enabled = true; style.appliedClasses.insert("satellite"); @@ -350,6 +355,28 @@ void Map::updateTiles() { } } + + +void Map::updateClippingIDs() { + std::forward_list ids; + + for (auto &pair : sources) { + const std::unique_ptr &source = pair.second; + if (source->enabled) { + ids.splice_after(ids.before_begin(), source->getIDs()); + } + } + + const std::map clipIDs = computeClipIDs(ids); + + for (auto &pair : sources) { + const std::unique_ptr &source = pair.second; + if (source->enabled) { + source->updateClipIDs(clipIDs); + } + } +} + void Map::prepare() { view.make_active(); @@ -366,14 +393,14 @@ void Map::prepare() { style.sprite->load(kSpriteURL); } - if (style.sprite && style.sprite->raster->isLoaded() && !style.sprite->raster->textured) { - style.sprite->raster->setTexturepool(&texturepool); - } - updateTiles(); + + updateClippingIDs(); } void Map::render() { + std::vector debug; + painter.clear(); painter.resize(); @@ -386,26 +413,30 @@ void Map::render() { painter.prepareClippingMask(); - bool is_baselayer = true; - + size_t source_id = 0; for (auto &pair : sources) { Source &source = *pair.second; - source.prepare_render(state, is_baselayer); - is_baselayer = source.enabled ? false : (true && is_baselayer); + size_t count = source.prepareRender(state); + debug.emplace_back(util::sprintf<100>("source %d: %d\n", source_id, count)); + source_id++; } painter.finishClippingMask(); + // Then, render each source's tiles. TODO: handle more than one // vector source. for (auto &pair : sources) { Source &source = *pair.second; - source.render(animationTime); + painter.setCurrentSourceName(pair.first); + source.render(); } painter.renderMatte(); + painter.renderDebugText(debug); + // Schedule another rerender when we definitely need a next frame. if (transform.needsTransition()) { update(); diff --git a/src/map/raster_tile_data.cpp b/src/map/raster_tile_data.cpp new file mode 100644 index 000000000..bf3999637 --- /dev/null +++ b/src/map/raster_tile_data.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +using namespace llmr; + + +RasterTileData::RasterTileData(Tile::ID id, Map &map, const std::string url) + : TileData(id, map, url), + bucket(map.getTexturepool()) { +} + +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, const LayerDescription& layer_desc) { + bucket.render(painter, layer_desc.name, id); +} diff --git a/src/map/source.cpp b/src/map/source.cpp index a83d06f88..91e9d7cb5 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -9,15 +9,18 @@ #include #include + +#include +#include + using namespace llmr; -Source::Source(Map &map, Painter &painter, Texturepool &texturepool, +Source::Source(Map &map, Painter &painter, const char *url, Source::Type type, std::vector zooms, uint32_t tile_size, uint32_t min_zoom, uint32_t max_zoom, bool enabled) : enabled(enabled), map(map), painter(painter), - texturepool(texturepool), type(type), zooms(zooms), url(url), @@ -29,43 +32,49 @@ bool Source::update() { return updateTiles(); } -void Source::prepare_render(const TransformState &transform, bool is_baselayer) { - if (!enabled) return; - - uint8_t i = 1; - - for (Tile& tile : tiles) { - if (tile.data && tile.data->state == TileData::State::parsed) { - transform.matrixFor(tile.matrix, tile.id); - matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); - tile.clip_id = i++; - if (is_baselayer) { - painter.drawClippingMask(tile.matrix, tile.clip_id); - } +void Source::updateClipIDs(const std::map &mapping) { + std::for_each(tiles.begin(), tiles.end(), [&mapping](Tile &tile) { + auto it = mapping.find(tile.id); + if (it != mapping.end()) { + tile.clip = it->second; + } else { + tile.clip = ClipID {}; } - } + }); } -void Source::render(time animationTime) { +size_t Source::prepareRender(const TransformState &transform) { + if (!enabled) return 0; + + size_t masks = 0; + for (Tile& tile : tiles) { + transform.matrixFor(tile.matrix, tile.id); + matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); + painter.drawClippingMask(tile.matrix, tile.clip); + masks++; + } + + return masks; +} + +void Source::render() { if (!enabled) return; for (const Tile& tile : tiles) { if (tile.data && tile.data->state == TileData::State::parsed) { - if (type == Type::raster) { - std::shared_ptr raster = tile.data->raster; - if (raster && !raster->textured) { - raster->setTexturepool(&texturepool); - raster->beginFadeInTransition(); - } - if (raster && raster->needsTransition()) { - raster->updateTransitions(animationTime); - } - } painter.render(tile); } } } +std::forward_list Source::getIDs() const { + std::forward_list ptrs; + std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const Tile &tile) { + return tile.id; + }); + return ptrs; +} + TileData::State Source::hasTile(const Tile::ID& id) { for (const Tile& tile : tiles) { if (tile.id == id && tile.data) { @@ -111,11 +120,12 @@ TileData::State Source::addTile(const Tile::ID& id) { if (type == Source::Type::vector) { formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y); + new_tile.data = std::make_shared(normalized_id, map, formed_url); } else { formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y, (map.getState().getPixelRatio() > 1.0 ? "@2x" : "")); + new_tile.data = std::make_shared(normalized_id, map, formed_url); } - new_tile.data = std::make_shared(normalized_id, map, formed_url, (type == Source::Type::raster)); new_tile.data->request(); tile_data.push_front(new_tile.data); } @@ -245,7 +255,7 @@ bool Source::updateTiles() { // We're painting front-to-back, so we want to draw more detailed tiles first // before filling in other parts with lower zoom levels. tiles.sort([](const Tile & a, const Tile & b) { - return a.id.z > b.id.z; + return a.id.z < b.id.z; }); // Remove all the expired pointers from the list. diff --git a/src/map/tile.cpp b/src/map/tile.cpp index 6ec05beca..5e22dd0bb 100644 --- a/src/map/tile.cpp +++ b/src/map/tile.cpp @@ -4,6 +4,8 @@ using namespace llmr; +#include + Tile::Tile(const ID& id) : id(id) { } @@ -31,14 +33,19 @@ std::forward_list Tile::ID::children(int32_t child_z) const { return children; } -void Tile::ID::normalize() { +Tile::ID Tile::ID::normalized() const { int32_t dim = pow(2, z); - while (x < 0) x += dim; - while (x >= dim) x -= dim; + int32_t nx = x, ny = y; + while (nx < 0) nx += dim; + while (nx >= dim) nx -= dim; + return ID { z, nx, ny }; } -Tile::ID Tile::ID::normalized() const { - ID pos(z, x, y); - pos.normalize(); - return pos; +bool Tile::ID::isChildOf(const Tile::ID &parent) const { + if (parent.z >= z || parent.w != w) { + return false; + } + int32_t scale = pow(2, z - parent.z); + return parent.x == ((x < 0 ? x - scale + 1 : x) / scale) && + parent.y == y / scale; } diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 5cfada7ff..9ad752526 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -1,33 +1,22 @@ #include -#include #include -#include -#include -#include -#include -#include -#include #include -#include - using namespace llmr; -TileData::TileData(Tile::ID id, Map &map, const std::string url, const bool is_raster) +TileData::TileData(Tile::ID id, Map &map, const std::string url) : id(id), state(State::initial), - raster(), + map(map), url(url), - is_raster(is_raster), - map(map) { + debugBucket(debugFontBuffer) { // Initialize tile debug coordinates const std::string str = util::sprintf<32>("%d/%d/%d", id.z, id.x, id.y); debugFontBuffer.addText(str.c_str(), 50, 200, 5); } TileData::~TileData() { - map.getGlyphAtlas().removeGlyphs((uint64_t)id); cancel(); } @@ -51,7 +40,7 @@ void TileData::request() { tile->data.swap(res->body); // Schedule tile parsing in another thread - tile->scheduleParsing(); + tile->reparse(); } else { fprintf(stderr, "[%s] tile loading failed: %d, %s\n", tile->url.c_str(), res->code, res->error_message.c_str()); } @@ -65,7 +54,7 @@ void TileData::cancel() { } } -void TileData::scheduleParsing() { +void TileData::reparse() { // We're creating a new work request. The work request deletes itself after it executed // the after work handler new uv::work>( @@ -74,24 +63,3 @@ void TileData::scheduleParsing() { [](std::shared_ptr &tile) { tile->map.update(); }, shared_from_this()); } - -void TileData::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, map.getStyle(), map.getGlyphAtlas(), is_raster); - } catch (const std::exception& ex) { - fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what()); - cancel(); - return; - } - - if (state != State::obsolete) { - state = State::parsed; - } -} diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 26af27f67..793ef353b 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -14,14 +14,13 @@ using namespace llmr; -TileParser::TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas, bool is_raster) - : vector_data(is_raster ? pbf(0, 0) : pbf((const uint8_t *)data.data(), data.size())), - raster_data(is_raster ? data : ""), +TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas) + : vector_data(pbf((const uint8_t *)data.data(), data.size())), tile(tile), style(style), glyphAtlas(glyphAtlas), - placement(is_raster ? 0 : tile.id.z) { - if (!is_raster) parseGlyphs(); + placement(tile.id.z) { + parseGlyphs(); parseStyleLayers(style.layers); } @@ -37,7 +36,7 @@ void TileParser::parseGlyphs() { GlyphPositions &glyphs = faces[name]; for (const VectorTileGlyph &glyph : face.glyphs) { const Rect rect = - glyphAtlas.addGlyph((uint64_t)tile.id, name, glyph); + glyphAtlas.addGlyph(tile.id.to_uint64(), name, glyph); glyphs.emplace(glyph.id, Glyph{rect, glyph.metrics}); } } @@ -85,31 +84,22 @@ void TileParser::parseStyleLayers(const std::vector& layers) { } std::unique_ptr TileParser::createBucket(const BucketDescription& bucket_desc) { - if (bucket_desc.type == BucketType::Raster) { - if (raster_data.length()) { - tile.raster = std::make_shared(); - tile.raster->setData(raster_data); - tile.raster->load(); - } - return createRasterBucket(bucket_desc); - } else { - 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.type == BucketType::Fill) { - return createFillBucket(layer, bucket_desc); - } else if (bucket_desc.type == BucketType::Line) { - return createLineBucket(layer, bucket_desc); - } else if (bucket_desc.type == BucketType::Point) { - return createPointBucket(layer, bucket_desc); - } else if (bucket_desc.type == BucketType::Text) { - return createTextBucket(layer, bucket_desc); - } else { - throw std::runtime_error("unknown bucket type"); - } + 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.type == BucketType::Fill) { + return createFillBucket(layer, bucket_desc); + } else if (bucket_desc.type == BucketType::Line) { + return createLineBucket(layer, bucket_desc); + } else if (bucket_desc.type == BucketType::Point) { + return createPointBucket(layer, bucket_desc); + } else if (bucket_desc.type == BucketType::Text) { + return createTextBucket(layer, bucket_desc); } else { - // The layer specified in the bucket does not exist. Do nothing. + throw std::runtime_error("unknown bucket type"); } + } else { + // The layer specified in the bucket does not exist. Do nothing. } return nullptr; @@ -152,6 +142,8 @@ std::unique_ptr TileParser::createPointBucket(const VectorTileLayer& lay } std::unique_ptr TileParser::createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) { + return nullptr; + // Determine the correct text stack. if (!layer.shaping.size()) { return nullptr; @@ -183,10 +175,3 @@ std::unique_ptr TileParser::createTextBucket(const VectorTileLayer& laye return std::move(bucket); } - -std::unique_ptr TileParser::createRasterBucket(const BucketDescription& bucket_desc) { - std::unique_ptr bucket = std::make_unique(bucket_desc); - // Raster buckets are just empty dummies so that they behave - // similarly to vector buckets in styling configurations. - return obsolete() ? nullptr : std::move(bucket); -} diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp new file mode 100644 index 000000000..c5d3a23ec --- /dev/null +++ b/src/map/vector_tile_data.cpp @@ -0,0 +1,43 @@ +#include + +#include +#include + +using namespace llmr; + +VectorTileData::VectorTileData(Tile::ID id, Map &map, const std::string url) + : TileData(id, map, url) { +} + +VectorTileData::~VectorTileData() { + map.getGlyphAtlas().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, map.getStyle(), map.getGlyphAtlas()); + } catch (const std::exception& ex) { + fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what()); + cancel(); + return; + } + + if (state != State::obsolete) { + state = State::parsed; + } +} + +void VectorTileData::render(Painter &painter, const LayerDescription& layer_desc) { + 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.name, id); + } +} \ No newline at end of file diff --git a/src/renderer/debug_bucket.cpp b/src/renderer/debug_bucket.cpp new file mode 100644 index 000000000..2cdec50d3 --- /dev/null +++ b/src/renderer/debug_bucket.cpp @@ -0,0 +1,29 @@ +#include + +#include + +#include + +#include + +struct geometry_too_long_exception : std::exception {}; + +using namespace llmr; + +DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer) + : fontBuffer(fontBuffer) { +} + +void DebugBucket::render(Painter& painter, const std::string& /*layer_name*/, const Tile::ID& /*id*/) { + painter.renderDebugText(*this); +} + +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/renderer/painter.cpp b/src/renderer/painter.cpp index 0915e790f..5ab9c91d3 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -22,6 +22,7 @@ #include #include #include +#include using namespace llmr; @@ -139,10 +140,15 @@ void Painter::prepareClippingMask() { coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); } -void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) { +void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { gl::group group("mask"); plainShader->setMatrix(matrix); + GLint id = clip.mask.to_ulong(); + GLuint mask = clipMask[clip.length]; + glStencilFunc(GL_ALWAYS, id, mask); + glStencilMask(0xFF); + Color background = map.getStyle().computed.background.color; background[0] *= map.getStyle().computed.background.opacity; background[1] *= map.getStyle().computed.background.opacity; @@ -151,7 +157,6 @@ void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) { plainShader->setColor(background); - glStencilFunc(GL_ALWAYS, clip_id, 0xFF); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); } @@ -162,6 +167,11 @@ void Painter::finishClippingMask() { gl::end_group(); } +void Painter::setCurrentSourceName(const std::string &name) { + currentSourceName = name; +} + + void Painter::clear() { gl::group group("clear"); glStencilMask(0xFF); @@ -179,19 +189,23 @@ void Painter::render(const Tile& tile) { gl::group group(util::sprintf<32>("render %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); assert(tile.data); - if (tile.data->state != TileData::State::parsed) { - return; + if (tile.data->state == TileData::State::parsed) { + matrix = tile.matrix; + GLint id = tile.clip.mask.to_ulong(); + GLuint mask = clipMask[tile.clip.length]; + + glStencilFunc(GL_EQUAL, id, mask); + + renderLayers(tile.data, map.getStyle().layers); + } else { + fprintf(stderr, "tile not parsed yet\n"); } frameHistory.record(map.getAnimationTime(), map.getState().getNormalizedZoom()); - matrix = tile.matrix; - glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF); - - renderLayers(tile.data, map.getStyle().layers); - if (debug) { - renderDebug(tile.data); + renderDebugText(tile.data->debugBucket); + renderDebugFrame(); } } @@ -242,16 +256,10 @@ void Painter::renderLayer(const std::shared_ptr& tile_data, const Laye // this layer and render it. auto bucket_it = map.getStyle().buckets.find(layer_desc.bucket_name); if (bucket_it != map.getStyle().buckets.end()) { - if (bucket_it->second.type == BucketType::Raster) { - if (tile_data && tile_data->raster) { - renderRaster(layer_desc.name, tile_data); - } - } else { - auto databucket_it = tile_data->buckets.find(layer_desc.bucket_name); - if (databucket_it != tile_data->buckets.end()) { - assert(databucket_it->second); - databucket_it->second->render(*this, layer_desc.name, tile_data->id); - } + const BucketDescription &bucket = bucket_it->second; + // Only try to render the bucket if it draws data from the current source. + if (bucket.source_name == currentSourceName) { + tile_data->render(*this, layer_desc); } } } @@ -271,8 +279,8 @@ void Painter::translateLayer(std::array translation, bool reverse) { } } -void Painter::renderRaster(const std::string& layer_name, const std::shared_ptr& tile_data) { - if (pass == Opaque) return; +void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { + if (pass == Translucent) return; auto raster_properties = map.getStyle().computed.rasters; auto raster_properties_it = raster_properties.find(layer_name); @@ -283,15 +291,17 @@ void Painter::renderRaster(const std::string& layer_name, const std::shared_ptr< gl::group group(layer_name + " (raster)"); + depthMask(false); + useProgram(rasterShader->program); rasterShader->setMatrix(matrix); - rasterShader->setImage(0); - rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); - tile_data->raster->bind(true); + rasterShader->setOpacity(1); + // rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); - coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0)); - glDepthRange(strata, 1.0f); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray); + + depthMask(true); } void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { @@ -390,7 +400,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }}); patternShader->setColor(fill_color); patternShader->setMix(mix); - sprite->raster->bind(true); + sprite->raster.bind(true); // Draw the actual triangles into the color & stencil buffer. glDepthRange(strata + strata_epsilon, 1.0f); @@ -579,7 +589,7 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }}); - sprite->raster->bind(map.getState().isChanging()); + sprite->raster.bind(map.getState().isChanging()); const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio(); #if defined(GL_ES_VERSION_2_0) @@ -707,8 +717,35 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons translateLayer(properties.translate, true); } -void Painter::renderDebug(const TileData::Ptr& tile_data) { - gl::group group("debug"); +void Painter::renderDebugText(DebugBucket& bucket) { + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + + useProgram(plainShader->program); + plainShader->setMatrix(matrix); + + // Draw white outline + plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); + lineWidth(4.0f * map.getState().getPixelRatio()); + bucket.drawLines(*plainShader); + +#ifndef GL_ES_VERSION_2_0 + // Draw line "end caps" + glPointSize(2); + bucket.drawPoints(*plainShader); +#endif + + // Draw black text. + plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); + lineWidth(2.0f * map.getState().getPixelRatio()); + bucket.drawLines(*plainShader); + + glEnable(GL_DEPTH_TEST); +} + +void Painter::renderDebugFrame() { + 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 @@ -724,15 +761,6 @@ void Painter::renderDebug(const TileData::Ptr& tile_data) { lineWidth(4.0f * map.getState().getPixelRatio()); glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index()); - // draw debug info - tile_data->debugFontArray.bind(*plainShader, tile_data->debugFontBuffer, BUFFER_OFFSET(0)); - plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); - lineWidth(4.0f * map.getState().getPixelRatio()); - glDrawArrays(GL_LINES, 0, (GLsizei)tile_data->debugFontBuffer.index()); - plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); - lineWidth(2.0f * map.getState().getPixelRatio()); - glDrawArrays(GL_LINES, 0, (GLsizei)tile_data->debugFontBuffer.index()); - glEnable(GL_DEPTH_TEST); } @@ -741,7 +769,8 @@ void Painter::renderMatte() { glDisable(GL_DEPTH_TEST); glStencilFunc(GL_EQUAL, 0x0, 0xFF); - Color white = {{ 0.9, 0.9, 0.9, 1 }}; + // Color white = {{ 0.9, 0.9, 0.9, 1 }}; + Color white = {{ 1, 1, 0, 1 }}; useProgram(plainShader->program); plainShader->setMatrix(nativeMatrix); @@ -753,3 +782,38 @@ void Painter::renderMatte() { glEnable(GL_DEPTH_TEST); } + + +void Painter::renderDebugText(const std::vector &strings) { + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); + + useProgram(plainShader->program); + plainShader->setMatrix(nativeMatrix); + + DebugFontBuffer debugFontBuffer; + int line = 25; + for (const std::string &str : strings) { + debugFontBuffer.addText(str.c_str(), 10, line, 0.75); + line += 20; + } + + // draw debug info + VertexArrayObject debugFontArray; + debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0)); + plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); + lineWidth(4.0f * map.getState().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->setColor(0.0f, 0.0f, 0.0f, 1.0f); + lineWidth(2.0f * map.getState().getPixelRatio()); + glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); + + + glEnable(GL_DEPTH_TEST); +} \ No newline at end of file diff --git a/src/renderer/raster_bucket.cpp b/src/renderer/raster_bucket.cpp index f8c656b2c..ca173680a 100644 --- a/src/renderer/raster_bucket.cpp +++ b/src/renderer/raster_bucket.cpp @@ -3,12 +3,21 @@ using namespace llmr; -RasterBucket::RasterBucket(const BucketDescription &) { +RasterBucket::RasterBucket(Texturepool &texturepool) + : raster(texturepool) { } -void RasterBucket::render(Painter &, const std::string &, const Tile::ID &) { - // The painter renders rasters directly. This function is a no-op - // and exists just to satisfy the abstract superclass requirements. - // That way, we can avoid having raster buckets pass around - // references to tile data and/or rasters directly. +void RasterBucket::render(Painter &painter, const std::string &layer_name, const Tile::ID &id) { + painter.renderRaster(*this, layer_name, id); +} + +bool RasterBucket::setImage(const std::string &data) { + return raster.load(data); +} + +void RasterBucket::drawRaster(RasterShader& shader, VertexBuffer &vertices, VertexArrayObject &array) { + raster.bind(true); + shader.setImage(0); + array.bind(shader, vertices, BUFFER_OFFSET(0)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index()); } diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index a2a88e9a7..5b75ca99f 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -14,8 +14,6 @@ #include -#define BUFFER_OFFSET(i) ((char *) nullptr + (i)) - #include using namespace llmr; diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index cf8f3ffed..e91a24c49 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -15,14 +15,20 @@ SpritePosition::SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t y(y), width(width), height(height), - pixelRatio(pixelRatio) {} + pixelRatio(pixelRatio) { +} ImagePosition::ImagePosition(const vec2& size, vec2 tl, vec2 br) : size(size), tl(tl), br(br) {} -Sprite::Sprite(Map &map, float pixelRatio) : pixelRatio(pixelRatio), raster(std::make_shared()), map(map) {} +Sprite::Sprite(Map &map, float pixelRatio) + : pixelRatio(pixelRatio), + raster(map.getTexturepool()), + map(map), + loaded(false) { +} void Sprite::load(const std::string& base_url) { std::shared_ptr sprite = shared_from_this(); @@ -40,7 +46,7 @@ void Sprite::load(const std::string& base_url) { platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) { if (res->code == 200) { - sprite->raster->setData(res->body); + sprite->image.swap(res->body); sprite->asyncParseImage(); } else { fprintf(stderr, "failed to load sprite image\n"); @@ -49,8 +55,7 @@ void Sprite::load(const std::string& base_url) { } void Sprite::complete(std::shared_ptr &sprite) { - std::lock_guard lock(sprite->mtx); - if (sprite->raster && sprite->raster->isLoaded() && sprite->pos.size()) { + if (sprite->raster.isLoaded() && sprite->pos.size()) { sprite->loaded = true; sprite->map.update(); fprintf(stderr, "sprite loaded\n"); @@ -58,7 +63,6 @@ void Sprite::complete(std::shared_ptr &sprite) { } bool Sprite::isLoaded() const { - std::lock_guard lock(mtx); return loaded; } @@ -71,14 +75,14 @@ void Sprite::asyncParseJSON() { } void Sprite::parseImage(std::shared_ptr &sprite) { - sprite->raster->load(); + sprite->raster.load(sprite->image); + sprite->image.clear(); } void Sprite::parseJSON(std::shared_ptr &sprite) { - std::lock_guard lock(sprite->mtx); - rapidjson::Document d; d.Parse<0>(sprite->body.c_str()); + sprite->body.clear(); if (d.IsObject()) { for (rapidjson::Value::ConstMemberIterator itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) { @@ -105,7 +109,7 @@ void Sprite::parseJSON(std::shared_ptr &sprite) { } ImagePosition Sprite::getPosition(const std::string& name, bool repeating) { - if (!this->raster || !this->raster->isLoaded()) return {}; + if (!isLoaded()) return {}; // `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 @@ -122,12 +126,12 @@ ImagePosition Sprite::getPosition(const std::string& name, bool repeating) { pos.height }, { - (float)(pos.x + offset) / raster->width, - (float)(pos.y + offset) / raster->height + (float)(pos.x + offset) / raster.width, + (float)(pos.y + offset) / raster.height }, { - (float)(pos.x + pos.width - 2 * offset) / raster->width, - (float)(pos.y + pos.height - 2 * offset) / raster->height + (float)(pos.x + pos.width - 2 * offset) / raster.width, + (float)(pos.y + pos.height - 2 * offset) / raster.height } }; } diff --git a/src/util/clip_ids.cpp b/src/util/clip_ids.cpp new file mode 100644 index 000000000..457d6ddb3 --- /dev/null +++ b/src/util/clip_ids.cpp @@ -0,0 +1,194 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace llmr { + +struct TileHierarchy { + TileHierarchy(Tile::ID id, std::list &&children) + : id(id), children(std::move(children)) {} + + const Tile::ID id; + ClipID clip; + std::list children; +}; + +std::list partition(std::forward_list &&array) { + if (array.empty()) { + // We don't have to update the clipping mask because there are no tiles + // anyway. + return {}; + } + + int8_t minZ = array.begin()->z; + + std::list result; + std::forward_list remainder; + auto remainder_it = remainder.before_begin(); + + while (!array.empty()) { + const Tile::ID id = array.front(); + array.pop_front(); + if (id.z == minZ) { + std::forward_list children; + auto children_it = children.before_begin(); + + array.remove_if([&id, &children, &children_it](const Tile::ID &child) { + if (child.isChildOf(id)) { + children_it = children.insert_after(children_it, child); + return true; + } else { + return false; + } + }); + + result.emplace_back(id, partition(std::move(children))); + } else { + remainder_it = remainder.insert_after(remainder_it, id); + } + } + + // Concatenate the remainder. + if (!array.empty()) { + result.splice(result.begin(), partition(std::move(remainder))); + } + + return result; +} + +uint8_t prefix(std::list &array, TileHierarchy *parent = nullptr) { + if (array.empty()) { + return 0; + } + + bool all_children_are_immediate = true; + uint8_t max_child_prefix_length = 0; + + struct Huffman { + explicit Huffman(int prefix_length, TileHierarchy *item) + : prefix_length(prefix_length), children(1, item) {} + uint8_t prefix_length; + std::vector children; + }; + + // Create a temporary structure that we use for sorting the prefix tree. + std::list huffman; + std::transform(array.begin(), array.end(), std::back_inserter(huffman), [parent, &all_children_are_immediate, &max_child_prefix_length](TileHierarchy &item) { + uint8_t prefix_length = prefix(item.children, &item); + + if (prefix_length > max_child_prefix_length) { + max_child_prefix_length = prefix_length; + } + + if (!parent || item.id.z != parent->id.z + 1) { + all_children_are_immediate = false; + } + + return Huffman { prefix_length + 1, &item }; + }); + + while (huffman.size() > 1) { + huffman.sort([](const Huffman &a, const Huffman &b) { + return a.prefix_length < b.prefix_length; + }); + + Huffman &first = *huffman.begin(); + Huffman &second = *(++huffman.begin()); + + assert(&first != &second); + + // Prefix with 0 + std::for_each(first.children.begin(), first.children.end(), [](TileHierarchy *child) { + child->clip.mask >>= 1; + child->clip.mask.set(7, false); // noop + child->clip.length++; + }); + first.prefix_length++; + + // Prefix with 1 + std::for_each(second.children.begin(), second.children.end(), [](TileHierarchy *child) { + child->clip.mask >>= 1; + child->clip.mask.set(7, true); + child->clip.length++; + }); + second.prefix_length++; + + second.children.insert(second.children.end(), first.children.begin(), first.children.end()); + second.prefix_length = first.prefix_length + second.prefix_length; + + // Remove the first child as we've just merged it into the second version. + huffman.erase(huffman.begin()); + } + + uint8_t prefix_length = 0; + + // Filter out all-zero bits + bool filter_zero = !all_children_are_immediate || array.size() != 4; + + for (TileHierarchy &item : array) { + if (filter_zero && !item.clip.mask.any()) { + // Make sure we don't have a prefix that is all zeros. + // item.clip.mask |= (0x80 >> item.length); + item.clip.mask.set(7 - item.clip.length); + item.clip.length++; + } + + if (item.clip.length > prefix_length) { + prefix_length = item.clip.length; + } + } + + return max_child_prefix_length + prefix_length; +} + +void propagate(std::map &mapping, std::list &array, const ClipID &parent = ClipID{}) { + for (auto &item : array) { + item.clip.mask >>= parent.length; + item.clip.mask |= parent.mask; + item.clip.length += parent.length; + auto result = mapping.emplace(item.id, item.clip); + assert("Tried to insert a duplicate item" && result.second == true); + propagate(mapping, item.children, const_cast(item.clip)); + }; +} + +void updateClipIDs(const std::list &array) { + std::forward_list ids; + std::transform(array.begin(), array.end(), std::front_inserter(ids), [](Tile *item) { + return item->id; + }); + + const std::map mapping = computeClipIDs(ids); + + std::for_each(array.begin(), array.end(), [&mapping](Tile *item) { + auto it = mapping.find(item->id); + if (it != mapping.end()) { + item->clip = it->second; + } else { + item->clip = ClipID {}; + } + }); +} + +std::map computeClipIDs(std::forward_list array) { + // Sort by zoom level and make sure that we don't have duplicate elements. + array.sort(); + array.unique(); + + std::list hierarchy = partition(std::move(array)); + prefix(hierarchy); + + std::map mapping; + propagate(mapping, hierarchy); + return mapping; +} + +} diff --git a/src/util/raster.cpp b/src/util/raster.cpp index e7cb89e42..90268c09b 100644 --- a/src/util/raster.cpp +++ b/src/util/raster.cpp @@ -13,11 +13,15 @@ using namespace llmr; +Raster::Raster(Texturepool &texturepool) + : texturepool(texturepool) +{} + Raster::~Raster() { if (img && !textured) { free(img); } else if (textured) { - texturepool->removeTextureID(texture); + texturepool.removeTextureID(texture); } } @@ -26,17 +30,14 @@ bool Raster::isLoaded() const { return loaded; } -void Raster::load() { +bool Raster::load(const std::string &data) { loadImage(data); - { - std::lock_guard lock(mtx); - if (img) { - loaded = true; - } + std::lock_guard lock(mtx); + if (img) { + loaded = true; } - - data.clear(); + return loaded; } struct Buffer { @@ -154,9 +155,6 @@ void Raster::loadImage(const std::string& data) { } } -void Raster::setTexturepool(Texturepool* new_texturepool) { - texturepool = new_texturepool; -} void Raster::bind(bool linear) { if (!width || !height) { @@ -164,8 +162,8 @@ void Raster::bind(bool linear) { return; } - if (img && !textured && texturepool) { - texture = texturepool->getTextureID(); + if (img && !textured) { + texture = texturepool.getTextureID(); glBindTexture(GL_TEXTURE_2D, 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); diff --git a/src/util/texturepool.cpp b/src/util/texturepool.cpp index a1966b700..9feadb3db 100644 --- a/src/util/texturepool.cpp +++ b/src/util/texturepool.cpp @@ -25,7 +25,7 @@ GLuint Texturepool::getTextureID() { return id; } - + void Texturepool::removeTextureID(GLuint texture_id) { bool needs_clear = false; diff --git a/test/clip_ids.cpp b/test/clip_ids.cpp new file mode 100644 index 000000000..3fd9ba062 --- /dev/null +++ b/test/clip_ids.cpp @@ -0,0 +1,234 @@ +#include +#include "gtest/gtest.h" + +#include + +#include +#include + +using namespace llmr; + +TEST(ClipIDs, ClipMasks) { + ASSERT_EQ(0, clipMask[0]); + ASSERT_EQ(0x80, clipMask[1]); + ASSERT_EQ(0xC0, clipMask[2]); + ASSERT_EQ(0xE0, clipMask[3]); + ASSERT_EQ(0xF0, clipMask[4]); + ASSERT_EQ(0xF8, clipMask[5]); + ASSERT_EQ(0xFC, clipMask[6]); + ASSERT_EQ(0xFE, clipMask[7]); + ASSERT_EQ(0xFF, clipMask[8]); +} + + +TEST(ClipIDs, ParentAndFourChildren) { + std::array, 5> tiles {{ + std::make_unique(Tile::ID { 1, 0, 0 }), // 1/0/0: 11000000 (3) + std::make_unique(Tile::ID { 1, 0, 1 }), // 1/0/1: 11100000 (3) + std::make_unique(Tile::ID { 1, 1, 0 }), // 1/1/0: 10000000 (3) + std::make_unique(Tile::ID { 1, 1, 1 }), // 1/1/1: 10100000 (3) + std::make_unique(Tile::ID { 0, 0, 0 }), // 0/0/0: 10000000 (1) + }}; + + std::list tile_ptrs; + std::transform(tiles.begin(), tiles.end(), std::back_inserter(tile_ptrs), [](std::unique_ptr &tile) { return tile.get(); }); + + updateClipIDs(tile_ptrs); + + // for (const auto &it : tiles) { + // std::cout << std::string(it->id) << ": " << it->clip.mask << " (" << (int)it->clip.length << ")" << std::endl; + // } + + ASSERT_EQ(std::bitset<8>("11000000"), tiles[0]->clip.mask); ASSERT_EQ(3, tiles[0]->clip.length); + ASSERT_EQ(std::bitset<8>("11100000"), tiles[1]->clip.mask); ASSERT_EQ(3, tiles[1]->clip.length); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[2]->clip.mask); ASSERT_EQ(3, tiles[2]->clip.length); + ASSERT_EQ(std::bitset<8>("10100000"), tiles[3]->clip.mask); ASSERT_EQ(3, tiles[3]->clip.length); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[4]->clip.mask); ASSERT_EQ(1, tiles[4]->clip.length); +} + +TEST(ClipIDs, ParentAndFourChildrenNegative) { + std::array, 5> tiles {{ + std::make_unique(Tile::ID { 1, -2, 0 }), // 1/0/0: 11000000 (3) + std::make_unique(Tile::ID { 1, -2, 1 }), // 1/0/1: 11100000 (3) + std::make_unique(Tile::ID { 1, -1, 0 }), // 1/1/0: 10000000 (3) + std::make_unique(Tile::ID { 1, -1, 1 }), // 1/1/1: 10100000 (3) + std::make_unique(Tile::ID { 0, -1, 0 }), // 0/0/0: 10000000 (1) + }}; + + std::list tile_ptrs; + std::transform(tiles.begin(), tiles.end(), std::back_inserter(tile_ptrs), [](std::unique_ptr &tile) { return tile.get(); }); + + updateClipIDs(tile_ptrs); + + // for (const auto &it : tiles) { + // std::cout << std::string(it->id) << ": " << it->clip.mask << " (" << (int)it->clip.length << ")" << std::endl; + // } + + ASSERT_EQ(std::bitset<8>("11000000"), tiles[0]->clip.mask); ASSERT_EQ(3, tiles[0]->clip.length); + ASSERT_EQ(std::bitset<8>("11100000"), tiles[1]->clip.mask); ASSERT_EQ(3, tiles[1]->clip.length); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[2]->clip.mask); ASSERT_EQ(3, tiles[2]->clip.length); + ASSERT_EQ(std::bitset<8>("10100000"), tiles[3]->clip.mask); ASSERT_EQ(3, tiles[3]->clip.length); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[4]->clip.mask); ASSERT_EQ(1, tiles[4]->clip.length); +} + +TEST(ClipIDs, NegativeParentAndMissingLevel) { + std::array, 5> tiles {{ + std::make_unique(Tile::ID { 1, -1, 0 }), // 1/-1/0: 10000000 (1) + std::make_unique(Tile::ID { 2, -1, 0 }), // 2/-1/0: 10000000 (3) + std::make_unique(Tile::ID { 2, -2, 1 }), // 2/-2/1: 11100000 (3) + std::make_unique(Tile::ID { 2, -1, 1 }), // 2/-1/1: 10100000 (3) + std::make_unique(Tile::ID { 2, -2, 0 }), // 2/-2/0: 11000000 (3) + }}; + + std::list tile_ptrs; + std::transform(tiles.begin(), tiles.end(), std::back_inserter(tile_ptrs), [](std::unique_ptr &tile) { return tile.get(); }); + + updateClipIDs(tile_ptrs); + + // for (const auto &it : tiles) { + // std::cout << std::string(it->id) << ": " << it->clip.mask << " (" << (int)it->clip.length << ")" << std::endl; + // } + + ASSERT_EQ(std::bitset<8>("10000000"), tiles[0]->clip.mask); ASSERT_EQ(1, tiles[0]->clip.length); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[1]->clip.mask); ASSERT_EQ(3, tiles[1]->clip.length); + ASSERT_EQ(std::bitset<8>("11100000"), tiles[2]->clip.mask); ASSERT_EQ(3, tiles[2]->clip.length); + ASSERT_EQ(std::bitset<8>("10100000"), tiles[3]->clip.mask); ASSERT_EQ(3, tiles[3]->clip.length); + ASSERT_EQ(std::bitset<8>("11000000"), tiles[4]->clip.mask); ASSERT_EQ(3, tiles[4]->clip.length); +} + + +TEST(ClipIDs, SevenOnSameLevel) { + std::array, 7> tiles {{ + std::make_unique(Tile::ID { 2, 0, 0 }), // 2/0/0: 11000000 (3) + std::make_unique(Tile::ID { 2, 0, 1 }), // 2/0/1: 11100000 (3) + std::make_unique(Tile::ID { 2, 0, 2 }), // 2/0/2: 10000000 (3) + std::make_unique(Tile::ID { 2, 1, 0 }), // 2/1/0: 10100000 (3) + std::make_unique(Tile::ID { 2, 1, 1 }), // 2/1/1: 01000000 (3) + std::make_unique(Tile::ID { 2, 1, 2 }), // 2/1/2: 01100000 (3) + std::make_unique(Tile::ID { 2, 2, 0 }), // 2/2/0: 00100000 (3) + }}; + + std::list tile_ptrs; + std::transform(tiles.begin(), tiles.end(), std::back_inserter(tile_ptrs), [](std::unique_ptr &tile) { return tile.get(); }); + + updateClipIDs(tile_ptrs); + + // for (const auto &it : tiles) { + // std::cout << std::string(it->id) << ": " << it->clip.mask << " (" << (int)it->clip.length << ")" << std::endl; + // } + + ASSERT_EQ(std::bitset<8>("11000000"), tiles[0]->clip.mask); ASSERT_EQ(3, tiles[0]->clip.length); + ASSERT_EQ(std::bitset<8>("11100000"), tiles[1]->clip.mask); ASSERT_EQ(3, tiles[1]->clip.length); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[2]->clip.mask); ASSERT_EQ(3, tiles[2]->clip.length); + ASSERT_EQ(std::bitset<8>("10100000"), tiles[3]->clip.mask); ASSERT_EQ(3, tiles[3]->clip.length); + ASSERT_EQ(std::bitset<8>("01000000"), tiles[4]->clip.mask); ASSERT_EQ(3, tiles[4]->clip.length); + ASSERT_EQ(std::bitset<8>("01100000"), tiles[5]->clip.mask); ASSERT_EQ(3, tiles[5]->clip.length); + ASSERT_EQ(std::bitset<8>("00100000"), tiles[6]->clip.mask); ASSERT_EQ(3, tiles[6]->clip.length); +} + +TEST(ClipIDs, MultipleLevels) { + std::array, 12> tiles {{ + std::make_unique(Tile::ID { 2, 0, 0 }), // 2/0/0: 10000000 (1) + std::make_unique(Tile::ID { 3, 0, 0 }), // 3/0/0: 10000000 (3) + std::make_unique(Tile::ID { 3, 0, 1 }), // 3/0/1: 11100000 (3) + std::make_unique(Tile::ID { 4, 0, 2 }), // 4/0/2: 11110000 (5) + std::make_unique(Tile::ID { 4, 1, 2 }), // 4/0/3: 11111000 (5) + std::make_unique(Tile::ID { 4, 0, 3 }), // 4/1/2: 11100000 (5) + std::make_unique(Tile::ID { 4, 1, 3 }), // 4/1/3: 11101000 (5) + std::make_unique(Tile::ID { 3, 1, 0 }), // 3/1/0: 10100000 (3) + std::make_unique(Tile::ID { 3, 1, 1 }), // 3/1/1: 11000000 (3) + std::make_unique(Tile::ID { 2, 1, 0 }), // 2/1/0: 01000000 (2) + std::make_unique(Tile::ID { 3, 2, 0 }), // 3/2/0: 01010000 (4) + std::make_unique(Tile::ID { 3, 2, 1 }), // 3/2/1: 01100000 (3) + }}; + + // Use a random order to verify that the result of this algorithm is independent of the + std::random_shuffle(tiles.begin(), tiles.end()); + + std::list tile_ptrs; + std::transform(tiles.begin(), tiles.end(), std::back_inserter(tile_ptrs), [](std::unique_ptr &tile) { return tile.get(); }); + + updateClipIDs(tile_ptrs); + + // Sort them by tile ID so that we know what order we have to test in. + std::sort(tiles.begin(), tiles.end(), [](const std::unique_ptr &a, const std::unique_ptr &b) { + return a->id < b->id; + }); + + // for (const auto &it : tiles) { + // std::cout << std::string(it->id) << ": " << it->clip.mask << " (" << (int)it->clip.length << ")" << std::endl; + // } + + ASSERT_EQ(std::string("2/0/0"), std::string(tiles[0]->id)); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[0]->clip.mask); + ASSERT_EQ(1, tiles[0]->clip.length); + + ASSERT_EQ(std::string("2/1/0"), std::string(tiles[1]->id)); + ASSERT_EQ(std::bitset<8>("01000000"), tiles[1]->clip.mask); + ASSERT_EQ(2, tiles[1]->clip.length); + + ASSERT_EQ(std::string("3/0/0"), std::string(tiles[2]->id)); + ASSERT_EQ(std::bitset<8>("10000000"), tiles[2]->clip.mask); + ASSERT_EQ(3, tiles[2]->clip.length); + + ASSERT_EQ(std::string("3/0/1"), std::string(tiles[3]->id)); + ASSERT_EQ(std::bitset<8>("11100000"), tiles[3]->clip.mask); + ASSERT_EQ(3, tiles[3]->clip.length); + + ASSERT_EQ(std::string("3/1/0"), std::string(tiles[4]->id)); + ASSERT_EQ(std::bitset<8>("10100000"), tiles[4]->clip.mask); + ASSERT_EQ(3, tiles[4]->clip.length); + + ASSERT_EQ(std::string("3/1/1"), std::string(tiles[5]->id)); + ASSERT_EQ(std::bitset<8>("11000000"), tiles[5]->clip.mask); + ASSERT_EQ(3, tiles[5]->clip.length); + + ASSERT_EQ(std::string("3/2/0"), std::string(tiles[6]->id)); + ASSERT_EQ(std::bitset<8>("01010000"), tiles[6]->clip.mask); + ASSERT_EQ(4, tiles[6]->clip.length); + + ASSERT_EQ(std::string("3/2/1"), std::string(tiles[7]->id)); + ASSERT_EQ(std::bitset<8>("01100000"), tiles[7]->clip.mask); + ASSERT_EQ(3, tiles[7]->clip.length); + + ASSERT_EQ(std::string("4/0/2"), std::string(tiles[8]->id)); + ASSERT_EQ(std::bitset<8>("11110000"), tiles[8]->clip.mask); + ASSERT_EQ(5, tiles[8]->clip.length); + + ASSERT_EQ(std::string("4/0/3"), std::string(tiles[9]->id)); + ASSERT_EQ(std::bitset<8>("11111000"), tiles[9]->clip.mask); + ASSERT_EQ(5, tiles[9]->clip.length); + + ASSERT_EQ(std::string("4/1/2"), std::string(tiles[10]->id)); + ASSERT_EQ(std::bitset<8>("11100000"), tiles[10]->clip.mask); + ASSERT_EQ(5, tiles[10]->clip.length); + + ASSERT_EQ(std::string("4/1/3"), std::string(tiles[11]->id)); + ASSERT_EQ(std::bitset<8>("11101000"), tiles[11]->clip.mask); + ASSERT_EQ(5, tiles[11]->clip.length); +} + +TEST(ClipIDs, DuplicateIDs) { + + std::forward_list tiles {{ + Tile::ID { 2, 0, 0 }, + Tile::ID { 2, 0, 1 }, + Tile::ID { 2, 0, 0 }, + Tile::ID { 2, 0, 1 }, + Tile::ID { 2, 0, 1 }, + }}; + + std::map mapping = computeClipIDs(tiles); + + ASSERT_EQ(2ull, mapping.size()); + + // for (const auto &it : mapping) { + // std::cout << std::string(it.first) << "(" << it.first << ")" << ": " << it.second.mask << " (" << (int)it.second.length << ")" << std::endl; + // } + + ASSERT_EQ(std::bitset<8>("01000000"), mapping[Tile::ID(2, 0, 0)].mask); + ASSERT_EQ(2, mapping[Tile::ID(2, 0, 0)].length); + + ASSERT_EQ(std::bitset<8>("10000000"), mapping[Tile::ID(2, 0, 1)].mask); + ASSERT_EQ(1, mapping[Tile::ID(2, 0, 1)].length); +} diff --git a/test/test.gyp b/test/test.gyp index 3e5b7c88d..9cb44d854 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -64,6 +64,22 @@ "../llmr.gyp:llmr-x86" ] }, + { + "target_name": "clip_ids", + "product_name": "test_clip_ids", + "type": "executable", + "libraries": [ + "-lpthread", + ], + "sources": [ + "./main.cpp", + "./clip_ids.cpp", + ], + "dependencies": [ + "../deps/gtest/gtest.gyp:gtest", + "../llmr.gyp:llmr-x86" + ] + }, { "target_name": "variant", "product_name": "test_variant", @@ -80,6 +96,22 @@ "../llmr.gyp:llmr-x86" ] }, + { + "target_name": "tile", + "product_name": "test_tile", + "type": "executable", + "libraries": [ + "-lpthread", + ], + "sources": [ + "./main.cpp", + "./tile.cpp", + ], + "dependencies": [ + "../deps/gtest/gtest.gyp:gtest", + "../llmr.gyp:llmr-x86" + ] + }, { "target_name": "headless", "product_name": "test_headless", @@ -105,7 +137,9 @@ "type": "none", "dependencies": [ "rotation_range", + "clip_ids", "variant", + "tile", ], } ] diff --git a/test/tile.cpp b/test/tile.cpp new file mode 100644 index 000000000..9e36fbecf --- /dev/null +++ b/test/tile.cpp @@ -0,0 +1,50 @@ +#include +#include "gtest/gtest.h" + +#include + +using namespace llmr; + + +TEST(Variant, isChild) { + ASSERT_TRUE(Tile::ID(1, 0, 0).isChildOf(Tile::ID(0, 0, 0))); + ASSERT_TRUE(Tile::ID(1, 1, 0).isChildOf(Tile::ID(0, 0, 0))); + ASSERT_TRUE(Tile::ID(1, 2, 0).isChildOf(Tile::ID(0, 1, 0))); + ASSERT_TRUE(Tile::ID(1, 3, 0).isChildOf(Tile::ID(0, 1, 0))); + ASSERT_TRUE(Tile::ID(1, 4, 0).isChildOf(Tile::ID(0, 2, 0))); + ASSERT_TRUE(Tile::ID(1, 5, 0).isChildOf(Tile::ID(0, 2, 0))); + ASSERT_TRUE(Tile::ID(2, 0, 0).isChildOf(Tile::ID(0, 0, 0))); + + ASSERT_TRUE(Tile::ID(2, 8, 0).isChildOf(Tile::ID(0, 2, 0))); + ASSERT_TRUE(Tile::ID(2, 9, 0).isChildOf(Tile::ID(0, 2, 0))); + ASSERT_TRUE(Tile::ID(2, 10, 0).isChildOf(Tile::ID(0, 2, 0))); + ASSERT_TRUE(Tile::ID(2, 11, 0).isChildOf(Tile::ID(0, 2, 0))); + ASSERT_TRUE(Tile::ID(2, 12, 0).isChildOf(Tile::ID(0, 3, 0))); + ASSERT_TRUE(Tile::ID(2, 13, 0).isChildOf(Tile::ID(0, 3, 0))); + + ASSERT_TRUE(Tile::ID(1, -1, 0).isChildOf(Tile::ID(0, -1, 0))); + ASSERT_TRUE(Tile::ID(1, -2, 0).isChildOf(Tile::ID(0, -1, 0))); + ASSERT_TRUE(Tile::ID(1, -3, 0).isChildOf(Tile::ID(0, -2, 0))); + ASSERT_TRUE(Tile::ID(1, -4, 0).isChildOf(Tile::ID(0, -2, 0))); + ASSERT_TRUE(Tile::ID(2, -1, 0).isChildOf(Tile::ID(0, -1, 0))); + ASSERT_TRUE(Tile::ID(2, -2, 0).isChildOf(Tile::ID(0, -1, 0))); + ASSERT_TRUE(Tile::ID(2, -3, 0).isChildOf(Tile::ID(0, -1, 0))); + ASSERT_TRUE(Tile::ID(2, -4, 0).isChildOf(Tile::ID(0, -1, 0))); + ASSERT_TRUE(Tile::ID(2, -5, 0).isChildOf(Tile::ID(0, -2, 0))); + ASSERT_TRUE(Tile::ID(2, -6, 0).isChildOf(Tile::ID(0, -2, 0))); + ASSERT_TRUE(Tile::ID(2, -7, 0).isChildOf(Tile::ID(0, -2, 0))); + ASSERT_TRUE(Tile::ID(2, -8, 0).isChildOf(Tile::ID(0, -2, 0))); + + ASSERT_FALSE(Tile::ID(4, -16, 0).isChildOf(Tile::ID(0, -2, 0))); + ASSERT_TRUE(Tile::ID(4, -17, 0).isChildOf(Tile::ID(0, -2, 0))); + + ASSERT_TRUE(Tile::ID(2, -1, 0).isChildOf(Tile::ID(1, -1, 0))); + ASSERT_TRUE(Tile::ID(2, -2, 0).isChildOf(Tile::ID(1, -1, 0))); + ASSERT_TRUE(Tile::ID(2, -3, 0).isChildOf(Tile::ID(1, -2, 0))); + ASSERT_TRUE(Tile::ID(2, -4, 0).isChildOf(Tile::ID(1, -2, 0))); + ASSERT_TRUE(Tile::ID(3, -1, 0).isChildOf(Tile::ID(1, -1, 0))); + ASSERT_TRUE(Tile::ID(3, -2, 0).isChildOf(Tile::ID(1, -1, 0))); + ASSERT_TRUE(Tile::ID(3, -3, 0).isChildOf(Tile::ID(1, -1, 0))); + ASSERT_TRUE(Tile::ID(3, -4, 0).isChildOf(Tile::ID(1, -1, 0))); + ASSERT_TRUE(Tile::ID(3, -5, 0).isChildOf(Tile::ID(1, -2, 0))); +} From 604e558d46e0e49a0e3f232610c608bc8f698e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Sat, 10 May 2014 10:32:30 +0200 Subject: [PATCH 04/21] add missing include header --- include/llmr/style/sprite.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index 40da8c4a8..6dd4828e3 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include From d964128cc5ab682055f972e74bd617e163bc7bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 10:40:10 +0200 Subject: [PATCH 05/21] make sure node is available --- Makefile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 1b262cc91..d3b74c86b 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,12 @@ V ?= 1 all: llmr # Builds the regular library -llmr: config.gypi llmr.gyp +llmr: config.gypi llmr.gyp node deps/run_gyp llmr.gyp --depth=. -Goutput_dir=.. --generator-output=./build/llmr -f make make -C build/llmr V=$(V) llmr-x86 +node: + @if [ ! `which node` ]; then echo 'error: depends on node.js. please make sure node is on your PATH'; exit 1; fi; ##### Test cases ############################################################### @@ -40,7 +42,7 @@ run-headless-test: build/test/Makefile # Builds the linux app with make. This is also used by Travis CI -linux: config.gypi linux/llmr-app.gyp +linux: config.gypi linux/llmr-app.gyp node deps/run_gyp linux/llmr-app.gyp --depth=. -Goutput_dir=.. --generator-output=./build/linux -f make make -C build/linux V=$(V) linuxapp @@ -52,17 +54,17 @@ run-linux: linux ##### Xcode projects ########################################################### # build Mac OS X project for Xcode -xproj: config.gypi macosx/llmr-app.gyp +xproj: config.gypi macosx/llmr-app.gyp node deps/run_gyp macosx/llmr-app.gyp --depth=. --generator-output=./build -f xcode open ./build/macosx/llmr-app.xcodeproj # build iOS project for Xcode -iproj: config.gypi ios/llmr-app.gyp +iproj: config.gypi ios/llmr-app.gyp node deps/run_gyp ios/llmr-app.gyp --depth=. --generator-output=./build -f xcode open ./build/ios/llmr-app.xcodeproj # build Linux project for Xcode (Runs on Mac OS X too, but without platform-specific code) -lproj: config.gypi linux/llmr-app.gyp +lproj: config.gypi linux/llmr-app.gyp node deps/run_gyp linux/llmr-app.gyp --depth=. --generator-output=./build -f xcode open ./build/linux/llmr-app.xcodeproj From b705382b9fdd6df43eadb116b42a692d86416492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 04:00:38 -0700 Subject: [PATCH 06/21] add cross compile build script [skip ci] --- setup-libraries-cross.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 setup-libraries-cross.sh diff --git a/setup-libraries-cross.sh b/setup-libraries-cross.sh new file mode 100755 index 000000000..656f20242 --- /dev/null +++ b/setup-libraries-cross.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e -u + + +export CXX11=true + +export SYSROOT=`pwd`/../rootfs +export SDK_PATH=`pwd`/../arm-linux-gnueabi-linaro_4.8.3-2014.04 +export V=1 + +cd mapnik-packaging/osx/ + +source Linaro-softfp.sh + echo $PATH + if [ ! -f out/build-cpp11-libstdcpp-gcc-arm-linux-gnueabi/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi + # if [ ! -f out/build-cpp11-libstdcpp-gcc-arm-linux-gnueabi/lib/libglfw3.a ] ; then ./scripts/build_glfw.sh ; fi + if [ ! -f out/build-cpp11-libstdcpp-gcc-arm-linux-gnueabi/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi + if [ ! -f out/build-cpp11-libstdcpp-gcc-arm-linux-gnueabi/lib/libssl.a ] ; then ./scripts/build_openssl.sh ; fi + # if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi + # if [ ! -d out/build-cpp11-libstdcpp-gcc-arm-linux-gnueabi/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../linux/ `pwd`/../../common/ ; fi + + + +# cd ../../ +# ./configure \ +# --pkg-config-root=`pwd`/mapnik-packaging/osx/out/build-cpp11-libstdcpp-gcc-x86_64/lib/pkgconfig \ +# --boost=`pwd`/mapnik-packaging/osx/out/build-cpp11-libstdcpp-gcc-x86_64 + +# fi From 6136ab6d213c0aa5e6afba632fc42b5999e70615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 17:13:26 +0200 Subject: [PATCH 07/21] draw by layer, not by tile This is needed for correctly compositing raster imagery into the map fixes #122 fixes #157 --- bin/style.js | 13 ++++ include/llmr/map/map.hpp | 2 + include/llmr/map/source.hpp | 3 +- include/llmr/renderer/painter.hpp | 30 +++++++-- src/map/map.cpp | 64 ++++++++++++++++-- src/map/source.cpp | 13 +++- src/map/vector_tile_data.cpp | 2 +- src/renderer/painter.cpp | 106 +++++++++++------------------- 8 files changed, 148 insertions(+), 85 deletions(-) diff --git a/bin/style.js b/bin/style.js index 47cdcc1f9..9bbe86631 100644 --- a/bin/style.js +++ b/bin/style.js @@ -2067,6 +2067,19 @@ module.exports = { "opacity": 0.25 }, } + }, { + "name": "satellite", + "layers": { + "building": { + "enabled": false, + }, + "building_wall": { + "enabled": false, + }, + "building_shadow": { + "enabled": false, + }, + } }], "sprite": "/img/maki-sprite" }; diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index a9bdf53e5..e82ef42b5 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -115,6 +115,8 @@ private: // Unconditionally performs a render with the current map state. void render(); + void renderLayers(const std::vector& layers); + void renderLayer(const LayerDescription& layer_desc); private: // If cleared, the next time the render thread attempts to render the map, it will *actually* diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index 4da8e6149..7d47d61b7 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -36,7 +36,8 @@ public: bool update(); size_t prepareRender(const TransformState &transform); - void render(); + void render(const LayerDescription& layer_desc, const BucketDescription &bucket_desc); + void finishRender(); std::forward_list getIDs() const; void updateClipIDs(const std::map &mapping); diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index a5455ba62..d236ead44 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -43,11 +43,23 @@ public: void setup(); void clear(); + + // Updates the default matrices to the current viewport dimensions. void changeMatrix(); - void render(const Tile& tile); + + // Renders a particular layer from a tile. + void renderTileLayer(const Tile& tile, const LayerDescription &layer_desc); + + // Renders debug information for a tile. + void renderTileDebug(const Tile& tile); + + // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any + // tiles whatsoever. void renderMatte(); + // Renders the red debug frame around a tile, visualizing its perimeter. void renderDebugFrame(); + void renderDebugText(DebugBucket& bucket); void renderDebugText(const std::vector &strings); void renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id); @@ -61,19 +73,24 @@ public: // Changes whether debug information is drawn onto the map void setDebug(bool enabled); + // Opaque/Translucent pass setting + void startOpaquePass(); + void startTranslucentPass(); + void endPass(); + + // Configures the painter strata that is used for early z-culling of fragments. + void setStrata(float strata); + void prepareClippingMask(); void drawClippingMask(const mat4& matrix, const ClipID& clip); void finishClippingMask(); - void setCurrentSourceName(const std::string &name); - bool needsAnimation() const; private: void setupShaders(); - void renderLayers(const std::shared_ptr& tile, const std::vector& layers); - void renderLayer(const std::shared_ptr& tile_data, const LayerDescription& layer_desc); void translateLayer(std::array translation, bool reverse = false); + void prepareTile(const Tile& tile); void useProgram(uint32_t program); void lineWidth(float lineWidth); void depthMask(bool value); @@ -96,9 +113,8 @@ private: bool gl_depthMask = true; std::array gl_viewport = {{ 0, 0 }}; float strata = 0; - const float strata_epsilon = 1.0f / (1 << 16); enum { Opaque, Translucent } pass = Opaque; - std::string currentSourceName; + const float strata_epsilon = 1.0f / (1 << 16); std::unique_ptr plainShader; std::unique_ptr outlineShader; diff --git a/src/map/map.cpp b/src/map/map.cpp index dc707ee8f..0155874ac 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -423,14 +423,16 @@ void Map::render() { painter.finishClippingMask(); + // Render per layer in the stylesheet. + renderLayers(style.layers); - // Then, render each source's tiles. TODO: handle more than one - // vector source. - + // 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 (auto &pair : sources) { Source &source = *pair.second; - painter.setCurrentSourceName(pair.first); - source.render(); + source.finishRender(); } painter.renderMatte(); @@ -442,3 +444,55 @@ void Map::render() { update(); } } + +void Map::renderLayers(const std::vector& layers) { + // TODO: Correctly compute the number of layers recursively beforehand. + float strata_thickness = 1.0f / (layers.size() + 1); + + // - FIRST PASS ------------------------------------------------------------ + // Render everything top-to-bottom by using reverse iterators. Render opaque + // objects first. + painter.startOpaquePass(); + typedef std::vector::const_reverse_iterator riterator; + int i = 0; + for (riterator it = layers.rbegin(), end = layers.rend(); it != end; ++it, ++i) { + painter.setStrata(i * strata_thickness); + renderLayer(*it); + } + painter.endPass(); + + // - SECOND PASS ----------------------------------------------------------- + // Make a second pass, rendering translucent objects. This time, we render + // bottom-to-top. + painter.startTranslucentPass(); + typedef std::vector::const_iterator iterator; + --i; + for (iterator it = layers.begin(), end = layers.end(); it != end; ++it, --i) { + painter.setStrata(i * strata_thickness); + renderLayer(*it); + } + painter.endPass(); +} + +void Map::renderLayer(const LayerDescription& layer_desc) { + if (layer_desc.child_layer.size()) { + // This is a layer group. + // TODO: create framebuffer + renderLayers(layer_desc.child_layer); + // TODO: render framebuffer on previous framebuffer + } else { + // This is a singular layer. Try to find the bucket associated with + // this layer and render it. + auto bucket_it = style.buckets.find(layer_desc.bucket_name); + if (bucket_it != style.buckets.end()) { + const BucketDescription &bucket_desc = bucket_it->second; + + // Find the source and render all tiles in it. + auto source_it = sources.find(bucket_desc.source_name); + if (source_it != sources.end()) { + const std::unique_ptr &source = source_it->second; + source->render(layer_desc, bucket_desc); + } + } + } +} diff --git a/src/map/source.cpp b/src/map/source.cpp index 91e9d7cb5..c6f3f63e1 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -57,16 +57,25 @@ size_t Source::prepareRender(const TransformState &transform) { return masks; } -void Source::render() { +void Source::render(const LayerDescription& layer_desc, const BucketDescription &bucket_desc) { if (!enabled) return; for (const Tile& tile : tiles) { if (tile.data && tile.data->state == TileData::State::parsed) { - painter.render(tile); + painter.renderTileLayer(tile, layer_desc); } } } +void Source::finishRender() { + if (!enabled) return; + + for (const Tile& tile : tiles) { + painter.renderTileDebug(tile); + } +} + + std::forward_list Source::getIDs() const { std::forward_list ptrs; std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const Tile &tile) { diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp index c5d3a23ec..6b06b74b4 100644 --- a/src/map/vector_tile_data.cpp +++ b/src/map/vector_tile_data.cpp @@ -40,4 +40,4 @@ void VectorTileData::render(Painter &painter, const LayerDescription& layer_desc assert(databucket_it->second); databucket_it->second->render(painter, layer_desc.name, id); } -} \ No newline at end of file +} diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 5ab9c91d3..6deb884b4 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -167,11 +167,6 @@ void Painter::finishClippingMask() { gl::end_group(); } -void Painter::setCurrentSourceName(const std::string &name) { - currentSourceName = name; -} - - void Painter::clear() { gl::group group("clear"); glStencilMask(0xFF); @@ -185,86 +180,59 @@ void Painter::clear() { #endif } -void Painter::render(const Tile& tile) { +void Painter::startOpaquePass() { + gl::start_group("opaque pass"); + pass = Opaque; + glDisable(GL_BLEND); + depthMask(true); +} + +void Painter::startTranslucentPass() { + gl::start_group("translucent pass"); + pass = Translucent; + glEnable(GL_BLEND); + depthMask(false); +} + +void Painter::endPass() { + gl::end_group(); +} + +void Painter::setStrata(float value) { + strata = value; +} + +void Painter::prepareTile(const Tile& tile) { + matrix = tile.matrix; + + GLint id = tile.clip.mask.to_ulong(); + GLuint mask = clipMask[tile.clip.length]; + glStencilFunc(GL_EQUAL, id, mask); +} + +void Painter::renderTileLayer(const Tile& tile, const LayerDescription &layer_desc) { gl::group group(util::sprintf<32>("render %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); assert(tile.data); if (tile.data->state == TileData::State::parsed) { - matrix = tile.matrix; - GLint id = tile.clip.mask.to_ulong(); - GLuint mask = clipMask[tile.clip.length]; - - glStencilFunc(GL_EQUAL, id, mask); - - renderLayers(tile.data, map.getStyle().layers); + prepareTile(tile); + tile.data->render(*this, layer_desc); } else { fprintf(stderr, "tile not parsed yet\n"); } frameHistory.record(map.getAnimationTime(), map.getState().getNormalizedZoom()); +} +void Painter::renderTileDebug(const Tile& tile) { + assert(tile.data); if (debug) { + prepareTile(tile); renderDebugText(tile.data->debugBucket); renderDebugFrame(); } } -void Painter::renderLayers(const std::shared_ptr& tile_data, const std::vector& layers) { - float strata_thickness = 1.0f / (layers.size() + 1); - - // - FIRST PASS ------------------------------------------------------------ - // Render everything top-to-bottom by using reverse iterators. Render opaque - // objects first. - gl::start_group("opaque pass"); - pass = Opaque; - glDisable(GL_BLEND); - depthMask(true); - - typedef std::vector::const_reverse_iterator riterator; - int i = 0; - for (riterator it = layers.rbegin(), end = layers.rend(); it != end; ++it, ++i) { - strata = i * strata_thickness; - renderLayer(tile_data, *it); - } - gl::end_group(); - - // - SECOND PASS ----------------------------------------------------------- - // Make a second pass, rendering translucent objects. This time, we render - // bottom-to-top. - gl::start_group("translucent pass"); - pass = Translucent; - glEnable(GL_BLEND); - depthMask(false); - - typedef std::vector::const_iterator iterator; - --i; - for (iterator it = layers.begin(), end = layers.end(); it != end; ++it, --i) { - strata = i * strata_thickness; - renderLayer(tile_data, *it); - } - gl::end_group(); -} - -void Painter::renderLayer(const std::shared_ptr& tile_data, const LayerDescription& layer_desc) { - if (layer_desc.child_layer.size()) { - // This is a layer group. - // TODO: create framebuffer - renderLayers(tile_data, layer_desc.child_layer); - // TODO: render framebuffer on previous framebuffer - } else { - // This is a singular layer. Try to find the bucket associated with - // this layer and render it. - auto bucket_it = map.getStyle().buckets.find(layer_desc.bucket_name); - if (bucket_it != map.getStyle().buckets.end()) { - const BucketDescription &bucket = bucket_it->second; - // Only try to render the bucket if it draws data from the current source. - if (bucket.source_name == currentSourceName) { - tile_data->render(*this, layer_desc); - } - } - } -} - void Painter::translateLayer(std::array translation, bool reverse) { if (translation[0] || translation[1]) { if (reverse) { From 6ebb4c21f7e5fce9648d066cd2ac79891d78f139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 18:25:48 +0200 Subject: [PATCH 08/21] clean up gl::group() structure --- src/map/map.cpp | 4 ++++ src/map/source.cpp | 1 + src/renderer/painter.cpp | 12 ++---------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/map/map.cpp b/src/map/map.cpp index 0155874ac..5476f21d3 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -432,6 +432,8 @@ void Map::render() { // ever visit a tile during rendering. for (auto &pair : sources) { Source &source = *pair.second; + const std::string &name = pair.first; + gl::group group(std::string("debug ") + name); source.finishRender(); } @@ -475,6 +477,8 @@ void Map::renderLayers(const std::vector& layers) { } void Map::renderLayer(const LayerDescription& layer_desc) { + gl::group group(std::string("layer: ") + layer_desc.name); + if (layer_desc.child_layer.size()) { // This is a layer group. // TODO: create framebuffer diff --git a/src/map/source.cpp b/src/map/source.cpp index c6f3f63e1..2f2668e7b 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -48,6 +48,7 @@ size_t Source::prepareRender(const TransformState &transform) { size_t masks = 0; for (Tile& tile : tiles) { + gl::group group(util::sprintf<32>("mask %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); transform.matrixFor(tile.matrix, tile.id); matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); painter.drawClippingMask(tile.matrix, tile.clip); diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 6deb884b4..f7b205a31 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -141,7 +141,6 @@ void Painter::prepareClippingMask() { } void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { - gl::group group("mask"); plainShader->setMatrix(matrix); GLint id = clip.mask.to_ulong(); @@ -205,7 +204,7 @@ void Painter::setStrata(float value) { void Painter::prepareTile(const Tile& tile) { matrix = tile.matrix; - GLint id = tile.clip.mask.to_ulong(); + GLint id = (GLint)tile.clip.mask.to_ulong(); GLuint mask = clipMask[tile.clip.length]; glStencilFunc(GL_EQUAL, id, mask); } @@ -225,6 +224,7 @@ void Painter::renderTileLayer(const Tile& tile, const LayerDescription &layer_de } void Painter::renderTileDebug(const Tile& tile) { + gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); assert(tile.data); if (debug) { prepareTile(tile); @@ -257,8 +257,6 @@ void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const RasterProperties& properties = raster_properties_it->second; if (!properties.enabled) return; - gl::group group(layer_name + " (raster)"); - depthMask(false); useProgram(rasterShader->program); @@ -304,7 +302,6 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons translateLayer(properties.translate); - gl::group group(layer_name + " (fill)"); // Because we're drawing top-to-bottom, and we update the stencil mask // below, we have to draw the outline first (!) if (outline && pass == Translucent) { @@ -441,7 +438,6 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons translateLayer(properties.translate); - gl::group group(layer_name + " (line)"); glDepthRange(strata, 1.0f); // We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway. @@ -512,8 +508,6 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co translateLayer(properties.translate); - gl::group group(layer_name + " (point)"); - Color color = properties.color; color[0] *= properties.opacity; color[1] *= properties.opacity; @@ -588,8 +582,6 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons translateLayer(properties.translate); - gl::group group(layer_name + " (text)"); - mat4 exMatrix; matrix::copy(exMatrix, projMatrix); if (bucket.geom_desc.path == TextPathType::Curve) { From d9d9b281c47f79d85395686bf41eb60cc8dfdcf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 18:25:58 +0200 Subject: [PATCH 09/21] disable texture deleting for now --- src/util/texturepool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/texturepool.cpp b/src/util/texturepool.cpp index 9feadb3db..579f80472 100644 --- a/src/util/texturepool.cpp +++ b/src/util/texturepool.cpp @@ -36,7 +36,8 @@ void Texturepool::removeTextureID(GLuint texture_id) { } if (needs_clear) { - clearTextureIDs(); + // TODO: We need to find a better way to deal with texture pool cleanup +// clearTextureIDs(); } } From 2a3377cc4d6cb910dd2f4e637060d7155cbfbf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 18:28:21 +0200 Subject: [PATCH 10/21] fix typo fixes #186 --- setup-libraries.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup-libraries.sh b/setup-libraries.sh index 994ee9ed2..cd5dd4385 100755 --- a/setup-libraries.sh +++ b/setup-libraries.sh @@ -25,7 +25,7 @@ fi if [[ $MISSING_DEPS != false ]]; then if [ ${UNAME} = 'Darwin' ]; then - echo 'Please run "brew install autoconf automake libtool makedependc cmake" and then re-run ./setup-libraries.sh' + echo 'Please run "brew install autoconf automake libtool makedepend cmake" and then re-run ./setup-libraries.sh' elif [ ${UNAME} = 'Linux' ]; then echo 'Please run "sudo apt-get install automake libtool xutils-dev cmake" and then re-run ./setup-libraries.sh' fi From 2b1457998638e97660b5b19ba1e05341523030dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 19:08:59 +0200 Subject: [PATCH 11/21] only paint layer if it has data refs #184 --- include/llmr/map/raster_tile_data.hpp | 1 + include/llmr/map/tile_data.hpp | 1 + include/llmr/map/vector_tile_data.hpp | 1 + include/llmr/renderer/bucket.hpp | 1 + include/llmr/renderer/debug_bucket.hpp | 1 + include/llmr/renderer/fill_bucket.hpp | 2 +- include/llmr/renderer/line_bucket.hpp | 2 +- include/llmr/renderer/point_bucket.hpp | 3 +-- include/llmr/renderer/raster_bucket.hpp | 1 + include/llmr/renderer/text_bucket.hpp | 6 ++---- src/map/raster_tile_data.cpp | 4 ++++ src/map/vector_tile_data.cpp | 12 ++++++++++++ src/renderer/debug_bucket.cpp | 4 ++++ src/renderer/fill_bucket.cpp | 4 ++-- src/renderer/line_bucket.cpp | 4 ++-- src/renderer/painter.cpp | 18 +++++++----------- src/renderer/point_bucket.cpp | 2 +- src/renderer/raster_bucket.cpp | 4 ++++ src/renderer/text_bucket.cpp | 4 +++- 19 files changed, 50 insertions(+), 25 deletions(-) diff --git a/include/llmr/map/raster_tile_data.hpp b/include/llmr/map/raster_tile_data.hpp index 33243334b..fd72cca9b 100644 --- a/include/llmr/map/raster_tile_data.hpp +++ b/include/llmr/map/raster_tile_data.hpp @@ -17,6 +17,7 @@ public: virtual void parse(); virtual void render(Painter &painter, const LayerDescription& layer_desc); + virtual bool hasData(const LayerDescription& layer_desc) const; protected: RasterBucket bucket; diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index dde48e9c4..65a2892c7 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -48,6 +48,7 @@ public: // Override this in the child class. virtual void parse() = 0; virtual void render(Painter &painter, const LayerDescription& layer_desc) = 0; + virtual bool hasData(const LayerDescription& layer_desc) const = 0; public: diff --git a/include/llmr/map/vector_tile_data.hpp b/include/llmr/map/vector_tile_data.hpp index 6deae7bd7..b9a428dde 100644 --- a/include/llmr/map/vector_tile_data.hpp +++ b/include/llmr/map/vector_tile_data.hpp @@ -27,6 +27,7 @@ public: virtual void parse(); virtual void render(Painter &painter, const LayerDescription& layer_desc); + virtual bool hasData(const LayerDescription& layer_desc) const; protected: // Holds the actual geometries in this tile. diff --git a/include/llmr/renderer/bucket.hpp b/include/llmr/renderer/bucket.hpp index 9326aaed1..283a2c94b 100644 --- a/include/llmr/renderer/bucket.hpp +++ b/include/llmr/renderer/bucket.hpp @@ -12,6 +12,7 @@ class Painter; class Bucket : private util::noncopyable { public: virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id) = 0; + virtual bool hasData() const = 0; virtual ~Bucket() {} }; diff --git a/include/llmr/renderer/debug_bucket.hpp b/include/llmr/renderer/debug_bucket.hpp index f1a7d151f..0b9df57fa 100644 --- a/include/llmr/renderer/debug_bucket.hpp +++ b/include/llmr/renderer/debug_bucket.hpp @@ -22,6 +22,7 @@ public: DebugBucket(DebugFontBuffer& fontBuffer); virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + virtual bool hasData() const; void drawLines(PlainShader& shader); void drawPoints(PlainShader& shader); diff --git a/include/llmr/renderer/fill_bucket.hpp b/include/llmr/renderer/fill_bucket.hpp index 7d2b81287..2d1f61d6a 100644 --- a/include/llmr/renderer/fill_bucket.hpp +++ b/include/llmr/renderer/fill_bucket.hpp @@ -45,9 +45,9 @@ public: ~FillBucket(); virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + virtual bool hasData() const; void addGeometry(pbf& data); - bool empty() const; void tessellate(); void drawElements(PlainShader& shader); diff --git a/include/llmr/renderer/line_bucket.hpp b/include/llmr/renderer/line_bucket.hpp index eb55bba5a..e28c85b7a 100644 --- a/include/llmr/renderer/line_bucket.hpp +++ b/include/llmr/renderer/line_bucket.hpp @@ -28,11 +28,11 @@ public: const BucketDescription& bucket_desc); virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + virtual bool hasData() const; void addGeometry(pbf& data); void addGeometry(const std::vector& line); - bool empty() const; bool hasPoints() const; void drawLines(LineShader& shader); diff --git a/include/llmr/renderer/point_bucket.hpp b/include/llmr/renderer/point_bucket.hpp index 6bbaaac8a..2aacbe865 100644 --- a/include/llmr/renderer/point_bucket.hpp +++ b/include/llmr/renderer/point_bucket.hpp @@ -28,11 +28,10 @@ public: const BucketDescription& bucket_desc); virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + virtual bool hasData() const; void addGeometry(pbf& data); - bool hasPoints() const; - void drawPoints(PointShader& shader); void drawPoints(DotShader& shader); diff --git a/include/llmr/renderer/raster_bucket.hpp b/include/llmr/renderer/raster_bucket.hpp index bac87cc72..17e36de77 100644 --- a/include/llmr/renderer/raster_bucket.hpp +++ b/include/llmr/renderer/raster_bucket.hpp @@ -16,6 +16,7 @@ public: RasterBucket(Texturepool &texturepool); virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); + virtual bool hasData() const; bool setImage(const std::string &data); diff --git a/include/llmr/renderer/text_bucket.hpp b/include/llmr/renderer/text_bucket.hpp index babec7ea4..2b73aa74d 100644 --- a/include/llmr/renderer/text_bucket.hpp +++ b/include/llmr/renderer/text_bucket.hpp @@ -29,8 +29,8 @@ public: TriangleElementsBuffer &triangleElementsBuffer, const BucketDescription &bucket_desc, Placement &placement); - virtual void render(Painter &painter, const std::string &layer_name, - const Tile::ID &id); + virtual void render(Painter &painter, const std::string &layer_name, const Tile::ID &id); + virtual bool hasData() const; void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, PlacementRange placementRange, float zoom); @@ -39,8 +39,6 @@ public: const IndexedFaces &faces, const std::map &shapings); - bool empty() const; - void drawGlyphs(TextShader &shader); public: diff --git a/src/map/raster_tile_data.cpp b/src/map/raster_tile_data.cpp index bf3999637..5c522ef2a 100644 --- a/src/map/raster_tile_data.cpp +++ b/src/map/raster_tile_data.cpp @@ -28,3 +28,7 @@ void RasterTileData::parse() { void RasterTileData::render(Painter &painter, const LayerDescription& layer_desc) { bucket.render(painter, layer_desc.name, id); } + +bool RasterTileData::hasData(const LayerDescription& layer_desc) const { + return bucket.hasData(); +} diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp index 6b06b74b4..84fc4ffc4 100644 --- a/src/map/vector_tile_data.cpp +++ b/src/map/vector_tile_data.cpp @@ -41,3 +41,15 @@ void VectorTileData::render(Painter &painter, const LayerDescription& layer_desc databucket_it->second->render(painter, layer_desc.name, id); } } + +bool VectorTileData::hasData(const LayerDescription& layer_desc) const { + if (state != State::parsed) return false; + + auto databucket_it = buckets.find(layer_desc.bucket_name); + if (databucket_it != buckets.end()) { + assert(databucket_it->second); + return databucket_it->second->hasData(); + } else { + return false; + } +} diff --git a/src/renderer/debug_bucket.cpp b/src/renderer/debug_bucket.cpp index 2cdec50d3..284d8a371 100644 --- a/src/renderer/debug_bucket.cpp +++ b/src/renderer/debug_bucket.cpp @@ -18,6 +18,10 @@ void DebugBucket::render(Painter& painter, const std::string& /*layer_name*/, co painter.renderDebugText(*this); } +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())); diff --git a/src/renderer/fill_bucket.cpp b/src/renderer/fill_bucket.cpp index 4e553d23d..e4783c5c3 100644 --- a/src/renderer/fill_bucket.cpp +++ b/src/renderer/fill_bucket.cpp @@ -206,8 +206,8 @@ void FillBucket::render(Painter& painter, const std::string& layer_name, const T painter.renderFill(*this, layer_name, id); } -bool FillBucket::empty() const { - return triangleGroups.empty() && lineGroups.empty(); +bool FillBucket::hasData() const { + return !triangleGroups.empty() || !lineGroups.empty(); } void FillBucket::drawElements(PlainShader& shader) { diff --git a/src/renderer/line_bucket.cpp b/src/renderer/line_bucket.cpp index 01d7c52a2..f968c56cb 100644 --- a/src/renderer/line_bucket.cpp +++ b/src/renderer/line_bucket.cpp @@ -348,8 +348,8 @@ void LineBucket::render(Painter& painter, const std::string& layer_name, const T painter.renderLine(*this, layer_name, id); } -bool LineBucket::empty() const { - return triangleGroups.empty(); +bool LineBucket::hasData() const { + return !triangleGroups.empty() || !pointGroups.empty(); } bool LineBucket::hasPoints() const { diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index f7b205a31..9f1372674 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -210,17 +210,13 @@ void Painter::prepareTile(const Tile& tile) { } void Painter::renderTileLayer(const Tile& tile, const LayerDescription &layer_desc) { - gl::group group(util::sprintf<32>("render %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); - assert(tile.data); - if (tile.data->state == TileData::State::parsed) { + if (tile.data->hasData(layer_desc)) { + gl::group group(util::sprintf<32>("render %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); prepareTile(tile); tile.data->render(*this, layer_desc); - } else { - fprintf(stderr, "tile not parsed yet\n"); + frameHistory.record(map.getAnimationTime(), map.getState().getNormalizedZoom()); } - - frameHistory.record(map.getAnimationTime(), map.getState().getNormalizedZoom()); } void Painter::renderTileDebug(const Tile& tile) { @@ -272,7 +268,7 @@ void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { // Abort early. - if (bucket.empty()) return; + if (!bucket.hasData()) return; auto fill_properties = map.getStyle().computed.fills; auto fill_properties_it = fill_properties.find(layer_name); @@ -410,7 +406,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { // Abort early. if (pass == Opaque) return; - if (bucket.empty()) return; + if (!bucket.hasData()) return; auto line_properties = map.getStyle().computed.lines; auto line_properties_it = line_properties.find(layer_name); @@ -496,7 +492,7 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) { // Abort early. - if (!bucket.hasPoints()) return; + if (!bucket.hasData()) return; if (pass == Opaque) return; auto point_properties = map.getStyle().computed.points; @@ -571,7 +567,7 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { // Abort early. if (pass == Opaque) return; - if (bucket.empty()) return; + if (!bucket.hasData()) return; auto text_properties = map.getStyle().computed.texts; auto text_properties_it = text_properties.find(layer_name); diff --git a/src/renderer/point_bucket.cpp b/src/renderer/point_bucket.cpp index d662595c2..fa8482944 100644 --- a/src/renderer/point_bucket.cpp +++ b/src/renderer/point_bucket.cpp @@ -41,7 +41,7 @@ void PointBucket::render(Painter& painter, const std::string& layer_name, const painter.renderPoint(*this, layer_name, id); } -bool PointBucket::hasPoints() const { +bool PointBucket::hasData() const { return vertex_end > 0; } diff --git a/src/renderer/raster_bucket.cpp b/src/renderer/raster_bucket.cpp index ca173680a..c0260023d 100644 --- a/src/renderer/raster_bucket.cpp +++ b/src/renderer/raster_bucket.cpp @@ -21,3 +21,7 @@ void RasterBucket::drawRaster(RasterShader& shader, VertexBuffer &vertices, Vert 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/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index 5b75ca99f..939a8363e 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -146,7 +146,9 @@ void TextBucket::render(Painter &painter, const std::string &layer_name, painter.renderText(*this, layer_name, id); } -bool TextBucket::empty() const { return triangleGroups.empty(); } +bool TextBucket::hasData() const { + return !triangleGroups.empty(); +} void TextBucket::drawGlyphs(TextShader &shader) { char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); From 263c88777708c42526881ac79b63f7a81f90d4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 12 May 2014 19:53:06 +0200 Subject: [PATCH 12/21] don't try to render layers that do not produce anyting during the current render pass refs #184 --- include/llmr/map/map.hpp | 10 ++- include/llmr/style/layer_description.hpp | 2 + include/llmr/style/style.hpp | 1 + src/map/map.cpp | 83 +++++++++++++++--------- src/map/source.cpp | 1 + src/style/layer_description.cpp | 10 +++ src/style/style.cpp | 8 +++ 7 files changed, 82 insertions(+), 33 deletions(-) diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index e82ef42b5..4600e1a96 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -109,14 +109,18 @@ private: void updateTiles(); void updateClippingIDs(); + size_t countLayers(const std::vector& layers); + // Prepares a map render by updating the tiles we need for the current view, as well as updating // the stylesheet. void prepare(); + enum RenderPass { Opaque, Translucent }; + // Unconditionally performs a render with the current map state. void render(); - void renderLayers(const std::vector& layers); - void renderLayer(const LayerDescription& layer_desc); + void renderLayers(const std::vector& layers, RenderPass pass); + void renderLayer(const LayerDescription& layer_desc, RenderPass pass); private: // If cleared, the next time the render thread attempts to render the map, it will *actually* @@ -145,6 +149,8 @@ private: bool debug = false; time animationTime = 0; + float strata_thickness = 0.0f; + int strata = 0; private: bool async = false; diff --git a/include/llmr/style/layer_description.hpp b/include/llmr/style/layer_description.hpp index 405c884e5..97cb7d6f2 100644 --- a/include/llmr/style/layer_description.hpp +++ b/include/llmr/style/layer_description.hpp @@ -11,6 +11,8 @@ public: std::string name; std::string bucket_name; std::vector child_layer; + + size_t size() const; }; std::ostream& operator<<(std::ostream&, const LayerDescription& layer); diff --git a/include/llmr/style/style.hpp b/include/llmr/style/style.hpp index 2f1e42c4f..213a0b94c 100644 --- a/include/llmr/style/style.hpp +++ b/include/llmr/style/style.hpp @@ -28,6 +28,7 @@ public: void loadJSON(const uint8_t *const data, size_t bytes); + size_t layerCount() const; void cascade(float z); public: diff --git a/src/map/map.cpp b/src/map/map.cpp index 5476f21d3..6775f4721 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -423,8 +423,27 @@ void Map::render() { painter.finishClippingMask(); + + // Render per layer in the stylesheet. - renderLayers(style.layers); + strata_thickness = 1.0f / (style.layerCount() + 1); + strata = 0; + + // - FIRST PASS ------------------------------------------------------------ + // Render everything top-to-bottom by using reverse iterators. Render opaque + // objects first. + painter.startOpaquePass(); + renderLayers(style.layers, Opaque); + painter.endPass(); + + // - SECOND PASS ----------------------------------------------------------- + // Make a second pass, rendering translucent objects. This time, we render + // bottom-to-top. + --strata; + painter.startTranslucentPass(); + renderLayers(style.layers, Translucent); + painter.endPass(); + // Finalize the rendering, e.g. by calling debug render calls per tile. // This guarantees that we have at least one function per tile called. @@ -447,42 +466,30 @@ void Map::render() { } } -void Map::renderLayers(const std::vector& layers) { - // TODO: Correctly compute the number of layers recursively beforehand. - float strata_thickness = 1.0f / (layers.size() + 1); - - // - FIRST PASS ------------------------------------------------------------ - // Render everything top-to-bottom by using reverse iterators. Render opaque - // objects first. - painter.startOpaquePass(); - typedef std::vector::const_reverse_iterator riterator; - int i = 0; - for (riterator it = layers.rbegin(), end = layers.rend(); it != end; ++it, ++i) { - painter.setStrata(i * strata_thickness); - renderLayer(*it); +void Map::renderLayers(const std::vector& layers, RenderPass pass) { + if (pass == Opaque) { + typedef std::vector::const_reverse_iterator riterator; + for (riterator it = layers.rbegin(), end = layers.rend(); it != end; ++it, ++strata) { + painter.setStrata(strata * strata_thickness); + renderLayer(*it, pass); + } + } else if (pass == Translucent) { + typedef std::vector::const_iterator iterator; + for (iterator it = layers.begin(), end = layers.end(); it != end; ++it, --strata) { + painter.setStrata(strata * strata_thickness); + renderLayer(*it, pass); + } + } else { + throw std::runtime_error("unknown render pass"); } - painter.endPass(); - - // - SECOND PASS ----------------------------------------------------------- - // Make a second pass, rendering translucent objects. This time, we render - // bottom-to-top. - painter.startTranslucentPass(); - typedef std::vector::const_iterator iterator; - --i; - for (iterator it = layers.begin(), end = layers.end(); it != end; ++it, --i) { - painter.setStrata(i * strata_thickness); - renderLayer(*it); - } - painter.endPass(); } -void Map::renderLayer(const LayerDescription& layer_desc) { - gl::group group(std::string("layer: ") + layer_desc.name); - +void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) { if (layer_desc.child_layer.size()) { + gl::group group(std::string("group: ") + layer_desc.name); // This is a layer group. // TODO: create framebuffer - renderLayers(layer_desc.child_layer); + renderLayers(layer_desc.child_layer, pass); // TODO: render framebuffer on previous framebuffer } else { // This is a singular layer. Try to find the bucket associated with @@ -491,6 +498,20 @@ void Map::renderLayer(const LayerDescription& layer_desc) { if (bucket_it != style.buckets.end()) { const BucketDescription &bucket_desc = bucket_it->second; + // Abort early if we can already deduce from the bucket type that + // we're not going to render anything anyway during this pass. + switch (bucket_desc.type) { + case BucketType::Line: + case BucketType::Point: + case BucketType::Text: + if (pass == Opaque) return; + break; + case BucketType::Raster: + if (pass == Translucent) return; + default: + break; + } + // Find the source and render all tiles in it. auto source_it = sources.find(bucket_desc.source_name); if (source_it != sources.end()) { diff --git a/src/map/source.cpp b/src/map/source.cpp index 2f2668e7b..384cb0a62 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -61,6 +61,7 @@ size_t Source::prepareRender(const TransformState &transform) { void Source::render(const LayerDescription& layer_desc, const BucketDescription &bucket_desc) { if (!enabled) return; + gl::group group(std::string("layer: ") + layer_desc.name); for (const Tile& tile : tiles) { if (tile.data && tile.data->state == TileData::State::parsed) { painter.renderTileLayer(tile, layer_desc); diff --git a/src/style/layer_description.cpp b/src/style/layer_description.cpp index d4b8b7d67..53c6230cb 100644 --- a/src/style/layer_description.cpp +++ b/src/style/layer_description.cpp @@ -2,6 +2,16 @@ #include +using namespace llmr; + +size_t LayerDescription::size() const { + size_t count = 1; + for (const LayerDescription &layer : child_layer) { + count += layer.size(); + } + return count; +} + std::ostream& llmr::operator<<(std::ostream& os, const LayerDescription& layer) { os << "Structure: " << layer.name << std::endl; os << " - bucket_name: " << layer.bucket_name << std::endl; diff --git a/src/style/style.cpp b/src/style/style.cpp index 9c766419b..18fe0f416 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -123,6 +123,14 @@ void Style::cascade(float z) { } } +size_t Style::layerCount() const { + size_t count = 0; + for (const LayerDescription &layer : layers) { + count += layer.size(); + } + return count; +} + void Style::loadJSON(const uint8_t *const data, size_t bytes) { rapidjson::Document doc; From b6d6c7ee3058c2dd18919bc65d8f83ff97965ab5 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 12 May 2014 14:07:20 -0400 Subject: [PATCH 13/21] re-add iOS Xcode instructions --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fd15f0197..c2af76b08 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ Because `libpng` isn't included in the iOS SDK, you will need to build a cross-a yourself. Run `./setup-libraries.sh`, which is derived from Mapnik's cross-architecture build scripts. This will also run `./configure`. +Then, `make iproj` to create and open an Xcode project with an iOS-specific view controller housing and basic gesture support. + Target devices: iPhone 4 and above (4S, 5, 5c, 5s) and iPad 2 and above (3, 4, mini and/or retina). ## Ubuntu From 6538f580ce52259ff837b85622f0553d0d0c5885 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Mon, 12 May 2014 13:40:58 -0700 Subject: [PATCH 14/21] ensure setup-libraries.sh checks for valid pkg-config --- setup-libraries.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/setup-libraries.sh b/setup-libraries.sh index cd5dd4385..d43461111 100755 --- a/setup-libraries.sh +++ b/setup-libraries.sh @@ -3,7 +3,7 @@ set -e -u UNAME=$(uname -s); -MISSING_DEPS=false +MISSING_DEPS="" function ensure_dep { if [[ ! `which $1` ]]; then @@ -15,6 +15,7 @@ ensure_dep aclocal ensure_dep cmake ensure_dep automake ensure_dep autoconf +ensure_dep pkg-config if [ ${UNAME} = 'Darwin' ]; then ensure_dep makedepend ensure_dep glibtool @@ -23,11 +24,13 @@ elif [ ${UNAME} = 'Linux' ]; then ensure_dep gccmakedep fi -if [[ $MISSING_DEPS != false ]]; then +if [[ $MISSING_DEPS != "" ]]; then if [ ${UNAME} = 'Darwin' ]; then - echo 'Please run "brew install autoconf automake libtool makedepend cmake" and then re-run ./setup-libraries.sh' + echo "Missing build deps: ${MISSING_DEPS}" + echo 'Please run "brew install autoconf automake libtool makedepend cmake pkg-config" and then re-run ./setup-libraries.sh' elif [ ${UNAME} = 'Linux' ]; then - echo 'Please run "sudo apt-get install automake libtool xutils-dev cmake" and then re-run ./setup-libraries.sh' + echo "Missing build deps: ${MISSING_DEPS}" + echo 'Please run "sudo apt-get install automake libtool xutils-dev cmake pkg-config" and then re-run ./setup-libraries.sh' fi exit 1 fi From bc29ed5a9c5bd7703f861b4839235a55f6b8f343 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 12 May 2014 14:00:10 -0700 Subject: [PATCH 15/21] refs #166: fix for std::stoll et al. not handling strings --- include/llmr/style/value.hpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/include/llmr/style/value.hpp b/include/llmr/style/value.hpp index 0dc53248b..40be29f42 100644 --- a/include/llmr/style/value.hpp +++ b/include/llmr/style/value.hpp @@ -34,21 +34,51 @@ template <> struct string_to_number { template <> struct string_to_number { double operator()(std::string const &str) const { int val = string_to_bool(str); - return val < 0 ? std::stod(str) : val; + if (val < 0) { + try { + double b = std::stod(str); + return b; + } catch (std::invalid_argument const& ex) { + return false; + } + } else { + return val; + } + return false; } }; template <> struct string_to_number { int64_t operator()(std::string const &str) const { int val = string_to_bool(str); - return val < 0 ? std::stoll(str) : val; + if (val < 0) { + try { + long long b = std::stoll(str); + return b; + } catch (std::invalid_argument const& ex) { + return false; + } + } else { + return val; + } + return false; } }; template <> struct string_to_number { uint64_t operator()(std::string const &str) const { int val = string_to_bool(str); - return val < 0 ? std::stoull(str) : val; + if (val < 0) { + try { + unsigned long long b = std::stoull(str); + return b; + } catch (std::invalid_argument const& ex) { + return false; + } + } else { + return val; + } + return false; } }; From 654520ea343e0c8572bbf08be9963be098f4fbc8 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 12 May 2014 17:14:28 -0700 Subject: [PATCH 16/21] Revert "refs #166: fix for std::stoll et al. not handling strings" This reverts commit bc29ed5a9c5bd7703f861b4839235a55f6b8f343. --- include/llmr/style/value.hpp | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/include/llmr/style/value.hpp b/include/llmr/style/value.hpp index 40be29f42..0dc53248b 100644 --- a/include/llmr/style/value.hpp +++ b/include/llmr/style/value.hpp @@ -34,51 +34,21 @@ template <> struct string_to_number { template <> struct string_to_number { double operator()(std::string const &str) const { int val = string_to_bool(str); - if (val < 0) { - try { - double b = std::stod(str); - return b; - } catch (std::invalid_argument const& ex) { - return false; - } - } else { - return val; - } - return false; + return val < 0 ? std::stod(str) : val; } }; template <> struct string_to_number { int64_t operator()(std::string const &str) const { int val = string_to_bool(str); - if (val < 0) { - try { - long long b = std::stoll(str); - return b; - } catch (std::invalid_argument const& ex) { - return false; - } - } else { - return val; - } - return false; + return val < 0 ? std::stoll(str) : val; } }; template <> struct string_to_number { uint64_t operator()(std::string const &str) const { int val = string_to_bool(str); - if (val < 0) { - try { - unsigned long long b = std::stoull(str); - return b; - } catch (std::invalid_argument const& ex) { - return false; - } - } else { - return val; - } - return false; + return val < 0 ? std::stoull(str) : val; } }; From d8fa9aa322d00e796402cf99a35bdeae9b193c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 12:01:29 +0200 Subject: [PATCH 17/21] move weak_ptr list of tile_data objects to a map for faster lookups --- include/llmr/map/source.hpp | 3 ++- include/llmr/util/std.hpp | 17 +++++++++++++++++ src/map/source.cpp | 16 +++++++--------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index 7d47d61b7..da2d87ae8 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace llmr { @@ -69,7 +70,7 @@ private: const int32_t max_zoom; std::forward_list tiles; - std::forward_list> tile_data; + std::map> tile_data; }; } diff --git a/include/llmr/util/std.hpp b/include/llmr/util/std.hpp index 4ef28d5f8..c09aa5ff6 100644 --- a/include/llmr/util/std.hpp +++ b/include/llmr/util/std.hpp @@ -11,6 +11,23 @@ template return ::std::unique_ptr(new T(::std::forward(args)...)); } + +template +void erase_if(Container &container, ForwardIterator it, const ForwardIterator end, Predicate pred) { + while (it != end) { + if (pred(*it)) { + container.erase(it++); + } else { + ++it; + } + } +} + +template +void erase_if(Container &container, Predicate pred) { + erase_if(container, container.begin(), container.end(), pred); +} + } #endif diff --git a/src/map/source.cpp b/src/map/source.cpp index 384cb0a62..5571f74c7 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -110,13 +111,10 @@ TileData::State Source::addTile(const Tile::ID& id) { // Try to find the associated TileData object. const Tile::ID normalized_id = id.normalized(); - auto it = std::find_if(tile_data.begin(), tile_data.end(), [normalized_id](const std::weak_ptr& tile_data) { - return !tile_data.expired() && tile_data.lock()->id == normalized_id; - }); - + 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->lock(); + new_tile.data = it->second.lock(); } if (new_tile.data && new_tile.data->state == TileData::State::obsolete) { @@ -138,7 +136,7 @@ TileData::State Source::addTile(const Tile::ID& id) { } new_tile.data->request(); - tile_data.push_front(new_tile.data); + tile_data.emplace(new_tile.data->id, new_tile.data); } return new_tile.data->state; @@ -269,9 +267,9 @@ bool Source::updateTiles() { return a.id.z < b.id.z; }); - // Remove all the expired pointers from the list. - tile_data.remove_if([&retain_data](const std::weak_ptr& tile_data) { - const std::shared_ptr tile = tile_data.lock(); + // Remove all the expired pointers from the set. + std::erase_if(tile_data, [&retain_data](const std::pair>& pair) { + const std::shared_ptr tile = pair.second.lock(); if (!tile) { return true; } From ab71db336c43e8244a099d7a9c665db4e279f9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 12:20:59 +0200 Subject: [PATCH 18/21] move tiles to std::map for faster lookups --- include/llmr/map/source.hpp | 2 +- src/map/source.cpp | 43 ++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index da2d87ae8..40b3bf4cf 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -69,7 +69,7 @@ private: const int32_t min_zoom; const int32_t max_zoom; - std::forward_list tiles; + std::map> tiles; std::map> tile_data; }; diff --git a/src/map/source.cpp b/src/map/source.cpp index 5571f74c7..a5985668f 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -34,7 +34,8 @@ bool Source::update() { } void Source::updateClipIDs(const std::map &mapping) { - std::for_each(tiles.begin(), tiles.end(), [&mapping](Tile &tile) { + std::for_each(tiles.begin(), tiles.end(), [&mapping](std::pair> &pair) { + Tile &tile = *pair.second; auto it = mapping.find(tile.id); if (it != mapping.end()) { tile.clip = it->second; @@ -48,7 +49,8 @@ size_t Source::prepareRender(const TransformState &transform) { if (!enabled) return 0; size_t masks = 0; - for (Tile& tile : tiles) { + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; gl::group group(util::sprintf<32>("mask %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); transform.matrixFor(tile.matrix, tile.id); matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); @@ -63,7 +65,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription if (!enabled) return; gl::group group(std::string("layer: ") + layer_desc.name); - for (const Tile& tile : tiles) { + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; if (tile.data && tile.data->state == TileData::State::parsed) { painter.renderTileLayer(tile, layer_desc); } @@ -73,7 +76,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription void Source::finishRender() { if (!enabled) return; - for (const Tile& tile : tiles) { + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; painter.renderTileDebug(tile); } } @@ -81,14 +85,18 @@ void Source::finishRender() { std::forward_list Source::getIDs() const { std::forward_list ptrs; - std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const Tile &tile) { + + std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const std::pair> &pair) { + Tile &tile = *pair.second; return tile.id; }); return ptrs; } TileData::State Source::hasTile(const Tile::ID& id) { - for (const Tile& tile : tiles) { + auto it = tiles.find(id); + if (it != tiles.end()) { + Tile &tile = *it->second; if (tile.id == id && tile.data) { return tile.data->state; } @@ -104,8 +112,8 @@ TileData::State Source::addTile(const Tile::ID& id) { return state; } - tiles.emplace_front(id); - Tile& new_tile = tiles.front(); + auto pos = tiles.emplace(id, std::make_unique(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. @@ -249,31 +257,26 @@ bool Source::updateTiles() { // Remove tiles that we definitely don't need, i.e. tiles that are not on // the required list. - std::forward_list retain_data; - tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) { + std::set retain_data; + std::erase_if(tiles, [&retain, &retain_data, &changed](std::pair> &pair) { + Tile &tile = *pair.second; bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end(); if (obsolete) { changed = true; } else { - retain_data.push_front(tile.data->id); + retain_data.insert(tile.data->id); } return obsolete; }); - // Sort tiles by zoom level, front to back. - // We're painting front-to-back, so we want to draw more detailed tiles first - // before filling in other parts with lower zoom levels. - tiles.sort([](const Tile & a, const Tile & b) { - return a.id.z < b.id.z; - }); - // Remove all the expired pointers from the set. - std::erase_if(tile_data, [&retain_data](const std::pair>& pair) { + std::erase_if(tile_data, [&retain_data](std::pair> &pair) { const std::shared_ptr tile = pair.second.lock(); if (!tile) { return true; } - bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end(); + + bool obsolete = retain_data.find(tile->id) == retain_data.end(); if (obsolete) { tile->cancel(); return true; From 72c0410a4d040a25b7500bd277095d5e25897370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 13:16:47 +0200 Subject: [PATCH 19/21] save/restore state on ios --- ios/MBXAppDelegate.m | 11 +++++++++++ ios/MBXViewController.h | 3 +++ ios/MBXViewController.mm | 32 ++++++++++++++++++++++---------- ios/llmr-app.gyp | 2 ++ 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/ios/MBXAppDelegate.m b/ios/MBXAppDelegate.m index 4b860a361..aa048ca67 100644 --- a/ios/MBXAppDelegate.m +++ b/ios/MBXAppDelegate.m @@ -21,4 +21,15 @@ return YES; } +- (void)applicationDidEnterBackground:(UIApplication *)application +{ + [(MBXViewController *)self.window.rootViewController saveState]; +} + +- (void)applicationWillEnterForeground:(UIApplication *)application +{ + [(MBXViewController *)self.window.rootViewController restoreState]; +} + + @end \ No newline at end of file diff --git a/ios/MBXViewController.h b/ios/MBXViewController.h index 924d3af60..64021c2de 100644 --- a/ios/MBXViewController.h +++ b/ios/MBXViewController.h @@ -2,4 +2,7 @@ @interface MBXViewController : UIViewController +- (void)saveState; +- (void)restoreState; + @end diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm index dcd2271dc..a8e710f31 100644 --- a/ios/MBXViewController.mm +++ b/ios/MBXViewController.mm @@ -77,11 +77,29 @@ MBXViewController *viewController = nullptr; // Load settings settings = new llmr::Settings_NSUserDefaults(); - map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom); - map->setAngle(settings->angle); - map->setDebug(settings->debug); + [self restoreState]; } +- (void)saveState +{ + if (map && settings) + { + map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom); + settings->angle = map->getAngle(); + settings->debug = map->getDebug(); + settings->save(); + } +} + +- (void)restoreState +{ + if (map && settings) { + settings->load(); + map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom); + map->setAngle(settings->angle); + map->setDebug(settings->debug); + } +} - (void)setupInteraction { @@ -237,13 +255,7 @@ MBXViewController *viewController = nullptr; if (settings) { // Save settings - if (map) - { - map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom); - settings->angle = map->getAngle(); - settings->debug = map->getDebug(); - settings->save(); - } + [self saveState]; delete settings; settings = nullptr; } diff --git a/ios/llmr-app.gyp b/ios/llmr-app.gyp index e40ffe9a1..82b9b8963 100644 --- a/ios/llmr-app.gyp +++ b/ios/llmr-app.gyp @@ -10,7 +10,9 @@ "type": "executable", "sources": [ "./main.m", + "./MBXAppDelegate.h", "./MBXAppDelegate.m", + "./MBXViewController.h", "./MBXViewController.mm", "../common/settings_nsuserdefaults.hpp", "../common/settings_nsuserdefaults.mm", From b096373b7150e6b96782d10f2a0441f4f826030d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 15:15:54 +0200 Subject: [PATCH 20/21] abort rendering early if the style is disabled refs #184 --- include/llmr/style/properties.hpp | 2 ++ src/map/map.cpp | 22 ++++++++++++++++++++++ src/style/style.cpp | 1 + 3 files changed, 25 insertions(+) diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index e68239025..9a6d6d86d 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -127,6 +127,7 @@ struct TextClass { FunctionProperty size = 12.0f; FunctionProperty rotate = 0.0f; FunctionProperty alwaysVisible = false; + FunctionProperty opacity = 1; }; struct TextProperties { @@ -138,6 +139,7 @@ struct TextProperties { float size = 12.0f; float rotate = 0.0f; bool alwaysVisible = false; + float opacity = 1.0; }; struct BackgroundClass { diff --git a/src/map/map.cpp b/src/map/map.cpp index 6775f4721..1d797baaf 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -484,6 +484,16 @@ void Map::renderLayers(const std::vector& layers, RenderPass p } } +template +bool is_invisible(const Styles &styles, const LayerDescription &layer_desc) { + auto it = styles.find(layer_desc.name); + if (it == styles.end()) return true; + const auto &properties = it->second; + if (!properties.enabled) return true; + if (properties.opacity <= 0) return true; + return false; +} + void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) { if (layer_desc.child_layer.size()) { gl::group group(std::string("group: ") + layer_desc.name); @@ -501,13 +511,25 @@ void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) { // Abort early if we can already deduce from the bucket type that // we're not going to render anything anyway during this pass. switch (bucket_desc.type) { + case BucketType::Fill: + if (is_invisible(style.computed.fills, layer_desc)) return; + break; case BucketType::Line: + if (pass == Opaque) return; + if (is_invisible(style.computed.lines, layer_desc)) return; + break; case BucketType::Point: + if (pass == Opaque) return; + if (is_invisible(style.computed.points, layer_desc)) return; + break; case BucketType::Text: if (pass == Opaque) return; + if (is_invisible(style.computed.texts, layer_desc)) return; break; case BucketType::Raster: if (pass == Translucent) return; + if (is_invisible(style.computed.lines, layer_desc)) return; + break; default: break; } diff --git a/src/style/style.cpp b/src/style/style.cpp index 18fe0f416..a49311030 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -103,6 +103,7 @@ void Style::cascade(float z) { text.haloRadius = layer.haloRadius.evaluate(z); text.rotate = layer.rotate.evaluate(z); text.alwaysVisible = layer.alwaysVisible.evaluate(z); + text.opacity = layer.opacity.evaluate(z); } // Cascade raster classes From 7bfcde40254ba1d208783e8dbc8fa8ab15da9a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 15:37:45 +0200 Subject: [PATCH 21/21] split up painter into multiple files --- src/renderer/painter.cpp | 581 +----------------------------- src/renderer/painter_clipping.cpp | 42 +++ src/renderer/painter_debug.cpp | 97 +++++ src/renderer/painter_fill.cpp | 143 ++++++++ src/renderer/painter_line.cpp | 92 +++++ src/renderer/painter_point.cpp | 80 ++++ src/renderer/painter_raster.cpp | 28 ++ src/renderer/painter_text.cpp | 114 ++++++ src/renderer/text_bucket.cpp | 1 - 9 files changed, 601 insertions(+), 577 deletions(-) create mode 100644 src/renderer/painter_clipping.cpp create mode 100644 src/renderer/painter_debug.cpp create mode 100644 src/renderer/painter_fill.cpp create mode 100644 src/renderer/painter_line.cpp create mode 100644 src/renderer/painter_point.cpp create mode 100644 src/renderer/painter_raster.cpp create mode 100644 src/renderer/painter_text.cpp diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 9f1372674..6adbc71c4 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -1,29 +1,16 @@ -#include -#include - - #include -#include - -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include + +#include #include #include #include #include +#include +#include + using namespace llmr; #define BUFFER_OFFSET(i) ((char *)nullptr + (i)) @@ -130,42 +117,6 @@ void Painter::changeMatrix() { matrix::multiply(nativeMatrix, projMatrix, nativeMatrix); } -void Painter::prepareClippingMask() { - gl::start_group("clipping masks"); - useProgram(plainShader->program); - glDisable(GL_DEPTH_TEST); - depthMask(false); - glDepthRange(1.0f, 1.0f); - - coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); -} - -void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { - plainShader->setMatrix(matrix); - - GLint id = clip.mask.to_ulong(); - GLuint mask = clipMask[clip.length]; - glStencilFunc(GL_ALWAYS, id, mask); - glStencilMask(0xFF); - - Color background = map.getStyle().computed.background.color; - background[0] *= map.getStyle().computed.background.opacity; - background[1] *= map.getStyle().computed.background.opacity; - background[2] *= map.getStyle().computed.background.opacity; - background[3] *= map.getStyle().computed.background.opacity; - - plainShader->setColor(background); - - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); -} - -void Painter::finishClippingMask() { - glEnable(GL_DEPTH_TEST); - depthMask(true); - glStencilMask(0x0); - gl::end_group(); -} - void Painter::clear() { gl::group group("clear"); glStencilMask(0xFF); @@ -219,16 +170,6 @@ void Painter::renderTileLayer(const Tile& tile, const LayerDescription &layer_de } } -void Painter::renderTileDebug(const Tile& tile) { - gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); - assert(tile.data); - if (debug) { - prepareTile(tile); - renderDebugText(tile.data->debugBucket); - renderDebugFrame(); - } -} - void Painter::translateLayer(std::array translation, bool reverse) { if (translation[0] || translation[1]) { if (reverse) { @@ -243,483 +184,6 @@ void Painter::translateLayer(std::array translation, bool reverse) { } } -void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { - if (pass == Translucent) return; - - auto raster_properties = map.getStyle().computed.rasters; - auto raster_properties_it = raster_properties.find(layer_name); - if (raster_properties_it == raster_properties.end()) return; - - const RasterProperties& properties = raster_properties_it->second; - if (!properties.enabled) return; - - depthMask(false); - - useProgram(rasterShader->program); - rasterShader->setMatrix(matrix); - rasterShader->setOpacity(1); - // rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); - - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray); - - depthMask(true); -} - -void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { - // Abort early. - if (!bucket.hasData()) return; - - auto fill_properties = map.getStyle().computed.fills; - auto fill_properties_it = fill_properties.find(layer_name); - if (fill_properties_it == fill_properties.end()) return; - - const FillProperties& properties = fill_properties_it->second; - if (!properties.enabled) return; - - 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; - stroke_color[0] *= properties.opacity; - stroke_color[1] *= properties.opacity; - stroke_color[2] *= properties.opacity; - stroke_color[3] *= properties.opacity; - - bool outline = properties.antialias && properties.stroke_color != properties.fill_color; - bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color; - if (fringeline) { - outline = true; - stroke_color = fill_color; - } - - translateLayer(properties.translate); - - // Because we're drawing top-to-bottom, and we update the stencil mask - // below, we have to draw the outline first (!) - if (outline && pass == Translucent) { - useProgram(outlineShader->program); - outlineShader->setMatrix(matrix); - lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! - - outlineShader->setColor(stroke_color); - - // Draw the entire line - outlineShader->setWorld({{ - static_cast(map.getState().getFramebufferWidth()), - static_cast(map.getState().getFramebufferHeight()) - }}); - glDepthRange(strata, 1.0f); - bucket.drawVertices(*outlineShader); - } else if (fringeline) { - // // We're only drawing to the first seven bits (== support a maximum of - // // 127 overlapping polygons in one place before we get rendering errors). - // glStencilMask(0x3F); - // glClear(GL_STENCIL_BUFFER_BIT); - - // // Draw front facing triangles. Wherever the 0x80 bit is 1, we are - // // increasing the lower 7 bits by one if the triangle is a front-facing - // // triangle. This means that all visible polygons should be in CCW - // // orientation, while all holes (see below) are in CW orientation. - // glStencilFunc(GL_EQUAL, 0x80, 0x80); - - // // When we do a nonzero fill, we count the number of times a pixel is - // // covered by a counterclockwise polygon, and subtract the number of - // // times it is "uncovered" by a clockwise polygon. - // glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP); - } - - if ((fill_color[3] >= 1.0f) == (pass == Opaque)) { - auto &sprite = map.getStyle().sprite; - if (properties.image.size() && sprite && sprite->isLoaded()) { - // Draw texture fill - ImagePosition imagePos = sprite->getPosition(properties.image, true); - - float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z); - float mix = fmod(map.getState().getZoom(), 1.0); - - std::array imageSize = {{ - imagePos.size.x * factor, - imagePos.size.y *factor - } - }; - - std::array offset = {{ - (float)fmod(id.x * 4096, imageSize[0]), - (float)fmod(id.y * 4096, imageSize[1]) - } - }; - - useProgram(patternShader->program); - patternShader->setMatrix(matrix); - patternShader->setOffset(offset); - patternShader->setPatternSize(imageSize); - patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); - patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }}); - patternShader->setColor(fill_color); - patternShader->setMix(mix); - sprite->raster.bind(true); - - // Draw the actual triangles into the color & stencil buffer. - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawElements(*patternShader); - } else { - // 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->setMatrix(matrix); - plainShader->setColor(fill_color); - - // Draw the actual triangles into the color & stencil buffer. - glDepthRange(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 == Translucent) { - useProgram(outlineShader->program); - outlineShader->setMatrix(matrix); - lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! - - outlineShader->setColor(fill_color); - - // Draw the entire line - outlineShader->setWorld({{ - static_cast(map.getState().getFramebufferWidth()), - static_cast(map.getState().getFramebufferHeight()) - }}); - - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawVertices(*outlineShader); - } - - translateLayer(properties.translate, true); -} - -void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { - // Abort early. - if (pass == Opaque) return; - if (!bucket.hasData()) return; - - auto line_properties = map.getStyle().computed.lines; - auto line_properties_it = line_properties.find(layer_name); - if (line_properties_it == line_properties.end()) return; - - const LineProperties& properties = line_properties_it->second; - if (!properties.enabled) return; - - float width = properties.width; - float offset = properties.offset / 2; - - // These are the radii of the line. We are limiting it to 16, which will result - // in a point size of 64 on retina. - float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f); - float outset = fmin(offset + width / 2 + 0.5, 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]; - - translateLayer(properties.translate); - - glDepthRange(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->setMatrix(matrix); - linejoinShader->setColor(color); - linejoinShader->setWorld({{ - map.getState().getFramebufferWidth() * 0.5f, - map.getState().getFramebufferHeight() * 0.5f - } - }); - linejoinShader->setLineWidth({{ - ((outset - 0.25f) * map.getState().getPixelRatio()), - ((inset - 0.25f) * map.getState().getPixelRatio()) - } - }); - - float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0); -#if defined(GL_ES_VERSION_2_0) - linejoinShader->setSize(pointSize); -#else - glPointSize(pointSize); -#endif - bucket.drawPoints(*linejoinShader); - } - - // var imagePos = properties.image && imageSprite.getPosition(properties.image); - bool imagePos = false; - if (imagePos) { - // var factor = 8 / Math.pow(2, painter.transform.zoom - params.z); - - // imageSprite.bind(gl, true); - - // //factor = Math.pow(2, 4 - painter.transform.zoom + params.z); - // gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix); - // shader = painter.linepatternShader; - // glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]); - // glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl); - // glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br); - // glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0); - - } else { - useProgram(lineShader->program); - lineShader->setMatrix(matrix); - lineShader->setExtrudeMatrix(extrudeMatrix); - lineShader->setDashArray({{ dash_length, dash_gap }}); - lineShader->setLineWidth({{ outset, inset }}); - lineShader->setRatio(map.getState().getPixelRatio()); - lineShader->setColor(color); - bucket.drawLines(*lineShader); - } - - translateLayer(properties.translate, true); -} - -void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) { - // Abort early. - if (!bucket.hasData()) return; - if (pass == Opaque) return; - - auto point_properties = map.getStyle().computed.points; - auto point_properties_it = point_properties.find(layer_name); - if (point_properties_it == point_properties.end()) return; - - const PointProperties& properties = point_properties_it->second; - if (!properties.enabled) return; - - translateLayer(properties.translate); - - Color color = properties.color; - color[0] *= properties.opacity; - color[1] *= properties.opacity; - color[2] *= properties.opacity; - color[3] *= properties.opacity; - - auto &sprite = map.getStyle().sprite; - ImagePosition imagePos; - - if (properties.image.length() && sprite && sprite->isLoaded()) { - std::string sized_image = properties.image; - if (properties.size) { - sized_image.append("-"); - sized_image.append(std::to_string(static_cast(std::round(properties.size)))); - } - imagePos = sprite->getPosition(sized_image, false); - } - - if (!imagePos.size) { - useProgram(dotShader->program); - dotShader->setMatrix(matrix); - dotShader->setColor(color); - - const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio(); -#if defined(GL_ES_VERSION_2_0) - dotShader->setSize(pointSize); -#else - glPointSize(pointSize); - glEnable(GL_POINT_SPRITE); -#endif - dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize); - - glDepthRange(strata, 1.0f); - bucket.drawPoints(*dotShader); - } else { - useProgram(pointShader->program); - pointShader->setMatrix(matrix); - pointShader->setColor(color); - - pointShader->setImage(0); - pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); - pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }}); - - sprite->raster.bind(map.getState().isChanging()); - - const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio(); -#if defined(GL_ES_VERSION_2_0) - pointShader->setSize(pointSize); -#else - glPointSize(pointSize); - glEnable(GL_POINT_SPRITE); -#endif - - glDepthRange(strata, 1.0f); - bucket.drawPoints(*pointShader); - } - - translateLayer(properties.translate, true); -} - -void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { - // Abort early. - if (pass == Opaque) return; - if (!bucket.hasData()) return; - - auto text_properties = map.getStyle().computed.texts; - auto text_properties_it = text_properties.find(layer_name); - if (text_properties_it == text_properties.end()) return; - - const TextProperties& properties = text_properties_it->second; - if (!properties.enabled) return; - - translateLayer(properties.translate); - - mat4 exMatrix; - matrix::copy(exMatrix, projMatrix); - if (bucket.geom_desc.path == TextPathType::Curve) { - matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle()); - } - - const float rotate = properties.rotate; - if (rotate != 0.0f) { - matrix::rotate_z(exMatrix, exMatrix, rotate); - } - - // If layerStyle.size > bucket.info.fontSize then labels may collide - float fontSize = fmin(properties.size, bucket.geom_desc.font_size); - matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f); - - useProgram(textShader->program); - textShader->setMatrix(matrix); - textShader->setExtrudeMatrix(exMatrix); - - map.getGlyphAtlas().bind(); - textShader->setTextureSize({{static_cast(map.getGlyphAtlas().width), - static_cast(map.getGlyphAtlas().height)}}); - - textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio()); - - // Convert the -pi..pi to an int8 range. - float angle = round((map.getState().getAngle() + rotate) / M_PI * 128); - - // adjust min/max zooms for variable font sies - float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2); - - textShader->setAngle((int32_t)(angle + 256) % 256); - textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0); - textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level - - // Label fading - const time duration = 300_milliseconds; - const time currentTime = util::now(); - - std::deque &history = frameHistory.history; - - // Remove frames until only one is outside the duration, or until there are only three - while (history.size() > 3 && history[1].timestamp + duration < currentTime) { - history.pop_front(); - } - - if (history[1].timestamp + duration < currentTime) { - history[0].z = history[1].z; - } - - size_t frameLen = history.size(); - assert("there should never be less than three frames in the history" && frameLen >= 3); - - // 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 = fmin(startingZ, endingZ); - float highZ = 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.timestamp - history[1].timestamp; - if (timeDiff > duration) timeDiff = 1; - float fadedist = zoomDiff / (timeDiff / duration); - - if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n"); - - // 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.timestamp) / duration * fadedist; - - textShader->setFadeDist(fadedist * 10); - textShader->setMinFadeZoom(floor(lowZ * 10)); - textShader->setMaxFadeZoom(floor(highZ * 10)); - textShader->setFadeZoom((map.getState().getZoom() + bump) * 10); - - // We're drawing in the translucent pass which is bottom-to-top, so we need - // to draw the halo first. - if (properties.halo[3] > 0.0f) { - textShader->setColor(properties.halo); - textShader->setBuffer(properties.haloRadius); - glDepthRange(strata, 1.0f); - bucket.drawGlyphs(*textShader); - } - - // Then, we draw the text over the halo - textShader->setColor(properties.color); - textShader->setBuffer((256.0f - 64.0f) / 256.0f); - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawGlyphs(*textShader); - - translateLayer(properties.translate, true); -} - -void Painter::renderDebugText(DebugBucket& bucket) { - gl::group group("debug text"); - - glDisable(GL_DEPTH_TEST); - - useProgram(plainShader->program); - plainShader->setMatrix(matrix); - - // Draw white outline - plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); - lineWidth(4.0f * map.getState().getPixelRatio()); - bucket.drawLines(*plainShader); - -#ifndef GL_ES_VERSION_2_0 - // Draw line "end caps" - glPointSize(2); - bucket.drawPoints(*plainShader); -#endif - - // Draw black text. - plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); - lineWidth(2.0f * map.getState().getPixelRatio()); - bucket.drawLines(*plainShader); - - glEnable(GL_DEPTH_TEST); -} - -void Painter::renderDebugFrame() { - 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->setMatrix(matrix); - - // draw tile outline - tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0)); - plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f); - lineWidth(4.0f * map.getState().getPixelRatio()); - glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index()); - - glEnable(GL_DEPTH_TEST); -} - void Painter::renderMatte() { gl::group group("matte"); glDisable(GL_DEPTH_TEST); @@ -738,38 +202,3 @@ void Painter::renderMatte() { glEnable(GL_DEPTH_TEST); } - - -void Painter::renderDebugText(const std::vector &strings) { - gl::group group("debug text"); - - glDisable(GL_DEPTH_TEST); - glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); - - useProgram(plainShader->program); - plainShader->setMatrix(nativeMatrix); - - DebugFontBuffer debugFontBuffer; - int line = 25; - for (const std::string &str : strings) { - debugFontBuffer.addText(str.c_str(), 10, line, 0.75); - line += 20; - } - - // draw debug info - VertexArrayObject debugFontArray; - debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0)); - plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); - lineWidth(4.0f * map.getState().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->setColor(0.0f, 0.0f, 0.0f, 1.0f); - lineWidth(2.0f * map.getState().getPixelRatio()); - glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); - - - glEnable(GL_DEPTH_TEST); -} \ No newline at end of file diff --git a/src/renderer/painter_clipping.cpp b/src/renderer/painter_clipping.cpp new file mode 100644 index 000000000..140555237 --- /dev/null +++ b/src/renderer/painter_clipping.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::prepareClippingMask() { + gl::start_group("clipping masks"); + useProgram(plainShader->program); + glDisable(GL_DEPTH_TEST); + depthMask(false); + glDepthRange(1.0f, 1.0f); + + coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); +} + +void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { + plainShader->setMatrix(matrix); + + GLint id = clip.mask.to_ulong(); + GLuint mask = clipMask[clip.length]; + glStencilFunc(GL_ALWAYS, id, mask); + glStencilMask(0xFF); + + Color background = map.getStyle().computed.background.color; + background[0] *= map.getStyle().computed.background.opacity; + background[1] *= map.getStyle().computed.background.opacity; + background[2] *= map.getStyle().computed.background.opacity; + background[3] *= map.getStyle().computed.background.opacity; + + plainShader->setColor(background); + + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); +} + +void Painter::finishClippingMask() { + glEnable(GL_DEPTH_TEST); + depthMask(true); + glStencilMask(0x0); + gl::end_group(); +} diff --git a/src/renderer/painter_debug.cpp b/src/renderer/painter_debug.cpp new file mode 100644 index 000000000..23bf32fda --- /dev/null +++ b/src/renderer/painter_debug.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::renderTileDebug(const Tile& tile) { + gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); + assert(tile.data); + if (debug) { + prepareTile(tile); + renderDebugText(tile.data->debugBucket); + renderDebugFrame(); + } +} + +void Painter::renderDebugText(DebugBucket& bucket) { + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + + useProgram(plainShader->program); + plainShader->setMatrix(matrix); + + // Draw white outline + plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); + lineWidth(4.0f * map.getState().getPixelRatio()); + bucket.drawLines(*plainShader); + +#ifndef GL_ES_VERSION_2_0 + // Draw line "end caps" + glPointSize(2); + bucket.drawPoints(*plainShader); +#endif + + // Draw black text. + plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); + lineWidth(2.0f * map.getState().getPixelRatio()); + bucket.drawLines(*plainShader); + + glEnable(GL_DEPTH_TEST); +} + +void Painter::renderDebugFrame() { + 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->setMatrix(matrix); + + // draw tile outline + tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0)); + plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f); + lineWidth(4.0f * map.getState().getPixelRatio()); + glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index()); + + glEnable(GL_DEPTH_TEST); +} + +void Painter::renderDebugText(const std::vector &strings) { + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); + + useProgram(plainShader->program); + plainShader->setMatrix(nativeMatrix); + + DebugFontBuffer debugFontBuffer; + int line = 25; + for (const std::string &str : strings) { + debugFontBuffer.addText(str.c_str(), 10, line, 0.75); + line += 20; + } + + // draw debug info + VertexArrayObject debugFontArray; + debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0)); + plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); + lineWidth(4.0f * map.getState().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->setColor(0.0f, 0.0f, 0.0f, 1.0f); + lineWidth(2.0f * map.getState().getPixelRatio()); + glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); + + + glEnable(GL_DEPTH_TEST); +} diff --git a/src/renderer/painter_fill.cpp b/src/renderer/painter_fill.cpp new file mode 100644 index 000000000..921947857 --- /dev/null +++ b/src/renderer/painter_fill.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { + // Abort early. + if (!bucket.hasData()) return; + + auto fill_properties = map.getStyle().computed.fills; + auto fill_properties_it = fill_properties.find(layer_name); + if (fill_properties_it == fill_properties.end()) return; + + const FillProperties& properties = fill_properties_it->second; + if (!properties.enabled) return; + + 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; + stroke_color[0] *= properties.opacity; + stroke_color[1] *= properties.opacity; + stroke_color[2] *= properties.opacity; + stroke_color[3] *= properties.opacity; + + bool outline = properties.antialias && properties.stroke_color != properties.fill_color; + bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color; + if (fringeline) { + outline = true; + stroke_color = fill_color; + } + + translateLayer(properties.translate); + + // Because we're drawing top-to-bottom, and we update the stencil mask + // below, we have to draw the outline first (!) + if (outline && pass == Translucent) { + useProgram(outlineShader->program); + outlineShader->setMatrix(matrix); + lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! + + outlineShader->setColor(stroke_color); + + // Draw the entire line + outlineShader->setWorld({{ + static_cast(map.getState().getFramebufferWidth()), + static_cast(map.getState().getFramebufferHeight()) + }}); + glDepthRange(strata, 1.0f); + bucket.drawVertices(*outlineShader); + } else if (fringeline) { + // // We're only drawing to the first seven bits (== support a maximum of + // // 127 overlapping polygons in one place before we get rendering errors). + // glStencilMask(0x3F); + // glClear(GL_STENCIL_BUFFER_BIT); + + // // Draw front facing triangles. Wherever the 0x80 bit is 1, we are + // // increasing the lower 7 bits by one if the triangle is a front-facing + // // triangle. This means that all visible polygons should be in CCW + // // orientation, while all holes (see below) are in CW orientation. + // glStencilFunc(GL_EQUAL, 0x80, 0x80); + + // // When we do a nonzero fill, we count the number of times a pixel is + // // covered by a counterclockwise polygon, and subtract the number of + // // times it is "uncovered" by a clockwise polygon. + // glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP); + } + + if ((fill_color[3] >= 1.0f) == (pass == Opaque)) { + auto &sprite = map.getStyle().sprite; + if (properties.image.size() && sprite && sprite->isLoaded()) { + // Draw texture fill + ImagePosition imagePos = sprite->getPosition(properties.image, true); + + float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z); + float mix = fmod(map.getState().getZoom(), 1.0); + + std::array imageSize = {{ + imagePos.size.x * factor, + imagePos.size.y *factor + } + }; + + std::array offset = {{ + (float)fmod(id.x * 4096, imageSize[0]), + (float)fmod(id.y * 4096, imageSize[1]) + } + }; + + useProgram(patternShader->program); + patternShader->setMatrix(matrix); + patternShader->setOffset(offset); + patternShader->setPatternSize(imageSize); + patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); + patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }}); + patternShader->setColor(fill_color); + patternShader->setMix(mix); + sprite->raster.bind(true); + + // Draw the actual triangles into the color & stencil buffer. + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawElements(*patternShader); + } else { + // 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->setMatrix(matrix); + plainShader->setColor(fill_color); + + // Draw the actual triangles into the color & stencil buffer. + glDepthRange(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 == Translucent) { + useProgram(outlineShader->program); + outlineShader->setMatrix(matrix); + lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! + + outlineShader->setColor(fill_color); + + // Draw the entire line + outlineShader->setWorld({{ + static_cast(map.getState().getFramebufferWidth()), + static_cast(map.getState().getFramebufferHeight()) + }}); + + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawVertices(*outlineShader); + } + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/painter_line.cpp b/src/renderer/painter_line.cpp new file mode 100644 index 000000000..1998e5584 --- /dev/null +++ b/src/renderer/painter_line.cpp @@ -0,0 +1,92 @@ +#include +#include +#include + +using namespace llmr; + +void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { + // Abort early. + if (pass == Opaque) return; + if (!bucket.hasData()) return; + + auto line_properties = map.getStyle().computed.lines; + auto line_properties_it = line_properties.find(layer_name); + if (line_properties_it == line_properties.end()) return; + + const LineProperties& properties = line_properties_it->second; + if (!properties.enabled) return; + + float width = properties.width; + float offset = properties.offset / 2; + + // These are the radii of the line. We are limiting it to 16, which will result + // in a point size of 64 on retina. + float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f); + float outset = fmin(offset + width / 2 + 0.5, 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]; + + translateLayer(properties.translate); + + glDepthRange(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->setMatrix(matrix); + linejoinShader->setColor(color); + linejoinShader->setWorld({{ + map.getState().getFramebufferWidth() * 0.5f, + map.getState().getFramebufferHeight() * 0.5f + } + }); + linejoinShader->setLineWidth({{ + ((outset - 0.25f) * map.getState().getPixelRatio()), + ((inset - 0.25f) * map.getState().getPixelRatio()) + } + }); + + float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0); +#if defined(GL_ES_VERSION_2_0) + linejoinShader->setSize(pointSize); +#else + glPointSize(pointSize); +#endif + bucket.drawPoints(*linejoinShader); + } + + // var imagePos = properties.image && imageSprite.getPosition(properties.image); + bool imagePos = false; + if (imagePos) { + // var factor = 8 / Math.pow(2, painter.transform.zoom - params.z); + + // imageSprite.bind(gl, true); + + // //factor = Math.pow(2, 4 - painter.transform.zoom + params.z); + // gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix); + // shader = painter.linepatternShader; + // glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]); + // glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl); + // glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br); + // glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0); + + } else { + useProgram(lineShader->program); + lineShader->setMatrix(matrix); + lineShader->setExtrudeMatrix(extrudeMatrix); + lineShader->setDashArray({{ dash_length, dash_gap }}); + lineShader->setLineWidth({{ outset, inset }}); + lineShader->setRatio(map.getState().getPixelRatio()); + lineShader->setColor(color); + bucket.drawLines(*lineShader); + } + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/painter_point.cpp b/src/renderer/painter_point.cpp new file mode 100644 index 000000000..d54c66be9 --- /dev/null +++ b/src/renderer/painter_point.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) { + // Abort early. + if (!bucket.hasData()) return; + if (pass == Opaque) return; + + auto point_properties = map.getStyle().computed.points; + auto point_properties_it = point_properties.find(layer_name); + if (point_properties_it == point_properties.end()) return; + + const PointProperties& properties = point_properties_it->second; + if (!properties.enabled) return; + + translateLayer(properties.translate); + + Color color = properties.color; + color[0] *= properties.opacity; + color[1] *= properties.opacity; + color[2] *= properties.opacity; + color[3] *= properties.opacity; + + auto &sprite = map.getStyle().sprite; + ImagePosition imagePos; + + if (properties.image.length() && sprite && sprite->isLoaded()) { + std::string sized_image = properties.image; + if (properties.size) { + sized_image.append("-"); + sized_image.append(std::to_string(static_cast(std::round(properties.size)))); + } + imagePos = sprite->getPosition(sized_image, false); + } + + if (!imagePos.size) { + useProgram(dotShader->program); + dotShader->setMatrix(matrix); + dotShader->setColor(color); + + const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio(); +#if defined(GL_ES_VERSION_2_0) + dotShader->setSize(pointSize); +#else + glPointSize(pointSize); + glEnable(GL_POINT_SPRITE); +#endif + dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize); + + glDepthRange(strata, 1.0f); + bucket.drawPoints(*dotShader); + } else { + useProgram(pointShader->program); + pointShader->setMatrix(matrix); + pointShader->setColor(color); + + pointShader->setImage(0); + pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); + pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }}); + + sprite->raster.bind(map.getState().isChanging()); + + const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio(); +#if defined(GL_ES_VERSION_2_0) + pointShader->setSize(pointSize); +#else + glPointSize(pointSize); + glEnable(GL_POINT_SPRITE); +#endif + + glDepthRange(strata, 1.0f); + bucket.drawPoints(*pointShader); + } + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/painter_raster.cpp b/src/renderer/painter_raster.cpp new file mode 100644 index 000000000..410f1a000 --- /dev/null +++ b/src/renderer/painter_raster.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +using namespace llmr; + +void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { + if (pass == Translucent) return; + + auto raster_properties = map.getStyle().computed.rasters; + auto raster_properties_it = raster_properties.find(layer_name); + if (raster_properties_it == raster_properties.end()) return; + + const RasterProperties& properties = raster_properties_it->second; + if (!properties.enabled) return; + + depthMask(false); + + useProgram(rasterShader->program); + rasterShader->setMatrix(matrix); + rasterShader->setOpacity(1); + // rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); + + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray); + + depthMask(true); +} diff --git a/src/renderer/painter_text.cpp b/src/renderer/painter_text.cpp new file mode 100644 index 000000000..092b74b09 --- /dev/null +++ b/src/renderer/painter_text.cpp @@ -0,0 +1,114 @@ +#include +#include +#include + +using namespace llmr; + +void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { + // Abort early. + if (pass == Opaque) return; + if (!bucket.hasData()) return; + + auto text_properties = map.getStyle().computed.texts; + auto text_properties_it = text_properties.find(layer_name); + if (text_properties_it == text_properties.end()) return; + + const TextProperties& properties = text_properties_it->second; + if (!properties.enabled) return; + + translateLayer(properties.translate); + + mat4 exMatrix; + matrix::copy(exMatrix, projMatrix); + if (bucket.geom_desc.path == TextPathType::Curve) { + matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle()); + } + + const float rotate = properties.rotate; + if (rotate != 0.0f) { + matrix::rotate_z(exMatrix, exMatrix, rotate); + } + + // If layerStyle.size > bucket.info.fontSize then labels may collide + float fontSize = fmin(properties.size, bucket.geom_desc.font_size); + matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f); + + useProgram(textShader->program); + textShader->setMatrix(matrix); + textShader->setExtrudeMatrix(exMatrix); + + map.getGlyphAtlas().bind(); + textShader->setTextureSize({{static_cast(map.getGlyphAtlas().width), + static_cast(map.getGlyphAtlas().height)}}); + + textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio()); + + // Convert the -pi..pi to an int8 range. + float angle = round((map.getState().getAngle() + rotate) / M_PI * 128); + + // adjust min/max zooms for variable font sies + float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2); + + textShader->setAngle((int32_t)(angle + 256) % 256); + textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0); + textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level + + // Label fading + const time duration = 300_milliseconds; + const time currentTime = util::now(); + + std::deque &history = frameHistory.history; + + // Remove frames until only one is outside the duration, or until there are only three + while (history.size() > 3 && history[1].timestamp + duration < currentTime) { + history.pop_front(); + } + + if (history[1].timestamp + duration < currentTime) { + history[0].z = history[1].z; + } + + size_t frameLen = history.size(); + assert("there should never be less than three frames in the history" && frameLen >= 3); + + // 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 = fmin(startingZ, endingZ); + float highZ = 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.timestamp - history[1].timestamp; + if (timeDiff > duration) timeDiff = 1; + float fadedist = zoomDiff / (timeDiff / duration); + + if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n"); + + // 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.timestamp) / duration * fadedist; + + textShader->setFadeDist(fadedist * 10); + textShader->setMinFadeZoom(floor(lowZ * 10)); + textShader->setMaxFadeZoom(floor(highZ * 10)); + textShader->setFadeZoom((map.getState().getZoom() + bump) * 10); + + // We're drawing in the translucent pass which is bottom-to-top, so we need + // to draw the halo first. + if (properties.halo[3] > 0.0f) { + textShader->setColor(properties.halo); + textShader->setBuffer(properties.haloRadius); + glDepthRange(strata, 1.0f); + bucket.drawGlyphs(*textShader); + } + + // Then, we draw the text over the halo + textShader->setColor(properties.color); + textShader->setBuffer((256.0f - 64.0f) / 256.0f); + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawGlyphs(*textShader); + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index 939a8363e..052fd5a08 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include