diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e82553a4f..165f3ae8b5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ mason_use(pixelmatch VERSION 0.10.0 HEADER_ONLY) mason_use(geojson VERSION 0.4.0 HEADER_ONLY) mason_use(polylabel VERSION 1.0.2 HEADER_ONLY) mason_use(wagyu VERSION 0.4.1 HEADER_ONLY) -mason_use(shelf-pack VERSION 2.0.1 HEADER_ONLY) +mason_use(shelf-pack VERSION 2.1.0 HEADER_ONLY) add_definitions(-DRAPIDJSON_HAS_STDSTRING=1) diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 44e4f40f19a..2700cdee1e3 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -41,7 +41,6 @@ set(MBGL_CORE_FILES # geometry src/mbgl/geometry/anchor.hpp - src/mbgl/geometry/binpack.hpp src/mbgl/geometry/debug_font_data.hpp src/mbgl/geometry/feature_index.cpp src/mbgl/geometry/feature_index.hpp @@ -167,6 +166,10 @@ set(MBGL_CORE_FILES src/mbgl/renderer/frame_history.hpp src/mbgl/renderer/group_by_layout.cpp src/mbgl/renderer/group_by_layout.hpp + src/mbgl/renderer/image_atlas.cpp + src/mbgl/renderer/image_atlas.hpp + src/mbgl/renderer/image_manager.cpp + src/mbgl/renderer/image_manager.hpp src/mbgl/renderer/paint_parameters.hpp src/mbgl/renderer/paint_property_binder.hpp src/mbgl/renderer/paint_property_statistics.hpp @@ -291,8 +294,6 @@ set(MBGL_CORE_FILES src/mbgl/shaders/symbol_sdf.hpp # sprite - src/mbgl/sprite/sprite_atlas.cpp - src/mbgl/sprite/sprite_atlas.hpp src/mbgl/sprite/sprite_loader.cpp src/mbgl/sprite/sprite_loader.hpp src/mbgl/sprite/sprite_loader_observer.hpp @@ -473,7 +474,9 @@ set(MBGL_CORE_FILES src/mbgl/text/glyph.hpp src/mbgl/text/glyph_atlas.cpp src/mbgl/text/glyph_atlas.hpp - src/mbgl/text/glyph_atlas_observer.hpp + src/mbgl/text/glyph_manager.cpp + src/mbgl/text/glyph_manager.hpp + src/mbgl/text/glyph_manager_observer.hpp src/mbgl/text/glyph_pbf.cpp src/mbgl/text/glyph_pbf.hpp src/mbgl/text/glyph_range.hpp diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index c0673bedefd..a93d9e90160 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -19,9 +19,6 @@ set(MBGL_TEST_FILES test/api/render_missing.test.cpp test/api/repeated_render.test.cpp - # geometry - test/geometry/binpack.test.cpp - # gl test/gl/bucket.test.cpp test/gl/object.test.cpp @@ -43,9 +40,9 @@ set(MBGL_TEST_FILES # renderer test/renderer/group_by_layout.test.cpp + test/renderer/image_manager.test.cpp # sprite - test/sprite/sprite_atlas.test.cpp test/sprite/sprite_loader.test.cpp test/sprite/sprite_parser.test.cpp @@ -106,7 +103,7 @@ set(MBGL_TEST_FILES test/style/style_parser.test.cpp # text - test/text/glyph_atlas.test.cpp + test/text/glyph_loader.test.cpp test/text/glyph_pbf.test.cpp test/text/quads.test.cpp diff --git a/include/mbgl/util/image.hpp b/include/mbgl/util/image.hpp index a41b8462bd0..91bf06d7273 100644 --- a/include/mbgl/util/image.hpp +++ b/include/mbgl/util/image.hpp @@ -78,10 +78,27 @@ class Image : private util::noncopyable { std::fill(data.get(), data.get() + bytes(), value); } + void resize(Size size_) { + if (size == size_) { + return; + } + Image newImage(size_); + newImage.fill(0); + copy(*this, newImage, {0, 0}, {0, 0}, { + std::min(size.width, size_.width), + std::min(size.height, size_.height) + }); + operator=(std::move(newImage)); + } + // Copy image data within `rect` from `src` to the rectangle of the same size at `pt` // in `dst`. If the specified bounds exceed the bounds of the source or destination, // throw `std::out_of_range`. Must not be used to move data within a single Image. static void copy(const Image& srcImg, Image& dstImg, const Point& srcPt, const Point& dstPt, const Size& size) { + if (size.isEmpty()) { + return; + } + if (!srcImg.valid()) { throw std::invalid_argument("invalid source for image copy"); } diff --git a/mapbox-gl-js b/mapbox-gl-js index 943dc3917b2..0ee15860e3f 160000 --- a/mapbox-gl-js +++ b/mapbox-gl-js @@ -1 +1 @@ -Subproject commit 943dc3917b28ec8712d08afbcc1ef9a277c6c93a +Subproject commit 0ee15860e3ffb25a535ae374f44b3007b765c6fc diff --git a/platform/node/test/suite_implementation.js b/platform/node/test/suite_implementation.js index 8ac372b7c30..cae5d121578 100644 --- a/platform/node/test/suite_implementation.js +++ b/platform/node/test/suite_implementation.js @@ -70,7 +70,7 @@ module.exports = function (style, options, callback) { applyOperations(operations.slice(1), callback); }); - } else if (operation[0] === 'addImage') { + } else if (operation[0] === 'addImage' || operation[0] === 'updateImage') { var img = PNG.sync.read(fs.readFileSync(path.join(__dirname, '../../../mapbox-gl-js/test/integration', operation[2]))); map.addImage(operation[1], img.data, { diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index 4618761273d..b8601d4ed2a 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -43,9 +44,9 @@ void RenderAnnotationSource::update(Immutable baseImpl_, }); } -void RenderAnnotationSource::startRender(algorithm::ClipIDGenerator& generator, const mat4& projMatrix, const mat4& clipMatrix, const TransformState& transform) { - generator.update(tilePyramid.getRenderTiles()); - tilePyramid.startRender(projMatrix, clipMatrix, transform); +void RenderAnnotationSource::startRender(Painter& painter) { + painter.clipIDGenerator.update(tilePyramid.getRenderTiles()); + tilePyramid.startRender(painter); } void RenderAnnotationSource::finishRender(Painter& painter) { diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp index e0c9018e17e..fe384c64ca3 100644 --- a/src/mbgl/annotation/render_annotation_source.hpp +++ b/src/mbgl/annotation/render_annotation_source.hpp @@ -18,10 +18,7 @@ class RenderAnnotationSource : public RenderSource { bool needsRelayout, const TileParameters&) final; - void startRender(algorithm::ClipIDGenerator&, - const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState&) final; + void startRender(Painter&) final; void finishRender(Painter&) final; std::map& getRenderTiles() final; diff --git a/src/mbgl/geometry/binpack.hpp b/src/mbgl/geometry/binpack.hpp deleted file mode 100644 index b715cbc2bee..00000000000 --- a/src/mbgl/geometry/binpack.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace mbgl { - -template -class BinPack : private util::noncopyable { -public: - BinPack(T width, T height) - : free(1, Rect{ 0, 0, width, height }) {} -public: - Rect allocate(T width, T height) { - // Find the smallest free rect angle - auto smallest = free.end(); - for (auto it = free.begin(); it != free.end(); ++it) { - const Rect& ref = *it; - const Rect& rect = *smallest; - if (width <= ref.w && height <= ref.h) { - if (smallest == free.end() || (ref.y <= rect.y && ref.x <= rect.x)) { - smallest = it; - } else { - // Our current "smallest" rect is already closer to 0/0. - } - } else { - // The rect in the free list is not big enough. - } - } - - if (smallest == free.end()) { - // There's no space left for this char. - return Rect{ 0, 0, 0, 0 }; - } else { - Rect rect = *smallest; - free.erase(smallest); - - // Shorter/Longer Axis Split Rule (SAS) - // http://clb.demon.fi/files/RectangleBinPack.pdf p. 15 - // Ignore the dimension of R and just split long the shorter dimension - // See Also: http://www.cs.princeton.edu/~chazelle/pubs/blbinpacking.pdf - if (rect.w < rect.h) { - // split horizontally - // +--+---+ - // |__|___| <-- b1 - // +------+ <-- b2 - if (rect.w > width) free.emplace_back(rect.x + width, rect.y, rect.w - width, height); - if (rect.h > height) free.emplace_back(rect.x, rect.y + height, rect.w, rect.h - height); - } else { - // split vertically - // +--+---+ - // |__| | <-- b1 - // +--|---+ <-- b2 - if (rect.w > width) free.emplace_back(rect.x + width, rect.y, rect.w - width, rect.h); - if (rect.h > height) free.emplace_back(rect.x, rect.y + height, width, rect.h - height); - } - - return Rect{ rect.x, rect.y, width, height }; - } - } - - - void release(Rect rect) { - // Simple algorithm to recursively merge the newly released cell with its - // neighbor. This doesn't merge more than two cells at a time, and fails - // for complicated merges. - for (auto it = free.begin(); it != free.end(); ++it) { - Rect ref = *it; - if (ref.y == rect.y && ref.h == rect.h && ref.x + ref.w == rect.x) { - ref.w += rect.w; - } - else if (ref.x == rect.x && ref.w == rect.w && ref.y + ref.h == rect.y) { - ref.h += rect.h; - } - else if (rect.y == ref.y && rect.h == ref.h && rect.x + rect.w == ref.x) { - ref.x = rect.x; - ref.w += rect.w; - } - else if (rect.x == ref.x && rect.w == ref.w && rect.y + rect.h == ref.y) { - ref.y = rect.y; - ref.h += rect.h; - } else { - continue; - } - - free.erase(it); - release(ref); - return; - - } - - free.emplace_back(rect); - }; - -private: - std::list> free; -}; - -} // namespace mbgl diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 6a68c3ff698..6dd1682d0e7 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -258,11 +258,9 @@ std::unique_ptr Context::readFramebuffer(const Size size, const Textu const size_t stride = size.width * (format == TextureFormat::RGBA ? 4 : 1); auto data = std::make_unique(stride * size.height); -#if not MBGL_USE_GLES2 // When reading data from the framebuffer, make sure that we are storing the values // tightly packed into the buffer to avoid buffer overruns. pixelStorePack = { 1 }; -#endif // MBGL_USE_GLES2 MBGL_CHECK_ERROR(glReadPixels(0, 0, size.width, size.height, static_cast(format), GL_UNSIGNED_BYTE, data.get())); @@ -399,6 +397,7 @@ Context::createFramebuffer(const Texture& color, UniqueTexture Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit) { auto obj = createTexture(); + pixelStoreUnpack = { 1 }; updateTexture(obj, size, data, format, unit); // We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures. // We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus. @@ -489,12 +488,12 @@ void Context::setDirtyState() { program.setDirty(); lineWidth.setDirty(); activeTexture.setDirty(); + pixelStorePack.setDirty(); + pixelStoreUnpack.setDirty(); #if not MBGL_USE_GLES2 pointSize.setDirty(); pixelZoom.setDirty(); rasterPos.setDirty(); - pixelStorePack.setDirty(); - pixelStoreUnpack.setDirty(); pixelTransferDepth.setDirty(); pixelTransferStencil.setDirty(); #endif // MBGL_USE_GLES2 diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 56c0618989a..94928600826 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -205,11 +205,12 @@ class Context : private util::noncopyable { State vertexBuffer; State elementBuffer; + State pixelStorePack; + State pixelStoreUnpack; + #if not MBGL_USE_GLES2 State pixelZoom; State rasterPos; - State pixelStorePack; - State pixelStoreUnpack; State pixelTransferDepth; State pixelTransferStencil; #endif // MBGL_USE_GLES2 diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index 0595419674a..31e3076f677 100644 --- a/src/mbgl/gl/types.hpp +++ b/src/mbgl/gl/types.hpp @@ -66,8 +66,6 @@ enum class PrimitiveType { TriangleFan = 0x0006 }; -#if not MBGL_USE_GLES2 - struct PixelStorageType { int32_t alignment; }; @@ -76,8 +74,6 @@ constexpr bool operator!=(const PixelStorageType& a, const PixelStorageType& b) return a.alignment != b.alignment; } -#endif // MBGL_USE_GLES2 - using BinaryProgramFormat = uint32_t; } // namespace gl diff --git a/src/mbgl/gl/value.cpp b/src/mbgl/gl/value.cpp index c081c941f54..51031940938 100644 --- a/src/mbgl/gl/value.cpp +++ b/src/mbgl/gl/value.cpp @@ -353,6 +353,34 @@ BindVertexArray::Type BindVertexArray::Get(const Context& context) { return binding; } +const constexpr PixelStorePack::Type PixelStorePack::Default; + +void PixelStorePack::Set(const Type& value) { + assert(value.alignment == 1 || value.alignment == 2 || value.alignment == 4 || + value.alignment == 8); + MBGL_CHECK_ERROR(glPixelStorei(GL_PACK_ALIGNMENT, value.alignment)); +} + +PixelStorePack::Type PixelStorePack::Get() { + Type value; + MBGL_CHECK_ERROR(glGetIntegerv(GL_PACK_ALIGNMENT, &value.alignment)); + return value; +} + +const constexpr PixelStoreUnpack::Type PixelStoreUnpack::Default; + +void PixelStoreUnpack::Set(const Type& value) { + assert(value.alignment == 1 || value.alignment == 2 || value.alignment == 4 || + value.alignment == 8); + MBGL_CHECK_ERROR(glPixelStorei(GL_UNPACK_ALIGNMENT, value.alignment)); +} + +PixelStoreUnpack::Type PixelStoreUnpack::Get() { + Type value; + MBGL_CHECK_ERROR(glGetIntegerv(GL_UNPACK_ALIGNMENT, &value.alignment)); + return value; +} + #if not MBGL_USE_GLES2 const constexpr PointSize::Type PointSize::Default; @@ -392,34 +420,6 @@ RasterPos::Type RasterPos::Get() { return { pos[0], pos[1], pos[2], pos[3] }; } -const constexpr PixelStorePack::Type PixelStorePack::Default; - -void PixelStorePack::Set(const Type& value) { - assert(value.alignment == 1 || value.alignment == 2 || value.alignment == 4 || - value.alignment == 8); - MBGL_CHECK_ERROR(glPixelStorei(GL_PACK_ALIGNMENT, value.alignment)); -} - -PixelStorePack::Type PixelStorePack::Get() { - Type value; - MBGL_CHECK_ERROR(glGetIntegerv(GL_PACK_ALIGNMENT, &value.alignment)); - return value; -} - -const constexpr PixelStoreUnpack::Type PixelStoreUnpack::Default; - -void PixelStoreUnpack::Set(const Type& value) { - assert(value.alignment == 1 || value.alignment == 2 || value.alignment == 4 || - value.alignment == 8); - MBGL_CHECK_ERROR(glPixelStorei(GL_UNPACK_ALIGNMENT, value.alignment)); -} - -PixelStoreUnpack::Type PixelStoreUnpack::Get() { - Type value; - MBGL_CHECK_ERROR(glGetIntegerv(GL_UNPACK_ALIGNMENT, &value.alignment)); - return value; -} - const constexpr PixelTransferDepth::Type PixelTransferDepth::Default; void PixelTransferDepth::Set(const Type& value) { diff --git a/src/mbgl/gl/value.hpp b/src/mbgl/gl/value.hpp index aa5cca6fec0..58fc55556e0 100644 --- a/src/mbgl/gl/value.hpp +++ b/src/mbgl/gl/value.hpp @@ -232,6 +232,20 @@ struct BindVertexArray { static Type Get(const Context&); }; +struct PixelStorePack { + using Type = PixelStorageType; + static const constexpr Type Default = { 4 }; + static void Set(const Type&); + static Type Get(); +}; + +struct PixelStoreUnpack { + using Type = PixelStorageType; + static const constexpr Type Default = { 4 }; + static void Set(const Type&); + static Type Get(); +}; + #if not MBGL_USE_GLES2 struct PointSize { @@ -271,20 +285,6 @@ constexpr bool operator!=(const RasterPos::Type& a, const RasterPos::Type& b) { return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; } -struct PixelStorePack { - using Type = PixelStorageType; - static const constexpr Type Default = { 4 }; - static void Set(const Type&); - static Type Get(); -}; - -struct PixelStoreUnpack { - using Type = PixelStorageType; - static const constexpr Type Default = { 4 }; - static void Set(const Type&); - static Type Get(); -}; - struct PixelTransferDepth { struct Type { float scale; diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index 8816f4c95cb..ffb70c7ca25 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -19,7 +19,7 @@ SymbolInstance::SymbolInstance(Anchor& anchor, const float iconBoxScale, const float iconPadding, const SymbolPlacementType iconPlacement, - const GlyphPositions& face, + const GlyphPositionMap& positions, const IndexedSubfeature& indexedFeature, const std::size_t featureIndex_) : point(anchor.point), @@ -38,11 +38,11 @@ SymbolInstance::SymbolInstance(Anchor& anchor, iconQuad = getIconQuad(anchor, *shapedIcon, line, layout, layoutTextSize, iconPlacement, shapedTextOrientations.first); } if (shapedTextOrientations.first) { - auto quads = getGlyphQuads(anchor, shapedTextOrientations.first, textBoxScale, line, layout, textPlacement, face); + auto quads = getGlyphQuads(anchor, shapedTextOrientations.first, textBoxScale, line, layout, textPlacement, positions); glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); } if (shapedTextOrientations.second) { - auto quads = getGlyphQuads(anchor, shapedTextOrientations.second, textBoxScale, line, layout, textPlacement, face); + auto quads = getGlyphQuads(anchor, shapedTextOrientations.second, textBoxScale, line, layout, textPlacement, positions); glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); } } diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index eadbf674757..f199d929df9 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include @@ -26,7 +26,7 @@ class SymbolInstance { const float iconBoxScale, const float iconPadding, style::SymbolPlacementType iconPlacement, - const GlyphPositions& face, + const GlyphPositionMap&, const IndexedSubfeature&, const std::size_t featureIndex); diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index b1cfc113fb8..ed580bcb168 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -5,8 +5,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -41,7 +41,7 @@ static bool has(const style::SymbolLayoutProperties::PossiblyEvaluated& layout) SymbolLayout::SymbolLayout(const BucketParameters& parameters, const std::vector& layers, const GeometryTileLayer& sourceLayer, - IconDependencies& iconDependencies, + ImageDependencies& imageDependencies, GlyphDependencies& glyphDependencies) : sourceLayerName(sourceLayer.getName()), bucketName(layers.at(0)->getID()), @@ -158,7 +158,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, icon = util::replaceTokens(icon, getValue); } ft.icon = icon; - iconDependencies.insert(*ft.icon); + imageDependencies.insert(*ft.icon); } if (ft.text || ft.icon) { @@ -175,7 +175,8 @@ bool SymbolLayout::hasSymbolInstances() const { return !symbolInstances.empty(); } -void SymbolLayout::prepare(const GlyphPositionMap& glyphs, const IconMap& icons) { +void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions, + const ImageMap& imageMap, const ImagePositions& imagePositions) { float horizontalAlign = 0.5; float verticalAlign = 0.5; @@ -217,61 +218,65 @@ void SymbolLayout::prepare(const GlyphPositionMap& glyphs, const IconMap& icons) layout.get() == TextJustifyType::Left ? 0 : 0.5; - const bool textAlongLine = layout.get() == AlignmentType::Map && layout.get() == SymbolPlacementType::Line; + auto glyphMapIt = glyphMap.find(layout.get()); + const Glyphs& glyphs = glyphMapIt != glyphMap.end() + ? glyphMapIt->second : Glyphs(); + + auto glyphPositionsIt = glyphPositions.find(layout.get()); + const GlyphPositionMap& glyphPositionMap = glyphPositionsIt != glyphPositions.end() + ? glyphPositionsIt->second : GlyphPositionMap(); + for (auto it = features.begin(); it != features.end(); ++it) { auto& feature = *it; if (feature.geometry.empty()) continue; std::pair shapedTextOrientations; optional shapedIcon; - GlyphPositions face; // if feature has text, shape the text if (feature.text) { - auto glyphPositions = glyphs.find(layout.get()); - if (glyphPositions != glyphs.end()) { // If there are no glyphs available for this feature, skip shaping - auto applyShaping = [&] (const std::u16string& text, WritingModeType writingMode) { - const float oneEm = 24.0f; - const Shaping result = getShaping( - /* string */ text, - /* maxWidth: ems */ layout.get() != SymbolPlacementType::Line ? - layout.get() * oneEm : 0, - /* lineHeight: ems */ layout.get() * oneEm, - /* horizontalAlign */ horizontalAlign, - /* verticalAlign */ verticalAlign, - /* justify */ justify, - /* spacing: ems */ util::i18n::allowsLetterSpacing(*feature.text) ? layout.get() * oneEm : 0.0f, - /* translate */ Point(layout.evaluate(zoom, feature)[0] * oneEm, layout.evaluate(zoom, feature)[1] * oneEm), - /* verticalHeight */ oneEm, - /* writingMode */ writingMode, - /* bidirectional algorithm object */ bidi, - /* glyphs */ glyphPositions->second); - - return result; - }; - - shapedTextOrientations.first = applyShaping(*feature.text, WritingModeType::Horizontal); - - if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) { - shapedTextOrientations.second = applyShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical); - } + auto applyShaping = [&] (const std::u16string& text, WritingModeType writingMode) { + const float oneEm = 24.0f; + const Shaping result = getShaping( + /* string */ text, + /* maxWidth: ems */ layout.get() != SymbolPlacementType::Line ? + layout.get() * oneEm : 0, + /* lineHeight: ems */ layout.get() * oneEm, + /* horizontalAlign */ horizontalAlign, + /* verticalAlign */ verticalAlign, + /* justify */ justify, + /* spacing: ems */ util::i18n::allowsLetterSpacing(*feature.text) ? layout.get() * oneEm : 0.0f, + /* translate */ Point(layout.evaluate(zoom, feature)[0] * oneEm, layout.evaluate(zoom, feature)[1] * oneEm), + /* verticalHeight */ oneEm, + /* writingMode */ writingMode, + /* bidirectional algorithm object */ bidi, + /* glyphs */ glyphs); + + return result; + }; + + shapedTextOrientations.first = applyShaping(*feature.text, WritingModeType::Horizontal); + + if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) { + shapedTextOrientations.second = applyShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical); } } // if feature has icon, get sprite atlas position if (feature.icon) { - auto image = icons.find(*feature.icon); - if (image != icons.end()) { - shapedIcon = PositionedIcon::shapeIcon(image->second, + auto image = imageMap.find(*feature.icon); + if (image != imageMap.end()) { + shapedIcon = PositionedIcon::shapeIcon( + imagePositions.at(*feature.icon), layout.evaluate(zoom, feature), layout.evaluate(zoom, feature) * util::DEG2RAD); - if (image->second.sdf) { + if (image->second->sdf) { sdfIcons = true; } - if (image->second.pixelRatio != pixelRatio) { + if (image->second->pixelRatio != pixelRatio) { iconsNeedLinear = true; } else if (layout.get().constantOr(1) != 0) { iconsNeedLinear = true; @@ -281,8 +286,7 @@ void SymbolLayout::prepare(const GlyphPositionMap& glyphs, const IconMap& icons) // if either shapedText or icon position is present, add the feature if (shapedTextOrientations.first || shapedIcon) { - auto glyphPositionsIt = glyphs.find(layout.get()); - addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionsIt == glyphs.end() ? GlyphPositions() : glyphPositionsIt->second); + addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap); } feature.geometry.clear(); @@ -295,7 +299,7 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolFeature& feature, const std::pair& shapedTextOrientations, optional shapedIcon, - const GlyphPositions& glyphs) { + const GlyphPositionMap& glyphPositionMap) { const float minScale = 0.5f; const float glyphSize = 24.0f; @@ -352,7 +356,7 @@ void SymbolLayout::addFeature(const std::size_t index, addToBuffers, symbolInstances.size(), textBoxScale, textPadding, textPlacement, iconBoxScale, iconPadding, iconPlacement, - glyphs, indexedFeature, index); + glyphPositionMap, indexedFeature, index); }; const auto& type = feature.getType(); diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 770820542d9..b22c47c567e 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -30,10 +30,11 @@ class SymbolLayout { SymbolLayout(const BucketParameters&, const std::vector&, const GeometryTileLayer&, - IconDependencies&, + ImageDependencies&, GlyphDependencies&); - void prepare(const GlyphPositionMap& glyphs, const IconMap& icons); + void prepare(const GlyphMap&, const GlyphPositions&, + const ImageMap&, const ImagePositions&); std::unique_ptr place(CollisionTile&); @@ -54,7 +55,7 @@ class SymbolLayout { const SymbolFeature&, const std::pair& shapedTextOrientations, optional shapedIcon, - const GlyphPositions& face); + const GlyphPositionMap&); bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&); std::map> compareText; diff --git a/src/mbgl/programs/fill_extrusion_program.cpp b/src/mbgl/programs/fill_extrusion_program.cpp index 3f85d83788e..aaf192a843e 100644 --- a/src/mbgl/programs/fill_extrusion_program.cpp +++ b/src/mbgl/programs/fill_extrusion_program.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -46,8 +46,8 @@ FillExtrusionUniforms::values(mat4 matrix, FillExtrusionPatternUniforms::Values FillExtrusionPatternUniforms::values(mat4 matrix, Size atlasSize, - const SpriteAtlasElement& a, - const SpriteAtlasElement& b, + const ImagePosition& a, + const ImagePosition& b, const Faded& fading, const UnwrappedTileID& tileID, const TransformState& state, diff --git a/src/mbgl/programs/fill_extrusion_program.hpp b/src/mbgl/programs/fill_extrusion_program.hpp index 7f1c76a6ada..820670068e1 100644 --- a/src/mbgl/programs/fill_extrusion_program.hpp +++ b/src/mbgl/programs/fill_extrusion_program.hpp @@ -16,7 +16,7 @@ namespace mbgl { -class SpriteAtlasElement; +class ImagePosition; class UnwrappedTileID; class TransformState; template class Faded; @@ -68,8 +68,8 @@ struct FillExtrusionPatternUniforms : gl::Uniforms< { static Values values(mat4, Size atlasSize, - const SpriteAtlasElement&, - const SpriteAtlasElement&, + const ImagePosition&, + const ImagePosition&, const Faded&, const UnwrappedTileID&, const TransformState&, diff --git a/src/mbgl/programs/fill_program.cpp b/src/mbgl/programs/fill_program.cpp index 6c19e503cea..46dc830102f 100644 --- a/src/mbgl/programs/fill_program.cpp +++ b/src/mbgl/programs/fill_program.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -14,8 +14,8 @@ FillPatternUniforms::Values FillPatternUniforms::values(mat4 matrix, Size framebufferSize, Size atlasSize, - const SpriteAtlasElement& a, - const SpriteAtlasElement& b, + const ImagePosition& a, + const ImagePosition& b, const Faded& fading, const UnwrappedTileID& tileID, const TransformState& state) diff --git a/src/mbgl/programs/fill_program.hpp b/src/mbgl/programs/fill_program.hpp index 093485fc7f2..2dfeea32799 100644 --- a/src/mbgl/programs/fill_program.hpp +++ b/src/mbgl/programs/fill_program.hpp @@ -16,7 +16,7 @@ namespace mbgl { -class SpriteAtlasElement; +class ImagePosition; class UnwrappedTileID; class TransformState; template class Faded; @@ -51,8 +51,8 @@ struct FillPatternUniforms : gl::Uniforms< static Values values(mat4 matrix, Size framebufferSize, Size atlasSize, - const SpriteAtlasElement&, - const SpriteAtlasElement&, + const ImagePosition&, + const ImagePosition&, const Faded&, const UnwrappedTileID&, const TransformState&); diff --git a/src/mbgl/programs/line_program.cpp b/src/mbgl/programs/line_program.cpp index 2c65cb74edf..86645588ca6 100644 --- a/src/mbgl/programs/line_program.cpp +++ b/src/mbgl/programs/line_program.cpp @@ -1,9 +1,9 @@ #include #include #include +#include #include #include -#include #include namespace mbgl { @@ -89,8 +89,8 @@ LinePatternProgram::uniformValues(const LinePaintProperties::PossiblyEvaluated& const TransformState& state, const std::array& pixelsToGLUnits, const Size atlasSize, - const SpriteAtlasElement& posA, - const SpriteAtlasElement& posB) { + const ImagePosition& posA, + const ImagePosition& posB) { std::array sizeA {{ tile.id.pixelsToTileUnits(posA.displaySize()[0] * properties.get().fromScale, state.getIntegerZoom()), posA.displaySize()[1] diff --git a/src/mbgl/programs/line_program.hpp b/src/mbgl/programs/line_program.hpp index 6f6ceeb32b3..fadd351026c 100644 --- a/src/mbgl/programs/line_program.hpp +++ b/src/mbgl/programs/line_program.hpp @@ -16,7 +16,7 @@ namespace mbgl { class RenderTile; class TransformState; class LinePatternPos; -class SpriteAtlasElement; +class ImagePosition; namespace uniforms { MBGL_DEFINE_UNIFORM_SCALAR(float, u_ratio); @@ -125,8 +125,8 @@ class LinePatternProgram : public Program< const TransformState&, const std::array& pixelsToGLUnits, Size atlasSize, - const SpriteAtlasElement& posA, - const SpriteAtlasElement& posB); + const ImagePosition& posA, + const ImagePosition& posB); }; class LineSDFProgram : public Program< diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index bc4e5f57cbb..cbddade8996 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace mbgl { diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index 652f2ea8e37..002b6e28b31 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -57,6 +57,7 @@ class SymbolBucket : public Bucket { gl::VertexVector vertices; gl::IndexVector triangles; gl::SegmentVector segments; + PremultipliedImage atlasImage; optional> vertexBuffer; optional> indexBuffer; diff --git a/src/mbgl/renderer/image_atlas.cpp b/src/mbgl/renderer/image_atlas.cpp new file mode 100644 index 00000000000..8eee7c20951 --- /dev/null +++ b/src/mbgl/renderer/image_atlas.cpp @@ -0,0 +1,60 @@ +#include + +#include + +namespace mbgl { + +static constexpr uint32_t padding = 1; + +ImagePosition::ImagePosition(const mapbox::Bin& bin, const style::Image::Impl& image) + : pixelRatio(image.pixelRatio), + textureRect( + bin.x + padding, + bin.y + padding, + bin.w - padding * 2, + bin.h - padding * 2 + ) { +} + +ImageAtlas makeImageAtlas(const ImageMap& images) { + ImageAtlas result; + + mapbox::ShelfPack::ShelfPackOptions options; + options.autoResize = true; + mapbox::ShelfPack pack(0, 0, options); + + for (const auto& entry : images) { + const style::Image::Impl& image = *entry.second; + + const mapbox::Bin& bin = *pack.packOne(-1, + image.image.size.width + 2 * padding, + image.image.size.height + 2 * padding); + + result.image.resize({ + static_cast(pack.width()), + static_cast(pack.height()) + }); + + PremultipliedImage::copy(image.image, + result.image, + { 0, 0 }, + { + bin.x + padding, + bin.y + padding + }, + image.image.size); + + result.positions.emplace(image.id, + ImagePosition { bin, image }); + } + + pack.shrink(); + result.image.resize({ + static_cast(pack.width()), + static_cast(pack.height()) + }); + + return result; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/image_atlas.hpp b/src/mbgl/renderer/image_atlas.hpp new file mode 100644 index 00000000000..b3cc166effd --- /dev/null +++ b/src/mbgl/renderer/image_atlas.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace mbgl { + +class ImagePosition { +public: + ImagePosition(const mapbox::Bin&, const style::Image::Impl&); + + float pixelRatio; + Rect textureRect; + + std::array tl() const { + return {{ + textureRect.x, + textureRect.y + }}; + } + + std::array br() const { + return {{ + static_cast(textureRect.x + textureRect.w), + static_cast(textureRect.y + textureRect.h) + }}; + } + + std::array displaySize() const { + return {{ + textureRect.w / pixelRatio, + textureRect.h / pixelRatio, + }}; + } +}; + +using ImagePositions = std::map; + +class ImageAtlas { +public: + PremultipliedImage image; + ImagePositions positions; +}; + +ImageAtlas makeImageAtlas(const ImageMap&); + +} // namespace mbgl diff --git a/src/mbgl/renderer/image_manager.cpp b/src/mbgl/renderer/image_manager.cpp new file mode 100644 index 00000000000..d0a106ede69 --- /dev/null +++ b/src/mbgl/renderer/image_manager.cpp @@ -0,0 +1,166 @@ +#include +#include +#include + +namespace mbgl { + +void ImageManager::onSpriteLoaded() { + loaded = true; + for (const auto& entry : requestors) { + notify(*entry.first, entry.second); + } + requestors.clear(); +} + +void ImageManager::addImage(Immutable image_) { + assert(images.find(image_->id) == images.end()); + images.emplace(image_->id, std::move(image_)); +} + +void ImageManager::updateImage(Immutable image_) { + removeImage(image_->id); + addImage(std::move(image_)); +} + +void ImageManager::removeImage(const std::string& id) { + assert(images.find(id) != images.end()); + images.erase(id); + + auto it = patterns.find(id); + if (it != patterns.end()) { + shelfPack.unref(*it->second.bin); + patterns.erase(it); + } +} + +const style::Image::Impl* ImageManager::getImage(const std::string& id) const { + const auto it = images.find(id); + if (it != images.end()) { + return it->second.get(); + } + return nullptr; +} + +void ImageManager::getImages(ImageRequestor& requestor, ImageDependencies dependencies) { + // If the sprite has been loaded, or if all the icon dependencies are already present + // (i.e. if they've been addeded via runtime styling), then notify the requestor immediately. + // Otherwise, delay notification until the sprite is loaded. At that point, if any of the + // dependencies are still unavailable, we'll just assume they are permanently missing. + bool hasAllDependencies = true; + if (!isLoaded()) { + for (const auto& dependency : dependencies) { + if (images.find(dependency) == images.end()) { + hasAllDependencies = false; + } + } + } + if (isLoaded() || hasAllDependencies) { + notify(requestor, dependencies); + } else { + requestors.emplace(&requestor, std::move(dependencies)); + } +} + +void ImageManager::removeRequestor(ImageRequestor& requestor) { + requestors.erase(&requestor); +} + +void ImageManager::notify(ImageRequestor& requestor, const ImageDependencies& dependencies) const { + ImageMap response; + + for (const auto& dependency : dependencies) { + auto it = images.find(dependency); + if (it != images.end()) { + response.emplace(*it); + } + } + + requestor.onImagesAvailable(response); +} + +void ImageManager::dumpDebugLogs() const { + Log::Info(Event::General, "ImageManager::loaded: %d", loaded); +} + +// When copied into the atlas texture, image data is padded by one pixel on each side. Icon +// images are padded with fully transparent pixels, while pattern images are padded with a +// copy of the image data wrapped from the opposite side. In both cases, this ensures the +// correct behavior of GL_LINEAR texture sampling mode. +static constexpr uint16_t padding = 1; + +static mapbox::ShelfPack::ShelfPackOptions shelfPackOptions() { + mapbox::ShelfPack::ShelfPackOptions options; + options.autoResize = true; + return options; +} + +ImageManager::ImageManager() + : shelfPack(64, 64, shelfPackOptions()) { +} + +ImageManager::~ImageManager() = default; + +optional ImageManager::getPattern(const std::string& id) { + auto it = patterns.find(id); + if (it != patterns.end()) { + return it->second.position; + } + + const style::Image::Impl* image = getImage(id); + if (!image) { + return {}; + } + + const uint16_t width = image->image.size.width + padding * 2; + const uint16_t height = image->image.size.height + padding * 2; + + mapbox::Bin* bin = shelfPack.packOne(-1, width, height); + if (!bin) { + return {}; + } + + atlasImage.resize(getPixelSize()); + + const PremultipliedImage& src = image->image; + + const uint32_t x = bin->x + padding; + const uint32_t y = bin->y + padding; + const uint32_t w = src.size.width; + const uint32_t h = src.size.height; + + PremultipliedImage::copy(src, atlasImage, { 0, 0 }, { x, y }, { w, h }); + + // Add 1 pixel wrapped padding on each side of the image. + PremultipliedImage::copy(src, atlasImage, { 0, h - 1 }, { x, y - 1 }, { w, 1 }); // T + PremultipliedImage::copy(src, atlasImage, { 0, 0 }, { x, y + h }, { w, 1 }); // B + PremultipliedImage::copy(src, atlasImage, { w - 1, 0 }, { x - 1, y }, { 1, h }); // L + PremultipliedImage::copy(src, atlasImage, { 0, 0 }, { x + w, y }, { 1, h }); // R + + dirty = true; + + return patterns.emplace(id, Pattern { bin, { *bin, *image } }).first->second.position; +} + +Size ImageManager::getPixelSize() const { + return Size { + static_cast(shelfPack.width()), + static_cast(shelfPack.height()) + }; +} + +void ImageManager::upload(gl::Context& context, gl::TextureUnit unit) { + if (!atlasTexture) { + atlasTexture = context.createTexture(atlasImage, unit); + } else if (dirty) { + context.updateTexture(*atlasTexture, atlasImage, unit); + } + + dirty = false; +} + +void ImageManager::bind(gl::Context& context, gl::TextureUnit unit) { + upload(context, unit); + context.bindTexture(*atlasTexture, unit, gl::TextureFilter::Linear); +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/image_manager.hpp b/src/mbgl/renderer/image_manager.hpp new file mode 100644 index 00000000000..9a9a4ce997d --- /dev/null +++ b/src/mbgl/renderer/image_manager.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace mbgl { + +namespace gl { +class Context; +} // namespace gl + +class ImageRequestor { +public: + virtual ~ImageRequestor() = default; + virtual void onImagesAvailable(ImageMap) = 0; +}; + +/* + ImageManager does two things: + + 1. Tracks requests for icon images from tile workers and sends responses when the requests are fulfilled. + 2. Builds a texture atlas for pattern images. + + These are disparate responsibilities and should eventually be handled by different classes. When we implement + data-driven support for `*-pattern`, we'll likely use per-bucket pattern atlases, and that would be a good time + to refactor this. +*/ +class ImageManager : public util::noncopyable { +public: + ImageManager(); + ~ImageManager(); + + void onSpriteLoaded(); + + bool isLoaded() const { + return loaded; + } + + void dumpDebugLogs() const; + + const style::Image::Impl* getImage(const std::string&) const; + + void addImage(Immutable); + void updateImage(Immutable); + void removeImage(const std::string&); + + void getImages(ImageRequestor&, ImageDependencies); + void removeRequestor(ImageRequestor&); + +private: + void notify(ImageRequestor&, const ImageDependencies&) const; + + bool loaded = false; + + std::unordered_map requestors; + ImageMap images; + +// Pattern stuff +public: + optional getPattern(const std::string& name); + + void bind(gl::Context&, gl::TextureUnit unit); + void upload(gl::Context&, gl::TextureUnit unit); + + Size getPixelSize() const; + + // Only for use in tests. + const PremultipliedImage& getAtlasImage() const { + return atlasImage; + } + +private: + struct Pattern { + mapbox::Bin* bin; + ImagePosition position; + }; + + mapbox::ShelfPack shelfPack; + std::unordered_map patterns; + PremultipliedImage atlasImage; + mbgl::optional atlasTexture; + bool dirty = true; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_raster_layer.cpp b/src/mbgl/renderer/layers/render_raster_layer.cpp index 28cf722c14e..30b506b63b9 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.cpp +++ b/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -36,16 +36,6 @@ bool RenderRasterLayer::hasTransition() const { return unevaluated.hasTransition(); } -void RenderRasterLayer::uploadBuckets(gl::Context& context, RenderSource* source) { - RenderLayer::uploadBuckets(context, source); - if (renderTiles.empty()) { - RenderImageSource* imageSource = source->as(); - if (imageSource) { - imageSource->upload(context); - } - } -} - void RenderRasterLayer::render(Painter& painter, PaintParameters& parameters, RenderSource* source) { RenderLayer::render(painter, parameters, source); if (renderTiles.empty()) { diff --git a/src/mbgl/renderer/layers/render_raster_layer.hpp b/src/mbgl/renderer/layers/render_raster_layer.hpp index f252d802427..ce46152a956 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.hpp +++ b/src/mbgl/renderer/layers/render_raster_layer.hpp @@ -15,8 +15,7 @@ class RenderRasterLayer: public RenderLayer { void evaluate(const PropertyEvaluationParameters&) override; bool hasTransition() const override; - void uploadBuckets(gl::Context&, RenderSource*) override; - void render(Painter& , PaintParameters& , RenderSource*) override; + void render(Painter&, PaintParameters&, RenderSource*) override; std::unique_ptr createBucket(const BucketParameters&, const std::vector&) const override; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 0054d9f874f..395cf283d5d 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -25,11 +25,11 @@ std::unique_ptr RenderSymbolLayer::createLayout(const BucketParame const std::vector& group, const GeometryTileLayer& layer, GlyphDependencies& glyphDependencies, - IconDependencies& iconDependencies) const { + ImageDependencies& imageDependencies) const { return std::make_unique(parameters, group, layer, - iconDependencies, + imageDependencies, glyphDependencies); } diff --git a/src/mbgl/renderer/layers/render_symbol_layer.hpp b/src/mbgl/renderer/layers/render_symbol_layer.hpp index 42205496d90..8c7d43bf3a0 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.hpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -76,7 +76,7 @@ class RenderSymbolLayer: public RenderLayer { std::unique_ptr createBucket(const BucketParameters&, const std::vector&) const override; std::unique_ptr createLayout(const BucketParameters&, const std::vector&, - const GeometryTileLayer&, GlyphDependencies&, IconDependencies&) const; + const GeometryTileLayer&, GlyphDependencies&, ImageDependencies&) const; // Paint properties style::SymbolPaintProperties::Unevaluated unevaluated; diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index 6fb325db407..673bf669019 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -21,16 +21,12 @@ #include #include -#include +#include #include -#include #include #include -#include -#include - #include #include #include @@ -141,8 +137,7 @@ void Painter::render(RenderStyle& style, const FrameData& frame_, View& view) { view }; - glyphAtlas = style.glyphAtlas.get(); - spriteAtlas = style.spriteAtlas.get(); + imageManager = style.imageManager.get(); lineAtlas = style.lineAtlas.get(); evaluatedLight = style.getRenderLight().getEvaluated(); @@ -172,15 +167,9 @@ void Painter::render(RenderStyle& style, const FrameData& frame_, View& view) { { MBGL_DEBUG_GROUP(context, "upload"); - spriteAtlas->upload(context, 0); - + imageManager->upload(context, 0); lineAtlas->upload(context, 0); - glyphAtlas->upload(context, 0); frameHistory.upload(context, 0); - - for (const auto& item : order) { - item.layer.uploadBuckets(context, item.source); - } } // - CLEAR ------------------------------------------------------------------------------------- @@ -202,14 +191,14 @@ void Painter::render(RenderStyle& style, const FrameData& frame_, View& view) { MBGL_DEBUG_GROUP(context, "clip"); // Update all clipping IDs. - algorithm::ClipIDGenerator generator; + clipIDGenerator = algorithm::ClipIDGenerator(); for (const auto& source : sources) { - source->startRender(generator, projMatrix, nearClippedProjMatrix, state); + source->startRender(*this); } MBGL_DEBUG_GROUP(context, "clipping masks"); - for (const auto& stencil : generator.getStencils()) { + for (const auto& stencil : clipIDGenerator.getStencils()) { MBGL_DEBUG_GROUP(context, std::string{ "mask: " } + util::toString(stencil.first)); renderClippingMask(stencil.first, stencil.second); } diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp index fddaff63186..f2d06a0e20f 100644 --- a/src/mbgl/renderer/painter.hpp +++ b/src/mbgl/renderer/painter.hpp @@ -21,6 +21,8 @@ #include #include +#include + #include #include #include @@ -30,9 +32,8 @@ namespace mbgl { class RenderStyle; class RenderTile; -class SpriteAtlas; +class ImageManager; class View; -class GlyphAtlas; class LineAtlas; struct FrameData; class Tile; @@ -100,7 +101,6 @@ class Painter : private util::noncopyable { bool needsAnimation() const; -private: template void renderPass(PaintParameters&, RenderPass, @@ -123,9 +123,10 @@ class Painter : private util::noncopyable { } #endif -private: gl::Context& context; + algorithm::ClipIDGenerator clipIDGenerator; + mat4 projMatrix; mat4 nearClippedProjMatrix; @@ -150,8 +151,7 @@ class Painter : private util::noncopyable { float depthRangeSize; const float depthEpsilon = 1.0f / (1 << 16); - SpriteAtlas* spriteAtlas = nullptr; - GlyphAtlas* glyphAtlas = nullptr; + ImageManager* imageManager = nullptr; LineAtlas* lineAtlas = nullptr; optional extrusionTexture; diff --git a/src/mbgl/renderer/painters/painter_background.cpp b/src/mbgl/renderer/painters/painter_background.cpp index 9cbc3d516cd..7ebb735df84 100644 --- a/src/mbgl/renderer/painters/painter_background.cpp +++ b/src/mbgl/renderer/painters/painter_background.cpp @@ -1,10 +1,10 @@ #include #include #include +#include #include #include #include -#include #include namespace mbgl { @@ -24,13 +24,13 @@ void Painter::renderBackground(PaintParameters& parameters, const RenderBackgrou const FillProgram::PaintPropertyBinders paintAttibuteData(properties, 0); if (!background.get().to.empty()) { - optional imagePosA = spriteAtlas->getPattern(background.get().from); - optional imagePosB = spriteAtlas->getPattern(background.get().to); + optional imagePosA = imageManager->getPattern(background.get().from); + optional imagePosB = imageManager->getPattern(background.get().to); if (!imagePosA || !imagePosB) return; - spriteAtlas->bind(true, context, 0); + imageManager->bind(context, 0); for (const auto& tileID : util::tileCover(state, state.getIntegerZoom())) { parameters.programs.fillPattern.draw( @@ -42,7 +42,7 @@ void Painter::renderBackground(PaintParameters& parameters, const RenderBackgrou FillPatternUniforms::values( matrixForTile(tileID), context.viewport.getCurrentValue().size, - spriteAtlas->getPixelSize(), + imageManager->getPixelSize(), *imagePosA, *imagePosB, background.get(), diff --git a/src/mbgl/renderer/painters/painter_fill.cpp b/src/mbgl/renderer/painters/painter_fill.cpp index d15a871d980..b7e0077ed05 100644 --- a/src/mbgl/renderer/painters/painter_fill.cpp +++ b/src/mbgl/renderer/painters/painter_fill.cpp @@ -3,8 +3,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -24,14 +24,14 @@ void Painter::renderFill(PaintParameters& parameters, return; } - optional imagePosA = spriteAtlas->getPattern(properties.get().from); - optional imagePosB = spriteAtlas->getPattern(properties.get().to); + optional imagePosA = imageManager->getPattern(properties.get().from); + optional imagePosB = imageManager->getPattern(properties.get().to); if (!imagePosA || !imagePosB) { return; } - spriteAtlas->bind(true, context, 0); + imageManager->bind(context, 0); auto draw = [&] (uint8_t sublayer, auto& program, @@ -49,7 +49,7 @@ void Painter::renderFill(PaintParameters& parameters, properties.get(), state), context.viewport.getCurrentValue().size, - spriteAtlas->getPixelSize(), + imageManager->getPixelSize(), *imagePosA, *imagePosB, properties.get(), diff --git a/src/mbgl/renderer/painters/painter_fill_extrusion.cpp b/src/mbgl/renderer/painters/painter_fill_extrusion.cpp index c28cb76bff5..55e56554dc7 100644 --- a/src/mbgl/renderer/painters/painter_fill_extrusion.cpp +++ b/src/mbgl/renderer/painters/painter_fill_extrusion.cpp @@ -3,8 +3,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -25,16 +25,14 @@ void Painter::renderFillExtrusion(PaintParameters& parameters, } if (!properties.get().from.empty()) { - optional imagePosA = - spriteAtlas->getPattern(properties.get().from); - optional imagePosB = - spriteAtlas->getPattern(properties.get().to); + optional imagePosA = imageManager->getPattern(properties.get().from); + optional imagePosB = imageManager->getPattern(properties.get().to); if (!imagePosA || !imagePosB) { return; } - spriteAtlas->bind(true, context, 0); + imageManager->bind(context, 0); parameters.programs.fillExtrusionPattern.draw( context, @@ -46,7 +44,7 @@ void Painter::renderFillExtrusion(PaintParameters& parameters, tile.translatedClipMatrix(properties.get(), properties.get(), state), - spriteAtlas->getPixelSize(), + imageManager->getPixelSize(), *imagePosA, *imagePosB, properties.get(), diff --git a/src/mbgl/renderer/painters/painter_line.cpp b/src/mbgl/renderer/painters/painter_line.cpp index 40076726af3..ea2a63529d0 100644 --- a/src/mbgl/renderer/painters/painter_line.cpp +++ b/src/mbgl/renderer/painters/painter_line.cpp @@ -3,10 +3,10 @@ #include #include #include +#include #include #include #include -#include #include namespace mbgl { @@ -61,13 +61,13 @@ void Painter::renderLine(PaintParameters& parameters, lineAtlas->getSize().width)); } else if (!properties.get().from.empty()) { - optional posA = spriteAtlas->getPattern(properties.get().from); - optional posB = spriteAtlas->getPattern(properties.get().to); + optional posA = imageManager->getPattern(properties.get().from); + optional posB = imageManager->getPattern(properties.get().to); if (!posA || !posB) return; - spriteAtlas->bind(true, context, 0); + imageManager->bind(context, 0); draw(parameters.programs.linePattern, LinePatternProgram::uniformValues( @@ -75,7 +75,7 @@ void Painter::renderLine(PaintParameters& parameters, tile, state, pixelsToGLUnits, - spriteAtlas->getPixelSize(), + imageManager->getPixelSize(), *posA, *posB)); diff --git a/src/mbgl/renderer/painters/painter_symbol.cpp b/src/mbgl/renderer/painters/painter_symbol.cpp index 563489987c2..58700fc4e8e 100644 --- a/src/mbgl/renderer/painters/painter_symbol.cpp +++ b/src/mbgl/renderer/painters/painter_symbol.cpp @@ -5,12 +5,11 @@ #include #include #include -#include #include #include #include #include -#include +#include #include @@ -63,15 +62,21 @@ void Painter::renderSymbol(PaintParameters& parameters, ); }; + assert(dynamic_cast(&tile.tile)); + GeometryTile& geometryTile = static_cast(tile.tile); + if (bucket.hasIconData()) { auto values = layer.iconPropertyValues(layout); auto paintPropertyValues = layer.iconPaintProperties(); const bool iconScaled = layout.get().constantOr(1.0) != 1.0 || bucket.iconsNeedLinear; const bool iconTransformed = values.rotationAlignment == AlignmentType::Map || state.getPitch() != 0; - spriteAtlas->bind(bucket.sdfIcons || state.isChanging() || iconScaled || iconTransformed, context, 0); - const Size texsize = spriteAtlas->getPixelSize(); + context.bindTexture(*geometryTile.iconAtlasTexture, 0, + bucket.sdfIcons || state.isChanging() || iconScaled || iconTransformed + ? gl::TextureFilter::Linear : gl::TextureFilter::Nearest); + + const Size texsize = geometryTile.iconAtlasTexture->size; if (bucket.sdfIcons) { if (values.hasHalo) { @@ -105,12 +110,12 @@ void Painter::renderSymbol(PaintParameters& parameters, } if (bucket.hasTextData()) { - glyphAtlas->bind(context, 0); + context.bindTexture(*geometryTile.glyphAtlasTexture, 0, gl::TextureFilter::Linear); auto values = layer.textPropertyValues(layout); auto paintPropertyValues = layer.textPaintProperties(); - const Size texsize = glyphAtlas->getSize(); + const Size texsize = geometryTile.glyphAtlasTexture->size; if (values.hasHalo) { draw(parameters.programs.symbolGlyph, diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp index 4bffb87cf1d..3f9d68003e6 100644 --- a/src/mbgl/renderer/render_layer.cpp +++ b/src/mbgl/renderer/render_layer.cpp @@ -68,15 +68,6 @@ void RenderLayer::setRenderTiles(std::vector> renderTiles = std::move(tiles); } -void RenderLayer::uploadBuckets(gl::Context& context, RenderSource*) { - for (const auto& tileRef : renderTiles) { - const auto& bucket = tileRef.get().tile.getBucket(*baseImpl); - if (bucket && bucket->needsUpload()) { - bucket->upload(context); - } - } -} - void RenderLayer::render(Painter& painter, PaintParameters& parameters, RenderSource*) { for (auto& tileRef : renderTiles) { auto& tile = tileRef.get(); diff --git a/src/mbgl/renderer/render_layer.hpp b/src/mbgl/renderer/render_layer.hpp index ce71794c078..50ad4c771a1 100644 --- a/src/mbgl/renderer/render_layer.hpp +++ b/src/mbgl/renderer/render_layer.hpp @@ -66,8 +66,8 @@ class RenderLayer { // Checks whether this layer can be rendered. bool needsRendering(float zoom) const; - virtual void uploadBuckets(gl::Context&, RenderSource* source); - virtual void render(Painter& , PaintParameters& , RenderSource* source); + virtual void render(Painter&, PaintParameters&, RenderSource*); + // Check wether the given geometry intersects // with the feature virtual bool queryIntersectsFeature( diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index b396a0fdb60..a00a6c797d9 100644 --- a/src/mbgl/renderer/render_source.hpp +++ b/src/mbgl/renderer/render_source.hpp @@ -26,10 +26,6 @@ class Tile; class RenderSourceObserver; class TileParameters; -namespace algorithm { -class ClipIDGenerator; -} // namespace algorithm - class RenderSource : protected TileObserver { public: static std::unique_ptr create(Immutable); @@ -58,10 +54,7 @@ class RenderSource : protected TileObserver { bool needsRelayout, const TileParameters&) = 0; - virtual void startRender(algorithm::ClipIDGenerator&, - const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState&) = 0; + virtual void startRender(Painter&) = 0; virtual void finishRender(Painter&) = 0; virtual std::map& getRenderTiles() = 0; diff --git a/src/mbgl/renderer/render_style.cpp b/src/mbgl/renderer/render_style.cpp index 0998722f75a..9b0b512bf9e 100644 --- a/src/mbgl/renderer/render_style.cpp +++ b/src/mbgl/renderer/render_style.cpp @@ -16,14 +16,13 @@ #include #include #include +#include #include #include #include -#include #include -#include +#include #include -#include #include #include #include @@ -38,15 +37,15 @@ RenderStyleObserver nullObserver; RenderStyle::RenderStyle(Scheduler& scheduler_, FileSource& fileSource_) : scheduler(scheduler_), fileSource(fileSource_), - glyphAtlas(std::make_unique(Size{ 2048, 2048 }, fileSource)), - spriteAtlas(std::make_unique()), + glyphManager(std::make_unique(fileSource)), + imageManager(std::make_unique()), lineAtlas(std::make_unique(Size{ 256, 512 })), imageImpls(makeMutable>>()), sourceImpls(makeMutable>>()), layerImpls(makeMutable>>()), renderLight(makeMutable()), observer(&nullObserver) { - glyphAtlas->setObserver(this); + glyphManager->setObserver(this); } RenderStyle::~RenderStyle() = default; @@ -100,11 +99,11 @@ void RenderStyle::update(const UpdateParameters& parameters) { parameters.fileSource, parameters.mode, parameters.annotationManager, - *spriteAtlas, - *glyphAtlas + *imageManager, + *glyphManager }; - glyphAtlas->setURL(parameters.glyphURL); + glyphManager->setURL(parameters.glyphURL); // Update light. const bool lightChanged = renderLight.impl != parameters.light; @@ -124,21 +123,21 @@ void RenderStyle::update(const UpdateParameters& parameters) { // Remove removed images from sprite atlas. for (const auto& entry : imageDiff.removed) { - spriteAtlas->removeImage(entry.first); + imageManager->removeImage(entry.first); } // Add added images to sprite atlas. for (const auto& entry : imageDiff.added) { - spriteAtlas->addImage(entry.second); + imageManager->addImage(entry.second); } // Update changed images. for (const auto& entry : imageDiff.changed) { - spriteAtlas->updateImage(entry.second.after); + imageManager->updateImage(entry.second.after); } - if (parameters.spriteLoaded && !spriteAtlas->isLoaded()) { - spriteAtlas->onSpriteLoaded(); + if (parameters.spriteLoaded && !imageManager->isLoaded()) { + imageManager->onSpriteLoaded(); } @@ -208,7 +207,10 @@ void RenderStyle::update(const UpdateParameters& parameters) { needsRendering = true; } - if (hasLayoutDifference(layerDiff, layer->id)) { + if (hasLayoutDifference(layerDiff, layer->id) || + !imageDiff.added.empty() || + !imageDiff.removed.empty() || + !imageDiff.changed.empty()) { needsRelayout = true; } @@ -249,7 +251,7 @@ bool RenderStyle::isLoaded() const { } } - if (!spriteAtlas->isLoaded()) { + if (!imageManager->isLoaded()) { return false; } @@ -442,7 +444,7 @@ void RenderStyle::dumpDebugLogs() const { entry.second->dumpDebugLogs(); } - spriteAtlas->dumpDebugLogs(); + imageManager->dumpDebugLogs(); } } // namespace mbgl diff --git a/src/mbgl/renderer/render_style.hpp b/src/mbgl/renderer/render_style.hpp index ff222f2569d..dc33e7b2f40 100644 --- a/src/mbgl/renderer/render_style.hpp +++ b/src/mbgl/renderer/render_style.hpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include @@ -16,8 +16,8 @@ namespace mbgl { class FileSource; -class GlyphAtlas; -class SpriteAtlas; +class GlyphManager; +class ImageManager; class LineAtlas; class RenderData; class TransformState; @@ -32,7 +32,7 @@ class Source; class Layer; } // namespace style -class RenderStyle : public GlyphAtlasObserver, +class RenderStyle : public GlyphManagerObserver, public RenderSourceObserver { public: RenderStyle(Scheduler&, FileSource&); @@ -66,8 +66,8 @@ class RenderStyle : public GlyphAtlasObserver, Scheduler& scheduler; FileSource& fileSource; - std::unique_ptr glyphAtlas; - std::unique_ptr spriteAtlas; + std::unique_ptr glyphManager; + std::unique_ptr imageManager; std::unique_ptr lineAtlas; private: @@ -79,7 +79,7 @@ class RenderStyle : public GlyphAtlasObserver, std::unordered_map> renderLayers; RenderLight renderLight; - // GlyphAtlasObserver implementation. + // GlyphManagerObserver implementation. void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr) override; // RenderSourceObserver implementation. diff --git a/src/mbgl/renderer/render_tile.cpp b/src/mbgl/renderer/render_tile.cpp index ce59186e617..59c3ea076b6 100644 --- a/src/mbgl/renderer/render_tile.cpp +++ b/src/mbgl/renderer/render_tile.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include namespace mbgl { @@ -44,15 +46,15 @@ mat4 RenderTile::translatedClipMatrix(const std::array& translation, return translateVtxMatrix(nearClippedMatrix, translation, anchor, state); } -void RenderTile::calculateMatrices(const mat4& projMatrix, - const mat4& projClipMatrix, - const TransformState& transform) { +void RenderTile::startRender(Painter& painter) { + tile.upload(painter.context); + // Calculate two matrices for this tile: matrix is the standard tile matrix; nearClippedMatrix // clips the near plane to 100 to save depth buffer precision - transform.matrixFor(matrix, id); - transform.matrixFor(nearClippedMatrix, id); - matrix::multiply(matrix, projMatrix, matrix); - matrix::multiply(nearClippedMatrix, projClipMatrix, nearClippedMatrix); + painter.state.matrixFor(matrix, id); + painter.state.matrixFor(nearClippedMatrix, id); + matrix::multiply(matrix, painter.projMatrix, matrix); + matrix::multiply(nearClippedMatrix, painter.nearClippedProjMatrix, nearClippedMatrix); } } // namespace mbgl diff --git a/src/mbgl/renderer/render_tile.hpp b/src/mbgl/renderer/render_tile.hpp index 02e8667eecb..66772788738 100644 --- a/src/mbgl/renderer/render_tile.hpp +++ b/src/mbgl/renderer/render_tile.hpp @@ -11,6 +11,7 @@ namespace mbgl { class Tile; class TransformState; +class Painter; class RenderTile { public: @@ -35,9 +36,8 @@ class RenderTile { style::TranslateAnchorType anchor, const TransformState&) const; - void calculateMatrices(const mat4& projMatrix, - const mat4& projClipMatrix, - const TransformState&); + void startRender(Painter&); + private: mat4 translateVtxMatrix(const mat4& tileMatrix, const std::array& translation, diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index 96500d8fae3..337b7b8b7a8 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -58,9 +59,9 @@ void RenderGeoJSONSource::update(Immutable baseImpl_, }); } -void RenderGeoJSONSource::startRender(algorithm::ClipIDGenerator& generator, const mat4& projMatrix, const mat4& clipMatrix, const TransformState& transform) { - generator.update(tilePyramid.getRenderTiles()); - tilePyramid.startRender(projMatrix, clipMatrix, transform); +void RenderGeoJSONSource::startRender(Painter& painter) { + painter.clipIDGenerator.update(tilePyramid.getRenderTiles()); + tilePyramid.startRender(painter); } void RenderGeoJSONSource::finishRender(Painter& painter) { diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp index 2683ef9d213..9b5477e1d0b 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.hpp +++ b/src/mbgl/renderer/sources/render_geojson_source.hpp @@ -22,10 +22,7 @@ class RenderGeoJSONSource : public RenderSource { bool needsRelayout, const TileParameters&) final; - void startRender(algorithm::ClipIDGenerator&, - const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState&) final; + void startRender(Painter&) final; void finishRender(Painter&) final; std::map& getRenderTiles() final; diff --git a/src/mbgl/renderer/sources/render_image_source.cpp b/src/mbgl/renderer/sources/render_image_source.cpp index ffa8e6bb5e2..7fb7caa580a 100644 --- a/src/mbgl/renderer/sources/render_image_source.cpp +++ b/src/mbgl/renderer/sources/render_image_source.cpp @@ -26,23 +26,24 @@ bool RenderImageSource::isLoaded() const { return !!bucket; } -void RenderImageSource::startRender(algorithm::ClipIDGenerator&, - const mat4& projMatrix, - const mat4&, - const TransformState& transformState) { - +void RenderImageSource::startRender(Painter& painter) { if (!isLoaded()) { return; } + matrices.clear(); for (size_t i = 0; i < tileIds.size(); i++) { mat4 matrix; matrix::identity(matrix); - transformState.matrixFor(matrix, tileIds[i]); - matrix::multiply(matrix, projMatrix, matrix); + painter.state.matrixFor(matrix, tileIds[i]); + matrix::multiply(matrix, painter.projMatrix, matrix); matrices.push_back(matrix); } + + if (bucket->needsUpload() && shouldRender) { + bucket->upload(painter.context); + } } void RenderImageSource::finishRender(Painter& painter) { @@ -66,12 +67,6 @@ std::vector RenderImageSource::querySourceFeatures(const SourceQueryOpt return {}; } -void RenderImageSource::upload(gl::Context& context) { - if (isLoaded() && bucket->needsUpload() && shouldRender) { - bucket->upload(context); - } -} - void RenderImageSource::update(Immutable baseImpl_, const std::vector>&, const bool needsRendering, diff --git a/src/mbgl/renderer/sources/render_image_source.hpp b/src/mbgl/renderer/sources/render_image_source.hpp index c4685cf8918..5175cbf4a4e 100644 --- a/src/mbgl/renderer/sources/render_image_source.hpp +++ b/src/mbgl/renderer/sources/render_image_source.hpp @@ -19,15 +19,9 @@ class RenderImageSource : public RenderSource { ~RenderImageSource() override; bool isLoaded() const final; - void upload(gl::Context&); - - void startRender(algorithm::ClipIDGenerator&, - const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState&) final; + void startRender(Painter&) final; void render(Painter&, PaintParameters&, const RenderLayer&); - void finishRender(Painter&) final; void update(Immutable, diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index 81a97716760..385437af1db 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -56,8 +56,8 @@ void RenderRasterSource::update(Immutable baseImpl_, }); } -void RenderRasterSource::startRender(algorithm::ClipIDGenerator&, const mat4& projMatrix, const mat4& clipMatrix, const TransformState& transform) { - tilePyramid.startRender(projMatrix, clipMatrix, transform); +void RenderRasterSource::startRender(Painter& painter) { + tilePyramid.startRender(painter); } void RenderRasterSource::finishRender(Painter& painter) { diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index 9209a5ca65b..d1e37a3099e 100644 --- a/src/mbgl/renderer/sources/render_raster_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_source.hpp @@ -18,10 +18,7 @@ class RenderRasterSource : public RenderSource { bool needsRelayout, const TileParameters&) final; - void startRender(algorithm::ClipIDGenerator&, - const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState&) final; + void startRender(Painter&) final; void finishRender(Painter&) final; std::map& getRenderTiles() final; diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index 30014a89dcd..5b266b10a5a 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -59,9 +60,9 @@ void RenderVectorSource::update(Immutable baseImpl_, }); } -void RenderVectorSource::startRender(algorithm::ClipIDGenerator& generator, const mat4& projMatrix, const mat4& clipMatrix, const TransformState& transform) { - generator.update(tilePyramid.getRenderTiles()); - tilePyramid.startRender(projMatrix, clipMatrix, transform); +void RenderVectorSource::startRender(Painter& painter) { + painter.clipIDGenerator.update(tilePyramid.getRenderTiles()); + tilePyramid.startRender(painter); } void RenderVectorSource::finishRender(Painter& painter) { diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp index 1f1acdd551e..d5d9598a75f 100644 --- a/src/mbgl/renderer/sources/render_vector_source.hpp +++ b/src/mbgl/renderer/sources/render_vector_source.hpp @@ -18,10 +18,7 @@ class RenderVectorSource : public RenderSource { bool needsRelayout, const TileParameters&) final; - void startRender(algorithm::ClipIDGenerator&, - const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState&) final; + void startRender(Painter&) final; void finishRender(Painter&) final; std::map& getRenderTiles() final; diff --git a/src/mbgl/renderer/tile_parameters.hpp b/src/mbgl/renderer/tile_parameters.hpp index b4a84ec6c81..cf7a5b100ab 100644 --- a/src/mbgl/renderer/tile_parameters.hpp +++ b/src/mbgl/renderer/tile_parameters.hpp @@ -8,8 +8,8 @@ class TransformState; class Scheduler; class FileSource; class AnnotationManager; -class SpriteAtlas; -class GlyphAtlas; +class ImageManager; +class GlyphManager; class TileParameters { public: @@ -20,8 +20,8 @@ class TileParameters { FileSource& fileSource; const MapMode mode; AnnotationManager& annotationManager; - SpriteAtlas& spriteAtlas; - GlyphAtlas& glyphAtlas; + ImageManager& imageManager; + GlyphManager& glyphManager; }; } // namespace mbgl diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 184bafc204f..c2806299e3f 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -39,12 +39,9 @@ bool TilePyramid::isLoaded() const { return true; } -void TilePyramid::startRender(const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState& transform) { +void TilePyramid::startRender(Painter& painter) { for (auto& pair : renderTiles) { - auto& tile = pair.second; - tile.calculateMatrices(projMatrix, clipMatrix, transform); + pair.second.startRender(painter); } } diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp index d989306414b..5846560808c 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -42,9 +42,7 @@ class TilePyramid { Range zoomRange, std::function (const OverscaledTileID&)> createTile); - void startRender(const mat4& projMatrix, - const mat4& clipMatrix, - const TransformState&); + void startRender(Painter&); void finishRender(Painter&); std::map& getRenderTiles(); diff --git a/src/mbgl/sprite/sprite_atlas.cpp b/src/mbgl/sprite/sprite_atlas.cpp deleted file mode 100644 index 69f2b0ce71d..00000000000 --- a/src/mbgl/sprite/sprite_atlas.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace mbgl { - -// When copied into the atlas texture, image data is padded by one pixel on each side. Icon -// images are padded with fully transparent pixels, while pattern images are padded with a -// copy of the image data wrapped from the opposite side. In both cases, this ensures the -// correct behavior of GL_LINEAR texture sampling mode. -static constexpr uint16_t padding = 1; - -SpriteAtlasElement::SpriteAtlasElement(const mapbox::Bin& bin, const style::Image::Impl& image) - : sdf(image.sdf), - pixelRatio(image.pixelRatio), - textureRect( - bin.x + padding, - bin.y + padding, - bin.w - padding * 2, - bin.h - padding * 2 - ) { -} - -static mapbox::ShelfPack::ShelfPackOptions shelfPackOptions() { - mapbox::ShelfPack::ShelfPackOptions options; - options.autoResize = true; - return options; -} - -SpriteAtlas::SpriteAtlas() - : shelfPack(64, 64, shelfPackOptions()) { -} - -SpriteAtlas::~SpriteAtlas() = default; - -void SpriteAtlas::onSpriteLoaded() { - loaded = true; - for (auto requestor : requestors) { - requestor->onIconsAvailable(buildIconMap()); - } - requestors.clear(); -} - -void SpriteAtlas::addImage(Immutable image_) { - assert(entries.find(image_->id) == entries.end()); - entries.emplace(image_->id, Entry { image_ }); - icons.clear(); -} - -void SpriteAtlas::updateImage(Immutable image_) { - assert(entries.find(image_->id) != entries.end()); - Entry& entry = entries.at(image_->id); - - // Style::addImage should prevent changing size. - assert(entry.image->image.size == image_->image.size); - - entry.image = std::move(image_); - - if (entry.iconBin) { - copy(entry, &Entry::iconBin); - } - - if (entry.patternBin) { - copy(entry, &Entry::patternBin); - } - - icons.clear(); -} - -void SpriteAtlas::removeImage(const std::string& id) { - assert(entries.find(id) != entries.end()); - Entry& entry = entries.at(id); - - if (entry.iconBin) { - shelfPack.unref(*entry.iconBin); - } - - if (entry.patternBin) { - shelfPack.unref(*entry.patternBin); - } - - entries.erase(id); - icons.clear(); -} - -const style::Image::Impl* SpriteAtlas::getImage(const std::string& id) const { - const auto it = entries.find(id); - if (it != entries.end()) { - return it->second.image.get(); - } - if (!entries.empty()) { - Log::Info(Event::Sprite, "Can't find sprite named '%s'", id.c_str()); - } - return nullptr; -} - -void SpriteAtlas::getIcons(IconRequestor& requestor, IconDependencies dependencies) { - // If the sprite has been loaded, or if all the icon dependencies are already present - // (i.e. if they've been addeded via runtime styling), then notify the requestor immediately. - // Otherwise, delay notification until the sprite is loaded. At that point, if any of the - // dependencies are still unavailable, we'll just assume they are permanently missing. - bool hasAllDependencies = true; - if (!isLoaded()) { - for (const auto& dependency : dependencies) { - if (entries.find(dependency) == entries.end()) { - hasAllDependencies = false; - } - } - } - if (isLoaded() || hasAllDependencies) { - requestor.onIconsAvailable(buildIconMap()); - } else { - requestors.insert(&requestor); - } -} - -void SpriteAtlas::removeRequestor(IconRequestor& requestor) { - requestors.erase(&requestor); -} - -optional SpriteAtlas::getIcon(const std::string& id) { - return getImage(id, &Entry::iconBin); -} - -optional SpriteAtlas::getPattern(const std::string& id) { - return getImage(id, &Entry::patternBin); -} - -optional SpriteAtlas::getImage(const std::string& id, mapbox::Bin* Entry::*entryBin) { - auto it = entries.find(id); - if (it == entries.end()) { - if (!entries.empty()) { - Log::Info(Event::Sprite, "Can't find sprite named '%s'", id.c_str()); - } - return {}; - } - - Entry& entry = it->second; - - if (entry.*entryBin) { - assert(entry.image.get()); - return SpriteAtlasElement { - *(entry.*entryBin), - *entry.image - }; - } - - const uint16_t width = entry.image->image.size.width + padding * 2; - const uint16_t height = entry.image->image.size.height + padding * 2; - - mapbox::Bin* bin = shelfPack.packOne(-1, width, height); - if (!bin) { - if (debug::spriteWarnings) { - Log::Warning(Event::Sprite, "sprite atlas bitmap overflow"); - } - return {}; - } - - entry.*entryBin = bin; - copy(entry, entryBin); - - return SpriteAtlasElement { - *bin, - *entry.image - }; -} - -Size SpriteAtlas::getPixelSize() const { - return Size { - static_cast(shelfPack.width()), - static_cast(shelfPack.height()) - }; -} - -void SpriteAtlas::copy(const Entry& entry, mapbox::Bin* Entry::*entryBin) { - if (!image.valid()) { - image = PremultipliedImage(getPixelSize()); - image.fill(0); - } else if (image.size != getPixelSize()) { - PremultipliedImage newImage(getPixelSize()); - PremultipliedImage::copy(image, newImage, { 0, 0 }, { 0, 0 }, image.size); - image = std::move(newImage); - } - - const PremultipliedImage& src = entry.image->image; - const mapbox::Bin& bin = *(entry.*entryBin); - - const uint32_t x = bin.x + padding; - const uint32_t y = bin.y + padding; - const uint32_t w = src.size.width; - const uint32_t h = src.size.height; - - PremultipliedImage::copy(src, image, { 0, 0 }, { x, y }, { w, h }); - - if (entryBin == &Entry::patternBin) { - // Add 1 pixel wrapped padding on each side of the image. - PremultipliedImage::copy(src, image, { 0, h - 1 }, { x, y - 1 }, { w, 1 }); // T - PremultipliedImage::copy(src, image, { 0, 0 }, { x, y + h }, { w, 1 }); // B - PremultipliedImage::copy(src, image, { w - 1, 0 }, { x - 1, y }, { 1, h }); // L - PremultipliedImage::copy(src, image, { 0, 0 }, { x + w, y }, { 1, h }); // R - } - - dirty = true; -} - -IconMap SpriteAtlas::buildIconMap() { - if (icons.empty()) { - for (const auto& entry : entries) { - icons.emplace(std::piecewise_construct, - std::forward_as_tuple(entry.first), - std::forward_as_tuple(*getIcon(entry.first))); - - } - } - return icons; -} - -void SpriteAtlas::upload(gl::Context& context, gl::TextureUnit unit) { - if (!texture) { - texture = context.createTexture(image, unit); - } else if (dirty) { - context.updateTexture(*texture, image, unit); - } - -#if not MBGL_USE_GLES2 -// if (dirty) { -// platform::showColorDebugImage("Sprite Atlas", -// reinterpret_cast(image.data.get()), size.width, -// size.height, image.size.width, image.size.height); -// } -#endif // MBGL_USE_GLES2 - - dirty = false; -} - -void SpriteAtlas::bind(bool linear, gl::Context& context, gl::TextureUnit unit) { - upload(context, unit); - context.bindTexture(*texture, unit, - linear ? gl::TextureFilter::Linear : gl::TextureFilter::Nearest); -} - -void SpriteAtlas::dumpDebugLogs() const { - Log::Info(Event::General, "SpriteAtlas::loaded: %d", loaded); -} - -} // namespace mbgl diff --git a/src/mbgl/sprite/sprite_atlas.hpp b/src/mbgl/sprite/sprite_atlas.hpp deleted file mode 100644 index 1dbef86f7e3..00000000000 --- a/src/mbgl/sprite/sprite_atlas.hpp +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace mbgl { - -namespace gl { -class Context; -} // namespace gl - -class SpriteAtlasElement { -public: - SpriteAtlasElement(const mapbox::Bin&, const style::Image::Impl&); - - bool sdf; - float pixelRatio; - Rect textureRect; - - std::array tl() const { - return {{ - textureRect.x, - textureRect.y - }}; - } - - std::array br() const { - return {{ - static_cast(textureRect.x + textureRect.w), - static_cast(textureRect.y + textureRect.h) - }}; - } - - std::array displaySize() const { - return {{ - textureRect.w / pixelRatio, - textureRect.h / pixelRatio, - }}; - } -}; - -using IconMap = std::unordered_map; -using IconDependencies = std::set; - -class IconRequestor { -public: - virtual ~IconRequestor() = default; - virtual void onIconsAvailable(IconMap) = 0; -}; - -class SpriteAtlas : public util::noncopyable { -public: - SpriteAtlas(); - ~SpriteAtlas(); - - void onSpriteLoaded(); - - bool isLoaded() const { - return loaded; - } - - void dumpDebugLogs() const; - - const style::Image::Impl* getImage(const std::string&) const; - - void addImage(Immutable); - void updateImage(Immutable); - void removeImage(const std::string&); - - void getIcons(IconRequestor&, IconDependencies); - void removeRequestor(IconRequestor&); - - // Ensure that the atlas contains the named image suitable for rendering as an icon, and - // return its metrics. The image will be padded on each side with a one pixel wide transparent - // strip, but the returned metrics are exclusive of this padding. - optional getIcon(const std::string& name); - - // Ensure that the atlas contains the named image suitable for rendering as an pattern, and - // return its metrics. The image will be padded on each side with a one pixel wide copy of - // pixels from the opposite side, but the returned metrics are exclusive of this padding. - optional getPattern(const std::string& name); - - // Binds the atlas texture to the GPU, and uploads data if it is out of date. - void bind(bool linear, gl::Context&, gl::TextureUnit unit); - - // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; - // the texture is only bound when the data is out of date (=dirty). - void upload(gl::Context&, gl::TextureUnit unit); - - Size getPixelSize() const; - - // Only for use in tests. - const PremultipliedImage& getAtlasImage() const { - return image; - } - -private: - bool loaded = false; - - struct Entry { - Immutable image; - - // One sprite image might be used as both an icon image and a pattern image. If so, - // it must have two distinct entries in the texture. The one for the icon image has - // a single pixel transparent border, and the one for the pattern image has a single - // pixel border wrapped from the opposite side. - mapbox::Bin* iconBin = nullptr; - mapbox::Bin* patternBin = nullptr; - }; - - optional getImage(const std::string& name, mapbox::Bin* Entry::*bin); - void copy(const Entry&, mapbox::Bin* Entry::*bin); - - IconMap buildIconMap(); - - std::unordered_map entries; - mapbox::ShelfPack shelfPack; - PremultipliedImage image; - mbgl::optional texture; - bool dirty = true; - - std::set requestors; - IconMap icons; -}; - -} // namespace mbgl diff --git a/src/mbgl/sprite/sprite_loader.cpp b/src/mbgl/sprite/sprite_loader.cpp index 7c5fe40e058..60ece5ed734 100644 --- a/src/mbgl/sprite/sprite_loader.cpp +++ b/src/mbgl/sprite/sprite_loader.cpp @@ -20,9 +20,9 @@ namespace mbgl { static SpriteLoaderObserver nullObserver; struct SpriteLoader::Loader { - Loader(Scheduler& scheduler, SpriteLoader& spriteAtlas) + Loader(Scheduler& scheduler, SpriteLoader& imageManager) : mailbox(std::make_shared(*util::RunLoop::Get())), - worker(scheduler, ActorRef(spriteAtlas, mailbox)) { + worker(scheduler, ActorRef(imageManager, mailbox)) { } std::shared_ptr image; diff --git a/src/mbgl/style/image_impl.hpp b/src/mbgl/style/image_impl.hpp index aa2cad8278d..75dc83206c0 100644 --- a/src/mbgl/style/image_impl.hpp +++ b/src/mbgl/style/image_impl.hpp @@ -2,6 +2,10 @@ #include +#include +#include +#include + namespace mbgl { namespace style { @@ -21,4 +25,8 @@ class Image::Impl { }; } // namespace style + +using ImageMap = std::unordered_map>; +using ImageDependencies = std::set; + } // namespace mbgl diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 69a6d401ed8..27ca58e85da 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -232,14 +232,7 @@ bool Style::isLoaded() const { } void Style::addImage(std::unique_ptr image) { - if (style::Image* existing = images.get(image->getID())) { - if (existing->getImage().size != image->getImage().size) { - Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", image->getID().c_str()); - return; - } - images.remove(image->getID()); - } - + images.remove(image->getID()); // We permit using addImage to update. images.add(std::move(image)); } diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index 7cfb9aa0d92..b9eaedd3028 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -35,13 +37,23 @@ inline bool operator==(const GlyphMetrics& lhs, const GlyphMetrics& rhs) { lhs.advance == rhs.advance; } -struct Glyph { - Rect rect; +class Glyph { +public: + // We're using this value throughout the Mapbox GL ecosystem. If this is different, the glyphs + // also need to be reencoded. + static constexpr const uint8_t borderSize = 3; + + GlyphID id = 0; + + // A signed distance field of the glyph with a border (see above). + AlphaImage bitmap; + + // Glyph metrics GlyphMetrics metrics; }; -using GlyphPositions = std::map>; -using GlyphPositionMap = std::map; +using Glyphs = std::map>>; +using GlyphMap = std::map; class PositionedGlyph { public: diff --git a/src/mbgl/text/glyph_atlas.cpp b/src/mbgl/text/glyph_atlas.cpp index c6083fe0ccb..1b98ea36bf4 100644 --- a/src/mbgl/text/glyph_atlas.cpp +++ b/src/mbgl/text/glyph_atlas.cpp @@ -1,256 +1,65 @@ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include namespace mbgl { -static GlyphAtlasObserver nullObserver; +static constexpr uint32_t padding = 1; -GlyphAtlas::GlyphAtlas(const Size size, FileSource& fileSource_) - : fileSource(fileSource_), - observer(&nullObserver), - bin(size.width, size.height), - image(size), - dirty(true) { -} - -GlyphAtlas::~GlyphAtlas() = default; - -void GlyphAtlas::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphDependencies) { - auto dependencies = std::make_shared(std::move(glyphDependencies)); - - // Figure out which glyph ranges need to be fetched. For each range that does need to - // be fetched, record an entry mapping the requestor to a shared pointer containing the - // dependencies. When the shared pointer becomes unique, we know that all the dependencies - // for that requestor have been fetched, and can notify it of completion. - for (const auto& dependency : *dependencies) { - const FontStack& fontStack = dependency.first; - Entry& entry = entries[fontStack]; - - const GlyphIDs& glyphIDs = dependency.second; - GlyphRangeSet ranges; - for (const auto& glyphID : glyphIDs) { - ranges.insert(getGlyphRange(glyphID)); - } - - for (const auto& range : ranges) { - auto it = entry.ranges.find(range); - if (it == entry.ranges.end() || !it->second.parsed) { - GlyphRequest& request = requestRange(entry, fontStack, range); - request.requestors[&requestor] = dependencies; - } - } - } - - // If the shared dependencies pointer is already unique, then all dependent glyph ranges - // have already been loaded. Send a notification immediately. - if (dependencies.unique()) { - addGlyphs(requestor, *dependencies); - } -} - -GlyphAtlas::GlyphRequest& GlyphAtlas::requestRange(Entry& entry, const FontStack& fontStack, const GlyphRange& range) { - GlyphRequest& request = entry.ranges[range]; - - if (request.req) { - return request; - } - - request.req = fileSource.request(Resource::glyphs(glyphURL, fontStack, range), [this, fontStack, range](Response res) { - processResponse(res, fontStack, range); - }); +GlyphAtlas makeGlyphAtlas(const GlyphMap& glyphs) { + GlyphAtlas result; - return request; -} + mapbox::ShelfPack::ShelfPackOptions options; + options.autoResize = true; + mapbox::ShelfPack pack(0, 0, options); -void GlyphAtlas::processResponse(const Response& res, const FontStack& fontStack, const GlyphRange& range) { - if (res.error) { - observer->onGlyphsError(fontStack, range, std::make_exception_ptr(std::runtime_error(res.error->message))); - return; - } + for (const auto& glyphMapEntry : glyphs) { + const FontStack& fontStack = glyphMapEntry.first; + GlyphPositionMap& positions = result.positions[fontStack]; - if (res.notModified) { - return; - } + for (const auto& entry : glyphMapEntry.second) { + if (entry.second && (*entry.second)->bitmap.valid()) { + const Glyph& glyph = **entry.second; - Entry& entry = entries[fontStack]; - GlyphRequest& request = entry.ranges[range]; + const mapbox::Bin& bin = *pack.packOne(-1, + glyph.bitmap.size.width + 2 * padding, + glyph.bitmap.size.height + 2 * padding); - if (!res.noContent) { - std::vector glyphs; - - try { - glyphs = parseGlyphPBF(range, *res.data); - } catch (...) { - observer->onGlyphsError(fontStack, range, std::current_exception()); - return; - } - - for (auto& glyph : glyphs) { - auto it = entry.glyphs.find(glyph.id); - if (it == entry.glyphs.end()) { - // Glyph doesn't exist yet. - entry.glyphs.emplace(glyph.id, GlyphValue { - std::move(glyph.bitmap), - std::move(glyph.metrics), - {}, {} + result.image.resize({ + static_cast(pack.width()), + static_cast(pack.height()) }); - } else if (it->second.metrics == glyph.metrics) { - if (it->second.bitmap != glyph.bitmap) { - // The actual bitmap was updated; this is unsupported. - Log::Warning(Event::Glyph, "Modified glyph changed bitmap represenation"); - } - // At least try to update it in case it's currently unused. - // If it is already used, we won't attempt to update the glyph atlas texture. - it->second.bitmap = std::move(glyph.bitmap); - } else { - // The metrics were updated; this is unsupported. - Log::Warning(Event::Glyph, "Modified glyph has different metrics"); - return; - } - } - } - - request.parsed = true; - - for (auto& pair : request.requestors) { - GlyphRequestor& requestor = *pair.first; - const std::shared_ptr& dependencies = pair.second; - if (dependencies.unique()) { - addGlyphs(requestor, *dependencies); - } - } - - request.requestors.clear(); - - observer->onGlyphsLoaded(fontStack, range); -} - -void GlyphAtlas::setObserver(GlyphAtlasObserver* observer_) { - observer = observer_ ? observer_ : &nullObserver; -} -void GlyphAtlas::addGlyphs(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies) { - GlyphPositionMap glyphPositions; - - for (const auto& dependency : glyphDependencies) { - const FontStack& fontStack = dependency.first; - const GlyphIDs& glyphIDs = dependency.second; - - GlyphPositions& positions = glyphPositions[fontStack]; - Entry& entry = entries[fontStack]; - - for (const auto& glyphID : glyphIDs) { - // Make a glyph position entry even if we didn't get an SDF for the glyph. During layout, - // an empty optional is treated as "loaded but nothing to show", wheras no entry in the - // positions map means "not loaded yet". - optional& glyph = positions[glyphID]; - - auto it = entry.glyphs.find(glyphID); - if (it == entry.glyphs.end()) - continue; - - it->second.ids.insert(&requestor); - - glyph = Glyph { - addGlyph(it->second), - it->second.metrics - }; - } - } - - requestor.onGlyphsAvailable(glyphPositions); -} - -Rect GlyphAtlas::addGlyph(GlyphValue& value) { - // The glyph is already in this texture. - if (value.rect) { - return *value.rect; - } - - // We don't need to add glyphs without a bitmap (e.g. whitespace). - if (!value.bitmap.valid()) { - return {}; - } - - // Add a 1px border around every image. - const uint32_t padding = 1; - const uint16_t width = value.bitmap.size.width + 2 * padding; - const uint16_t height = value.bitmap.size.height + 2 * padding; - - Rect rect = bin.allocate(width, height); - if (rect.w == 0) { - Log::Error(Event::OpenGL, "glyph bitmap overflow"); - return {}; - } - - AlphaImage::copy(value.bitmap, image, { 0, 0 }, { rect.x + padding, rect.y + padding }, value.bitmap.size); - value.rect = rect; - dirty = true; - - return rect; -} - -void GlyphAtlas::removeGlyphValues(GlyphRequestor& requestor, std::map& values) { - for (auto& it : values) { - GlyphValue& value = it.second; - if (value.ids.erase(&requestor) && value.ids.empty() && value.rect) { - const Rect& rect = *value.rect; - - // Clear out the bitmap. - uint8_t *target = image.data.get(); - for (uint32_t y = 0; y < rect.h; y++) { - uint32_t y1 = image.size.width * (rect.y + y) + rect.x; - for (uint32_t x = 0; x < rect.w; x++) { - target[y1 + x] = 0; - } + AlphaImage::copy(glyph.bitmap, + result.image, + { 0, 0 }, + { + bin.x + padding, + bin.y + padding + }, + glyph.bitmap.size); + + positions.emplace(glyph.id, + GlyphPosition { + Rect { + static_cast(bin.x), + static_cast(bin.y), + static_cast(bin.w), + static_cast(bin.h) + }, + glyph.metrics + }); } - - bin.release(rect); - value.rect = {}; } } -} - -void GlyphAtlas::removePendingRanges(mbgl::GlyphRequestor& requestor, std::map& ranges) { - for (auto& range : ranges) { - range.second.requestors.erase(&requestor); - } -} -void GlyphAtlas::removeGlyphs(GlyphRequestor& requestor) { - for (auto& entry : entries) { - removeGlyphValues(requestor, entry.second.glyphs); - removePendingRanges(requestor, entry.second.ranges); - } -} - -Size GlyphAtlas::getSize() const { - return image.size; -} - -void GlyphAtlas::upload(gl::Context& context, gl::TextureUnit unit) { - if (!texture) { - texture = context.createTexture(image, unit); - } else if (dirty) { - context.updateTexture(*texture, image, unit); - } - - dirty = false; -} + pack.shrink(); + result.image.resize({ + static_cast(pack.width()), + static_cast(pack.height()) + }); -void GlyphAtlas::bind(gl::Context& context, gl::TextureUnit unit) { - upload(context, unit); - context.bindTexture(*texture, unit, gl::TextureFilter::Linear); + return result; } } // namespace mbgl diff --git a/src/mbgl/text/glyph_atlas.hpp b/src/mbgl/text/glyph_atlas.hpp index cd6f57d57ff..bb9115e4b40 100644 --- a/src/mbgl/text/glyph_atlas.hpp +++ b/src/mbgl/text/glyph_atlas.hpp @@ -1,109 +1,25 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class GlyphAtlasTest; +#include namespace mbgl { -class FileSource; -class AsyncRequest; -class Response; - -namespace gl { -class Context; -} // namespace gl - -class GlyphRequestor { -public: - virtual ~GlyphRequestor() = default; - virtual void onGlyphsAvailable(GlyphPositionMap) = 0; +struct GlyphPosition { + Rect rect; + GlyphMetrics metrics; }; - -class GlyphAtlas : public util::noncopyable { -public: - GlyphAtlas(Size, FileSource&); - ~GlyphAtlas(); - - // Workers send a `getGlyphs` message to the main thread once they have determined - // which glyphs they will need. Invoking this method will increment reference - // counts for all the glyphs in `GlyphDependencies`. If all glyphs are already - // locally available, the observer will be notified that the glyphs are available - // immediately. Otherwise, a request on the FileSource is made, and when all glyphs - // are parsed and added to the atlas, the observer will be notified. - // Workers are given a copied 'GlyphPositions' map to use for placing their glyphs. - // The positions specified in this object are guaranteed to be - // valid for the lifetime of the tile. - void getGlyphs(GlyphRequestor&, GlyphDependencies); - void removeGlyphs(GlyphRequestor&); - - void setURL(const std::string& url) { - glyphURL = url; - } - - void setObserver(GlyphAtlasObserver*); - - // Binds the atlas texture to the GPU, and uploads data if it is out of date. - void bind(gl::Context&, gl::TextureUnit unit); - - // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; - // the texture is only bound when the data is out of date (=dirty). - void upload(gl::Context&, gl::TextureUnit unit); - - Size getSize() const; -private: - FileSource& fileSource; - std::string glyphURL; +using GlyphPositionMap = std::map; +using GlyphPositions = std::map; - struct GlyphValue { - AlphaImage bitmap; - GlyphMetrics metrics; - optional> rect; - std::unordered_set ids; - }; - - struct GlyphRequest { - bool parsed = false; - std::unique_ptr req; - std::unordered_map> requestors; - }; - - struct Entry { - std::map ranges; - std::map glyphs; - }; - - std::unordered_map entries; - - GlyphRequest& requestRange(Entry&, const FontStack&, const GlyphRange&); - void processResponse(const Response&, const FontStack&, const GlyphRange&); - - void addGlyphs(GlyphRequestor&, const GlyphDependencies&); - Rect addGlyph(GlyphValue&); - - void removeGlyphValues(GlyphRequestor&, std::map&); - void removePendingRanges(GlyphRequestor&, std::map&); - - GlyphAtlasObserver* observer = nullptr; - - BinPack bin; +class GlyphAtlas { +public: AlphaImage image; - bool dirty; - mbgl::optional texture; + GlyphPositions positions; }; +GlyphAtlas makeGlyphAtlas(const GlyphMap&); + } // namespace mbgl diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp new file mode 100644 index 00000000000..916d39ae620 --- /dev/null +++ b/src/mbgl/text/glyph_manager.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +static GlyphManagerObserver nullObserver; + +GlyphManager::GlyphManager(FileSource& fileSource_) + : fileSource(fileSource_), + observer(&nullObserver) { +} + +GlyphManager::~GlyphManager() = default; + +void GlyphManager::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphDependencies) { + auto dependencies = std::make_shared(std::move(glyphDependencies)); + + // Figure out which glyph ranges need to be fetched. For each range that does need to + // be fetched, record an entry mapping the requestor to a shared pointer containing the + // dependencies. When the shared pointer becomes unique, we know that all the dependencies + // for that requestor have been fetched, and can notify it of completion. + for (const auto& dependency : *dependencies) { + const FontStack& fontStack = dependency.first; + Entry& entry = entries[fontStack]; + + const GlyphIDs& glyphIDs = dependency.second; + GlyphRangeSet ranges; + for (const auto& glyphID : glyphIDs) { + ranges.insert(getGlyphRange(glyphID)); + } + + for (const auto& range : ranges) { + auto it = entry.ranges.find(range); + if (it == entry.ranges.end() || !it->second.parsed) { + GlyphRequest& request = requestRange(entry, fontStack, range); + request.requestors[&requestor] = dependencies; + } + } + } + + // If the shared dependencies pointer is already unique, then all dependent glyph ranges + // have already been loaded. Send a notification immediately. + if (dependencies.unique()) { + notify(requestor, *dependencies); + } +} + +GlyphManager::GlyphRequest& GlyphManager::requestRange(Entry& entry, const FontStack& fontStack, const GlyphRange& range) { + GlyphRequest& request = entry.ranges[range]; + + if (request.req) { + return request; + } + + request.req = fileSource.request(Resource::glyphs(glyphURL, fontStack, range), [this, fontStack, range](Response res) { + processResponse(res, fontStack, range); + }); + + return request; +} + +void GlyphManager::processResponse(const Response& res, const FontStack& fontStack, const GlyphRange& range) { + if (res.error) { + observer->onGlyphsError(fontStack, range, std::make_exception_ptr(std::runtime_error(res.error->message))); + return; + } + + if (res.notModified) { + return; + } + + Entry& entry = entries[fontStack]; + GlyphRequest& request = entry.ranges[range]; + + if (!res.noContent) { + std::vector glyphs; + + try { + glyphs = parseGlyphPBF(range, *res.data); + } catch (...) { + observer->onGlyphsError(fontStack, range, std::current_exception()); + return; + } + + for (auto& glyph : glyphs) { + entry.glyphs.erase(glyph.id); + entry.glyphs.emplace(glyph.id, makeMutable(std::move(glyph))); + } + } + + request.parsed = true; + + for (auto& pair : request.requestors) { + GlyphRequestor& requestor = *pair.first; + const std::shared_ptr& dependencies = pair.second; + if (dependencies.unique()) { + notify(requestor, *dependencies); + } + } + + request.requestors.clear(); + + observer->onGlyphsLoaded(fontStack, range); +} + +void GlyphManager::setObserver(GlyphManagerObserver* observer_) { + observer = observer_ ? observer_ : &nullObserver; +} + +void GlyphManager::notify(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies) { + GlyphMap response; + + for (const auto& dependency : glyphDependencies) { + const FontStack& fontStack = dependency.first; + const GlyphIDs& glyphIDs = dependency.second; + + Glyphs& glyphs = response[fontStack]; + Entry& entry = entries[fontStack]; + + for (const auto& glyphID : glyphIDs) { + auto it = entry.glyphs.find(glyphID); + if (it != entry.glyphs.end()) { + glyphs.emplace(*it); + } else { + glyphs.emplace(glyphID, std::experimental::nullopt); + } + } + } + + requestor.onGlyphsAvailable(response); +} + +void GlyphManager::removeRequestor(GlyphRequestor& requestor) { + for (auto& entry : entries) { + for (auto& range : entry.second.ranges) { + range.second.requestors.erase(&requestor); + } + } +} + +} // namespace mbgl diff --git a/src/mbgl/text/glyph_manager.hpp b/src/mbgl/text/glyph_manager.hpp new file mode 100644 index 00000000000..00df079462a --- /dev/null +++ b/src/mbgl/text/glyph_manager.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace mbgl { + +class FileSource; +class AsyncRequest; +class Response; + +class GlyphRequestor { +public: + virtual ~GlyphRequestor() = default; + virtual void onGlyphsAvailable(GlyphMap) = 0; +}; + +class GlyphManager : public util::noncopyable { +public: + GlyphManager(FileSource&); + ~GlyphManager(); + + // Workers send a `getGlyphs` message to the main thread once they have determined + // their `GlyphDependencies`. If all glyphs are already locally available, GlyphManager + // will provide them to the requestor immediately. Otherwise, it makes a request on the + // FileSource is made for each range neeed, and notifies the observer when all are + // complete. + void getGlyphs(GlyphRequestor&, GlyphDependencies); + void removeRequestor(GlyphRequestor&); + + void setURL(const std::string& url) { + glyphURL = url; + } + + void setObserver(GlyphManagerObserver*); + +private: + FileSource& fileSource; + std::string glyphURL; + + struct GlyphRequest { + bool parsed = false; + std::unique_ptr req; + std::unordered_map> requestors; + }; + + struct Entry { + std::map ranges; + std::map> glyphs; + }; + + std::unordered_map entries; + + GlyphRequest& requestRange(Entry&, const FontStack&, const GlyphRange&); + void processResponse(const Response&, const FontStack&, const GlyphRange&); + void notify(GlyphRequestor&, const GlyphDependencies&); + + GlyphManagerObserver* observer = nullptr; +}; + +} // namespace mbgl diff --git a/src/mbgl/text/glyph_atlas_observer.hpp b/src/mbgl/text/glyph_manager_observer.hpp similarity index 82% rename from src/mbgl/text/glyph_atlas_observer.hpp rename to src/mbgl/text/glyph_manager_observer.hpp index 9841017117a..b8678e060ae 100644 --- a/src/mbgl/text/glyph_atlas_observer.hpp +++ b/src/mbgl/text/glyph_manager_observer.hpp @@ -8,9 +8,9 @@ namespace mbgl { -class GlyphAtlasObserver { +class GlyphManagerObserver { public: - virtual ~GlyphAtlasObserver() = default; + virtual ~GlyphManagerObserver() = default; virtual void onGlyphsLoaded(const FontStack&, const GlyphRange&) {} virtual void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr) {} diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp index 033f50fe9c7..cfaf803f75e 100644 --- a/src/mbgl/text/glyph_pbf.cpp +++ b/src/mbgl/text/glyph_pbf.cpp @@ -4,8 +4,8 @@ namespace mbgl { -std::vector parseGlyphPBF(const GlyphRange& glyphRange, const std::string& data) { - std::vector result; +std::vector parseGlyphPBF(const GlyphRange& glyphRange, const std::string& data) { + std::vector result; result.reserve(256); protozero::pbf_reader glyphs_pbf(data); @@ -15,7 +15,7 @@ std::vector parseGlyphPBF(const GlyphRange& glyphRange, const std::str while (fontstack_pbf.next(3)) { auto glyph_pbf = fontstack_pbf.get_message(); - SDFGlyph glyph; + Glyph glyph; protozero::data_view glyphData; bool hasID = false, hasWidth = false, hasHeight = false, hasLeft = false, @@ -73,8 +73,8 @@ std::vector parseGlyphPBF(const GlyphRange& glyphRange, const std::str // with the implicit border size, otherwise we expect there to be no bitmap at all. if (glyph.metrics.width && glyph.metrics.height) { const Size size { - glyph.metrics.width + 2 * SDFGlyph::borderSize, - glyph.metrics.height + 2 * SDFGlyph::borderSize + glyph.metrics.width + 2 * Glyph::borderSize, + glyph.metrics.height + 2 * Glyph::borderSize }; if (size.area() != glyphData.size()) { diff --git a/src/mbgl/text/glyph_pbf.hpp b/src/mbgl/text/glyph_pbf.hpp index 162aeed93a1..28a28b41143 100644 --- a/src/mbgl/text/glyph_pbf.hpp +++ b/src/mbgl/text/glyph_pbf.hpp @@ -2,28 +2,12 @@ #include #include -#include #include #include namespace mbgl { -class SDFGlyph { -public: - // We're using this value throughout the Mapbox GL ecosystem. If this is different, the glyphs - // also need to be reencoded. - static constexpr const uint8_t borderSize = 3; - - GlyphID id = 0; - - // A signed distance field of the glyph with a border (see above). - AlphaImage bitmap; - - // Glyph metrics - GlyphMetrics metrics; -}; - -std::vector parseGlyphPBF(const GlyphRange&, const std::string& data); +std::vector parseGlyphPBF(const GlyphRange&, const std::string& data); } // namespace mbgl diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp index 33b94d71145..ab10c5a6b75 100644 --- a/src/mbgl/text/quads.cpp +++ b/src/mbgl/text/quads.cpp @@ -22,7 +22,7 @@ SymbolQuad getIconQuad(const Anchor& anchor, const float layoutTextSize, const style::SymbolPlacementType placement, const Shaping& shapedText) { - const SpriteAtlasElement& image = shapedIcon.image(); + const ImagePosition& image = shapedIcon.image(); // If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual // pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped @@ -307,18 +307,18 @@ SymbolQuads getGlyphQuads(Anchor& anchor, const GeometryCoordinates& line, const SymbolLayoutProperties::Evaluated& layout, const style::SymbolPlacementType placement, - const GlyphPositions& face) { + const GlyphPositionMap& positions) { const float textRotate = layout.get() * util::DEG2RAD; const bool keepUpright = layout.get(); SymbolQuads quads; for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) { - auto face_it = face.find(positionedGlyph.glyph); - if (face_it == face.end() || !face_it->second || !(*face_it->second).rect.hasArea()) + auto positionsIt = positions.find(positionedGlyph.glyph); + if (positionsIt == positions.end()) continue; - const Glyph& glyph = *face_it->second; + const GlyphPosition& glyph = positionsIt->second; const Rect& rect = glyph.rect; const float centerX = (positionedGlyph.x + glyph.metrics.advance / 2.0f) * boxScale; diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp index 38dad95243c..b29f6b0ad3b 100644 --- a/src/mbgl/text/quads.hpp +++ b/src/mbgl/text/quads.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -65,6 +65,6 @@ SymbolQuads getGlyphQuads(Anchor& anchor, const GeometryCoordinates& line, const style::SymbolLayoutProperties::Evaluated&, style::SymbolPlacementType placement, - const GlyphPositions& face); + const GlyphPositionMap& positions); } // namespace mbgl diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index f9f90627d41..338abe2e431 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -10,7 +10,7 @@ namespace mbgl { -PositionedIcon PositionedIcon::shapeIcon(const SpriteAtlasElement& image, const std::array& iconOffset, const float iconRotation) { +PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array& iconOffset, const float iconRotation) { float dx = iconOffset[0]; float dy = iconOffset[1]; float x1 = dx - image.displaySize()[0] / 2.0f; @@ -42,7 +42,7 @@ void align(Shaping& shaping, // justify left = 0, right = 1, center = .5 void justifyLine(std::vector& positionedGlyphs, - const GlyphPositions& glyphs, + const Glyphs& glyphs, std::size_t start, std::size_t end, float justify) { @@ -53,7 +53,7 @@ void justifyLine(std::vector& positionedGlyphs, PositionedGlyph& glyph = positionedGlyphs[end]; auto it = glyphs.find(glyph.glyph); if (it != glyphs.end() && it->second) { - const uint32_t lastAdvance = it->second->metrics.advance; + const uint32_t lastAdvance = (*it->second)->metrics.advance; const float lineIndent = float(glyph.x + lastAdvance) * justify; for (std::size_t j = start; j <= end; j++) { @@ -65,13 +65,13 @@ void justifyLine(std::vector& positionedGlyphs, float determineAverageLineWidth(const std::u16string& logicalInput, const float spacing, float maxWidth, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { float totalWidth = 0; for (char16_t chr : logicalInput) { auto it = glyphs.find(chr); if (it != glyphs.end() && it->second) { - totalWidth += it->second->metrics.advance + spacing; + totalWidth += (*it->second)->metrics.advance + spacing; } } @@ -164,7 +164,7 @@ std::set determineLineBreaks(const std::u16string& logicalInput, const float spacing, float maxWidth, const WritingModeType writingMode, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { if (!maxWidth || writingMode != WritingModeType::Horizontal) { return {}; } @@ -182,7 +182,7 @@ std::set determineLineBreaks(const std::u16string& logicalInput, const char16_t codePoint = logicalInput[i]; auto it = glyphs.find(codePoint); if (it != glyphs.end() && it->second && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) { - currentX += it->second->metrics.advance + spacing; + currentX += (*it->second)->metrics.advance + spacing; } // Ideographic characters, spaces, and word-breaking punctuation that often appear without @@ -208,7 +208,7 @@ void shapeLines(Shaping& shaping, const Point& translate, const float verticalHeight, const WritingModeType writingMode, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { // the y offset *should* be part of the font metadata const int32_t yOffset = -17; @@ -234,7 +234,7 @@ void shapeLines(Shaping& shaping, continue; } - const Glyph& glyph = *it->second; + const Glyph& glyph = **it->second; if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) { shaping.positionedGlyphs.emplace_back(chr, x, y, 0); @@ -280,7 +280,7 @@ const Shaping getShaping(const std::u16string& logicalInput, const float verticalHeight, const WritingModeType writingMode, BiDi& bidi, - const GlyphPositions& glyphs) { + const Glyphs& glyphs) { Shaping shaping(translate.x, translate.y, writingMode); std::vector reorderedLines = diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index bf81b9213c1..ca475e2a6c1 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -1,31 +1,29 @@ #pragma once #include -#include -#include +#include namespace mbgl { -class SpriteAtlasElement; class SymbolFeature; class BiDi; class PositionedIcon { private: - PositionedIcon(const SpriteAtlasElement& image_, + PositionedIcon(ImagePosition image_, float top_, float bottom_, float left_, float right_, float angle_) - : _image(image_), + : _image(std::move(image_)), _top(top_), _bottom(bottom_), _left(left_), _right(right_), _angle(angle_) {} - SpriteAtlasElement _image; + ImagePosition _image; float _top; float _bottom; float _left; @@ -33,9 +31,9 @@ class PositionedIcon { float _angle; public: - static PositionedIcon shapeIcon(const SpriteAtlasElement&, const std::array& iconOffset, const float iconRotation); + static PositionedIcon shapeIcon(const ImagePosition&, const std::array& iconOffset, const float iconRotation); - const SpriteAtlasElement& image() const { return _image; } + const ImagePosition& image() const { return _image; } float top() const { return _top; } float bottom() const { return _bottom; } float left() const { return _left; } @@ -54,6 +52,6 @@ const Shaping getShaping(const std::u16string& string, float verticalHeight, const WritingModeType, BiDi& bidi, - const GlyphPositions& glyphs); + const Glyphs& glyphs); } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 32fb6148c20..4ab11d79fed 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -38,14 +40,14 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, obsolete, parameters.mode, parameters.pixelRatio), - glyphAtlas(parameters.glyphAtlas), - spriteAtlas(parameters.spriteAtlas), + glyphManager(parameters.glyphManager), + imageManager(parameters.imageManager), placementThrottler(Milliseconds(300), [this] { invokePlacement(); }) { } GeometryTile::~GeometryTile() { - glyphAtlas.removeGlyphs(*this); - spriteAtlas.removeRequestor(*this); + glyphManager.removeRequestor(*this); + imageManager.removeRequestor(*this); markObsolete(); } @@ -135,6 +137,12 @@ void GeometryTile::onPlacement(PlacementResult result) { } symbolBuckets = std::move(result.symbolBuckets); collisionTile = std::move(result.collisionTile); + if (result.glyphAtlasImage) { + glyphAtlasImage = std::move(*result.glyphAtlasImage); + } + if (result.iconAtlasImage) { + iconAtlasImage = std::move(*result.iconAtlasImage); + } observer->onTileChanged(*this); } @@ -145,20 +153,46 @@ void GeometryTile::onError(std::exception_ptr err) { observer->onTileError(*this, err); } -void GeometryTile::onGlyphsAvailable(GlyphPositionMap glyphPositions) { - worker.invoke(&GeometryTileWorker::onGlyphsAvailable, std::move(glyphPositions)); +void GeometryTile::onGlyphsAvailable(GlyphMap glyphs) { + worker.invoke(&GeometryTileWorker::onGlyphsAvailable, std::move(glyphs)); } void GeometryTile::getGlyphs(GlyphDependencies glyphDependencies) { - glyphAtlas.getGlyphs(*this, std::move(glyphDependencies)); + glyphManager.getGlyphs(*this, std::move(glyphDependencies)); +} + +void GeometryTile::onImagesAvailable(ImageMap images) { + worker.invoke(&GeometryTileWorker::onImagesAvailable, std::move(images)); } -void GeometryTile::onIconsAvailable(IconMap icons) { - worker.invoke(&GeometryTileWorker::onIconsAvailable, std::move(icons)); +void GeometryTile::getImages(ImageDependencies imageDependencies) { + imageManager.getImages(*this, std::move(imageDependencies)); } -void GeometryTile::getIcons(IconDependencies iconDependencies) { - spriteAtlas.getIcons(*this, std::move(iconDependencies)); +void GeometryTile::upload(gl::Context& context) { + auto upload = [&] (Bucket& bucket) { + if (bucket.needsUpload()) { + bucket.upload(context); + } + }; + + for (auto& entry : nonSymbolBuckets) { + upload(*entry.second); + } + + for (auto& entry : symbolBuckets) { + upload(*entry.second); + } + + if (glyphAtlasImage) { + glyphAtlasTexture = context.createTexture(*glyphAtlasImage, 0); + glyphAtlasImage = {}; + } + + if (iconAtlasImage) { + iconAtlasTexture = context.createTexture(*iconAtlasImage, 0); + iconAtlasImage = {}; + } } Bucket* GeometryTile::getBucket(const Layer::Impl& layer) const { diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index c6d0bb35ec2..77202d20b6d 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -1,9 +1,9 @@ #pragma once -#include #include #include -#include +#include +#include #include #include #include @@ -23,8 +23,10 @@ class RenderStyle; class RenderLayer; class SourceQueryOptions; class TileParameters; +class GlyphAtlas; +class ImageAtlas; -class GeometryTile : public Tile, public GlyphRequestor, IconRequestor { +class GeometryTile : public Tile, public GlyphRequestor, ImageRequestor { public: GeometryTile(const OverscaledTileID&, std::string sourceID, @@ -38,14 +40,18 @@ class GeometryTile : public Tile, public GlyphRequestor, IconRequestor { void setPlacementConfig(const PlacementConfig&) override; void setLayers(const std::vector>&) override; - void onGlyphsAvailable(GlyphPositionMap) override; - void onIconsAvailable(IconMap) override; + void onGlyphsAvailable(GlyphMap) override; + void onImagesAvailable(ImageMap) override; void getGlyphs(GlyphDependencies); - void getIcons(IconDependencies); + void getImages(ImageDependencies); + void upload(gl::Context&) override; Bucket* getBucket(const style::Layer::Impl&) const override; + Size bindGlyphAtlas(gl::Context&); + Size bindIconAtlas(gl::Context&); + void queryRenderedFeatures( std::unordered_map>& result, const GeometryCoordinates& queryGeometry, @@ -72,6 +78,8 @@ class GeometryTile : public Tile, public GlyphRequestor, IconRequestor { public: std::unordered_map> symbolBuckets; std::unique_ptr collisionTile; + optional glyphAtlasImage; + optional iconAtlasImage; uint64_t correlationID; }; void onPlacement(PlacementResult); @@ -95,8 +103,8 @@ class GeometryTile : public Tile, public GlyphRequestor, IconRequestor { std::shared_ptr mailbox; Actor worker; - GlyphAtlas& glyphAtlas; - SpriteAtlas& spriteAtlas; + GlyphManager& glyphManager; + ImageManager& imageManager; uint64_t correlationID = 0; optional requestedConfig; @@ -105,10 +113,17 @@ class GeometryTile : public Tile, public GlyphRequestor, IconRequestor { std::unique_ptr featureIndex; std::unique_ptr data; + optional glyphAtlasImage; + optional iconAtlasImage; + std::unordered_map> symbolBuckets; std::unique_ptr collisionTile; util::Throttler placementThrottler; + +public: + optional glyphAtlasTexture; + optional iconAtlasTexture; }; } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 3fd6bb47f0f..a5a82a79204 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -197,37 +196,37 @@ void GeometryTileWorker::coalesce() { self.invoke(&GeometryTileWorker::coalesced); } -void GeometryTileWorker::onGlyphsAvailable(GlyphPositionMap newGlyphPositions) { - for (auto& newFontGlyphs : newGlyphPositions) { +void GeometryTileWorker::onGlyphsAvailable(GlyphMap newGlyphMap) { + for (auto& newFontGlyphs : newGlyphMap) { const FontStack& fontStack = newFontGlyphs.first; - GlyphPositions& newPositions = newFontGlyphs.second; + Glyphs& newGlyphs = newFontGlyphs.second; - GlyphPositions& positions = glyphPositions[fontStack]; + Glyphs& glyphs = glyphMap[fontStack]; GlyphIDs& pendingGlyphIDs = pendingGlyphDependencies[fontStack]; - for (auto& newPosition : newPositions) { - const GlyphID& glyphID = newPosition.first; - optional& glyph = newPosition.second; + for (auto& newGlyph : newGlyphs) { + const GlyphID& glyphID = newGlyph.first; + optional>& glyph = newGlyph.second; if (pendingGlyphIDs.erase(glyphID)) { - positions.emplace(glyphID, std::move(glyph)); + glyphs.emplace(glyphID, std::move(glyph)); } } } symbolDependenciesChanged(); } -void GeometryTileWorker::onIconsAvailable(IconMap newIcons) { - icons = std::move(newIcons); - pendingIconDependencies.clear(); +void GeometryTileWorker::onImagesAvailable(ImageMap newImageMap) { + imageMap = std::move(newImageMap); + pendingImageDependencies.clear(); symbolDependenciesChanged(); } void GeometryTileWorker::requestNewGlyphs(const GlyphDependencies& glyphDependencies) { for (auto& fontDependencies : glyphDependencies) { - auto fontGlyphs = glyphPositions.find(fontDependencies.first); + auto fontGlyphs = glyphMap.find(fontDependencies.first); for (auto glyphID : fontDependencies.second) { - if (fontGlyphs == glyphPositions.end() || fontGlyphs->second.find(glyphID) == fontGlyphs->second.end()) { + if (fontGlyphs == glyphMap.end() || fontGlyphs->second.find(glyphID) == fontGlyphs->second.end()) { pendingGlyphDependencies[fontDependencies.first].insert(glyphID); } } @@ -237,10 +236,10 @@ void GeometryTileWorker::requestNewGlyphs(const GlyphDependencies& glyphDependen } } -void GeometryTileWorker::requestNewIcons(const IconDependencies& iconDependencies) { - pendingIconDependencies = iconDependencies; - if (!pendingIconDependencies.empty()) { - parent.invoke(&GeometryTile::getIcons, pendingIconDependencies); +void GeometryTileWorker::requestNewImages(const ImageDependencies& imageDependencies) { + pendingImageDependencies = imageDependencies; + if (!pendingImageDependencies.empty()) { + parent.invoke(&GeometryTile::getImages, pendingImageDependencies); } } @@ -280,7 +279,7 @@ void GeometryTileWorker::redoLayout() { BucketParameters parameters { id, mode, pixelRatio }; GlyphDependencies glyphDependencies; - IconDependencies iconDependencies; + ImageDependencies imageDependencies; // Create render layers and group by layout std::vector> renderLayers = toRenderLayers(*layers, id.overscaledZ); @@ -311,7 +310,7 @@ void GeometryTileWorker::redoLayout() { if (leader.is()) { symbolLayoutMap.emplace(leader.getID(), - leader.as()->createLayout(parameters, group, *geometryLayer, glyphDependencies, iconDependencies)); + leader.as()->createLayout(parameters, group, *geometryLayer, glyphDependencies, imageDependencies)); } else { const Filter& filter = leader.baseImpl->filter; const std::string& sourceLayerID = leader.baseImpl->sourceLayer; @@ -347,7 +346,7 @@ void GeometryTileWorker::redoLayout() { } requestNewGlyphs(glyphDependencies); - requestNewIcons(iconDependencies); + requestNewImages(imageDependencies); parent.invoke(&GeometryTile::onLayout, GeometryTile::LayoutResult { std::move(buckets), @@ -375,7 +374,7 @@ bool GeometryTileWorker::hasPendingSymbolDependencies() const { return true; } } - return !pendingIconDependencies.empty(); + return !pendingImageDependencies.empty(); } @@ -387,14 +386,24 @@ void GeometryTileWorker::attemptPlacement() { auto collisionTile = std::make_unique(*placementConfig); std::unordered_map> buckets; + optional glyphAtlasImage; + optional iconAtlasImage; + for (auto& symbolLayout : symbolLayouts) { if (obsolete) { return; } if (symbolLayout->state == SymbolLayout::Pending) { - symbolLayout->prepare(glyphPositions, icons); + GlyphAtlas glyphAtlas = makeGlyphAtlas(glyphMap); + ImageAtlas imageAtlas = makeImageAtlas(imageMap); + + symbolLayout->prepare(glyphMap, glyphAtlas.positions, + imageMap, imageAtlas.positions); symbolLayout->state = SymbolLayout::Placed; + + glyphAtlasImage = std::move(glyphAtlas.image); + iconAtlasImage = std::move(imageAtlas.image); } if (!symbolLayout->hasSymbolInstances()) { @@ -410,6 +419,8 @@ void GeometryTileWorker::attemptPlacement() { parent.invoke(&GeometryTile::onPlacement, GeometryTile::PlacementResult { std::move(buckets), std::move(collisionTile), + std::move(glyphAtlasImage), + std::move(iconAtlasImage), correlationID }); } diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp index 3a15763a824..194477e7b8b 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include #include @@ -17,9 +17,7 @@ namespace mbgl { class GeometryTile; class GeometryTileData; -class GlyphAtlas; class SymbolLayout; -class RenderLayer; namespace style { class Layer; @@ -39,8 +37,8 @@ class GeometryTileWorker { void setData(std::unique_ptr, uint64_t correlationID); void setPlacementConfig(PlacementConfig, uint64_t correlationID); - void onGlyphsAvailable(GlyphPositionMap glyphs); - void onIconsAvailable(IconMap icons); + void onGlyphsAvailable(GlyphMap glyphs); + void onImagesAvailable(ImageMap images); private: void coalesced(); @@ -50,7 +48,7 @@ class GeometryTileWorker { void coalesce(); void requestNewGlyphs(const GlyphDependencies&); - void requestNewIcons(const IconDependencies&); + void requestNewImages(const ImageDependencies&); void symbolDependenciesChanged(); bool hasPendingSymbolDependencies() const; @@ -81,9 +79,9 @@ class GeometryTileWorker { std::vector> symbolLayouts; GlyphDependencies pendingGlyphDependencies; - IconDependencies pendingIconDependencies; - GlyphPositionMap glyphPositions; - IconMap icons; + ImageDependencies pendingImageDependencies; + GlyphMap glyphMap; + ImageMap imageMap; }; } // namespace mbgl diff --git a/src/mbgl/tile/raster_tile.cpp b/src/mbgl/tile/raster_tile.cpp index bd27ae63ca0..8a92c40e4a9 100644 --- a/src/mbgl/tile/raster_tile.cpp +++ b/src/mbgl/tile/raster_tile.cpp @@ -55,6 +55,12 @@ void RasterTile::onError(std::exception_ptr err) { observer->onTileError(*this, err); } +void RasterTile::upload(gl::Context& context) { + if (bucket) { + bucket->upload(context); + } +} + Bucket* RasterTile::getBucket(const style::Layer::Impl&) const { return bucket.get(); } diff --git a/src/mbgl/tile/raster_tile.hpp b/src/mbgl/tile/raster_tile.hpp index b4804bdb7da..51075a2dbcb 100644 --- a/src/mbgl/tile/raster_tile.hpp +++ b/src/mbgl/tile/raster_tile.hpp @@ -29,6 +29,8 @@ class RasterTile : public Tile { optional expires_); void cancel() override; + + void upload(gl::Context&) override; Bucket* getBucket(const style::Layer::Impl&) const override; void onParsed(std::unique_ptr result); diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index bddae5138b9..a925d88af30 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -26,6 +26,10 @@ class RenderStyle; class RenderedQueryOptions; class SourceQueryOptions; +namespace gl { +class Context; +} // namespace gl + class Tile : private util::noncopyable { public: Tile(OverscaledTileID); @@ -45,6 +49,7 @@ class Tile : private util::noncopyable { // Mark this tile as no longer needed and cancel any pending work. virtual void cancel() = 0; + virtual void upload(gl::Context&) = 0; virtual Bucket* getBucket(const style::Layer::Impl&) const = 0; virtual void setPlacementConfig(const PlacementConfig&) {} diff --git a/test/fixtures/image_manager/basic/expected.png b/test/fixtures/image_manager/basic/expected.png new file mode 100644 index 00000000000..8c615234dc5 Binary files /dev/null and b/test/fixtures/image_manager/basic/expected.png differ diff --git a/test/fixtures/image_manager/updates_after/expected.png b/test/fixtures/image_manager/updates_after/expected.png new file mode 100644 index 00000000000..db588c739ba Binary files /dev/null and b/test/fixtures/image_manager/updates_after/expected.png differ diff --git a/test/fixtures/image_manager/updates_before/expected.png b/test/fixtures/image_manager/updates_before/expected.png new file mode 100644 index 00000000000..1466a92fe78 Binary files /dev/null and b/test/fixtures/image_manager/updates_before/expected.png differ diff --git a/test/fixtures/sprite_atlas/basic/expected.png b/test/fixtures/sprite_atlas/basic/expected.png deleted file mode 100644 index 2960891c049..00000000000 Binary files a/test/fixtures/sprite_atlas/basic/expected.png and /dev/null differ diff --git a/test/fixtures/sprite_atlas/size/expected.png b/test/fixtures/sprite_atlas/size/expected.png deleted file mode 100644 index 5b08197a826..00000000000 Binary files a/test/fixtures/sprite_atlas/size/expected.png and /dev/null differ diff --git a/test/fixtures/sprite_atlas/updates_after/expected.png b/test/fixtures/sprite_atlas/updates_after/expected.png deleted file mode 100644 index 626ceab58b0..00000000000 Binary files a/test/fixtures/sprite_atlas/updates_after/expected.png and /dev/null differ diff --git a/test/fixtures/sprite_atlas/updates_before/expected.png b/test/fixtures/sprite_atlas/updates_before/expected.png deleted file mode 100644 index 0858c19f052..00000000000 Binary files a/test/fixtures/sprite_atlas/updates_before/expected.png and /dev/null differ diff --git a/test/geometry/binpack.test.cpp b/test/geometry/binpack.test.cpp deleted file mode 100644 index 0b74df7fa98..00000000000 --- a/test/geometry/binpack.test.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include - -#include -#include - -namespace mbgl { -template ::std::ostream& operator<<(::std::ostream& os, const Rect& t) { - return os << "Rect { " << t.x << ", " << t.y << ", " << t.w << ", " << t.h << " }"; -} -} // namespace mbgl - -TEST(BinPack, Allocating) { - mbgl::BinPack bin(128, 128); - std::array, 4> rects; - - rects[0] = bin.allocate(32, 48); - ASSERT_EQ(mbgl::Rect(0, 0, 32, 48), rects[0]); - rects[1] = bin.allocate(8, 17); - ASSERT_EQ(mbgl::Rect(32, 0, 8, 17), rects[1]); - rects[2] = bin.allocate(8, 17); - ASSERT_EQ(mbgl::Rect(0, 48, 8, 17), rects[2]); - - bin.release(rects[0]); - rects[0] = bin.allocate(32, 24); - ASSERT_EQ(mbgl::Rect(0, 0, 32, 24), rects[0]); - rects[3] = bin.allocate(32, 24); - ASSERT_EQ(mbgl::Rect(32, 17, 32, 24), rects[3]); -} - - -TEST(BinPack, Full) { - mbgl::BinPack bin(128, 128); - std::vector> rects; - - for (int j = 0; j < 3; j++) { - for (int i = 0; i < 256; i++) { - auto rect = bin.allocate(8, 8); - ASSERT_TRUE(rect.hasArea()); - rects.push_back(rect); - } - - ASSERT_FALSE(bin.allocate(8, 8).hasArea()); - - for (auto& rect: rects) { - bin.release(rect); - } - rects.clear(); - } -} diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp new file mode 100644 index 00000000000..203e05d4921 --- /dev/null +++ b/test/renderer/image_manager.test.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace mbgl; + +TEST(ImageManager, Missing) { + ImageManager imageManager; + EXPECT_FALSE(imageManager.getImage("doesnotexist")); +} + +TEST(ImageManager, Basic) { + FixtureLog log; + ImageManager imageManager; + + auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + util::read_file("test/fixtures/annotations/emerald.json")); + for (auto& image : images) { + imageManager.addImage(image->baseImpl); + } + + auto metro = *imageManager.getPattern("metro"); + EXPECT_EQ(1, metro.tl()[0]); + EXPECT_EQ(1, metro.tl()[1]); + EXPECT_EQ(19, metro.br()[0]); + EXPECT_EQ(19, metro.br()[1]); + EXPECT_EQ(18, metro.displaySize()[0]); + EXPECT_EQ(18, metro.displaySize()[1]); + EXPECT_EQ(1.0f, metro.pixelRatio); + EXPECT_EQ(imageManager.getPixelSize(), imageManager.getAtlasImage().size); + + test::checkImage("test/fixtures/image_manager/basic", imageManager.getAtlasImage()); +} + +TEST(ImageManager, Updates) { + ImageManager imageManager; + + PremultipliedImage imageA({ 16, 12 }); + imageA.fill(255); + imageManager.addImage(makeMutable("one", std::move(imageA), 1)); + + auto a = *imageManager.getPattern("one"); + EXPECT_EQ(1, a.tl()[0]); + EXPECT_EQ(1, a.tl()[1]); + EXPECT_EQ(17, a.br()[0]); + EXPECT_EQ(13, a.br()[1]); + EXPECT_EQ(16, a.displaySize()[0]); + EXPECT_EQ(12, a.displaySize()[1]); + EXPECT_EQ(1.0f, a.pixelRatio); + test::checkImage("test/fixtures/image_manager/updates_before", imageManager.getAtlasImage()); + + PremultipliedImage imageB({ 5, 5 }); + imageA.fill(200); + imageManager.updateImage(makeMutable("one", std::move(imageB), 1)); + + auto b = *imageManager.getPattern("one"); + EXPECT_EQ(1, b.tl()[0]); + EXPECT_EQ(1, b.tl()[1]); + EXPECT_EQ(6, b.br()[0]); + EXPECT_EQ(6, b.br()[1]); + EXPECT_EQ(5, b.displaySize()[0]); + EXPECT_EQ(5, b.displaySize()[1]); + EXPECT_EQ(1.0f, b.pixelRatio); + test::checkImage("test/fixtures/image_manager/updates_after", imageManager.getAtlasImage()); +} + +TEST(ImageManager, AddRemove) { + FixtureLog log; + ImageManager imageManager; + + imageManager.addImage(makeMutable("one", PremultipliedImage({ 16, 16 }), 2)); + imageManager.addImage(makeMutable("two", PremultipliedImage({ 16, 16 }), 2)); + imageManager.addImage(makeMutable("three", PremultipliedImage({ 16, 16 }), 2)); + + imageManager.removeImage("one"); + imageManager.removeImage("two"); + + EXPECT_NE(nullptr, imageManager.getImage("three")); + EXPECT_EQ(nullptr, imageManager.getImage("two")); + EXPECT_EQ(nullptr, imageManager.getImage("four")); +} + +TEST(ImageManager, RemoveReleasesBinPackRect) { + FixtureLog log; + ImageManager imageManager; + + imageManager.addImage(makeMutable("big", PremultipliedImage({ 32, 32 }), 1)); + EXPECT_TRUE(imageManager.getImage("big")); + + imageManager.removeImage("big"); + + imageManager.addImage(makeMutable("big", PremultipliedImage({ 32, 32 }), 1)); + EXPECT_TRUE(imageManager.getImage("big")); + EXPECT_TRUE(log.empty()); +} + +class StubImageRequestor : public ImageRequestor { +public: + void onImagesAvailable(ImageMap images) final { + if (imagesAvailable) imagesAvailable(images); + } + + std::function imagesAvailable; +}; + +TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) { + ImageManager imageManager; + StubImageRequestor requestor; + bool notified = false; + + requestor.imagesAvailable = [&] (ImageMap) { + notified = true; + }; + + imageManager.getImages(requestor, {"one"}); + ASSERT_FALSE(notified); + + imageManager.onSpriteLoaded(); + ASSERT_TRUE(notified); +} + +TEST(ImageManager, NotifiesRequestorImmediatelyIfDependenciesAreSatisfied) { + ImageManager imageManager; + StubImageRequestor requestor; + bool notified = false; + + requestor.imagesAvailable = [&] (ImageMap) { + notified = true; + }; + + imageManager.addImage(makeMutable("one", PremultipliedImage({ 16, 16 }), 2)); + imageManager.getImages(requestor, {"one"}); + + ASSERT_TRUE(notified); +} diff --git a/test/sprite/sprite_atlas.test.cpp b/test/sprite/sprite_atlas.test.cpp deleted file mode 100644 index 78a58624759..00000000000 --- a/test/sprite/sprite_atlas.test.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -using namespace mbgl; - -TEST(SpriteAtlas, Basic) { - FixtureLog log; - SpriteAtlas atlas; - - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), - util::read_file("test/fixtures/annotations/emerald.json")); - for (auto& image : images) { - atlas.addImage(image->baseImpl); - } - - auto metro = *atlas.getIcon("metro"); - EXPECT_EQ(1, metro.tl()[0]); - EXPECT_EQ(1, metro.tl()[1]); - EXPECT_EQ(19, metro.br()[0]); - EXPECT_EQ(19, metro.br()[1]); - EXPECT_EQ(18, metro.displaySize()[0]); - EXPECT_EQ(18, metro.displaySize()[1]); - EXPECT_EQ(1.0f, metro.pixelRatio); - - EXPECT_EQ(atlas.getPixelSize(), atlas.getAtlasImage().size); - - auto missing = atlas.getIcon("doesnotexist"); - EXPECT_FALSE(missing); - - EXPECT_EQ(1u, log.count({ - EventSeverity::Info, - Event::Sprite, - int64_t(-1), - "Can't find sprite named 'doesnotexist'", - })); - - // Different wrapping mode produces different image. - auto metro2 = *atlas.getPattern("metro"); - EXPECT_EQ(21, metro2.tl()[0]); - EXPECT_EQ(1, metro2.tl()[1]); - EXPECT_EQ(39, metro2.br()[0]); - EXPECT_EQ(19, metro2.br()[1]); - - test::checkImage("test/fixtures/sprite_atlas/basic", atlas.getAtlasImage()); -} - -TEST(SpriteAtlas, Size) { - SpriteAtlas atlas; - - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), - util::read_file("test/fixtures/annotations/emerald.json")); - for (auto& image : images) { - atlas.addImage(image->baseImpl); - } - - auto metro = *atlas.getIcon("metro"); - EXPECT_EQ(1, metro.tl()[0]); - EXPECT_EQ(1, metro.tl()[1]); - EXPECT_EQ(19, metro.br()[0]); - EXPECT_EQ(19, metro.br()[1]); - EXPECT_EQ(18, metro.displaySize()[0]); - EXPECT_EQ(18, metro.displaySize()[1]); - EXPECT_EQ(1.0f, metro.pixelRatio); - - test::checkImage("test/fixtures/sprite_atlas/size", atlas.getAtlasImage()); -} - -TEST(SpriteAtlas, Updates) { - SpriteAtlas atlas; - - atlas.addImage(makeMutable("one", PremultipliedImage({ 16, 12 }), 1)); - auto one = *atlas.getIcon("one"); - EXPECT_EQ(1, one.tl()[0]); - EXPECT_EQ(1, one.tl()[1]); - EXPECT_EQ(17, one.br()[0]); - EXPECT_EQ(13, one.br()[1]); - EXPECT_EQ(16, one.displaySize()[0]); - EXPECT_EQ(12, one.displaySize()[1]); - EXPECT_EQ(1.0f, one.pixelRatio); - - test::checkImage("test/fixtures/sprite_atlas/updates_before", atlas.getAtlasImage()); - - // Update image - PremultipliedImage image2({ 16, 12 }); - for (size_t i = 0; i < image2.bytes(); i++) { - image2.data.get()[i] = 255; - } - atlas.updateImage(makeMutable("one", std::move(image2), 1)); - - test::checkImage("test/fixtures/sprite_atlas/updates_after", atlas.getAtlasImage()); -} - -TEST(SpriteAtlas, AddRemove) { - FixtureLog log; - SpriteAtlas atlas; - - atlas.addImage(makeMutable("one", PremultipliedImage({ 16, 16 }), 2)); - atlas.addImage(makeMutable("two", PremultipliedImage({ 16, 16 }), 2)); - atlas.addImage(makeMutable("three", PremultipliedImage({ 16, 16 }), 2)); - - atlas.removeImage("one"); - atlas.removeImage("two"); - - EXPECT_NE(nullptr, atlas.getImage("three")); - EXPECT_EQ(nullptr, atlas.getImage("two")); - EXPECT_EQ(nullptr, atlas.getImage("four")); - - EXPECT_EQ(1u, log.count({ - EventSeverity::Info, - Event::Sprite, - int64_t(-1), - "Can't find sprite named 'two'", - })); - EXPECT_EQ(1u, log.count({ - EventSeverity::Info, - Event::Sprite, - int64_t(-1), - "Can't find sprite named 'four'", - })); -} - -TEST(SpriteAtlas, RemoveReleasesBinPackRect) { - FixtureLog log; - SpriteAtlas atlas; - - atlas.addImage(makeMutable("big", PremultipliedImage({ 32, 32 }), 1)); - EXPECT_TRUE(atlas.getIcon("big")); - - atlas.removeImage("big"); - - atlas.addImage(makeMutable("big", PremultipliedImage({ 32, 32 }), 1)); - EXPECT_TRUE(atlas.getIcon("big")); - EXPECT_TRUE(log.empty()); -} - -class StubIconRequestor : public IconRequestor { -public: - void onIconsAvailable(IconMap icons) final { - if (iconsAvailable) iconsAvailable(icons); - } - - std::function iconsAvailable; -}; - -TEST(SpriteAtlas, NotifiesRequestorWhenSpriteIsLoaded) { - SpriteAtlas atlas; - StubIconRequestor requestor; - bool notified = false; - - requestor.iconsAvailable = [&] (IconMap) { - notified = true; - }; - - atlas.getIcons(requestor, {"one"}); - ASSERT_FALSE(notified); - - atlas.onSpriteLoaded(); - ASSERT_TRUE(notified); -} - -TEST(SpriteAtlas, NotifiesRequestorImmediatelyIfDependenciesAreSatisfied) { - SpriteAtlas atlas; - StubIconRequestor requestor; - bool notified = false; - - requestor.iconsAvailable = [&] (IconMap) { - notified = true; - }; - - atlas.addImage(makeMutable("one", PremultipliedImage({ 16, 16 }), 2)); - atlas.getIcons(requestor, {"one"}); - - ASSERT_TRUE(notified); -} diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index 0ab98a63b1d..eaa3c728774 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -31,8 +31,8 @@ #include #include #include -#include -#include +#include +#include #include @@ -48,8 +48,8 @@ class SourceTest { TransformState transformState; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; - GlyphAtlas glyphAtlas { { 512, 512, }, fileSource }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; TileParameters tileParameters { 1.0, @@ -59,8 +59,8 @@ class SourceTest { fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, - glyphAtlas + imageManager, + glyphManager }; SourceTest() { diff --git a/test/text/glyph_atlas.test.cpp b/test/text/glyph_loader.test.cpp similarity index 80% rename from test/text/glyph_atlas.test.cpp rename to test/text/glyph_loader.test.cpp index 01e0f115c02..be197ebb462 100644 --- a/test/text/glyph_atlas.test.cpp +++ b/test/text/glyph_loader.test.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include #include @@ -9,7 +9,7 @@ using namespace mbgl; -class StubGlyphAtlasObserver : public GlyphAtlasObserver { +class StubGlyphManagerObserver : public GlyphManagerObserver { public: void onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& glyphRange) override { if (glyphsLoaded) glyphsLoaded(fontStack, glyphRange); @@ -25,28 +25,28 @@ class StubGlyphAtlasObserver : public GlyphAtlasObserver { class StubGlyphRequestor : public GlyphRequestor { public: - void onGlyphsAvailable(GlyphPositionMap positions) override { - if (glyphsAvailable) glyphsAvailable(std::move(positions)); + void onGlyphsAvailable(GlyphMap glyphs) override { + if (glyphsAvailable) glyphsAvailable(std::move(glyphs)); } - std::function glyphsAvailable; + std::function glyphsAvailable; }; -class GlyphAtlasTest { +class GlyphManagerTest { public: util::RunLoop loop; StubFileSource fileSource; - StubGlyphAtlasObserver observer; + StubGlyphManagerObserver observer; StubGlyphRequestor requestor; - GlyphAtlas glyphAtlas{ { 32, 32 }, fileSource }; + GlyphManager glyphManager { fileSource }; void run(const std::string& url, GlyphDependencies dependencies) { // Squelch logging. Log::setObserver(std::make_unique()); - glyphAtlas.setURL(url); - glyphAtlas.setObserver(&observer); - glyphAtlas.getGlyphs(requestor, std::move(dependencies)); + glyphManager.setURL(url); + glyphManager.setObserver(&observer); + glyphManager.getGlyphs(requestor, std::move(dependencies)); loop.run(); } @@ -56,8 +56,8 @@ class GlyphAtlasTest { } }; -TEST(GlyphAtlas, LoadingSuccess) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingSuccess) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource& resource) { EXPECT_EQ(Resource::Kind::Glyphs, resource.kind); @@ -76,8 +76,8 @@ TEST(GlyphAtlas, LoadingSuccess) { ASSERT_EQ(range, GlyphRange(0, 255)); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap positions) { - const auto& testPositions = positions.at({{"Test Stack"}}); + test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) { + const auto& testPositions = glyphs.at({{"Test Stack"}}); ASSERT_EQ(testPositions.size(), 3u); ASSERT_EQ(testPositions.count(u'a'), 1u); @@ -95,8 +95,8 @@ TEST(GlyphAtlas, LoadingSuccess) { }); } -TEST(GlyphAtlas, LoadingFail) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingFail) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource&) { Response response; @@ -116,7 +116,7 @@ TEST(GlyphAtlas, LoadingFail) { test.end(); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap) { + test.requestor.glyphsAvailable = [&] (GlyphMap) { FAIL(); test.end(); }; @@ -128,8 +128,8 @@ TEST(GlyphAtlas, LoadingFail) { }); } -TEST(GlyphAtlas, LoadingCorrupted) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingCorrupted) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource&) { Response response; @@ -147,7 +147,7 @@ TEST(GlyphAtlas, LoadingCorrupted) { test.end(); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap) { + test.requestor.glyphsAvailable = [&] (GlyphMap) { FAIL(); test.end(); }; @@ -159,8 +159,8 @@ TEST(GlyphAtlas, LoadingCorrupted) { }); } -TEST(GlyphAtlas, LoadingCancel) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingCancel) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource&) { test.end(); @@ -178,8 +178,8 @@ TEST(GlyphAtlas, LoadingCancel) { }); } -TEST(GlyphAtlas, LoadingInvalid) { - GlyphAtlasTest test; +TEST(GlyphManager, LoadingInvalid) { + GlyphManagerTest test; test.fileSource.glyphsResponse = [&] (const Resource& resource) { EXPECT_EQ(Resource::Kind::Glyphs, resource.kind); @@ -198,8 +198,8 @@ TEST(GlyphAtlas, LoadingInvalid) { ASSERT_EQ(range, GlyphRange(0, 255)); }; - test.requestor.glyphsAvailable = [&] (GlyphPositionMap positions) { - const auto& testPositions = positions.at({{"Test Stack"}}); + test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) { + const auto& testPositions = glyphs.at({{"Test Stack"}}); ASSERT_EQ(testPositions.size(), 2u); ASSERT_FALSE(bool(testPositions.at(u'A'))); diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp index 0a1dbbd1c95..efc3912aaac 100644 --- a/test/text/quads.test.cpp +++ b/test/text/quads.test.cpp @@ -12,7 +12,7 @@ using namespace mbgl::style; TEST(getIconQuads, normal) { SymbolLayoutProperties::Evaluated layout; Anchor anchor(2.0, 3.0, 0.0, 0.5f, 0); - SpriteAtlasElement image = { + ImagePosition image = { mapbox::Bin(-1, 15, 11, 0, 0), style::Image::Impl("test", PremultipliedImage({1,1}), 1.0) }; @@ -42,7 +42,7 @@ TEST(getIconQuads, normal) { TEST(getIconQuads, style) { Anchor anchor(0.0, 0.0, 0.0, 0.5f, 0); - SpriteAtlasElement image = { + ImagePosition image = { mapbox::Bin(-1, 20, 20, 0, 0), style::Image::Impl("test", PremultipliedImage({1,1}), 1.0) }; diff --git a/test/tile/annotation_tile.test.cpp b/test/tile/annotation_tile.test.cpp index 927799b26d1..707f118fd51 100644 --- a/test/tile/annotation_tile.test.cpp +++ b/test/tile/annotation_tile.test.cpp @@ -12,8 +12,8 @@ #include #include #include -#include -#include +#include +#include #include @@ -27,8 +27,8 @@ class AnnotationTileTest { ThreadPool threadPool { 1 }; AnnotationManager annotationManager; RenderStyle style { threadPool, fileSource }; - SpriteAtlas spriteAtlas; - GlyphAtlas glyphAtlas { { 512, 512, }, fileSource }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; TileParameters tileParameters { 1.0, @@ -38,8 +38,8 @@ class AnnotationTileTest { fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, - glyphAtlas + imageManager, + glyphManager }; }; @@ -55,9 +55,9 @@ TEST(AnnotationTile, Issue8289) { // Simulate layout and placement of a symbol layer. tile.onLayout(GeometryTile::LayoutResult { {}, - std::make_unique(), - std::move(data), - 0 + std::make_unique(), + std::move(data), + 0 }); auto collisionTile = std::make_unique(PlacementConfig()); @@ -69,16 +69,18 @@ TEST(AnnotationTile, Issue8289) { tile.onPlacement(GeometryTile::PlacementResult { {}, - std::move(collisionTile), - 0 + std::move(collisionTile), + {}, + {}, + 0 }); // Simulate a second layout with empty data. tile.onLayout(GeometryTile::LayoutResult { {}, - std::make_unique(), - std::make_unique(), - 0 + std::make_unique(), + std::make_unique(), + 0 }); std::unordered_map> result; diff --git a/test/tile/geojson_tile.test.cpp b/test/tile/geojson_tile.test.cpp index 92068d5e43b..2aa85c3860a 100644 --- a/test/tile/geojson_tile.test.cpp +++ b/test/tile/geojson_tile.test.cpp @@ -10,8 +10,8 @@ #include #include #include -#include -#include +#include +#include #include @@ -25,8 +25,8 @@ class GeoJSONTileTest { util::RunLoop loop; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; - GlyphAtlas glyphAtlas { { 512, 512, }, fileSource }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; TileParameters tileParameters { @@ -37,8 +37,8 @@ class GeoJSONTileTest { fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, - glyphAtlas + imageManager, + glyphManager }; }; diff --git a/test/tile/raster_tile.test.cpp b/test/tile/raster_tile.test.cpp index 1bd35700b3e..a0666c2146e 100644 --- a/test/tile/raster_tile.test.cpp +++ b/test/tile/raster_tile.test.cpp @@ -9,8 +9,8 @@ #include #include #include -#include -#include +#include +#include using namespace mbgl; @@ -21,8 +21,8 @@ class RasterTileTest { util::RunLoop loop; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; - GlyphAtlas glyphAtlas { { 512, 512, }, fileSource }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; TileParameters tileParameters { @@ -33,8 +33,8 @@ class RasterTileTest { fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, - glyphAtlas + imageManager, + glyphManager }; }; diff --git a/test/tile/vector_tile.test.cpp b/test/tile/vector_tile.test.cpp index a44eeb90018..f24733dc9bd 100644 --- a/test/tile/vector_tile.test.cpp +++ b/test/tile/vector_tile.test.cpp @@ -13,8 +13,8 @@ #include #include #include -#include -#include +#include +#include #include @@ -27,8 +27,8 @@ class VectorTileTest { util::RunLoop loop; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; - GlyphAtlas glyphAtlas { { 512, 512, }, fileSource }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; TileParameters tileParameters { @@ -39,8 +39,8 @@ class VectorTileTest { fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, - glyphAtlas + imageManager, + glyphManager }; }; @@ -81,6 +81,8 @@ TEST(VectorTile, Issue7615) { symbolBucket }}, nullptr, + {}, + {}, 0 }); diff --git a/test/util/image.test.cpp b/test/util/image.test.cpp index f4031f1bc19..f4a64730406 100644 --- a/test/util/image.test.cpp +++ b/test/util/image.test.cpp @@ -86,33 +86,49 @@ TEST(Image, WebPTile) { } #endif // !defined(__ANDROID__) && !defined(__APPLE__) && !defined(QT_IMAGE_DECODERS) +TEST(Image, Resize) { + AlphaImage image({0, 0}); + + image.resize({1, 1}); + EXPECT_EQ(image.size, Size({1, 1})); + + image.fill(100); + image.resize({2, 1}); + EXPECT_EQ(image.size, Size({2, 1})); + EXPECT_EQ(image.data[0], 100); + EXPECT_EQ(image.data[1], 0); + + image.resize({0, 0}); + EXPECT_EQ(image.size, Size({0, 0})); +} + TEST(Image, Copy) { PremultipliedImage src5({5, 5}); PremultipliedImage dst5({5, 5}); PremultipliedImage src10({10, 10}); PremultipliedImage dst10({10, 10}); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {6, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {0, 6}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {5, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {0, 5}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {6, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {1, 6}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {5, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {1, 5}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {6, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {0, 6}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {5, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {0, 5}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {6, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {1, 6}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {5, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {1, 5}), std::out_of_range); const uint32_t max = std::numeric_limits::max(); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {max, 0}, {0, 0}, {1, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, max}, {0, 0}, {0, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {max, 0}, {1, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, max}, {0, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {max, 0}, {0, 0}, {1, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, max}, {0, 0}, {1, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {max, 0}, {1, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, max}, {1, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {1, 0}, {0, 0}, {max, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 1}, {0, 0}, {0, max}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {1, 0}, {max, 0}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, 1}, {0, max}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {1, 0}, {0, 0}, {max, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 1}, {0, 0}, {1, max}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {1, 0}, {max, 1}), std::out_of_range); + EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, 1}, {1, max}), std::out_of_range); } TEST(Image, Move) {