From ea6b24d02d9d7a2a24a97f29c60f485f331e9e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 21 Mar 2014 17:36:29 +0100 Subject: [PATCH] add text rendering code --- include/llmr/geometry/glyph_atlas.hpp | 6 +- include/llmr/map/map.hpp | 2 +- include/llmr/renderer/frame_history.hpp | 29 ++++++ include/llmr/renderer/painter.hpp | 6 +- include/llmr/shader/text_shader.hpp | 12 +-- include/llmr/style/properties.hpp | 12 ++- src/map/map.cpp | 4 +- src/renderer/frame_history.cpp | 16 ++++ src/renderer/painter.cpp | 116 +++++++++++++++++++++++- src/renderer/text_bucket.cpp | 10 +- src/shader/text_shader.cpp | 14 +-- src/style/style.cpp | 4 + src/style/style_parser.cpp | 45 +++++++-- 13 files changed, 241 insertions(+), 35 deletions(-) create mode 100644 include/llmr/renderer/frame_history.hpp create mode 100644 src/renderer/frame_history.cpp diff --git a/include/llmr/geometry/glyph_atlas.hpp b/include/llmr/geometry/glyph_atlas.hpp index b97b79f08..312572f5e 100644 --- a/include/llmr/geometry/glyph_atlas.hpp +++ b/include/llmr/geometry/glyph_atlas.hpp @@ -34,10 +34,12 @@ public: void removeGlyphs(uint64_t tile_id); void bind(); -private: - std::mutex mtx; +public: const uint16_t width = 0; const uint16_t height = 0; + +private: + std::mutex mtx; BinPack bin; std::map> index; char *const data = nullptr; diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index b24a7bc5a..90cd95c01 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -78,8 +78,8 @@ private: Transform transform; Texturepool texturepool; Style style; - Painter painter; GlyphAtlas glyphAtlas; + Painter painter; int32_t min_zoom; int32_t max_zoom; diff --git a/include/llmr/renderer/frame_history.hpp b/include/llmr/renderer/frame_history.hpp new file mode 100644 index 000000000..7326f627b --- /dev/null +++ b/include/llmr/renderer/frame_history.hpp @@ -0,0 +1,29 @@ +#ifndef LLMR_RENDERER_FRAME_HISTORY +#define LLMR_RENDERER_FRAME_HISTORY + +#include +#include +#include + +#include + +namespace llmr { + +struct FrameSnapshot { + explicit inline FrameSnapshot(float time, float z) : time(time), z(z) {} + float time; + float z; +}; + +class FrameHistory { +public: + // Record frame history that will be used to calculate fading params + void record(float zoom); + +public: + std::deque history; +}; + +} + +#endif diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index 71973aba0..027c7e215 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -30,7 +31,7 @@ class TextBucket; class Painter : private util::noncopyable { public: - Painter(Transform& transform, Settings& settings, Style& style); + Painter(Transform& transform, Settings& settings, Style& style, GlyphAtlas& glyphAtlas); void setup(); void clear(); @@ -68,8 +69,9 @@ private: Transform& transform; Settings& settings; Style& style; + GlyphAtlas& glyphAtlas; - + FrameHistory frameHistory; uint32_t gl_program = 0; float gl_lineWidth = 0; diff --git a/include/llmr/shader/text_shader.hpp b/include/llmr/shader/text_shader.hpp index 7edf10d23..db3028cf8 100644 --- a/include/llmr/shader/text_shader.hpp +++ b/include/llmr/shader/text_shader.hpp @@ -15,15 +15,15 @@ public: void setColor(const std::array &color); void setBuffer(float buffer); void setGamma(float gamma); - void setExmatrix(const std::array &exmatrix); + void setExtrudeMatrix(const std::array &exmatrix); void setAngle(float angle); void setZoom(float zoom); void setFlip(float flip); - void setFadedist(float fadedist); - void setMinfadezoom(float minfadezoom); - void setMaxfadezoom(float maxfadezoom); - void setFadezoom(float fadezoom); - void setTexsize(const std::array &texsize); + void setFadeDist(float fadedist); + void setMinFadeZoom(float minfadezoom); + void setMaxFadeZoom(float maxfadezoom); + void setFadeZoom(float fadezoom); + void setTextureSize(const std::array &texsize); private: int32_t a_pos = -1; diff --git a/include/llmr/style/properties.hpp b/include/llmr/style/properties.hpp index f3bc1054c..3fae1537c 100644 --- a/include/llmr/style/properties.hpp +++ b/include/llmr/style/properties.hpp @@ -113,11 +113,19 @@ struct FillProperties { }; struct TextClass { - Color color = {{ 1, 1, 1, 1 }}; + FunctionProperty hidden; + Color color = {{ 0, 0, 0, 1 }}; + Color halo = {{ 1, 1, 1, 0.75 }}; + FunctionProperty haloRadius = 0.25f; + FunctionProperty size = 12.0f; }; struct TextProperties { - Color color = {{ 1, 1, 1, 1 }}; + bool hidden = false; + Color color = {{ 0, 0, 0, 1 }}; + Color halo = {{ 1, 1, 1, 0.75 }}; + float haloRadius = 0.25f; + float size = 12.0f; }; struct BackgroundClass { diff --git a/src/map/map.cpp b/src/map/map.cpp index 961cf7423..c564ede64 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -15,8 +15,8 @@ Map::Map(Settings& settings) transform(), texturepool(), style(), - painter(transform, settings, style), glyphAtlas(1024, 1024), + painter(transform, settings, style, glyphAtlas), min_zoom(0), max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) { } @@ -443,8 +443,6 @@ bool Map::render() { transform.updateAnimations(); } - glyphAtlas.bind(); - if (*style.sprite->raster && !style.sprite->raster->textured) { style.sprite->raster->setTexturepool(&texturepool); } diff --git a/src/renderer/frame_history.cpp b/src/renderer/frame_history.cpp new file mode 100644 index 000000000..de65c1ad4 --- /dev/null +++ b/src/renderer/frame_history.cpp @@ -0,0 +1,16 @@ +#include + +using namespace llmr; + +// Record frame history that will be used to calculate fading params +void FrameHistory::record(float zoom) { + // first frame ever + if (!history.size()) { + history.emplace_back(FrameSnapshot{0.0f, zoom}); + history.emplace_back(FrameSnapshot{0.0f, zoom}); + } + + if (history.size() == 2 || history.back().z != zoom) { + history.emplace_back(FrameSnapshot{static_cast(platform::time() * 1000), zoom}); + } +} diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 91f466f5b..eef751c9c 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -22,10 +22,11 @@ using namespace llmr; #define BUFFER_OFFSET(i) ((char *)nullptr + (i)) -Painter::Painter(Transform& transform, Settings& settings, Style& style) +Painter::Painter(Transform& transform, Settings& settings, Style& style, GlyphAtlas& glyphAtlas) : transform(transform), settings(settings), - style(style) { + style(style), + glyphAtlas(glyphAtlas) { } void Painter::setup() { @@ -152,6 +153,8 @@ void Painter::render(const Tile& tile) { return; } + frameHistory.record(transform.getZoom()); + matrix = tile.matrix; glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF); @@ -477,9 +480,116 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co } void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& id) { - // noop + // Abort early. + if (pass == Opaque) return; + if (bucket.empty()) return; + + const TextProperties& properties = style.computed.texts[layer_name]; + if (properties.hidden) return; + + mat4 exMatrix; + matrix::copy(exMatrix, projMatrix); + if (bucket.geom_desc.path == TextPathType::Curve) { + matrix::rotate_z(exMatrix, exMatrix, transform.getAngle()); + } + + // var rotate = layerStyle.rotate || 0; + float rotate = 0.0f; + 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); + + glyphAtlas.bind(); + textShader->setTextureSize({{ static_cast(glyphAtlas.width), static_cast(glyphAtlas.height) }}); + + // bucket.geometry.glyphVertex.bind(gl); + + // var ubyte = gl.UNSIGNED_BYTE; + + // gl.vertexAttribPointer(shader.a_pos, 2, gl.SHORT, false, 16, 0); + // gl.vertexAttribPointer(shader.a_offset, 2, gl.SHORT, false, 16, 4); + // gl.vertexAttribPointer(shader.a_tex, 2, ubyte, false, 16, 8); + // gl.vertexAttribPointer(shader.a_labelminzoom, 1, ubyte, false, 16, 10); + // gl.vertexAttribPointer(shader.a_minzoom, 1, ubyte, false, 16, 11); + // gl.vertexAttribPointer(shader.a_maxzoom, 1, ubyte, false, 16, 12); + // gl.vertexAttribPointer(shader.a_angle, 1, ubyte, false, 16, 13); + // gl.vertexAttribPointer(shader.a_rangeend, 1, ubyte, false, 16, 14); + // gl.vertexAttribPointer(shader.a_rangestart, 1, ubyte, false, 16, 15); + + textShader->setGamma(2.5f / fontSize / transform.pixelRatio); + + // Convert the -pi..pi to an int8 range. + float angle = round((transform.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((transform.getZoom() - zoomAdjust) * 10); // current zoom level + + // Label fading + const float duration = 300.0f; + const float currentTime = platform::time() * 1000; + + std::deque &history = frameHistory.history; + + // Remove frames until only one is outside the duration, or until there are only three + while (history.size() > 3 && history[1].time + duration < currentTime) { + history.pop_front(); + } + + if (history[1].time + 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.time - history[1].time; + if (timeDiff > duration) timeDiff = 1; + float fadedist = zoomDiff / (timeDiff / duration); + + if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN"); + + // 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.time) / duration * fadedist; + + textShader->setFadeDist(fadedist * 10); + textShader->setMinFadeZoom(floor(lowZ * 10)); + textShader->setMaxFadeZoom(floor(highZ * 10)); + textShader->setFadeZoom((transform.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); + bucket.drawGlyphs(*textShader); + } + + // Then, we draw the text over the halo + textShader->setColor(properties.color); + textShader->setBuffer((256.0f - 64.0f) / 256.0f); + bucket.drawGlyphs(*textShader); } void Painter::renderDebug(const TileData::Ptr& tile_data) { diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index 9f2446e3d..a25d12ef2 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -150,5 +150,13 @@ void TextBucket::render(Painter &painter, const std::string &layer_name, bool TextBucket::empty() const { return triangleGroups.empty(); } void TextBucket::drawGlyphs(TextShader &shader) { - // noop + char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize); + char *elements_index = + BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer->itemSize); + for (triangle_group_type &group : triangleGroups) { + group.array.bind(shader, *vertexBuffer, *triangleElementsBuffer, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * vertexBuffer->itemSize; + elements_index += group.elements_length * triangleElementsBuffer->itemSize; + } } diff --git a/src/shader/text_shader.cpp b/src/shader/text_shader.cpp index 4c8301aff..8df22f25f 100644 --- a/src/shader/text_shader.cpp +++ b/src/shader/text_shader.cpp @@ -65,6 +65,8 @@ TextShader::TextShader() } void TextShader::bind(char *offset) { + fprintf(stderr, "bind textshader\n"); + glEnableVertexAttribArray(a_pos); glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 16, offset + 0); @@ -123,7 +125,7 @@ void TextShader::setGamma(float new_gamma) { } } -void TextShader::setExmatrix(const std::array &new_exmatrix) { +void TextShader::setExtrudeMatrix(const std::array &new_exmatrix) { if (exmatrix != new_exmatrix) { glUniformMatrix4fv(u_exmatrix, 1, GL_FALSE, new_exmatrix.data()); exmatrix = new_exmatrix; @@ -151,35 +153,35 @@ void TextShader::setFlip(float new_flip) { } } -void TextShader::setFadedist(float new_fadedist) { +void TextShader::setFadeDist(float new_fadedist) { if (fadedist != new_fadedist) { glUniform1f(u_fadedist, new_fadedist); fadedist = new_fadedist; } } -void TextShader::setMinfadezoom(float new_minfadezoom) { +void TextShader::setMinFadeZoom(float new_minfadezoom) { if (minfadezoom != new_minfadezoom) { glUniform1f(u_minfadezoom, new_minfadezoom); minfadezoom = new_minfadezoom; } } -void TextShader::setMaxfadezoom(float new_maxfadezoom) { +void TextShader::setMaxFadeZoom(float new_maxfadezoom) { if (maxfadezoom != new_maxfadezoom) { glUniform1f(u_maxfadezoom, new_maxfadezoom); maxfadezoom = new_maxfadezoom; } } -void TextShader::setFadezoom(float new_fadezoom) { +void TextShader::setFadeZoom(float new_fadezoom) { if (fadezoom != new_fadezoom) { glUniform1f(u_fadezoom, new_fadezoom); fadezoom = new_fadezoom; } } -void TextShader::setTexsize(const std::array &new_texsize) { +void TextShader::setTextureSize(const std::array &new_texsize) { if (texsize != new_texsize) { glUniform2fv(u_texsize, 1, new_texsize.data()); texsize = new_texsize; diff --git a/src/style/style.cpp b/src/style/style.cpp index 9ecf59e70..1bce72d76 100644 --- a/src/style/style.cpp +++ b/src/style/style.cpp @@ -83,7 +83,11 @@ void Style::cascade(float z) { // TODO: This should be restricted to point styles that have actual // values so as to not override with default values. llmr::TextProperties& text = computed.texts[layer_name]; + text.hidden = layer.hidden(z); text.color = layer.color; + text.size = layer.size(z); + text.halo = layer.halo; + text.haloRadius = layer.haloRadius(z); } // Cascade background diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index f9e294f97..42bda94b2 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -268,24 +268,35 @@ std::string StyleParser::parseString(JSVal value) { } Color StyleParser::parseColor(JSVal value) { - if (!value.IsString()) { + if (value.IsArray()) { + // [ r, g, b, a] array + if (value.Size() != 4) { + throw Style::exception("color array must have four elements"); + } + + JSVal r = value[(rapidjson::SizeType)0], g = value[1], b = value[2], a = value[3]; + if (!r.IsNumber() || !g.IsNumber() || !b.IsNumber() || !a.IsNumber()) { + throw Style::exception("color values must be numbers"); + } + + return {{static_cast(r.GetDouble() / 255), + static_cast(g.GetDouble() / 255), + static_cast(b.GetDouble() / 255), + static_cast(a.GetDouble())}}; + + } else if (!value.IsString()) { throw Style::exception("color value must be a string"); } - const std::string str { value.GetString(), value.GetStringLength() }; + const std::string str{value.GetString(), value.GetStringLength()}; auto it = constants.find(str); if (it != constants.end()) { return parseColor(*it->second); } else { CSSColorParser::Color css_color = CSSColorParser::parse(str); - return {{ - (float)css_color.r / 255, - (float)css_color.g / 255, - (float)css_color.b / 255, - css_color.a - } - }; + return {{(float)css_color.r / 255, (float)css_color.g / 255, + (float)css_color.b / 255, css_color.a}}; } } @@ -477,10 +488,26 @@ PointClass StyleParser::parsePointClass(JSVal value) { TextClass StyleParser::parseTextClass(JSVal value) { TextClass klass; + if (value.HasMember("hidden")) { + klass.hidden = parseBoolFunction(value["hidden"]); + } + if (value.HasMember("color")) { klass.color = parseColor(value["color"]); } + if (value.HasMember("stroke")) { + klass.halo = parseColor(value["stroke"]); + } + + if (value.HasMember("strokeWidth")) { + klass.haloRadius = parseFloatFunction(value["strokeWidth"]); + } + + if (value.HasMember("size")) { + klass.size = parseFloatFunction(value["size"]); + } + return klass; }