diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-03-21 13:36:32 +0100 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-03-21 13:36:32 +0100 |
commit | 3413ee536d1e3fdd30aeb8a0105f713115302279 (patch) | |
tree | f5d622751328ab7233b3631ae167f781c6e7e292 | |
parent | 4ee0cf1791f8f4b787793d2612c583bfb8b9c62f (diff) | |
parent | 0730ca640b095662a918fa55486c7b5bc4eba31c (diff) | |
download | qtlocation-mapboxgl-3413ee536d1e3fdd30aeb8a0105f713115302279.tar.gz |
Merge branch 'master' into sdf
Conflicts:
include/llmr/map/tile_data.hpp
include/llmr/shader/shaders.hpp
src/map/map.cpp
src/map/tile_data.cpp
src/platform/platform.cpp
src/shader/shaders.cpp
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | include/llmr/map/coverage.hpp | 4 | ||||
-rw-r--r-- | include/llmr/map/map.hpp | 7 | ||||
-rw-r--r-- | include/llmr/map/tile.hpp | 4 | ||||
-rw-r--r-- | include/llmr/map/tile_data.hpp | 6 | ||||
-rw-r--r-- | include/llmr/platform/platform.hpp | 5 | ||||
-rw-r--r-- | include/llmr/renderer/painter.hpp | 7 | ||||
-rw-r--r-- | include/llmr/shader/raster_shader.hpp | 28 | ||||
-rw-r--r-- | include/llmr/shader/shaders.hpp | 1 | ||||
-rw-r--r-- | include/llmr/style/sprite.hpp | 14 | ||||
-rw-r--r-- | include/llmr/util/raster.hpp | 72 | ||||
-rw-r--r-- | include/llmr/util/texturepool.hpp | 25 | ||||
-rw-r--r-- | ios/MBXViewController.mm | 14 | ||||
-rw-r--r-- | src/map/coverage.cpp | 22 | ||||
-rw-r--r-- | src/map/map.cpp | 38 | ||||
-rw-r--r-- | src/map/tile.cpp | 15 | ||||
-rw-r--r-- | src/map/tile_data.cpp | 23 | ||||
-rw-r--r-- | src/platform/platform.cpp | 6 | ||||
-rw-r--r-- | src/renderer/painter.cpp | 34 | ||||
-rw-r--r-- | src/shader/raster.fragment.glsl | 8 | ||||
-rw-r--r-- | src/shader/raster.vertex.glsl | 10 | ||||
-rw-r--r-- | src/shader/raster_shader.cpp | 47 | ||||
-rw-r--r-- | src/shader/shaders.cpp | 4 | ||||
-rw-r--r-- | src/style/sprite.cpp | 170 | ||||
-rw-r--r-- | src/util/raster.cpp | 191 | ||||
-rw-r--r-- | src/util/texturepool.cpp | 56 |
26 files changed, 609 insertions, 206 deletions
@@ -115,8 +115,9 @@ This is automatically taken care of as a build phase if you are using the Xcode ## Desktop -- Press 'R' to reset the transform +- Press 'X' to reset the transform - Press 'N' to reset north +- Press 'R' to toggle raster - Press Tab to toggle debug information - Press Esc to quit @@ -130,3 +131,4 @@ This is automatically taken care of as a build phase if you are using the Xcode - Long-press to reset north - Two-finger long press to reset the transform - Three-finger long press to toggle debug information +- Four-finger long press to toggle raster diff --git a/include/llmr/map/coverage.hpp b/include/llmr/map/coverage.hpp index c889ce7016..ed6317a560 100644 --- a/include/llmr/map/coverage.hpp +++ b/include/llmr/map/coverage.hpp @@ -1,13 +1,13 @@ #ifndef LLMR_MAP_COVERAGE #define LLMR_MAP_COVERAGE -#include <llmr/util/vec.hpp> #include <llmr/map/tile.hpp> + #include <forward_list> namespace llmr { -std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points); +std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points, const bool use_raster = false, const bool use_retina = false); } diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index 3506f4f43b..b24a7bc5ae 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -9,6 +9,7 @@ #include <llmr/geometry/glyph_atlas.hpp> #include <llmr/renderer/painter.hpp> #include <llmr/util/noncopyable.hpp> +#include <llmr/util/texturepool.hpp> #include <cstdint> #include <string> @@ -25,9 +26,9 @@ public: /* setup */ void setup(float pixelRatio = 1); void loadStyle(const uint8_t *const data, uint32_t bytes); - void loadSprite(const std::string& url); void loadSettings(); void resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb_height); + void toggleRaster(); /* callback */ bool render(); @@ -70,12 +71,12 @@ private: TileData::State addTile(const Tile::ID& id); TileData::State hasTile(const Tile::ID& id); - void update(); private: Settings& settings; Transform transform; + Texturepool texturepool; Style style; Painter painter; GlyphAtlas glyphAtlas; @@ -85,6 +86,8 @@ private: float pixel_ratio; + bool use_raster = false; + std::forward_list<Tile> tiles; std::forward_list<std::weak_ptr<TileData>> tile_data; }; diff --git a/include/llmr/map/tile.hpp b/include/llmr/map/tile.hpp index 2ae1a64fe1..0857cf17b5 100644 --- a/include/llmr/map/tile.hpp +++ b/include/llmr/map/tile.hpp @@ -18,7 +18,7 @@ public: int8_t z = 0; int32_t x = 0, y = 0; - inline ID(uint8_t z, uint32_t x, uint32_t y) + inline ID(int8_t z, int32_t x, int32_t y) : z(z), x(x), y(y) { } @@ -31,7 +31,7 @@ public: return z == rhs.z && x == rhs.x && y == rhs.y; } - ID parent(int32_t z) const; + ID parent(int8_t z) const; void normalize(); ID normalized() const; std::forward_list<ID> children(int32_t z) const; diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index c136f334ca..c5a664eb16 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -22,6 +22,7 @@ namespace llmr { class Style; +class Raster; class Bucket; class LayerDescription; class BucketDescription; @@ -54,7 +55,7 @@ public: }; public: - TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas); + TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster = false, const bool use_retina = false); ~TileData(); void request(); @@ -65,7 +66,10 @@ public: public: const Tile::ID id; + const bool use_raster; + const bool use_retina; std::atomic<State> state; + std::shared_ptr<Raster> raster; // Holds the actual geometries in this tile. DebugFontBuffer debugFontBuffer; diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index bb9e619cba..1724ebd358 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -7,8 +7,11 @@ namespace llmr { -extern const char *kTileURL; +extern const char *kTileVectorURL; +extern const char *kTileRasterURL; extern const char *kSpriteURL; +extern const int32_t kTileVectorMaxZoom; +extern const int32_t kTileRasterMaxZoom; namespace platform { diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index 71bb606153..0f8118ca5f 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -13,7 +13,7 @@ #include <llmr/shader/line_shader.hpp> #include <llmr/shader/linejoin_shader.hpp> #include <llmr/shader/point_shader.hpp> - +#include <llmr/shader/raster_shader.hpp> namespace llmr { @@ -44,10 +44,11 @@ public: void resize(int width, int height); void prepareClippingMask(); - void drawClippingMask(const mat4& matrix, uint8_t clip_id); + void drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque = true); void finishClippingMask(); private: void setupShaders(); + void renderRaster(const std::shared_ptr<TileData>& tile); void renderLayers(const std::shared_ptr<TileData>& tile, const std::vector<LayerDescription>& layers); void renderLayer(const std::shared_ptr<TileData>& tile_data, const LayerDescription& layer_desc); void renderDebug(const std::shared_ptr<TileData>& tile); @@ -82,6 +83,7 @@ private: std::unique_ptr<LinejoinShader> linejoinShader; std::unique_ptr<PatternShader> patternShader; std::unique_ptr<PointShader> pointShader; + std::unique_ptr<RasterShader> rasterShader; // Set up the stencil quad we're using to generate the stencil mask. VertexBuffer tileStencilBuffer = { @@ -98,6 +100,7 @@ private: VertexArrayObject coveringPlainArray; VertexArrayObject coveringPatternArray; + VertexArrayObject coveringRasterArray; // Set up the tile boundary lines we're using to draw the tile outlines. VertexBuffer tileBorderBuffer = { diff --git a/include/llmr/shader/raster_shader.hpp b/include/llmr/shader/raster_shader.hpp new file mode 100644 index 0000000000..00f423cbfc --- /dev/null +++ b/include/llmr/shader/raster_shader.hpp @@ -0,0 +1,28 @@ +#ifndef LLMR_RENDERER_SHADER_RASTER +#define LLMR_RENDERER_SHADER_RASTER + +#include <llmr/shader/shader.hpp> + +namespace llmr { + +class RasterShader : public Shader { +public: + RasterShader(); + + void bind(char *offset); + + void setImage(int32_t image); + void setOpacity(float opacity); + +private: + int32_t a_pos = -1; + + int32_t image = -1; + int32_t u_image = -1; + float opacity = 0.0f; + float u_opacity = 0.0f; +}; + +} + +#endif diff --git a/include/llmr/shader/shaders.hpp b/include/llmr/shader/shaders.hpp index 5cd4f493e8..4f85a33028 100644 --- a/include/llmr/shader/shaders.hpp +++ b/include/llmr/shader/shaders.hpp @@ -17,6 +17,7 @@ enum { PATTERN_SHADER, PLAIN_SHADER, POINT_SHADER, + RASTER_SHADER, TEXT_SHADER, SHADER_COUNT }; diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index 78ca76f237..f7d281e209 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -1,13 +1,12 @@ #ifndef LLMR_STYLE_SPRITE #define LLMR_STYLE_SPRITE - -#include <cstdint> #include <map> #include <string> #include <mutex> #include <memory> +#include <llmr/util/raster.hpp> #include <llmr/util/vec.hpp> namespace llmr { @@ -34,27 +33,24 @@ public: class Sprite : public std::enable_shared_from_this<Sprite> { public: - ~Sprite(); + Sprite(); void load(const std::string& base_url, float pixelRatio = 1); - void bind(bool linear = false); ImagePosition getPosition(const std::string& name, bool repeating = false); operator bool() const; +public: + std::shared_ptr<Raster> raster; + private: void parseJSON(const std::string& data); - void loadImage(const std::string& data); private: mutable std::mutex mtx; bool loaded = false; - uint32_t filter = 0; - uint32_t texture = 0; std::map<std::string, SpritePosition> pos; - uint32_t width = 0, height = 0; - char *img = nullptr; }; } diff --git a/include/llmr/util/raster.hpp b/include/llmr/util/raster.hpp new file mode 100644 index 0000000000..cf69663ce0 --- /dev/null +++ b/include/llmr/util/raster.hpp @@ -0,0 +1,72 @@ +#ifndef LLMR_UTIL_RASTER +#define LLMR_UTIL_RASTER + +#include <llmr/util/animation.hpp> +#include <llmr/util/texturepool.hpp> + +#include <string> +#include <mutex> + +namespace llmr { + +class Raster : public std::enable_shared_from_this<Raster> { + +public: + ~Raster(); + + // load image data + void load(const std::string& data); + + // set shared texture pool + void setTexturepool(Texturepool* texturepool); + + // bind current texture + void bind(bool linear = false); + + // loaded status + operator bool() const; + + // animations + void beginFadeInAnimation(); + bool needsAnimation() const; + void updateAnimations(); + +public: + // loaded image dimensions + uint32_t width = 0, height = 0; + + // has been uploaded to texture + bool textured = false; + + // the uploaded texture + uint32_t texture = 0; + + // texture opacity + double opacity = 0; + +private: + // load raw pixels + void loadImage(const std::string& data); + +private: + mutable std::mutex mtx; + + // raw pixels have been loaded + bool loaded = false; + + // shared texture pool + Texturepool* texturepool = nullptr; + + // min/mag filter + uint32_t filter = 0; + + // the raw pixels + char *img = nullptr; + + // fade in animation + std::shared_ptr<util::animation> fade_animation = nullptr; +}; + +} + +#endif diff --git a/include/llmr/util/texturepool.hpp b/include/llmr/util/texturepool.hpp new file mode 100644 index 0000000000..348d25d29a --- /dev/null +++ b/include/llmr/util/texturepool.hpp @@ -0,0 +1,25 @@ +#ifndef LLMR_UTIL_TEXTUREPOOL +#define LLMR_UTIL_TEXTUREPOOL + +#include <llmr/util/noncopyable.hpp> +#include <llmr/platform/gl.hpp> + +#include <set> +#include <mutex> + +namespace llmr { + +class Texturepool : private util::noncopyable { + +public: + GLuint getTextureID(); + void removeTextureID(GLuint texture_id); + void clearTextureIDs(); + +private: + std::set<GLuint> texture_ids; +}; + +} + +#endif diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm index 6713f33457..7cc616c0e0 100644 --- a/ios/MBXViewController.mm +++ b/ios/MBXViewController.mm @@ -121,6 +121,10 @@ class MBXMapView threeFingerLongPress.numberOfTouchesRequired = 3; [self.view addGestureRecognizer:threeFingerLongPress]; + UILongPressGestureRecognizer *fourFingerLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleFourFingerLongPressGesture:)]; + fourFingerLongPress.numberOfTouchesRequired = 4; + [self.view addGestureRecognizer:fourFingerLongPress]; + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { UILongPressGestureRecognizer *quickZoom = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleQuickZoomGesture:)]; @@ -360,6 +364,16 @@ class MBXMapView [self updateRender]; } +- (void)handleFourFingerLongPressGesture:(UILongPressGestureRecognizer *)fourFingerLongPress +{ + mapView->map.cancelAnimations(); + + if (fourFingerLongPress.state == UIGestureRecognizerStateBegan) + mapView->map.toggleRaster(); + + [self updateRender]; +} + - (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom { mapView->map.cancelAnimations(); diff --git a/src/map/coverage.cpp b/src/map/coverage.cpp index c040811b52..b98518a588 100644 --- a/src/map/coverage.cpp +++ b/src/map/coverage.cpp @@ -1,4 +1,7 @@ #include <llmr/map/coverage.hpp> +#include <llmr/util/constants.hpp> +#include <llmr/util/vec.hpp> + #include <functional> #include <cmath> @@ -64,15 +67,28 @@ void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine); } -std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points) { +std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points, const bool use_raster, const bool use_retina) { int32_t dim = pow(2, zoom); std::forward_list<llmr::Tile::ID> tiles; - auto scanLine = [&tiles, zoom](int32_t x0, int32_t x1, int32_t y, int32_t ymax) { + auto scanLine = [&tiles, zoom, use_raster, use_retina](int32_t x0, int32_t x1, int32_t y, int32_t ymax) { int32_t x; if (y >= 0 && y <= ymax) { for (x = x0; x < x1; x++) { - tiles.emplace_front(zoom, x, y); + if (use_raster) { + uint32_t search_zoom = zoom; + search_zoom += (uint32_t)((llmr::util::tileSize / 256.0f) - 1.0f); + if (use_retina) { + search_zoom += 1; + } + Tile::ID id = Tile::ID(zoom, x, y); + auto ids = id.children(search_zoom); + for (const Tile::ID& child_id : ids) { + tiles.emplace_front(child_id.z, child_id.x, child_id.y); + } + } else { + tiles.emplace_front(zoom, x, y); + } } } }; diff --git a/src/map/map.cpp b/src/map/map.cpp index 1a9e6c609b..961cf7423c 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -4,6 +4,7 @@ #include <llmr/style/resources.hpp> #include <llmr/style/sprite.hpp> #include <llmr/map/coverage.hpp> +#include <llmr/util/animation.hpp> #include <algorithm> @@ -12,11 +13,12 @@ using namespace llmr; Map::Map(Settings& settings) : settings(settings), transform(), + texturepool(), style(), painter(transform, settings, style), glyphAtlas(1024, 1024), min_zoom(0), - max_zoom(14) { + max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) { } Map::~Map() { @@ -24,6 +26,7 @@ Map::~Map() { } void Map::setup(float pixelRatio) { + painter.setup(); pixel_ratio = pixelRatio; @@ -34,10 +37,6 @@ void Map::setup(float pixelRatio) { style.loadJSON(resources::style, resources::style_size); } -void Map::loadSprite(const std::string& url) { - -} - void Map::loadStyle(const uint8_t *const data, uint32_t bytes) { style.loadJSON(data, bytes); update(); @@ -61,6 +60,13 @@ void Map::resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb update(); } +void Map::toggleRaster() { + use_raster = ! use_raster; + max_zoom = (use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom); + tiles.clear(); + update(); +} + void Map::moveBy(double dx, double dy, double duration) { transform.moveBy(dx, dy, duration); update(); @@ -279,7 +285,7 @@ TileData::State Map::addTile(const Tile::ID& id) { if (!new_tile.data) { // If we don't find working tile data, we're just going to load it. - new_tile.data = std::make_shared<TileData>(normalized_id, style, glyphAtlas); + new_tile.data = std::make_shared<TileData>(normalized_id, style, glyphAtlas, use_raster, (pixel_ratio > 1.0)); new_tile.data->request(); tile_data.push_front(new_tile.data); } @@ -353,12 +359,14 @@ bool Map::updateTiles() { int32_t min_covering_zoom = zoom - 10; if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom; + int32_t max_dim = pow(2, zoom); + // Map four viewport corners to pixel coordinates box box = transform.mapCornersToBox(zoom); // Performs a scanline algorithm search that covers the rectangle of the box // and sorts them by proximity to the center. - std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box); + std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box, use_raster, (pixel_ratio > 1.0)); // Retain is a list of tiles that we shouldn't delete, even if they are not // the most ideal tile for the current viewport. This may include tiles like @@ -370,6 +378,9 @@ bool Map::updateTiles() { const TileData::State state = addTile(id); if (state != TileData::State::parsed) { + if (use_raster && (transform.rotating || transform.scaling || transform.panning)) + break; + // The tile we require is not yet loaded. Try to find a parent or // child tile that we already have. @@ -434,6 +445,10 @@ bool Map::render() { glyphAtlas.bind(); + if (*style.sprite->raster && !style.sprite->raster->textured) { + style.sprite->raster->setTexturepool(&texturepool); + } + bool changed = updateTiles(); painter.clear(); @@ -450,13 +465,20 @@ bool Map::render() { transform.matrixFor(tile.matrix, tile.id); matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); tile.clip_id = i++; - painter.drawClippingMask(tile.matrix, tile.clip_id); + painter.drawClippingMask(tile.matrix, tile.clip_id, !tile.data->use_raster); } } painter.finishClippingMask(); for (const Tile& tile : tiles) { if (tile.data && tile.data->state == TileData::State::parsed) { + if (tile.data->use_raster && *tile.data->raster && !tile.data->raster->textured) { + tile.data->raster->setTexturepool(&texturepool); + tile.data->raster->beginFadeInAnimation(); + } + if (tile.data->use_raster && tile.data->raster->needsAnimation()) { + tile.data->raster->updateAnimations(); + } painter.render(tile); } } diff --git a/src/map/tile.cpp b/src/map/tile.cpp index 163aa60afd..6ec05beca7 100644 --- a/src/map/tile.cpp +++ b/src/map/tile.cpp @@ -8,15 +8,14 @@ Tile::Tile(const ID& id) : id(id) { } -Tile::ID Tile::ID::parent(int32_t parent_z) const { +Tile::ID Tile::ID::parent(int8_t parent_z) const { assert(parent_z < z); - ID pos(z, x, y); - while (pos.z > parent_z) { - --pos.z; - pos.x = floor(pos.x / 2); - pos.y = floor(pos.y / 2); - } - return pos; + int32_t dim = pow(2, z - parent_z); + return Tile::ID{ + parent_z, + (x >= 0 ? x : x - dim + 1) / dim, + y / dim + }; } std::forward_list<Tile::ID> Tile::ID::children(int32_t child_z) const { diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 57c8885e38..7b070f8696 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -6,13 +6,17 @@ #include <llmr/geometry/point_buffer.hpp> #include <llmr/geometry/text_buffer.hpp> #include <llmr/geometry/elements_buffer.hpp> +#include <llmr/util/raster.hpp> #include <llmr/util/string.hpp> using namespace llmr; -TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas) +TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster, const bool use_retina) : id(id), + use_raster(use_raster), + use_retina(use_retina), state(State::initial), + raster(), fillVertexBuffer(std::make_shared<FillVertexBuffer>()), lineVertexBuffer(std::make_shared<LineVertexBuffer>()), pointVertexBuffer(std::make_shared<PointVertexBuffer>()), @@ -41,9 +45,13 @@ const std::string TileData::toString() const { void TileData::request() { state = State::loading; - // Create http request - std::string url = util::sprintf(kTileURL, - id.z, id.x, id.y); + std::string url; + + if (use_raster) { + url = util::sprintf(kTileRasterURL, id.z, id.x, id.y, (use_retina ? "@2x" : "")); + } else { + url = util::sprintf(kTileVectorURL, id.z, id.x, id.y); + } // Note: Somehow this feels slower than the change to request_http() std::weak_ptr<TileData> weak_tile = shared_from_this(); @@ -77,6 +85,13 @@ bool TileData::parse() { return false; } + if (use_raster) { + raster = std::make_shared<Raster>(); + raster->load(data); + state = State::parsed; + return true; + } + try { // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index e2d6d828cf..6d0f845bde 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -1,4 +1,8 @@ #include <llmr/platform/platform.hpp> -const char *llmr::kTileURL = "http://localhost:3333/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf"; +const char *llmr::kTileVectorURL = "http://localhost:3333/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf"; +const char *llmr::kTileRasterURL = "https://a.tiles.mapbox.com/v3/justin.map-pgygbwdm/%d/%d/%d%s.png256"; const char *llmr::kSpriteURL = "http://localhost:3333/images/sprite"; + +const int32_t llmr::kTileVectorMaxZoom = 14; +const int32_t llmr::kTileRasterMaxZoom = 21; diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index ed7cc506e3..4e625ab435 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -16,6 +16,7 @@ #include <llmr/platform/gl.hpp> #include <llmr/style/style.hpp> #include <llmr/style/sprite.hpp> +#include <llmr/util/raster.hpp> using namespace llmr; @@ -27,7 +28,6 @@ Painter::Painter(Transform& transform, Settings& settings, Style& style) style(style) { } - void Painter::setup() { setupShaders(); @@ -37,6 +37,7 @@ void Painter::setup() { assert(lineShader); assert(linejoinShader); assert(patternShader); + assert(rasterShader); // Blending @@ -63,6 +64,7 @@ void Painter::setupShaders() { linejoinShader = std::make_unique<LinejoinShader>(); patternShader = std::make_unique<PatternShader>(); pointShader = std::make_unique<PointShader>(); + rasterShader = std::make_unique<RasterShader>(); } void Painter::resize(int width, int height) { @@ -114,9 +116,11 @@ void Painter::prepareClippingMask() { coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); } -void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) { +void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque) { plainShader->setMatrix(matrix); - plainShader->setColor(style.computed.background.color); + if (opaque) { + plainShader->setColor(style.computed.background.color); + } glStencilFunc(GL_ALWAYS, clip_id, 0xFF); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); @@ -149,13 +153,29 @@ void Painter::render(const Tile& tile) { matrix = tile.matrix; glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF); - renderLayers(tile.data, style.layers); + if (tile.data->use_raster) { + renderRaster(tile.data); + } else { + renderLayers(tile.data, style.layers); + } if (settings.debug) { renderDebug(tile.data); } } +void Painter::renderRaster(const std::shared_ptr<TileData>& tile_data) { + useProgram(rasterShader->program); + rasterShader->setMatrix(matrix); + rasterShader->setImage(0); + rasterShader->setOpacity(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::renderLayers(const std::shared_ptr<TileData>& tile_data, const std::vector<LayerDescription>& layers) { float strata_thickness = 1.0f / (layers.size() + 1); @@ -290,7 +310,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }}); patternShader->setColor(fill_color); patternShader->setMix(mix); - style.sprite->bind(true); + style.sprite->raster->bind(true); // Draw the actual triangles into the color & stencil buffer. glDepthRange(strata + strata_epsilon, 1.0f); @@ -447,7 +467,9 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co #endif pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }}); - style.sprite->bind(transform.rotating || transform.scaling || transform.panning); + if (*style.sprite->raster) { + style.sprite->raster->bind(transform.rotating || transform.scaling || transform.panning); + } glDepthRange(strata, 1.0f); bucket.drawPoints(*pointShader); } diff --git a/src/shader/raster.fragment.glsl b/src/shader/raster.fragment.glsl new file mode 100644 index 0000000000..feb0a58f07 --- /dev/null +++ b/src/shader/raster.fragment.glsl @@ -0,0 +1,8 @@ +uniform sampler2D u_image; +uniform float u_opacity; + +varying vec2 v_pos; + +void main() { + gl_FragColor = texture2D(u_image, v_pos) * u_opacity; +} diff --git a/src/shader/raster.vertex.glsl b/src/shader/raster.vertex.glsl new file mode 100644 index 0000000000..75d6c8d145 --- /dev/null +++ b/src/shader/raster.vertex.glsl @@ -0,0 +1,10 @@ +uniform mat4 u_matrix; + +attribute vec2 a_pos; + +varying vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + v_pos = a_pos / 4096.0; +} diff --git a/src/shader/raster_shader.cpp b/src/shader/raster_shader.cpp new file mode 100644 index 0000000000..5852c45c7b --- /dev/null +++ b/src/shader/raster_shader.cpp @@ -0,0 +1,47 @@ +#include <llmr/shader/raster_shader.hpp> +#include <llmr/shader/shaders.hpp> +#include <llmr/platform/gl.hpp> + +#include <cstdio> + +using namespace llmr; + +RasterShader::RasterShader() + : Shader( + shaders[RASTER_SHADER].vertex, + shaders[RASTER_SHADER].fragment + ) { + if (!valid) { + fprintf(stderr, "invalid raster shader\n"); + return; + } + + a_pos = glGetAttribLocation(program, "a_pos"); + + u_matrix = glGetUniformLocation(program, "u_matrix"); + u_opacity = glGetUniformLocation(program, "u_opacity"); + + // fprintf(stderr, "RasterShader:\n"); + // fprintf(stderr, " - u_matrix: %d\n", u_matrix); + // fprintf(stderr, " - u_image: %d\n", u_image); + // fprintf(stderr, " - u_opacity: %f\n", u_opacity); +} + +void RasterShader::bind(char *offset) { + glEnableVertexAttribArray(a_pos); + glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset); +} + +void RasterShader::setImage(int32_t new_image) { + if (image != new_image) { + glUniform1i(u_image, new_image); + image = new_image; + } +} + +void RasterShader::setOpacity(float new_opacity) { + if (opacity != new_opacity) { + glUniform1f(u_opacity, new_opacity); + opacity = new_opacity; + } +} diff --git a/src/shader/shaders.cpp b/src/shader/shaders.cpp index f5cc884c26..ff83a1af27 100644 --- a/src/shader/shaders.cpp +++ b/src/shader/shaders.cpp @@ -29,6 +29,10 @@ const shader_source llmr::shaders[SHADER_COUNT] = { "#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", }, { + "uniform 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", + "uniform 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", + }, + { "attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n", "uniform vec4 u_color;\n\nvoid main() {\n gl_FragColor = u_color;\n}\n", } diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index 2052c54370..3998afc628 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -1,10 +1,8 @@ #include <llmr/style/sprite.hpp> +#include <llmr/util/raster.hpp> #include <string> #include <llmr/platform/platform.hpp> -#include <llmr/platform/gl.hpp> - -#include <png.h> #include <rapidjson/document.h> @@ -22,16 +20,8 @@ ImagePosition::ImagePosition(const vec2<uint16_t>& size, vec2<float> tl, vec2<fl tl(tl), br(br) {} - -Sprite::~Sprite() { - if (img) { - free(img); - } -} - -Sprite::operator bool() const { - std::lock_guard<std::mutex> lock(mtx); - return loaded; +Sprite::Sprite() { + raster = std::make_shared<Raster>(); } void Sprite::load(const std::string& base_url, float pixelRatio) { @@ -39,7 +29,7 @@ void Sprite::load(const std::string& base_url, float pixelRatio) { auto complete = [sprite]() { std::lock_guard<std::mutex> lock(sprite->mtx); - if (sprite->img && sprite->pos.size()) { + if (*sprite->raster && sprite->pos.size()) { sprite->loaded = true; platform::restart(); fprintf(stderr, "sprite loaded\n"); @@ -58,13 +48,17 @@ void Sprite::load(const std::string& base_url, float pixelRatio) { platform::request_http(base_url + suffix + ".png", [sprite](const platform::Response & res) { if (res.code == 200) { - sprite->loadImage(res.body); + sprite->raster->load(res.body); } else { fprintf(stderr, "failed to load sprite image\n"); } }, complete); } +Sprite::operator bool() const { + std::lock_guard<std::mutex> lock(mtx); + return loaded; +} void Sprite::parseJSON(const std::string& data) { std::lock_guard<std::mutex> lock(mtx); @@ -96,118 +90,8 @@ void Sprite::parseJSON(const std::string& data) { } } -struct Buffer { - Buffer(const std::string& data) - : data(data.data()), length(data.size()) {} - const char *const data = 0; - const size_t length = 0; - size_t pos = 0; -}; - - -void readCallback(png_structp png, png_bytep data, png_size_t length) { - Buffer *reader = static_cast<Buffer *>(png_get_io_ptr(png)); - - // Read `length` bytes into `data`. - if (reader->pos + length > reader->length) { - png_error(png, "Read Error"); - } else { - memcpy(data, reader->data + reader->pos, length); - reader->pos += length; - } -} - -void errorHandler(png_structp png, png_const_charp error_msg) { - throw std::runtime_error(error_msg); -} - -void warningHandler(png_structp png, png_const_charp error_msg) { - fprintf(stderr, "PNG: %s\n", error_msg); -} - -void Sprite::loadImage(const std::string& data) { - std::lock_guard<std::mutex> lock(mtx); - - Buffer buffer(data); - - if (buffer.length < 8 || !png_check_sig((const png_bytep)buffer.data, 8)) { - fprintf(stderr, "image is not a valid PNG image\n"); - return; - } - - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, errorHandler, warningHandler); - assert(png); - - png_infop info = png_create_info_struct(png); - assert(info); - - int depth, color, interlace; - - try { - png_set_read_fn(png, (png_voidp)&buffer, readCallback); - png_read_info(png, info); - png_get_IHDR(png, info, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr); - bool alpha = (color & PNG_COLOR_MASK_ALPHA) || png_get_valid(png, info, PNG_INFO_tRNS); - - // From http://trac.mapnik.org/browser/trunk/src/png_reader.cpp - if (color == PNG_COLOR_TYPE_PALETTE) - png_set_expand(png); - if (color == PNG_COLOR_TYPE_GRAY) - png_set_expand(png); - if (png_get_valid(png, info, PNG_INFO_tRNS)) - png_set_expand(png); - if (depth == 16) - png_set_strip_16(png); - if (depth < 8) - png_set_packing(png); - if (color == PNG_COLOR_TYPE_GRAY || - color == PNG_COLOR_TYPE_GRAY_ALPHA) - png_set_gray_to_rgb(png); - - if (interlace == PNG_INTERLACE_ADAM7) - png_set_interlace_handling(png); - - // Always add an alpha channel. - if (!alpha) { - png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER); - } - - double gamma; - if (png_get_gAMA(png, info, &gamma)) - png_set_gamma(png, 2.2, gamma); - - png_read_update_info(png, info); - - png_size_t rowbytes = png_get_rowbytes(png, info); - assert(width * 4 == rowbytes); - - img = (char *)malloc(width * height * 4); - char *surface = img; - assert(surface); - - png_bytep row_pointers[height]; - for (unsigned i = 0; i < height; ++i) { - row_pointers[i] = (png_bytep)(surface + (i * rowbytes)); - } - - // Read image data - png_read_image(png, row_pointers); - - png_read_end(png, nullptr); - } catch (std::exception& e) { - fprintf(stderr, "loading PNG failed: %s\n", e.what()); - png_destroy_read_struct(&png, &info, nullptr); - if (img) { - free(img); - img = nullptr; - } - width = 0; - height = 0; - } -} - ImagePosition Sprite::getPosition(const std::string& name, bool repeating) { - if (!*this) return {}; + if (!*this->raster) 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 @@ -224,38 +108,12 @@ ImagePosition Sprite::getPosition(const std::string& name, bool repeating) { pos.height }, { - (float)(pos.x + offset) / width, - (float)(pos.y + offset) / height + (float)(pos.x + offset) / raster->width, + (float)(pos.y + offset) / raster->height }, { - (float)(pos.x + pos.width - 2 * offset) / width, - (float)(pos.y + pos.height - 2 * offset) / height + (float)(pos.x + pos.width - 2 * offset) / raster->width, + (float)(pos.y + pos.height - 2 * offset) / raster->height } }; } - -void Sprite::bind(bool linear) { - if (!width || !height) { - // fprintf(stderr, "trying to bind texture without dimension\n"); - return; - } - - if (!texture) { - glGenTextures(1, &texture); - glActiveTexture(GL_TEXTURE0); - 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); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img); - } else { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); - } - - GLuint filter = linear ? GL_LINEAR : GL_NEAREST; - if (filter != this->filter) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); - this->filter = filter; - } -} diff --git a/src/util/raster.cpp b/src/util/raster.cpp new file mode 100644 index 0000000000..566f0941e7 --- /dev/null +++ b/src/util/raster.cpp @@ -0,0 +1,191 @@ +#include <llmr/util/raster.hpp> + +#include <memory> +#include <cassert> + +#include <llmr/platform/gl.hpp> + +#include <png.h> + +using namespace llmr; + +Raster::~Raster() { + if (img && !textured) { + free(img); + } else if (textured) { + texturepool->removeTextureID(texture); + } +} + +Raster::operator bool() const { + std::lock_guard<std::mutex> lock(mtx); + return loaded; +} + +void Raster::load(const std::string& data) { + std::shared_ptr<Raster> raster = shared_from_this(); + + raster->loadImage(data); + + std::lock_guard<std::mutex> lock(raster->mtx); + if (raster->img) { + raster->loaded = true; + } +} + +struct Buffer { + Buffer(const std::string& data) + : data(data.data()), length(data.size()) {} + const char *const data = 0; + const size_t length = 0; + size_t pos = 0; +}; + +void readCallback(png_structp png, png_bytep data, png_size_t length) { + Buffer *reader = static_cast<Buffer *>(png_get_io_ptr(png)); + + // Read `length` bytes into `data`. + if (reader->pos + length > reader->length) { + png_error(png, "Read Error"); + } else { + memcpy(data, reader->data + reader->pos, length); + reader->pos += length; + } +} + +void errorHandler(png_structp png, png_const_charp error_msg) { + throw std::runtime_error(error_msg); +} + +void warningHandler(png_structp png, png_const_charp error_msg) { + fprintf(stderr, "PNG: %s\n", error_msg); +} + +void Raster::loadImage(const std::string& data) { + std::lock_guard<std::mutex> lock(mtx); + + Buffer buffer(data); + + if (buffer.length < 8 || !png_check_sig((const png_bytep)buffer.data, 8)) { + fprintf(stderr, "image is not a valid PNG image\n"); + return; + } + + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, errorHandler, warningHandler); + assert(png); + + png_infop info = png_create_info_struct(png); + assert(info); + + int depth, color, interlace; + + try { + png_set_read_fn(png, (png_voidp)&buffer, readCallback); + png_read_info(png, info); + png_get_IHDR(png, info, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr); + bool alpha = (color & PNG_COLOR_MASK_ALPHA) || png_get_valid(png, info, PNG_INFO_tRNS); + + // From http://trac.mapnik.org/browser/trunk/src/png_reader.cpp + if (color == PNG_COLOR_TYPE_PALETTE) + png_set_expand(png); + if (color == PNG_COLOR_TYPE_GRAY) + png_set_expand(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_expand(png); + if (depth == 16) + png_set_strip_16(png); + if (depth < 8) + png_set_packing(png); + if (color == PNG_COLOR_TYPE_GRAY || + color == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + if (interlace == PNG_INTERLACE_ADAM7) + png_set_interlace_handling(png); + + // Always add an alpha channel. + if (!alpha) { + png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER); + } + + double gamma; + if (png_get_gAMA(png, info, &gamma)) + png_set_gamma(png, 2.2, gamma); + + png_read_update_info(png, info); + + png_size_t rowbytes = png_get_rowbytes(png, info); + assert(width * 4 == rowbytes); + + img = (char *)malloc(width * height * 4); + char *surface = img; + assert(surface); + + png_bytep row_pointers[height]; + for (unsigned i = 0; i < height; ++i) { + row_pointers[i] = (png_bytep)(surface + (i * rowbytes)); + } + + // Read image data + png_read_image(png, row_pointers); + + png_read_end(png, nullptr); + + png_destroy_read_struct(&png, &info, nullptr); + } catch (std::exception& e) { + fprintf(stderr, "loading PNG failed: %s\n", e.what()); + png_destroy_read_struct(&png, &info, nullptr); + if (img) { + free(img); + img = nullptr; + } + width = 0; + height = 0; + } +} + +void Raster::setTexturepool(Texturepool* new_texturepool) { + texturepool = new_texturepool; +} + +void Raster::bind(bool linear) { + if (!width || !height) { + fprintf(stderr, "trying to bind texture without dimension\n"); + return; + } + + if (img && !textured && texturepool) { + 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); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img); + free(img); + img = nullptr; + textured = true; + } else { + glBindTexture(GL_TEXTURE_2D, texture); + } + + GLuint filter = linear ? GL_LINEAR : GL_NEAREST; + if (filter != this->filter) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + this->filter = filter; + } +} + +void Raster::beginFadeInAnimation() { + fade_animation = std::make_shared<util::ease_animation>(opacity, 1.0, opacity, 0.25); +} + +bool Raster::needsAnimation() const { + return fade_animation != nullptr; +} + +void Raster::updateAnimations() { + if (fade_animation->update() == util::animation::complete) { + fade_animation = nullptr; + } +} + diff --git a/src/util/texturepool.cpp b/src/util/texturepool.cpp new file mode 100644 index 0000000000..c0857caa4d --- /dev/null +++ b/src/util/texturepool.cpp @@ -0,0 +1,56 @@ +#include <llmr/util/texturepool.hpp> + +#include <vector> + +#define TextureMax 64 + +using namespace llmr; + +GLuint Texturepool::getTextureID() { + if (texture_ids.empty()) + { + GLuint new_texture_ids[TextureMax]; + glGenTextures(TextureMax, new_texture_ids); + for (uint32_t id = 0; id < TextureMax; id++) { + texture_ids.insert(new_texture_ids[id]); + } + } + + GLuint id = 0; + + if (!texture_ids.empty()) { + std::set<GLuint>::iterator id_iterator = texture_ids.begin(); + id = *id_iterator; + texture_ids.erase(id_iterator); + } + + return id; +} + +void Texturepool::removeTextureID(GLuint texture_id) { + bool needs_clear = false; + + glBindTexture(GL_TEXTURE_2D, texture_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + + texture_ids.insert(texture_id); + + if (texture_ids.size() > TextureMax) + needs_clear = true; + + if (needs_clear) + clearTextureIDs(); +} + +void Texturepool::clearTextureIDs() { + std::vector<GLuint> ids_to_remove; + ids_to_remove.reserve(texture_ids.size()); + + for (std::set<GLuint>::iterator id_iterator = texture_ids.begin(); id_iterator != texture_ids.end(); ++id_iterator) + ids_to_remove.push_back(*id_iterator); + + if (!ids_to_remove.empty()) + glDeleteTextures((GLsizei)ids_to_remove.size(), &ids_to_remove[0]); + + texture_ids.clear(); +} |