From 1e350b7ea485117cadc413d4d41062cf3c3c43a1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 5 Nov 2015 15:05:43 -0800 Subject: [core] Reorganize sprite related files --- src/mbgl/annotation/sprite_image.cpp | 29 ---- src/mbgl/annotation/sprite_parser.cpp | 148 ------------------- src/mbgl/annotation/sprite_parser.hpp | 46 ------ src/mbgl/annotation/sprite_store.cpp | 71 --------- src/mbgl/annotation/sprite_store.hpp | 53 ------- src/mbgl/geometry/sprite_atlas.cpp | 270 ---------------------------------- src/mbgl/geometry/sprite_atlas.hpp | 103 ------------- src/mbgl/map/map_context.cpp | 6 +- src/mbgl/map/sprite.cpp | 120 --------------- src/mbgl/map/sprite.hpp | 57 ------- src/mbgl/map/tile_worker.cpp | 2 +- src/mbgl/renderer/painter.cpp | 2 +- src/mbgl/renderer/painter_circle.cpp | 1 - src/mbgl/renderer/painter_fill.cpp | 3 +- src/mbgl/renderer/painter_line.cpp | 3 +- src/mbgl/renderer/painter_symbol.cpp | 2 +- src/mbgl/renderer/symbol_bucket.cpp | 6 +- src/mbgl/sprite/sprite.cpp | 119 +++++++++++++++ src/mbgl/sprite/sprite.hpp | 57 +++++++ src/mbgl/sprite/sprite_atlas.cpp | 270 ++++++++++++++++++++++++++++++++++ src/mbgl/sprite/sprite_atlas.hpp | 103 +++++++++++++ src/mbgl/sprite/sprite_image.cpp | 29 ++++ src/mbgl/sprite/sprite_parser.cpp | 148 +++++++++++++++++++ src/mbgl/sprite/sprite_parser.hpp | 46 ++++++ src/mbgl/sprite/sprite_store.cpp | 71 +++++++++ src/mbgl/sprite/sprite_store.hpp | 53 +++++++ src/mbgl/style/style.cpp | 6 +- src/mbgl/style/style.hpp | 2 +- 28 files changed, 911 insertions(+), 915 deletions(-) delete mode 100644 src/mbgl/annotation/sprite_image.cpp delete mode 100644 src/mbgl/annotation/sprite_parser.cpp delete mode 100644 src/mbgl/annotation/sprite_parser.hpp delete mode 100644 src/mbgl/annotation/sprite_store.cpp delete mode 100644 src/mbgl/annotation/sprite_store.hpp delete mode 100644 src/mbgl/geometry/sprite_atlas.cpp delete mode 100644 src/mbgl/geometry/sprite_atlas.hpp delete mode 100644 src/mbgl/map/sprite.cpp delete mode 100644 src/mbgl/map/sprite.hpp create mode 100644 src/mbgl/sprite/sprite.cpp create mode 100644 src/mbgl/sprite/sprite.hpp create mode 100644 src/mbgl/sprite/sprite_atlas.cpp create mode 100644 src/mbgl/sprite/sprite_atlas.hpp create mode 100644 src/mbgl/sprite/sprite_image.cpp create mode 100644 src/mbgl/sprite/sprite_parser.cpp create mode 100644 src/mbgl/sprite/sprite_parser.hpp create mode 100644 src/mbgl/sprite/sprite_store.cpp create mode 100644 src/mbgl/sprite/sprite_store.hpp (limited to 'src') diff --git a/src/mbgl/annotation/sprite_image.cpp b/src/mbgl/annotation/sprite_image.cpp deleted file mode 100644 index e482d8f13f..0000000000 --- a/src/mbgl/annotation/sprite_image.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include - -#include - -#include - -namespace mbgl { - -SpriteImage::SpriteImage(const uint16_t width_, - const uint16_t height_, - const float pixelRatio_, - std::string&& data_, - bool sdf_) - : width(width_), - height(height_), - pixelRatio(pixelRatio_), - pixelWidth(std::ceil(width * pixelRatio)), - pixelHeight(std::ceil(height * pixelRatio)), - data(std::move(data_)), - sdf(sdf_) { - const size_t size = pixelWidth * pixelHeight * 4; - if (size == 0) { - throw util::SpriteImageException("Sprite image dimensions may not be zero"); - } else if (size != data.size()) { - throw util::SpriteImageException("Sprite image pixel count mismatch"); - } -} - -} // namespace mbgl diff --git a/src/mbgl/annotation/sprite_parser.cpp b/src/mbgl/annotation/sprite_parser.cpp deleted file mode 100644 index acd93b47ce..0000000000 --- a/src/mbgl/annotation/sprite_parser.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include -#include - -#include - -#include - -#include -#include - -#include -#include -#include - -namespace mbgl { - -SpriteImagePtr createSpriteImage(const util::Image& image, - const uint16_t srcX, - const uint16_t srcY, - const uint16_t srcWidth, - const uint16_t srcHeight, - const double ratio, - const bool sdf) { - // Disallow invalid parameter configurations. - if (srcWidth == 0 || srcHeight == 0 || ratio <= 0 || ratio > 10 || srcWidth > 1024 || - srcHeight > 1024) { - Log::Warning(Event::Sprite, "Can't create sprite with invalid metrics"); - return nullptr; - } - - const uint16_t width = std::ceil(double(srcWidth) / ratio); - const uint16_t dstWidth = std::ceil(width * ratio); - assert(dstWidth >= srcWidth); - const uint16_t height = std::ceil(double(srcHeight) / ratio); - const uint16_t dstHeight = std::ceil(height * ratio); - assert(dstHeight >= srcHeight); - - std::string data(dstWidth * dstHeight * 4, '\0'); - - auto srcData = reinterpret_cast(image.getData()); - auto dstData = reinterpret_cast(const_cast(data.data())); - - const int32_t maxX = std::min(image.getWidth(), uint32_t(srcWidth + srcX)) - srcX; - assert(maxX <= int32_t(image.getWidth())); - const int32_t maxY = std::min(image.getHeight(), uint32_t(srcHeight + srcY)) - srcY; - assert(maxY <= int32_t(image.getHeight())); - - // Copy from the source image into our individual sprite image - for (uint16_t y = 0; y < maxY; ++y) { - const auto dstRow = y * dstWidth; - const auto srcRow = (y + srcY) * image.getWidth() + srcX; - for (uint16_t x = 0; x < maxX; ++x) { - dstData[dstRow + x] = srcData[srcRow + x]; - } - } - - return std::make_unique(width, height, ratio, std::move(data), sdf); -} - -namespace { - -inline uint16_t getUInt16(const rapidjson::Value& value, const char* name, const uint16_t def = 0) { - if (value.HasMember(name)) { - auto& v = value[name]; - if (v.IsUint() && v.GetUint() <= std::numeric_limits::max()) { - return v.GetUint(); - } else { - Log::Warning(Event::Sprite, "Value of '%s' must be an integer between 0 and 65535", - name); - } - } - - return def; -} - -inline double getDouble(const rapidjson::Value& value, const char* name, const double def = 0) { - if (value.HasMember(name)) { - auto& v = value[name]; - if (v.IsNumber()) { - return v.GetDouble(); - } else { - Log::Warning(Event::Sprite, "Value of '%s' must be a number", name); - } - } - - return def; -} - -inline bool getBoolean(const rapidjson::Value& value, const char* name, const bool def = false) { - if (value.HasMember(name)) { - auto& v = value[name]; - if (v.IsBool()) { - return v.GetBool(); - } else { - Log::Warning(Event::Sprite, "Value of '%s' must be a boolean", name); - } - } - - return def; -} - -} // namespace - -SpriteParseResult parseSprite(const std::string& image, const std::string& json) { - using namespace rapidjson; - - Sprites sprites; - - // Parse the sprite image. - const util::Image raster(image); - if (!raster) { - return std::string("Could not parse sprite image"); - } - - Document doc; - doc.Parse<0>(json.c_str()); - - if (doc.HasParseError()) { - std::stringstream message; - message << "Failed to parse JSON: " << rapidjson::GetParseError_En(doc.GetParseError()) << " at offset " << doc.GetErrorOffset(); - return message.str(); - } else if (!doc.IsObject()) { - return std::string("Sprite JSON root must be an object"); - } else { - for (Value::ConstMemberIterator itr = doc.MemberBegin(); itr != doc.MemberEnd(); ++itr) { - const std::string name = { itr->name.GetString(), itr->name.GetStringLength() }; - const Value& value = itr->value; - - if (value.IsObject()) { - const uint16_t x = getUInt16(value, "x", 0); - const uint16_t y = getUInt16(value, "y", 0); - const uint16_t width = getUInt16(value, "width", 0); - const uint16_t height = getUInt16(value, "height", 0); - const double pixelRatio = getDouble(value, "pixelRatio", 1); - const bool sdf = getBoolean(value, "sdf", false); - - auto sprite = createSpriteImage(raster, x, y, width, height, pixelRatio, sdf); - if (sprite) { - sprites.emplace(name, sprite); - } - } - } - } - - return sprites; -} - -} // namespace mbgl diff --git a/src/mbgl/annotation/sprite_parser.hpp b/src/mbgl/annotation/sprite_parser.hpp deleted file mode 100644 index c5856ebbc7..0000000000 --- a/src/mbgl/annotation/sprite_parser.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef MBGL_ANNOTATIONS_SPRITE_PARSER -#define MBGL_ANNOTATIONS_SPRITE_PARSER - -#include - -#include -#include - -#include -#include -#include - -namespace mbgl { - -namespace util { - -class Image; - -} // namespace util - -class SpriteImage; - -using SpriteImagePtr = std::shared_ptr; - -// Extracts an individual image from a spritesheet from the given location. -SpriteImagePtr createSpriteImage(const util::Image& image, - uint16_t srcX, - uint16_t srcY, - uint16_t srcWidth, - uint16_t srcHeight, - double ratio, - bool sdf); - -using Sprites = std::map; - - -using SpriteParseResult = mapbox::util::variant< - Sprites, // success - std::string>; // error - -// Parses an image and an associated JSON file and returns the sprite objects. -SpriteParseResult parseSprite(const std::string& image, const std::string& json); - -} // namespace mbgl - -#endif diff --git a/src/mbgl/annotation/sprite_store.cpp b/src/mbgl/annotation/sprite_store.cpp deleted file mode 100644 index 8d2231a2b7..0000000000 --- a/src/mbgl/annotation/sprite_store.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include - -#include - -namespace mbgl { - -void SpriteStore::setSprite(const std::string& name, std::shared_ptr sprite) { - std::lock_guard lock(mutex); - _setSprite(name, sprite); -} - -void SpriteStore::_setSprite(const std::string& name, - const std::shared_ptr& sprite) { - if (sprite) { - auto it = sprites.find(name); - if (it != sprites.end()) { - // There is already a sprite with that name in our store. - if ((it->second->width != sprite->width || it->second->height != sprite->height)) { - Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", name.c_str()); - return; - } - it->second = sprite; - } else { - sprites.emplace(name, sprite); - } - - // Always add/replace the value in the dirty list. - auto dirty_it = dirty.find(name); - if (dirty_it != dirty.end()) { - dirty_it->second = sprite; - } else { - dirty.emplace(name, sprite); - } - } else if (sprites.erase(name) > 0) { - dirty.emplace(name, nullptr); - } -} - -void SpriteStore::setSprites(const Sprites& newSprites) { - std::lock_guard lock(mutex); - for (const auto& pair : newSprites) { - _setSprite(pair.first, pair.second); - } -} - -void SpriteStore::removeSprite(const std::string& name) { - std::lock_guard lock(mutex); - _setSprite(name); -} - -std::shared_ptr SpriteStore::getSprite(const std::string& name) { - std::lock_guard lock(mutex); - const auto it = sprites.find(name); - if (it != sprites.end()) { - return it->second; - } else { - if (!sprites.empty()) { - Log::Info(Event::Sprite, "Can't find sprite named '%s'", name.c_str()); - } - return nullptr; - } -} - -SpriteStore::Sprites SpriteStore::getDirty() { - Sprites result; - std::lock_guard lock(mutex); - dirty.swap(result); - return result; -} - -} // namespace mbgl diff --git a/src/mbgl/annotation/sprite_store.hpp b/src/mbgl/annotation/sprite_store.hpp deleted file mode 100644 index 78e02ac695..0000000000 --- a/src/mbgl/annotation/sprite_store.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef MBGL_ANNOTATION_SPRITE_STORE -#define MBGL_ANNOTATION_SPRITE_STORE - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace mbgl { - -// The SpriteStore object holds Sprite images. -class SpriteStore : private util::noncopyable { - using Sprites = std::map>; - -public: - // Adds/replaces a Sprite image. - void setSprite(const std::string&, std::shared_ptr = nullptr); - - // Adds/replaces mutliple Sprite images. - void setSprites(const Sprites& sprites); - - // Removes a Sprite. - void removeSprite(const std::string&); - - // Obtains a Sprite image. - std::shared_ptr getSprite(const std::string&); - - // Returns Sprite images that changed since the last invocation of this function. - Sprites getDirty(); - -private: - void _setSprite(const std::string&, const std::shared_ptr& = nullptr); - - // Lock for sprites and dirty maps. - std::mutex mutex; - - // Stores all current sprites. - Sprites sprites; - - // Stores all Sprite IDs that changed since the last invocation. - Sprites dirty; -}; - -} // namespace mbgl - -#endif diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp deleted file mode 100644 index 743848d5f2..0000000000 --- a/src/mbgl/geometry/sprite_atlas.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - - -using namespace mbgl; - -SpriteAtlas::SpriteAtlas(dimension width_, dimension height_, float pixelRatio_, SpriteStore& store_) - : width(width_), - height(height_), - pixelWidth(std::ceil(width * pixelRatio_)), - pixelHeight(std::ceil(height * pixelRatio_)), - pixelRatio(pixelRatio_), - store(store_), - bin(width_, height_), - data(std::make_unique(pixelWidth * pixelHeight)), - dirty(true) { - std::fill(data.get(), data.get() + pixelWidth * pixelHeight, 0); -} - -Rect SpriteAtlas::allocateImage(const size_t pixel_width, const size_t pixel_height) { - // Increase to next number divisible by 4, but at least 1. - // This is so we can scale down the texture coordinates and pack them - // into 2 bytes rather than 4 bytes. - const uint16_t pack_width = (pixel_width + 1) + (4 - (pixel_width + 1) % 4); - const uint16_t pack_height = (pixel_height + 1) + (4 - (pixel_height + 1) % 4); - - // We have to allocate a new area in the bin, and store an empty image in it. - // Add a 1px border around every image. - Rect rect = bin.allocate(pack_width, pack_height); - if (rect.w == 0) { - return rect; - } - - rect.originalW = pixel_width; - rect.originalH = pixel_height; - - return rect; -} - -SpriteAtlasElement SpriteAtlas::getImage(const std::string& name, const bool wrap) { - std::lock_guard lock(mtx); - - auto rect_it = images.find({ name, wrap }); - if (rect_it != images.end()) { - return { rect_it->second.pos, rect_it->second.texture }; - } - - auto sprite = store.getSprite(name); - if (!sprite) { - return { Rect { 0, 0, 0, 0 }, nullptr }; - } - - Rect rect = allocateImage(sprite->width, sprite->height); - if (rect.w == 0) { - if (debug::spriteWarnings) { - Log::Warning(Event::Sprite, "sprite atlas bitmap overflow"); - } - return { Rect { 0, 0, 0, 0 }, nullptr }; - } - - const Holder& holder = images.emplace(Key{ name, wrap }, Holder{ sprite, rect }).first->second; - copy(holder, wrap); - - return { rect, sprite }; -} - -SpriteAtlasPosition SpriteAtlas::getPosition(const std::string& name, bool repeating) { - std::lock_guard lock(mtx); - - auto rect = getImage(name, repeating).pos; - if (repeating) { - // When the image is repeating, get the correct position of the image, rather than the - // one rounded up to 4 pixels. - // TODO: Can't we just use originalW/originalH? - auto sprite = store.getSprite(name); - if (!sprite) { - return SpriteAtlasPosition {}; - } - - rect.w = sprite->width; - rect.h = sprite->height; - } - - const float padding = 1; - - return SpriteAtlasPosition { - {{ float(rect.w), float(rect.h) }}, - {{ float(rect.x + padding) / width, float(rect.y + padding) / height }}, - {{ float(rect.x + padding + rect.w) / width, float(rect.y + padding + rect.h) / height }} - }; -} - -void SpriteAtlas::copy(const Holder& holder, const bool wrap) { - const uint32_t *srcData = reinterpret_cast(holder.texture->data.data()); - if (!srcData) return; - const vec2 srcSize { holder.texture->pixelWidth, holder.texture->pixelHeight }; - const Rect srcPos { 0, 0, srcSize.x, srcSize.y }; - const auto& dst = holder.pos; - - const int offset = 1; - - uint32_t *const dstData = data.get(); - const vec2 dstSize{ pixelWidth, pixelHeight }; - const Rect dstPos{ static_cast((offset + dst.x) * pixelRatio), - static_cast((offset + dst.y) * pixelRatio), - static_cast(dst.originalW * pixelRatio), - static_cast(dst.originalH * pixelRatio) }; - - util::bilinearScale(srcData, srcSize, srcPos, dstData, dstSize, dstPos, wrap); - - // Add borders around the copied image if required. - if (wrap) { - // We're copying from the same image so we don't have to scale again. - const uint32_t border = 1; - const uint32_t borderX = dstPos.x != 0 ? border : 0; - const uint32_t borderY = dstPos.y != 0 ? border : 0; - - // Left border - util::nearestNeighborScale( - dstData, dstSize, { dstPos.x + dstPos.w - borderX, dstPos.y, borderX, dstPos.h }, - dstData, dstSize, { dstPos.x - borderX, dstPos.y, borderX, dstPos.h }); - - // Right border - util::nearestNeighborScale(dstData, dstSize, { dstPos.x, dstPos.y, border, dstPos.h }, - dstData, dstSize, - { dstPos.x + dstPos.w, dstPos.y, border, dstPos.h }); - - // Top border - util::nearestNeighborScale( - dstData, dstSize, { dstPos.x - borderX, dstPos.y + dstPos.h - borderY, - dstPos.w + border + borderX, borderY }, - dstData, dstSize, - { dstPos.x - borderX, dstPos.y - borderY, dstPos.w + 2 * borderX, borderY }); - - // Bottom border - util::nearestNeighborScale( - dstData, dstSize, { dstPos.x - borderX, dstPos.y, dstPos.w + 2 * borderX, border }, - dstData, dstSize, - { dstPos.x - borderX, dstPos.y + dstPos.h, dstPos.w + border + borderX, border }); - } - - dirty = true; -} - -void SpriteAtlas::upload() { - if (dirty) { - bind(); - } -} - -void SpriteAtlas::updateDirty() { - auto dirtySprites = store.getDirty(); - if (dirtySprites.empty()) { - return; - } - - std::lock_guard lock(mtx); - - auto imageIterator = images.begin(); - auto spriteIterator = dirtySprites.begin(); - while (imageIterator != images.end() && spriteIterator != dirtySprites.end()) { - if (imageIterator->first.first < spriteIterator->first) { - ++imageIterator; - } else if (spriteIterator->first < imageIterator->first.first) { - ++spriteIterator; - } else { - // The two names match; - Holder& holder = imageIterator->second; - holder.texture = spriteIterator->second; - copy(holder, imageIterator->first.second); - - ++imageIterator; - // Don't advance the spriteIterator because there might be another sprite with the same - // name, but a different wrap value. - } - } -} - -void SpriteAtlas::bind(bool linear) { - if (!texture) { - MBGL_CHECK_ERROR(glGenTextures(1, &texture)); - MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); -#ifndef GL_ES_VERSION_2_0 - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); -#endif - // 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. - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - fullUploadRequired = true; - } else { - MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); - } - - GLuint filter_val = linear ? GL_LINEAR : GL_NEAREST; - if (filter_val != filter) { - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_val)); - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_val)); - filter = filter_val; - } - - if (dirty) { - std::lock_guard lock(mtx); - - if (fullUploadRequired) { - MBGL_CHECK_ERROR(glTexImage2D( - GL_TEXTURE_2D, // GLenum target - 0, // GLint level - GL_RGBA, // GLint internalformat - pixelWidth, // GLsizei width - pixelHeight, // GLsizei height - 0, // GLint border - GL_RGBA, // GLenum format - GL_UNSIGNED_BYTE, // GLenum type - data.get() // const GLvoid * data - )); - fullUploadRequired = false; - } else { - MBGL_CHECK_ERROR(glTexSubImage2D( - GL_TEXTURE_2D, // GLenum target - 0, // GLint level - 0, // GLint xoffset - 0, // GLint yoffset - pixelWidth, // GLsizei width - pixelHeight, // GLsizei height - GL_RGBA, // GLenum format - GL_UNSIGNED_BYTE, // GLenum type - data.get() // const GLvoid *pixels - )); - } - - dirty = false; - -#ifndef GL_ES_VERSION_2_0 - // platform::showColorDebugImage("Sprite Atlas", reinterpret_cast(data.get()), - // pixelWidth, pixelHeight, pixelWidth, pixelHeight); -#endif - } -}; - -SpriteAtlas::~SpriteAtlas() { - std::lock_guard lock(mtx); - if (texture) { - mbgl::util::ThreadContext::getGLObjectStore()->abandonTexture(texture); - texture = 0; - } -} - -SpriteAtlas::Holder::Holder(const std::shared_ptr& texture_, - const Rect& pos_) - : texture(texture_), pos(pos_) { -} - -SpriteAtlas::Holder::Holder(Holder&& h) : texture(std::move(h.texture)), pos(h.pos) { -} diff --git a/src/mbgl/geometry/sprite_atlas.hpp b/src/mbgl/geometry/sprite_atlas.hpp deleted file mode 100644 index 2e794f651b..0000000000 --- a/src/mbgl/geometry/sprite_atlas.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef MBGL_GEOMETRY_SPRITE_ATLAS -#define MBGL_GEOMETRY_SPRITE_ATLAS - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace mbgl { - -class SpriteStore; -class SpriteImage; -class SpritePosition; - -struct SpriteAtlasPosition { - inline SpriteAtlasPosition(const std::array size_ = {{0, 0}}, - const std::array tl_ = {{0, 0}}, - const std::array br_ = {{0, 0}}) - : size(size_), tl(tl_), br(br_) {} - std::array size; - std::array tl; - std::array br; -}; - -struct SpriteAtlasElement { - const Rect pos; - const std::shared_ptr texture; -}; - -class SpriteAtlas : public util::noncopyable { -public: - typedef uint16_t dimension; - - SpriteAtlas(dimension width, dimension height, float pixelRatio, SpriteStore& store); - ~SpriteAtlas(); - - // Returns the coordinates of an image that is sourced from the sprite image. - // This getter attempts to read the image from the sprite if it is already loaded. - // In that case, it copies it into the sprite atlas and returns the dimensions. - // Otherwise, it returns a 0/0/0/0 rect. - // This function is used during bucket creation. - SpriteAtlasElement getImage(const std::string& name, const bool wrap); - - // This function is used for getting the position during render time. - SpriteAtlasPosition getPosition(const std::string& name, bool repeating = false); - - // Binds the atlas texture to the GPU, and uploads data if it is out of date. - void bind(bool linear = false); - - // Updates sprites in the atlas texture that may have changed in the source SpriteStore object. - void updateDirty(); - - // 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(); - - inline dimension getWidth() const { return width; } - inline dimension getHeight() const { return height; } - inline dimension getTextureWidth() const { return pixelWidth; } - inline dimension getTextureHeight() const { return pixelHeight; } - inline float getPixelRatio() const { return pixelRatio; } - inline const uint32_t* getData() const { return data.get(); } - -private: - const GLsizei width, height; - const dimension pixelWidth, pixelHeight; - const float pixelRatio; - - struct Holder : private util::noncopyable { - inline Holder(const std::shared_ptr&, const Rect&); - inline Holder(Holder&&); - std::shared_ptr texture; - const Rect pos; - }; - - using Key = std::pair; - - Rect allocateImage(size_t width, size_t height); - void copy(const Holder& holder, const bool wrap); - - std::recursive_mutex mtx; - SpriteStore& store; - BinPack bin; - std::map images; - std::set uninitialized; - const std::unique_ptr data; - std::atomic dirty; - bool fullUploadRequired = true; - GLuint texture = 0; - uint32_t filter = 0; - static const int buffer = 1; -}; - -}; - -#endif diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 75f195a3f9..dedfff02d8 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include @@ -12,10 +11,11 @@ #include #include -#include - #include +#include +#include + #include #include #include diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp deleted file mode 100644 index 518979c5f0..0000000000 --- a/src/mbgl/map/sprite.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace mbgl { - -struct Sprite::Loader { - std::shared_ptr image; - std::shared_ptr json; - RequestHolder jsonRequest; - RequestHolder spriteRequest; -}; - -Sprite::Sprite(const std::string& baseUrl, float pixelRatio_) - : pixelRatio(pixelRatio_ > 1 ? 2 : 1) { - if (baseUrl.empty()) { - // Treat a non-existent sprite as a successfully loaded empty sprite. - loaded = true; - return; - } - - std::string spriteURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".png"); - std::string jsonURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".json"); - - loader = std::make_unique(); - - FileSource* fs = util::ThreadContext::getFileSource(); - loader->jsonRequest = fs->request({ Resource::Kind::SpriteJSON, jsonURL }, util::RunLoop::getLoop(), - [this, jsonURL](const Response& res) { - if (res.stale) { - // Only handle fresh responses. - return; - } - loader->jsonRequest = nullptr; - - if (res.error) { - std::stringstream message; - message << "Failed to load [" << jsonURL << "]: " << res.error->message; - emitSpriteLoadingFailed(message.str()); - return; - } else { - loader->json = res.data; - } - emitSpriteLoadedIfComplete(); - }); - - loader->spriteRequest = - fs->request({ Resource::Kind::SpriteImage, spriteURL }, util::RunLoop::getLoop(), - [this, spriteURL](const Response& res) { - if (res.stale) { - // Only handle fresh responses. - return; - } - loader->spriteRequest = nullptr; - - if (res.error) { - std::stringstream message; - message << "Failed to load [" << spriteURL << "]: " << res.error->message; - emitSpriteLoadingFailed(message.str()); - return; - } else { - loader->image = res.data; - } - emitSpriteLoadedIfComplete(); - }); -} - -Sprite::~Sprite() { -} - -void Sprite::emitSpriteLoadedIfComplete() { - assert(loader); - - if (!loader->image || !loader->json || !observer) { - return; - } - - auto local = std::move(loader); - auto result = parseSprite(*local->image, *local->json); - if (result.is()) { - loaded = true; - observer->onSpriteLoaded(result.get()); - } else { - emitSpriteLoadingFailed(result.get()); - } -} - -void Sprite::emitSpriteLoadingFailed(const std::string& message) { - if (!observer) { - return; - } - - auto error = std::make_exception_ptr(util::SpriteLoadingException(message)); - observer->onSpriteLoadingFailed(error); -} - -void Sprite::setObserver(Observer* observer_) { - observer = observer_; -} - -void Sprite::dumpDebugLogs() const { - Log::Info(Event::General, "Sprite::loaded: %d", loaded); -} - -} // namespace mbgl diff --git a/src/mbgl/map/sprite.hpp b/src/mbgl/map/sprite.hpp deleted file mode 100644 index cd82460a12..0000000000 --- a/src/mbgl/map/sprite.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef MBGL_STYLE_SPRITE -#define MBGL_STYLE_SPRITE - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace mbgl { - -class Request; - -class Sprite : private util::noncopyable { -public: - class Observer { - public: - virtual ~Observer() = default; - - virtual void onSpriteLoaded(const Sprites& sprites) = 0; - virtual void onSpriteLoadingFailed(std::exception_ptr error) = 0; - }; - - Sprite(const std::string& baseUrl, float pixelRatio); - ~Sprite(); - - inline bool isLoaded() const { - return loaded; - } - - void dumpDebugLogs() const; - - const float pixelRatio; - - void setObserver(Observer* observer); - -private: - void emitSpriteLoadedIfComplete(); - void emitSpriteLoadingFailed(const std::string& message); - - struct Loader; - std::unique_ptr loader; - - bool loaded = false; - - Observer* observer = nullptr; -}; - -} // namespace mbgl - -#endif diff --git a/src/mbgl/map/tile_worker.cpp b/src/mbgl/map/tile_worker.cpp index 8c1115170d..effb90a73d 100644 --- a/src/mbgl/map/tile_worker.cpp +++ b/src/mbgl/map/tile_worker.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index 140ab2991c..284bed3705 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -13,7 +13,7 @@ #include -#include +#include #include #include diff --git a/src/mbgl/renderer/painter_circle.cpp b/src/mbgl/renderer/painter_circle.cpp index 708064bf89..67c23f8615 100644 --- a/src/mbgl/renderer/painter_circle.cpp +++ b/src/mbgl/renderer/painter_circle.cpp @@ -3,7 +3,6 @@ #include -#include #include #include diff --git a/src/mbgl/renderer/painter_fill.cpp b/src/mbgl/renderer/painter_fill.cpp index d9e069d4e8..4bee5b6d1e 100644 --- a/src/mbgl/renderer/painter_fill.cpp +++ b/src/mbgl/renderer/painter_fill.cpp @@ -1,9 +1,8 @@ #include #include #include -#include #include -#include +#include #include #include #include diff --git a/src/mbgl/renderer/painter_line.cpp b/src/mbgl/renderer/painter_line.cpp index cfc85fcb48..e33864622e 100644 --- a/src/mbgl/renderer/painter_line.cpp +++ b/src/mbgl/renderer/painter_line.cpp @@ -1,13 +1,12 @@ #include #include #include -#include #include #include #include #include #include -#include +#include #include #include diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp index 343c9d5233..f46a9c8454 100644 --- a/src/mbgl/renderer/painter_symbol.cpp +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index ee42d7f47e..1071fdcce9 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -1,11 +1,12 @@ #include #include #include -#include +#include +#include +#include #include #include #include -#include #include #include #include @@ -16,7 +17,6 @@ #include #include #include -#include #include #include diff --git a/src/mbgl/sprite/sprite.cpp b/src/mbgl/sprite/sprite.cpp new file mode 100644 index 0000000000..881a6ff8ed --- /dev/null +++ b/src/mbgl/sprite/sprite.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace mbgl { + +struct Sprite::Loader { + std::shared_ptr image; + std::shared_ptr json; + RequestHolder jsonRequest; + RequestHolder spriteRequest; +}; + +Sprite::Sprite(const std::string& baseUrl, float pixelRatio_) + : pixelRatio(pixelRatio_ > 1 ? 2 : 1) { + if (baseUrl.empty()) { + // Treat a non-existent sprite as a successfully loaded empty sprite. + loaded = true; + return; + } + + std::string spriteURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".png"); + std::string jsonURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".json"); + + loader = std::make_unique(); + + FileSource* fs = util::ThreadContext::getFileSource(); + loader->jsonRequest = fs->request({ Resource::Kind::SpriteJSON, jsonURL }, util::RunLoop::getLoop(), + [this, jsonURL](const Response& res) { + if (res.stale) { + // Only handle fresh responses. + return; + } + loader->jsonRequest = nullptr; + + if (res.error) { + std::stringstream message; + message << "Failed to load [" << jsonURL << "]: " << res.error->message; + emitSpriteLoadingFailed(message.str()); + return; + } else { + loader->json = res.data; + } + emitSpriteLoadedIfComplete(); + }); + + loader->spriteRequest = + fs->request({ Resource::Kind::SpriteImage, spriteURL }, util::RunLoop::getLoop(), + [this, spriteURL](const Response& res) { + if (res.stale) { + // Only handle fresh responses. + return; + } + loader->spriteRequest = nullptr; + + if (res.error) { + std::stringstream message; + message << "Failed to load [" << spriteURL << "]: " << res.error->message; + emitSpriteLoadingFailed(message.str()); + return; + } else { + loader->image = res.data; + } + emitSpriteLoadedIfComplete(); + }); +} + +Sprite::~Sprite() { +} + +void Sprite::emitSpriteLoadedIfComplete() { + assert(loader); + + if (!loader->image || !loader->json || !observer) { + return; + } + + auto local = std::move(loader); + auto result = parseSprite(*local->image, *local->json); + if (result.is()) { + loaded = true; + observer->onSpriteLoaded(result.get()); + } else { + emitSpriteLoadingFailed(result.get()); + } +} + +void Sprite::emitSpriteLoadingFailed(const std::string& message) { + if (!observer) { + return; + } + + auto error = std::make_exception_ptr(util::SpriteLoadingException(message)); + observer->onSpriteLoadingFailed(error); +} + +void Sprite::setObserver(Observer* observer_) { + observer = observer_; +} + +void Sprite::dumpDebugLogs() const { + Log::Info(Event::General, "Sprite::loaded: %d", loaded); +} + +} // namespace mbgl diff --git a/src/mbgl/sprite/sprite.hpp b/src/mbgl/sprite/sprite.hpp new file mode 100644 index 0000000000..d204b42e85 --- /dev/null +++ b/src/mbgl/sprite/sprite.hpp @@ -0,0 +1,57 @@ +#ifndef MBGL_SPRITE +#define MBGL_SPRITE + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mbgl { + +class Request; + +class Sprite : private util::noncopyable { +public: + class Observer { + public: + virtual ~Observer() = default; + + virtual void onSpriteLoaded(const Sprites& sprites) = 0; + virtual void onSpriteLoadingFailed(std::exception_ptr error) = 0; + }; + + Sprite(const std::string& baseUrl, float pixelRatio); + ~Sprite(); + + inline bool isLoaded() const { + return loaded; + } + + void dumpDebugLogs() const; + + const float pixelRatio; + + void setObserver(Observer* observer); + +private: + void emitSpriteLoadedIfComplete(); + void emitSpriteLoadingFailed(const std::string& message); + + struct Loader; + std::unique_ptr loader; + + bool loaded = false; + + Observer* observer = nullptr; +}; + +} // namespace mbgl + +#endif diff --git a/src/mbgl/sprite/sprite_atlas.cpp b/src/mbgl/sprite/sprite_atlas.cpp new file mode 100644 index 0000000000..22c2ba8b95 --- /dev/null +++ b/src/mbgl/sprite/sprite_atlas.cpp @@ -0,0 +1,270 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + + +using namespace mbgl; + +SpriteAtlas::SpriteAtlas(dimension width_, dimension height_, float pixelRatio_, SpriteStore& store_) + : width(width_), + height(height_), + pixelWidth(std::ceil(width * pixelRatio_)), + pixelHeight(std::ceil(height * pixelRatio_)), + pixelRatio(pixelRatio_), + store(store_), + bin(width_, height_), + data(std::make_unique(pixelWidth * pixelHeight)), + dirty(true) { + std::fill(data.get(), data.get() + pixelWidth * pixelHeight, 0); +} + +Rect SpriteAtlas::allocateImage(const size_t pixel_width, const size_t pixel_height) { + // Increase to next number divisible by 4, but at least 1. + // This is so we can scale down the texture coordinates and pack them + // into 2 bytes rather than 4 bytes. + const uint16_t pack_width = (pixel_width + 1) + (4 - (pixel_width + 1) % 4); + const uint16_t pack_height = (pixel_height + 1) + (4 - (pixel_height + 1) % 4); + + // We have to allocate a new area in the bin, and store an empty image in it. + // Add a 1px border around every image. + Rect rect = bin.allocate(pack_width, pack_height); + if (rect.w == 0) { + return rect; + } + + rect.originalW = pixel_width; + rect.originalH = pixel_height; + + return rect; +} + +SpriteAtlasElement SpriteAtlas::getImage(const std::string& name, const bool wrap) { + std::lock_guard lock(mtx); + + auto rect_it = images.find({ name, wrap }); + if (rect_it != images.end()) { + return { rect_it->second.pos, rect_it->second.texture }; + } + + auto sprite = store.getSprite(name); + if (!sprite) { + return { Rect { 0, 0, 0, 0 }, nullptr }; + } + + Rect rect = allocateImage(sprite->width, sprite->height); + if (rect.w == 0) { + if (debug::spriteWarnings) { + Log::Warning(Event::Sprite, "sprite atlas bitmap overflow"); + } + return { Rect { 0, 0, 0, 0 }, nullptr }; + } + + const Holder& holder = images.emplace(Key{ name, wrap }, Holder{ sprite, rect }).first->second; + copy(holder, wrap); + + return { rect, sprite }; +} + +SpriteAtlasPosition SpriteAtlas::getPosition(const std::string& name, bool repeating) { + std::lock_guard lock(mtx); + + auto rect = getImage(name, repeating).pos; + if (repeating) { + // When the image is repeating, get the correct position of the image, rather than the + // one rounded up to 4 pixels. + // TODO: Can't we just use originalW/originalH? + auto sprite = store.getSprite(name); + if (!sprite) { + return SpriteAtlasPosition {}; + } + + rect.w = sprite->width; + rect.h = sprite->height; + } + + const float padding = 1; + + return SpriteAtlasPosition { + {{ float(rect.w), float(rect.h) }}, + {{ float(rect.x + padding) / width, float(rect.y + padding) / height }}, + {{ float(rect.x + padding + rect.w) / width, float(rect.y + padding + rect.h) / height }} + }; +} + +void SpriteAtlas::copy(const Holder& holder, const bool wrap) { + const uint32_t *srcData = reinterpret_cast(holder.texture->data.data()); + if (!srcData) return; + const vec2 srcSize { holder.texture->pixelWidth, holder.texture->pixelHeight }; + const Rect srcPos { 0, 0, srcSize.x, srcSize.y }; + const auto& dst = holder.pos; + + const int offset = 1; + + uint32_t *const dstData = data.get(); + const vec2 dstSize{ pixelWidth, pixelHeight }; + const Rect dstPos{ static_cast((offset + dst.x) * pixelRatio), + static_cast((offset + dst.y) * pixelRatio), + static_cast(dst.originalW * pixelRatio), + static_cast(dst.originalH * pixelRatio) }; + + util::bilinearScale(srcData, srcSize, srcPos, dstData, dstSize, dstPos, wrap); + + // Add borders around the copied image if required. + if (wrap) { + // We're copying from the same image so we don't have to scale again. + const uint32_t border = 1; + const uint32_t borderX = dstPos.x != 0 ? border : 0; + const uint32_t borderY = dstPos.y != 0 ? border : 0; + + // Left border + util::nearestNeighborScale( + dstData, dstSize, { dstPos.x + dstPos.w - borderX, dstPos.y, borderX, dstPos.h }, + dstData, dstSize, { dstPos.x - borderX, dstPos.y, borderX, dstPos.h }); + + // Right border + util::nearestNeighborScale(dstData, dstSize, { dstPos.x, dstPos.y, border, dstPos.h }, + dstData, dstSize, + { dstPos.x + dstPos.w, dstPos.y, border, dstPos.h }); + + // Top border + util::nearestNeighborScale( + dstData, dstSize, { dstPos.x - borderX, dstPos.y + dstPos.h - borderY, + dstPos.w + border + borderX, borderY }, + dstData, dstSize, + { dstPos.x - borderX, dstPos.y - borderY, dstPos.w + 2 * borderX, borderY }); + + // Bottom border + util::nearestNeighborScale( + dstData, dstSize, { dstPos.x - borderX, dstPos.y, dstPos.w + 2 * borderX, border }, + dstData, dstSize, + { dstPos.x - borderX, dstPos.y + dstPos.h, dstPos.w + border + borderX, border }); + } + + dirty = true; +} + +void SpriteAtlas::upload() { + if (dirty) { + bind(); + } +} + +void SpriteAtlas::updateDirty() { + auto dirtySprites = store.getDirty(); + if (dirtySprites.empty()) { + return; + } + + std::lock_guard lock(mtx); + + auto imageIterator = images.begin(); + auto spriteIterator = dirtySprites.begin(); + while (imageIterator != images.end() && spriteIterator != dirtySprites.end()) { + if (imageIterator->first.first < spriteIterator->first) { + ++imageIterator; + } else if (spriteIterator->first < imageIterator->first.first) { + ++spriteIterator; + } else { + // The two names match; + Holder& holder = imageIterator->second; + holder.texture = spriteIterator->second; + copy(holder, imageIterator->first.second); + + ++imageIterator; + // Don't advance the spriteIterator because there might be another sprite with the same + // name, but a different wrap value. + } + } +} + +void SpriteAtlas::bind(bool linear) { + if (!texture) { + MBGL_CHECK_ERROR(glGenTextures(1, &texture)); + MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); +#ifndef GL_ES_VERSION_2_0 + MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); +#endif + // 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. + MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + fullUploadRequired = true; + } else { + MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); + } + + GLuint filter_val = linear ? GL_LINEAR : GL_NEAREST; + if (filter_val != filter) { + MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_val)); + MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_val)); + filter = filter_val; + } + + if (dirty) { + std::lock_guard lock(mtx); + + if (fullUploadRequired) { + MBGL_CHECK_ERROR(glTexImage2D( + GL_TEXTURE_2D, // GLenum target + 0, // GLint level + GL_RGBA, // GLint internalformat + pixelWidth, // GLsizei width + pixelHeight, // GLsizei height + 0, // GLint border + GL_RGBA, // GLenum format + GL_UNSIGNED_BYTE, // GLenum type + data.get() // const GLvoid * data + )); + fullUploadRequired = false; + } else { + MBGL_CHECK_ERROR(glTexSubImage2D( + GL_TEXTURE_2D, // GLenum target + 0, // GLint level + 0, // GLint xoffset + 0, // GLint yoffset + pixelWidth, // GLsizei width + pixelHeight, // GLsizei height + GL_RGBA, // GLenum format + GL_UNSIGNED_BYTE, // GLenum type + data.get() // const GLvoid *pixels + )); + } + + dirty = false; + +#ifndef GL_ES_VERSION_2_0 + // platform::showColorDebugImage("Sprite Atlas", reinterpret_cast(data.get()), + // pixelWidth, pixelHeight, pixelWidth, pixelHeight); +#endif + } +}; + +SpriteAtlas::~SpriteAtlas() { + std::lock_guard lock(mtx); + if (texture) { + mbgl::util::ThreadContext::getGLObjectStore()->abandonTexture(texture); + texture = 0; + } +} + +SpriteAtlas::Holder::Holder(const std::shared_ptr& texture_, + const Rect& pos_) + : texture(texture_), pos(pos_) { +} + +SpriteAtlas::Holder::Holder(Holder&& h) : texture(std::move(h.texture)), pos(h.pos) { +} diff --git a/src/mbgl/sprite/sprite_atlas.hpp b/src/mbgl/sprite/sprite_atlas.hpp new file mode 100644 index 0000000000..0d86279e2d --- /dev/null +++ b/src/mbgl/sprite/sprite_atlas.hpp @@ -0,0 +1,103 @@ +#ifndef MBGL_SPRITE_ATLAS +#define MBGL_SPRITE_ATLAS + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +class SpriteStore; +class SpriteImage; +class SpritePosition; + +struct SpriteAtlasPosition { + inline SpriteAtlasPosition(const std::array size_ = {{0, 0}}, + const std::array tl_ = {{0, 0}}, + const std::array br_ = {{0, 0}}) + : size(size_), tl(tl_), br(br_) {} + std::array size; + std::array tl; + std::array br; +}; + +struct SpriteAtlasElement { + const Rect pos; + const std::shared_ptr texture; +}; + +class SpriteAtlas : public util::noncopyable { +public: + typedef uint16_t dimension; + + SpriteAtlas(dimension width, dimension height, float pixelRatio, SpriteStore& store); + ~SpriteAtlas(); + + // Returns the coordinates of an image that is sourced from the sprite image. + // This getter attempts to read the image from the sprite if it is already loaded. + // In that case, it copies it into the sprite atlas and returns the dimensions. + // Otherwise, it returns a 0/0/0/0 rect. + // This function is used during bucket creation. + SpriteAtlasElement getImage(const std::string& name, const bool wrap); + + // This function is used for getting the position during render time. + SpriteAtlasPosition getPosition(const std::string& name, bool repeating = false); + + // Binds the atlas texture to the GPU, and uploads data if it is out of date. + void bind(bool linear = false); + + // Updates sprites in the atlas texture that may have changed in the source SpriteStore object. + void updateDirty(); + + // 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(); + + inline dimension getWidth() const { return width; } + inline dimension getHeight() const { return height; } + inline dimension getTextureWidth() const { return pixelWidth; } + inline dimension getTextureHeight() const { return pixelHeight; } + inline float getPixelRatio() const { return pixelRatio; } + inline const uint32_t* getData() const { return data.get(); } + +private: + const GLsizei width, height; + const dimension pixelWidth, pixelHeight; + const float pixelRatio; + + struct Holder : private util::noncopyable { + inline Holder(const std::shared_ptr&, const Rect&); + inline Holder(Holder&&); + std::shared_ptr texture; + const Rect pos; + }; + + using Key = std::pair; + + Rect allocateImage(size_t width, size_t height); + void copy(const Holder& holder, const bool wrap); + + std::recursive_mutex mtx; + SpriteStore& store; + BinPack bin; + std::map images; + std::set uninitialized; + const std::unique_ptr data; + std::atomic dirty; + bool fullUploadRequired = true; + GLuint texture = 0; + uint32_t filter = 0; + static const int buffer = 1; +}; + +}; + +#endif diff --git a/src/mbgl/sprite/sprite_image.cpp b/src/mbgl/sprite/sprite_image.cpp new file mode 100644 index 0000000000..d5e4a7828e --- /dev/null +++ b/src/mbgl/sprite/sprite_image.cpp @@ -0,0 +1,29 @@ +#include + +#include + +#include + +namespace mbgl { + +SpriteImage::SpriteImage(const uint16_t width_, + const uint16_t height_, + const float pixelRatio_, + std::string&& data_, + bool sdf_) + : width(width_), + height(height_), + pixelRatio(pixelRatio_), + pixelWidth(std::ceil(width * pixelRatio)), + pixelHeight(std::ceil(height * pixelRatio)), + data(std::move(data_)), + sdf(sdf_) { + const size_t size = pixelWidth * pixelHeight * 4; + if (size == 0) { + throw util::SpriteImageException("Sprite image dimensions may not be zero"); + } else if (size != data.size()) { + throw util::SpriteImageException("Sprite image pixel count mismatch"); + } +} + +} // namespace mbgl diff --git a/src/mbgl/sprite/sprite_parser.cpp b/src/mbgl/sprite/sprite_parser.cpp new file mode 100644 index 0000000000..a8ed4f0e12 --- /dev/null +++ b/src/mbgl/sprite/sprite_parser.cpp @@ -0,0 +1,148 @@ +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include + +namespace mbgl { + +SpriteImagePtr createSpriteImage(const util::Image& image, + const uint16_t srcX, + const uint16_t srcY, + const uint16_t srcWidth, + const uint16_t srcHeight, + const double ratio, + const bool sdf) { + // Disallow invalid parameter configurations. + if (srcWidth == 0 || srcHeight == 0 || ratio <= 0 || ratio > 10 || srcWidth > 1024 || + srcHeight > 1024) { + Log::Warning(Event::Sprite, "Can't create sprite with invalid metrics"); + return nullptr; + } + + const uint16_t width = std::ceil(double(srcWidth) / ratio); + const uint16_t dstWidth = std::ceil(width * ratio); + assert(dstWidth >= srcWidth); + const uint16_t height = std::ceil(double(srcHeight) / ratio); + const uint16_t dstHeight = std::ceil(height * ratio); + assert(dstHeight >= srcHeight); + + std::string data(dstWidth * dstHeight * 4, '\0'); + + auto srcData = reinterpret_cast(image.getData()); + auto dstData = reinterpret_cast(const_cast(data.data())); + + const int32_t maxX = std::min(image.getWidth(), uint32_t(srcWidth + srcX)) - srcX; + assert(maxX <= int32_t(image.getWidth())); + const int32_t maxY = std::min(image.getHeight(), uint32_t(srcHeight + srcY)) - srcY; + assert(maxY <= int32_t(image.getHeight())); + + // Copy from the source image into our individual sprite image + for (uint16_t y = 0; y < maxY; ++y) { + const auto dstRow = y * dstWidth; + const auto srcRow = (y + srcY) * image.getWidth() + srcX; + for (uint16_t x = 0; x < maxX; ++x) { + dstData[dstRow + x] = srcData[srcRow + x]; + } + } + + return std::make_unique(width, height, ratio, std::move(data), sdf); +} + +namespace { + +inline uint16_t getUInt16(const rapidjson::Value& value, const char* name, const uint16_t def = 0) { + if (value.HasMember(name)) { + auto& v = value[name]; + if (v.IsUint() && v.GetUint() <= std::numeric_limits::max()) { + return v.GetUint(); + } else { + Log::Warning(Event::Sprite, "Value of '%s' must be an integer between 0 and 65535", + name); + } + } + + return def; +} + +inline double getDouble(const rapidjson::Value& value, const char* name, const double def = 0) { + if (value.HasMember(name)) { + auto& v = value[name]; + if (v.IsNumber()) { + return v.GetDouble(); + } else { + Log::Warning(Event::Sprite, "Value of '%s' must be a number", name); + } + } + + return def; +} + +inline bool getBoolean(const rapidjson::Value& value, const char* name, const bool def = false) { + if (value.HasMember(name)) { + auto& v = value[name]; + if (v.IsBool()) { + return v.GetBool(); + } else { + Log::Warning(Event::Sprite, "Value of '%s' must be a boolean", name); + } + } + + return def; +} + +} // namespace + +SpriteParseResult parseSprite(const std::string& image, const std::string& json) { + using namespace rapidjson; + + Sprites sprites; + + // Parse the sprite image. + const util::Image raster(image); + if (!raster) { + return std::string("Could not parse sprite image"); + } + + Document doc; + doc.Parse<0>(json.c_str()); + + if (doc.HasParseError()) { + std::stringstream message; + message << "Failed to parse JSON: " << rapidjson::GetParseError_En(doc.GetParseError()) << " at offset " << doc.GetErrorOffset(); + return message.str(); + } else if (!doc.IsObject()) { + return std::string("Sprite JSON root must be an object"); + } else { + for (Value::ConstMemberIterator itr = doc.MemberBegin(); itr != doc.MemberEnd(); ++itr) { + const std::string name = { itr->name.GetString(), itr->name.GetStringLength() }; + const Value& value = itr->value; + + if (value.IsObject()) { + const uint16_t x = getUInt16(value, "x", 0); + const uint16_t y = getUInt16(value, "y", 0); + const uint16_t width = getUInt16(value, "width", 0); + const uint16_t height = getUInt16(value, "height", 0); + const double pixelRatio = getDouble(value, "pixelRatio", 1); + const bool sdf = getBoolean(value, "sdf", false); + + auto sprite = createSpriteImage(raster, x, y, width, height, pixelRatio, sdf); + if (sprite) { + sprites.emplace(name, sprite); + } + } + } + } + + return sprites; +} + +} // namespace mbgl diff --git a/src/mbgl/sprite/sprite_parser.hpp b/src/mbgl/sprite/sprite_parser.hpp new file mode 100644 index 0000000000..d385d77aea --- /dev/null +++ b/src/mbgl/sprite/sprite_parser.hpp @@ -0,0 +1,46 @@ +#ifndef MBGL_SPRITE_PARSER +#define MBGL_SPRITE_PARSER + +#include + +#include +#include + +#include +#include +#include + +namespace mbgl { + +namespace util { + +class Image; + +} // namespace util + +class SpriteImage; + +using SpriteImagePtr = std::shared_ptr; + +// Extracts an individual image from a spritesheet from the given location. +SpriteImagePtr createSpriteImage(const util::Image& image, + uint16_t srcX, + uint16_t srcY, + uint16_t srcWidth, + uint16_t srcHeight, + double ratio, + bool sdf); + +using Sprites = std::map; + + +using SpriteParseResult = mapbox::util::variant< + Sprites, // success + std::string>; // error + +// Parses an image and an associated JSON file and returns the sprite objects. +SpriteParseResult parseSprite(const std::string& image, const std::string& json); + +} // namespace mbgl + +#endif diff --git a/src/mbgl/sprite/sprite_store.cpp b/src/mbgl/sprite/sprite_store.cpp new file mode 100644 index 0000000000..fb6833114e --- /dev/null +++ b/src/mbgl/sprite/sprite_store.cpp @@ -0,0 +1,71 @@ +#include + +#include + +namespace mbgl { + +void SpriteStore::setSprite(const std::string& name, std::shared_ptr sprite) { + std::lock_guard lock(mutex); + _setSprite(name, sprite); +} + +void SpriteStore::_setSprite(const std::string& name, + const std::shared_ptr& sprite) { + if (sprite) { + auto it = sprites.find(name); + if (it != sprites.end()) { + // There is already a sprite with that name in our store. + if ((it->second->width != sprite->width || it->second->height != sprite->height)) { + Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", name.c_str()); + return; + } + it->second = sprite; + } else { + sprites.emplace(name, sprite); + } + + // Always add/replace the value in the dirty list. + auto dirty_it = dirty.find(name); + if (dirty_it != dirty.end()) { + dirty_it->second = sprite; + } else { + dirty.emplace(name, sprite); + } + } else if (sprites.erase(name) > 0) { + dirty.emplace(name, nullptr); + } +} + +void SpriteStore::setSprites(const Sprites& newSprites) { + std::lock_guard lock(mutex); + for (const auto& pair : newSprites) { + _setSprite(pair.first, pair.second); + } +} + +void SpriteStore::removeSprite(const std::string& name) { + std::lock_guard lock(mutex); + _setSprite(name); +} + +std::shared_ptr SpriteStore::getSprite(const std::string& name) { + std::lock_guard lock(mutex); + const auto it = sprites.find(name); + if (it != sprites.end()) { + return it->second; + } else { + if (!sprites.empty()) { + Log::Info(Event::Sprite, "Can't find sprite named '%s'", name.c_str()); + } + return nullptr; + } +} + +SpriteStore::Sprites SpriteStore::getDirty() { + Sprites result; + std::lock_guard lock(mutex); + dirty.swap(result); + return result; +} + +} // namespace mbgl diff --git a/src/mbgl/sprite/sprite_store.hpp b/src/mbgl/sprite/sprite_store.hpp new file mode 100644 index 0000000000..ed903f074b --- /dev/null +++ b/src/mbgl/sprite/sprite_store.hpp @@ -0,0 +1,53 @@ +#ifndef MBGL_SPRITE_STORE +#define MBGL_SPRITE_STORE + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +// The SpriteStore object holds Sprite images. +class SpriteStore : private util::noncopyable { + using Sprites = std::map>; + +public: + // Adds/replaces a Sprite image. + void setSprite(const std::string&, std::shared_ptr = nullptr); + + // Adds/replaces mutliple Sprite images. + void setSprites(const Sprites& sprites); + + // Removes a Sprite. + void removeSprite(const std::string&); + + // Obtains a Sprite image. + std::shared_ptr getSprite(const std::string&); + + // Returns Sprite images that changed since the last invocation of this function. + Sprites getDirty(); + +private: + void _setSprite(const std::string&, const std::shared_ptr& = nullptr); + + // Lock for sprites and dirty maps. + std::mutex mutex; + + // Stores all current sprites. + Sprites sprites; + + // Stores all Sprite IDs that changed since the last invocation. + Sprites dirty; +}; + +} // namespace mbgl + +#endif diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 5509447275..acf4752e07 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -1,9 +1,10 @@ #include -#include #include #include #include -#include +#include +#include +#include #include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp index 412107b737..837703e264 100644 --- a/src/mbgl/style/style.hpp +++ b/src/mbgl/style/style.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include -- cgit v1.2.1