diff options
Diffstat (limited to 'src/mbgl/renderer')
24 files changed, 3302 insertions, 0 deletions
diff --git a/src/mbgl/renderer/bucket.hpp b/src/mbgl/renderer/bucket.hpp new file mode 100644 index 0000000000..696bfb1110 --- /dev/null +++ b/src/mbgl/renderer/bucket.hpp @@ -0,0 +1,24 @@ +#ifndef MBGL_RENDERER_BUCKET +#define MBGL_RENDERER_BUCKET + +#include <mbgl/map/tile.hpp> +#include <mbgl/util/noncopyable.hpp> + +#include <string> + +namespace mbgl { + +class Painter; +class StyleLayer; + +class Bucket : private util::noncopyable { +public: + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) = 0; + virtual bool hasData() const = 0; + virtual ~Bucket() {} + +}; + +} + +#endif diff --git a/src/mbgl/renderer/debug_bucket.cpp b/src/mbgl/renderer/debug_bucket.cpp new file mode 100644 index 0000000000..f089374564 --- /dev/null +++ b/src/mbgl/renderer/debug_bucket.cpp @@ -0,0 +1,32 @@ +#include <mbgl/renderer/debug_bucket.hpp> +#include <mbgl/renderer/painter.hpp> + +#include <mbgl/platform/gl.hpp> + +#include <cassert> + +struct geometry_too_long_exception : std::exception {}; + +using namespace mbgl; + +DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer_) + : fontBuffer(fontBuffer_) { +} + +void DebugBucket::render(Painter& painter, util::ptr<StyleLayer> /*layer_desc*/, const Tile::ID& /*id*/, const mat4 &matrix) { + painter.renderDebugText(*this, matrix); +} + +bool DebugBucket::hasData() const { + return fontBuffer.index() > 0; +} + +void DebugBucket::drawLines(PlainShader& shader) { + array.bind(shader, fontBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_LINES, 0, (GLsizei)(fontBuffer.index())); +} + +void DebugBucket::drawPoints(PlainShader& shader) { + array.bind(shader, fontBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_POINTS, 0, (GLsizei)(fontBuffer.index())); +} diff --git a/src/mbgl/renderer/debug_bucket.hpp b/src/mbgl/renderer/debug_bucket.hpp new file mode 100644 index 0000000000..fb6cfb4cae --- /dev/null +++ b/src/mbgl/renderer/debug_bucket.hpp @@ -0,0 +1,35 @@ +#ifndef MBGL_RENDERER_DEBUGBUCKET +#define MBGL_RENDERER_DEBUGBUCKET + +#include <mbgl/renderer/bucket.hpp> +#include <mbgl/geometry/debug_font_buffer.hpp> +#include <mbgl/geometry/vao.hpp> + +#include <vector> + +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + +namespace mbgl { + +class PlainShader; + +class DebugBucket : public Bucket { +public: + DebugBucket(DebugFontBuffer& fontBuffer); + + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual bool hasData() const; + + void drawLines(PlainShader& shader); + void drawPoints(PlainShader& shader); + +private: + DebugFontBuffer& fontBuffer; + VertexArrayObject array; +}; + +} + +#endif diff --git a/src/mbgl/renderer/fill_bucket.cpp b/src/mbgl/renderer/fill_bucket.cpp new file mode 100644 index 0000000000..0a7d77935d --- /dev/null +++ b/src/mbgl/renderer/fill_bucket.cpp @@ -0,0 +1,246 @@ +#include <mbgl/renderer/fill_bucket.hpp> +#include <mbgl/geometry/fill_buffer.hpp> +#include <mbgl/geometry/elements_buffer.hpp> +#include <mbgl/geometry/geometry.hpp> + +#include <mbgl/renderer/painter.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/map/vector_tile.hpp> + +#include <mbgl/platform/gl.hpp> + + +#include <cassert> + +struct geometry_too_long_exception : std::exception {}; + +using namespace mbgl; + + + +void *FillBucket::alloc(void *, unsigned int size) { + return ::malloc(size); +} + +void *FillBucket::realloc(void *, void *ptr, unsigned int size) { + return ::realloc(ptr, size); +} + +void FillBucket::free(void *, void *ptr) { + ::free(ptr); +} + +FillBucket::FillBucket(FillVertexBuffer &vertexBuffer_, + TriangleElementsBuffer &triangleElementsBuffer_, + LineElementsBuffer &lineElementsBuffer_, + const StyleBucketFill &properties_) + : properties(properties_), + allocator(new TESSalloc{&alloc, &realloc, &free, nullptr, // userData + 64, // meshEdgeBucketSize + 64, // meshVertexBucketSize + 32, // meshFaceBucketSize + 64, // dictNodeBucketSize + 8, // regionBucketSize + 128, // extraVertices allocated for the priority queue. + }), + tesselator(tessNewTess(allocator)), + vertexBuffer(vertexBuffer_), + triangleElementsBuffer(triangleElementsBuffer_), + lineElementsBuffer(lineElementsBuffer_), + vertex_start(vertexBuffer_.index()), + triangle_elements_start(triangleElementsBuffer_.index()), + line_elements_start(lineElementsBuffer.index()) { + assert(tesselator); +} + +FillBucket::~FillBucket() { + if (tesselator) { + tessDeleteTess(tesselator); + } + if (allocator) { + delete allocator; + } +} + +void FillBucket::addGeometry(pbf& geom) { + Geometry::command cmd; + + Coordinate coord; + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (line.size()) { + clipper.AddPath(line, ClipperLib::ptSubject, true); + line.clear(); + hasVertices = true; + } + } + line.emplace_back(x, y); + } + + if (line.size()) { + clipper.AddPath(line, ClipperLib::ptSubject, true); + line.clear(); + hasVertices = true; + } + + tessellate(); +} + +void FillBucket::tessellate() { + if (!hasVertices) { + return; + } + hasVertices = false; + + std::vector<std::vector<ClipperLib::IntPoint>> polygons; + clipper.Execute(ClipperLib::ctUnion, polygons, ClipperLib::pftPositive); + clipper.Clear(); + + if (polygons.size() == 0) { + return; + } + + size_t total_vertex_count = 0; + for (const std::vector<ClipperLib::IntPoint>& polygon : polygons) { + total_vertex_count += polygon.size(); + } + + if (total_vertex_count > 65536) { + throw geometry_too_long_exception(); + } + + if (!lineGroups.size() || (lineGroups.back().vertex_length + total_vertex_count > 65535)) { + // Move to a new group because the old one can't hold the geometry. + lineGroups.emplace_back(); + } + + line_group_type& lineGroup = lineGroups.back(); + uint32_t lineIndex = lineGroup.vertex_length; + + for (const std::vector<ClipperLib::IntPoint>& polygon : polygons) { + const size_t group_count = polygon.size(); + assert(group_count >= 3); + + std::vector<TESSreal> clipped_line; + for (const ClipperLib::IntPoint& pt : polygon) { + clipped_line.push_back(pt.X); + clipped_line.push_back(pt.Y); + vertexBuffer.add(pt.X, pt.Y); + } + + for (size_t i = 0; i < group_count; i++) { + const size_t prev_i = (i == 0 ? group_count : i) - 1; + lineElementsBuffer.add(lineIndex + prev_i, lineIndex + i); + } + + lineIndex += group_count; + + tessAddContour(tesselator, vertexSize, clipped_line.data(), stride, (int)clipped_line.size() / vertexSize); + } + + lineGroup.elements_length += total_vertex_count; + + if (tessTesselate(tesselator, TESS_WINDING_POSITIVE, TESS_POLYGONS, vertices_per_group, vertexSize, 0)) { + const TESSreal *vertices = tessGetVertices(tesselator); + const size_t vertex_count = tessGetVertexCount(tesselator); + TESSindex *vertex_indices = const_cast<TESSindex *>(tessGetVertexIndices(tesselator)); + const TESSindex *elements = tessGetElements(tesselator); + const int triangle_count = tessGetElementCount(tesselator); + + for (size_t i = 0; i < vertex_count; ++i) { + if (vertex_indices[i] == TESS_UNDEF) { + vertexBuffer.add(std::round(vertices[i * 2]), std::round(vertices[i * 2 + 1])); + vertex_indices[i] = (TESSindex)total_vertex_count; + total_vertex_count++; + } + } + + if (!triangleGroups.size() || (triangleGroups.back().vertex_length + total_vertex_count > 65535)) { + // Move to a new group because the old one can't hold the geometry. + triangleGroups.emplace_back(); + } + + // We're generating triangle fans, so we always start with the first + // coordinate in this polygon. + triangle_group_type& triangleGroup = triangleGroups.back(); + uint32_t triangleIndex = triangleGroup.vertex_length; + + for (int i = 0; i < triangle_count; ++i) { + const TESSindex *element_group = &elements[i * vertices_per_group]; + + if (element_group[0] != TESS_UNDEF && element_group[1] != TESS_UNDEF && element_group[2] != TESS_UNDEF) { + const TESSindex a = vertex_indices[element_group[0]]; + const TESSindex b = vertex_indices[element_group[1]]; + const TESSindex c = vertex_indices[element_group[2]]; + + if (a != TESS_UNDEF && b != TESS_UNDEF && c != TESS_UNDEF) { + triangleElementsBuffer.add(triangleIndex + a, triangleIndex + b, triangleIndex + c); + } else { +#if defined(DEBUG) + // TODO: We're missing a vertex that was not part of the line. + fprintf(stderr, "undefined element buffer\n"); +#endif + } + } else { +#if defined(DEBUG) + fprintf(stderr, "undefined element buffer\n"); +#endif + } + } + + triangleGroup.vertex_length += total_vertex_count; + triangleGroup.elements_length += triangle_count; + } else { +#if defined(DEBUG) + fprintf(stderr, "tessellation failed\n"); +#endif + } + + // We're adding the total vertex count *after* we added additional vertices + // in the tessellation step. They won't be part of the actual lines, but + // we need to skip over them anyway if we draw the next group. + lineGroup.vertex_length += total_vertex_count; +} + +void FillBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { + painter.renderFill(*this, layer_desc, id, matrix); +} + +bool FillBucket::hasData() const { + return !triangleGroups.empty() || !lineGroups.empty(); +} + +void FillBucket::drawElements(PlainShader& shader) { + 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[0].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; + } +} + +void FillBucket::drawElements(PatternShader& shader) { + 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[1].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; + } +} + +void FillBucket::drawVertices(OutlineShader& shader) { + char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char *elements_index = BUFFER_OFFSET(line_elements_start * lineElementsBuffer.itemSize); + for (line_group_type& group : lineGroups) { + group.array[0].bind(shader, vertexBuffer, lineElementsBuffer, vertex_index); + glDrawElements(GL_LINES, group.elements_length * 2, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * vertexBuffer.itemSize; + elements_index += group.elements_length * lineElementsBuffer.itemSize; + } +} diff --git a/src/mbgl/renderer/fill_bucket.hpp b/src/mbgl/renderer/fill_bucket.hpp new file mode 100644 index 0000000000..ae766ec28d --- /dev/null +++ b/src/mbgl/renderer/fill_bucket.hpp @@ -0,0 +1,88 @@ +#ifndef MBGL_RENDERER_FILLBUCKET +#define MBGL_RENDERER_FILLBUCKET + +#include <mbgl/renderer/bucket.hpp> +#include <mbgl/geometry/elements_buffer.hpp> +#include <mbgl/geometry/fill_buffer.hpp> +#include <mbgl/style/style_bucket.hpp> + +#include <clipper/clipper.hpp> +#include <libtess2/tesselator.h> + +#include <vector> +#include <memory> + +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + +namespace mbgl { + +class Style; +class FillVertexBuffer; +class TriangleElementsBuffer; +class LineElementsBuffer; +class BucketDescription; +class OutlineShader; +class PlainShader; +class PatternShader; +struct pbf; + +class FillBucket : public Bucket { + + static void *alloc(void *data, unsigned int size); + static void *realloc(void *data, void *ptr, unsigned int size); + static void free(void *userData, void *ptr); + + typedef ElementGroup<2> triangle_group_type; + typedef ElementGroup<1> line_group_type; + +public: + FillBucket(FillVertexBuffer& vertexBuffer, + TriangleElementsBuffer& triangleElementsBuffer, + LineElementsBuffer& lineElementsBuffer, + const StyleBucketFill& properties); + ~FillBucket(); + + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual bool hasData() const; + + void addGeometry(pbf& data); + void tessellate(); + + void drawElements(PlainShader& shader); + void drawElements(PatternShader& shader); + void drawVertices(OutlineShader& shader); + +public: + const StyleBucketFill &properties; + +private: + TESSalloc *allocator; + TESStesselator *tesselator; + ClipperLib::Clipper clipper; + + FillVertexBuffer& vertexBuffer; + TriangleElementsBuffer& triangleElementsBuffer; + LineElementsBuffer& lineElementsBuffer; + + // hold information on where the vertices are located in the FillBuffer + const size_t vertex_start; + const size_t triangle_elements_start; + const size_t line_elements_start; + VertexArrayObject array; + + std::vector<triangle_group_type> triangleGroups; + std::vector<line_group_type> lineGroups; + + std::vector<ClipperLib::IntPoint> line; + bool hasVertices = false; + + static const int vertexSize = 2; + static const int stride = sizeof(TESSreal) * vertexSize; + static const int vertices_per_group = 3; +}; + +} + +#endif diff --git a/src/mbgl/renderer/frame_history.cpp b/src/mbgl/renderer/frame_history.cpp new file mode 100644 index 0000000000..8b69162a23 --- /dev/null +++ b/src/mbgl/renderer/frame_history.cpp @@ -0,0 +1,85 @@ +#include <mbgl/renderer/frame_history.hpp> + +using namespace mbgl; + +// Record frame history that will be used to calculate fading params +void FrameHistory::record(timestamp now, float zoom) { + // first frame ever + if (!history.size()) { + history.emplace_back(FrameSnapshot{0, zoom}); + history.emplace_back(FrameSnapshot{0, zoom}); + } + + if (history.size() > 0 || history.back().z != zoom) { + history.emplace_back(FrameSnapshot{now, zoom}); + } +} + +bool FrameHistory::needsAnimation(const timestamp duration) const { + if (!history.size()) { + return false; + } + + // If we have a value that is older than duration and whose z value is the + // same as the most current z value, and if all values inbetween have the + // same z value, we don't need animation, otherwise we probably do. + const FrameSnapshot &pivot = history.back(); + + int i = -1; + while ((int)history.size() > i + 1 && history[i + 1].t + duration < pivot.t) { + i++; + } + + if (i < 0) { + // There is no frame that is older than the duration time, so we need to + // check all frames. + i = 0; + } + + // Make sure that all subsequent snapshots have the same zoom as the last + // pivot element. + for (; (int)history.size() > i; i++) { + if (history[i].z != pivot.z) { + return true; + } + } + + return false; +} + +FadeProperties FrameHistory::getFadeProperties(timestamp duration) +{ + const timestamp currentTime = util::now(); + + // Remove frames until only one is outside the duration, or until there are only three + while (history.size() > 3 && history[1].t + duration < currentTime) { + history.pop_front(); + } + + if (history[1].t + duration < currentTime) { + history[0].z = history[1].z; + } + + // 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 = std::fmin(startingZ, endingZ); + float highZ = std::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.t - history[1].t; + float fadedist = zoomDiff / (timeDiff / duration); + + // 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.t) / duration * fadedist; + + return FadeProperties { + fadedist, + lowZ, + highZ, + bump + }; +} diff --git a/src/mbgl/renderer/frame_history.hpp b/src/mbgl/renderer/frame_history.hpp new file mode 100644 index 0000000000..61bb59da33 --- /dev/null +++ b/src/mbgl/renderer/frame_history.hpp @@ -0,0 +1,40 @@ +#ifndef MBGL_RENDERER_FRAME_HISTORY +#define MBGL_RENDERER_FRAME_HISTORY + +#include <deque> +#include <cassert> +#include <cmath> + +#include <mbgl/platform/platform.hpp> +#include <mbgl/util/time.hpp> + +namespace mbgl { + +struct FrameSnapshot { + explicit inline FrameSnapshot(timestamp t_, float z_) : t(t_), z(z_) {} + float t; + float z; +}; + +struct FadeProperties { + float fadedist; + float minfadezoom; + float maxfadezoom; + float bump; +}; + +class FrameHistory { +public: + // Record frame history that will be used to calculate fading params + void record(timestamp now, float zoom); + + bool needsAnimation(timestamp duration) const; + FadeProperties getFadeProperties(timestamp duration); + +public: + std::deque<FrameSnapshot> history; +}; + +} + +#endif diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp new file mode 100644 index 0000000000..8267cbaba2 --- /dev/null +++ b/src/mbgl/renderer/line_bucket.cpp @@ -0,0 +1,403 @@ +#include <mbgl/renderer/line_bucket.hpp> +#include <mbgl/geometry/elements_buffer.hpp> +#include <mbgl/geometry/geometry.hpp> + +#include <mbgl/renderer/painter.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/map/vector_tile.hpp> + +#include <mbgl/util/math.hpp> +#include <mbgl/platform/gl.hpp> + +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) + +#include <cassert> + +struct geometry_too_long_exception : std::exception {}; + +using namespace mbgl; + +LineBucket::LineBucket(LineVertexBuffer& vertexBuffer_, + TriangleElementsBuffer& triangleElementsBuffer_, + PointElementsBuffer& pointElementsBuffer_, + const StyleBucketLine& properties_) + : properties(properties_), + vertexBuffer(vertexBuffer_), + triangleElementsBuffer(triangleElementsBuffer_), + pointElementsBuffer(pointElementsBuffer_), + vertex_start(vertexBuffer_.index()), + triangle_elements_start(triangleElementsBuffer_.index()), + point_elements_start(pointElementsBuffer_.index()) +{ +} + +void LineBucket::addGeometry(pbf& geom) { + std::vector<Coordinate> line; + Geometry::command cmd; + + Coordinate coord; + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (!line.empty()) { + addGeometry(line); + line.clear(); + } + } + line.emplace_back(x, y); + } + if (line.size()) { + addGeometry(line); + } +} + +struct TriangleElement { + TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) : a(a_), b(b_), c(c_) {} + uint16_t a, b, c; +}; + +typedef uint16_t PointElement; + +void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) { + // TODO: use roundLimit + // const float roundLimit = geometry.round_limit; + + if (vertices.size() < 2) { + // fprintf(stderr, "a line must have at least two vertices\n"); + return; + } + + Coordinate firstVertex = vertices.front(); + Coordinate lastVertex = vertices.back(); + bool closed = firstVertex.x == lastVertex.x && firstVertex.y == lastVertex.y; + + if (vertices.size() == 2 && closed) { + // fprintf(stderr, "a line may not have coincident points\n"); + return; + } + + CapType beginCap = properties.cap; + CapType endCap = closed ? CapType::Butt : properties.cap; + + JoinType currentJoin = JoinType::Miter; + + Coordinate currentVertex = Coordinate::null(), + prevVertex = Coordinate::null(), + nextVertex = Coordinate::null(); + vec2<double> prevNormal = vec2<double>::null(), + nextNormal = vec2<double>::null(); + + int32_t e1 = -1, e2 = -1, e3 = -1; + + int8_t flip = 1; + double distance = 0; + + if (closed) { + currentVertex = vertices[vertices.size() - 2]; + nextNormal = util::normal<double>(currentVertex, lastVertex); + } + + int32_t start_vertex = (int32_t)vertexBuffer.index(); + + std::vector<TriangleElement> triangle_store; + std::vector<PointElement> point_store; + + for (size_t i = 0; i < vertices.size(); ++i) { + if (nextNormal) prevNormal = { -nextNormal.x, -nextNormal.y }; + if (currentVertex) prevVertex = currentVertex; + + currentVertex = vertices[i]; + currentJoin = properties.join; + + if (prevVertex) distance += util::dist<double>(currentVertex, prevVertex); + + // Find the next vertex. + if (i + 1 < vertices.size()) { + nextVertex = vertices[i + 1]; + } else { + nextVertex = Coordinate::null(); + } + + // If the line is closed, we treat the last vertex like the first vertex. + if (!nextVertex && closed) { + nextVertex = vertices[1]; + } + + if (nextVertex) { + // if two consecutive vertices exist, skip one + if (currentVertex.x == nextVertex.x && currentVertex.y == nextVertex.y) continue; + } + + // Calculate the normal towards the next vertex in this line. In case + // there is no next vertex, pretend that the line is continuing straight, + // meaning that we are just reversing the previous normal + if (nextVertex) { + nextNormal = util::normal<double>(currentVertex, nextVertex); + } else { + nextNormal = { -prevNormal.x, -prevNormal.y }; + } + + // If we still don't have a previous normal, this is the beginning of a + // non-closed line, so we're doing a straight "join". + if (!prevNormal) { + prevNormal = { -nextNormal.x, -nextNormal.y }; + } + + // Determine the normal of the join extrusion. It is the angle bisector + // of the segments between the previous line and the next line. + vec2<double> joinNormal = { + prevNormal.x + nextNormal.x, + prevNormal.y + nextNormal.y + }; + + // Cross product yields 0..1 depending on whether they are parallel + // or perpendicular. + double joinAngularity = nextNormal.x * joinNormal.y - nextNormal.y * joinNormal.x; + joinNormal.x /= joinAngularity; + joinNormal.y /= joinAngularity; + double roundness = std::fmax(std::abs(joinNormal.x), std::abs(joinNormal.y)); + + + // Switch to miter joins if the angle is very low. + if (currentJoin != JoinType::Miter) { + if (std::fabs(joinAngularity) < 0.5 && roundness < properties.miter_limit) { + currentJoin = JoinType::Miter; + } + } + + // Add offset square begin cap. + if (!prevVertex && beginCap == CapType::Square) { + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + } + + // Add offset square end cap. + else if (!nextVertex && endCap == CapType::Square) { + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + } + + else if (currentJoin == JoinType::Miter) { + // MITER JOIN + if (std::fabs(joinAngularity) < 0.01) { + // The two normals are almost parallel. + joinNormal.x = -nextNormal.y; + joinNormal.y = nextNormal.x; + } else if (roundness > properties.miter_limit) { + // If the miter grows too large, flip the direction to make a + // bevel join. + joinNormal.x = (prevNormal.x - nextNormal.x) / joinAngularity; + joinNormal.y = (prevNormal.y - nextNormal.y) / joinAngularity; + } + + if (roundness > properties.miter_limit) { + flip = -flip; + } + + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * joinNormal.x, flip * joinNormal.y, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + -flip * joinNormal.x, -flip * joinNormal.y, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + if ((!prevVertex && beginCap == CapType::Round) || + (!nextVertex && endCap == CapType::Round)) { + point_store.emplace_back(e1); + } + } + + else { + // Close up the previous line + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * prevNormal.y, -flip * prevNormal.x, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex. + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + -flip * prevNormal.y, flip * prevNormal.x, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + prevNormal = { -nextNormal.x, -nextNormal.y }; + flip = 1; + + + // begin/end caps + if ((!prevVertex && beginCap == CapType::Round) || + (!nextVertex && endCap == CapType::Round)) { + point_store.emplace_back(e1); + } + + + if (currentJoin == JoinType::Round) { + if (prevVertex && nextVertex && (!closed || i > 0)) { + point_store.emplace_back(e1); + } + + // Reset the previous vertices so that we don't accidentally create + // any triangles. + e1 = -1; e2 = -1; e3 = -1; + } + + // Start the new quad. + // Add first vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + -flip * nextNormal.y, flip * nextNormal.x, // extrude normal + 0, 0, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + + // Add second vertex + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos + flip * nextNormal.y, -flip * nextNormal.x, // extrude normal + 0, 1, distance) - start_vertex; // texture normal + + if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); + e1 = e2; e2 = e3; + } + } + + size_t end_vertex = vertexBuffer.index(); + size_t vertex_count = end_vertex - start_vertex; + + // Store the triangle/line groups. + { + if (!triangleGroups.size() || (triangleGroups.back().vertex_length + vertex_count > 65535)) { + // Move to a new group because the old one can't hold the geometry. + triangleGroups.emplace_back(); + } + + triangle_group_type& group = triangleGroups.back(); + for (const TriangleElement& triangle : triangle_store) { + triangleElementsBuffer.add( + group.vertex_length + triangle.a, + group.vertex_length + triangle.b, + group.vertex_length + triangle.c + ); + } + + group.vertex_length += vertex_count; + group.elements_length += triangle_store.size(); + } + + // Store the line join/cap groups. + { + if (!pointGroups.size() || (pointGroups.back().vertex_length + vertex_count > 65535)) { + // Move to a new group because the old one can't hold the geometry. + pointGroups.emplace_back(); + } + + point_group_type& group = pointGroups.back(); + for (PointElement point : point_store) { + pointElementsBuffer.add(group.vertex_length + point); + } + + group.vertex_length += vertex_count; + group.elements_length += point_store.size(); + } +} + +void LineBucket::render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { + painter.renderLine(*this, layer_desc, id, matrix); +} + +bool LineBucket::hasData() const { + return !triangleGroups.empty() || !pointGroups.empty(); +} + +bool LineBucket::hasPoints() const { + if (!pointGroups.empty()) { + for (const point_group_type& group : pointGroups) { + if (group.elements_length) { + return true; + } + } + } + return false; +} + +void LineBucket::drawLines(LineShader& shader) { + 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) { + if (!group.elements_length) { + continue; + } + group.array[0].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; + } +} + +void LineBucket::drawLinePatterns(LinepatternShader& shader) { + 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) { + if (!group.elements_length) { + continue; + } + group.array[1].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; + } +} + +void LineBucket::drawPoints(LinejoinShader& shader) { + char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer.itemSize); + for (point_group_type& group : pointGroups) { + if (!group.elements_length) { + continue; + } + group.array[0].bind(shader, vertexBuffer, pointElementsBuffer, vertex_index); + glDrawElements(GL_POINTS, group.elements_length, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * vertexBuffer.itemSize; + elements_index += group.elements_length * pointElementsBuffer.itemSize; + } +} diff --git a/src/mbgl/renderer/line_bucket.hpp b/src/mbgl/renderer/line_bucket.hpp new file mode 100644 index 0000000000..7337ca80ad --- /dev/null +++ b/src/mbgl/renderer/line_bucket.hpp @@ -0,0 +1,62 @@ +#ifndef MBGL_RENDERER_LINEBUCKET +#define MBGL_RENDERER_LINEBUCKET + +#include <mbgl/renderer/bucket.hpp> +#include <mbgl/geometry/vao.hpp> +#include <mbgl/geometry/elements_buffer.hpp> +#include <mbgl/geometry/line_buffer.hpp> +#include <mbgl/style/style_bucket.hpp> + +#include <vector> + +namespace mbgl { + +class Style; +class LineVertexBuffer; +class TriangleElementsBuffer; +class LineShader; +class LinejoinShader; +class LinepatternShader; +struct pbf; + +class LineBucket : public Bucket { + typedef ElementGroup<2> triangle_group_type; + typedef ElementGroup<1> point_group_type; + +public: + LineBucket(LineVertexBuffer& vertexBuffer, + TriangleElementsBuffer& triangleElementsBuffer, + PointElementsBuffer& pointElementsBuffer, + const StyleBucketLine& properties); + + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual bool hasData() const; + + void addGeometry(pbf& data); + void addGeometry(const std::vector<Coordinate>& line); + + bool hasPoints() const; + + void drawLines(LineShader& shader); + void drawLinePatterns(LinepatternShader& shader); + void drawPoints(LinejoinShader& shader); + +public: + const StyleBucketLine &properties; + +private: + LineVertexBuffer& vertexBuffer; + TriangleElementsBuffer& triangleElementsBuffer; + PointElementsBuffer& pointElementsBuffer; + + const size_t vertex_start; + const size_t triangle_elements_start; + const size_t point_elements_start; + + std::vector<triangle_group_type> triangleGroups; + std::vector<point_group_type> pointGroups; +}; + +} + +#endif diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp new file mode 100644 index 0000000000..bfc59942e7 --- /dev/null +++ b/src/mbgl/renderer/painter.cpp @@ -0,0 +1,465 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/style/style_layer.hpp> +#include <mbgl/style/style_layer_group.hpp> +#include <mbgl/style/style_bucket.hpp> +#include <mbgl/util/std.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/util/time.hpp> +#include <mbgl/util/clip_ids.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/mat3.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/map/source.hpp> + +#if defined(DEBUG) +#include <mbgl/util/stopwatch.hpp> +#endif + +#include <cassert> +#include <algorithm> +#include <iostream> + +using namespace mbgl; + +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) + +Painter::Painter(SpriteAtlas& spriteAtlas_, GlyphAtlas& glyphAtlas_) + : spriteAtlas(spriteAtlas_) + , glyphAtlas(glyphAtlas_) +{ +} + +Painter::~Painter() { + cleanup(); +} + +bool Painter::needsAnimation() const { + return frameHistory.needsAnimation(300); +} + +void Painter::setup() { +#if defined(DEBUG) + util::stopwatch stopwatch("painter setup"); +#endif + + // Enable GL debugging + if ((gl::DebugMessageControl != nullptr) && (gl::DebugMessageCallback != nullptr)) { + // This will enable all messages including performance hints + //gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + + // This will only enable high and medium severity messages + gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE); + gl::DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, GL_TRUE); + + gl::DebugMessageCallback(gl::debug_callback, nullptr); + } + + setupShaders(); + + assert(iconShader); + assert(plainShader); + assert(outlineShader); + assert(lineShader); + assert(linejoinShader); + assert(linepatternShader); + assert(patternShader); + assert(rasterShader); + assert(sdfGlyphShader); + assert(sdfIconShader); + assert(dotShader); + assert(gaussianShader); + + + // Blending + // We are blending new pixels on top of old pixels. Since we have depth testing + // and are drawing opaque fragments first front-to-back, then translucent + // fragments back-to-front, this shades the fewest fragments possible. + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // Set clear values + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearDepth(1.0f); + glClearStencil(0x0); + + // Stencil test + glEnable(GL_STENCIL_TEST); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); +} + +void Painter::setupShaders() { + if (!plainShader) plainShader = util::make_unique<PlainShader>(); + if (!outlineShader) outlineShader = util::make_unique<OutlineShader>(); + if (!lineShader) lineShader = util::make_unique<LineShader>(); + if (!linejoinShader) linejoinShader = util::make_unique<LinejoinShader>(); + if (!linepatternShader) linepatternShader = util::make_unique<LinepatternShader>(); + if (!patternShader) patternShader = util::make_unique<PatternShader>(); + if (!iconShader) iconShader = util::make_unique<IconShader>(); + if (!rasterShader) rasterShader = util::make_unique<RasterShader>(); + if (!sdfGlyphShader) sdfGlyphShader = util::make_unique<SDFGlyphShader>(); + if (!sdfIconShader) sdfIconShader = util::make_unique<SDFIconShader>(); + if (!dotShader) dotShader = util::make_unique<DotShader>(); + if (!gaussianShader) gaussianShader = util::make_unique<GaussianShader>(); +} + +void Painter::deleteShaders() { + plainShader = nullptr; + outlineShader = nullptr; + lineShader = nullptr; + linejoinShader = nullptr; + linepatternShader = nullptr; + patternShader = nullptr; + iconShader = nullptr; + rasterShader = nullptr; + sdfGlyphShader = nullptr; + sdfIconShader = nullptr; + dotShader = nullptr; + gaussianShader = nullptr; +} + +void Painter::cleanup() { +} + +void Painter::terminate() { + cleanup(); + deleteShaders(); +} + +void Painter::resize() { + if (gl_viewport != state.getFramebufferDimensions()) { + gl_viewport = state.getFramebufferDimensions(); + assert(gl_viewport[0] > 0 && gl_viewport[1] > 0); + glViewport(0, 0, gl_viewport[0], gl_viewport[1]); + } +} + +void Painter::setDebug(bool enabled) { + debug = enabled; +} + +void Painter::useProgram(uint32_t program) { + if (gl_program != program) { + glUseProgram(program); + gl_program = program; + } +} + +void Painter::lineWidth(float line_width) { + if (gl_lineWidth != line_width) { + glLineWidth(line_width); + gl_lineWidth = line_width; + } +} + +void Painter::depthMask(bool value) { + if (gl_depthMask != value) { + glDepthMask(value ? GL_TRUE : GL_FALSE); + gl_depthMask = value; + } +} + +void Painter::depthRange(const float near, const float far) { + if (gl_depthRange[0] != near || gl_depthRange[1] != far) { + glDepthRange(near, far); + gl_depthRange = {{ near, far }}; + } +} + + +void Painter::changeMatrix() { + // Initialize projection matrix + matrix::ortho(projMatrix, 0, state.getWidth(), state.getHeight(), 0, 0, 1); + + // The extrusion matrix. + matrix::identity(extrudeMatrix); + matrix::multiply(extrudeMatrix, projMatrix, extrudeMatrix); + matrix::rotate_z(extrudeMatrix, extrudeMatrix, state.getAngle()); + + // The native matrix is a 1:1 matrix that paints the coordinates at the + // same screen position as the vertex specifies. + matrix::identity(nativeMatrix); + matrix::multiply(nativeMatrix, projMatrix, nativeMatrix); +} + +void Painter::clear() { + gl::group group("clear"); + glStencilMask(0xFF); + depthMask(true); + + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void Painter::setOpaque() { + if (pass != RenderPass::Opaque) { + pass = RenderPass::Opaque; + glDisable(GL_BLEND); + depthMask(true); + } +} + +void Painter::setTranslucent() { + if (pass != RenderPass::Translucent) { + pass = RenderPass::Translucent; + glEnable(GL_BLEND); + depthMask(false); + } +} + +void Painter::setStrata(float value) { + strata = value; +} + +void Painter::prepareTile(const Tile& tile) { + const GLint ref = (GLint)tile.clip.reference.to_ulong(); + const GLuint mask = (GLuint)tile.clip.mask.to_ulong(); + glStencilFunc(GL_EQUAL, ref, mask); +} + +void Painter::render(const Style& style, const std::set<util::ptr<StyleSource>>& sources, + TransformState state_, timestamp time) { + state = state_; + + clear(); + resize(); + changeMatrix(); + + // Update all clipping IDs. + ClipIDGenerator generator; + for (const util::ptr<StyleSource> &source : sources) { + generator.update(source->source->getLoadedTiles()); + source->source->updateMatrices(projMatrix, state); + } + + drawClippingMasks(sources); + + frameHistory.record(time, state.getNormalizedZoom()); + + // Actually render the layers + if (debug::renderTree) { std::cout << "{" << std::endl; indent++; } + renderLayers(style.layers); + if (debug::renderTree) { std::cout << "}" << std::endl; indent--; } + + // Finalize the rendering, e.g. by calling debug render calls per tile. + // This guarantees that we have at least one function per tile called. + // When only rendering layers via the stylesheet, it's possible that we don't + // ever visit a tile during rendering. + for (const util::ptr<StyleSource> &source : sources) { + source->source->finishRender(*this); + } + + glFlush(); +} + +void Painter::renderLayers(util::ptr<StyleLayerGroup> group) { + if (!group) { + // Make sure that we actually do have a layer group. + return; + } + + // TODO: Correctly compute the number of layers recursively beforehand. + float strata_thickness = 1.0f / (group->layers.size() + 1); + + // - FIRST PASS ------------------------------------------------------------ + // Render everything top-to-bottom by using reverse iterators. Render opaque + // objects first. + + if (debug::renderTree) { + std::cout << std::string(indent++ * 4, ' ') << "OPAQUE {" << std::endl; + } + int i = 0; + for (auto it = group->layers.rbegin(), end = group->layers.rend(); it != end; ++it, ++i) { + setOpaque(); + setStrata(i * strata_thickness); + renderLayer(*it); + } + if (debug::renderTree) { + std::cout << std::string(--indent * 4, ' ') << "}" << std::endl; + } + + // - SECOND PASS ----------------------------------------------------------- + // Make a second pass, rendering translucent objects. This time, we render + // bottom-to-top. + if (debug::renderTree) { + std::cout << std::string(indent++ * 4, ' ') << "TRANSLUCENT {" << std::endl; + } + --i; + for (auto it = group->layers.begin(), end = group->layers.end(); it != end; ++it, --i) { + setTranslucent(); + setStrata(i * strata_thickness); + renderLayer(*it); + } + if (debug::renderTree) { + std::cout << std::string(--indent * 4, ' ') << "}" << std::endl; + } +} + +void Painter::renderLayer(util::ptr<StyleLayer> layer_desc, const Tile::ID* id, const mat4* matrix) { + if (layer_desc->type == StyleLayerType::Background) { + // This layer defines a background color/image. + + if (debug::renderTree) { + std::cout << std::string(indent * 4, ' ') << "- " << layer_desc->id << " (" + << layer_desc->type << ")" << std::endl; + } + + renderBackground(layer_desc); + } else { + // This is a singular layer. + if (!layer_desc->bucket) { + fprintf(stderr, "[WARNING] layer '%s' is missing bucket\n", layer_desc->id.c_str()); + return; + } + + if (!layer_desc->bucket->style_source) { + fprintf(stderr, "[WARNING] can't find source for layer '%s'\n", layer_desc->id.c_str()); + return; + } + + StyleSource const& style_source = *layer_desc->bucket->style_source; + + // Skip this layer if there is no data. + if (!style_source.source) { + return; + } + + // Skip this layer if it's outside the range of min/maxzoom. + // This may occur when there /is/ a bucket created for this layer, but the min/max-zoom + // is set to a fractional value, or value that is larger than the source maxzoom. + const double zoom = state.getZoom(); + if (layer_desc->bucket->min_zoom > zoom || + layer_desc->bucket->max_zoom <= zoom) { + return; + } + + // Abort early if we can already deduce from the bucket type that + // we're not going to render anything anyway during this pass. + switch (layer_desc->type) { + case StyleLayerType::Fill: + if (!layer_desc->getProperties<FillProperties>().isVisible()) return; + break; + case StyleLayerType::Line: + if (pass == RenderPass::Opaque) return; + if (!layer_desc->getProperties<LineProperties>().isVisible()) return; + break; + case StyleLayerType::Symbol: + if (pass == RenderPass::Opaque) return; + if (!layer_desc->getProperties<SymbolProperties>().isVisible()) return; + break; + case StyleLayerType::Raster: + if (pass == RenderPass::Opaque) return; + if (!layer_desc->getProperties<RasterProperties>().isVisible()) return; + break; + default: + break; + } + + if (debug::renderTree) { + std::cout << std::string(indent * 4, ' ') << "- " << layer_desc->id << " (" + << layer_desc->type << ")" << std::endl; + } + if (!id) { + style_source.source->render(*this, layer_desc); + } else { + style_source.source->render(*this, layer_desc, *id, *matrix); + } + } +} + +void Painter::renderTileLayer(const Tile& tile, util::ptr<StyleLayer> layer_desc, const mat4 &matrix) { + assert(tile.data); + if (tile.data->hasData(*layer_desc) || layer_desc->type == StyleLayerType::Raster) { + gl::group group(std::string { "render " } + tile.data->name); + prepareTile(tile); + tile.data->render(*this, layer_desc, matrix); + } +} + +void Painter::renderBackground(util::ptr<StyleLayer> layer_desc) { + const BackgroundProperties& properties = layer_desc->getProperties<BackgroundProperties>(); + + if (properties.image.size()) { + if ((properties.opacity >= 1.0f) != (pass == RenderPass::Opaque)) + return; + + SpriteAtlasPosition imagePos = spriteAtlas.getPosition(properties.image, true); + float zoomFraction = state.getZoomFraction(); + + useProgram(patternShader->program); + patternShader->u_matrix = identityMatrix; + patternShader->u_pattern_tl = imagePos.tl; + patternShader->u_pattern_br = imagePos.br; + patternShader->u_mix = zoomFraction; + patternShader->u_opacity = properties.opacity; + + std::array<float, 2> size = imagePos.size; + double lon, lat; + state.getLonLat(lon, lat); + std::array<float, 2> center = state.locationCoordinate(lon, lat); + float scale = 1 / std::pow(2, zoomFraction); + + mat3 matrix; + matrix::identity(matrix); + matrix::scale(matrix, matrix, + 1.0f / size[0], + 1.0f / size[1]); + matrix::translate(matrix, matrix, + std::fmod(center[0] * 512, size[0]), + std::fmod(center[1] * 512, size[1])); + matrix::rotate(matrix, matrix, -state.getAngle()); + matrix::scale(matrix, matrix, + scale * state.getWidth() / 2, + -scale * state.getHeight() / 2); + patternShader->u_patternmatrix = matrix; + + backgroundBuffer.bind(); + patternShader->bind(0); + spriteAtlas.bind(true); + } else { + Color color = properties.color; + color[0] *= properties.opacity; + color[1] *= properties.opacity; + color[2] *= properties.opacity; + color[3] *= properties.opacity; + + if ((color[3] >= 1.0f) != (pass == RenderPass::Opaque)) + return; + + useProgram(plainShader->program); + plainShader->u_matrix = identityMatrix; + plainShader->u_color = color; + backgroundArray.bind(*plainShader, backgroundBuffer, BUFFER_OFFSET(0)); + } + + glDisable(GL_STENCIL_TEST); + depthRange(strata + strata_epsilon, 1.0f); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glEnable(GL_STENCIL_TEST); +} + +mat4 Painter::translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor) { + if (translation[0] == 0 && translation[1] == 0) { + return matrix; + } else { + // TODO: Get rid of the 8 (scaling from 4096 to tile size) + const double factor = ((double)(1 << id.z)) / state.getScale() * (4096.0 / util::tileSize); + + mat4 vtxMatrix; + if (anchor == TranslateAnchorType::Viewport) { + const double sin_a = std::sin(-state.getAngle()); + const double cos_a = std::cos(-state.getAngle()); + matrix::translate(vtxMatrix, matrix, + factor * (translation[0] * cos_a - translation[1] * sin_a), + factor * (translation[0] * sin_a + translation[1] * cos_a), + 0); + } else { + matrix::translate(vtxMatrix, matrix, + factor * translation[0], + factor * translation[1], + 0); + } + + return vtxMatrix; + } +} diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp new file mode 100644 index 0000000000..be4bd12710 --- /dev/null +++ b/src/mbgl/renderer/painter.hpp @@ -0,0 +1,259 @@ +#ifndef MBGL_RENDERER_PAINTER +#define MBGL_RENDERER_PAINTER + +#include <mbgl/map/tile_data.hpp> +#include <mbgl/geometry/vao.hpp> +#include <mbgl/geometry/static_vertex_buffer.hpp> +#include <mbgl/util/mat4.hpp> +#include <mbgl/util/noncopyable.hpp> +#include <mbgl/renderer/frame_history.hpp> +#include <mbgl/style/types.hpp> + +#include <mbgl/shader/plain_shader.hpp> +#include <mbgl/shader/outline_shader.hpp> +#include <mbgl/shader/pattern_shader.hpp> +#include <mbgl/shader/line_shader.hpp> +#include <mbgl/shader/linejoin_shader.hpp> +#include <mbgl/shader/linepattern_shader.hpp> +#include <mbgl/shader/icon_shader.hpp> +#include <mbgl/shader/raster_shader.hpp> +#include <mbgl/shader/sdf_shader.hpp> +#include <mbgl/shader/dot_shader.hpp> +#include <mbgl/shader/gaussian_shader.hpp> + +#include <mbgl/map/transform_state.hpp> +#include <mbgl/util/ptr.hpp> + +#include <map> +#include <unordered_map> +#include <set> + +namespace mbgl { + +enum class RenderPass : bool { Opaque, Translucent }; + +class Transform; +class Style; +class Tile; +class Sprite; +class SpriteAtlas; +class GlyphAtlas; +class Source; +class StyleSource; +class StyleLayerGroup; + +class FillBucket; +class LineBucket; +class SymbolBucket; +class RasterBucket; +class PrerenderedTexture; + +struct FillProperties; +struct RasterProperties; + +class LayerDescription; +class RasterTileData; + +class Painter : private util::noncopyable { +public: + Painter(SpriteAtlas&, GlyphAtlas&); + ~Painter(); + + void setup(); + + // Perform cleanup tasks that prepare shutting down the app. This doesn't mean that the + // app will be shut down. That means all operations must be automatically be reversed (e.g. through + // lazy initialization) in case rendering continues. + void cleanup(); + + void terminate(); + + // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any + // tiles whatsoever. + void clear(); + + // Updates the default matrices to the current viewport dimensions. + void changeMatrix(); + + void render(const Style& style, + const std::set<util::ptr<StyleSource>>& sources, + TransformState state, + timestamp time); + + void renderLayers(util::ptr<StyleLayerGroup> group); + void renderLayer(util::ptr<StyleLayer> layer_desc, const Tile::ID* id = nullptr, const mat4* matrix = nullptr); + + // Renders a particular layer from a tile. + void renderTileLayer(const Tile& tile, util::ptr<StyleLayer> layer_desc, const mat4 &matrix); + + // Renders debug information for a tile. + void renderTileDebug(const Tile& tile); + + // Renders the red debug frame around a tile, visualizing its perimeter. + void renderDebugFrame(const mat4 &matrix); + + void renderDebugText(DebugBucket& bucket, const mat4 &matrix); + void renderDebugText(const std::vector<std::string> &strings); + void renderFill(FillBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderLine(LineBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderSymbol(SymbolBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderRaster(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + void renderBackground(util::ptr<StyleLayer> layer_desc); + + float saturationFactor(float saturation); + float contrastFactor(float contrast); + std::array<float, 3> spinWeights(float spin_value); + + void preparePrerender(RasterBucket &bucket); + + void renderPrerenderedTexture(RasterBucket &bucket, const mat4 &matrix, const RasterProperties& properties); + + void createPrerendered(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id); + + void resize(); + + // Changes whether debug information is drawn onto the map + void setDebug(bool enabled); + + // Opaque/Translucent pass setting + void setOpaque(); + void setTranslucent(); + + // Configures the painter strata that is used for early z-culling of fragments. + void setStrata(float strata); + + void drawClippingMasks(const std::set<util::ptr<StyleSource>> &sources); + void drawClippingMask(const mat4& matrix, const ClipID& clip); + + void resetFramebuffer(); + void bindFramebuffer(); + void pushFramebuffer(); + GLuint popFramebuffer(); + void discardFramebuffers(); + + bool needsAnimation() const; + +private: + void setupShaders(); + void deleteShaders(); + mat4 translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor); + + void prepareTile(const Tile& tile); + + template <typename BucketProperties, typename StyleProperties> + void renderSDF(SymbolBucket &bucket, + const Tile::ID &id, + const mat4 &matrixSymbol, + const BucketProperties& bucketProperties, + const StyleProperties& styleProperties, + float scaleDivisor, + std::array<float, 2> texsize, + SDFShader& sdfShader, + void (SymbolBucket::*drawSDF)(SDFShader&)); + +public: + void useProgram(uint32_t program); + void lineWidth(float lineWidth); + void depthMask(bool value); + void depthRange(float near, float far); + +public: + mat4 projMatrix; + mat4 nativeMatrix; + mat4 extrudeMatrix; + + // used to composite images and flips the geometry upside down + const mat4 flipMatrix = []{ + mat4 flip; + matrix::ortho(flip, 0, 4096, -4096, 0, 0, 1); + matrix::translate(flip, flip, 0, -4096, 0); + return flip; + }(); + + const mat4 identityMatrix = []{ + mat4 identity; + matrix::identity(identity); + return identity; + }(); + +private: + TransformState state; + + bool debug = false; + int indent = 0; + + uint32_t gl_program = 0; + float gl_lineWidth = 0; + bool gl_depthMask = true; + std::array<uint16_t, 2> gl_viewport = {{ 0, 0 }}; + std::array<float, 2> gl_depthRange = {{ 0, 1 }}; + float strata = 0; + RenderPass pass = RenderPass::Opaque; + const float strata_epsilon = 1.0f / (1 << 16); + +public: + FrameHistory frameHistory; + + SpriteAtlas& spriteAtlas; + GlyphAtlas& glyphAtlas; + + std::unique_ptr<PlainShader> plainShader; + std::unique_ptr<OutlineShader> outlineShader; + std::unique_ptr<LineShader> lineShader; + std::unique_ptr<LinejoinShader> linejoinShader; + std::unique_ptr<LinepatternShader> linepatternShader; + std::unique_ptr<PatternShader> patternShader; + std::unique_ptr<IconShader> iconShader; + std::unique_ptr<RasterShader> rasterShader; + std::unique_ptr<SDFGlyphShader> sdfGlyphShader; + std::unique_ptr<SDFIconShader> sdfIconShader; + std::unique_ptr<DotShader> dotShader; + std::unique_ptr<GaussianShader> gaussianShader; + + StaticVertexBuffer backgroundBuffer = { + { -1, -1 }, { 1, -1 }, + { -1, 1 }, { 1, 1 } + }; + + VertexArrayObject backgroundArray; + + // Set up the stencil quad we're using to generate the stencil mask. + StaticVertexBuffer tileStencilBuffer = { + // top left triangle + { 0, 0 }, + { 4096, 0 }, + { 0, 4096 }, + + // bottom right triangle + { 4096, 0 }, + { 0, 4096 }, + { 4096, 4096 }, + }; + + VertexArrayObject coveringPlainArray; + VertexArrayObject coveringRasterArray; + VertexArrayObject coveringGaussianArray; + + // Set up the tile boundary lines we're using to draw the tile outlines. + StaticVertexBuffer tileBorderBuffer = { + { 0, 0 }, + { 4096, 0 }, + { 4096, 4096 }, + { 0, 4096 }, + { 0, 0 }, + }; + + VertexArrayObject tileBorderArray; + + // Framebuffer management + std::vector<GLuint> fbos; + std::vector<GLuint> fbos_color; + GLuint fbo_depth_stencil; + int fbo_level = -1; + bool fbo_depth_stencil_valid = false; + +}; + +} + +#endif diff --git a/src/mbgl/renderer/painter_clipping.cpp b/src/mbgl/renderer/painter_clipping.cpp new file mode 100644 index 0000000000..dc625ded4e --- /dev/null +++ b/src/mbgl/renderer/painter_clipping.cpp @@ -0,0 +1,39 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/renderer/fill_bucket.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/map/source.hpp> +#include <mbgl/util/clip_ids.hpp> + +using namespace mbgl; + +void Painter::drawClippingMasks(const std::set<util::ptr<StyleSource>> &sources) { + gl::group group("clipping masks"); + + useProgram(plainShader->program); + glDisable(GL_DEPTH_TEST); + depthMask(false); + glColorMask(false, false, false, false); + depthRange(1.0f, 1.0f); + + coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); + + for (const util::ptr<StyleSource> &source : sources) { + source->source->drawClippingMasks(*this); + } + + glEnable(GL_DEPTH_TEST); + glColorMask(true, true, true, true); + depthMask(true); + glStencilMask(0x0); +} + +void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { + plainShader->u_matrix = matrix; + + const GLint ref = (GLint)(clip.reference.to_ulong()); + const GLuint mask = (GLuint)(clip.mask.to_ulong()); + glStencilFunc(GL_ALWAYS, ref, mask); + glStencilMask(mask); + + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); +} diff --git a/src/mbgl/renderer/painter_debug.cpp b/src/mbgl/renderer/painter_debug.cpp new file mode 100644 index 0000000000..c4d273aa47 --- /dev/null +++ b/src/mbgl/renderer/painter_debug.cpp @@ -0,0 +1,102 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/renderer/debug_bucket.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/util/string.hpp> + +using namespace mbgl; + +void Painter::renderTileDebug(const Tile& tile) { + gl::group group(std::string { "debug " } + std::string(tile.id)); + assert(tile.data); + if (debug) { + prepareTile(tile); + renderDebugText(tile.data->debugBucket, tile.matrix); + renderDebugFrame(tile.matrix); + } +} + +void Painter::renderDebugText(DebugBucket& bucket, const mat4 &matrix) { + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + + useProgram(plainShader->program); + plainShader->u_matrix = matrix; + + // Draw white outline + plainShader->u_color = {{ 1.0f, 1.0f, 1.0f, 1.0f }}; + lineWidth(4.0f * state.getPixelRatio()); + bucket.drawLines(*plainShader); + +#ifndef GL_ES_VERSION_2_0 + // Draw line "end caps" + glPointSize(2); + bucket.drawPoints(*plainShader); +#endif + + // Draw black text. + plainShader->u_color = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; + lineWidth(2.0f * state.getPixelRatio()); + bucket.drawLines(*plainShader); + + glEnable(GL_DEPTH_TEST); +} + +void Painter::renderDebugFrame(const mat4 &matrix) { + 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->u_matrix = matrix; + + // draw tile outline + tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0)); + plainShader->u_color = {{ 1.0f, 0.0f, 0.0f, 1.0f }}; + lineWidth(4.0f * state.getPixelRatio()); + glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index()); + + glEnable(GL_DEPTH_TEST); +} + +void Painter::renderDebugText(const std::vector<std::string> &strings) { + if (strings.empty()) { + return; + } + + gl::group group("debug text"); + + glDisable(GL_DEPTH_TEST); + glStencilFunc(GL_ALWAYS, 0xFF, 0xFF); + + useProgram(plainShader->program); + plainShader->u_matrix = nativeMatrix; + + DebugFontBuffer debugFontBuffer; + int line = 25; + for (const std::string &str : strings) { + debugFontBuffer.addText(str.c_str(), 10, line, 0.75); + line += 20; + } + + if (!debugFontBuffer.empty()) { + // draw debug info + VertexArrayObject debugFontArray; + debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0)); + plainShader->u_color = {{ 1.0f, 1.0f, 1.0f, 1.0f }}; + lineWidth(4.0f * state.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->u_color = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; + lineWidth(2.0f * state.getPixelRatio()); + glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index()); + } + + glEnable(GL_DEPTH_TEST); +} diff --git a/src/mbgl/renderer/painter_fill.cpp b/src/mbgl/renderer/painter_fill.cpp new file mode 100644 index 0000000000..f2759ffd61 --- /dev/null +++ b/src/mbgl/renderer/painter_fill.cpp @@ -0,0 +1,122 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/renderer/fill_bucket.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/style/style_layer.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/map/sprite.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/util/std.hpp> +#include <mbgl/util/mat3.hpp> + +using namespace mbgl; + +void Painter::renderFill(FillBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { + // Abort early. + if (!bucket.hasData()) return; + + const FillProperties &properties = layer_desc->getProperties<FillProperties>(); + mat4 vtxMatrix = translatedMatrix(matrix, properties.translate, id, properties.translateAnchor); + + 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; + if (stroke_color[3] < 0) { + stroke_color = fill_color; + } else { + stroke_color[0] *= properties.opacity; + stroke_color[1] *= properties.opacity; + stroke_color[2] *= properties.opacity; + stroke_color[3] *= properties.opacity; + } + + const bool pattern = properties.image.size(); + + bool outline = properties.antialias && !pattern && properties.stroke_color != properties.fill_color; + bool fringeline = properties.antialias && !pattern && properties.stroke_color == properties.fill_color; + + // Because we're drawing top-to-bottom, and we update the stencil mask + // below, we have to draw the outline first (!) + if (outline && pass == RenderPass::Translucent) { + useProgram(outlineShader->program); + outlineShader->u_matrix = vtxMatrix; + lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! + + outlineShader->u_color = stroke_color; + + // Draw the entire line + outlineShader->u_world = {{ + static_cast<float>(state.getFramebufferWidth()), + static_cast<float>(state.getFramebufferHeight()) + }}; + depthRange(strata, 1.0f); + bucket.drawVertices(*outlineShader); + } + + if (pattern) { + // Image fill. + if (pass == RenderPass::Translucent) { + const SpriteAtlasPosition pos = spriteAtlas.getPosition(properties.image, true); + const float mix = std::fmod(float(state.getZoom()), 1.0f); + const float factor = 8.0 / std::pow(2, state.getIntegerZoom() - id.z); + + mat3 patternMatrix; + matrix::identity(patternMatrix); + matrix::scale(patternMatrix, patternMatrix, 1.0f / (pos.size[0] * factor), 1.0f / (pos.size[1] * factor)); + + useProgram(patternShader->program); + patternShader->u_matrix = vtxMatrix; + patternShader->u_pattern_tl = pos.tl; + patternShader->u_pattern_br = pos.br; + patternShader->u_opacity = properties.opacity; + patternShader->u_image = 0; + patternShader->u_mix = mix; + patternShader->u_patternmatrix = patternMatrix; + + glActiveTexture(GL_TEXTURE0); + spriteAtlas.bind(true); + + // Draw the actual triangles into the color & stencil buffer. + depthRange(strata, 1.0f); + bucket.drawElements(*patternShader); + } + } + else { + // No image fill. + if ((fill_color[3] >= 1.0f) == (pass == RenderPass::Opaque)) { + // 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->u_matrix = vtxMatrix; + plainShader->u_color = fill_color; + + // Draw the actual triangles into the color & stencil buffer. + depthRange(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 == RenderPass::Translucent) { + useProgram(outlineShader->program); + outlineShader->u_matrix = vtxMatrix; + lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio! + + outlineShader->u_color = fill_color; + + // Draw the entire line + outlineShader->u_world = {{ + static_cast<float>(state.getFramebufferWidth()), + static_cast<float>(state.getFramebufferHeight()) + }}; + + depthRange(strata + strata_epsilon, 1.0f); + bucket.drawVertices(*outlineShader); + } +} diff --git a/src/mbgl/renderer/painter_line.cpp b/src/mbgl/renderer/painter_line.cpp new file mode 100644 index 0000000000..4bf50569ac --- /dev/null +++ b/src/mbgl/renderer/painter_line.cpp @@ -0,0 +1,101 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/renderer/line_bucket.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/style/style_layer.hpp> +#include <mbgl/map/sprite.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/map/map.hpp> + +using namespace mbgl; + +void Painter::renderLine(LineBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { + // Abort early. + if (pass == RenderPass::Opaque) return; + if (!bucket.hasData()) return; + + const LineProperties &properties = layer_desc->getProperties<LineProperties>(); + + float antialiasing = 1 / state.getPixelRatio(); + float width = properties.width; + float offset = properties.gap_width == 0 ? 0 : (properties.gap_width + width) / 2; + float blur = properties.blur + antialiasing; + + float inset = std::fmin((std::fmax(-1, offset - width / 2 - antialiasing / 2) + 1), 16.0f); + float outset = std::fmin(offset + width / 2 + antialiasing / 2, 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]; + + float ratio = state.getPixelRatio(); + mat4 vtxMatrix = translatedMatrix(matrix, properties.translate, id, properties.translateAnchor); + + depthRange(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->u_matrix = vtxMatrix; + linejoinShader->u_color = color; + linejoinShader->u_world = {{ + state.getFramebufferWidth() * 0.5f, + state.getFramebufferHeight() * 0.5f + }}; + linejoinShader->u_linewidth = {{ + ((outset - 0.25f) * state.getPixelRatio()), + ((inset - 0.25f) * state.getPixelRatio()) + }}; + + float pointSize = std::ceil(state.getPixelRatio() * outset * 2.0); +#if defined(GL_ES_VERSION_2_0) + linejoinShader->u_size = pointSize; +#else + glPointSize(pointSize); +#endif + bucket.drawPoints(*linejoinShader); + } + + if (properties.image.size()) { + SpriteAtlasPosition imagePos = spriteAtlas.getPosition(properties.image); + + float factor = 8.0 / std::pow(2, state.getIntegerZoom() - id.z); + float fade = std::fmod(state.getZoom(), 1.0); + + useProgram(linepatternShader->program); + + linepatternShader->u_matrix = vtxMatrix; + linepatternShader->u_exmatrix = extrudeMatrix; + linepatternShader->u_linewidth = {{ outset, inset }}; + linepatternShader->u_ratio = ratio; + linepatternShader->u_blur = blur; + + linepatternShader->u_pattern_size = {{imagePos.size[0] * factor, imagePos.size[1]}}; + linepatternShader->u_pattern_tl = imagePos.tl; + linepatternShader->u_pattern_br = imagePos.br; + linepatternShader->u_fade = fade; + + spriteAtlas.bind(true); + glDepthRange(strata + strata_epsilon, 1.0f); // may or may not matter + + bucket.drawLinePatterns(*linepatternShader); + + } else { + useProgram(lineShader->program); + + lineShader->u_matrix = vtxMatrix; + lineShader->u_exmatrix = extrudeMatrix; + lineShader->u_linewidth = {{ outset, inset }}; + lineShader->u_ratio = ratio; + lineShader->u_blur = blur; + + lineShader->u_color = color; + lineShader->u_dasharray = {{ dash_length, dash_gap }}; + + bucket.drawLines(*lineShader); + } +} diff --git a/src/mbgl/renderer/painter_prerender.cpp b/src/mbgl/renderer/painter_prerender.cpp new file mode 100644 index 0000000000..22a2be9b64 --- /dev/null +++ b/src/mbgl/renderer/painter_prerender.cpp @@ -0,0 +1,45 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/style/style_properties.hpp> +#include <mbgl/renderer/prerendered_texture.hpp> +#include <mbgl/renderer/raster_bucket.hpp> + +using namespace mbgl; + +void Painter::preparePrerender(RasterBucket &bucket) { + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + +// Render the actual tile. +#if GL_EXT_discard_framebuffer && !__ANDROID__ + const GLenum discards[] = {GL_COLOR_ATTACHMENT0}; + glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards); +#endif + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, bucket.properties.size, bucket.properties.size); +} + +void Painter::renderPrerenderedTexture(RasterBucket &bucket, const mat4 &matrix, const RasterProperties& properties) { + const int buffer = bucket.properties.buffer * 4096.0f; + + // draw the texture on a quad + useProgram(rasterShader->program); + rasterShader->u_matrix = matrix; + rasterShader->u_opacity = 1; + + depthRange(strata, 1.0f); + + glActiveTexture(GL_TEXTURE0); + rasterShader->u_image = 0; + rasterShader->u_buffer = buffer; + rasterShader->u_opacity = properties.opacity; + rasterShader->u_brightness_low = properties.brightness[0]; + rasterShader->u_brightness_high = properties.brightness[1]; + rasterShader->u_saturation_factor = saturationFactor(properties.saturation); + rasterShader->u_contrast_factor = contrastFactor(properties.contrast); + rasterShader->u_spin_weights = spinWeights(properties.hue_rotate); + bucket.texture.bindTexture(); + coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()); +} diff --git a/src/mbgl/renderer/painter_raster.cpp b/src/mbgl/renderer/painter_raster.cpp new file mode 100644 index 0000000000..df655cdae8 --- /dev/null +++ b/src/mbgl/renderer/painter_raster.cpp @@ -0,0 +1,107 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/platform/gl.hpp> +#include <mbgl/renderer/raster_bucket.hpp> +#include <mbgl/style/style_layer.hpp> +#include <mbgl/style/style_layer_group.hpp> +#include <mbgl/util/std.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/map/transform.hpp> + +using namespace mbgl; + +void Painter::renderRaster(RasterBucket& bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix) { + if (pass != RenderPass::Translucent) return; + + const RasterProperties &properties = layer_desc->getProperties<RasterProperties>(); + + if (layer_desc->layers) { + + if (!bucket.texture.getTexture()) { + + bucket.texture.bindFramebuffer(); + + preparePrerender(bucket); + + const int buffer = bucket.properties.buffer * 4096.0f; + + const mat4 preMatrix = [&]{ + mat4 vtxMatrix; + matrix::ortho(vtxMatrix, -buffer, 4096 + buffer, -4096 - buffer, buffer, 0, 1); + matrix::translate(vtxMatrix, vtxMatrix, 0, -4096, 0); + return vtxMatrix; + }(); + + for (const util::ptr<StyleLayer> &layer : layer_desc->layers->layers) { + setOpaque(); + renderLayer(layer, &id, &preMatrix); + setTranslucent(); + renderLayer(layer, &id, &preMatrix); + } + + if (bucket.properties.blur > 0) { + bucket.texture.blur(*this, bucket.properties.blur); + } + + bucket.texture.unbindFramebuffer(); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_STENCIL_TEST); + + glViewport(0, 0, gl_viewport[0], gl_viewport[1]); + + } + + renderPrerenderedTexture(bucket, matrix, properties); + + } + + // Only draw non-prerendered raster here + if (bucket.hasData()) { + depthMask(false); + + useProgram(rasterShader->program); + rasterShader->u_matrix = matrix; + rasterShader->u_buffer = 0; + rasterShader->u_opacity = properties.opacity; + rasterShader->u_brightness_low = properties.brightness[0]; + rasterShader->u_brightness_high = properties.brightness[1]; + rasterShader->u_saturation_factor = saturationFactor(properties.saturation); + rasterShader->u_contrast_factor = contrastFactor(properties.contrast); + rasterShader->u_spin_weights = spinWeights(properties.hue_rotate); + + depthRange(strata + strata_epsilon, 1.0f); + + bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray); + + depthMask(true); + } + +} + +float Painter::saturationFactor(float saturation) { + if (saturation > 0) { + return 1 - 1 / (1.001 - saturation); + } else { + return -saturation; + } +} + +float Painter::contrastFactor(float contrast) { + if (contrast > 0) { + return 1 / (1 - contrast); + } else { + return 1 + contrast; + } +} + +std::array<float, 3> Painter::spinWeights(float spin) { + spin *= M_PI / 180; + float s = std::sin(spin); + float c = std::cos(spin); + std::array<float, 3> spin_weights = {{ + (2 * c + 1) / 3, + (-std::sqrt(3.0f) * s - c + 1) / 3, + (std::sqrt(3.0f) * s - c + 1) / 3 + }}; + return spin_weights; +} diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp new file mode 100644 index 0000000000..79625f1681 --- /dev/null +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -0,0 +1,208 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/style/style_layer.hpp> +#include <mbgl/geometry/glyph_atlas.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/util/math.hpp> +#include <cmath> + +using namespace mbgl; + +template <typename BucketProperties, typename StyleProperties> +void Painter::renderSDF(SymbolBucket &bucket, + const Tile::ID &id, + const mat4 &matrix, + const BucketProperties& bucketProperties, + const StyleProperties& styleProperties, + float sdfFontSize, + std::array<float, 2> texsize, + SDFShader& sdfShader, + void (SymbolBucket::*drawSDF)(SDFShader&)) +{ + mat4 vtxMatrix = translatedMatrix(matrix, styleProperties.translate, id, styleProperties.translate_anchor); + + mat4 exMatrix; + matrix::copy(exMatrix, projMatrix); + + bool aligned_with_map = (bucketProperties.rotation_alignment == RotationAlignmentType::Map); + const float angleOffset = aligned_with_map ? state.getAngle() : 0; + + if (angleOffset) { + matrix::rotate_z(exMatrix, exMatrix, angleOffset); + } + + // If layerStyle.size > bucket.info.fontSize then labels may collide + float fontSize = std::fmin(styleProperties.size, bucketProperties.max_size); + float fontScale = fontSize / sdfFontSize; + matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f); + + useProgram(sdfShader.program); + sdfShader.u_matrix = vtxMatrix; + sdfShader.u_exmatrix = exMatrix; + sdfShader.u_texsize = texsize; + + // Convert the -pi..pi to an int8 range. + float angle = std::round(state.getAngle() / M_PI * 128); + + // adjust min/max zooms for variable font sies + float zoomAdjust = std::log(fontSize / bucketProperties.max_size) / std::log(2); + + sdfShader.u_flip = (aligned_with_map && bucketProperties.keep_upright) ? 1 : 0; + sdfShader.u_angle = (int32_t)(angle + 256) % 256; + sdfShader.u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level + + FadeProperties f = frameHistory.getFadeProperties(300_milliseconds); + sdfShader.u_fadedist = f.fadedist * 10; + sdfShader.u_minfadezoom = std::floor(f.minfadezoom * 10); + sdfShader.u_maxfadezoom = std::floor(f.maxfadezoom * 10); + sdfShader.u_fadezoom = (state.getNormalizedZoom() + f.bump) * 10; + + // The default gamma value has to be adjust for the current pixelratio so that we're not + // drawing blurry font on retina screens. + const float gamma = 0.105 * sdfFontSize / fontSize / state.getPixelRatio(); + + const float sdfPx = 8.0f; + const float blurOffset = 1.19f; + const float haloOffset = 6.0f; + + // We're drawing in the translucent pass which is bottom-to-top, so we need + // to draw the halo first. + if (styleProperties.halo_color[3] > 0.0f) { + sdfShader.u_gamma = styleProperties.halo_blur * blurOffset / fontScale / sdfPx + gamma; + + if (styleProperties.opacity < 1.0f) { + Color color = styleProperties.halo_color; + color[0] *= styleProperties.opacity; + color[1] *= styleProperties.opacity; + color[2] *= styleProperties.opacity; + color[3] *= styleProperties.opacity; + sdfShader.u_color = color; + } else { + sdfShader.u_color = styleProperties.halo_color; + } + + sdfShader.u_buffer = (haloOffset - styleProperties.halo_width / fontScale) / sdfPx; + + depthRange(strata, 1.0f); + (bucket.*drawSDF)(sdfShader); + } + + // Then, we draw the text/icon over the halo + if (styleProperties.color[3] > 0.0f) { + sdfShader.u_gamma = gamma; + + if (styleProperties.opacity < 1.0f) { + Color color = styleProperties.color; + color[0] *= styleProperties.opacity; + color[1] *= styleProperties.opacity; + color[2] *= styleProperties.opacity; + color[3] *= styleProperties.opacity; + sdfShader.u_color = color; + } else { + sdfShader.u_color = styleProperties.color; + } + + sdfShader.u_buffer = (256.0f - 64.0f) / 256.0f; + + depthRange(strata + strata_epsilon, 1.0f); + (bucket.*drawSDF)(sdfShader); + } +} + +void Painter::renderSymbol(SymbolBucket &bucket, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { + // Abort early. + if (pass == RenderPass::Opaque) { + return; + } + + const SymbolProperties &properties = layer_desc->getProperties<SymbolProperties>(); + + glDisable(GL_STENCIL_TEST); + + if (bucket.hasIconData()) { + bool sdf = bucket.sdfIcons; + + const float angleOffset = + bucket.properties.icon.rotation_alignment == RotationAlignmentType::Map + ? state.getAngle() + : 0; + + // If layerStyle.size > bucket.info.fontSize then labels may collide + const float fontSize = properties.icon.size != 0 ? properties.icon.size : bucket.properties.icon.max_size; + const float fontScale = fontSize / 1.0f; + + spriteAtlas.bind(state.isChanging() || bucket.properties.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1 || sdf); + + std::array<float, 2> texsize = {{ + float(spriteAtlas.getWidth()), + float(spriteAtlas.getHeight()) + }}; + + if (sdf) { + renderSDF(bucket, + id, + matrix, + bucket.properties.icon, + properties.icon, + 1.0f, + texsize, + *sdfIconShader, + &SymbolBucket::drawIcons); + } else { + mat4 vtxMatrix = translatedMatrix(matrix, properties.icon.translate, id, properties.icon.translate_anchor); + + mat4 exMatrix; + matrix::copy(exMatrix, projMatrix); + + if (angleOffset) { + matrix::rotate_z(exMatrix, exMatrix, angleOffset); + } + + matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f); + + useProgram(iconShader->program); + iconShader->u_matrix = vtxMatrix; + iconShader->u_exmatrix = exMatrix; + iconShader->u_texsize = texsize; + + // Convert the -pi..pi to an int8 range. + const float angle = std::round(state.getAngle() / M_PI * 128); + + // adjust min/max zooms for variable font sies + float zoomAdjust = std::log(fontSize / bucket.properties.icon.max_size) / std::log(2); + + iconShader->u_angle = (int32_t)(angle + 256) % 256; + + bool flip = (bucket.properties.icon.rotation_alignment == RotationAlignmentType::Map) + && bucket.properties.icon.keep_upright; + iconShader->u_flip = flip ? 1 : 0; + iconShader->u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level + + iconShader->u_fadedist = 0 * 10; + iconShader->u_minfadezoom = state.getNormalizedZoom() * 10; + iconShader->u_maxfadezoom = state.getNormalizedZoom() * 10; + iconShader->u_fadezoom = state.getNormalizedZoom() * 10; + iconShader->u_opacity = properties.icon.opacity; + + depthRange(strata, 1.0f); + bucket.drawIcons(*iconShader); + } + } + + if (bucket.hasTextData()) { + glyphAtlas.bind(); + + renderSDF(bucket, + id, + matrix, + bucket.properties.text, + properties.text, + 24.0f, + {{ float(glyphAtlas.width) / 4, float(glyphAtlas.height) / 4 }}, + *sdfGlyphShader, + &SymbolBucket::drawGlyphs); + } + + glEnable(GL_STENCIL_TEST); +} diff --git a/src/mbgl/renderer/prerendered_texture.cpp b/src/mbgl/renderer/prerendered_texture.cpp new file mode 100644 index 0000000000..f17fe3b82a --- /dev/null +++ b/src/mbgl/renderer/prerendered_texture.cpp @@ -0,0 +1,186 @@ +#include <mbgl/renderer/prerendered_texture.hpp> + +#include <mbgl/renderer/painter.hpp> +#include <mbgl/style/style_bucket.hpp> + +#include <mbgl/platform/log.hpp> + +using namespace mbgl; + +PrerenderedTexture::PrerenderedTexture(const StyleBucketRaster &properties_) + : properties(properties_) { +} + +PrerenderedTexture::~PrerenderedTexture() { + if (texture != 0) { + glDeleteTextures(1, &texture); + texture = 0; + } + + if (fboDepth != 0) { + glDeleteRenderbuffers(1, &fboDepth); + fboDepth = 0; + } + + if (fboStencil != 0) { + glDeleteRenderbuffers(1, &fboStencil); + fboStencil = 0; + } + + if (fbo != 0) { + glDeleteFramebuffers(1, &fbo); + fbo = 0; + } +} + + +void PrerenderedTexture::bindTexture() { + if (texture == 0) { + bindFramebuffer(); + unbindFramebuffer(); + } + + glBindTexture(GL_TEXTURE_2D, texture); +} + +void PrerenderedTexture::bindFramebuffer() { + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFbo); + + if (texture == 0) { + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); +#ifndef GL_ES_VERSION_2_0 + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); +#endif + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, properties.size, properties.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); + } + + if (fboDepth == 0) { + // Create depth buffer + glGenRenderbuffers(1, &fboDepth); + glBindRenderbuffer(GL_RENDERBUFFER, fboDepth); + if (gl::isPackedDepthStencilSupported) { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, properties.size, properties.size); + } else { + if (gl::isDepth24Supported) { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, properties.size, properties.size); + } else { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, properties.size, properties.size); + } + } + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + } + + if (!gl::isPackedDepthStencilSupported && (fboStencil == 0)) { + // Create stencil buffer + glGenRenderbuffers(1, &fboStencil); + glBindRenderbuffer(GL_RENDERBUFFER, fboStencil); + glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, properties.size, properties.size); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + } + + if (fbo == 0) { + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + + if (gl::isPackedDepthStencilSupported) { +#ifdef GL_ES_VERSION_2_0 + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboDepth); +#else + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboDepth); +#endif + } else { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fboDepth); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fboStencil); + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + mbgl::Log::Error(mbgl::Event::OpenGL, "Couldn't create framebuffer: "); + switch (status) { + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete attachment\n"); break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete missing attachment\n"); break; +#ifdef GL_ES_VERSION_2_0 + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete dimensions\n"); break; +#else + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: mbgl::Log::Error(mbgl::Event::OpenGL, "incomplete draw buffer\n"); break; +#endif + case GL_FRAMEBUFFER_UNSUPPORTED: mbgl::Log::Error(mbgl::Event::OpenGL, "unsupported\n"); break; + default: mbgl::Log::Error(mbgl::Event::OpenGL, "other\n"); break; + } + return; + } + } else { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + } +} + +void PrerenderedTexture::unbindFramebuffer() { + glBindFramebuffer(GL_FRAMEBUFFER, previousFbo); + + if (fbo != 0) { + glDeleteFramebuffers(1, &fbo); + fbo = 0; + } +} + +void PrerenderedTexture::blur(Painter& painter, uint16_t passes) { + const GLuint originalTexture = texture; + + // Create a secondary texture + GLuint secondaryTexture; + glGenTextures(1, &secondaryTexture); + glBindTexture(GL_TEXTURE_2D, secondaryTexture); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, properties.size, properties.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + + painter.useProgram(painter.gaussianShader->program); + painter.gaussianShader->u_matrix = painter.flipMatrix; + painter.gaussianShader->u_image = 0; + glActiveTexture(GL_TEXTURE0); + + for (int i = 0; i < passes; i++) { + // Render horizontal + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, secondaryTexture, 0); +#if GL_EXT_discard_framebuffer && !__ANDROID__ + const GLenum discards[] = { GL_COLOR_ATTACHMENT0 }; + glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards); +#endif + glClear(GL_COLOR_BUFFER_BIT); + + painter.gaussianShader->u_offset = {{ 1.0f / float(properties.size), 0 }}; + glBindTexture(GL_TEXTURE_2D, originalTexture); + painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index()); + + + + // Render vertical + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, originalTexture, 0); +#if GL_EXT_discard_framebuffer && !__ANDROID__ + glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, discards); +#endif + glClear(GL_COLOR_BUFFER_BIT); + + painter.gaussianShader->u_offset = {{ 0, 1.0f / float(properties.size) }}; + glBindTexture(GL_TEXTURE_2D, secondaryTexture); + painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index()); + } + + glDeleteTextures(1, &secondaryTexture); +} diff --git a/src/mbgl/renderer/prerendered_texture.hpp b/src/mbgl/renderer/prerendered_texture.hpp new file mode 100644 index 0000000000..3ccd24038d --- /dev/null +++ b/src/mbgl/renderer/prerendered_texture.hpp @@ -0,0 +1,38 @@ +#ifndef MBGL_RENDERER_PRERENDERED_TEXTURE +#define MBGL_RENDERER_PRERENDERED_TEXTURE + +#include <mbgl/util/noncopyable.hpp> +#include <mbgl/platform/gl.hpp> + +namespace mbgl { + +class StyleBucketRaster; +class Painter; + +class PrerenderedTexture : private util::noncopyable { +public: + PrerenderedTexture(const StyleBucketRaster &properties); + ~PrerenderedTexture(); + + void bindTexture(); + void bindFramebuffer(); + void unbindFramebuffer(); + + inline GLuint getTexture() const { return texture; } + + void blur(Painter& painter, uint16_t passes); + +public: + const StyleBucketRaster &properties; + +private: + GLint previousFbo = 0; + GLuint fbo = 0; + GLuint texture = 0; + GLuint fboDepth= 0; + GLuint fboStencil = 0; +}; + +} + +#endif diff --git a/src/mbgl/renderer/raster_bucket.cpp b/src/mbgl/renderer/raster_bucket.cpp new file mode 100644 index 0000000000..85bb66970e --- /dev/null +++ b/src/mbgl/renderer/raster_bucket.cpp @@ -0,0 +1,36 @@ +#include <mbgl/renderer/raster_bucket.hpp> +#include <mbgl/renderer/painter.hpp> + +using namespace mbgl; + +RasterBucket::RasterBucket(TexturePool& texturePool, const StyleBucketRaster& properties_) +: properties(properties_), + texture(properties_), + raster(texturePool) { +} + +void RasterBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) { + painter.renderRaster(*this, layer_desc, id, matrix); +} + +bool RasterBucket::setImage(const std::string &data) { + return raster.load(data); +} + +void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array) { + raster.bind(true); + shader.u_image = 0; + array.bind(shader, vertices, BUFFER_OFFSET(0)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index()); +} + +void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array, GLuint texture_) { + raster.bind(texture_); + shader.u_image = 0; + array.bind(shader, vertices, BUFFER_OFFSET(0)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index()); +} + +bool RasterBucket::hasData() const { + return raster.isLoaded(); +} diff --git a/src/mbgl/renderer/raster_bucket.hpp b/src/mbgl/renderer/raster_bucket.hpp new file mode 100644 index 0000000000..0a7651d7cc --- /dev/null +++ b/src/mbgl/renderer/raster_bucket.hpp @@ -0,0 +1,38 @@ +#ifndef MBGL_RENDERER_RASTERBUCKET +#define MBGL_RENDERER_RASTERBUCKET + +#include <mbgl/renderer/bucket.hpp> +#include <mbgl/util/raster.hpp> +#include <mbgl/renderer/prerendered_texture.hpp> +#include <mbgl/style/style_bucket.hpp> + + + +namespace mbgl { + +class RasterShader; +class StaticVertexBuffer; +class VertexArrayObject; + +class RasterBucket : public Bucket { +public: + RasterBucket(TexturePool&, const StyleBucketRaster&); + + virtual void render(Painter& painter, util::ptr<StyleLayer> layer_desc, const Tile::ID& id, const mat4 &matrix); + virtual bool hasData() const; + + bool setImage(const std::string &data); + + const StyleBucketRaster &properties; + PrerenderedTexture texture; + + void drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array); + + void drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array, GLuint texture); + + Raster raster; +}; + +} + +#endif diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp new file mode 100644 index 0000000000..a005449628 --- /dev/null +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -0,0 +1,427 @@ +#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/geometry/text_buffer.hpp> +#include <mbgl/geometry/icon_buffer.hpp> +#include <mbgl/geometry/glyph_atlas.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/geometry/geometry.hpp> +#include <mbgl/geometry/anchor.hpp> +#include <mbgl/geometry/resample.hpp> +#include <mbgl/renderer/painter.hpp> +#include <mbgl/text/glyph_store.hpp> +#include <mbgl/text/placement.hpp> +#include <mbgl/platform/log.hpp> +#include <mbgl/text/collision.hpp> +#include <mbgl/map/sprite.hpp> + +#include <mbgl/util/utf.hpp> +#include <mbgl/util/token.hpp> +#include <mbgl/util/math.hpp> + +namespace mbgl { + +SymbolBucket::SymbolBucket(const StyleBucketSymbol &properties_, Collision &collision_) + : properties(properties_), collision(collision_) {} + +void SymbolBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc, + const Tile::ID &id, const mat4 &matrix) { + painter.renderSymbol(*this, layer_desc, id, matrix); +} + +bool SymbolBucket::hasData() const { return hasTextData() || hasIconData(); } + +bool SymbolBucket::hasTextData() const { return !text.groups.empty(); } + +bool SymbolBucket::hasIconData() const { return !icon.groups.empty(); } + +void SymbolBucket::addGlyphsToAtlas(uint64_t tileid, const std::string stackname, + const std::u32string &text, const FontStack &fontStack, + GlyphAtlas &glyphAtlas, GlyphPositions &face) { + glyphAtlas.addGlyphs(tileid, text, stackname, fontStack,face); +} + +std::vector<SymbolFeature> SymbolBucket::processFeatures(const VectorTileLayer &layer, + const FilterExpression &filter, + GlyphStore &glyphStore, + const Sprite &sprite) { + const bool has_text = properties.text.field.size(); + const bool has_icon = properties.icon.image.size(); + + std::vector<SymbolFeature> features; + + if (!has_text && !has_icon) { + return features; + } + + // Determine and load glyph ranges + std::set<GlyphRange> ranges; + + FilteredVectorTileLayer filtered_layer(layer, filter); + for (const pbf &feature_pbf : filtered_layer) { + const VectorTileFeature feature{feature_pbf, layer}; + + SymbolFeature ft; + + if (has_text) { + std::string u8string = util::replaceTokens(properties.text.field, feature.properties); + + if (properties.text.transform == TextTransformType::Uppercase) { + u8string = platform::uppercase(u8string); + } else if (properties.text.transform == TextTransformType::Lowercase) { + u8string = platform::lowercase(u8string); + } + + ft.label = util::utf8_to_utf32::convert(u8string); + + if (ft.label.size()) { + // Loop through all characters of this text and collect unique codepoints. + for (char32_t chr : ft.label) { + ranges.insert(getGlyphRange(chr)); + } + } + } + + if (has_icon) { + ft.sprite = util::replaceTokens(properties.icon.image, feature.properties); + } + + if (ft.label.length() || ft.sprite.length()) { + ft.geometry = feature.geometry; + features.push_back(std::move(ft)); + } + } + + glyphStore.waitForGlyphRanges(properties.text.font, ranges); + sprite.waitUntilLoaded(); + + return features; +} + +void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter, + const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite, + GlyphAtlas & glyphAtlas, GlyphStore &glyphStore) { + + const std::vector<SymbolFeature> features = processFeatures(layer, filter, glyphStore, sprite); + + float horizontalAlign = 0.5; + float verticalAlign = 0.5; + + switch (properties.text.anchor) { + case TextAnchorType::Top: + case TextAnchorType::Bottom: + case TextAnchorType::Center: + break; + case TextAnchorType::Right: + case TextAnchorType::TopRight: + case TextAnchorType::BottomRight: + horizontalAlign = 1; + break; + case TextAnchorType::Left: + case TextAnchorType::TopLeft: + case TextAnchorType::BottomLeft: + horizontalAlign = 0; + break; + } + + switch (properties.text.anchor) { + case TextAnchorType::Left: + case TextAnchorType::Right: + case TextAnchorType::Center: + break; + case TextAnchorType::Bottom: + case TextAnchorType::BottomLeft: + case TextAnchorType::BottomRight: + verticalAlign = 1; + break; + case TextAnchorType::Top: + case TextAnchorType::TopLeft: + case TextAnchorType::TopRight: + verticalAlign = 0; + break; + } + + float justify = 0.5; + if (properties.text.justify == TextJustifyType::Right) justify = 1; + else if (properties.text.justify == TextJustifyType::Left) justify = 0; + + const FontStack &fontStack = glyphStore.getFontStack(properties.text.font); + + for (const SymbolFeature &feature : features) { + Shaping shaping; + Rect<uint16_t> image; + GlyphPositions face; + + // if feature has text, shape the text + if (feature.label.length()) { + shaping = fontStack.getShaping( + /* string */ feature.label, + /* maxWidth */ properties.text.max_width, + /* lineHeight */ properties.text.line_height, + /* horizontalAlign */ horizontalAlign, + /* verticalAlign */ verticalAlign, + /* justify */ justify, + /* spacing */ properties.text.letter_spacing, + /* translate */ properties.text.offset); + + // Add the glyphs we need for this label to the glyph atlas. + if (shaping.size()) { + SymbolBucket::addGlyphsToAtlas(id.to_uint64(), properties.text.font, feature.label, fontStack, + glyphAtlas, face); + } + } + + // if feature has icon, get sprite atlas position + if (feature.sprite.length()) { + sprite.waitUntilLoaded(); + image = spriteAtlas.getImage(feature.sprite); + + if (sprite.getSpritePosition(feature.sprite).sdf) { + sdfIcons = true; + } + } + + // if either shaping or icon position is present, add the feature + if (shaping.size() || image) { + addFeature(feature.geometry, shaping, face, image); + } + } +} + +void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping, + const GlyphPositions &face, const Rect<uint16_t> &image) { + // Decode all lines. + std::vector<Coordinate> line; + Geometry::command cmd; + + Coordinate coord; + pbf geom(geom_pbf); + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (!line.empty()) { + addFeature(line, shaping, face, image); + line.clear(); + } + } + line.emplace_back(x, y); + } + if (line.size()) { + addFeature(line, shaping, face, image); + } +} + +bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; } + +const PlacementRange fullRange{{2 * M_PI, 0}}; + +void SymbolBucket::addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, + const GlyphPositions &face, const Rect<uint16_t> &image) { + assert(line.size()); + + const float minScale = 0.5f; + const float glyphSize = 24.0f; + + const bool horizontalText = + properties.text.rotation_alignment == RotationAlignmentType::Viewport; + const bool horizontalIcon = + properties.icon.rotation_alignment == RotationAlignmentType::Viewport; + const float fontScale = properties.text.max_size / glyphSize; + const float textBoxScale = collision.tilePixelRatio * fontScale; + const float iconBoxScale = collision.tilePixelRatio * properties.icon.max_size; + const bool iconWithoutText = properties.text.optional || !shaping.size(); + const bool textWithoutIcon = properties.icon.optional || !image; + const bool avoidEdges = properties.avoid_edges && properties.placement != PlacementType::Line; + + Anchors anchors; + + if (properties.placement == PlacementType::Line) { + // Line labels + anchors = resample(line, properties.min_distance, minScale, collision.maxPlacementScale, + collision.tilePixelRatio); + + // Sort anchors by segment so that we can start placement with the + // anchors that can be shown at the lowest zoom levels. + std::sort(anchors.begin(), anchors.end(), byScale); + + } else { + // Point labels + anchors = {Anchor{float(line[0].x), float(line[0].y), 0, minScale}}; + } + + // TODO: figure out correct ascender height. + const vec2<float> origin = {0, -17}; + + for (Anchor &anchor : anchors) { + + // Calculate the scales at which the text and icons can be first shown without overlap + Placement glyphPlacement; + Placement iconPlacement; + float glyphScale = 0; + float iconScale = 0; + const bool inside = !(anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096); + + if (avoidEdges && !inside) continue; + + if (shaping.size()) { + glyphPlacement = Placement::getGlyphs(anchor, origin, shaping, face, textBoxScale, + horizontalText, line, properties); + glyphScale = + properties.text.allow_overlap + ? glyphPlacement.minScale + : collision.getPlacementScale(glyphPlacement.boxes, glyphPlacement.minScale, avoidEdges); + if (!glyphScale && !iconWithoutText) + continue; + } + + if (image) { + iconPlacement = Placement::getIcon(anchor, image, iconBoxScale, line, properties); + iconScale = + properties.icon.allow_overlap + ? iconPlacement.minScale + : collision.getPlacementScale(iconPlacement.boxes, iconPlacement.minScale, avoidEdges); + if (!iconScale && !textWithoutIcon) + continue; + } + + if (!iconWithoutText && !textWithoutIcon) { + iconScale = glyphScale = util::max(iconScale, glyphScale); + } else if (!textWithoutIcon && glyphScale) { + glyphScale = util::max(iconScale, glyphScale); + } else if (!iconWithoutText && iconScale) { + iconScale = util::max(iconScale, glyphScale); + } + + // Get the rotation ranges it is safe to show the glyphs + PlacementRange glyphRange = + (!glyphScale || properties.text.allow_overlap) + ? fullRange + : collision.getPlacementRange(glyphPlacement.boxes, glyphScale, horizontalText); + PlacementRange iconRange = + (!iconScale || properties.icon.allow_overlap) + ? fullRange + : collision.getPlacementRange(iconPlacement.boxes, iconScale, horizontalIcon); + + const PlacementRange maxRange = {{ + util::min(iconRange[0], glyphRange[0]), util::max(iconRange[1], glyphRange[1]), + }}; + + if (!iconWithoutText && !textWithoutIcon) { + iconRange = glyphRange = maxRange; + } else if (!textWithoutIcon) { + glyphRange = maxRange; + } else if (!iconWithoutText) { + iconRange = maxRange; + } + + // Insert final placement into collision tree and add glyphs/icons to buffers + if (glyphScale && std::isfinite(glyphScale)) { + if (!properties.text.ignore_placement) { + collision.insert(glyphPlacement.boxes, anchor, glyphScale, glyphRange, + horizontalText); + } + if (inside) addSymbols(text, glyphPlacement.shapes, glyphScale, glyphRange); + } + + if (iconScale && std::isfinite(iconScale)) { + if (!properties.icon.ignore_placement) { + collision.insert(iconPlacement.boxes, anchor, iconScale, iconRange, horizontalIcon); + } + if (inside) addSymbols(icon, iconPlacement.shapes, iconScale, iconRange); + } + } +} + +template <typename Buffer> +void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, + PlacementRange placementRange) { + const float zoom = collision.zoom; + + const float placementZoom = std::log(scale) / std::log(2) + zoom; + + for (const PlacedGlyph &symbol : symbols) { + const auto &tl = symbol.tl; + const auto &tr = symbol.tr; + const auto &bl = symbol.bl; + const auto &br = symbol.br; + const auto &tex = symbol.tex; + const auto &angle = symbol.angle; + + float minZoom = + util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)), placementZoom); + float maxZoom = util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f); + const auto &glyphAnchor = symbol.anchor; + + if (maxZoom <= minZoom) + continue; + + // Lower min zoom so that while fading out the label + // it can be shown outside of collision-free zoom levels + if (minZoom == placementZoom) { + minZoom = 0; + } + + const int glyph_vertex_length = 4; + + if (!buffer.groups.size() || + (buffer.groups.back().vertex_length + glyph_vertex_length > 65535)) { + // Move to a new group because the old one can't hold the geometry. + buffer.groups.emplace_back(); + } + + // We're generating triangle fans, so we always start with the first + // coordinate in this polygon. + auto &triangleGroup = buffer.groups.back(); + uint32_t triangleIndex = triangleGroup.vertex_length; + + // coordinates (2 triangles) + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom, + placementRange, maxZoom, placementZoom); + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle, + minZoom, placementRange, maxZoom, placementZoom); + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle, + minZoom, placementRange, maxZoom, placementZoom); + buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, + angle, minZoom, placementRange, maxZoom, placementZoom); + + // add the two triangles, referencing the four coordinates we just inserted. + buffer.triangles.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); + buffer.triangles.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); + + triangleGroup.vertex_length += glyph_vertex_length; + triangleGroup.elements_length += 2; + } +} + +void SymbolBucket::drawGlyphs(SDFShader &shader) { + char *vertex_index = BUFFER_OFFSET(0); + char *elements_index = BUFFER_OFFSET(0); + for (TextElementGroup &group : text.groups) { + group.array[0].bind(shader, text.vertices, text.triangles, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * text.vertices.itemSize; + elements_index += group.elements_length * text.triangles.itemSize; + } +} + +void SymbolBucket::drawIcons(SDFShader &shader) { + char *vertex_index = BUFFER_OFFSET(0); + char *elements_index = BUFFER_OFFSET(0); + for (IconElementGroup &group : icon.groups) { + group.array[0].bind(shader, icon.vertices, icon.triangles, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * icon.vertices.itemSize; + elements_index += group.elements_length * icon.triangles.itemSize; + } +} + +void SymbolBucket::drawIcons(IconShader &shader) { + char *vertex_index = BUFFER_OFFSET(0); + char *elements_index = BUFFER_OFFSET(0); + for (IconElementGroup &group : icon.groups) { + group.array[1].bind(shader, icon.vertices, icon.triangles, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * icon.vertices.itemSize; + elements_index += group.elements_length * icon.triangles.itemSize; + } +} +} diff --git a/src/mbgl/renderer/symbol_bucket.hpp b/src/mbgl/renderer/symbol_bucket.hpp new file mode 100644 index 0000000000..dd596b1a00 --- /dev/null +++ b/src/mbgl/renderer/symbol_bucket.hpp @@ -0,0 +1,114 @@ +#ifndef MBGL_RENDERER_SYMBOLBUCKET +#define MBGL_RENDERER_SYMBOLBUCKET + +#include <mbgl/renderer/bucket.hpp> +#include <mbgl/geometry/vao.hpp> +#include <mbgl/geometry/elements_buffer.hpp> +#include <mbgl/geometry/text_buffer.hpp> +#include <mbgl/geometry/icon_buffer.hpp> +#include <mbgl/map/vector_tile.hpp> +#include <mbgl/text/types.hpp> +#include <mbgl/text/glyph.hpp> +#include <mbgl/style/style_bucket.hpp> + +#include <memory> +#include <map> +#include <vector> + +namespace mbgl { + +class Style; +class SDFShader; +class IconShader; +class DotShader; +class Collision; +class SpriteAtlas; +class Sprite; +class GlyphAtlas; +class GlyphStore; +class FontStack; + +class SymbolFeature { +public: + pbf geometry; + std::u32string label; + std::string sprite; +}; + + +class Symbol { +public: + vec2<float> tl, tr, bl, br; + Rect<uint16_t> tex; + float angle; + float minScale = 0.0f; + float maxScale = std::numeric_limits<float>::infinity(); + CollisionAnchor anchor; +}; + +typedef std::vector<Symbol> Symbols; + + +class SymbolBucket : public Bucket { + typedef ElementGroup<1> TextElementGroup; + typedef ElementGroup<2> IconElementGroup; + +public: + SymbolBucket(const StyleBucketSymbol &properties, Collision &collision); + + virtual void render(Painter &painter, util::ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix); + virtual bool hasData() const; + virtual bool hasTextData() const; + virtual bool hasIconData() const; + + void addFeatures(const VectorTileLayer &layer, const FilterExpression &filter, + const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite, + GlyphAtlas &glyphAtlas, GlyphStore &glyphStore); + + void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, PlacementRange placementRange, + float zoom); + + void drawGlyphs(SDFShader& shader); + void drawIcons(SDFShader& shader); + void drawIcons(IconShader& shader); + +private: + + std::vector<SymbolFeature> processFeatures(const VectorTileLayer &layer, const FilterExpression &filter, GlyphStore &glyphStore, const Sprite &sprite); + + + void addFeature(const pbf &geom_pbf, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image); + void addFeature(const std::vector<Coordinate> &line, const Shaping &shaping, const GlyphPositions &face, const Rect<uint16_t> &image); + + + // Adds placed items to the buffer. + template <typename Buffer> + void addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale, PlacementRange placementRange); + + // Adds glyphs to the glyph atlas so that they have a left/top/width/height coordinates associated to them that we can use for writing to a buffer. + static void addGlyphsToAtlas(uint64_t tileid, const std::string stackname, const std::u32string &string, + const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face); + +public: + const StyleBucketSymbol &properties; + bool sdfIcons = false; + +private: + Collision &collision; + + struct { + TextVertexBuffer vertices; + TriangleElementsBuffer triangles; + std::vector<TextElementGroup> groups; + } text; + + struct { + IconVertexBuffer vertices; + TriangleElementsBuffer triangles; + std::vector<IconElementGroup> groups; + } icon; + +}; +} + +#endif |