diff --git a/Makefile b/Makefile index c16c3f87c..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 @@ -72,6 +74,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/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 diff --git a/bin/style.js b/bin/style.js index ffc74b025..977d7c337 100644 --- a/bin/style.js +++ b/bin/style.js @@ -2,6 +2,10 @@ module.exports = { "buckets": { + "satellite": { + "source": "satellite", + "type": "raster" + }, "admin_maritime": { "source": "outdoors", "layer": "admin", diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index 72e955a93..4600e1a96 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,13 +107,20 @@ private: void loadStyle(const uint8_t *const data, uint32_t bytes); 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, 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* @@ -141,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/map/raster_tile_data.hpp b/include/llmr/map/raster_tile_data.hpp new file mode 100644 index 000000000..fd72cca9b --- /dev/null +++ b/include/llmr/map/raster_tile_data.hpp @@ -0,0 +1,28 @@ +#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); + virtual bool hasData(const LayerDescription& layer_desc) const; + +protected: + RasterBucket bucket; +}; + +} + +#endif diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index 14770d9d2..40b3bf4cf 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -6,10 +6,13 @@ #include #include +#include #include #include #include #include +#include +#include namespace llmr { @@ -26,17 +29,19 @@ 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(const LayerDescription& layer_desc, const BucketDescription &bucket_desc); + void finishRender(); + std::forward_list getIDs() const; + void updateClipIDs(const std::map &mapping); public: bool enabled; @@ -56,7 +61,6 @@ private: private: Map& map; Painter& painter; - Texturepool& texturepool; const Type type; const std::vector zooms; @@ -65,8 +69,8 @@ private: const int32_t min_zoom; const int32_t max_zoom; - std::forward_list tiles; - std::forward_list> tile_data; + std::map> tiles; + std::map> tile_data; }; } 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..65a2892c7 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,37 @@ 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; + virtual bool hasData(const LayerDescription& layer_desc) const = 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..b9a428dde --- /dev/null +++ b/include/llmr/map/vector_tile_data.hpp @@ -0,0 +1,51 @@ +#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); + virtual bool hasData(const LayerDescription& layer_desc) const; + +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/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 new file mode 100644 index 000000000..0b9df57fa --- /dev/null +++ b/include/llmr/renderer/debug_bucket.hpp @@ -0,0 +1,37 @@ +#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); + virtual bool hasData() const; + + void drawLines(PlainShader& shader); + void drawPoints(PlainShader& shader); + +private: + DebugFontBuffer& fontBuffer; + VertexArrayObject array; +}; + +} + +#endif 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/painter.hpp b/include/llmr/renderer/painter.hpp index 03d894597..d236ead44 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); @@ -40,32 +43,54 @@ 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); 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(); // 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, uint8_t clip_id); + void drawClippingMask(const mat4& matrix, const ClipID& clip); void finishClippingMask(); 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 prepareTile(const Tile& tile); void useProgram(uint32_t program); void lineWidth(float lineWidth); void depthMask(bool value); @@ -88,8 +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; + const float strata_epsilon = 1.0f / (1 << 16); std::unique_ptr plainShader; std::unique_ptr outlineShader; 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 88bb3bd84..17e36de77 100644 --- a/include/llmr/renderer/raster_bucket.hpp +++ b/include/llmr/renderer/raster_bucket.hpp @@ -3,16 +3,27 @@ #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); + virtual bool hasData() const; + + bool setImage(const std::string &data); + + void drawRaster(RasterShader& shader, VertexBuffer &vertices, VertexArrayObject &array); + +private: + Raster raster; }; } 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/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/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/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index ea1a29dff..6dd4828e3 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -45,7 +46,7 @@ public: public: const float pixelRatio; - std::shared_ptr raster; + Raster raster; private: void asyncParseJSON(); @@ -57,9 +58,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/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/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/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/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", 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', 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 diff --git a/setup-libraries.sh b/setup-libraries.sh index 44ad91fc6..d43461111 100755 --- a/setup-libraries.sh +++ b/setup-libraries.sh @@ -3,16 +3,36 @@ set -e -u UNAME=$(uname -s); +MISSING_DEPS="" + +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 +ensure_dep pkg-config 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 != "" ]]; then + if [ ${UNAME} = 'Darwin' ]; then + 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 "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 if [ ! -d 'mapnik-packaging/.git' ]; then diff --git a/src/map/map.cpp b/src/map/map.cpp index 06f9b38ca..b59b3940b 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("outdoors", std::unique_ptr(new Source(*this, painter, - texturepool, "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-terrain-v1,mapbox.mapbox-streets-v42-dev/%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,28 +413,133 @@ 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. + + // Render per layer in the stylesheet. + 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. + // 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; - source.render(animationTime); + const std::string &name = pair.first; + gl::group group(std::string("debug ") + name); + source.finishRender(); } painter.renderMatte(); + painter.renderDebugText(debug); + // Schedule another rerender when we definitely need a next frame. if (transform.needsTransition()) { update(); } } + +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"); + } +} + +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); + // This is a layer group. + // TODO: create framebuffer + 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 + // 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; + + // 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; + } + + // 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/raster_tile_data.cpp b/src/map/raster_tile_data.cpp new file mode 100644 index 000000000..5c522ef2a --- /dev/null +++ b/src/map/raster_tile_data.cpp @@ -0,0 +1,34 @@ +#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); +} + +bool RasterTileData::hasData(const LayerDescription& layer_desc) const { + return bucket.hasData(); +} diff --git a/src/map/source.cpp b/src/map/source.cpp index a83d06f88..a5985668f 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -7,17 +7,21 @@ #include #include #include +#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,45 +33,70 @@ bool Source::update() { return updateTiles(); } -void Source::prepare_render(const TransformState &transform, bool is_baselayer) { +void Source::updateClipIDs(const std::map &mapping) { + 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; + } else { + tile.clip = ClipID {}; + } + }); +} + +size_t Source::prepareRender(const TransformState &transform) { + if (!enabled) return 0; + + size_t masks = 0; + 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); + painter.drawClippingMask(tile.matrix, tile.clip); + masks++; + } + + return masks; +} + +void Source::render(const LayerDescription& layer_desc, const BucketDescription &bucket_desc) { if (!enabled) return; - uint8_t i = 1; - - for (Tile& tile : tiles) { + gl::group group(std::string("layer: ") + layer_desc.name); + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; 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); - } + painter.renderTileLayer(tile, layer_desc); } } } -void Source::render(time animationTime) { +void Source::finishRender() { 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); - } + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; + painter.renderTileDebug(tile); } } + +std::forward_list Source::getIDs() const { + std::forward_list ptrs; + + 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; } @@ -83,20 +112,17 @@ 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. 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) { @@ -111,13 +137,14 @@ 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); + tile_data.emplace(new_tile.data->id, new_tile.data); } return new_tile.data->state; @@ -230,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 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](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; 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..2eba7b23a 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; @@ -183,10 +173,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..84fc4ffc4 --- /dev/null +++ b/src/map/vector_tile_data.cpp @@ -0,0 +1,55 @@ +#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); + } +} + +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 new file mode 100644 index 000000000..284d8a371 --- /dev/null +++ b/src/renderer/debug_bucket.cpp @@ -0,0 +1,33 @@ +#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); +} + +bool DebugBucket::hasData() const { + return fontBuffer.index() > 0; +} + +void DebugBucket::drawLines(PlainShader& shader) { + array.bind(shader, fontBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_LINES, 0, (GLsizei)(fontBuffer.index())); +} + +void DebugBucket::drawPoints(PlainShader& shader) { + array.bind(shader, fontBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_POINTS, 0, (GLsizei)(fontBuffer.index())); +} diff --git a/src/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 0915e790f..6adbc71c4 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -1,27 +1,15 @@ -#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; @@ -129,39 +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, uint8_t clip_id) { - gl::group group("mask"); - plainShader->setMatrix(matrix); - - 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); - - glStencilFunc(GL_ALWAYS, clip_id, 0xFF); - 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); @@ -175,85 +130,43 @@ void Painter::clear() { #endif } -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; - } - - 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); - } -} - -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. +void Painter::startOpaquePass() { 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. +void Painter::startTranslucentPass() { 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); - } +void Painter::endPass() { 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()) { - 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); - } - } - } +void Painter::setStrata(float value) { + strata = value; +} + +void Painter::prepareTile(const Tile& tile) { + matrix = tile.matrix; + + GLint id = (GLint)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) { + assert(tile.data); + 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); + frameHistory.record(map.getAnimationTime(), map.getState().getNormalizedZoom()); } } @@ -271,477 +184,13 @@ 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; - - 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; - - gl::group group(layer_name + " (raster)"); - - useProgram(rasterShader->program); - rasterShader->setMatrix(matrix); - rasterShader->setImage(0); - rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); - tile_data->raster->bind(true); - - coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0)); - glDepthRange(strata, 1.0f); - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); -} - -void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { - // Abort early. - if (bucket.empty()) 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); - - 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) { - 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.empty()) 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); - - 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. - 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.hasPoints()) 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); - - gl::group group(layer_name + " (point)"); - - 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.empty()) 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); - - gl::group group(layer_name + " (text)"); - - 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::renderDebug(const TileData::Ptr& tile_data) { - gl::group group("debug"); - - // 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()); - - // 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); -} - void Painter::renderMatte() { gl::group group("matte"); 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); 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/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 f8c656b2c..c0260023d 100644 --- a/src/renderer/raster_bucket.cpp +++ b/src/renderer/raster_bucket.cpp @@ -3,12 +3,25 @@ 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()); +} + +bool RasterBucket::hasData() const { + return raster.isLoaded(); } diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index a2a88e9a7..052fd5a08 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -14,8 +13,6 @@ #include -#define BUFFER_OFFSET(i) ((char *) nullptr + (i)) - #include using namespace llmr; @@ -148,7 +145,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); diff --git a/src/shader/shaders_gl.cpp b/src/shader/shaders_gl.cpp index aa98275be..32f8af081 100644 --- a/src/shader/shaders_gl.cpp +++ b/src/shader/shaders_gl.cpp @@ -7,40 +7,40 @@ using namespace llmr; const shader_source llmr::shaders[SHADER_COUNT] = { { - "#version 120\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main(void) {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n", - "#version 120\nuniform vec4 u_color;\nuniform float u_blur;\n\nvoid main() {\n\tfloat dist = length(gl_PointCoord - 0.5);\n\tfloat t = smoothstep(0.5, 0.5 - u_blur, dist);\n\n gl_FragColor = u_color * t;\n}\n", + "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n", + "#version 120\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n", }, { - "#version 120\n// floor(127 / 2) == 63.0\n// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is\n// stored in a byte (-128..127). we scale regular normals up to length 63, but\n// there are also \"special\" normals that have a bigger length (of up to 126 in\n// this case).\n// #define scale 63.0\n#define scale 0.015873016\n\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\n\n// posmatrix is for the vertex position, exmatrix is for rotating and projecting\n// the extrusion vector.\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\n\n// shared\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // We store the texture normals in the most insignificant bit\n // transform y so that 0 => -1 and 1 => 1\n // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap\n // y is 1 if the normal points up, and -1 if it points down\n vec2 normal = mod(a_pos, 2.0);\n normal.y = sign(normal.y - 0.5);\n v_normal = normal;\n\n // Scale the extrusion vector down to a normal and then up by the line width\n // of this vertex.\n vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0);\n\n // Remove the texture normal bit of the position before scaling it with the\n // model/view matrix. Add the extrusion vector *after* the model/view matrix\n // because we're extruding the line in pixel space, regardless of the current\n // tile's zoom level.\n gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist;\n v_linesofar = a_linesofar * u_ratio;\n}", - "#version 120\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nuniform vec2 u_dasharray;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // Calculate the distance of the pixel from the line in pixels.\n float dist = length(v_normal) * u_linewidth.s;\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n\n // Calculate the antialiasing fade factor based on distance to the dash.\n // Only affects alpha when line is dashed\n float pos = mod(v_linesofar, u_dasharray.x + u_dasharray.y);\n alpha *= max(step(0.0, -u_dasharray.y), clamp(min(pos, u_dasharray.x - pos), 0.0, 1.0));\n\n gl_FragColor = u_color * alpha;\n}", + "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n", + "#version 120\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\nuniform vec2 u_dasharray;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n float tmpvar_1;\n tmpvar_1 = (sqrt(dot (v_normal, v_normal)) * u_linewidth.x);\n float tmpvar_2;\n tmpvar_2 = (float(mod (v_linesofar, (u_dasharray.x + u_dasharray.y))));\n gl_FragColor = (u_color * (clamp (\n min ((tmpvar_1 - (u_linewidth.y - 1.0)), (u_linewidth.x - tmpvar_1))\n , 0.0, 1.0) * max (\n float((-(u_dasharray.y) >= 0.0))\n , \n clamp (min (tmpvar_2, (u_dasharray.x - tmpvar_2)), 0.0, 1.0)\n )));\n}\n\n", }, { - "#version 120\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform vec2 u_world;\nuniform float u_size;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0);\n v_pos = (gl_Position.xy + 1.0) * u_world;\n gl_PointSize = u_size;\n}\n", - "#version 120\nuniform vec4 u_color;\nuniform vec2 u_linewidth;\n\nvarying vec2 v_pos;\n\nvoid main() {\n float dist = length(v_pos - gl_FragCoord.xy);\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n gl_FragColor = u_color * alpha;\n}\n", + "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\nuniform float u_size;\nvarying vec2 v_pos;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = floor((a_pos / 2.0));\n vec4 tmpvar_2;\n tmpvar_2 = (u_matrix * tmpvar_1);\n gl_Position = tmpvar_2;\n v_pos = ((tmpvar_2.xy + 1.0) * u_world);\n gl_PointSize = u_size;\n}\n\n", + "#version 120\nuniform vec4 u_color;\nuniform vec2 u_linewidth;\nvarying vec2 v_pos;\nvoid main ()\n{\n float tmpvar_1;\n vec2 x_2;\n x_2 = (v_pos - gl_FragCoord.xy);\n tmpvar_1 = sqrt(dot (x_2, x_2));\n gl_FragColor = (u_color * clamp (min (\n (tmpvar_1 - (u_linewidth.y - 1.0))\n , \n (u_linewidth.x - tmpvar_1)\n ), 0.0, 1.0));\n}\n\n", }, { - "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = (gl_Position.xy + 1.0) / 2.0 * u_world;\n}\n", - "#version 120\nuniform vec4 u_color;\n\nvarying vec2 v_pos;\n\nvoid main() {\n float dist = length(v_pos - gl_FragCoord.xy);\n float alpha = smoothstep(1.0, 0.0, dist);\n gl_FragColor = u_color * alpha;\n}\n", + "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\nvarying vec2 v_pos;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n vec4 tmpvar_2;\n tmpvar_2 = (u_matrix * tmpvar_1);\n gl_Position = tmpvar_2;\n v_pos = (((tmpvar_2.xy + 1.0) / 2.0) * u_world);\n}\n\n", + "#version 120\nuniform vec4 u_color;\nvarying vec2 v_pos;\nvoid main ()\n{\n vec2 x_1;\n x_1 = (v_pos - gl_FragCoord.xy);\n float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 1.0) / -1.0), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n", }, { - "#version 120\nuniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n v_pos = a_pos;\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n", - "#version 120\nuniform vec4 u_color;\n\nuniform vec2 u_offset;\nuniform vec2 u_pattern_size;\nuniform vec2 u_pattern_tl;\nuniform vec2 u_pattern_br;\nuniform float u_mix;\n\n\nuniform sampler2D u_image;\n\nvarying vec2 v_pos;\n\nvoid main() {\n\n vec2 imagecoord = mod((v_pos + u_offset) / u_pattern_size, 1.0);\n vec2 pos = mix(u_pattern_tl, u_pattern_br, imagecoord);\n vec4 color1 = texture2D(u_image, pos);\n\n vec2 imagecoord2 = mod(imagecoord * 2.0, 1.0);\n vec2 pos2 = mix(u_pattern_tl, u_pattern_br, imagecoord2);\n vec4 color2 = texture2D(u_image, pos2);\n\n vec4 color = mix(color1, color2, u_mix);\n gl_FragColor = color + u_color * (1.0 - color.a);\n}\n", + "#version 120\nuniform mat4 u_matrix;\nattribute vec2 a_pos;\nvarying vec2 v_pos;\nvoid main ()\n{\n v_pos = a_pos;\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n}\n\n", + "#version 120\nuniform vec4 u_color;\nuniform vec2 u_offset;\nuniform vec2 u_pattern_size;\nuniform vec2 u_pattern_tl;\nuniform vec2 u_pattern_br;\nuniform float u_mix;\nuniform sampler2D u_image;\nvarying vec2 v_pos;\nvoid main ()\n{\n vec2 tmpvar_1;\n tmpvar_1 = (vec2(mod (((v_pos + u_offset) / u_pattern_size), 1.0)));\n vec4 tmpvar_2;\n tmpvar_2 = mix (texture2D (u_image, mix (u_pattern_tl, u_pattern_br, tmpvar_1)), texture2D (u_image, mix (u_pattern_tl, u_pattern_br, (vec2(mod (\n (tmpvar_1 * 2.0)\n , 1.0))))), u_mix);\n gl_FragColor = (tmpvar_2 + (u_color * (1.0 - tmpvar_2.w)));\n}\n\n", }, { - "#version 120\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n", - "#version 120\nuniform vec4 u_color;\n\nvoid main() {\n gl_FragColor = u_color;\n}\n", + "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n}\n\n", + "#version 120\nuniform vec4 u_color;\nvoid main ()\n{\n gl_FragColor = u_color;\n}\n\n", }, { - "#version 120\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n", - "#version 120\n#define root2 1.4142135623730951\n\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\n\nuniform vec2 pos;\nuniform float inbounds;\nuniform vec4 color;\n\nvoid main() {\n vec2 pos = (gl_PointCoord * 2.0 - 1.0) * root2 / 2.0 + 0.5;\n\n float inbounds = step(0.0, pos.x) * step(0.0, pos.y) *\n (1.0 - step(1.0, pos.x)) * (1.0 - step(1.0, pos.y));\n\n vec4 color = texture2D(u_image, mix(u_tl, u_br, pos)) * inbounds;\n\n gl_FragColor = color * u_color;\n}\n", + "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n", + "#version 120\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\nvoid main ()\n{\n vec2 tmpvar_1;\n tmpvar_1 = (((\n ((gl_PointCoord * 2.0) - 1.0)\n * 1.41421) / 2.0) + 0.5);\n gl_FragColor = ((texture2D (u_image, mix (u_tl, u_br, tmpvar_1)) * (\n ((float((tmpvar_1.x >= 0.0)) * float((tmpvar_1.y >= 0.0))) * (1.0 - float((tmpvar_1.x >= 1.0))))\n * \n (1.0 - float((tmpvar_1.y >= 1.0)))\n )) * u_color);\n}\n\n", }, { - "#version 120\nuniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = a_pos / 4096.0;\n}\n", - "#version 120\nuniform sampler2D u_image;\nuniform float u_opacity;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_FragColor = texture2D(u_image, v_pos) * u_opacity;\n}\n", + "#version 120\nuniform mat4 u_matrix;\nattribute vec2 a_pos;\nvarying vec2 v_pos;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n v_pos = (a_pos / 4096.0);\n}\n\n", + "#version 120\nuniform sampler2D u_image;\nuniform float u_opacity;\nvarying vec2 v_pos;\nvoid main ()\n{\n gl_FragColor = (texture2D (u_image, v_pos) * u_opacity);\n}\n\n", }, { - "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_offset;\nattribute vec4 a_data1;\nattribute vec4 a_data2;\n\n\n// posmatrix is for the vertex position, exmatrix is for rotating and projecting\n// the extrusion vector.\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_angle;\nuniform float u_zoom;\nuniform float u_flip;\nuniform float u_fadedist;\nuniform float u_minfadezoom;\nuniform float u_maxfadezoom;\nuniform float u_fadezoom;\n\nuniform vec2 u_texsize;\n\nvarying vec2 v_tex;\nvarying float v_alpha;\n\nvoid main() {\n vec2 a_tex = a_data1.xy;\n float a_labelminzoom = a_data1[2];\n float a_angle = a_data1[3];\n vec2 a_zoom = a_data2.st;\n vec2 a_range = a_data2.pq;\n\n float rev = 0.0;\n\n // u_angle is angle of the map, -128..128 representing 0..2PI\n // a_angle is angle of the label, 0..256 representing 0..2PI, where 0 is horizontal text\n float rotated = mod(a_angle + u_angle, 256.0);\n // if the label rotates with the map, and if the rotated label is upside down, hide it\n if (u_flip > 0.0 && rotated >= 64.0 && rotated < 192.0) rev = 1.0;\n\n // If the label should be invisible, we move the vertex outside\n // of the view plane so that the triangle gets clipped. This makes it easier\n // for us to create degenerate triangle strips.\n // u_zoom is the current zoom level adjusted for the change in font size\n float z = 2.0 - step(a_zoom[0], u_zoom) - (1.0 - step(a_zoom[1], u_zoom)) + rev;\n\n // fade out labels\n float alpha = clamp((u_fadezoom - a_labelminzoom) / u_fadedist, 0.0, 1.0);\n\n if (u_fadedist >= 0.0) {\n v_alpha = alpha;\n } else {\n v_alpha = 1.0 - alpha;\n }\n if (u_maxfadezoom < a_labelminzoom) {\n v_alpha = 0.0;\n }\n if (u_minfadezoom >= a_labelminzoom) {\n v_alpha = 1.0;\n }\n\n // if label has been faded out, clip it\n z += step(v_alpha, 0.0);\n\n // all the angles are 0..256 representing 0..2PI\n // hide if (angle >= a_rangeend && angle < rangestart)\n z += (1.0 - step(a_range[0], u_angle)) * step(a_range[1], u_angle);\n\n gl_Position = u_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0);\n v_tex = a_tex * 4.0 / u_texsize;\n}\n", - "#version 120\nuniform sampler2D u_texture;\nuniform vec4 u_color;\nuniform float u_buffer;\nuniform float u_gamma;\n\nvarying vec2 v_tex;\nvarying float v_alpha;\n\nvoid main() {\n float dist = texture2D(u_texture, v_tex).a;\n float alpha = smoothstep(u_buffer - u_gamma, u_buffer + u_gamma, dist) * v_alpha;\n gl_FragColor = u_color * alpha;\n}\n", + "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_offset;\nattribute vec4 a_data1;\nattribute vec4 a_data2;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_angle;\nuniform float u_zoom;\nuniform float u_flip;\nuniform float u_fadedist;\nuniform float u_minfadezoom;\nuniform float u_maxfadezoom;\nuniform float u_fadezoom;\nuniform vec2 u_texsize;\nvarying vec2 v_tex;\nvarying float v_alpha;\nvoid main ()\n{\n float rev_1;\n rev_1 = 0.0;\n float tmpvar_2;\n tmpvar_2 = (float(mod ((a_data1.w + u_angle), 256.0)));\n if ((((u_flip > 0.0) && (tmpvar_2 >= 64.0)) && (tmpvar_2 < 192.0))) {\n rev_1 = 1.0;\n };\n float tmpvar_3;\n tmpvar_3 = (((2.0 - \n float((u_zoom >= a_data2.x))\n ) - (1.0 - \n float((u_zoom >= a_data2.y))\n )) + rev_1);\n float tmpvar_4;\n tmpvar_4 = clamp (((u_fadezoom - a_data1.z) / u_fadedist), 0.0, 1.0);\n if ((u_fadedist >= 0.0)) {\n v_alpha = tmpvar_4;\n } else {\n v_alpha = (1.0 - tmpvar_4);\n };\n if ((u_maxfadezoom < a_data1.z)) {\n v_alpha = 0.0;\n };\n if ((u_minfadezoom >= a_data1.z)) {\n v_alpha = 1.0;\n };\n vec4 tmpvar_5;\n tmpvar_5.zw = vec2(0.0, 1.0);\n tmpvar_5.xy = a_pos;\n vec4 tmpvar_6;\n tmpvar_6.w = 0.0;\n tmpvar_6.xy = (a_offset / 64.0);\n tmpvar_6.z = ((tmpvar_3 + float(\n (0.0 >= v_alpha)\n )) + ((1.0 - \n float((u_angle >= a_data2.z))\n ) * float(\n (u_angle >= a_data2.w)\n )));\n gl_Position = ((u_matrix * tmpvar_5) + (u_exmatrix * tmpvar_6));\n v_tex = ((a_data1.xy * 4.0) / u_texsize);\n}\n\n", + "#version 120\nuniform sampler2D u_texture;\nuniform vec4 u_color;\nuniform float u_buffer;\nuniform float u_gamma;\nvarying vec2 v_tex;\nvarying float v_alpha;\nvoid main ()\n{\n float edge0_1;\n edge0_1 = (u_buffer - u_gamma);\n float tmpvar_2;\n tmpvar_2 = clamp (((texture2D (u_texture, v_tex).w - edge0_1) / (\n (u_buffer + u_gamma)\n - edge0_1)), 0.0, 1.0);\n gl_FragColor = (u_color * ((tmpvar_2 * \n (tmpvar_2 * (3.0 - (2.0 * tmpvar_2)))\n ) * v_alpha));\n}\n\n", } }; #endif diff --git a/src/shader/shaders_gles2.cpp b/src/shader/shaders_gles2.cpp index 60417ed6f..cd247a9e5 100644 --- a/src/shader/shaders_gles2.cpp +++ b/src/shader/shaders_gles2.cpp @@ -7,40 +7,40 @@ using namespace llmr; const shader_source llmr::shaders[SHADER_COUNT] = { { - "precision highp float;\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main(void) {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n", - "precision highp float;\nuniform vec4 u_color;\nuniform float u_blur;\n\nvoid main() {\n\tfloat dist = length(gl_PointCoord - 0.5);\n\tfloat t = smoothstep(0.5, 0.5 - u_blur, dist);\n\n gl_FragColor = u_color * t;\n}\n", + "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n", + "precision highp float;\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n mediump vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n mediump float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n", }, { - "precision highp float;\n// floor(127 / 2) == 63.0\n// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is\n// stored in a byte (-128..127). we scale regular normals up to length 63, but\n// there are also \"special\" normals that have a bigger length (of up to 126 in\n// this case).\n// #define scale 63.0\n#define scale 0.015873016\n\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\n\n// posmatrix is for the vertex position, exmatrix is for rotating and projecting\n// the extrusion vector.\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\n\n// shared\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // We store the texture normals in the most insignificant bit\n // transform y so that 0 => -1 and 1 => 1\n // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap\n // y is 1 if the normal points up, and -1 if it points down\n vec2 normal = mod(a_pos, 2.0);\n normal.y = sign(normal.y - 0.5);\n v_normal = normal;\n\n // Scale the extrusion vector down to a normal and then up by the line width\n // of this vertex.\n vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0);\n\n // Remove the texture normal bit of the position before scaling it with the\n // model/view matrix. Add the extrusion vector *after* the model/view matrix\n // because we're extruding the line in pixel space, regardless of the current\n // tile's zoom level.\n gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist;\n v_linesofar = a_linesofar * u_ratio;\n}", - "precision highp float;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nuniform vec2 u_dasharray;\n\nvarying vec2 v_normal;\nvarying float v_linesofar;\n\nvoid main() {\n // Calculate the distance of the pixel from the line in pixels.\n float dist = length(v_normal) * u_linewidth.s;\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n\n // Calculate the antialiasing fade factor based on distance to the dash.\n // Only affects alpha when line is dashed\n float pos = mod(v_linesofar, u_dasharray.x + u_dasharray.y);\n alpha *= max(step(0.0, -u_dasharray.y), clamp(min(pos, u_dasharray.x - pos), 0.0, 1.0));\n\n gl_FragColor = u_color * alpha;\n}", + "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n", + "precision highp float;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\nuniform vec2 u_dasharray;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n float tmpvar_1;\n tmpvar_1 = (sqrt(dot (v_normal, v_normal)) * u_linewidth.x);\n float tmpvar_2;\n tmpvar_2 = (float(mod (v_linesofar, (u_dasharray.x + u_dasharray.y))));\n gl_FragColor = (u_color * (clamp (\n min ((tmpvar_1 - (u_linewidth.y - 1.0)), (u_linewidth.x - tmpvar_1))\n , 0.0, 1.0) * max (\n float((-(u_dasharray.y) >= 0.0))\n , \n clamp (min (tmpvar_2, (u_dasharray.x - tmpvar_2)), 0.0, 1.0)\n )));\n}\n\n", }, { - "precision highp float;\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform vec2 u_world;\nuniform float u_size;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0);\n v_pos = (gl_Position.xy + 1.0) * u_world;\n gl_PointSize = u_size;\n}\n", - "precision highp float;\nuniform vec4 u_color;\nuniform vec2 u_linewidth;\n\nvarying vec2 v_pos;\n\nvoid main() {\n float dist = length(v_pos - gl_FragCoord.xy);\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n gl_FragColor = u_color * alpha;\n}\n", + "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\nuniform float u_size;\nvarying highp vec2 v_pos;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = floor((a_pos / 2.0));\n vec4 tmpvar_2;\n tmpvar_2 = (u_matrix * tmpvar_1);\n gl_Position = tmpvar_2;\n v_pos = ((tmpvar_2.xy + 1.0) * u_world);\n gl_PointSize = u_size;\n}\n\n", + "precision highp float;\nuniform vec4 u_color;\nuniform vec2 u_linewidth;\nvarying vec2 v_pos;\nvoid main ()\n{\n highp float tmpvar_1;\n highp vec2 x_2;\n x_2 = (v_pos - gl_FragCoord.xy);\n tmpvar_1 = sqrt(dot (x_2, x_2));\n highp vec4 tmpvar_3;\n tmpvar_3 = (u_color * clamp (min (\n (tmpvar_1 - (u_linewidth.y - 1.0))\n , \n (u_linewidth.x - tmpvar_1)\n ), 0.0, 1.0));\n gl_FragColor = tmpvar_3;\n}\n\n", }, { - "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = (gl_Position.xy + 1.0) / 2.0 * u_world;\n}\n", - "precision highp float;\nuniform vec4 u_color;\n\nvarying vec2 v_pos;\n\nvoid main() {\n float dist = length(v_pos - gl_FragCoord.xy);\n float alpha = smoothstep(1.0, 0.0, dist);\n gl_FragColor = u_color * alpha;\n}\n", + "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\nvarying highp vec2 v_pos;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n vec4 tmpvar_2;\n tmpvar_2 = (u_matrix * tmpvar_1);\n gl_Position = tmpvar_2;\n v_pos = (((tmpvar_2.xy + 1.0) / 2.0) * u_world);\n}\n\n", + "precision highp float;\nuniform vec4 u_color;\nvarying vec2 v_pos;\nvoid main ()\n{\n highp vec2 x_1;\n x_1 = (v_pos - gl_FragCoord.xy);\n highp float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 1.0) / -1.0), 0.0, 1.0);\n highp vec4 tmpvar_3;\n tmpvar_3 = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n gl_FragColor = tmpvar_3;\n}\n\n", }, { - "precision highp float;\nuniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n v_pos = a_pos;\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n", - "precision highp float;\nuniform vec4 u_color;\n\nuniform vec2 u_offset;\nuniform vec2 u_pattern_size;\nuniform vec2 u_pattern_tl;\nuniform vec2 u_pattern_br;\nuniform float u_mix;\n\n\nuniform sampler2D u_image;\n\nvarying vec2 v_pos;\n\nvoid main() {\n\n vec2 imagecoord = mod((v_pos + u_offset) / u_pattern_size, 1.0);\n vec2 pos = mix(u_pattern_tl, u_pattern_br, imagecoord);\n vec4 color1 = texture2D(u_image, pos);\n\n vec2 imagecoord2 = mod(imagecoord * 2.0, 1.0);\n vec2 pos2 = mix(u_pattern_tl, u_pattern_br, imagecoord2);\n vec4 color2 = texture2D(u_image, pos2);\n\n vec4 color = mix(color1, color2, u_mix);\n gl_FragColor = color + u_color * (1.0 - color.a);\n}\n", + "precision highp float;\nuniform mat4 u_matrix;\nattribute vec2 a_pos;\nvarying vec2 v_pos;\nvoid main ()\n{\n v_pos = a_pos;\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n}\n\n", + "precision highp float;\nuniform vec4 u_color;\nuniform vec2 u_offset;\nuniform vec2 u_pattern_size;\nuniform vec2 u_pattern_tl;\nuniform vec2 u_pattern_br;\nuniform float u_mix;\nuniform sampler2D u_image;\nvarying vec2 v_pos;\nvoid main ()\n{\n vec2 tmpvar_1;\n tmpvar_1 = (vec2(mod (((v_pos + u_offset) / u_pattern_size), 1.0)));\n lowp vec4 tmpvar_2;\n tmpvar_2 = mix (texture2D (u_image, mix (u_pattern_tl, u_pattern_br, tmpvar_1)), texture2D (u_image, mix (u_pattern_tl, u_pattern_br, (vec2(mod (\n (tmpvar_1 * 2.0)\n , 1.0))))), u_mix);\n lowp vec4 tmpvar_3;\n tmpvar_3 = (tmpvar_2 + (u_color * (1.0 - tmpvar_2.w)));\n gl_FragColor = tmpvar_3;\n}\n\n", }, { - "precision highp float;\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n", - "precision highp float;\nuniform vec4 u_color;\n\nvoid main() {\n gl_FragColor = u_color;\n}\n", + "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n}\n\n", + "precision highp float;\nuniform vec4 u_color;\nvoid main ()\n{\n gl_FragColor = u_color;\n}\n\n", }, { - "precision highp float;\nattribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n", - "precision highp float;\n#define root2 1.4142135623730951\n\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\n\nuniform vec2 pos;\nuniform float inbounds;\nuniform vec4 color;\n\nvoid main() {\n vec2 pos = (gl_PointCoord * 2.0 - 1.0) * root2 / 2.0 + 0.5;\n\n float inbounds = step(0.0, pos.x) * step(0.0, pos.y) *\n (1.0 - step(1.0, pos.x)) * (1.0 - step(1.0, pos.y));\n\n vec4 color = texture2D(u_image, mix(u_tl, u_br, pos)) * inbounds;\n\n gl_FragColor = color * u_color;\n}\n", + "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n", + "precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = (((\n ((gl_PointCoord * 2.0) - 1.0)\n * 1.41421) / 2.0) + 0.5);\n mediump vec2 tmpvar_2;\n tmpvar_2 = mix (u_tl, u_br, tmpvar_1);\n lowp vec4 tmpvar_3;\n tmpvar_3 = texture2D (u_image, tmpvar_2);\n gl_FragColor = ((tmpvar_3 * (\n ((float((tmpvar_1.x >= 0.0)) * float((tmpvar_1.y >= 0.0))) * (1.0 - float((tmpvar_1.x >= 1.0))))\n * \n (1.0 - float((tmpvar_1.y >= 1.0)))\n )) * u_color);\n}\n\n", }, { - "precision highp float;\nuniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = a_pos / 4096.0;\n}\n", - "precision highp float;\nuniform sampler2D u_image;\nuniform float u_opacity;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_FragColor = texture2D(u_image, v_pos) * u_opacity;\n}\n", + "precision highp float;\nuniform mat4 u_matrix;\nattribute vec2 a_pos;\nvarying vec2 v_pos;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n v_pos = (a_pos / 4096.0);\n}\n\n", + "precision highp float;\nuniform sampler2D u_image;\nuniform float u_opacity;\nvarying vec2 v_pos;\nvoid main ()\n{\n lowp vec4 tmpvar_1;\n tmpvar_1 = (texture2D (u_image, v_pos) * u_opacity);\n gl_FragColor = tmpvar_1;\n}\n\n", }, { - "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_offset;\nattribute vec4 a_data1;\nattribute vec4 a_data2;\n\n\n// posmatrix is for the vertex position, exmatrix is for rotating and projecting\n// the extrusion vector.\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_angle;\nuniform float u_zoom;\nuniform float u_flip;\nuniform float u_fadedist;\nuniform float u_minfadezoom;\nuniform float u_maxfadezoom;\nuniform float u_fadezoom;\n\nuniform vec2 u_texsize;\n\nvarying vec2 v_tex;\nvarying float v_alpha;\n\nvoid main() {\n vec2 a_tex = a_data1.xy;\n float a_labelminzoom = a_data1[2];\n float a_angle = a_data1[3];\n vec2 a_zoom = a_data2.st;\n vec2 a_range = a_data2.pq;\n\n float rev = 0.0;\n\n // u_angle is angle of the map, -128..128 representing 0..2PI\n // a_angle is angle of the label, 0..256 representing 0..2PI, where 0 is horizontal text\n float rotated = mod(a_angle + u_angle, 256.0);\n // if the label rotates with the map, and if the rotated label is upside down, hide it\n if (u_flip > 0.0 && rotated >= 64.0 && rotated < 192.0) rev = 1.0;\n\n // If the label should be invisible, we move the vertex outside\n // of the view plane so that the triangle gets clipped. This makes it easier\n // for us to create degenerate triangle strips.\n // u_zoom is the current zoom level adjusted for the change in font size\n float z = 2.0 - step(a_zoom[0], u_zoom) - (1.0 - step(a_zoom[1], u_zoom)) + rev;\n\n // fade out labels\n float alpha = clamp((u_fadezoom - a_labelminzoom) / u_fadedist, 0.0, 1.0);\n\n if (u_fadedist >= 0.0) {\n v_alpha = alpha;\n } else {\n v_alpha = 1.0 - alpha;\n }\n if (u_maxfadezoom < a_labelminzoom) {\n v_alpha = 0.0;\n }\n if (u_minfadezoom >= a_labelminzoom) {\n v_alpha = 1.0;\n }\n\n // if label has been faded out, clip it\n z += step(v_alpha, 0.0);\n\n // all the angles are 0..256 representing 0..2PI\n // hide if (angle >= a_rangeend && angle < rangestart)\n z += (1.0 - step(a_range[0], u_angle)) * step(a_range[1], u_angle);\n\n gl_Position = u_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0);\n v_tex = a_tex * 4.0 / u_texsize;\n}\n", - "precision highp float;\nuniform sampler2D u_texture;\nuniform vec4 u_color;\nuniform float u_buffer;\nuniform float u_gamma;\n\nvarying vec2 v_tex;\nvarying float v_alpha;\n\nvoid main() {\n float dist = texture2D(u_texture, v_tex).a;\n float alpha = smoothstep(u_buffer - u_gamma, u_buffer + u_gamma, dist) * v_alpha;\n gl_FragColor = u_color * alpha;\n}\n", + "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_offset;\nattribute vec4 a_data1;\nattribute vec4 a_data2;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_angle;\nuniform float u_zoom;\nuniform float u_flip;\nuniform float u_fadedist;\nuniform float u_minfadezoom;\nuniform float u_maxfadezoom;\nuniform float u_fadezoom;\nuniform vec2 u_texsize;\nvarying vec2 v_tex;\nvarying float v_alpha;\nvoid main ()\n{\n float rev_1;\n rev_1 = 0.0;\n float tmpvar_2;\n tmpvar_2 = (float(mod ((a_data1.w + u_angle), 256.0)));\n if ((((u_flip > 0.0) && (tmpvar_2 >= 64.0)) && (tmpvar_2 < 192.0))) {\n rev_1 = 1.0;\n };\n float tmpvar_3;\n tmpvar_3 = (((2.0 - \n float((u_zoom >= a_data2.x))\n ) - (1.0 - \n float((u_zoom >= a_data2.y))\n )) + rev_1);\n float tmpvar_4;\n tmpvar_4 = clamp (((u_fadezoom - a_data1.z) / u_fadedist), 0.0, 1.0);\n if ((u_fadedist >= 0.0)) {\n v_alpha = tmpvar_4;\n } else {\n v_alpha = (1.0 - tmpvar_4);\n };\n if ((u_maxfadezoom < a_data1.z)) {\n v_alpha = 0.0;\n };\n if ((u_minfadezoom >= a_data1.z)) {\n v_alpha = 1.0;\n };\n vec4 tmpvar_5;\n tmpvar_5.zw = vec2(0.0, 1.0);\n tmpvar_5.xy = a_pos;\n vec4 tmpvar_6;\n tmpvar_6.w = 0.0;\n tmpvar_6.xy = (a_offset / 64.0);\n tmpvar_6.z = ((tmpvar_3 + float(\n (0.0 >= v_alpha)\n )) + ((1.0 - \n float((u_angle >= a_data2.z))\n ) * float(\n (u_angle >= a_data2.w)\n )));\n gl_Position = ((u_matrix * tmpvar_5) + (u_exmatrix * tmpvar_6));\n v_tex = ((a_data1.xy * 4.0) / u_texsize);\n}\n\n", + "precision highp float;\nuniform sampler2D u_texture;\nuniform vec4 u_color;\nuniform float u_buffer;\nuniform float u_gamma;\nvarying vec2 v_tex;\nvarying float v_alpha;\nvoid main ()\n{\n float edge0_1;\n edge0_1 = (u_buffer - u_gamma);\n lowp float tmpvar_2;\n tmpvar_2 = clamp (((texture2D (u_texture, v_tex).w - edge0_1) / (\n (u_buffer + u_gamma)\n - edge0_1)), 0.0, 1.0);\n lowp vec4 tmpvar_3;\n tmpvar_3 = (u_color * ((tmpvar_2 * \n (tmpvar_2 * (3.0 - (2.0 * tmpvar_2)))\n ) * v_alpha));\n gl_FragColor = tmpvar_3;\n}\n\n", } }; #endif 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/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/style/style.cpp b/src/style/style.cpp index 9c766419b..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 @@ -123,6 +124,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; 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..579f80472 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; @@ -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(); } } 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))); +}