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] 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))); +}