Merge branch 'master' of github.com:mapbox/llmr-native into clear-caches

This commit is contained in:
Dane Springmeyer
2014-05-13 12:08:08 -07:00
18 changed files with 715 additions and 618 deletions

View File

@@ -12,6 +12,7 @@
#include <vector>
#include <string>
#include <map>
#include <set>
namespace llmr {
@@ -68,8 +69,8 @@ private:
const int32_t min_zoom;
const int32_t max_zoom;
std::forward_list<Tile> tiles;
std::forward_list<std::weak_ptr<TileData>> tile_data;
std::map<Tile::ID, std::unique_ptr<Tile>> tiles;
std::map<Tile::ID, std::weak_ptr<TileData>> tile_data;
};
}

View File

@@ -127,6 +127,7 @@ struct TextClass {
FunctionProperty size = 12.0f;
FunctionProperty rotate = 0.0f;
FunctionProperty alwaysVisible = false;
FunctionProperty opacity = 1;
};
struct TextProperties {
@@ -138,6 +139,7 @@ struct TextProperties {
float size = 12.0f;
float rotate = 0.0f;
bool alwaysVisible = false;
float opacity = 1.0;
};
struct BackgroundClass {

View File

@@ -11,6 +11,23 @@ template<typename T, typename ...Args>
return ::std::unique_ptr<T>(new T(::std::forward<Args>(args)...));
}
template <typename Container, typename ForwardIterator, typename Predicate>
void erase_if(Container &container, ForwardIterator it, const ForwardIterator end, Predicate pred) {
while (it != end) {
if (pred(*it)) {
container.erase(it++);
} else {
++it;
}
}
}
template <typename Container, typename Predicate>
void erase_if(Container &container, Predicate pred) {
erase_if(container, container.begin(), container.end(), pred);
}
}
#endif

View File

@@ -21,4 +21,4 @@
return YES;
}
@end
@end

View File

@@ -36,6 +36,19 @@ MBXViewController *viewController = nullptr;
#pragma mark - Setup
- (id)init
{
self = [super init];
if (self)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState) name:UIApplicationWillEnterForegroundNotification object:nil];
}
return self;
}
- (void)loadView
{
[super loadView];
@@ -77,11 +90,29 @@ MBXViewController *viewController = nullptr;
// Load settings
settings = new llmr::Settings_NSUserDefaults();
map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
map->setAngle(settings->angle);
map->setDebug(settings->debug);
[self restoreState];
}
- (void)saveState
{
if (map && settings)
{
map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
settings->angle = map->getAngle();
settings->debug = map->getDebug();
settings->save();
}
}
- (void)restoreState
{
if (map && settings) {
settings->load();
map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
map->setAngle(settings->angle);
map->setDebug(settings->debug);
}
}
- (void)setupInteraction
{
@@ -234,16 +265,12 @@ MBXViewController *viewController = nullptr;
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (settings)
{
// Save settings
if (map)
{
map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
settings->angle = map->getAngle();
settings->debug = map->getDebug();
settings->save();
}
[self saveState];
delete settings;
settings = nullptr;
}

View File

@@ -10,7 +10,9 @@
"type": "executable",
"sources": [
"./main.m",
"./MBXAppDelegate.h",
"./MBXAppDelegate.m",
"./MBXViewController.h",
"./MBXViewController.mm",
"../common/settings_nsuserdefaults.hpp",
"../common/settings_nsuserdefaults.mm",

View File

@@ -484,6 +484,16 @@ void Map::renderLayers(const std::vector<LayerDescription>& layers, RenderPass p
}
}
template <typename Styles>
bool is_invisible(const Styles &styles, const LayerDescription &layer_desc) {
auto it = styles.find(layer_desc.name);
if (it == styles.end()) return true;
const auto &properties = it->second;
if (!properties.enabled) return true;
if (properties.opacity <= 0) return true;
return false;
}
void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) {
if (layer_desc.child_layer.size()) {
gl::group group(std::string("group: ") + layer_desc.name);
@@ -501,13 +511,25 @@ void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) {
// Abort early if we can already deduce from the bucket type that
// we're not going to render anything anyway during this pass.
switch (bucket_desc.type) {
case BucketType::Fill:
if (is_invisible(style.computed.fills, layer_desc)) return;
break;
case BucketType::Line:
if (pass == Opaque) return;
if (is_invisible(style.computed.lines, layer_desc)) return;
break;
case BucketType::Point:
if (pass == Opaque) return;
if (is_invisible(style.computed.points, layer_desc)) return;
break;
case BucketType::Text:
if (pass == Opaque) return;
if (is_invisible(style.computed.texts, layer_desc)) return;
break;
case BucketType::Raster:
if (pass == Translucent) return;
if (is_invisible(style.computed.lines, layer_desc)) return;
break;
default:
break;
}

View File

@@ -7,6 +7,7 @@
#include <llmr/util/string.hpp>
#include <llmr/util/texturepool.hpp>
#include <llmr/util/vec.hpp>
#include <llmr/util/std.hpp>
#include <llmr/geometry/glyph_atlas.hpp>
@@ -33,7 +34,8 @@ bool Source::update() {
}
void Source::updateClipIDs(const std::map<Tile::ID, ClipID> &mapping) {
std::for_each(tiles.begin(), tiles.end(), [&mapping](Tile &tile) {
std::for_each(tiles.begin(), tiles.end(), [&mapping](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
Tile &tile = *pair.second;
auto it = mapping.find(tile.id);
if (it != mapping.end()) {
tile.clip = it->second;
@@ -47,7 +49,8 @@ size_t Source::prepareRender(const TransformState &transform) {
if (!enabled) return 0;
size_t masks = 0;
for (Tile& tile : tiles) {
for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
Tile &tile = *pair.second;
gl::group group(util::sprintf<32>("mask %d/%d/%d", tile.id.z, tile.id.y, tile.id.z));
transform.matrixFor(tile.matrix, tile.id);
matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix);
@@ -62,7 +65,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription
if (!enabled) return;
gl::group group(std::string("layer: ") + layer_desc.name);
for (const Tile& tile : tiles) {
for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
Tile &tile = *pair.second;
if (tile.data && tile.data->state == TileData::State::parsed) {
painter.renderTileLayer(tile, layer_desc);
}
@@ -72,7 +76,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription
void Source::finishRender() {
if (!enabled) return;
for (const Tile& tile : tiles) {
for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
Tile &tile = *pair.second;
painter.renderTileDebug(tile);
}
}
@@ -80,14 +85,18 @@ void Source::finishRender() {
std::forward_list<Tile::ID> Source::getIDs() const {
std::forward_list<Tile::ID> ptrs;
std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const Tile &tile) {
std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
Tile &tile = *pair.second;
return tile.id;
});
return ptrs;
}
TileData::State Source::hasTile(const Tile::ID& id) {
for (const Tile& tile : tiles) {
auto it = tiles.find(id);
if (it != tiles.end()) {
Tile &tile = *it->second;
if (tile.id == id && tile.data) {
return tile.data->state;
}
@@ -103,20 +112,17 @@ TileData::State Source::addTile(const Tile::ID& id) {
return state;
}
tiles.emplace_front(id);
Tile& new_tile = tiles.front();
auto pos = tiles.emplace(id, std::make_unique<Tile>(id));
Tile& new_tile = *pos.first->second;
// We couldn't find the tile in the list. Create a new one.
// Try to find the associated TileData object.
const Tile::ID normalized_id = id.normalized();
auto it = std::find_if(tile_data.begin(), tile_data.end(), [normalized_id](const std::weak_ptr<TileData>& tile_data) {
return !tile_data.expired() && tile_data.lock()->id == normalized_id;
});
auto it = tile_data.find(normalized_id);
if (it != tile_data.end()) {
// Create a shared_ptr handle. Note that this might be empty!
new_tile.data = it->lock();
new_tile.data = it->second.lock();
}
if (new_tile.data && new_tile.data->state == TileData::State::obsolete) {
@@ -138,7 +144,7 @@ TileData::State Source::addTile(const Tile::ID& id) {
}
new_tile.data->request();
tile_data.push_front(new_tile.data);
tile_data.emplace(new_tile.data->id, new_tile.data);
}
return new_tile.data->state;
@@ -251,31 +257,26 @@ bool Source::updateTiles() {
// Remove tiles that we definitely don't need, i.e. tiles that are not on
// the required list.
std::forward_list<Tile::ID> retain_data;
tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) {
std::set<Tile::ID> retain_data;
std::erase_if(tiles, [&retain, &retain_data, &changed](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
Tile &tile = *pair.second;
bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end();
if (obsolete) {
changed = true;
} else {
retain_data.push_front(tile.data->id);
retain_data.insert(tile.data->id);
}
return obsolete;
});
// Sort tiles by zoom level, front to back.
// We're painting front-to-back, so we want to draw more detailed tiles first
// before filling in other parts with lower zoom levels.
tiles.sort([](const Tile & a, const Tile & b) {
return a.id.z < b.id.z;
});
// Remove all the expired pointers from the list.
tile_data.remove_if([&retain_data](const std::weak_ptr<TileData>& tile_data) {
const std::shared_ptr<TileData> tile = tile_data.lock();
// Remove all the expired pointers from the set.
std::erase_if(tile_data, [&retain_data](std::pair<const Tile::ID, std::weak_ptr<TileData>> &pair) {
const std::shared_ptr<TileData> tile = pair.second.lock();
if (!tile) {
return true;
}
bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end();
bool obsolete = retain_data.find(tile->id) == retain_data.end();
if (obsolete) {
tile->cancel();
return true;

View File

@@ -1,29 +1,16 @@
#include <cassert>
#include <algorithm>
#include <llmr/renderer/painter.hpp>
#include <llmr/util/std.hpp>
#include <llmr/renderer/fill_bucket.hpp>
#include <llmr/renderer/line_bucket.hpp>
#include <llmr/renderer/point_bucket.hpp>
#include <llmr/renderer/text_bucket.hpp>
#include <llmr/renderer/raster_bucket.hpp>
#include <llmr/map/map.hpp>
#include <llmr/map/transform.hpp>
#include <llmr/geometry/debug_font_buffer.hpp>
#include <llmr/geometry/glyph_atlas.hpp>
#include <llmr/platform/gl.hpp>
#include <llmr/style/style.hpp>
#include <llmr/style/sprite.hpp>
#include <llmr/util/raster.hpp>
#include <llmr/util/std.hpp>
#include <llmr/util/string.hpp>
#include <llmr/util/timer.hpp>
#include <llmr/util/time.hpp>
#include <llmr/util/clip_ids.hpp>
#include <cassert>
#include <algorithm>
using namespace llmr;
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
@@ -130,42 +117,6 @@ void Painter::changeMatrix() {
matrix::multiply(nativeMatrix, projMatrix, nativeMatrix);
}
void Painter::prepareClippingMask() {
gl::start_group("clipping masks");
useProgram(plainShader->program);
glDisable(GL_DEPTH_TEST);
depthMask(false);
glDepthRange(1.0f, 1.0f);
coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
}
void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) {
plainShader->setMatrix(matrix);
GLint id = clip.mask.to_ulong();
GLuint mask = clipMask[clip.length];
glStencilFunc(GL_ALWAYS, id, mask);
glStencilMask(0xFF);
Color background = map.getStyle().computed.background.color;
background[0] *= map.getStyle().computed.background.opacity;
background[1] *= map.getStyle().computed.background.opacity;
background[2] *= map.getStyle().computed.background.opacity;
background[3] *= map.getStyle().computed.background.opacity;
plainShader->setColor(background);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
}
void Painter::finishClippingMask() {
glEnable(GL_DEPTH_TEST);
depthMask(true);
glStencilMask(0x0);
gl::end_group();
}
void Painter::clear() {
gl::group group("clear");
glStencilMask(0xFF);
@@ -219,16 +170,6 @@ void Painter::renderTileLayer(const Tile& tile, const LayerDescription &layer_de
}
}
void Painter::renderTileDebug(const Tile& tile) {
gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z));
assert(tile.data);
if (debug) {
prepareTile(tile);
renderDebugText(tile.data->debugBucket);
renderDebugFrame();
}
}
void Painter::translateLayer(std::array<float, 2> translation, bool reverse) {
if (translation[0] || translation[1]) {
if (reverse) {
@@ -243,483 +184,6 @@ void Painter::translateLayer(std::array<float, 2> translation, bool reverse) {
}
}
void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
if (pass == Translucent) return;
auto raster_properties = map.getStyle().computed.rasters;
auto raster_properties_it = raster_properties.find(layer_name);
if (raster_properties_it == raster_properties.end()) return;
const RasterProperties& properties = raster_properties_it->second;
if (!properties.enabled) return;
depthMask(false);
useProgram(rasterShader->program);
rasterShader->setMatrix(matrix);
rasterShader->setOpacity(1);
// rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity);
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray);
depthMask(true);
}
void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) {
// Abort early.
if (!bucket.hasData()) return;
auto fill_properties = map.getStyle().computed.fills;
auto fill_properties_it = fill_properties.find(layer_name);
if (fill_properties_it == fill_properties.end()) return;
const FillProperties& properties = fill_properties_it->second;
if (!properties.enabled) return;
Color fill_color = properties.fill_color;
fill_color[0] *= properties.opacity;
fill_color[1] *= properties.opacity;
fill_color[2] *= properties.opacity;
fill_color[3] *= properties.opacity;
Color stroke_color = properties.stroke_color;
stroke_color[0] *= properties.opacity;
stroke_color[1] *= properties.opacity;
stroke_color[2] *= properties.opacity;
stroke_color[3] *= properties.opacity;
bool outline = properties.antialias && properties.stroke_color != properties.fill_color;
bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color;
if (fringeline) {
outline = true;
stroke_color = fill_color;
}
translateLayer(properties.translate);
// Because we're drawing top-to-bottom, and we update the stencil mask
// below, we have to draw the outline first (!)
if (outline && pass == Translucent) {
useProgram(outlineShader->program);
outlineShader->setMatrix(matrix);
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
outlineShader->setColor(stroke_color);
// Draw the entire line
outlineShader->setWorld({{
static_cast<float>(map.getState().getFramebufferWidth()),
static_cast<float>(map.getState().getFramebufferHeight())
}});
glDepthRange(strata, 1.0f);
bucket.drawVertices(*outlineShader);
} else if (fringeline) {
// // We're only drawing to the first seven bits (== support a maximum of
// // 127 overlapping polygons in one place before we get rendering errors).
// glStencilMask(0x3F);
// glClear(GL_STENCIL_BUFFER_BIT);
// // Draw front facing triangles. Wherever the 0x80 bit is 1, we are
// // increasing the lower 7 bits by one if the triangle is a front-facing
// // triangle. This means that all visible polygons should be in CCW
// // orientation, while all holes (see below) are in CW orientation.
// glStencilFunc(GL_EQUAL, 0x80, 0x80);
// // When we do a nonzero fill, we count the number of times a pixel is
// // covered by a counterclockwise polygon, and subtract the number of
// // times it is "uncovered" by a clockwise polygon.
// glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
}
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
auto &sprite = map.getStyle().sprite;
if (properties.image.size() && sprite && sprite->isLoaded()) {
// Draw texture fill
ImagePosition imagePos = sprite->getPosition(properties.image, true);
float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z);
float mix = fmod(map.getState().getZoom(), 1.0);
std::array<float, 2> imageSize = {{
imagePos.size.x * factor,
imagePos.size.y *factor
}
};
std::array<float, 2> offset = {{
(float)fmod(id.x * 4096, imageSize[0]),
(float)fmod(id.y * 4096, imageSize[1])
}
};
useProgram(patternShader->program);
patternShader->setMatrix(matrix);
patternShader->setOffset(offset);
patternShader->setPatternSize(imageSize);
patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
patternShader->setColor(fill_color);
patternShader->setMix(mix);
sprite->raster.bind(true);
// Draw the actual triangles into the color & stencil buffer.
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawElements(*patternShader);
} else {
// Only draw the fill when it's either opaque and we're drawing opaque
// fragments or when it's translucent and we're drawing translucent
// fragments
// Draw filling rectangle.
useProgram(plainShader->program);
plainShader->setMatrix(matrix);
plainShader->setColor(fill_color);
// Draw the actual triangles into the color & stencil buffer.
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawElements(*plainShader);
}
}
// Because we're drawing top-to-bottom, and we update the stencil mask
// below, we have to draw the outline first (!)
if (fringeline && pass == Translucent) {
useProgram(outlineShader->program);
outlineShader->setMatrix(matrix);
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
outlineShader->setColor(fill_color);
// Draw the entire line
outlineShader->setWorld({{
static_cast<float>(map.getState().getFramebufferWidth()),
static_cast<float>(map.getState().getFramebufferHeight())
}});
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawVertices(*outlineShader);
}
translateLayer(properties.translate, true);
}
void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
// Abort early.
if (pass == Opaque) return;
if (!bucket.hasData()) return;
auto line_properties = map.getStyle().computed.lines;
auto line_properties_it = line_properties.find(layer_name);
if (line_properties_it == line_properties.end()) return;
const LineProperties& properties = line_properties_it->second;
if (!properties.enabled) return;
float width = properties.width;
float offset = properties.offset / 2;
// These are the radii of the line. We are limiting it to 16, which will result
// in a point size of 64 on retina.
float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f);
float outset = fmin(offset + width / 2 + 0.5, 16.0f);
Color color = properties.color;
color[0] *= properties.opacity;
color[1] *= properties.opacity;
color[2] *= properties.opacity;
color[3] *= properties.opacity;
float dash_length = properties.dash_array[0];
float dash_gap = properties.dash_array[1];
translateLayer(properties.translate);
glDepthRange(strata, 1.0f);
// We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway.
if (bucket.hasPoints() && outset > 1.0f) {
useProgram(linejoinShader->program);
linejoinShader->setMatrix(matrix);
linejoinShader->setColor(color);
linejoinShader->setWorld({{
map.getState().getFramebufferWidth() * 0.5f,
map.getState().getFramebufferHeight() * 0.5f
}
});
linejoinShader->setLineWidth({{
((outset - 0.25f) * map.getState().getPixelRatio()),
((inset - 0.25f) * map.getState().getPixelRatio())
}
});
float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0);
#if defined(GL_ES_VERSION_2_0)
linejoinShader->setSize(pointSize);
#else
glPointSize(pointSize);
#endif
bucket.drawPoints(*linejoinShader);
}
// var imagePos = properties.image && imageSprite.getPosition(properties.image);
bool imagePos = false;
if (imagePos) {
// var factor = 8 / Math.pow(2, painter.transform.zoom - params.z);
// imageSprite.bind(gl, true);
// //factor = Math.pow(2, 4 - painter.transform.zoom + params.z);
// gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix);
// shader = painter.linepatternShader;
// glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]);
// glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl);
// glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br);
// glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0);
} else {
useProgram(lineShader->program);
lineShader->setMatrix(matrix);
lineShader->setExtrudeMatrix(extrudeMatrix);
lineShader->setDashArray({{ dash_length, dash_gap }});
lineShader->setLineWidth({{ outset, inset }});
lineShader->setRatio(map.getState().getPixelRatio());
lineShader->setColor(color);
bucket.drawLines(*lineShader);
}
translateLayer(properties.translate, true);
}
void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) {
// Abort early.
if (!bucket.hasData()) return;
if (pass == Opaque) return;
auto point_properties = map.getStyle().computed.points;
auto point_properties_it = point_properties.find(layer_name);
if (point_properties_it == point_properties.end()) return;
const PointProperties& properties = point_properties_it->second;
if (!properties.enabled) return;
translateLayer(properties.translate);
Color color = properties.color;
color[0] *= properties.opacity;
color[1] *= properties.opacity;
color[2] *= properties.opacity;
color[3] *= properties.opacity;
auto &sprite = map.getStyle().sprite;
ImagePosition imagePos;
if (properties.image.length() && sprite && sprite->isLoaded()) {
std::string sized_image = properties.image;
if (properties.size) {
sized_image.append("-");
sized_image.append(std::to_string(static_cast<int>(std::round(properties.size))));
}
imagePos = sprite->getPosition(sized_image, false);
}
if (!imagePos.size) {
useProgram(dotShader->program);
dotShader->setMatrix(matrix);
dotShader->setColor(color);
const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio();
#if defined(GL_ES_VERSION_2_0)
dotShader->setSize(pointSize);
#else
glPointSize(pointSize);
glEnable(GL_POINT_SPRITE);
#endif
dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize);
glDepthRange(strata, 1.0f);
bucket.drawPoints(*dotShader);
} else {
useProgram(pointShader->program);
pointShader->setMatrix(matrix);
pointShader->setColor(color);
pointShader->setImage(0);
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
sprite->raster.bind(map.getState().isChanging());
const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio();
#if defined(GL_ES_VERSION_2_0)
pointShader->setSize(pointSize);
#else
glPointSize(pointSize);
glEnable(GL_POINT_SPRITE);
#endif
glDepthRange(strata, 1.0f);
bucket.drawPoints(*pointShader);
}
translateLayer(properties.translate, true);
}
void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
// Abort early.
if (pass == Opaque) return;
if (!bucket.hasData()) return;
auto text_properties = map.getStyle().computed.texts;
auto text_properties_it = text_properties.find(layer_name);
if (text_properties_it == text_properties.end()) return;
const TextProperties& properties = text_properties_it->second;
if (!properties.enabled) return;
translateLayer(properties.translate);
mat4 exMatrix;
matrix::copy(exMatrix, projMatrix);
if (bucket.geom_desc.path == TextPathType::Curve) {
matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
}
const float rotate = properties.rotate;
if (rotate != 0.0f) {
matrix::rotate_z(exMatrix, exMatrix, rotate);
}
// If layerStyle.size > bucket.info.fontSize then labels may collide
float fontSize = fmin(properties.size, bucket.geom_desc.font_size);
matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);
useProgram(textShader->program);
textShader->setMatrix(matrix);
textShader->setExtrudeMatrix(exMatrix);
map.getGlyphAtlas().bind();
textShader->setTextureSize({{static_cast<float>(map.getGlyphAtlas().width),
static_cast<float>(map.getGlyphAtlas().height)}});
textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio());
// Convert the -pi..pi to an int8 range.
float angle = round((map.getState().getAngle() + rotate) / M_PI * 128);
// adjust min/max zooms for variable font sies
float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2);
textShader->setAngle((int32_t)(angle + 256) % 256);
textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0);
textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
// Label fading
const time duration = 300_milliseconds;
const time currentTime = util::now();
std::deque<FrameSnapshot> &history = frameHistory.history;
// Remove frames until only one is outside the duration, or until there are only three
while (history.size() > 3 && history[1].timestamp + duration < currentTime) {
history.pop_front();
}
if (history[1].timestamp + duration < currentTime) {
history[0].z = history[1].z;
}
size_t frameLen = history.size();
assert("there should never be less than three frames in the history" && frameLen >= 3);
// Find the range of zoom levels we want to fade between
float startingZ = history.front().z;
const FrameSnapshot lastFrame = history.back();
float endingZ = lastFrame.z;
float lowZ = fmin(startingZ, endingZ);
float highZ = fmax(startingZ, endingZ);
// Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration
float zoomDiff = endingZ - history[1].z,
timeDiff = lastFrame.timestamp - history[1].timestamp;
if (timeDiff > duration) timeDiff = 1;
float fadedist = zoomDiff / (timeDiff / duration);
if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n");
// At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
// bump is how much farther it would have been if it had continued zooming at the same rate
float bump = (currentTime - lastFrame.timestamp) / duration * fadedist;
textShader->setFadeDist(fadedist * 10);
textShader->setMinFadeZoom(floor(lowZ * 10));
textShader->setMaxFadeZoom(floor(highZ * 10));
textShader->setFadeZoom((map.getState().getZoom() + bump) * 10);
// We're drawing in the translucent pass which is bottom-to-top, so we need
// to draw the halo first.
if (properties.halo[3] > 0.0f) {
textShader->setColor(properties.halo);
textShader->setBuffer(properties.haloRadius);
glDepthRange(strata, 1.0f);
bucket.drawGlyphs(*textShader);
}
// Then, we draw the text over the halo
textShader->setColor(properties.color);
textShader->setBuffer((256.0f - 64.0f) / 256.0f);
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawGlyphs(*textShader);
translateLayer(properties.translate, true);
}
void Painter::renderDebugText(DebugBucket& bucket) {
gl::group group("debug text");
glDisable(GL_DEPTH_TEST);
useProgram(plainShader->program);
plainShader->setMatrix(matrix);
// Draw white outline
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
lineWidth(4.0f * map.getState().getPixelRatio());
bucket.drawLines(*plainShader);
#ifndef GL_ES_VERSION_2_0
// Draw line "end caps"
glPointSize(2);
bucket.drawPoints(*plainShader);
#endif
// Draw black text.
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
lineWidth(2.0f * map.getState().getPixelRatio());
bucket.drawLines(*plainShader);
glEnable(GL_DEPTH_TEST);
}
void Painter::renderDebugFrame() {
gl::group group("debug frame");
// Disable depth test and don't count this towards the depth buffer,
// but *don't* disable stencil test, as we want to clip the red tile border
// to the tile viewport.
glDisable(GL_DEPTH_TEST);
useProgram(plainShader->program);
plainShader->setMatrix(matrix);
// draw tile outline
tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f);
lineWidth(4.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index());
glEnable(GL_DEPTH_TEST);
}
void Painter::renderMatte() {
gl::group group("matte");
glDisable(GL_DEPTH_TEST);
@@ -738,38 +202,3 @@ void Painter::renderMatte() {
glEnable(GL_DEPTH_TEST);
}
void Painter::renderDebugText(const std::vector<std::string> &strings) {
gl::group group("debug text");
glDisable(GL_DEPTH_TEST);
glStencilFunc(GL_ALWAYS, 0xFF, 0xFF);
useProgram(plainShader->program);
plainShader->setMatrix(nativeMatrix);
DebugFontBuffer debugFontBuffer;
int line = 25;
for (const std::string &str : strings) {
debugFontBuffer.addText(str.c_str(), 10, line, 0.75);
line += 20;
}
// draw debug info
VertexArrayObject debugFontArray;
debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0));
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
lineWidth(4.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
#ifndef GL_ES_VERSION_2_0
glPointSize(2);
glDrawArrays(GL_POINTS, 0, (GLsizei)debugFontBuffer.index());
#endif
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
lineWidth(2.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
glEnable(GL_DEPTH_TEST);
}

View File

@@ -0,0 +1,42 @@
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/fill_bucket.hpp>
#include <llmr/map/map.hpp>
#include <llmr/util/clip_ids.hpp>
using namespace llmr;
void Painter::prepareClippingMask() {
gl::start_group("clipping masks");
useProgram(plainShader->program);
glDisable(GL_DEPTH_TEST);
depthMask(false);
glDepthRange(1.0f, 1.0f);
coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
}
void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) {
plainShader->setMatrix(matrix);
GLint id = clip.mask.to_ulong();
GLuint mask = clipMask[clip.length];
glStencilFunc(GL_ALWAYS, id, mask);
glStencilMask(0xFF);
Color background = map.getStyle().computed.background.color;
background[0] *= map.getStyle().computed.background.opacity;
background[1] *= map.getStyle().computed.background.opacity;
background[2] *= map.getStyle().computed.background.opacity;
background[3] *= map.getStyle().computed.background.opacity;
plainShader->setColor(background);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
}
void Painter::finishClippingMask() {
glEnable(GL_DEPTH_TEST);
depthMask(true);
glStencilMask(0x0);
gl::end_group();
}

View File

@@ -0,0 +1,97 @@
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/debug_bucket.hpp>
#include <llmr/map/map.hpp>
#include <llmr/util/string.hpp>
using namespace llmr;
void Painter::renderTileDebug(const Tile& tile) {
gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z));
assert(tile.data);
if (debug) {
prepareTile(tile);
renderDebugText(tile.data->debugBucket);
renderDebugFrame();
}
}
void Painter::renderDebugText(DebugBucket& bucket) {
gl::group group("debug text");
glDisable(GL_DEPTH_TEST);
useProgram(plainShader->program);
plainShader->setMatrix(matrix);
// Draw white outline
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
lineWidth(4.0f * map.getState().getPixelRatio());
bucket.drawLines(*plainShader);
#ifndef GL_ES_VERSION_2_0
// Draw line "end caps"
glPointSize(2);
bucket.drawPoints(*plainShader);
#endif
// Draw black text.
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
lineWidth(2.0f * map.getState().getPixelRatio());
bucket.drawLines(*plainShader);
glEnable(GL_DEPTH_TEST);
}
void Painter::renderDebugFrame() {
gl::group group("debug frame");
// Disable depth test and don't count this towards the depth buffer,
// but *don't* disable stencil test, as we want to clip the red tile border
// to the tile viewport.
glDisable(GL_DEPTH_TEST);
useProgram(plainShader->program);
plainShader->setMatrix(matrix);
// draw tile outline
tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f);
lineWidth(4.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index());
glEnable(GL_DEPTH_TEST);
}
void Painter::renderDebugText(const std::vector<std::string> &strings) {
gl::group group("debug text");
glDisable(GL_DEPTH_TEST);
glStencilFunc(GL_ALWAYS, 0xFF, 0xFF);
useProgram(plainShader->program);
plainShader->setMatrix(nativeMatrix);
DebugFontBuffer debugFontBuffer;
int line = 25;
for (const std::string &str : strings) {
debugFontBuffer.addText(str.c_str(), 10, line, 0.75);
line += 20;
}
// draw debug info
VertexArrayObject debugFontArray;
debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0));
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
lineWidth(4.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
#ifndef GL_ES_VERSION_2_0
glPointSize(2);
glDrawArrays(GL_POINTS, 0, (GLsizei)debugFontBuffer.index());
#endif
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
lineWidth(2.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
glEnable(GL_DEPTH_TEST);
}

View File

@@ -0,0 +1,143 @@
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/fill_bucket.hpp>
#include <llmr/map/map.hpp>
#include <llmr/style/sprite.hpp>
using namespace llmr;
void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) {
// Abort early.
if (!bucket.hasData()) return;
auto fill_properties = map.getStyle().computed.fills;
auto fill_properties_it = fill_properties.find(layer_name);
if (fill_properties_it == fill_properties.end()) return;
const FillProperties& properties = fill_properties_it->second;
if (!properties.enabled) return;
Color fill_color = properties.fill_color;
fill_color[0] *= properties.opacity;
fill_color[1] *= properties.opacity;
fill_color[2] *= properties.opacity;
fill_color[3] *= properties.opacity;
Color stroke_color = properties.stroke_color;
stroke_color[0] *= properties.opacity;
stroke_color[1] *= properties.opacity;
stroke_color[2] *= properties.opacity;
stroke_color[3] *= properties.opacity;
bool outline = properties.antialias && properties.stroke_color != properties.fill_color;
bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color;
if (fringeline) {
outline = true;
stroke_color = fill_color;
}
translateLayer(properties.translate);
// Because we're drawing top-to-bottom, and we update the stencil mask
// below, we have to draw the outline first (!)
if (outline && pass == Translucent) {
useProgram(outlineShader->program);
outlineShader->setMatrix(matrix);
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
outlineShader->setColor(stroke_color);
// Draw the entire line
outlineShader->setWorld({{
static_cast<float>(map.getState().getFramebufferWidth()),
static_cast<float>(map.getState().getFramebufferHeight())
}});
glDepthRange(strata, 1.0f);
bucket.drawVertices(*outlineShader);
} else if (fringeline) {
// // We're only drawing to the first seven bits (== support a maximum of
// // 127 overlapping polygons in one place before we get rendering errors).
// glStencilMask(0x3F);
// glClear(GL_STENCIL_BUFFER_BIT);
// // Draw front facing triangles. Wherever the 0x80 bit is 1, we are
// // increasing the lower 7 bits by one if the triangle is a front-facing
// // triangle. This means that all visible polygons should be in CCW
// // orientation, while all holes (see below) are in CW orientation.
// glStencilFunc(GL_EQUAL, 0x80, 0x80);
// // When we do a nonzero fill, we count the number of times a pixel is
// // covered by a counterclockwise polygon, and subtract the number of
// // times it is "uncovered" by a clockwise polygon.
// glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
}
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
auto &sprite = map.getStyle().sprite;
if (properties.image.size() && sprite && sprite->isLoaded()) {
// Draw texture fill
ImagePosition imagePos = sprite->getPosition(properties.image, true);
float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z);
float mix = fmod(map.getState().getZoom(), 1.0);
std::array<float, 2> imageSize = {{
imagePos.size.x * factor,
imagePos.size.y *factor
}
};
std::array<float, 2> offset = {{
(float)fmod(id.x * 4096, imageSize[0]),
(float)fmod(id.y * 4096, imageSize[1])
}
};
useProgram(patternShader->program);
patternShader->setMatrix(matrix);
patternShader->setOffset(offset);
patternShader->setPatternSize(imageSize);
patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
patternShader->setColor(fill_color);
patternShader->setMix(mix);
sprite->raster.bind(true);
// Draw the actual triangles into the color & stencil buffer.
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawElements(*patternShader);
} else {
// Only draw the fill when it's either opaque and we're drawing opaque
// fragments or when it's translucent and we're drawing translucent
// fragments
// Draw filling rectangle.
useProgram(plainShader->program);
plainShader->setMatrix(matrix);
plainShader->setColor(fill_color);
// Draw the actual triangles into the color & stencil buffer.
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawElements(*plainShader);
}
}
// Because we're drawing top-to-bottom, and we update the stencil mask
// below, we have to draw the outline first (!)
if (fringeline && pass == Translucent) {
useProgram(outlineShader->program);
outlineShader->setMatrix(matrix);
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
outlineShader->setColor(fill_color);
// Draw the entire line
outlineShader->setWorld({{
static_cast<float>(map.getState().getFramebufferWidth()),
static_cast<float>(map.getState().getFramebufferHeight())
}});
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawVertices(*outlineShader);
}
translateLayer(properties.translate, true);
}

View File

@@ -0,0 +1,92 @@
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/line_bucket.hpp>
#include <llmr/map/map.hpp>
using namespace llmr;
void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
// Abort early.
if (pass == Opaque) return;
if (!bucket.hasData()) return;
auto line_properties = map.getStyle().computed.lines;
auto line_properties_it = line_properties.find(layer_name);
if (line_properties_it == line_properties.end()) return;
const LineProperties& properties = line_properties_it->second;
if (!properties.enabled) return;
float width = properties.width;
float offset = properties.offset / 2;
// These are the radii of the line. We are limiting it to 16, which will result
// in a point size of 64 on retina.
float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f);
float outset = fmin(offset + width / 2 + 0.5, 16.0f);
Color color = properties.color;
color[0] *= properties.opacity;
color[1] *= properties.opacity;
color[2] *= properties.opacity;
color[3] *= properties.opacity;
float dash_length = properties.dash_array[0];
float dash_gap = properties.dash_array[1];
translateLayer(properties.translate);
glDepthRange(strata, 1.0f);
// We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway.
if (bucket.hasPoints() && outset > 1.0f) {
useProgram(linejoinShader->program);
linejoinShader->setMatrix(matrix);
linejoinShader->setColor(color);
linejoinShader->setWorld({{
map.getState().getFramebufferWidth() * 0.5f,
map.getState().getFramebufferHeight() * 0.5f
}
});
linejoinShader->setLineWidth({{
((outset - 0.25f) * map.getState().getPixelRatio()),
((inset - 0.25f) * map.getState().getPixelRatio())
}
});
float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0);
#if defined(GL_ES_VERSION_2_0)
linejoinShader->setSize(pointSize);
#else
glPointSize(pointSize);
#endif
bucket.drawPoints(*linejoinShader);
}
// var imagePos = properties.image && imageSprite.getPosition(properties.image);
bool imagePos = false;
if (imagePos) {
// var factor = 8 / Math.pow(2, painter.transform.zoom - params.z);
// imageSprite.bind(gl, true);
// //factor = Math.pow(2, 4 - painter.transform.zoom + params.z);
// gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix);
// shader = painter.linepatternShader;
// glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]);
// glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl);
// glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br);
// glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0);
} else {
useProgram(lineShader->program);
lineShader->setMatrix(matrix);
lineShader->setExtrudeMatrix(extrudeMatrix);
lineShader->setDashArray({{ dash_length, dash_gap }});
lineShader->setLineWidth({{ outset, inset }});
lineShader->setRatio(map.getState().getPixelRatio());
lineShader->setColor(color);
bucket.drawLines(*lineShader);
}
translateLayer(properties.translate, true);
}

View File

@@ -0,0 +1,80 @@
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/point_bucket.hpp>
#include <llmr/map/map.hpp>
#include <llmr/style/sprite.hpp>
using namespace llmr;
void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) {
// Abort early.
if (!bucket.hasData()) return;
if (pass == Opaque) return;
auto point_properties = map.getStyle().computed.points;
auto point_properties_it = point_properties.find(layer_name);
if (point_properties_it == point_properties.end()) return;
const PointProperties& properties = point_properties_it->second;
if (!properties.enabled) return;
translateLayer(properties.translate);
Color color = properties.color;
color[0] *= properties.opacity;
color[1] *= properties.opacity;
color[2] *= properties.opacity;
color[3] *= properties.opacity;
auto &sprite = map.getStyle().sprite;
ImagePosition imagePos;
if (properties.image.length() && sprite && sprite->isLoaded()) {
std::string sized_image = properties.image;
if (properties.size) {
sized_image.append("-");
sized_image.append(std::to_string(static_cast<int>(std::round(properties.size))));
}
imagePos = sprite->getPosition(sized_image, false);
}
if (!imagePos.size) {
useProgram(dotShader->program);
dotShader->setMatrix(matrix);
dotShader->setColor(color);
const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio();
#if defined(GL_ES_VERSION_2_0)
dotShader->setSize(pointSize);
#else
glPointSize(pointSize);
glEnable(GL_POINT_SPRITE);
#endif
dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize);
glDepthRange(strata, 1.0f);
bucket.drawPoints(*dotShader);
} else {
useProgram(pointShader->program);
pointShader->setMatrix(matrix);
pointShader->setColor(color);
pointShader->setImage(0);
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
sprite->raster.bind(map.getState().isChanging());
const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio();
#if defined(GL_ES_VERSION_2_0)
pointShader->setSize(pointSize);
#else
glPointSize(pointSize);
glEnable(GL_POINT_SPRITE);
#endif
glDepthRange(strata, 1.0f);
bucket.drawPoints(*pointShader);
}
translateLayer(properties.translate, true);
}

View File

@@ -0,0 +1,28 @@
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/raster_bucket.hpp>
#include <llmr/map/map.hpp>
using namespace llmr;
void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
if (pass == Translucent) return;
auto raster_properties = map.getStyle().computed.rasters;
auto raster_properties_it = raster_properties.find(layer_name);
if (raster_properties_it == raster_properties.end()) return;
const RasterProperties& properties = raster_properties_it->second;
if (!properties.enabled) return;
depthMask(false);
useProgram(rasterShader->program);
rasterShader->setMatrix(matrix);
rasterShader->setOpacity(1);
// rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity);
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray);
depthMask(true);
}

View File

@@ -0,0 +1,114 @@
#include <llmr/renderer/painter.hpp>
#include <llmr/renderer/text_bucket.hpp>
#include <llmr/map/map.hpp>
using namespace llmr;
void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
// Abort early.
if (pass == Opaque) return;
if (!bucket.hasData()) return;
auto text_properties = map.getStyle().computed.texts;
auto text_properties_it = text_properties.find(layer_name);
if (text_properties_it == text_properties.end()) return;
const TextProperties& properties = text_properties_it->second;
if (!properties.enabled) return;
translateLayer(properties.translate);
mat4 exMatrix;
matrix::copy(exMatrix, projMatrix);
if (bucket.geom_desc.path == TextPathType::Curve) {
matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
}
const float rotate = properties.rotate;
if (rotate != 0.0f) {
matrix::rotate_z(exMatrix, exMatrix, rotate);
}
// If layerStyle.size > bucket.info.fontSize then labels may collide
float fontSize = fmin(properties.size, bucket.geom_desc.font_size);
matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);
useProgram(textShader->program);
textShader->setMatrix(matrix);
textShader->setExtrudeMatrix(exMatrix);
map.getGlyphAtlas().bind();
textShader->setTextureSize({{static_cast<float>(map.getGlyphAtlas().width),
static_cast<float>(map.getGlyphAtlas().height)}});
textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio());
// Convert the -pi..pi to an int8 range.
float angle = round((map.getState().getAngle() + rotate) / M_PI * 128);
// adjust min/max zooms for variable font sies
float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2);
textShader->setAngle((int32_t)(angle + 256) % 256);
textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0);
textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
// Label fading
const time duration = 300_milliseconds;
const time currentTime = util::now();
std::deque<FrameSnapshot> &history = frameHistory.history;
// Remove frames until only one is outside the duration, or until there are only three
while (history.size() > 3 && history[1].timestamp + duration < currentTime) {
history.pop_front();
}
if (history[1].timestamp + duration < currentTime) {
history[0].z = history[1].z;
}
size_t frameLen = history.size();
assert("there should never be less than three frames in the history" && frameLen >= 3);
// Find the range of zoom levels we want to fade between
float startingZ = history.front().z;
const FrameSnapshot lastFrame = history.back();
float endingZ = lastFrame.z;
float lowZ = fmin(startingZ, endingZ);
float highZ = fmax(startingZ, endingZ);
// Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration
float zoomDiff = endingZ - history[1].z,
timeDiff = lastFrame.timestamp - history[1].timestamp;
if (timeDiff > duration) timeDiff = 1;
float fadedist = zoomDiff / (timeDiff / duration);
if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n");
// At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
// bump is how much farther it would have been if it had continued zooming at the same rate
float bump = (currentTime - lastFrame.timestamp) / duration * fadedist;
textShader->setFadeDist(fadedist * 10);
textShader->setMinFadeZoom(floor(lowZ * 10));
textShader->setMaxFadeZoom(floor(highZ * 10));
textShader->setFadeZoom((map.getState().getZoom() + bump) * 10);
// We're drawing in the translucent pass which is bottom-to-top, so we need
// to draw the halo first.
if (properties.halo[3] > 0.0f) {
textShader->setColor(properties.halo);
textShader->setBuffer(properties.haloRadius);
glDepthRange(strata, 1.0f);
bucket.drawGlyphs(*textShader);
}
// Then, we draw the text over the halo
textShader->setColor(properties.color);
textShader->setBuffer((256.0f - 64.0f) / 256.0f);
glDepthRange(strata + strata_epsilon, 1.0f);
bucket.drawGlyphs(*textShader);
translateLayer(properties.translate, true);
}

View File

@@ -4,7 +4,6 @@
#include <llmr/geometry/geometry.hpp>
#include <llmr/renderer/painter.hpp>
#include <llmr/shader/text_shader.hpp>
#include <llmr/style/style.hpp>
#include <llmr/map/vector_tile.hpp>
#include <llmr/text/placement.hpp>

View File

@@ -103,6 +103,7 @@ void Style::cascade(float z) {
text.haloRadius = layer.haloRadius.evaluate<float>(z);
text.rotate = layer.rotate.evaluate<float>(z);
text.alwaysVisible = layer.alwaysVisible.evaluate<bool>(z);
text.opacity = layer.opacity.evaluate<float>(z);
}
// Cascade raster classes