diff options
author | John Firebaugh <john.firebaugh@gmail.com> | 2017-06-07 12:45:35 -0700 |
---|---|---|
committer | John Firebaugh <john.firebaugh@gmail.com> | 2017-06-13 10:18:43 -0700 |
commit | 0b687312071305c050d97e04fef1c80193f443c5 (patch) | |
tree | 64c20efaa17fefef9f902811a000fd6e425c849b | |
parent | 92252849c1a2ddf7887d1908841fa3c90dd59766 (diff) | |
download | qtlocation-mapboxgl-0b687312071305c050d97e04fef1c80193f443c5.tar.gz |
[core] Per-bucket icon atlases
55 files changed, 688 insertions, 728 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index d228f9b429..9c4c730765 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -167,6 +167,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 +295,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 diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 421c6045c8..ed84c679fb 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -44,9 +44,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 diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 6d769e43c4..b64fc4f66e 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -5,8 +5,8 @@ #include <mbgl/style/filter_evaluator.hpp> #include <mbgl/renderer/bucket_parameters.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> +#include <mbgl/renderer/image_atlas.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/text/get_anchors.hpp> #include <mbgl/text/collision_tile.hpp> #include <mbgl/text/shaping.hpp> @@ -41,7 +41,7 @@ static bool has(const style::SymbolLayoutProperties::PossiblyEvaluated& layout) SymbolLayout::SymbolLayout(const BucketParameters& parameters, const std::vector<const RenderLayer*>& 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,7 @@ bool SymbolLayout::hasSymbolInstances() const { return !symbolInstances.empty(); } -void SymbolLayout::prepare(const GlyphMap& glyphs, const IconMap& icons) { +void SymbolLayout::prepare(const GlyphMap& glyphs, const ImageMap& images) { float horizontalAlign = 0.5; float verticalAlign = 0.5; @@ -225,6 +225,8 @@ void SymbolLayout::prepare(const GlyphMap& glyphs, const IconMap& icons) { glyphAtlas = makeGlyphAtlas(glyphPositionsIt->second); } + imageAtlas = makeImageAtlas(images); + for (auto it = features.begin(); it != features.end(); ++it) { auto& feature = *it; if (feature.geometry.empty()) continue; @@ -266,15 +268,16 @@ void SymbolLayout::prepare(const GlyphMap& glyphs, const IconMap& icons) { // 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 = images.find(*feature.icon); + if (image != images.end()) { + shapedIcon = PositionedIcon::shapeIcon( + imageAtlas.positions.at(*feature.icon), layout.evaluate<IconOffset>(zoom, feature), layout.evaluate<IconRotate>(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<IconRotate>().constantOr(1) != 0) { iconsNeedLinear = true; @@ -428,6 +431,7 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear); bucket->text.atlasImage = glyphAtlas.image.clone(); + bucket->icon.atlasImage = imageAtlas.image.clone(); // Calculate which labels can be shown and when they can be shown and // create the bufers used for rendering. diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index e947ee354b..7d6f2319cd 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -30,10 +30,10 @@ public: SymbolLayout(const BucketParameters&, const std::vector<const RenderLayer*>&, const GeometryTileLayer&, - IconDependencies&, + ImageDependencies&, GlyphDependencies&); - void prepare(const GlyphMap& glyphs, const IconMap& icons); + void prepare(const GlyphMap& glyphs, const ImageMap& icons); std::unique_ptr<SymbolBucket> place(CollisionTile&); @@ -94,6 +94,7 @@ private: std::vector<SymbolFeature> features; GlyphAtlas glyphAtlas; + ImageAtlas imageAtlas; BiDi bidi; // Consider moving this up to geometry tile worker to reduce reinstantiation costs; use of BiDi/ubiditransform object must be constrained to one thread }; diff --git a/src/mbgl/programs/fill_extrusion_program.cpp b/src/mbgl/programs/fill_extrusion_program.cpp index 3f85d83788..aaf192a843 100644 --- a/src/mbgl/programs/fill_extrusion_program.cpp +++ b/src/mbgl/programs/fill_extrusion_program.cpp @@ -1,5 +1,5 @@ #include <mbgl/programs/fill_extrusion_program.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_atlas.hpp> #include <mbgl/renderer/cross_faded_property_evaluator.hpp> #include <mbgl/tile/tile_id.hpp> #include <mbgl/map/transform_state.hpp> @@ -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<std::string>& 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 7f1c76a6ad..820670068e 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> 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<std::string>&, const UnwrappedTileID&, const TransformState&, diff --git a/src/mbgl/programs/fill_program.cpp b/src/mbgl/programs/fill_program.cpp index 6c19e503ce..46dc830102 100644 --- a/src/mbgl/programs/fill_program.cpp +++ b/src/mbgl/programs/fill_program.cpp @@ -1,5 +1,5 @@ #include <mbgl/programs/fill_program.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_atlas.hpp> #include <mbgl/renderer/cross_faded_property_evaluator.hpp> #include <mbgl/tile/tile_id.hpp> #include <mbgl/map/transform_state.hpp> @@ -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<std::string>& fading, const UnwrappedTileID& tileID, const TransformState& state) diff --git a/src/mbgl/programs/fill_program.hpp b/src/mbgl/programs/fill_program.hpp index 093485fc7f..2dfeea3279 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> 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<std::string>&, const UnwrappedTileID&, const TransformState&); diff --git a/src/mbgl/programs/line_program.cpp b/src/mbgl/programs/line_program.cpp index 2c65cb74ed..86645588ca 100644 --- a/src/mbgl/programs/line_program.cpp +++ b/src/mbgl/programs/line_program.cpp @@ -1,9 +1,9 @@ #include <mbgl/programs/line_program.hpp> #include <mbgl/style/layers/line_layer_properties.hpp> #include <mbgl/renderer/render_tile.hpp> +#include <mbgl/renderer/image_atlas.hpp> #include <mbgl/map/transform_state.hpp> #include <mbgl/util/mat2.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/geometry/line_atlas.hpp> namespace mbgl { @@ -89,8 +89,8 @@ LinePatternProgram::uniformValues(const LinePaintProperties::PossiblyEvaluated& const TransformState& state, const std::array<float, 2>& pixelsToGLUnits, const Size atlasSize, - const SpriteAtlasElement& posA, - const SpriteAtlasElement& posB) { + const ImagePosition& posA, + const ImagePosition& posB) { std::array<float, 2> sizeA {{ tile.id.pixelsToTileUnits(posA.displaySize()[0] * properties.get<LinePattern>().fromScale, state.getIntegerZoom()), posA.displaySize()[1] diff --git a/src/mbgl/programs/line_program.hpp b/src/mbgl/programs/line_program.hpp index 6f6ceeb32b..fadd351026 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 @@ public: const TransformState&, const std::array<float, 2>& 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 fceffaa5f4..21d463b1fc 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -46,6 +46,7 @@ void SymbolBucket::upload(gl::Context& context) { if (hasIconData()) { icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices)); icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles)); + icon.atlasTexture = context.createTexture(std::move(icon.atlasImage), 0); iconSizeBinder->upload(context); } diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index 76b3467a9a..bc9d564aac 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -59,9 +59,11 @@ public: gl::VertexVector<SymbolLayoutVertex> vertices; gl::IndexVector<gl::Triangles> triangles; gl::SegmentVector<SymbolIconAttributes> segments; + PremultipliedImage atlasImage; optional<gl::VertexBuffer<SymbolLayoutVertex>> vertexBuffer; optional<gl::IndexBuffer<gl::Triangles>> indexBuffer; + optional<gl::Texture> atlasTexture; } icon; struct CollisionBoxBuffer { diff --git a/src/mbgl/renderer/image_atlas.cpp b/src/mbgl/renderer/image_atlas.cpp new file mode 100644 index 0000000000..b53c2162ea --- /dev/null +++ b/src/mbgl/renderer/image_atlas.cpp @@ -0,0 +1,68 @@ +#include <mbgl/renderer/image_atlas.hpp> + +#include <mapbox/shelf-pack.hpp> + +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); + + std::vector<const style::Image::Impl*> pointers; + pointers.reserve(images.size()); + + std::vector<mapbox::Bin> bins; + bins.reserve(images.size()); + + for (const auto& entry : images) { + const style::Image::Impl& image = *entry.second; + pointers.emplace_back(&image); + bins.emplace_back(pointers.size() - 1, + image.image.size.width + 2 * padding, + image.image.size.height + 2 * padding); + } + + mapbox::ShelfPack::PackOptions packOptions; + packOptions.inPlace = true; + pack.pack(bins, packOptions); + + result.image = PremultipliedImage({ + static_cast<uint32_t>(pack.width()), + static_cast<uint32_t>(pack.height()) + }); + + for (const auto& bin : bins) { + const style::Image::Impl& image = *pointers.at(bin.id); + + 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 }); + } + + 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 0000000000..b3cc166eff --- /dev/null +++ b/src/mbgl/renderer/image_atlas.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include <mbgl/style/image_impl.hpp> +#include <mbgl/util/rect.hpp> + +#include <mapbox/shelf-pack.hpp> + +#include <array> + +namespace mbgl { + +class ImagePosition { +public: + ImagePosition(const mapbox::Bin&, const style::Image::Impl&); + + float pixelRatio; + Rect<uint16_t> textureRect; + + std::array<uint16_t, 2> tl() const { + return {{ + textureRect.x, + textureRect.y + }}; + } + + std::array<uint16_t, 2> br() const { + return {{ + static_cast<uint16_t>(textureRect.x + textureRect.w), + static_cast<uint16_t>(textureRect.y + textureRect.h) + }}; + } + + std::array<float, 2> displaySize() const { + return {{ + textureRect.w / pixelRatio, + textureRect.h / pixelRatio, + }}; + } +}; + +using ImagePositions = std::map<std::string, ImagePosition>; + +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 0000000000..be47004b69 --- /dev/null +++ b/src/mbgl/renderer/image_manager.cpp @@ -0,0 +1,173 @@ +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/util/logging.hpp> +#include <mbgl/gl/context.hpp> + +namespace mbgl { + +void ImageManager::onSpriteLoaded() { + loaded = true; + for (const auto& entry : requestors) { + notify(*entry.first, entry.second); + } + requestors.clear(); +} + +void ImageManager::addImage(Immutable<style::Image::Impl> image_) { + assert(images.find(image_->id) == images.end()); + images.emplace(image_->id, std::move(image_)); +} + +void ImageManager::updateImage(Immutable<style::Image::Impl> 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<ImagePosition> 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 {}; + } + + if (!atlasImage.valid()) { + atlasImage = PremultipliedImage(getPixelSize()); + atlasImage.fill(0); + } else if (atlasImage.size != getPixelSize()) { + PremultipliedImage newImage(getPixelSize()); + PremultipliedImage::copy(atlasImage, newImage, { 0, 0 }, { 0, 0 }, atlasImage.size); + atlasImage = std::move(newImage); + } + + 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<uint32_t>(shelfPack.width()), + static_cast<uint32_t>(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 0000000000..9a9a4ce997 --- /dev/null +++ b/src/mbgl/renderer/image_manager.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include <mbgl/style/image_impl.hpp> +#include <mbgl/renderer/image_atlas.hpp> +#include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/immutable.hpp> +#include <mbgl/util/optional.hpp> +#include <mbgl/gl/texture.hpp> + +#include <mapbox/shelf-pack.hpp> + +#include <set> +#include <string> + +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<style::Image::Impl>); + void updateImage(Immutable<style::Image::Impl>); + 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<ImageRequestor*, ImageDependencies> requestors; + ImageMap images; + +// Pattern stuff +public: + optional<ImagePosition> 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<std::string, Pattern> patterns; + PremultipliedImage atlasImage; + mbgl::optional<gl::Texture> atlasTexture; + bool dirty = true; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 0054d9f874..395cf283d5 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<SymbolLayout> RenderSymbolLayer::createLayout(const BucketParame const std::vector<const RenderLayer*>& group, const GeometryTileLayer& layer, GlyphDependencies& glyphDependencies, - IconDependencies& iconDependencies) const { + ImageDependencies& imageDependencies) const { return std::make_unique<SymbolLayout>(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 42205496d9..8c7d43bf3a 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.hpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.hpp @@ -2,7 +2,7 @@ #include <mbgl/text/glyph.hpp> #include <mbgl/renderer/render_layer.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/style/image_impl.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> #include <mbgl/style/layers/symbol_layer_properties.hpp> @@ -76,7 +76,7 @@ public: std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; std::unique_ptr<SymbolLayout> createLayout(const BucketParameters&, const std::vector<const RenderLayer*>&, - 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 6f0ddf8467..673bf66901 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -21,7 +21,7 @@ #include <mbgl/style/layers/custom_layer_impl.hpp> #include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/geometry/line_atlas.hpp> #include <mbgl/programs/program_parameters.hpp> @@ -137,7 +137,7 @@ void Painter::render(RenderStyle& style, const FrameData& frame_, View& view) { view }; - spriteAtlas = style.spriteAtlas.get(); + imageManager = style.imageManager.get(); lineAtlas = style.lineAtlas.get(); evaluatedLight = style.getRenderLight().getEvaluated(); @@ -167,8 +167,7 @@ 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); frameHistory.upload(context, 0); } diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp index c8b61c6bf8..f2d06a0e20 100644 --- a/src/mbgl/renderer/painter.hpp +++ b/src/mbgl/renderer/painter.hpp @@ -32,7 +32,7 @@ namespace mbgl { class RenderStyle; class RenderTile; -class SpriteAtlas; +class ImageManager; class View; class LineAtlas; struct FrameData; @@ -151,7 +151,7 @@ public: float depthRangeSize; const float depthEpsilon = 1.0f / (1 << 16); - SpriteAtlas* spriteAtlas = nullptr; + ImageManager* imageManager = nullptr; LineAtlas* lineAtlas = nullptr; optional<OffscreenTexture> extrusionTexture; diff --git a/src/mbgl/renderer/painters/painter_background.cpp b/src/mbgl/renderer/painters/painter_background.cpp index 9cbc3d516c..7ebb735df8 100644 --- a/src/mbgl/renderer/painters/painter_background.cpp +++ b/src/mbgl/renderer/painters/painter_background.cpp @@ -1,10 +1,10 @@ #include <mbgl/renderer/painter.hpp> #include <mbgl/renderer/paint_parameters.hpp> #include <mbgl/renderer/layers/render_background_layer.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/style/layers/background_layer_impl.hpp> #include <mbgl/programs/programs.hpp> #include <mbgl/programs/fill_program.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/util/tile_cover.hpp> namespace mbgl { @@ -24,13 +24,13 @@ void Painter::renderBackground(PaintParameters& parameters, const RenderBackgrou const FillProgram::PaintPropertyBinders paintAttibuteData(properties, 0); if (!background.get<BackgroundPattern>().to.empty()) { - optional<SpriteAtlasElement> imagePosA = spriteAtlas->getPattern(background.get<BackgroundPattern>().from); - optional<SpriteAtlasElement> imagePosB = spriteAtlas->getPattern(background.get<BackgroundPattern>().to); + optional<ImagePosition> imagePosA = imageManager->getPattern(background.get<BackgroundPattern>().from); + optional<ImagePosition> imagePosB = imageManager->getPattern(background.get<BackgroundPattern>().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<BackgroundPattern>(), diff --git a/src/mbgl/renderer/painters/painter_fill.cpp b/src/mbgl/renderer/painters/painter_fill.cpp index d15a871d98..b7e0077ed0 100644 --- a/src/mbgl/renderer/painters/painter_fill.cpp +++ b/src/mbgl/renderer/painters/painter_fill.cpp @@ -3,8 +3,8 @@ #include <mbgl/renderer/buckets/fill_bucket.hpp> #include <mbgl/renderer/render_tile.hpp> #include <mbgl/renderer/layers/render_fill_layer.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/style/layers/fill_layer_impl.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/programs/programs.hpp> #include <mbgl/programs/fill_program.hpp> #include <mbgl/util/convert.hpp> @@ -24,14 +24,14 @@ void Painter::renderFill(PaintParameters& parameters, return; } - optional<SpriteAtlasElement> imagePosA = spriteAtlas->getPattern(properties.get<FillPattern>().from); - optional<SpriteAtlasElement> imagePosB = spriteAtlas->getPattern(properties.get<FillPattern>().to); + optional<ImagePosition> imagePosA = imageManager->getPattern(properties.get<FillPattern>().from); + optional<ImagePosition> imagePosB = imageManager->getPattern(properties.get<FillPattern>().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<FillTranslateAnchor>(), state), context.viewport.getCurrentValue().size, - spriteAtlas->getPixelSize(), + imageManager->getPixelSize(), *imagePosA, *imagePosB, properties.get<FillPattern>(), diff --git a/src/mbgl/renderer/painters/painter_fill_extrusion.cpp b/src/mbgl/renderer/painters/painter_fill_extrusion.cpp index c28cb76bff..55e56554dc 100644 --- a/src/mbgl/renderer/painters/painter_fill_extrusion.cpp +++ b/src/mbgl/renderer/painters/painter_fill_extrusion.cpp @@ -3,8 +3,8 @@ #include <mbgl/renderer/buckets/fill_extrusion_bucket.hpp> #include <mbgl/renderer/render_tile.hpp> #include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/style/layers/fill_extrusion_layer_impl.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/programs/programs.hpp> #include <mbgl/programs/fill_extrusion_program.hpp> #include <mbgl/util/constants.hpp> @@ -25,16 +25,14 @@ void Painter::renderFillExtrusion(PaintParameters& parameters, } if (!properties.get<FillExtrusionPattern>().from.empty()) { - optional<SpriteAtlasElement> imagePosA = - spriteAtlas->getPattern(properties.get<FillExtrusionPattern>().from); - optional<SpriteAtlasElement> imagePosB = - spriteAtlas->getPattern(properties.get<FillExtrusionPattern>().to); + optional<ImagePosition> imagePosA = imageManager->getPattern(properties.get<FillExtrusionPattern>().from); + optional<ImagePosition> imagePosB = imageManager->getPattern(properties.get<FillExtrusionPattern>().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<FillExtrusionTranslate>(), properties.get<FillExtrusionTranslateAnchor>(), state), - spriteAtlas->getPixelSize(), + imageManager->getPixelSize(), *imagePosA, *imagePosB, properties.get<FillExtrusionPattern>(), diff --git a/src/mbgl/renderer/painters/painter_line.cpp b/src/mbgl/renderer/painters/painter_line.cpp index 40076726af..ea2a63529d 100644 --- a/src/mbgl/renderer/painters/painter_line.cpp +++ b/src/mbgl/renderer/painters/painter_line.cpp @@ -3,10 +3,10 @@ #include <mbgl/renderer/buckets/line_bucket.hpp> #include <mbgl/renderer/render_tile.hpp> #include <mbgl/renderer/layers/render_line_layer.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/style/layers/line_layer_impl.hpp> #include <mbgl/programs/programs.hpp> #include <mbgl/programs/line_program.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/geometry/line_atlas.hpp> namespace mbgl { @@ -61,13 +61,13 @@ void Painter::renderLine(PaintParameters& parameters, lineAtlas->getSize().width)); } else if (!properties.get<LinePattern>().from.empty()) { - optional<SpriteAtlasElement> posA = spriteAtlas->getPattern(properties.get<LinePattern>().from); - optional<SpriteAtlasElement> posB = spriteAtlas->getPattern(properties.get<LinePattern>().to); + optional<ImagePosition> posA = imageManager->getPattern(properties.get<LinePattern>().from); + optional<ImagePosition> posB = imageManager->getPattern(properties.get<LinePattern>().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 5a5264d178..13baa1a514 100644 --- a/src/mbgl/renderer/painters/painter_symbol.cpp +++ b/src/mbgl/renderer/painters/painter_symbol.cpp @@ -5,7 +5,6 @@ #include <mbgl/renderer/layers/render_symbol_layer.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> #include <mbgl/text/glyph_atlas.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/programs/programs.hpp> #include <mbgl/programs/symbol_program.hpp> #include <mbgl/programs/collision_box_program.hpp> @@ -69,9 +68,12 @@ void Painter::renderSymbol(PaintParameters& parameters, const bool iconScaled = layout.get<IconSize>().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(*bucket.icon.atlasTexture, 0, + bucket.sdfIcons || state.isChanging() || iconScaled || iconTransformed + ? gl::TextureFilter::Linear : gl::TextureFilter::Nearest); + + const Size texsize = bucket.icon.atlasTexture->size; if (bucket.sdfIcons) { if (values.hasHalo) { diff --git a/src/mbgl/renderer/render_style.cpp b/src/mbgl/renderer/render_style.cpp index 845bdbe63b..f76d1f48d8 100644 --- a/src/mbgl/renderer/render_style.cpp +++ b/src/mbgl/renderer/render_style.cpp @@ -16,14 +16,13 @@ #include <mbgl/renderer/layers/render_raster_layer.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> #include <mbgl/renderer/style_diff.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/style/style.hpp> #include <mbgl/style/source_impl.hpp> #include <mbgl/style/transition_options.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/sprite/sprite_loader.hpp> #include <mbgl/text/glyph_manager.hpp> #include <mbgl/geometry/line_atlas.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/map/query.hpp> #include <mbgl/tile/tile.hpp> #include <mbgl/util/math.hpp> @@ -39,7 +38,7 @@ RenderStyle::RenderStyle(Scheduler& scheduler_, FileSource& fileSource_) : scheduler(scheduler_), fileSource(fileSource_), glyphManager(std::make_unique<GlyphManager>(fileSource)), - spriteAtlas(std::make_unique<SpriteAtlas>()), + imageManager(std::make_unique<ImageManager>()), lineAtlas(std::make_unique<LineAtlas>(Size{ 256, 512 })), imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>()), sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>()), @@ -100,7 +99,7 @@ void RenderStyle::update(const UpdateParameters& parameters) { parameters.fileSource, parameters.mode, parameters.annotationManager, - *spriteAtlas, + *imageManager, *glyphManager }; @@ -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,7 @@ void RenderStyle::update(const UpdateParameters& parameters) { needsRendering = true; } - if (hasLayoutDifference(layerDiff, layer->id)) { + if (hasLayoutDifference(layerDiff, layer->id) || !imageDiff.changed.empty()) { needsRelayout = true; } @@ -249,7 +248,7 @@ bool RenderStyle::isLoaded() const { } } - if (!spriteAtlas->isLoaded()) { + if (!imageManager->isLoaded()) { return false; } @@ -442,7 +441,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 26aebda3ab..dc33e7b2f4 100644 --- a/src/mbgl/renderer/render_style.hpp +++ b/src/mbgl/renderer/render_style.hpp @@ -17,7 +17,7 @@ namespace mbgl { class FileSource; class GlyphManager; -class SpriteAtlas; +class ImageManager; class LineAtlas; class RenderData; class TransformState; @@ -67,7 +67,7 @@ public: Scheduler& scheduler; FileSource& fileSource; std::unique_ptr<GlyphManager> glyphManager; - std::unique_ptr<SpriteAtlas> spriteAtlas; + std::unique_ptr<ImageManager> imageManager; std::unique_ptr<LineAtlas> lineAtlas; private: diff --git a/src/mbgl/renderer/tile_parameters.hpp b/src/mbgl/renderer/tile_parameters.hpp index 88def11585..cf7a5b100a 100644 --- a/src/mbgl/renderer/tile_parameters.hpp +++ b/src/mbgl/renderer/tile_parameters.hpp @@ -8,7 +8,7 @@ class TransformState; class Scheduler; class FileSource; class AnnotationManager; -class SpriteAtlas; +class ImageManager; class GlyphManager; class TileParameters { @@ -20,7 +20,7 @@ public: FileSource& fileSource; const MapMode mode; AnnotationManager& annotationManager; - SpriteAtlas& spriteAtlas; + ImageManager& imageManager; GlyphManager& glyphManager; }; diff --git a/src/mbgl/sprite/sprite_atlas.cpp b/src/mbgl/sprite/sprite_atlas.cpp deleted file mode 100644 index 69f2b0ce71..0000000000 --- a/src/mbgl/sprite/sprite_atlas.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/style/image_impl.hpp> -#include <mbgl/gl/context.hpp> -#include <mbgl/util/logging.hpp> -#include <mbgl/util/platform.hpp> -#include <mbgl/util/math.hpp> -#include <mbgl/util/std.hpp> -#include <mbgl/util/constants.hpp> -#include <mbgl/util/exception.hpp> - -#include <cassert> -#include <cmath> -#include <algorithm> - -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<style::Image::Impl> image_) { - assert(entries.find(image_->id) == entries.end()); - entries.emplace(image_->id, Entry { image_ }); - icons.clear(); -} - -void SpriteAtlas::updateImage(Immutable<style::Image::Impl> 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<SpriteAtlasElement> SpriteAtlas::getIcon(const std::string& id) { - return getImage(id, &Entry::iconBin); -} - -optional<SpriteAtlasElement> SpriteAtlas::getPattern(const std::string& id) { - return getImage(id, &Entry::patternBin); -} - -optional<SpriteAtlasElement> 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<uint32_t>(shelfPack.width()), - static_cast<uint32_t>(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<const char*>(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 1dbef86f7e..0000000000 --- a/src/mbgl/sprite/sprite_atlas.hpp +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once - -#include <mbgl/gl/texture.hpp> -#include <mbgl/util/noncopyable.hpp> -#include <mbgl/util/optional.hpp> -#include <mbgl/util/rect.hpp> -#include <mbgl/style/image.hpp> - -#include <mapbox/shelf-pack.hpp> - -#include <string> -#include <set> -#include <unordered_map> -#include <array> -#include <memory> - -namespace mbgl { - -namespace gl { -class Context; -} // namespace gl - -class SpriteAtlasElement { -public: - SpriteAtlasElement(const mapbox::Bin&, const style::Image::Impl&); - - bool sdf; - float pixelRatio; - Rect<uint16_t> textureRect; - - std::array<uint16_t, 2> tl() const { - return {{ - textureRect.x, - textureRect.y - }}; - } - - std::array<uint16_t, 2> br() const { - return {{ - static_cast<uint16_t>(textureRect.x + textureRect.w), - static_cast<uint16_t>(textureRect.y + textureRect.h) - }}; - } - - std::array<float, 2> displaySize() const { - return {{ - textureRect.w / pixelRatio, - textureRect.h / pixelRatio, - }}; - } -}; - -using IconMap = std::unordered_map<std::string, SpriteAtlasElement>; -using IconDependencies = std::set<std::string>; - -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<style::Image::Impl>); - void updateImage(Immutable<style::Image::Impl>); - 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<SpriteAtlasElement> 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<SpriteAtlasElement> 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<style::Image::Impl> 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<SpriteAtlasElement> getImage(const std::string& name, mapbox::Bin* Entry::*bin); - void copy(const Entry&, mapbox::Bin* Entry::*bin); - - IconMap buildIconMap(); - - std::unordered_map<std::string, Entry> entries; - mapbox::ShelfPack shelfPack; - PremultipliedImage image; - mbgl::optional<gl::Texture> texture; - bool dirty = true; - - std::set<IconRequestor*> requestors; - IconMap icons; -}; - -} // namespace mbgl diff --git a/src/mbgl/sprite/sprite_loader.cpp b/src/mbgl/sprite/sprite_loader.cpp index 7c5fe40e05..60ece5ed73 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<Mailbox>(*util::RunLoop::Get())), - worker(scheduler, ActorRef<SpriteLoader>(spriteAtlas, mailbox)) { + worker(scheduler, ActorRef<SpriteLoader>(imageManager, mailbox)) { } std::shared_ptr<const std::string> image; diff --git a/src/mbgl/style/image_impl.hpp b/src/mbgl/style/image_impl.hpp index aa2cad8278..75dc83206c 100644 --- a/src/mbgl/style/image_impl.hpp +++ b/src/mbgl/style/image_impl.hpp @@ -2,6 +2,10 @@ #include <mbgl/style/image.hpp> +#include <string> +#include <unordered_map> +#include <set> + namespace mbgl { namespace style { @@ -21,4 +25,8 @@ public: }; } // namespace style + +using ImageMap = std::unordered_map<std::string, Immutable<style::Image::Impl>>; +using ImageDependencies = std::set<std::string>; + } // namespace mbgl diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 69a6d401ed..27ca58e85d 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<style::Image> 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/quads.cpp b/src/mbgl/text/quads.cpp index ad57342b82..81827ca32e 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 diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 0ef347ba0b..338abe2e43 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<float, 2>& iconOffset, const float iconRotation) { +PositionedIcon PositionedIcon::shapeIcon(const ImagePosition& image, const std::array<float, 2>& iconOffset, const float iconRotation) { float dx = iconOffset[0]; float dy = iconOffset[1]; float x1 = dx - image.displaySize()[0] / 2.0f; diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index 1984b14e1d..ca475e2a6c 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -1,30 +1,29 @@ #pragma once #include <mbgl/text/glyph.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_atlas.hpp> 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; @@ -32,9 +31,9 @@ private: float _angle; public: - static PositionedIcon shapeIcon(const SpriteAtlasElement&, const std::array<float, 2>& iconOffset, const float iconRotation); + static PositionedIcon shapeIcon(const ImagePosition&, const std::array<float, 2>& 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; } diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 13034910a4..b7561d677c 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -39,13 +39,13 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, parameters.mode, parameters.pixelRatio), glyphManager(parameters.glyphManager), - spriteAtlas(parameters.spriteAtlas), + imageManager(parameters.imageManager), placementThrottler(Milliseconds(300), [this] { invokePlacement(); }) { } GeometryTile::~GeometryTile() { glyphManager.removeRequestor(*this); - spriteAtlas.removeRequestor(*this); + imageManager.removeRequestor(*this); markObsolete(); } @@ -153,12 +153,12 @@ void GeometryTile::getGlyphs(GlyphDependencies glyphDependencies) { glyphManager.getGlyphs(*this, std::move(glyphDependencies)); } -void GeometryTile::onIconsAvailable(IconMap icons) { - worker.invoke(&GeometryTileWorker::onIconsAvailable, std::move(icons)); +void GeometryTile::onImagesAvailable(ImageMap images) { + worker.invoke(&GeometryTileWorker::onImagesAvailable, std::move(images)); } -void GeometryTile::getIcons(IconDependencies iconDependencies) { - spriteAtlas.getIcons(*this, std::move(iconDependencies)); +void GeometryTile::getImages(ImageDependencies imageDependencies) { + imageManager.getImages(*this, std::move(imageDependencies)); } void GeometryTile::upload(gl::Context& context) { diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index 659f4f7e9f..f2510e317b 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -1,8 +1,8 @@ #pragma once -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/tile/tile.hpp> #include <mbgl/tile/geometry_tile_worker.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> #include <mbgl/text/placement_config.hpp> #include <mbgl/util/feature.hpp> @@ -24,7 +24,7 @@ class RenderLayer; class SourceQueryOptions; class TileParameters; -class GeometryTile : public Tile, public GlyphRequestor, IconRequestor { +class GeometryTile : public Tile, public GlyphRequestor, ImageRequestor { public: GeometryTile(const OverscaledTileID&, std::string sourceID, @@ -39,10 +39,10 @@ public: void setLayers(const std::vector<Immutable<style::Layer::Impl>>&) override; void onGlyphsAvailable(GlyphMap) override; - void onIconsAvailable(IconMap) 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; @@ -97,7 +97,7 @@ private: Actor<GeometryTileWorker> worker; GlyphManager& glyphManager; - SpriteAtlas& spriteAtlas; + ImageManager& imageManager; uint64_t correlationID = 0; optional<PlacementConfig> requestedConfig; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 8eaea161d2..d67cbca76b 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -3,7 +3,6 @@ #include <mbgl/tile/geometry_tile.hpp> #include <mbgl/text/collision_tile.hpp> #include <mbgl/layout/symbol_layout.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/renderer/bucket_parameters.hpp> #include <mbgl/renderer/group_by_layout.hpp> #include <mbgl/style/filter.hpp> @@ -217,9 +216,9 @@ void GeometryTileWorker::onGlyphsAvailable(GlyphMap newGlyphMap) { symbolDependenciesChanged(); } -void GeometryTileWorker::onIconsAvailable(IconMap newIconMap) { - iconMap = std::move(newIconMap); - pendingIconDependencies.clear(); +void GeometryTileWorker::onImagesAvailable(ImageMap newImageMap) { + imageMap = std::move(newImageMap); + pendingImageDependencies.clear(); symbolDependenciesChanged(); } @@ -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<std::unique_ptr<RenderLayer>> renderLayers = toRenderLayers(*layers, id.overscaledZ); @@ -311,7 +310,7 @@ void GeometryTileWorker::redoLayout() { if (leader.is<RenderSymbolLayer>()) { symbolLayoutMap.emplace(leader.getID(), - leader.as<RenderSymbolLayer>()->createLayout(parameters, group, *geometryLayer, glyphDependencies, iconDependencies)); + leader.as<RenderSymbolLayer>()->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(); } @@ -393,7 +392,7 @@ void GeometryTileWorker::attemptPlacement() { } if (symbolLayout->state == SymbolLayout::Pending) { - symbolLayout->prepare(glyphMap, iconMap); + symbolLayout->prepare(glyphMap, imageMap); symbolLayout->state = SymbolLayout::Placed; } diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp index c07c16dd9b..194477e7b8 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -2,7 +2,7 @@ #include <mbgl/map/mode.hpp> #include <mbgl/tile/tile_id.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/style/image_impl.hpp> #include <mbgl/text/glyph.hpp> #include <mbgl/text/placement_config.hpp> #include <mbgl/actor/actor_ref.hpp> @@ -38,7 +38,7 @@ public: void setPlacementConfig(PlacementConfig, uint64_t correlationID); void onGlyphsAvailable(GlyphMap glyphs); - void onIconsAvailable(IconMap icons); + void onImagesAvailable(ImageMap images); private: void coalesced(); @@ -48,7 +48,7 @@ private: void coalesce(); void requestNewGlyphs(const GlyphDependencies&); - void requestNewIcons(const IconDependencies&); + void requestNewImages(const ImageDependencies&); void symbolDependenciesChanged(); bool hasPendingSymbolDependencies() const; @@ -79,9 +79,9 @@ private: std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts; GlyphDependencies pendingGlyphDependencies; - IconDependencies pendingIconDependencies; + ImageDependencies pendingImageDependencies; GlyphMap glyphMap; - IconMap iconMap; + ImageMap imageMap; }; } // namespace mbgl diff --git a/test/fixtures/image_manager/basic/expected.png b/test/fixtures/image_manager/basic/expected.png Binary files differnew file mode 100644 index 0000000000..8c615234dc --- /dev/null +++ b/test/fixtures/image_manager/basic/expected.png diff --git a/test/fixtures/image_manager/updates_after/expected.png b/test/fixtures/image_manager/updates_after/expected.png Binary files differnew file mode 100644 index 0000000000..db588c739b --- /dev/null +++ b/test/fixtures/image_manager/updates_after/expected.png diff --git a/test/fixtures/image_manager/updates_before/expected.png b/test/fixtures/image_manager/updates_before/expected.png Binary files differnew file mode 100644 index 0000000000..1466a92fe7 --- /dev/null +++ b/test/fixtures/image_manager/updates_before/expected.png diff --git a/test/fixtures/sprite_atlas/basic/expected.png b/test/fixtures/sprite_atlas/basic/expected.png Binary files differdeleted file mode 100644 index 2960891c04..0000000000 --- a/test/fixtures/sprite_atlas/basic/expected.png +++ /dev/null diff --git a/test/fixtures/sprite_atlas/size/expected.png b/test/fixtures/sprite_atlas/size/expected.png Binary files differdeleted file mode 100644 index 5b08197a82..0000000000 --- a/test/fixtures/sprite_atlas/size/expected.png +++ /dev/null diff --git a/test/fixtures/sprite_atlas/updates_after/expected.png b/test/fixtures/sprite_atlas/updates_after/expected.png Binary files differdeleted file mode 100644 index 626ceab58b..0000000000 --- a/test/fixtures/sprite_atlas/updates_after/expected.png +++ /dev/null diff --git a/test/fixtures/sprite_atlas/updates_before/expected.png b/test/fixtures/sprite_atlas/updates_before/expected.png Binary files differdeleted file mode 100644 index 0858c19f05..0000000000 --- a/test/fixtures/sprite_atlas/updates_before/expected.png +++ /dev/null diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp new file mode 100644 index 0000000000..203e05d492 --- /dev/null +++ b/test/renderer/image_manager.test.cpp @@ -0,0 +1,147 @@ +#include <mbgl/test/util.hpp> +#include <mbgl/test/fixture_log_observer.hpp> +#include <mbgl/test/stub_file_source.hpp> +#include <mbgl/test/stub_style_observer.hpp> + +#include <mbgl/renderer/image_manager.hpp> +#include <mbgl/sprite/sprite_parser.hpp> +#include <mbgl/style/image_impl.hpp> +#include <mbgl/util/io.hpp> +#include <mbgl/util/image.hpp> +#include <mbgl/util/run_loop.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/string.hpp> + +#include <utility> + +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<style::Image::Impl>("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<style::Image::Impl>("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<style::Image::Impl>("one", PremultipliedImage({ 16, 16 }), 2)); + imageManager.addImage(makeMutable<style::Image::Impl>("two", PremultipliedImage({ 16, 16 }), 2)); + imageManager.addImage(makeMutable<style::Image::Impl>("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<style::Image::Impl>("big", PremultipliedImage({ 32, 32 }), 1)); + EXPECT_TRUE(imageManager.getImage("big")); + + imageManager.removeImage("big"); + + imageManager.addImage(makeMutable<style::Image::Impl>("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<void (ImageMap)> 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<style::Image::Impl>("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 78a5862475..0000000000 --- a/test/sprite/sprite_atlas.test.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include <mbgl/test/util.hpp> -#include <mbgl/test/fixture_log_observer.hpp> -#include <mbgl/test/stub_file_source.hpp> -#include <mbgl/test/stub_style_observer.hpp> - -#include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/sprite/sprite_parser.hpp> -#include <mbgl/style/image_impl.hpp> -#include <mbgl/util/io.hpp> -#include <mbgl/util/image.hpp> -#include <mbgl/util/run_loop.hpp> -#include <mbgl/util/default_thread_pool.hpp> -#include <mbgl/util/string.hpp> - -#include <utility> - -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<style::Image::Impl>("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<style::Image::Impl>("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<style::Image::Impl>("one", PremultipliedImage({ 16, 16 }), 2)); - atlas.addImage(makeMutable<style::Image::Impl>("two", PremultipliedImage({ 16, 16 }), 2)); - atlas.addImage(makeMutable<style::Image::Impl>("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<style::Image::Impl>("big", PremultipliedImage({ 32, 32 }), 1)); - EXPECT_TRUE(atlas.getIcon("big")); - - atlas.removeImage("big"); - - atlas.addImage(makeMutable<style::Image::Impl>("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<void (IconMap)> 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<style::Image::Impl>("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 630d701299..eaa3c72877 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -31,7 +31,7 @@ #include <mbgl/map/transform.hpp> #include <mbgl/annotation/annotation_manager.hpp> #include <mbgl/annotation/annotation_source.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> #include <cstdint> @@ -48,7 +48,7 @@ public: TransformState transformState; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; + ImageManager imageManager; GlyphManager glyphManager { fileSource }; TileParameters tileParameters { @@ -59,7 +59,7 @@ public: fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, + imageManager, glyphManager }; diff --git a/test/text/quads.test.cpp b/test/text/quads.test.cpp index 0a1dbbd1c9..efc3912aaa 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 168dbf5292..8aa91b1517 100644 --- a/test/tile/annotation_tile.test.cpp +++ b/test/tile/annotation_tile.test.cpp @@ -12,7 +12,7 @@ #include <mbgl/geometry/feature_index.hpp> #include <mbgl/annotation/annotation_manager.hpp> #include <mbgl/annotation/annotation_tile.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> #include <memory> @@ -27,7 +27,7 @@ public: ThreadPool threadPool { 1 }; AnnotationManager annotationManager; RenderStyle style { threadPool, fileSource }; - SpriteAtlas spriteAtlas; + ImageManager imageManager; GlyphManager glyphManager { fileSource }; TileParameters tileParameters { @@ -38,7 +38,7 @@ public: fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, + imageManager, glyphManager }; }; diff --git a/test/tile/geojson_tile.test.cpp b/test/tile/geojson_tile.test.cpp index 9a9fe9a78f..2aa85c3860 100644 --- a/test/tile/geojson_tile.test.cpp +++ b/test/tile/geojson_tile.test.cpp @@ -10,7 +10,7 @@ #include <mbgl/renderer/tile_parameters.hpp> #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/annotation/annotation_manager.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> #include <memory> @@ -25,7 +25,7 @@ public: util::RunLoop loop; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; + ImageManager imageManager; GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; @@ -37,7 +37,7 @@ public: fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, + imageManager, glyphManager }; }; diff --git a/test/tile/raster_tile.test.cpp b/test/tile/raster_tile.test.cpp index ba296c0360..a0666c2146 100644 --- a/test/tile/raster_tile.test.cpp +++ b/test/tile/raster_tile.test.cpp @@ -9,7 +9,7 @@ #include <mbgl/annotation/annotation_manager.hpp> #include <mbgl/renderer/tile_parameters.hpp> #include <mbgl/renderer/buckets/raster_bucket.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> using namespace mbgl; @@ -21,7 +21,7 @@ public: util::RunLoop loop; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; + ImageManager imageManager; GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; @@ -33,7 +33,7 @@ public: fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, + imageManager, glyphManager }; }; diff --git a/test/tile/vector_tile.test.cpp b/test/tile/vector_tile.test.cpp index e1a1635bd3..c427a1a869 100644 --- a/test/tile/vector_tile.test.cpp +++ b/test/tile/vector_tile.test.cpp @@ -13,7 +13,7 @@ #include <mbgl/text/collision_tile.hpp> #include <mbgl/geometry/feature_index.hpp> #include <mbgl/annotation/annotation_manager.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> #include <memory> @@ -27,7 +27,7 @@ public: util::RunLoop loop; ThreadPool threadPool { 1 }; AnnotationManager annotationManager; - SpriteAtlas spriteAtlas; + ImageManager imageManager; GlyphManager glyphManager { fileSource }; Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" }; @@ -39,7 +39,7 @@ public: fileSource, MapMode::Continuous, annotationManager, - spriteAtlas, + imageManager, glyphManager }; }; |