Files
MapLibreParticleLab/src/mbgl/text/glyph_atlas.cpp
2016-09-29 10:17:47 -07:00

263 lines
8.3 KiB
C++

#include <mbgl/text/glyph_atlas.hpp>
#include <mbgl/text/glyph_atlas_observer.hpp>
#include <mbgl/text/glyph_pbf.hpp>
#include <mbgl/gl/gl.hpp>
#include <mbgl/gl/context.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/platform/platform.hpp>
#include <cassert>
#include <algorithm>
namespace mbgl {
static GlyphAtlasObserver nullObserver;
GlyphAtlas::GlyphAtlas(uint16_t width_, uint16_t height_, FileSource& fileSource_)
: width(width_),
height(height_),
fileSource(fileSource_),
observer(&nullObserver),
bin(width_, height_),
data(std::make_unique<uint8_t[]>(width_ * height_)),
dirty(true) {
}
GlyphAtlas::~GlyphAtlas() = default;
void GlyphAtlas::requestGlyphRange(const FontStack& fontStack, const GlyphRange& range) {
std::lock_guard<std::mutex> lock(rangesMutex);
auto& rangeSets = ranges[fontStack];
const auto& rangeSetsIt = rangeSets.find(range);
if (rangeSetsIt != rangeSets.end()) {
return;
}
rangeSets.emplace(range,
std::make_unique<GlyphPBF>(this, fontStack, range, observer, fileSource));
}
bool GlyphAtlas::hasGlyphRanges(const FontStack& fontStack, const GlyphRangeSet& glyphRanges) {
if (glyphRanges.empty()) {
return true;
}
std::lock_guard<std::mutex> lock(rangesMutex);
const auto& rangeSets = ranges[fontStack];
bool hasRanges = true;
for (const auto& range : glyphRanges) {
const auto& rangeSetsIt = rangeSets.find(range);
if (rangeSetsIt == rangeSets.end()) {
// Push the request to the MapThread, so we can easly cancel
// if it is still pending when we destroy this object.
workQueue.push(std::bind(&GlyphAtlas::requestGlyphRange, this, fontStack, range));
hasRanges = false;
continue;
}
if (!rangeSetsIt->second->isParsed()) {
hasRanges = false;
}
}
return hasRanges;
}
util::exclusive<GlyphSet> GlyphAtlas::getGlyphSet(const FontStack& fontStack) {
auto lock = std::make_unique<std::lock_guard<std::mutex>>(glyphSetsMutex);
auto it = glyphSets.find(fontStack);
if (it == glyphSets.end()) {
it = glyphSets.emplace(fontStack, std::make_unique<GlyphSet>()).first;
}
// FIXME: We lock all GlyphSets, but what we should
// really do is lock only the one we are returning.
return { it->second.get(), std::move(lock) };
}
void GlyphAtlas::setObserver(GlyphAtlasObserver* observer_) {
observer = observer_;
}
void GlyphAtlas::addGlyphs(uintptr_t tileUID,
const std::u32string& text,
const FontStack& fontStack,
const GlyphSet& glyphSet,
GlyphPositions& face)
{
std::lock_guard<std::mutex> lock(mtx);
const std::map<uint32_t, SDFGlyph>& sdfs = glyphSet.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<uint16_t> rect = addGlyph(tileUID, fontStack, sdf);
face.emplace(chr, Glyph{rect, sdf.metrics});
}
}
Rect<uint16_t> GlyphAtlas::addGlyph(uintptr_t tileUID,
const FontStack& fontStack,
const SDFGlyph& glyph)
{
// Use constant value for now.
const uint8_t buffer = 3;
std::map<uint32_t, GlyphValue>& face = index[fontStack];
auto 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<uint16_t>{ 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<uint16_t> 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<const uint8_t*>(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<std::mutex> lock(mtx);
for (auto& faces : index) {
std::map<uint32_t, GlyphValue>& 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<uint16_t>& 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::Context& context, gl::TextureUnit unit) {
if (dirty) {
const bool first = !texture;
bind(context, unit);
std::lock_guard<std::mutex> lock(mtx);
context.activeTexture = unit;
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;
}
}
void GlyphAtlas::bind(gl::Context& context, gl::TextureUnit unit) {
if (!texture) {
texture = context.createTexture();
context.activeTexture = unit;
context.texture[unit] = *texture;
#if not MBGL_USE_GLES2
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 if (context.texture[unit] != *texture) {
context.activeTexture = unit;
context.texture[unit] = *texture;
}
}
} // namespace mbgl