From bc29ed5a9c5bd7703f861b4839235a55f6b8f343 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 12 May 2014 14:00:10 -0700 Subject: [PATCH 1/9] refs #166: fix for std::stoll et al. not handling strings --- include/llmr/style/value.hpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/include/llmr/style/value.hpp b/include/llmr/style/value.hpp index 0dc53248b..40be29f42 100644 --- a/include/llmr/style/value.hpp +++ b/include/llmr/style/value.hpp @@ -34,21 +34,51 @@ template <> struct string_to_number { template <> struct string_to_number { double operator()(std::string const &str) const { int val = string_to_bool(str); - return val < 0 ? std::stod(str) : val; + if (val < 0) { + try { + double b = std::stod(str); + return b; + } catch (std::invalid_argument const& ex) { + return false; + } + } else { + return val; + } + return false; } }; template <> struct string_to_number { int64_t operator()(std::string const &str) const { int val = string_to_bool(str); - return val < 0 ? std::stoll(str) : val; + if (val < 0) { + try { + long long b = std::stoll(str); + return b; + } catch (std::invalid_argument const& ex) { + return false; + } + } else { + return val; + } + return false; } }; template <> struct string_to_number { uint64_t operator()(std::string const &str) const { int val = string_to_bool(str); - return val < 0 ? std::stoull(str) : val; + if (val < 0) { + try { + unsigned long long b = std::stoull(str); + return b; + } catch (std::invalid_argument const& ex) { + return false; + } + } else { + return val; + } + return false; } }; From 654520ea343e0c8572bbf08be9963be098f4fbc8 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Mon, 12 May 2014 17:14:28 -0700 Subject: [PATCH 2/9] Revert "refs #166: fix for std::stoll et al. not handling strings" This reverts commit bc29ed5a9c5bd7703f861b4839235a55f6b8f343. --- include/llmr/style/value.hpp | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/include/llmr/style/value.hpp b/include/llmr/style/value.hpp index 40be29f42..0dc53248b 100644 --- a/include/llmr/style/value.hpp +++ b/include/llmr/style/value.hpp @@ -34,51 +34,21 @@ template <> struct string_to_number { template <> struct string_to_number { double operator()(std::string const &str) const { int val = string_to_bool(str); - if (val < 0) { - try { - double b = std::stod(str); - return b; - } catch (std::invalid_argument const& ex) { - return false; - } - } else { - return val; - } - return false; + return val < 0 ? std::stod(str) : val; } }; template <> struct string_to_number { int64_t operator()(std::string const &str) const { int val = string_to_bool(str); - if (val < 0) { - try { - long long b = std::stoll(str); - return b; - } catch (std::invalid_argument const& ex) { - return false; - } - } else { - return val; - } - return false; + return val < 0 ? std::stoll(str) : val; } }; template <> struct string_to_number { uint64_t operator()(std::string const &str) const { int val = string_to_bool(str); - if (val < 0) { - try { - unsigned long long b = std::stoull(str); - return b; - } catch (std::invalid_argument const& ex) { - return false; - } - } else { - return val; - } - return false; + return val < 0 ? std::stoull(str) : val; } }; From d8fa9aa322d00e796402cf99a35bdeae9b193c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 12:01:29 +0200 Subject: [PATCH 3/9] move weak_ptr list of tile_data objects to a map for faster lookups --- include/llmr/map/source.hpp | 3 ++- include/llmr/util/std.hpp | 17 +++++++++++++++++ src/map/source.cpp | 16 +++++++--------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index 7d47d61b7..da2d87ae8 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace llmr { @@ -69,7 +70,7 @@ private: const int32_t max_zoom; std::forward_list tiles; - std::forward_list> tile_data; + std::map> tile_data; }; } diff --git a/include/llmr/util/std.hpp b/include/llmr/util/std.hpp index 4ef28d5f8..c09aa5ff6 100644 --- a/include/llmr/util/std.hpp +++ b/include/llmr/util/std.hpp @@ -11,6 +11,23 @@ template return ::std::unique_ptr(new T(::std::forward(args)...)); } + +template +void erase_if(Container &container, ForwardIterator it, const ForwardIterator end, Predicate pred) { + while (it != end) { + if (pred(*it)) { + container.erase(it++); + } else { + ++it; + } + } +} + +template +void erase_if(Container &container, Predicate pred) { + erase_if(container, container.begin(), container.end(), pred); +} + } #endif diff --git a/src/map/source.cpp b/src/map/source.cpp index 384cb0a62..5571f74c7 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -110,13 +111,10 @@ TileData::State Source::addTile(const Tile::ID& id) { // Try to find the associated TileData object. const Tile::ID normalized_id = id.normalized(); - auto it = std::find_if(tile_data.begin(), tile_data.end(), [normalized_id](const std::weak_ptr& tile_data) { - return !tile_data.expired() && tile_data.lock()->id == normalized_id; - }); - + auto it = tile_data.find(normalized_id); if (it != tile_data.end()) { // Create a shared_ptr handle. Note that this might be empty! - new_tile.data = it->lock(); + new_tile.data = it->second.lock(); } if (new_tile.data && new_tile.data->state == TileData::State::obsolete) { @@ -138,7 +136,7 @@ TileData::State Source::addTile(const Tile::ID& id) { } new_tile.data->request(); - tile_data.push_front(new_tile.data); + tile_data.emplace(new_tile.data->id, new_tile.data); } return new_tile.data->state; @@ -269,9 +267,9 @@ bool Source::updateTiles() { return a.id.z < b.id.z; }); - // Remove all the expired pointers from the list. - tile_data.remove_if([&retain_data](const std::weak_ptr& tile_data) { - const std::shared_ptr tile = tile_data.lock(); + // Remove all the expired pointers from the set. + std::erase_if(tile_data, [&retain_data](const std::pair>& pair) { + const std::shared_ptr tile = pair.second.lock(); if (!tile) { return true; } From ab71db336c43e8244a099d7a9c665db4e279f9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 12:20:59 +0200 Subject: [PATCH 4/9] move tiles to std::map for faster lookups --- include/llmr/map/source.hpp | 2 +- src/map/source.cpp | 43 ++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index da2d87ae8..40b3bf4cf 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -69,7 +69,7 @@ private: const int32_t min_zoom; const int32_t max_zoom; - std::forward_list tiles; + std::map> tiles; std::map> tile_data; }; diff --git a/src/map/source.cpp b/src/map/source.cpp index 5571f74c7..a5985668f 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -34,7 +34,8 @@ bool Source::update() { } void Source::updateClipIDs(const std::map &mapping) { - std::for_each(tiles.begin(), tiles.end(), [&mapping](Tile &tile) { + std::for_each(tiles.begin(), tiles.end(), [&mapping](std::pair> &pair) { + Tile &tile = *pair.second; auto it = mapping.find(tile.id); if (it != mapping.end()) { tile.clip = it->second; @@ -48,7 +49,8 @@ size_t Source::prepareRender(const TransformState &transform) { if (!enabled) return 0; size_t masks = 0; - for (Tile& tile : tiles) { + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; gl::group group(util::sprintf<32>("mask %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); transform.matrixFor(tile.matrix, tile.id); matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix); @@ -63,7 +65,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription if (!enabled) return; gl::group group(std::string("layer: ") + layer_desc.name); - for (const Tile& tile : tiles) { + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; if (tile.data && tile.data->state == TileData::State::parsed) { painter.renderTileLayer(tile, layer_desc); } @@ -73,7 +76,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription void Source::finishRender() { if (!enabled) return; - for (const Tile& tile : tiles) { + for (std::pair> &pair : tiles) { + Tile &tile = *pair.second; painter.renderTileDebug(tile); } } @@ -81,14 +85,18 @@ void Source::finishRender() { std::forward_list Source::getIDs() const { std::forward_list ptrs; - std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const Tile &tile) { + + std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const std::pair> &pair) { + Tile &tile = *pair.second; return tile.id; }); return ptrs; } TileData::State Source::hasTile(const Tile::ID& id) { - for (const Tile& tile : tiles) { + auto it = tiles.find(id); + if (it != tiles.end()) { + Tile &tile = *it->second; if (tile.id == id && tile.data) { return tile.data->state; } @@ -104,8 +112,8 @@ TileData::State Source::addTile(const Tile::ID& id) { return state; } - tiles.emplace_front(id); - Tile& new_tile = tiles.front(); + auto pos = tiles.emplace(id, std::make_unique(id)); + Tile& new_tile = *pos.first->second; // We couldn't find the tile in the list. Create a new one. // Try to find the associated TileData object. @@ -249,31 +257,26 @@ bool Source::updateTiles() { // Remove tiles that we definitely don't need, i.e. tiles that are not on // the required list. - std::forward_list retain_data; - tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) { + std::set retain_data; + std::erase_if(tiles, [&retain, &retain_data, &changed](std::pair> &pair) { + Tile &tile = *pair.second; bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end(); if (obsolete) { changed = true; } else { - retain_data.push_front(tile.data->id); + retain_data.insert(tile.data->id); } return obsolete; }); - // Sort tiles by zoom level, front to back. - // We're painting front-to-back, so we want to draw more detailed tiles first - // before filling in other parts with lower zoom levels. - tiles.sort([](const Tile & a, const Tile & b) { - return a.id.z < b.id.z; - }); - // Remove all the expired pointers from the set. - std::erase_if(tile_data, [&retain_data](const std::pair>& pair) { + std::erase_if(tile_data, [&retain_data](std::pair> &pair) { const std::shared_ptr tile = pair.second.lock(); if (!tile) { return true; } - bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end(); + + bool obsolete = retain_data.find(tile->id) == retain_data.end(); if (obsolete) { tile->cancel(); return true; From 72c0410a4d040a25b7500bd277095d5e25897370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 13:16:47 +0200 Subject: [PATCH 5/9] save/restore state on ios --- ios/MBXAppDelegate.m | 11 +++++++++++ ios/MBXViewController.h | 3 +++ ios/MBXViewController.mm | 32 ++++++++++++++++++++++---------- ios/llmr-app.gyp | 2 ++ 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/ios/MBXAppDelegate.m b/ios/MBXAppDelegate.m index 4b860a361..aa048ca67 100644 --- a/ios/MBXAppDelegate.m +++ b/ios/MBXAppDelegate.m @@ -21,4 +21,15 @@ return YES; } +- (void)applicationDidEnterBackground:(UIApplication *)application +{ + [(MBXViewController *)self.window.rootViewController saveState]; +} + +- (void)applicationWillEnterForeground:(UIApplication *)application +{ + [(MBXViewController *)self.window.rootViewController restoreState]; +} + + @end \ No newline at end of file diff --git a/ios/MBXViewController.h b/ios/MBXViewController.h index 924d3af60..64021c2de 100644 --- a/ios/MBXViewController.h +++ b/ios/MBXViewController.h @@ -2,4 +2,7 @@ @interface MBXViewController : UIViewController +- (void)saveState; +- (void)restoreState; + @end diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm index dcd2271dc..a8e710f31 100644 --- a/ios/MBXViewController.mm +++ b/ios/MBXViewController.mm @@ -77,11 +77,29 @@ MBXViewController *viewController = nullptr; // Load settings settings = new llmr::Settings_NSUserDefaults(); - map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom); - map->setAngle(settings->angle); - map->setDebug(settings->debug); + [self restoreState]; } +- (void)saveState +{ + if (map && settings) + { + map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom); + settings->angle = map->getAngle(); + settings->debug = map->getDebug(); + settings->save(); + } +} + +- (void)restoreState +{ + if (map && settings) { + settings->load(); + map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom); + map->setAngle(settings->angle); + map->setDebug(settings->debug); + } +} - (void)setupInteraction { @@ -237,13 +255,7 @@ MBXViewController *viewController = nullptr; if (settings) { // Save settings - if (map) - { - map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom); - settings->angle = map->getAngle(); - settings->debug = map->getDebug(); - settings->save(); - } + [self saveState]; delete settings; settings = nullptr; } diff --git a/ios/llmr-app.gyp b/ios/llmr-app.gyp index e40ffe9a1..82b9b8963 100644 --- a/ios/llmr-app.gyp +++ b/ios/llmr-app.gyp @@ -10,7 +10,9 @@ "type": "executable", "sources": [ "./main.m", + "./MBXAppDelegate.h", "./MBXAppDelegate.m", + "./MBXViewController.h", "./MBXViewController.mm", "../common/settings_nsuserdefaults.hpp", "../common/settings_nsuserdefaults.mm", From b096373b7150e6b96782d10f2a0441f4f826030d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 15:15:54 +0200 Subject: [PATCH 6/9] abort rendering early if the style is disabled refs #184 --- include/llmr/style/properties.hpp | 2 ++ src/map/map.cpp | 22 ++++++++++++++++++++++ src/style/style.cpp | 1 + 3 files changed, 25 insertions(+) diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index e68239025..9a6d6d86d 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -127,6 +127,7 @@ struct TextClass { FunctionProperty size = 12.0f; FunctionProperty rotate = 0.0f; FunctionProperty alwaysVisible = false; + FunctionProperty opacity = 1; }; struct TextProperties { @@ -138,6 +139,7 @@ struct TextProperties { float size = 12.0f; float rotate = 0.0f; bool alwaysVisible = false; + float opacity = 1.0; }; struct BackgroundClass { diff --git a/src/map/map.cpp b/src/map/map.cpp index 6775f4721..1d797baaf 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -484,6 +484,16 @@ void Map::renderLayers(const std::vector& layers, RenderPass p } } +template +bool is_invisible(const Styles &styles, const LayerDescription &layer_desc) { + auto it = styles.find(layer_desc.name); + if (it == styles.end()) return true; + const auto &properties = it->second; + if (!properties.enabled) return true; + if (properties.opacity <= 0) return true; + return false; +} + void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) { if (layer_desc.child_layer.size()) { gl::group group(std::string("group: ") + layer_desc.name); @@ -501,13 +511,25 @@ void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) { // Abort early if we can already deduce from the bucket type that // we're not going to render anything anyway during this pass. switch (bucket_desc.type) { + case BucketType::Fill: + if (is_invisible(style.computed.fills, layer_desc)) return; + break; case BucketType::Line: + if (pass == Opaque) return; + if (is_invisible(style.computed.lines, layer_desc)) return; + break; case BucketType::Point: + if (pass == Opaque) return; + if (is_invisible(style.computed.points, layer_desc)) return; + break; case BucketType::Text: if (pass == Opaque) return; + if (is_invisible(style.computed.texts, layer_desc)) return; break; case BucketType::Raster: if (pass == Translucent) return; + if (is_invisible(style.computed.lines, layer_desc)) return; + break; default: break; } diff --git a/src/style/style.cpp b/src/style/style.cpp index 18fe0f416..a49311030 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -103,6 +103,7 @@ void Style::cascade(float z) { text.haloRadius = layer.haloRadius.evaluate(z); text.rotate = layer.rotate.evaluate(z); text.alwaysVisible = layer.alwaysVisible.evaluate(z); + text.opacity = layer.opacity.evaluate(z); } // Cascade raster classes From 7bfcde40254ba1d208783e8dbc8fa8ab15da9a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 13 May 2014 15:37:45 +0200 Subject: [PATCH 7/9] split up painter into multiple files --- src/renderer/painter.cpp | 581 +----------------------------- src/renderer/painter_clipping.cpp | 42 +++ src/renderer/painter_debug.cpp | 97 +++++ src/renderer/painter_fill.cpp | 143 ++++++++ src/renderer/painter_line.cpp | 92 +++++ src/renderer/painter_point.cpp | 80 ++++ src/renderer/painter_raster.cpp | 28 ++ src/renderer/painter_text.cpp | 114 ++++++ src/renderer/text_bucket.cpp | 1 - 9 files changed, 601 insertions(+), 577 deletions(-) create mode 100644 src/renderer/painter_clipping.cpp create mode 100644 src/renderer/painter_debug.cpp create mode 100644 src/renderer/painter_fill.cpp create mode 100644 src/renderer/painter_line.cpp create mode 100644 src/renderer/painter_point.cpp create mode 100644 src/renderer/painter_raster.cpp create mode 100644 src/renderer/painter_text.cpp diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 9f1372674..6adbc71c4 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -1,29 +1,16 @@ -#include -#include - - #include -#include - -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include + +#include #include #include #include #include +#include +#include + using namespace llmr; #define BUFFER_OFFSET(i) ((char *)nullptr + (i)) @@ -130,42 +117,6 @@ void Painter::changeMatrix() { matrix::multiply(nativeMatrix, projMatrix, nativeMatrix); } -void Painter::prepareClippingMask() { - gl::start_group("clipping masks"); - useProgram(plainShader->program); - glDisable(GL_DEPTH_TEST); - depthMask(false); - glDepthRange(1.0f, 1.0f); - - coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); -} - -void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { - plainShader->setMatrix(matrix); - - GLint id = clip.mask.to_ulong(); - GLuint mask = clipMask[clip.length]; - glStencilFunc(GL_ALWAYS, id, mask); - glStencilMask(0xFF); - - Color background = map.getStyle().computed.background.color; - background[0] *= map.getStyle().computed.background.opacity; - background[1] *= map.getStyle().computed.background.opacity; - background[2] *= map.getStyle().computed.background.opacity; - background[3] *= map.getStyle().computed.background.opacity; - - plainShader->setColor(background); - - glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); -} - -void Painter::finishClippingMask() { - glEnable(GL_DEPTH_TEST); - depthMask(true); - glStencilMask(0x0); - gl::end_group(); -} - void Painter::clear() { gl::group group("clear"); glStencilMask(0xFF); @@ -219,16 +170,6 @@ void Painter::renderTileLayer(const Tile& tile, const LayerDescription &layer_de } } -void Painter::renderTileDebug(const Tile& tile) { - gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); - assert(tile.data); - if (debug) { - prepareTile(tile); - renderDebugText(tile.data->debugBucket); - renderDebugFrame(); - } -} - void Painter::translateLayer(std::array translation, bool reverse) { if (translation[0] || translation[1]) { if (reverse) { @@ -243,483 +184,6 @@ void Painter::translateLayer(std::array translation, bool reverse) { } } -void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { - if (pass == Translucent) return; - - auto raster_properties = map.getStyle().computed.rasters; - auto raster_properties_it = raster_properties.find(layer_name); - if (raster_properties_it == raster_properties.end()) return; - - const RasterProperties& properties = raster_properties_it->second; - if (!properties.enabled) return; - - depthMask(false); - - useProgram(rasterShader->program); - rasterShader->setMatrix(matrix); - rasterShader->setOpacity(1); - // rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); - - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray); - - depthMask(true); -} - -void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { - // Abort early. - if (!bucket.hasData()) return; - - auto fill_properties = map.getStyle().computed.fills; - auto fill_properties_it = fill_properties.find(layer_name); - if (fill_properties_it == fill_properties.end()) return; - - const FillProperties& properties = fill_properties_it->second; - if (!properties.enabled) return; - - Color fill_color = properties.fill_color; - fill_color[0] *= properties.opacity; - fill_color[1] *= properties.opacity; - fill_color[2] *= properties.opacity; - fill_color[3] *= properties.opacity; - - Color stroke_color = properties.stroke_color; - stroke_color[0] *= properties.opacity; - stroke_color[1] *= properties.opacity; - stroke_color[2] *= properties.opacity; - stroke_color[3] *= properties.opacity; - - bool outline = properties.antialias && properties.stroke_color != properties.fill_color; - bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color; - if (fringeline) { - outline = true; - stroke_color = fill_color; - } - - translateLayer(properties.translate); - - // Because we're drawing top-to-bottom, and we update the stencil mask - // below, we have to draw the outline first (!) - if (outline && pass == Translucent) { - useProgram(outlineShader->program); - outlineShader->setMatrix(matrix); - lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! - - outlineShader->setColor(stroke_color); - - // Draw the entire line - outlineShader->setWorld({{ - static_cast(map.getState().getFramebufferWidth()), - static_cast(map.getState().getFramebufferHeight()) - }}); - glDepthRange(strata, 1.0f); - bucket.drawVertices(*outlineShader); - } else if (fringeline) { - // // We're only drawing to the first seven bits (== support a maximum of - // // 127 overlapping polygons in one place before we get rendering errors). - // glStencilMask(0x3F); - // glClear(GL_STENCIL_BUFFER_BIT); - - // // Draw front facing triangles. Wherever the 0x80 bit is 1, we are - // // increasing the lower 7 bits by one if the triangle is a front-facing - // // triangle. This means that all visible polygons should be in CCW - // // orientation, while all holes (see below) are in CW orientation. - // glStencilFunc(GL_EQUAL, 0x80, 0x80); - - // // When we do a nonzero fill, we count the number of times a pixel is - // // covered by a counterclockwise polygon, and subtract the number of - // // times it is "uncovered" by a clockwise polygon. - // glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP); - } - - if ((fill_color[3] >= 1.0f) == (pass == Opaque)) { - auto &sprite = map.getStyle().sprite; - if (properties.image.size() && sprite && sprite->isLoaded()) { - // Draw texture fill - ImagePosition imagePos = sprite->getPosition(properties.image, true); - - float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z); - float mix = fmod(map.getState().getZoom(), 1.0); - - std::array imageSize = {{ - imagePos.size.x * factor, - imagePos.size.y *factor - } - }; - - std::array offset = {{ - (float)fmod(id.x * 4096, imageSize[0]), - (float)fmod(id.y * 4096, imageSize[1]) - } - }; - - useProgram(patternShader->program); - patternShader->setMatrix(matrix); - patternShader->setOffset(offset); - patternShader->setPatternSize(imageSize); - patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); - patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }}); - patternShader->setColor(fill_color); - patternShader->setMix(mix); - sprite->raster.bind(true); - - // Draw the actual triangles into the color & stencil buffer. - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawElements(*patternShader); - } else { - // Only draw the fill when it's either opaque and we're drawing opaque - // fragments or when it's translucent and we're drawing translucent - // fragments - // Draw filling rectangle. - useProgram(plainShader->program); - plainShader->setMatrix(matrix); - plainShader->setColor(fill_color); - - // Draw the actual triangles into the color & stencil buffer. - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawElements(*plainShader); - } - } - - // Because we're drawing top-to-bottom, and we update the stencil mask - // below, we have to draw the outline first (!) - if (fringeline && pass == Translucent) { - useProgram(outlineShader->program); - outlineShader->setMatrix(matrix); - lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! - - outlineShader->setColor(fill_color); - - // Draw the entire line - outlineShader->setWorld({{ - static_cast(map.getState().getFramebufferWidth()), - static_cast(map.getState().getFramebufferHeight()) - }}); - - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawVertices(*outlineShader); - } - - translateLayer(properties.translate, true); -} - -void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { - // Abort early. - if (pass == Opaque) return; - if (!bucket.hasData()) return; - - auto line_properties = map.getStyle().computed.lines; - auto line_properties_it = line_properties.find(layer_name); - if (line_properties_it == line_properties.end()) return; - - const LineProperties& properties = line_properties_it->second; - if (!properties.enabled) return; - - float width = properties.width; - float offset = properties.offset / 2; - - // These are the radii of the line. We are limiting it to 16, which will result - // in a point size of 64 on retina. - float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f); - float outset = fmin(offset + width / 2 + 0.5, 16.0f); - - Color color = properties.color; - color[0] *= properties.opacity; - color[1] *= properties.opacity; - color[2] *= properties.opacity; - color[3] *= properties.opacity; - - float dash_length = properties.dash_array[0]; - float dash_gap = properties.dash_array[1]; - - translateLayer(properties.translate); - - glDepthRange(strata, 1.0f); - - // We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway. - if (bucket.hasPoints() && outset > 1.0f) { - useProgram(linejoinShader->program); - linejoinShader->setMatrix(matrix); - linejoinShader->setColor(color); - linejoinShader->setWorld({{ - map.getState().getFramebufferWidth() * 0.5f, - map.getState().getFramebufferHeight() * 0.5f - } - }); - linejoinShader->setLineWidth({{ - ((outset - 0.25f) * map.getState().getPixelRatio()), - ((inset - 0.25f) * map.getState().getPixelRatio()) - } - }); - - float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0); -#if defined(GL_ES_VERSION_2_0) - linejoinShader->setSize(pointSize); -#else - glPointSize(pointSize); -#endif - bucket.drawPoints(*linejoinShader); - } - - // var imagePos = properties.image && imageSprite.getPosition(properties.image); - bool imagePos = false; - if (imagePos) { - // var factor = 8 / Math.pow(2, painter.transform.zoom - params.z); - - // imageSprite.bind(gl, true); - - // //factor = Math.pow(2, 4 - painter.transform.zoom + params.z); - // gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix); - // shader = painter.linepatternShader; - // glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]); - // glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl); - // glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br); - // glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0); - - } else { - useProgram(lineShader->program); - lineShader->setMatrix(matrix); - lineShader->setExtrudeMatrix(extrudeMatrix); - lineShader->setDashArray({{ dash_length, dash_gap }}); - lineShader->setLineWidth({{ outset, inset }}); - lineShader->setRatio(map.getState().getPixelRatio()); - lineShader->setColor(color); - bucket.drawLines(*lineShader); - } - - translateLayer(properties.translate, true); -} - -void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) { - // Abort early. - if (!bucket.hasData()) return; - if (pass == Opaque) return; - - auto point_properties = map.getStyle().computed.points; - auto point_properties_it = point_properties.find(layer_name); - if (point_properties_it == point_properties.end()) return; - - const PointProperties& properties = point_properties_it->second; - if (!properties.enabled) return; - - translateLayer(properties.translate); - - Color color = properties.color; - color[0] *= properties.opacity; - color[1] *= properties.opacity; - color[2] *= properties.opacity; - color[3] *= properties.opacity; - - auto &sprite = map.getStyle().sprite; - ImagePosition imagePos; - - if (properties.image.length() && sprite && sprite->isLoaded()) { - std::string sized_image = properties.image; - if (properties.size) { - sized_image.append("-"); - sized_image.append(std::to_string(static_cast(std::round(properties.size)))); - } - imagePos = sprite->getPosition(sized_image, false); - } - - if (!imagePos.size) { - useProgram(dotShader->program); - dotShader->setMatrix(matrix); - dotShader->setColor(color); - - const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio(); -#if defined(GL_ES_VERSION_2_0) - dotShader->setSize(pointSize); -#else - glPointSize(pointSize); - glEnable(GL_POINT_SPRITE); -#endif - dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize); - - glDepthRange(strata, 1.0f); - bucket.drawPoints(*dotShader); - } else { - useProgram(pointShader->program); - pointShader->setMatrix(matrix); - pointShader->setColor(color); - - pointShader->setImage(0); - pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); - pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }}); - - sprite->raster.bind(map.getState().isChanging()); - - const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio(); -#if defined(GL_ES_VERSION_2_0) - pointShader->setSize(pointSize); -#else - glPointSize(pointSize); - glEnable(GL_POINT_SPRITE); -#endif - - glDepthRange(strata, 1.0f); - bucket.drawPoints(*pointShader); - } - - translateLayer(properties.translate, true); -} - -void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { - // Abort early. - if (pass == Opaque) return; - if (!bucket.hasData()) return; - - auto text_properties = map.getStyle().computed.texts; - auto text_properties_it = text_properties.find(layer_name); - if (text_properties_it == text_properties.end()) return; - - const TextProperties& properties = text_properties_it->second; - if (!properties.enabled) return; - - translateLayer(properties.translate); - - mat4 exMatrix; - matrix::copy(exMatrix, projMatrix); - if (bucket.geom_desc.path == TextPathType::Curve) { - matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle()); - } - - const float rotate = properties.rotate; - if (rotate != 0.0f) { - matrix::rotate_z(exMatrix, exMatrix, rotate); - } - - // If layerStyle.size > bucket.info.fontSize then labels may collide - float fontSize = fmin(properties.size, bucket.geom_desc.font_size); - matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f); - - useProgram(textShader->program); - textShader->setMatrix(matrix); - textShader->setExtrudeMatrix(exMatrix); - - map.getGlyphAtlas().bind(); - textShader->setTextureSize({{static_cast(map.getGlyphAtlas().width), - static_cast(map.getGlyphAtlas().height)}}); - - textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio()); - - // Convert the -pi..pi to an int8 range. - float angle = round((map.getState().getAngle() + rotate) / M_PI * 128); - - // adjust min/max zooms for variable font sies - float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2); - - textShader->setAngle((int32_t)(angle + 256) % 256); - textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0); - textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level - - // Label fading - const time duration = 300_milliseconds; - const time currentTime = util::now(); - - std::deque &history = frameHistory.history; - - // Remove frames until only one is outside the duration, or until there are only three - while (history.size() > 3 && history[1].timestamp + duration < currentTime) { - history.pop_front(); - } - - if (history[1].timestamp + duration < currentTime) { - history[0].z = history[1].z; - } - - size_t frameLen = history.size(); - assert("there should never be less than three frames in the history" && frameLen >= 3); - - // Find the range of zoom levels we want to fade between - float startingZ = history.front().z; - const FrameSnapshot lastFrame = history.back(); - float endingZ = lastFrame.z; - float lowZ = fmin(startingZ, endingZ); - float highZ = fmax(startingZ, endingZ); - - // Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration - float zoomDiff = endingZ - history[1].z, - timeDiff = lastFrame.timestamp - history[1].timestamp; - if (timeDiff > duration) timeDiff = 1; - float fadedist = zoomDiff / (timeDiff / duration); - - if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n"); - - // At end of a zoom when the zoom stops changing continue pretending to zoom at that speed - // bump is how much farther it would have been if it had continued zooming at the same rate - float bump = (currentTime - lastFrame.timestamp) / duration * fadedist; - - textShader->setFadeDist(fadedist * 10); - textShader->setMinFadeZoom(floor(lowZ * 10)); - textShader->setMaxFadeZoom(floor(highZ * 10)); - textShader->setFadeZoom((map.getState().getZoom() + bump) * 10); - - // We're drawing in the translucent pass which is bottom-to-top, so we need - // to draw the halo first. - if (properties.halo[3] > 0.0f) { - textShader->setColor(properties.halo); - textShader->setBuffer(properties.haloRadius); - glDepthRange(strata, 1.0f); - bucket.drawGlyphs(*textShader); - } - - // Then, we draw the text over the halo - textShader->setColor(properties.color); - textShader->setBuffer((256.0f - 64.0f) / 256.0f); - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawGlyphs(*textShader); - - translateLayer(properties.translate, true); -} - -void Painter::renderDebugText(DebugBucket& bucket) { - gl::group group("debug text"); - - glDisable(GL_DEPTH_TEST); - - useProgram(plainShader->program); - plainShader->setMatrix(matrix); - - // Draw white outline - plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); - lineWidth(4.0f * map.getState().getPixelRatio()); - bucket.drawLines(*plainShader); - -#ifndef GL_ES_VERSION_2_0 - // Draw line "end caps" - glPointSize(2); - bucket.drawPoints(*plainShader); -#endif - - // Draw black text. - plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); - lineWidth(2.0f * map.getState().getPixelRatio()); - bucket.drawLines(*plainShader); - - glEnable(GL_DEPTH_TEST); -} - -void Painter::renderDebugFrame() { - gl::group group("debug frame"); - - // Disable depth test and don't count this towards the depth buffer, - // but *don't* disable stencil test, as we want to clip the red tile border - // to the tile viewport. - glDisable(GL_DEPTH_TEST); - - useProgram(plainShader->program); - plainShader->setMatrix(matrix); - - // draw tile outline - tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0)); - plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f); - lineWidth(4.0f * map.getState().getPixelRatio()); - glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index()); - - glEnable(GL_DEPTH_TEST); -} - void Painter::renderMatte() { gl::group group("matte"); glDisable(GL_DEPTH_TEST); @@ -738,38 +202,3 @@ void Painter::renderMatte() { glEnable(GL_DEPTH_TEST); } - - -void Painter::renderDebugText(const std::vector &strings) { - gl::group group("debug text"); - - glDisable(GL_DEPTH_TEST); - glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); - - useProgram(plainShader->program); - plainShader->setMatrix(nativeMatrix); - - DebugFontBuffer debugFontBuffer; - int line = 25; - for (const std::string &str : strings) { - debugFontBuffer.addText(str.c_str(), 10, line, 0.75); - line += 20; - } - - // draw debug info - VertexArrayObject debugFontArray; - debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0)); - plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); - lineWidth(4.0f * map.getState().getPixelRatio()); - glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); -#ifndef GL_ES_VERSION_2_0 - glPointSize(2); - glDrawArrays(GL_POINTS, 0, (GLsizei)debugFontBuffer.index()); -#endif - plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); - lineWidth(2.0f * map.getState().getPixelRatio()); - glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); - - - glEnable(GL_DEPTH_TEST); -} \ No newline at end of file diff --git a/src/renderer/painter_clipping.cpp b/src/renderer/painter_clipping.cpp new file mode 100644 index 000000000..140555237 --- /dev/null +++ b/src/renderer/painter_clipping.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::prepareClippingMask() { + gl::start_group("clipping masks"); + useProgram(plainShader->program); + glDisable(GL_DEPTH_TEST); + depthMask(false); + glDepthRange(1.0f, 1.0f); + + coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); +} + +void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { + plainShader->setMatrix(matrix); + + GLint id = clip.mask.to_ulong(); + GLuint mask = clipMask[clip.length]; + glStencilFunc(GL_ALWAYS, id, mask); + glStencilMask(0xFF); + + Color background = map.getStyle().computed.background.color; + background[0] *= map.getStyle().computed.background.opacity; + background[1] *= map.getStyle().computed.background.opacity; + background[2] *= map.getStyle().computed.background.opacity; + background[3] *= map.getStyle().computed.background.opacity; + + plainShader->setColor(background); + + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); +} + +void Painter::finishClippingMask() { + glEnable(GL_DEPTH_TEST); + depthMask(true); + glStencilMask(0x0); + gl::end_group(); +} diff --git a/src/renderer/painter_debug.cpp b/src/renderer/painter_debug.cpp new file mode 100644 index 000000000..23bf32fda --- /dev/null +++ b/src/renderer/painter_debug.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::renderTileDebug(const Tile& tile) { + gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z)); + assert(tile.data); + if (debug) { + prepareTile(tile); + renderDebugText(tile.data->debugBucket); + renderDebugFrame(); + } +} + +void Painter::renderDebugText(DebugBucket& bucket) { + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + + useProgram(plainShader->program); + plainShader->setMatrix(matrix); + + // Draw white outline + plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); + lineWidth(4.0f * map.getState().getPixelRatio()); + bucket.drawLines(*plainShader); + +#ifndef GL_ES_VERSION_2_0 + // Draw line "end caps" + glPointSize(2); + bucket.drawPoints(*plainShader); +#endif + + // Draw black text. + plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); + lineWidth(2.0f * map.getState().getPixelRatio()); + bucket.drawLines(*plainShader); + + glEnable(GL_DEPTH_TEST); +} + +void Painter::renderDebugFrame() { + gl::group group("debug frame"); + + // Disable depth test and don't count this towards the depth buffer, + // but *don't* disable stencil test, as we want to clip the red tile border + // to the tile viewport. + glDisable(GL_DEPTH_TEST); + + useProgram(plainShader->program); + plainShader->setMatrix(matrix); + + // draw tile outline + tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0)); + plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f); + lineWidth(4.0f * map.getState().getPixelRatio()); + glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index()); + + glEnable(GL_DEPTH_TEST); +} + +void Painter::renderDebugText(const std::vector &strings) { + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); + + useProgram(plainShader->program); + plainShader->setMatrix(nativeMatrix); + + DebugFontBuffer debugFontBuffer; + int line = 25; + for (const std::string &str : strings) { + debugFontBuffer.addText(str.c_str(), 10, line, 0.75); + line += 20; + } + + // draw debug info + VertexArrayObject debugFontArray; + debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0)); + plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f); + lineWidth(4.0f * map.getState().getPixelRatio()); + glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); +#ifndef GL_ES_VERSION_2_0 + glPointSize(2); + glDrawArrays(GL_POINTS, 0, (GLsizei)debugFontBuffer.index()); +#endif + plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f); + lineWidth(2.0f * map.getState().getPixelRatio()); + glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); + + + glEnable(GL_DEPTH_TEST); +} diff --git a/src/renderer/painter_fill.cpp b/src/renderer/painter_fill.cpp new file mode 100644 index 000000000..921947857 --- /dev/null +++ b/src/renderer/painter_fill.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) { + // Abort early. + if (!bucket.hasData()) return; + + auto fill_properties = map.getStyle().computed.fills; + auto fill_properties_it = fill_properties.find(layer_name); + if (fill_properties_it == fill_properties.end()) return; + + const FillProperties& properties = fill_properties_it->second; + if (!properties.enabled) return; + + Color fill_color = properties.fill_color; + fill_color[0] *= properties.opacity; + fill_color[1] *= properties.opacity; + fill_color[2] *= properties.opacity; + fill_color[3] *= properties.opacity; + + Color stroke_color = properties.stroke_color; + stroke_color[0] *= properties.opacity; + stroke_color[1] *= properties.opacity; + stroke_color[2] *= properties.opacity; + stroke_color[3] *= properties.opacity; + + bool outline = properties.antialias && properties.stroke_color != properties.fill_color; + bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color; + if (fringeline) { + outline = true; + stroke_color = fill_color; + } + + translateLayer(properties.translate); + + // Because we're drawing top-to-bottom, and we update the stencil mask + // below, we have to draw the outline first (!) + if (outline && pass == Translucent) { + useProgram(outlineShader->program); + outlineShader->setMatrix(matrix); + lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! + + outlineShader->setColor(stroke_color); + + // Draw the entire line + outlineShader->setWorld({{ + static_cast(map.getState().getFramebufferWidth()), + static_cast(map.getState().getFramebufferHeight()) + }}); + glDepthRange(strata, 1.0f); + bucket.drawVertices(*outlineShader); + } else if (fringeline) { + // // We're only drawing to the first seven bits (== support a maximum of + // // 127 overlapping polygons in one place before we get rendering errors). + // glStencilMask(0x3F); + // glClear(GL_STENCIL_BUFFER_BIT); + + // // Draw front facing triangles. Wherever the 0x80 bit is 1, we are + // // increasing the lower 7 bits by one if the triangle is a front-facing + // // triangle. This means that all visible polygons should be in CCW + // // orientation, while all holes (see below) are in CW orientation. + // glStencilFunc(GL_EQUAL, 0x80, 0x80); + + // // When we do a nonzero fill, we count the number of times a pixel is + // // covered by a counterclockwise polygon, and subtract the number of + // // times it is "uncovered" by a clockwise polygon. + // glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP); + } + + if ((fill_color[3] >= 1.0f) == (pass == Opaque)) { + auto &sprite = map.getStyle().sprite; + if (properties.image.size() && sprite && sprite->isLoaded()) { + // Draw texture fill + ImagePosition imagePos = sprite->getPosition(properties.image, true); + + float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z); + float mix = fmod(map.getState().getZoom(), 1.0); + + std::array imageSize = {{ + imagePos.size.x * factor, + imagePos.size.y *factor + } + }; + + std::array offset = {{ + (float)fmod(id.x * 4096, imageSize[0]), + (float)fmod(id.y * 4096, imageSize[1]) + } + }; + + useProgram(patternShader->program); + patternShader->setMatrix(matrix); + patternShader->setOffset(offset); + patternShader->setPatternSize(imageSize); + patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); + patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }}); + patternShader->setColor(fill_color); + patternShader->setMix(mix); + sprite->raster.bind(true); + + // Draw the actual triangles into the color & stencil buffer. + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawElements(*patternShader); + } else { + // Only draw the fill when it's either opaque and we're drawing opaque + // fragments or when it's translucent and we're drawing translucent + // fragments + // Draw filling rectangle. + useProgram(plainShader->program); + plainShader->setMatrix(matrix); + plainShader->setColor(fill_color); + + // Draw the actual triangles into the color & stencil buffer. + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawElements(*plainShader); + } + } + + // Because we're drawing top-to-bottom, and we update the stencil mask + // below, we have to draw the outline first (!) + if (fringeline && pass == Translucent) { + useProgram(outlineShader->program); + outlineShader->setMatrix(matrix); + lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! + + outlineShader->setColor(fill_color); + + // Draw the entire line + outlineShader->setWorld({{ + static_cast(map.getState().getFramebufferWidth()), + static_cast(map.getState().getFramebufferHeight()) + }}); + + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawVertices(*outlineShader); + } + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/painter_line.cpp b/src/renderer/painter_line.cpp new file mode 100644 index 000000000..1998e5584 --- /dev/null +++ b/src/renderer/painter_line.cpp @@ -0,0 +1,92 @@ +#include +#include +#include + +using namespace llmr; + +void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { + // Abort early. + if (pass == Opaque) return; + if (!bucket.hasData()) return; + + auto line_properties = map.getStyle().computed.lines; + auto line_properties_it = line_properties.find(layer_name); + if (line_properties_it == line_properties.end()) return; + + const LineProperties& properties = line_properties_it->second; + if (!properties.enabled) return; + + float width = properties.width; + float offset = properties.offset / 2; + + // These are the radii of the line. We are limiting it to 16, which will result + // in a point size of 64 on retina. + float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f); + float outset = fmin(offset + width / 2 + 0.5, 16.0f); + + Color color = properties.color; + color[0] *= properties.opacity; + color[1] *= properties.opacity; + color[2] *= properties.opacity; + color[3] *= properties.opacity; + + float dash_length = properties.dash_array[0]; + float dash_gap = properties.dash_array[1]; + + translateLayer(properties.translate); + + glDepthRange(strata, 1.0f); + + // We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway. + if (bucket.hasPoints() && outset > 1.0f) { + useProgram(linejoinShader->program); + linejoinShader->setMatrix(matrix); + linejoinShader->setColor(color); + linejoinShader->setWorld({{ + map.getState().getFramebufferWidth() * 0.5f, + map.getState().getFramebufferHeight() * 0.5f + } + }); + linejoinShader->setLineWidth({{ + ((outset - 0.25f) * map.getState().getPixelRatio()), + ((inset - 0.25f) * map.getState().getPixelRatio()) + } + }); + + float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0); +#if defined(GL_ES_VERSION_2_0) + linejoinShader->setSize(pointSize); +#else + glPointSize(pointSize); +#endif + bucket.drawPoints(*linejoinShader); + } + + // var imagePos = properties.image && imageSprite.getPosition(properties.image); + bool imagePos = false; + if (imagePos) { + // var factor = 8 / Math.pow(2, painter.transform.zoom - params.z); + + // imageSprite.bind(gl, true); + + // //factor = Math.pow(2, 4 - painter.transform.zoom + params.z); + // gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix); + // shader = painter.linepatternShader; + // glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]); + // glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl); + // glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br); + // glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0); + + } else { + useProgram(lineShader->program); + lineShader->setMatrix(matrix); + lineShader->setExtrudeMatrix(extrudeMatrix); + lineShader->setDashArray({{ dash_length, dash_gap }}); + lineShader->setLineWidth({{ outset, inset }}); + lineShader->setRatio(map.getState().getPixelRatio()); + lineShader->setColor(color); + bucket.drawLines(*lineShader); + } + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/painter_point.cpp b/src/renderer/painter_point.cpp new file mode 100644 index 000000000..d54c66be9 --- /dev/null +++ b/src/renderer/painter_point.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +using namespace llmr; + +void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) { + // Abort early. + if (!bucket.hasData()) return; + if (pass == Opaque) return; + + auto point_properties = map.getStyle().computed.points; + auto point_properties_it = point_properties.find(layer_name); + if (point_properties_it == point_properties.end()) return; + + const PointProperties& properties = point_properties_it->second; + if (!properties.enabled) return; + + translateLayer(properties.translate); + + Color color = properties.color; + color[0] *= properties.opacity; + color[1] *= properties.opacity; + color[2] *= properties.opacity; + color[3] *= properties.opacity; + + auto &sprite = map.getStyle().sprite; + ImagePosition imagePos; + + if (properties.image.length() && sprite && sprite->isLoaded()) { + std::string sized_image = properties.image; + if (properties.size) { + sized_image.append("-"); + sized_image.append(std::to_string(static_cast(std::round(properties.size)))); + } + imagePos = sprite->getPosition(sized_image, false); + } + + if (!imagePos.size) { + useProgram(dotShader->program); + dotShader->setMatrix(matrix); + dotShader->setColor(color); + + const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio(); +#if defined(GL_ES_VERSION_2_0) + dotShader->setSize(pointSize); +#else + glPointSize(pointSize); + glEnable(GL_POINT_SPRITE); +#endif + dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize); + + glDepthRange(strata, 1.0f); + bucket.drawPoints(*dotShader); + } else { + useProgram(pointShader->program); + pointShader->setMatrix(matrix); + pointShader->setColor(color); + + pointShader->setImage(0); + pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); + pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }}); + + sprite->raster.bind(map.getState().isChanging()); + + const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio(); +#if defined(GL_ES_VERSION_2_0) + pointShader->setSize(pointSize); +#else + glPointSize(pointSize); + glEnable(GL_POINT_SPRITE); +#endif + + glDepthRange(strata, 1.0f); + bucket.drawPoints(*pointShader); + } + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/painter_raster.cpp b/src/renderer/painter_raster.cpp new file mode 100644 index 000000000..410f1a000 --- /dev/null +++ b/src/renderer/painter_raster.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +using namespace llmr; + +void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { + if (pass == Translucent) return; + + auto raster_properties = map.getStyle().computed.rasters; + auto raster_properties_it = raster_properties.find(layer_name); + if (raster_properties_it == raster_properties.end()) return; + + const RasterProperties& properties = raster_properties_it->second; + if (!properties.enabled) return; + + depthMask(false); + + useProgram(rasterShader->program); + rasterShader->setMatrix(matrix); + rasterShader->setOpacity(1); + // rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity); + + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray); + + depthMask(true); +} diff --git a/src/renderer/painter_text.cpp b/src/renderer/painter_text.cpp new file mode 100644 index 000000000..092b74b09 --- /dev/null +++ b/src/renderer/painter_text.cpp @@ -0,0 +1,114 @@ +#include +#include +#include + +using namespace llmr; + +void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) { + // Abort early. + if (pass == Opaque) return; + if (!bucket.hasData()) return; + + auto text_properties = map.getStyle().computed.texts; + auto text_properties_it = text_properties.find(layer_name); + if (text_properties_it == text_properties.end()) return; + + const TextProperties& properties = text_properties_it->second; + if (!properties.enabled) return; + + translateLayer(properties.translate); + + mat4 exMatrix; + matrix::copy(exMatrix, projMatrix); + if (bucket.geom_desc.path == TextPathType::Curve) { + matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle()); + } + + const float rotate = properties.rotate; + if (rotate != 0.0f) { + matrix::rotate_z(exMatrix, exMatrix, rotate); + } + + // If layerStyle.size > bucket.info.fontSize then labels may collide + float fontSize = fmin(properties.size, bucket.geom_desc.font_size); + matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f); + + useProgram(textShader->program); + textShader->setMatrix(matrix); + textShader->setExtrudeMatrix(exMatrix); + + map.getGlyphAtlas().bind(); + textShader->setTextureSize({{static_cast(map.getGlyphAtlas().width), + static_cast(map.getGlyphAtlas().height)}}); + + textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio()); + + // Convert the -pi..pi to an int8 range. + float angle = round((map.getState().getAngle() + rotate) / M_PI * 128); + + // adjust min/max zooms for variable font sies + float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2); + + textShader->setAngle((int32_t)(angle + 256) % 256); + textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0); + textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level + + // Label fading + const time duration = 300_milliseconds; + const time currentTime = util::now(); + + std::deque &history = frameHistory.history; + + // Remove frames until only one is outside the duration, or until there are only three + while (history.size() > 3 && history[1].timestamp + duration < currentTime) { + history.pop_front(); + } + + if (history[1].timestamp + duration < currentTime) { + history[0].z = history[1].z; + } + + size_t frameLen = history.size(); + assert("there should never be less than three frames in the history" && frameLen >= 3); + + // Find the range of zoom levels we want to fade between + float startingZ = history.front().z; + const FrameSnapshot lastFrame = history.back(); + float endingZ = lastFrame.z; + float lowZ = fmin(startingZ, endingZ); + float highZ = fmax(startingZ, endingZ); + + // Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration + float zoomDiff = endingZ - history[1].z, + timeDiff = lastFrame.timestamp - history[1].timestamp; + if (timeDiff > duration) timeDiff = 1; + float fadedist = zoomDiff / (timeDiff / duration); + + if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n"); + + // At end of a zoom when the zoom stops changing continue pretending to zoom at that speed + // bump is how much farther it would have been if it had continued zooming at the same rate + float bump = (currentTime - lastFrame.timestamp) / duration * fadedist; + + textShader->setFadeDist(fadedist * 10); + textShader->setMinFadeZoom(floor(lowZ * 10)); + textShader->setMaxFadeZoom(floor(highZ * 10)); + textShader->setFadeZoom((map.getState().getZoom() + bump) * 10); + + // We're drawing in the translucent pass which is bottom-to-top, so we need + // to draw the halo first. + if (properties.halo[3] > 0.0f) { + textShader->setColor(properties.halo); + textShader->setBuffer(properties.haloRadius); + glDepthRange(strata, 1.0f); + bucket.drawGlyphs(*textShader); + } + + // Then, we draw the text over the halo + textShader->setColor(properties.color); + textShader->setBuffer((256.0f - 64.0f) / 256.0f); + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawGlyphs(*textShader); + + translateLayer(properties.translate, true); +} diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index 939a8363e..052fd5a08 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include From 6668b9b303f01eb73cc044abf26e9b3e017463c1 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Tue, 13 May 2014 11:01:32 -0700 Subject: [PATCH 8/9] keep view controller self-contained for state save/restore --- ios/MBXAppDelegate.m | 13 +------------ ios/MBXViewController.mm | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/ios/MBXAppDelegate.m b/ios/MBXAppDelegate.m index aa048ca67..0fbcc6e81 100644 --- a/ios/MBXAppDelegate.m +++ b/ios/MBXAppDelegate.m @@ -21,15 +21,4 @@ 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 +@end diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm index a8e710f31..9c4b5d2fe 100644 --- a/ios/MBXViewController.mm +++ b/ios/MBXViewController.mm @@ -36,6 +36,19 @@ MBXViewController *viewController = nullptr; #pragma mark - Setup +- (id)init +{ + self = [super init]; + + if (self) + { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState) name:UIApplicationWillEnterForegroundNotification object:nil]; + } + + return self; +} + - (void)loadView { [super loadView]; @@ -252,6 +265,8 @@ MBXViewController *viewController = nullptr; - (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + if (settings) { // Save settings From 5c894884296a43caa21821eec8317af612a58047 Mon Sep 17 00:00:00 2001 From: "Justin R. Miller" Date: Tue, 13 May 2014 11:05:18 -0700 Subject: [PATCH 9/9] hide more API --- ios/MBXViewController.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/ios/MBXViewController.h b/ios/MBXViewController.h index 64021c2de..924d3af60 100644 --- a/ios/MBXViewController.h +++ b/ios/MBXViewController.h @@ -2,7 +2,4 @@ @interface MBXViewController : UIViewController -- (void)saveState; -- (void)restoreState; - @end