#include #include #include #include #include #include #include #include #include using namespace mbgl; GlyphAtlas::GlyphAtlas(uint16_t width_, uint16_t height_) : width(width_), height(height_), bin(width_, height_), data(std::make_unique(width_ * height_)), dirty(true) { } GlyphAtlas::~GlyphAtlas() { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); } void GlyphAtlas::addGlyphs(uintptr_t tileUID, const std::u32string& text, const std::string& stackName, const FontStack& fontStack, GlyphPositions& face) { std::lock_guard lock(mtx); const std::map& sdfs = fontStack.getSDFs(); for (uint32_t chr : text) { auto sdf_it = sdfs.find(chr); if (sdf_it == sdfs.end()) { continue; } const SDFGlyph& sdf = sdf_it->second; Rect rect = addGlyph(tileUID, stackName, sdf); face.emplace(chr, Glyph{rect, sdf.metrics}); } } Rect GlyphAtlas::addGlyph(uintptr_t tileUID, const std::string& stackName, const SDFGlyph& glyph) { // Use constant value for now. const uint8_t buffer = 3; std::map& face = index[stackName]; std::map::iterator it = face.find(glyph.id); // The glyph is already in this texture. if (it != face.end()) { GlyphValue& value = it->second; value.ids.insert(tileUID); return value.rect; } // The glyph bitmap has zero width. if (glyph.bitmap.empty()) { return Rect{ 0, 0, 0, 0 }; } uint16_t buffered_width = glyph.metrics.width + buffer * 2; uint16_t buffered_height = glyph.metrics.height + buffer * 2; // Add a 1px border around every image. const uint16_t padding = 1; uint16_t pack_width = buffered_width + 2 * padding; uint16_t pack_height = buffered_height + 2 * padding; // 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. pack_width += (4 - pack_width % 4); pack_height += (4 - pack_height % 4); Rect rect = bin.allocate(pack_width, pack_height); if (rect.w == 0) { Log::Error(Event::OpenGL, "glyph bitmap overflow"); return rect; } assert(rect.x + rect.w <= width); assert(rect.y + rect.h <= height); face.emplace(glyph.id, GlyphValue { rect, tileUID }); // Copy the bitmap const uint8_t* source = reinterpret_cast(glyph.bitmap.data()); for (uint32_t y = 0; y < buffered_height; y++) { uint32_t y1 = width * (rect.y + y + padding) + rect.x + padding; uint32_t y2 = buffered_width * y; for (uint32_t x = 0; x < buffered_width; x++) { data[y1 + x] = source[y2 + x]; } } dirty = true; return rect; } void GlyphAtlas::removeGlyphs(uintptr_t tileUID) { std::lock_guard lock(mtx); for (auto& faces : index) { std::map& face = faces.second; for (auto it = face.begin(); it != face.end(); /* we advance in the body */) { GlyphValue& value = it->second; value.ids.erase(tileUID); if (value.ids.empty()) { const Rect& rect = value.rect; // Clear out the bitmap. uint8_t *target = data.get(); for (uint32_t y = 0; y < rect.h; y++) { uint32_t y1 = width * (rect.y + y) + rect.x; for (uint32_t x = 0; x < rect.w; x++) { target[y1 + x] = 0; } } bin.release(rect); // Make sure to post-increment the iterator: This will return the // current iterator, but will go to the next position before we // erase the element from the map. That way, the iterator stays // valid. face.erase(it++); } else { ++it; } } } } void GlyphAtlas::upload(gl::GLObjectStore& glObjectStore) { if (dirty) { const bool first = !texture; bind(glObjectStore); std::lock_guard lock(mtx); if (first) { MBGL_CHECK_ERROR(glTexImage2D( GL_TEXTURE_2D, // GLenum target 0, // GLint level GL_ALPHA, // GLint internalformat width, // GLsizei width height, // GLsizei height 0, // GLint border GL_ALPHA, // GLenum format GL_UNSIGNED_BYTE, // GLenum type data.get() // const GLvoid* data )); } else { MBGL_CHECK_ERROR(glTexSubImage2D( GL_TEXTURE_2D, // GLenum target 0, // GLint level 0, // GLint xoffset 0, // GLint yoffset width, // GLsizei width height, // GLsizei height GL_ALPHA, // GLenum format GL_UNSIGNED_BYTE, // GLenum type data.get() // const GLvoid* data )); } dirty = false; #if defined(DEBUG) // platform::showDebugImage("Glyph Atlas", reinterpret_cast(data.get()), width, height); #endif } } void GlyphAtlas::bind(gl::GLObjectStore& glObjectStore) { if (!texture) { texture.create(glObjectStore); MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture.getID())); #ifndef GL_ES_VERSION_2_0 MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); #endif MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); 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)); } else { MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture.getID())); } };