From 89b9c3945c76ee0953db1919415e8e83114599a2 Mon Sep 17 00:00:00 2001 From: Stefan Karschti Date: Fri, 13 Oct 2023 16:24:36 +0300 Subject: [PATCH] Custom Drawable Layer v1 + DrawableBuilder thick lines (#1699) --- CMakeLists.txt | 11 + bazel/core.bzl | 13 +- include/mbgl/gfx/drawable_builder.hpp | 28 +- include/mbgl/gfx/polyline_generator.hpp | 103 +++ .../custom_drawable_layer_factory.hpp | 18 + include/mbgl/renderer/layer_tweaker.hpp | 12 +- .../style/layers/custom_drawable_layer.hpp | 52 ++ platform/BUILD.bazel | 2 +- platform/darwin/BUILD.bazel | 1 + platform/darwin/bazel/files.bzl | 1 - .../src/mbgl/layermanager/layer_manager.cpp | 10 + platform/ios/app/MBXViewController.m | 2 + src/mbgl/gfx/drawable_builder.cpp | 29 +- src/mbgl/gfx/drawable_builder_impl.cpp | 126 ++++ src/mbgl/gfx/drawable_builder_impl.hpp | 44 +- src/mbgl/gfx/polyline_generator.cpp | 632 ++++++++++++++++++ src/mbgl/gl/drawable_gl_builder.cpp | 5 +- src/mbgl/gl/enum.cpp | 1 + .../custom_drawable_layer_factory.cpp | 23 + src/mbgl/renderer/buckets/line_bucket.cpp | 510 +------------- src/mbgl/renderer/buckets/line_bucket.hpp | 31 - src/mbgl/renderer/layer_group.cpp | 4 +- src/mbgl/renderer/layer_tweaker.cpp | 15 +- .../layers/background_layer_tweaker.cpp | 2 +- .../layers/background_layer_tweaker.hpp | 2 +- .../renderer/layers/circle_layer_tweaker.cpp | 7 +- .../renderer/layers/circle_layer_tweaker.hpp | 2 +- .../layers/collision_layer_tweaker.cpp | 8 +- .../layers/collision_layer_tweaker.hpp | 2 +- .../layers/fill_extrusion_layer_tweaker.cpp | 7 +- .../layers/fill_extrusion_layer_tweaker.hpp | 2 +- .../renderer/layers/fill_layer_tweaker.cpp | 7 +- .../renderer/layers/fill_layer_tweaker.hpp | 2 +- .../renderer/layers/heatmap_layer_tweaker.cpp | 13 +- .../renderer/layers/heatmap_layer_tweaker.hpp | 2 +- .../layers/heatmap_texture_layer_tweaker.cpp | 4 +- .../layers/heatmap_texture_layer_tweaker.hpp | 2 +- .../layers/hillshade_layer_tweaker.cpp | 6 +- .../layers/hillshade_layer_tweaker.hpp | 2 +- .../hillshade_prepare_layer_tweaker.cpp | 4 +- .../hillshade_prepare_layer_tweaker.hpp | 2 +- .../renderer/layers/line_layer_tweaker.cpp | 7 +- .../renderer/layers/line_layer_tweaker.hpp | 2 +- .../renderer/layers/raster_layer_tweaker.cpp | 1 - .../renderer/layers/raster_layer_tweaker.hpp | 2 +- .../layers/render_custom_drawable_layer.cpp | 92 +++ .../layers/render_custom_drawable_layer.hpp | 39 ++ .../renderer/layers/symbol_layer_tweaker.cpp | 7 +- .../renderer/layers/symbol_layer_tweaker.hpp | 2 +- src/mbgl/renderer/renderer_impl.cpp | 2 +- .../style/layers/custom_drawable_layer.cpp | 57 ++ .../layers/custom_drawable_layer_impl.cpp | 18 + .../layers/custom_drawable_layer_impl.hpp | 34 + test/CMakeLists.txt | 1 + test/api/custom_drawable_layer.test.cpp | 243 +++++++ .../custom_drawable_layer/basic/expected.png | Bin 0 -> 9867 bytes 56 files changed, 1638 insertions(+), 618 deletions(-) create mode 100644 include/mbgl/gfx/polyline_generator.hpp create mode 100644 include/mbgl/layermanager/custom_drawable_layer_factory.hpp create mode 100644 include/mbgl/style/layers/custom_drawable_layer.hpp create mode 100644 src/mbgl/gfx/drawable_builder_impl.cpp create mode 100644 src/mbgl/gfx/polyline_generator.cpp create mode 100644 src/mbgl/layermanager/custom_drawable_layer_factory.cpp create mode 100644 src/mbgl/renderer/layers/render_custom_drawable_layer.cpp create mode 100644 src/mbgl/renderer/layers/render_custom_drawable_layer.hpp create mode 100644 src/mbgl/style/layers/custom_drawable_layer.cpp create mode 100644 src/mbgl/style/layers/custom_drawable_layer_impl.cpp create mode 100644 src/mbgl/style/layers/custom_drawable_layer_impl.hpp create mode 100644 test/api/custom_drawable_layer.test.cpp create mode 100644 test/fixtures/custom_drawable_layer/basic/expected.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 3205330ab13..1db54ade4c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ if(MLN_DRAWABLE_RENDERER) ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/drawable.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/drawable_builder.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/drawable_builder_impl.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/drawable_builder_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/drawable_atlases_tweaker.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/drawable_custom_layer_host_tweaker.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/hillshade_prepare_drawable_data.hpp @@ -236,6 +237,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/annotation/annotation.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/backend_scope.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/gfx_types.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/polyline_generator.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/renderable.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/renderer_backend.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/rendering_stats.hpp @@ -466,6 +468,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/index_buffer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/index_vector.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/offscreen_texture.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/polyline_generator.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/program.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/render_pass.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/renderbuffer.hpp @@ -1115,6 +1118,8 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/background_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/circle_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/collision_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_drawable_layer.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/custom_drawable_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/fill_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/heatmap_layer_ubo.hpp @@ -1143,6 +1148,12 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/src/mbgl/gl/uniform_block_gl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/uniform_buffer_gl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_attribute_gl.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_drawable_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/custom_drawable_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_drawable_layer_impl.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/custom_drawable_layer_impl.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_custom_drawable_layer.hpp ) endif() endif() diff --git a/bazel/core.bzl b/bazel/core.bzl index 9a13789faea..613bf535c41 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -612,6 +612,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/util/premultiply.cpp", "src/mbgl/util/quaternion.cpp", "src/mbgl/util/quaternion.hpp", + "src/mbgl/gfx/polyline_generator.cpp", "src/mbgl/util/rapidjson.cpp", "src/mbgl/util/rapidjson.hpp", "src/mbgl/util/rect.hpp", @@ -818,6 +819,7 @@ MLN_CORE_HEADERS = [ "include/mbgl/util/monotonic_timer.hpp", "include/mbgl/util/noncopyable.hpp", "include/mbgl/util/platform.hpp", + "include/mbgl/gfx/polyline_generator.hpp", "include/mbgl/util/premultiply.hpp", "include/mbgl/util/projection.hpp", "include/mbgl/util/range.hpp", @@ -900,6 +902,7 @@ MLN_DRAWABLES_SOURCE = [ "src/mbgl/gfx/drawable.cpp", "src/mbgl/gfx/drawable_builder.cpp", "src/mbgl/gfx/drawable_builder_impl.hpp", + "src/mbgl/gfx/drawable_builder_impl.cpp", "src/mbgl/gfx/drawable_atlases_tweaker.cpp", "src/mbgl/gfx/drawable_custom_layer_host_tweaker.cpp", "src/mbgl/gfx/hillshade_prepare_drawable_data.hpp", @@ -941,6 +944,12 @@ MLN_DRAWABLES_SOURCE = [ "src/mbgl/renderer/layers/collision_layer_tweaker.hpp", "src/mbgl/shaders/shader_program_base.cpp", "src/mbgl/util/identity.cpp", + "src/mbgl/style/layers/custom_drawable_layer.cpp", + "src/mbgl/layermanager/custom_drawable_layer_factory.cpp", + "src/mbgl/style/layers/custom_drawable_layer_impl.cpp", + "src/mbgl/style/layers/custom_drawable_layer_impl.hpp", + "src/mbgl/renderer/layers/render_custom_drawable_layer.cpp", + "src/mbgl/renderer/layers/render_custom_drawable_layer.hpp", ] MLN_DRAWABLES_HEADERS = [ @@ -974,7 +983,9 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/shader_program_base.hpp", "include/mbgl/shaders/symbol_layer_ubo.hpp", "include/mbgl/util/identity.hpp", - "include/mbgl/util/suppress_copies.hpp" + "include/mbgl/util/suppress_copies.hpp", + "include/mbgl/style/layers/custom_drawable_layer.hpp", + "include/mbgl/layermanager/custom_drawable_layer_factory.hpp", ] MLN_DRAWABLES_GL_SOURCE = [ diff --git a/include/mbgl/gfx/drawable_builder.hpp b/include/mbgl/gfx/drawable_builder.hpp index bc5fd59f038..e1ece18c5ee 100644 --- a/include/mbgl/gfx/drawable_builder.hpp +++ b/include/mbgl/gfx/drawable_builder.hpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include #include @@ -154,14 +157,6 @@ class DrawableBuilder { /// Clear the tweaker collection void clearTweakers() { tweakers.clear(); } - /// Add a triangle - void addTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2); - /// Add another triangle based on the previous two points - void appendTriangle(int16_t x0, int16_t y0); - - /// Add a rectangle consisting of two triangles - void addQuad(int16_t x0, int16_t y0, int16_t x1, int16_t y1); - /// A set of attribute values to be added for each vertex void setVertexAttributes(const VertexAttributeArray& value); void setVertexAttributes(VertexAttributeArray&&); @@ -180,6 +175,17 @@ class DrawableBuilder { /// Set shared indices and segments void setSegments(gfx::DrawMode, gfx::IndexVectorBasePtr, const SegmentBase*, std::size_t segmentCount); + /// Create a segment wrapper + virtual std::unique_ptr createSegment(gfx::DrawMode, SegmentBase&&) = 0; + + /// Add a triangle + void addTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2); + /// Add another triangle based on the previous two points + void appendTriangle(int16_t x0, int16_t y0); + + /// Add a rectangle consisting of two triangles + void addQuad(int16_t x0, int16_t y0, int16_t x1, int16_t y1); + /// Add lines based on existing vertices void addLines(const std::vector& indexes, std::size_t indexOffset, @@ -192,8 +198,8 @@ class DrawableBuilder { std::size_t indexLength, std::size_t baseIndex = 0); - /// Create a segment wrapper - virtual std::unique_ptr createSegment(gfx::DrawMode, SegmentBase&&) = 0; + /// Add a polyline. If the last point equals the first it will be closed, otherwise open + void addPolyline(const GeometryCoordinates& coordinates, const gfx::PolylineGeneratorOptions&); protected: std::size_t curVertexCount() const; @@ -224,7 +230,7 @@ class DrawableBuilder { gfx::Drawable::Textures textures; std::vector tweakers; - struct Impl; + class Impl; std::unique_ptr impl; }; diff --git a/include/mbgl/gfx/polyline_generator.hpp b/include/mbgl/gfx/polyline_generator.hpp new file mode 100644 index 00000000000..b54562c6777 --- /dev/null +++ b/include/mbgl/gfx/polyline_generator.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace gfx { + +class PolylineGeneratorDistances { +public: + PolylineGeneratorDistances(double clipStart_, double clipEnd_, double total_) + : clipStart(clipStart_), + clipEnd(clipEnd_), + total(total_) {} + + // Scale line distance from tile units to [0, 2^15). + double scaleToMaxLineDistance(double tileDistance) const; + +private: + double clipStart; + double clipEnd; + double total; +}; + +struct PolylineGeneratorOptions { + FeatureType type{FeatureType::LineString}; + style::LineJoinType joinType{style::LineJoinType::Miter}; + float miterLimit{2.f}; + style::LineCapType beginCap{style::LineCapType::Butt}; + style::LineCapType endCap{style::LineCapType::Butt}; + float roundLimit{1.f}; + uint32_t overscaling{1}; // TODO: what is this??? + std::optional lineDistances; +}; + +template +class PolylineGenerator { +public: + using Vertices = gfx::VertexVector; + using Segments = std::vector; + using LayoutVertexFunc = std::function p, Point e, bool round, bool up, int8_t dir, int32_t linesofar /*= 0*/)>; + using CreateSegmentFunc = std::function; + using GetSegmentFunc = std::function; + using Indexes = gfx::IndexVector; + +public: + PolylineGenerator(Vertices& polylineVertices, + LayoutVertexFunc layoutVertexFunc, + Segments& polylineSegments, + CreateSegmentFunc createSegmentFunc, + GetSegmentFunc getSegmentFunc, + Indexes& polylineIndexes); + ~PolylineGenerator() = default; + + void generate(const GeometryCoordinates& coordinates, const PolylineGeneratorOptions& options); + +private: + struct TriangleElement; + + void addCurrentVertex(const GeometryCoordinate& currentCoordinate, + double& distance, + const Point& normal, + double endLeft, + double endRight, + bool round, + std::size_t startVertex, + std::vector& triangleStore, + std::optional lineDistances); + void addPieSliceVertex(const GeometryCoordinate& currentVertex, + double distance, + const Point& extrude, + bool lineTurnsLeft, + std::size_t startVertex, + std::vector& triangleStore, + std::optional lineDistances); + +private: + Vertices& vertices; + LayoutVertexFunc layoutVertex; + Segments& segments; + CreateSegmentFunc createSegment; + GetSegmentFunc getSegment; + Indexes& indexes; + + std::ptrdiff_t e1; + std::ptrdiff_t e2; + std::ptrdiff_t e3; +}; + +} // namespace gfx +} // namespace mbgl \ No newline at end of file diff --git a/include/mbgl/layermanager/custom_drawable_layer_factory.hpp b/include/mbgl/layermanager/custom_drawable_layer_factory.hpp new file mode 100644 index 00000000000..8b24b0c6726 --- /dev/null +++ b/include/mbgl/layermanager/custom_drawable_layer_factory.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#include + +namespace mbgl { + +class CustomDrawableLayerFactory : public LayerFactory { +protected: + const style::LayerTypeInfo* getTypeInfo() const noexcept final; + std::unique_ptr createLayer(const std::string& id, + const style::conversion::Convertible& value) noexcept final; + std::unique_ptr createRenderLayer(Immutable) noexcept final; +}; + +} // namespace mbgl diff --git a/include/mbgl/renderer/layer_tweaker.hpp b/include/mbgl/renderer/layer_tweaker.hpp index 1f00a8b6f9d..200e05131b8 100644 --- a/include/mbgl/renderer/layer_tweaker.hpp +++ b/include/mbgl/renderer/layer_tweaker.hpp @@ -71,20 +71,15 @@ class LayerTweaker { void enableOverdrawInspector(bool); - virtual void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) = 0; + virtual void execute(LayerGroupBase&, const PaintParameters&) = 0; void updateProperties(Immutable); -protected: - /// Determine whether this tweaker should apply to the given drawable - bool checkTweakDrawable(const gfx::Drawable&) const; - /// Calculate matrices for this tile. /// @param nearClipped If true, the near plane is moved further to enhance depth buffer precision. /// @param inViewportPixelUnits If false, the translation is scaled based on the current zoom. static mat4 getTileMatrix(const UnwrappedTileID&, - const RenderTree&, - const TransformState&, + const PaintParameters&, const std::array& translation, style::TranslateAnchorType, bool nearClipped, @@ -92,6 +87,9 @@ class LayerTweaker { bool aligned = false); protected: + /// Determine whether this tweaker should apply to the given drawable + bool checkTweakDrawable(const gfx::Drawable&) const; + std::string id; Immutable evaluatedProperties; diff --git a/include/mbgl/style/layers/custom_drawable_layer.hpp b/include/mbgl/style/layers/custom_drawable_layer.hpp new file mode 100644 index 00000000000..0856e919728 --- /dev/null +++ b/include/mbgl/style/layers/custom_drawable_layer.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace style { + +class CustomDrawableLayerHost { +public: + virtual ~CustomDrawableLayerHost() = default; + + virtual void initialize() = 0; + + virtual void update(RenderLayer& proxyLayer, + gfx::ShaderRegistry& shaders, + gfx::Context& context, + const TransformState& state, + const std::shared_ptr&, + const RenderTree& renderTree, + UniqueChangeRequestVec& changes) = 0; + + virtual void deinitialize() = 0; +}; + +class CustomDrawableLayer final : public Layer { +public: + CustomDrawableLayer(const std::string& id, std::unique_ptr host); + + CustomDrawableLayer(const CustomDrawableLayer&) = delete; + ~CustomDrawableLayer() final; + class Impl; + const Impl& impl() const; + Mutable mutableImpl() const; + +private: + std::optional setPropertyInternal(const std::string& name, + const conversion::Convertible& value) final; + StyleProperty getProperty(const std::string&) const final; + std::unique_ptr cloneRef(const std::string& id) const final; + Mutable mutableBaseImpl() const final; +}; + +} // namespace style +} // namespace mbgl diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index 56f19f42352..7f77fff1c41 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -236,7 +236,7 @@ sh_binary( "//platform/macos:macos_objc_srcs", "//platform/macos:macos_objcpp_srcs", "//platform/macos:macos_private_hdrs", - "//platform/macos:macos_public_hdrs" + "//platform/macos:macos_public_hdrs", ] + select({ "//:metal_renderer": [], "//conditions:default": [ diff --git a/platform/darwin/BUILD.bazel b/platform/darwin/BUILD.bazel index 08f809b56e4..72eb895cd6f 100644 --- a/platform/darwin/BUILD.bazel +++ b/platform/darwin/BUILD.bazel @@ -40,6 +40,7 @@ filegroup( srcs = MLN_DARWIN_PUBLIC_OBJCPP_SOURCE, visibility = ["//visibility:public"], ) + filegroup( name = "darwin_objcpp_opengl_srcs", srcs = MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE, diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index 9932ab5aca2..8ed70ea2dc8 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -208,7 +208,6 @@ MLN_DARWIN_PUBLIC_OBJCPP_OPENGL_SOURCE = [ "src/MLNOpenGLStyleLayer.h", "src/MLNOpenGLStyleLayer.mm", ] - MLN_DARWIN_PUBLIC_OBJC_SOURCE = [ "src/MLNAttributedExpression.m", "src/MLNClockDirectionFormatter.m", diff --git a/platform/default/src/mbgl/layermanager/layer_manager.cpp b/platform/default/src/mbgl/layermanager/layer_manager.cpp index 8411a9831a5..b9e999e7ad3 100644 --- a/platform/default/src/mbgl/layermanager/layer_manager.cpp +++ b/platform/default/src/mbgl/layermanager/layer_manager.cpp @@ -13,6 +13,10 @@ #include #include +#if MLN_DRAWABLE_RENDERER +#include +#endif + #include #include #include @@ -69,6 +73,12 @@ LayerManagerDefault::LayerManagerDefault() { addLayerType(std::make_unique()); #endif #endif + +#if MLN_DRAWABLE_RENDERER +#if !defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_ALL) + addLayerType(std::make_unique()); +#endif +#endif } void LayerManagerDefault::addLayerType(std::unique_ptr factory) { diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 9ec99f89430..66b702c58a2 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -435,7 +435,9 @@ - (void)dismissSettings:(__unused id)sender @"Style Raster Tile Source", @"Style Image Source", @"Add Route Line", +#if !MLN_RENDER_BACKEND_METAL @"Add Lime Green Triangle Layer", +#endif @"Dynamically Style Polygon", @"Add Custom Lat/Lon Grid", @"Style Route line with gradient", diff --git a/src/mbgl/gfx/drawable_builder.cpp b/src/mbgl/gfx/drawable_builder.cpp index 08a1c7c788d..254e26f82bd 100644 --- a/src/mbgl/gfx/drawable_builder.cpp +++ b/src/mbgl/gfx/drawable_builder.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace mbgl { namespace gfx { @@ -45,6 +46,11 @@ UniqueDrawable& DrawableBuilder::getCurrentDrawable(bool createIfNone) { void DrawableBuilder::flush() { if (curVertexCount()) { + if (Impl::Mode::Polylines == impl->getMode()) { + // setup for polylines + impl->setupForPolylines(*this); + } + const auto& draw = getCurrentDrawable(/*createIfNone=*/true); draw->setEnabled(enabled); draw->setLineWidth(static_cast(lineWidth)); @@ -68,6 +74,9 @@ void DrawableBuilder::flush() { } init(); + + // reset mode + impl->setMode(Impl::Mode::Custom); } if (currentDrawable) { drawables.emplace_back(std::move(currentDrawable)); @@ -101,6 +110,9 @@ void DrawableBuilder::setTexture(const std::shared_ptr& texture, } void DrawableBuilder::addTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2) { + // check and set the current mode + if (!impl->checkAndSetMode(Impl::Mode::Primitives)) return; + const auto n = static_cast(impl->vertices.elements()); impl->vertices.emplace_back(Impl::VT({{{x0, y0}}})); impl->vertices.emplace_back(Impl::VT({{{x1, y1}}})); @@ -117,6 +129,9 @@ void DrawableBuilder::addTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1 } void DrawableBuilder::appendTriangle(int16_t x0, int16_t y0) { + // check and set the current mode + if (!impl->checkAndSetMode(Impl::Mode::Primitives)) return; + const auto n = (uint16_t)impl->vertices.elements(); impl->vertices.emplace_back(Impl::VT({{{x0, y0}}})); impl->buildIndexes.insert(impl->buildIndexes.end(), @@ -129,6 +144,9 @@ void DrawableBuilder::appendTriangle(int16_t x0, int16_t y0) { } void DrawableBuilder::addQuad(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { + // check and set the current mode + if (!impl->checkAndSetMode(Impl::Mode::Primitives)) return; + addTriangle(x0, y0, x1, y0, x0, y1); appendTriangle(x1, y1); } @@ -252,7 +270,16 @@ void DrawableBuilder::addTriangles(const std::vector& indexes, } std::size_t DrawableBuilder::curVertexCount() const { - return impl->rawVerticesCount ? impl->rawVerticesCount : impl->vertices.elements(); + return impl->vertexCount(); +} + +void DrawableBuilder::addPolyline(const GeometryCoordinates& coordinates, + const gfx::PolylineGeneratorOptions& options) { + // mark the current mode + if (!impl->checkAndSetMode(Impl::Mode::Polylines)) return; + + // append polyline + impl->addPolyline(*this, coordinates, options); } } // namespace gfx diff --git a/src/mbgl/gfx/drawable_builder_impl.cpp b/src/mbgl/gfx/drawable_builder_impl.cpp new file mode 100644 index 00000000000..9ed34d8eda9 --- /dev/null +++ b/src/mbgl/gfx/drawable_builder_impl.cpp @@ -0,0 +1,126 @@ +#include + +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace gfx { + +DrawableBuilder::Impl::LineLayoutVertex DrawableBuilder::Impl::layoutVertex( + Point p, Point e, bool round, bool up, int8_t dir, int32_t linesofar /*= 0*/) { + /* + * Scale the extrusion vector so that the normal length is this value. + * Contains the "texture" normals (-1..1). This is distinct from the extrude + * normals for line joins, because the x-value remains 0 for the texture + * normal array, while the extrude normal actually moves the vertex to + * create the acute/bevelled line join. + */ + static const int8_t extrudeScale = 63; + return LineLayoutVertex{ + {{static_cast((p.x * 2) | (round ? 1 : 0)), static_cast((p.y * 2) | (up ? 1 : 0))}}, + {{// add 128 to store a byte in an unsigned byte + static_cast(::round(extrudeScale * e.x) + 128), + static_cast(::round(extrudeScale * e.y) + 128), + + // Encode the -1/0/1 direction value into the first two bits of .z + // of a_data. Combine it with the lower 6 bits of `linesofar` + // (shifted by 2 bites to make room for the direction value). The + // upper 8 bits of `linesofar` are placed in the `w` component. + // `linesofar` is scaled down by `LINE_DISTANCE_SCALE` so that we + // can store longer distances while sacrificing precision. + + // Encode the -1/0/1 direction value into .zw coordinates of + // a_data, which is normally covered by linesofar, so we need to + // merge them. The z component's first bit, as well as the sign + // bit is reserved for the direction, so we need to shift the + // linesofar. + static_cast(((dir == 0 ? 0 : (dir < 0 ? -1 : 1)) + 1) | ((linesofar & 0x3F) << 2)), + static_cast(linesofar >> 6)}}}; +} + +void DrawableBuilder::Impl::addPolyline(gfx::DrawableBuilder& builder, + const GeometryCoordinates& coordinates, + const gfx::PolylineGeneratorOptions& options) { + using namespace std::placeholders; + gfx::PolylineGenerator> generator( + polylineVertices, + std::bind(&DrawableBuilder::Impl::layoutVertex, this, _1, _2, _3, _4, _5, _6), + segments, + [&builder](std::size_t vertexOffset, std::size_t indexOffset) -> std::unique_ptr { + return builder.createSegment(gfx::Triangles(), SegmentBase(vertexOffset, indexOffset)); + }, + [](auto& uniqueSegment) -> SegmentBase& { return uniqueSegment->getSegment(); }, + polylineIndexes); + generator.generate(coordinates, options); +} + +void DrawableBuilder::Impl::setupForPolylines(gfx::DrawableBuilder& builder) { + // setup vertex attributes + static const StringIdentity idVertexAttribName = stringIndexer().get("a_pos_normal"); + static const StringIdentity idDataAttribName = stringIndexer().get("a_data"); + + builder.setVertexAttrNameId(idVertexAttribName); + + builder.setRawVertices({}, polylineVertices.elements(), gfx::AttributeDataType::Short2); + + gfx::VertexAttributeArray attrs; + using VertexVector = gfx::VertexVector; + std::shared_ptr verts = std::make_shared(polylineVertices); + + if (const auto& attr = attrs.add(idVertexAttribName)) { + attr->setSharedRawData(verts, + offsetof(LineLayoutVertex, a1), + /*vertexOffset=*/0, + sizeof(LineLayoutVertex), + gfx::AttributeDataType::Short2); + } + + if (const auto& attr = attrs.add(idDataAttribName)) { + attr->setSharedRawData(verts, + offsetof(LineLayoutVertex, a2), + /*vertexOffset=*/0, + sizeof(LineLayoutVertex), + gfx::AttributeDataType::UByte4); + } + + builder.setVertexAttributes(std::move(attrs)); + + sharedIndexes = std::make_shared(std::move(polylineIndexes)); +} + +bool DrawableBuilder::Impl::checkAndSetMode(Mode target) { + if (target != mode && vertexCount()) { + // log error + using namespace std::string_literals; + auto to_string = [](Mode value) -> std::string { + switch (value) { + case Mode::Primitives: + return "Mode::Primitives"; + case Mode::Polylines: + return "Mode::Polylines"; + case Mode::Custom: + return "Mode::Custom"; + default: + return "Unknown"; // Handle unknown enum values if needed + } + }; + mbgl::Log::Error( + mbgl::Event::General, + "DrawableBuilder mode mismatch. Target is "s + to_string(target) + ", current is " + to_string(mode)); + + // the builder is building in a different mode + assert(false); + // TODO: throw? + return false; + } + mode = target; + return true; +} + +} // namespace gfx +} // namespace mbgl diff --git a/src/mbgl/gfx/drawable_builder_impl.hpp b/src/mbgl/gfx/drawable_builder_impl.hpp index 7c7e13c81da..292519aba08 100644 --- a/src/mbgl/gfx/drawable_builder_impl.hpp +++ b/src/mbgl/gfx/drawable_builder_impl.hpp @@ -5,20 +5,40 @@ #include #include #include +#include +#include #include +#include #include +#include +#include namespace mbgl { namespace gfx { -struct DrawableBuilder::Impl { +class DrawableBuilder::Impl { +public: using VT = gfx::detail::VertexType>; + enum class Mode { + Primitives, ///< building primitive drawables. Not implemented + Polylines, ///< building drawables for thick polylines + Custom ///< building custom drawables. + }; + struct LineLayoutVertex { + std::array a1; + std::array a2; + }; + +public: gfx::VertexVector vertices; std::vector rawVertices; std::size_t rawVerticesCount = 0; + gfx::VertexVector polylineVertices; + gfx::IndexVector polylineIndexes; + std::vector buildIndexes; std::shared_ptr sharedIndexes; std::vector> segments; @@ -28,6 +48,28 @@ struct DrawableBuilder::Impl { gfx::CullFaceMode cullFaceMode = gfx::CullFaceMode::disabled(); VertexAttributeArray vertexAttrs; + + void addPolyline(gfx::DrawableBuilder& builder, + const GeometryCoordinates& coordinates, + const gfx::PolylineGeneratorOptions& options); + + void setupForPolylines(gfx::DrawableBuilder& builder); + + bool checkAndSetMode(Mode); + + Mode getMode() const { return mode; }; + + bool setMode(Mode value) { return mode == value; }; + + std::size_t vertexCount() const { + return std::max(rawVerticesCount, std::max(vertices.elements(), polylineVertices.elements())); + } + +private: + LineLayoutVertex layoutVertex( + Point p, Point e, bool round, bool up, int8_t dir, int32_t linesofar = 0); + + Mode mode{Mode::Custom}; }; } // namespace gfx diff --git a/src/mbgl/gfx/polyline_generator.cpp b/src/mbgl/gfx/polyline_generator.cpp new file mode 100644 index 00000000000..51ccdf52e3c --- /dev/null +++ b/src/mbgl/gfx/polyline_generator.cpp @@ -0,0 +1,632 @@ +#include + +#include +#include +#include + +#if MLN_DRAWABLE_RENDERER +#include +#include +#include +#endif + +#include + +namespace mbgl { +namespace gfx { + +namespace { +/* + * Sharp corners cause dashed lines to tilt because the distance along the line + * is the same at both the inner and outer corners. To improve the appearance of + * dashed lines we add extra points near sharp corners so that a smaller part + * of the line is tilted. + * + * COS_HALF_SHARP_CORNER controls how sharp a corner has to be for us to add an + * extra vertex. The default is 75 degrees. + * + * The newly created vertices are placed SHARP_CORNER_OFFSET pixels from the corner. + */ +const float COS_HALF_SHARP_CORNER = std::cos(75.0f / 2.0f * (static_cast(M_PI) / 180.0f)); +constexpr float SHARP_CORNER_OFFSET = 15.0f; + +// Angle per triangle for approximating round line joins. +constexpr float DEG_PER_TRIANGLE = 20.0f; + +// The number of bits that is used to store the line distance in the buffer. +constexpr int LINE_DISTANCE_BUFFER_BITS = 14; + +// We don't have enough bits for the line distance as we'd like to have, so +// use this value to scale the line distance (in tile units) down to a smaller +// value. This lets us store longer distances while sacrificing precision. +constexpr float LINE_DISTANCE_SCALE = 1.0 / 2.0; + +// The maximum line distance, in tile units, that fits in the buffer. +constexpr auto MAX_LINE_DISTANCE = static_cast((1u << LINE_DISTANCE_BUFFER_BITS) / LINE_DISTANCE_SCALE); + +} // namespace + +double PolylineGeneratorDistances::scaleToMaxLineDistance(double tileDistance) const { + double relativeTileDistance = tileDistance / total; + if (std::isinf(relativeTileDistance) || std::isnan(relativeTileDistance)) { + assert(false); + relativeTileDistance = 0.0; + } + return (relativeTileDistance * (clipEnd - clipStart) + clipStart) * (MAX_LINE_DISTANCE - 1); +} + +template +struct PolylineGenerator::TriangleElement { + TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) + : a(a_), + b(b_), + c(c_) {} + uint16_t a, b, c; +}; + +template +PolylineGenerator::PolylineGenerator(Vertices& polylineVertices, + LayoutVertexFunc layoutVertexFunc, + Segments& polylineSegments, + CreateSegmentFunc createSegmentFunc, + GetSegmentFunc getSegmentFunc, + Indexes& polylineIndexes) + : vertices(polylineVertices), + layoutVertex(layoutVertexFunc), + segments(polylineSegments), + createSegment(createSegmentFunc), + getSegment(getSegmentFunc), + indexes(polylineIndexes) {} + +template +void PolylineGenerator::generate(const GeometryCoordinates& coordinates, + const PolylineGeneratorOptions& options) { + const std::size_t len = [&coordinates] { + std::size_t l = coordinates.size(); + // If the line has duplicate vertices at the end, adjust length to remove them. + while (l >= 2 && coordinates[l - 1] == coordinates[l - 2]) { + l--; + } + return l; + }(); + + const std::size_t first = [&coordinates, &len] { + std::size_t i = 0; + // If the line has duplicate vertices at the start, adjust index to remove them. + while (i < len - 1 && coordinates[i] == coordinates[i + 1]) { + i++; + } + return i; + }(); + + // Ignore invalid geometry. + if (len < (options.type == FeatureType::Polygon ? 3 : 2)) { + return; + } + + const style::LineJoinType joinType = options.joinType; + + const float miterLimit = joinType == style::LineJoinType::Bevel ? 1.05f : options.miterLimit; + + const uint32_t overscaling = options.overscaling; + + const double sharpCornerOffset = + overscaling == 0 + ? SHARP_CORNER_OFFSET * (util::EXTENT / util::tileSize_D) + : (overscaling <= 16.0 ? SHARP_CORNER_OFFSET * (util::EXTENT / (util::tileSize_D * overscaling)) : 0.0); + + const GeometryCoordinate firstCoordinate = coordinates[first]; + const style::LineCapType beginCap = options.beginCap; + const style::LineCapType endCap = options.type == FeatureType::Polygon ? style::LineCapType::Butt : options.endCap; + + double distance = 0.0; + bool startOfLine = true; + std::optional currentCoordinate; + std::optional prevCoordinate; + std::optional nextCoordinate; + std::optional> prevNormal; + std::optional> nextNormal; + + // the last three vertices added + e1 = e2 = e3 = -1; + + if (options.type == FeatureType::Polygon) { + currentCoordinate = coordinates[len - 2]; + nextNormal = util::perp(util::unit(convertPoint(firstCoordinate - *currentCoordinate))); + } + + const std::size_t startVertex = vertices.elements(); + std::vector triangleStore; + + for (std::size_t i = first; i < len; ++i) { + if (options.type == FeatureType::Polygon && i == len - 1) { + // if the line is closed, we treat the last vertex like the first + nextCoordinate = coordinates[first + 1]; + } else if (i + 1 < len) { + // just the next vertex + nextCoordinate = coordinates[i + 1]; + } else { + // there is no next vertex + nextCoordinate = {}; + } + + // if two consecutive vertices exist, skip the current one + if (nextCoordinate && coordinates[i] == *nextCoordinate) { + continue; + } + + if (nextNormal) { + prevNormal = *nextNormal; + } + if (currentCoordinate) { + prevCoordinate = *currentCoordinate; + } + + currentCoordinate = coordinates[i]; + + // 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 using the previous normal. + nextNormal = nextCoordinate ? util::perp(util::unit(convertPoint(*nextCoordinate - *currentCoordinate))) + : prevNormal; + + // 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; + } + + // Determine the normal of the join extrusion. It is the angle bisector + // of the segments between the previous line and the next line. + // In the case of 180° angles, the prev and next normals cancel each + // other out: prevNormal + nextNormal = (0, 0), its magnitude is 0, so + // the unit vector would be undefined. In that case, we're keeping the + // joinNormal at (0, 0), so that the cosHalfAngle below will also become + // 0 and miterLength will become Infinity. + Point joinNormal = *prevNormal + *nextNormal; + if (joinNormal.x != 0 || joinNormal.y != 0) { + joinNormal = util::unit(joinNormal); + } + + // * joinNormal prevNormal + // * ↖ ↑ + // * .________. prevVertex + // * | + // * nextNormal ← | currentVertex + // * | + // * nextVertex ! + // * + // + + // Calculate cosines of the angle (and its half) using dot product. + const double cosAngle = prevNormal->x * nextNormal->x + prevNormal->y * nextNormal->y; + const double cosHalfAngle = joinNormal.x * nextNormal->x + joinNormal.y * nextNormal->y; + + // Calculate the length of the miter (the ratio of the miter to the width) + // as the inverse of cosine of the angle between next and join normals. + const double miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle : std::numeric_limits::infinity(); + + // Approximate angle from cosine. + const double approxAngle = 2 * std::sqrt(2 - 2 * cosHalfAngle); + + const bool isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevCoordinate && nextCoordinate; + + if (isSharpCorner && i > first) { + const auto prevSegmentLength = util::dist(*currentCoordinate, *prevCoordinate); + if (prevSegmentLength > 2.0 * sharpCornerOffset) { + GeometryCoordinate newPrevVertex = *currentCoordinate - + convertPoint(util::round( + convertPoint(*currentCoordinate - *prevCoordinate) * + (sharpCornerOffset / prevSegmentLength))); + distance += util::dist(newPrevVertex, *prevCoordinate); + addCurrentVertex(newPrevVertex, + distance, + *prevNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + prevCoordinate = newPrevVertex; + } + } + + // The join if a middle vertex, otherwise the cap + const bool middleVertex = prevCoordinate && nextCoordinate; + style::LineJoinType currentJoin = joinType; + const style::LineCapType currentCap = nextCoordinate ? beginCap : endCap; + + if (middleVertex) { + if (currentJoin == style::LineJoinType::Round) { + if (miterLength < options.roundLimit) { + currentJoin = style::LineJoinType::Miter; + } else if (miterLength <= 2) { + currentJoin = style::LineJoinType::FakeRound; + } + } + + if (currentJoin == style::LineJoinType::Miter && miterLength > miterLimit) { + currentJoin = style::LineJoinType::Bevel; + } + + if (currentJoin == style::LineJoinType::Bevel) { + // The maximum extrude length is 128 / 63 = 2 times the width of + // the line so if miterLength >= 2 we need to draw a different + // type of bevel here. + if (miterLength > 2) { + currentJoin = style::LineJoinType::FlipBevel; + } + + // If the miterLength is really small and the line bevel wouldn't be visible, + // just draw a miter join to save a triangle. + if (miterLength < miterLimit) { + currentJoin = style::LineJoinType::Miter; + } + } + } + + // Calculate how far along the line the currentVertex is + if (prevCoordinate) distance += util::dist(*currentCoordinate, *prevCoordinate); + + if (middleVertex && currentJoin == style::LineJoinType::Miter) { + joinNormal = joinNormal * miterLength; + addCurrentVertex(*currentCoordinate, + distance, + joinNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + + } else if (middleVertex && currentJoin == style::LineJoinType::FlipBevel) { + // miter is too big, flip the direction to make a beveled join + + if (miterLength > 100) { + // Almost parallel lines + joinNormal = *nextNormal * -1.0; + } else { + const double direction = prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x > 0 ? -1 : 1; + const double bevelLength = miterLength * util::mag(*prevNormal + *nextNormal) / + util::mag(*prevNormal - *nextNormal); + joinNormal = util::perp(joinNormal) * bevelLength * direction; + } + + addCurrentVertex(*currentCoordinate, + distance, + joinNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + + addCurrentVertex(*currentCoordinate, + distance, + joinNormal * -1.0, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + } else if (middleVertex && + (currentJoin == style::LineJoinType::Bevel || currentJoin == style::LineJoinType::FakeRound)) { + const bool lineTurnsLeft = (prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x) > 0; + const auto offset = static_cast(-std::sqrt(miterLength * miterLength - 1)); + float offsetA; + float offsetB; + + if (lineTurnsLeft) { + offsetB = 0; + offsetA = offset; + } else { + offsetA = 0; + offsetB = offset; + } + + // Close previous segement with bevel + if (!startOfLine) { + addCurrentVertex(*currentCoordinate, + distance, + *prevNormal, + offsetA, + offsetB, + false, + startVertex, + triangleStore, + options.lineDistances); + } + + if (currentJoin == style::LineJoinType::FakeRound) { + // The join angle is sharp enough that a round join would be + // visible. Bevel joins fill the gap between segments with a + // single pie slice triangle. Create a round join by adding + // multiple pie slices. The join isn't actually round, but it + // looks like it is at the sizes we render lines at. + + // Pick the number of triangles for approximating round join by + // based on the angle between normals. + const auto n = static_cast(::round((approxAngle * 180 / M_PI) / DEG_PER_TRIANGLE)); + + for (unsigned m = 1; m < n; ++m) { + double t = static_cast(m) / n; + if (t != 0.5) { + // approximate spherical interpolation + // https://observablehq.com/@mourner/approximating-geometric-slerp + const double t2 = t - 0.5; + const double A = 1.0904 + cosAngle * (-3.2452 + cosAngle * (3.55645 - cosAngle * 1.43519)); + const double B = 0.848013 + cosAngle * (-1.06021 + cosAngle * 0.215638); + t = t + t * t2 * (t - 1) * (A * t2 * t2 + B); + } + auto approxFractionalNormal = util::unit(*prevNormal * (1.0 - t) + *nextNormal * t); + addPieSliceVertex(*currentCoordinate, + distance, + approxFractionalNormal, + lineTurnsLeft, + startVertex, + triangleStore, + options.lineDistances); + } + } + + // Start next segment + if (nextCoordinate) { + addCurrentVertex(*currentCoordinate, + distance, + *nextNormal, + -offsetA, + -offsetB, + false, + startVertex, + triangleStore, + options.lineDistances); + } + + } else if (!middleVertex && currentCap == style::LineCapType::Butt) { + if (!startOfLine) { + // Close previous segment with a butt + addCurrentVertex(*currentCoordinate, + distance, + *prevNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + } + + // Start next segment with a butt + if (nextCoordinate) { + addCurrentVertex(*currentCoordinate, + distance, + *nextNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + } + + } else if (!middleVertex && currentCap == style::LineCapType::Square) { + if (!startOfLine) { + // Close previous segment with a square cap + addCurrentVertex(*currentCoordinate, + distance, + *prevNormal, + 1, + 1, + false, + startVertex, + triangleStore, + options.lineDistances); + + // The segment is done. Unset vertices to disconnect segments. + e1 = e2 = -1; + } + + // Start next segment + if (nextCoordinate) { + addCurrentVertex(*currentCoordinate, + distance, + *nextNormal, + -1, + -1, + false, + startVertex, + triangleStore, + options.lineDistances); + } + + } else if (middleVertex ? currentJoin == style::LineJoinType::Round : currentCap == style::LineCapType::Round) { + if (!startOfLine) { + // Close previous segment with a butt + addCurrentVertex(*currentCoordinate, + distance, + *prevNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + + // Add round cap or linejoin at end of segment + addCurrentVertex(*currentCoordinate, + distance, + *prevNormal, + 1, + 1, + true, + startVertex, + triangleStore, + options.lineDistances); + + // The segment is done. Unset vertices to disconnect segments. + e1 = e2 = -1; + } + + // Start next segment with a butt + if (nextCoordinate) { + // Add round cap before first segment + addCurrentVertex(*currentCoordinate, + distance, + *nextNormal, + -1, + -1, + true, + startVertex, + triangleStore, + options.lineDistances); + + addCurrentVertex(*currentCoordinate, + distance, + *nextNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + } + } + + if (isSharpCorner && i < len - 1) { + const auto nextSegmentLength = util::dist(*currentCoordinate, *nextCoordinate); + if (nextSegmentLength > 2 * sharpCornerOffset) { + GeometryCoordinate newCurrentVertex = *currentCoordinate + + convertPoint(util::round( + convertPoint(*nextCoordinate - *currentCoordinate) * + (sharpCornerOffset / nextSegmentLength))); + distance += util::dist(newCurrentVertex, *currentCoordinate); + addCurrentVertex(newCurrentVertex, + distance, + *nextNormal, + 0, + 0, + false, + startVertex, + triangleStore, + options.lineDistances); + currentCoordinate = newCurrentVertex; + } + } + + startOfLine = false; + } + + // add segment(s) and indices + const std::size_t endVertex = vertices.elements(); + const std::size_t vertexCount = endVertex - startVertex; + + if (segments.empty() || + getSegment(segments.back()).vertexLength + vertexCount > std::numeric_limits::max()) { + segments.emplace_back(createSegment(startVertex, indexes.elements())); + } + + auto& segment = getSegment(segments.back()); + assert(segment.vertexLength <= std::numeric_limits::max()); + const uint16_t index = static_cast(segment.vertexLength); + + for (const auto& triangle : triangleStore) { + indexes.emplace_back(index + triangle.a, index + triangle.b, index + triangle.c); + } + + segment.vertexLength += vertexCount; + segment.indexLength += triangleStore.size() * 3; +} + +template +void PolylineGenerator::addCurrentVertex(const GeometryCoordinate& currentCoordinate, + double& distance, + const Point& normal, + double endLeft, + double endRight, + bool round, + std::size_t startVertex, + std::vector& triangleStore, + std::optional lineDistances) { + Point extrude = normal; + double scaledDistance = lineDistances ? lineDistances->scaleToMaxLineDistance(distance) : distance; + + if (endLeft) extrude = extrude - (util::perp(normal) * endLeft); + vertices.emplace_back(layoutVertex(currentCoordinate, + extrude, + round, + false, + static_cast(endLeft), + static_cast(scaledDistance * LINE_DISTANCE_SCALE))); + e3 = vertices.elements() - 1 - startVertex; + if (e1 >= 0 && e2 >= 0) { + triangleStore.emplace_back(static_cast(e1), static_cast(e2), static_cast(e3)); + } + e1 = e2; + e2 = e3; + + extrude = normal * -1.0; + if (endRight) extrude = extrude - (util::perp(normal) * endRight); + vertices.emplace_back(layoutVertex(currentCoordinate, + extrude, + round, + true, + static_cast(-endRight), + static_cast(scaledDistance * LINE_DISTANCE_SCALE))); + e3 = vertices.elements() - 1 - startVertex; + if (e1 >= 0 && e2 >= 0) { + triangleStore.emplace_back(static_cast(e1), static_cast(e2), static_cast(e3)); + } + e1 = e2; + e2 = e3; + + // There is a maximum "distance along the line" that we can store in the + // buffers. When we get close to the distance, reset it to zero and add the + // vertex again with a distance of zero. The max distance is determined by + // the number of bits we allocate to `linesofar`. + if (distance > MAX_LINE_DISTANCE / 2.0f && !lineDistances) { + distance = 0.0; + addCurrentVertex( + currentCoordinate, distance, normal, endLeft, endRight, round, startVertex, triangleStore, lineDistances); + } +} + +template +void PolylineGenerator::addPieSliceVertex(const GeometryCoordinate& currentVertex, + double distance, + const Point& extrude, + bool lineTurnsLeft, + std::size_t startVertex, + std::vector& triangleStore, + std::optional lineDistances) { + Point flippedExtrude = extrude * (lineTurnsLeft ? -1.0 : 1.0); + if (lineDistances) { + distance = lineDistances->scaleToMaxLineDistance(distance); + } + + vertices.emplace_back(layoutVertex( + currentVertex, flippedExtrude, false, lineTurnsLeft, 0, static_cast(distance * LINE_DISTANCE_SCALE))); + e3 = vertices.elements() - 1 - startVertex; + if (e1 >= 0 && e2 >= 0) { + triangleStore.emplace_back(static_cast(e1), static_cast(e2), static_cast(e3)); + } + + if (lineTurnsLeft) { + e2 = e3; + } else { + e1 = e3; + } +} + +#if MLN_DRAWABLE_RENDERER +template class PolylineGenerator>; +#endif + +template class PolylineGenerator>; + +} // namespace gfx +} // namespace mbgl diff --git a/src/mbgl/gl/drawable_gl_builder.cpp b/src/mbgl/gl/drawable_gl_builder.cpp index caa3afdb9e1..22bf7921427 100644 --- a/src/mbgl/gl/drawable_gl_builder.cpp +++ b/src/mbgl/gl/drawable_gl_builder.cpp @@ -15,8 +15,9 @@ gfx::UniqueDrawable DrawableGLBuilder::createDrawable() const { return std::make_unique(drawableName.empty() ? name : drawableName); }; -std::unique_ptr DrawableGLBuilder::createSegment(gfx::DrawMode mode, SegmentBase&& seg) { - return std::make_unique(mode, std::move(seg), VertexArray{{nullptr, false}}); +std::unique_ptr DrawableGLBuilder::createSegment(gfx::DrawMode drawMode, + SegmentBase&& seg) { + return std::make_unique(drawMode, std::move(seg), VertexArray{{nullptr, false}}); } void DrawableGLBuilder::init() { diff --git a/src/mbgl/gl/enum.cpp b/src/mbgl/gl/enum.cpp index d59306c4fb7..23443da4533 100644 --- a/src/mbgl/gl/enum.cpp +++ b/src/mbgl/gl/enum.cpp @@ -406,6 +406,7 @@ platform::GLenum Enum::sizedFor<>(const gfx::TexturePixel default: break; } + break; } } diff --git a/src/mbgl/layermanager/custom_drawable_layer_factory.cpp b/src/mbgl/layermanager/custom_drawable_layer_factory.cpp new file mode 100644 index 00000000000..6ccb613c32d --- /dev/null +++ b/src/mbgl/layermanager/custom_drawable_layer_factory.cpp @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +namespace mbgl { + +const style::LayerTypeInfo* CustomDrawableLayerFactory::getTypeInfo() const noexcept { + return style::CustomDrawableLayer::Impl::staticTypeInfo(); +} + +std::unique_ptr CustomDrawableLayerFactory::createLayer(const std::string&, + const style::conversion::Convertible&) noexcept { + assert(false); + return nullptr; +} + +std::unique_ptr CustomDrawableLayerFactory::createRenderLayer( + Immutable impl) noexcept { + return std::make_unique(staticImmutableCast(impl)); +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/line_bucket.cpp b/src/mbgl/renderer/buckets/line_bucket.cpp index af6912e5773..5458074b834 100644 --- a/src/mbgl/renderer/buckets/line_bucket.cpp +++ b/src/mbgl/renderer/buckets/line_bucket.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -50,61 +51,22 @@ void LineBucket::addFeature(const GeometryTileFeature& feature, } } -/* - * Sharp corners cause dashed lines to tilt because the distance along the line - * is the same at both the inner and outer corners. To improve the appearance of - * dashed lines we add extra points near sharp corners so that a smaller part - * of the line is tilted. - * - * COS_HALF_SHARP_CORNER controls how sharp a corner has to be for us to add an - * extra vertex. The default is 75 degrees. - * - * The newly created vertices are placed SHARP_CORNER_OFFSET pixels from the corner. - */ -const float COS_HALF_SHARP_CORNER = std::cos(75.0f / 2.0f * (static_cast(M_PI) / 180.0f)); -const float SHARP_CORNER_OFFSET = 15.0f; - -// Angle per triangle for approximating round line joins. -const float DEG_PER_TRIANGLE = 20.0f; - -// The number of bits that is used to store the line distance in the buffer. -const int LINE_DISTANCE_BUFFER_BITS = 14; - -// We don't have enough bits for the line distance as we'd like to have, so -// use this value to scale the line distance (in tile units) down to a smaller -// value. This lets us store longer distances while sacrificing precision. -const float LINE_DISTANCE_SCALE = 1.0 / 2.0; - -// The maximum line distance, in tile units, that fits in the buffer. -const auto MAX_LINE_DISTANCE = static_cast(std::pow(2, LINE_DISTANCE_BUFFER_BITS) / LINE_DISTANCE_SCALE); - -class LineBucket::Distances { -public: - Distances(double clipStart_, double clipEnd_, double total_) - : clipStart(clipStart_), - clipEnd(clipEnd_), - total(total_) {} - - // Scale line distance from tile units to [0, 2^15). - double scaleToMaxLineDistance(double tileDistance) const { - double relativeTileDistance = tileDistance / total; - if (std::isinf(relativeTileDistance) || std::isnan(relativeTileDistance)) { - assert(false); - relativeTileDistance = 0.0; - } - return (relativeTileDistance * (clipEnd - clipStart) + clipStart) * (MAX_LINE_DISTANCE - 1); - } - -private: - double clipStart; - double clipEnd; - double total; -}; - void LineBucket::addGeometry(const GeometryCoordinates& coordinates, const GeometryTileFeature& feature, const CanonicalTileID& canonical) { - const FeatureType type = feature.getType(); + gfx::PolylineGenerator> generator( + vertices, + LineProgram::layoutVertex, + segments, + [](std::size_t vertexOffset, std::size_t indexOffset) -> Segment { + return Segment(vertexOffset, indexOffset); + }, + [](auto& seg) -> Segment& { return seg; }, + triangles); + + gfx::PolylineGeneratorOptions options; + + options.type = feature.getType(); const std::size_t len = [&coordinates] { std::size_t l = coordinates.size(); // If the line has duplicate vertices at the end, adjust length to remove them. @@ -124,12 +86,10 @@ void LineBucket::addGeometry(const GeometryCoordinates& coordinates, }(); // Ignore invalid geometry. - if (len < (type == FeatureType::Polygon ? 3 : 2)) { + if (len < (options.type == FeatureType::Polygon ? 3 : 2)) { return; } - std::optional lineDistances; - const auto& props = feature.getProperties(); auto clip_start_it = props.find("mapbox_clip_start"); auto clip_end_it = props.find("mapbox_clip_end"); @@ -139,441 +99,19 @@ void LineBucket::addGeometry(const GeometryCoordinates& coordinates, total_length += util::dist(coordinates[i], coordinates[i + 1]); } - lineDistances = Distances{ + options.lineDistances = gfx::PolylineGeneratorDistances{ *numericValue(clip_start_it->second), *numericValue(clip_end_it->second), total_length}; } - const LineJoinType joinType = layout.evaluate(zoom, feature, canonical); - - const float miterLimit = joinType == LineJoinType::Bevel ? 1.05f : static_cast(layout.get()); - - const double sharpCornerOffset = - overscaling == 0 - ? SHARP_CORNER_OFFSET * (util::EXTENT / util::tileSize_D) - : (overscaling <= 16.0 ? SHARP_CORNER_OFFSET * (util::EXTENT / (util::tileSize_D * overscaling)) : 0.0); - - const GeometryCoordinate firstCoordinate = coordinates[first]; - const LineCapType beginCap = layout.get(); - const LineCapType endCap = type == FeatureType::Polygon ? LineCapType::Butt : LineCapType(layout.get()); - - double distance = 0.0; - bool startOfLine = true; - std::optional currentCoordinate; - std::optional prevCoordinate; - std::optional nextCoordinate; - std::optional> prevNormal; - std::optional> nextNormal; - - // the last three vertices added - e1 = e2 = e3 = -1; - - if (type == FeatureType::Polygon) { - currentCoordinate = coordinates[len - 2]; - nextNormal = util::perp(util::unit(convertPoint(firstCoordinate - *currentCoordinate))); - } - - const std::size_t startVertex = vertices.elements(); - std::vector triangleStore; - - for (std::size_t i = first; i < len; ++i) { - if (type == FeatureType::Polygon && i == len - 1) { - // if the line is closed, we treat the last vertex like the first - nextCoordinate = coordinates[first + 1]; - } else if (i + 1 < len) { - // just the next vertex - nextCoordinate = coordinates[i + 1]; - } else { - // there is no next vertex - nextCoordinate = {}; - } - - // if two consecutive vertices exist, skip the current one - if (nextCoordinate && coordinates[i] == *nextCoordinate) { - continue; - } - - if (nextNormal) { - prevNormal = *nextNormal; - } - if (currentCoordinate) { - prevCoordinate = *currentCoordinate; - } - - currentCoordinate = coordinates[i]; - - // 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 using the previous normal. - nextNormal = nextCoordinate ? util::perp(util::unit(convertPoint(*nextCoordinate - *currentCoordinate))) - : prevNormal; - - // 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; - } - - // Determine the normal of the join extrusion. It is the angle bisector - // of the segments between the previous line and the next line. - // In the case of 180° angles, the prev and next normals cancel each - // other out: prevNormal + nextNormal = (0, 0), its magnitude is 0, so - // the unit vector would be undefined. In that case, we're keeping the - // joinNormal at (0, 0), so that the cosHalfAngle below will also become - // 0 and miterLength will become Infinity. - Point joinNormal = *prevNormal + *nextNormal; - if (joinNormal.x != 0 || joinNormal.y != 0) { - joinNormal = util::unit(joinNormal); - } - - /* joinNormal prevNormal - * ↖ ↑ - * .________. prevVertex - * | - * nextNormal ← | currentVertex - * | - * nextVertex ! - * - */ - - // Calculate cosines of the angle (and its half) using dot product. - const double cosAngle = prevNormal->x * nextNormal->x + prevNormal->y * nextNormal->y; - const double cosHalfAngle = joinNormal.x * nextNormal->x + joinNormal.y * nextNormal->y; - - // Calculate the length of the miter (the ratio of the miter to the width) - // as the inverse of cosine of the angle between next and join normals. - const double miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle : std::numeric_limits::infinity(); - - // Approximate angle from cosine. - const double approxAngle = 2 * std::sqrt(2 - 2 * cosHalfAngle); - - const bool isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevCoordinate && nextCoordinate; - - if (isSharpCorner && i > first) { - const auto prevSegmentLength = util::dist(*currentCoordinate, *prevCoordinate); - if (prevSegmentLength > 2.0 * sharpCornerOffset) { - GeometryCoordinate newPrevVertex = *currentCoordinate - - convertPoint(util::round( - convertPoint(*currentCoordinate - *prevCoordinate) * - (sharpCornerOffset / prevSegmentLength))); - distance += util::dist(newPrevVertex, *prevCoordinate); - addCurrentVertex( - newPrevVertex, distance, *prevNormal, 0, 0, false, startVertex, triangleStore, lineDistances); - prevCoordinate = newPrevVertex; - } - } - - // The join if a middle vertex, otherwise the cap - const bool middleVertex = prevCoordinate && nextCoordinate; - LineJoinType currentJoin = joinType; - const LineCapType currentCap = nextCoordinate ? beginCap : endCap; - - if (middleVertex) { - if (currentJoin == LineJoinType::Round) { - if (miterLength < layout.get()) { - currentJoin = LineJoinType::Miter; - } else if (miterLength <= 2) { - currentJoin = LineJoinType::FakeRound; - } - } - - if (currentJoin == LineJoinType::Miter && miterLength > miterLimit) { - currentJoin = LineJoinType::Bevel; - } - - if (currentJoin == LineJoinType::Bevel) { - // The maximum extrude length is 128 / 63 = 2 times the width of - // the line so if miterLength >= 2 we need to draw a different - // type of bevel here. - if (miterLength > 2) { - currentJoin = LineJoinType::FlipBevel; - } - - // If the miterLength is really small and the line bevel wouldn't be visible, - // just draw a miter join to save a triangle. - if (miterLength < miterLimit) { - currentJoin = LineJoinType::Miter; - } - } - } - - // Calculate how far along the line the currentVertex is - if (prevCoordinate) distance += util::dist(*currentCoordinate, *prevCoordinate); - - if (middleVertex && currentJoin == LineJoinType::Miter) { - joinNormal = joinNormal * miterLength; - addCurrentVertex( - *currentCoordinate, distance, joinNormal, 0, 0, false, startVertex, triangleStore, lineDistances); + options.joinType = layout.evaluate(zoom, feature, canonical); + options.miterLimit = options.joinType == LineJoinType::Bevel ? 1.05f + : static_cast(layout.get()); + options.beginCap = layout.get(); + options.endCap = options.type == FeatureType::Polygon ? LineCapType::Butt : LineCapType(layout.get()); + options.roundLimit = layout.get(); + options.overscaling = overscaling; - } else if (middleVertex && currentJoin == LineJoinType::FlipBevel) { - // miter is too big, flip the direction to make a beveled join - - if (miterLength > 100) { - // Almost parallel lines - joinNormal = *nextNormal * -1.0; - } else { - const double direction = prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x > 0 ? -1 : 1; - const double bevelLength = miterLength * util::mag(*prevNormal + *nextNormal) / - util::mag(*prevNormal - *nextNormal); - joinNormal = util::perp(joinNormal) * bevelLength * direction; - } - - addCurrentVertex( - *currentCoordinate, distance, joinNormal, 0, 0, false, startVertex, triangleStore, lineDistances); - - addCurrentVertex(*currentCoordinate, - distance, - joinNormal * -1.0, - 0, - 0, - false, - startVertex, - triangleStore, - lineDistances); - } else if (middleVertex && (currentJoin == LineJoinType::Bevel || currentJoin == LineJoinType::FakeRound)) { - const bool lineTurnsLeft = (prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x) > 0; - const auto offset = static_cast(-std::sqrt(miterLength * miterLength - 1)); - float offsetA; - float offsetB; - - if (lineTurnsLeft) { - offsetB = 0; - offsetA = offset; - } else { - offsetA = 0; - offsetB = offset; - } - - // Close previous segement with bevel - if (!startOfLine) { - addCurrentVertex(*currentCoordinate, - distance, - *prevNormal, - offsetA, - offsetB, - false, - startVertex, - triangleStore, - lineDistances); - } - - if (currentJoin == LineJoinType::FakeRound) { - // The join angle is sharp enough that a round join would be - // visible. Bevel joins fill the gap between segments with a - // single pie slice triangle. Create a round join by adding - // multiple pie slices. The join isn't actually round, but it - // looks like it is at the sizes we render lines at. - - // Pick the number of triangles for approximating round join by - // based on the angle between normals. - const auto n = static_cast(::round((approxAngle * 180 / M_PI) / DEG_PER_TRIANGLE)); - - for (unsigned m = 1; m < n; ++m) { - double t = static_cast(m) / n; - if (t != 0.5) { - // approximate spherical interpolation - // https://observablehq.com/@mourner/approximating-geometric-slerp - const double t2 = t - 0.5; - const double A = 1.0904 + cosAngle * (-3.2452 + cosAngle * (3.55645 - cosAngle * 1.43519)); - const double B = 0.848013 + cosAngle * (-1.06021 + cosAngle * 0.215638); - t = t + t * t2 * (t - 1) * (A * t2 * t2 + B); - } - auto approxFractionalNormal = util::unit(*prevNormal * (1.0 - t) + *nextNormal * t); - addPieSliceVertex(*currentCoordinate, - distance, - approxFractionalNormal, - lineTurnsLeft, - startVertex, - triangleStore, - lineDistances); - } - } - - // Start next segment - if (nextCoordinate) { - addCurrentVertex(*currentCoordinate, - distance, - *nextNormal, - -offsetA, - -offsetB, - false, - startVertex, - triangleStore, - lineDistances); - } - - } else if (!middleVertex && currentCap == LineCapType::Butt) { - if (!startOfLine) { - // Close previous segment with a butt - addCurrentVertex( - *currentCoordinate, distance, *prevNormal, 0, 0, false, startVertex, triangleStore, lineDistances); - } - - // Start next segment with a butt - if (nextCoordinate) { - addCurrentVertex( - *currentCoordinate, distance, *nextNormal, 0, 0, false, startVertex, triangleStore, lineDistances); - } - - } else if (!middleVertex && currentCap == LineCapType::Square) { - if (!startOfLine) { - // Close previous segment with a square cap - addCurrentVertex( - *currentCoordinate, distance, *prevNormal, 1, 1, false, startVertex, triangleStore, lineDistances); - - // The segment is done. Unset vertices to disconnect segments. - e1 = e2 = -1; - } - - // Start next segment - if (nextCoordinate) { - addCurrentVertex(*currentCoordinate, - distance, - *nextNormal, - -1, - -1, - false, - startVertex, - triangleStore, - lineDistances); - } - - } else if (middleVertex ? currentJoin == LineJoinType::Round : currentCap == LineCapType::Round) { - if (!startOfLine) { - // Close previous segment with a butt - addCurrentVertex( - *currentCoordinate, distance, *prevNormal, 0, 0, false, startVertex, triangleStore, lineDistances); - - // Add round cap or linejoin at end of segment - addCurrentVertex( - *currentCoordinate, distance, *prevNormal, 1, 1, true, startVertex, triangleStore, lineDistances); - - // The segment is done. Unset vertices to disconnect segments. - e1 = e2 = -1; - } - - // Start next segment with a butt - if (nextCoordinate) { - // Add round cap before first segment - addCurrentVertex( - *currentCoordinate, distance, *nextNormal, -1, -1, true, startVertex, triangleStore, lineDistances); - - addCurrentVertex( - *currentCoordinate, distance, *nextNormal, 0, 0, false, startVertex, triangleStore, lineDistances); - } - } - - if (isSharpCorner && i < len - 1) { - const auto nextSegmentLength = util::dist(*currentCoordinate, *nextCoordinate); - if (nextSegmentLength > 2 * sharpCornerOffset) { - GeometryCoordinate newCurrentVertex = *currentCoordinate + - convertPoint(util::round( - convertPoint(*nextCoordinate - *currentCoordinate) * - (sharpCornerOffset / nextSegmentLength))); - distance += util::dist(newCurrentVertex, *currentCoordinate); - addCurrentVertex( - newCurrentVertex, distance, *nextNormal, 0, 0, false, startVertex, triangleStore, lineDistances); - currentCoordinate = newCurrentVertex; - } - } - - startOfLine = false; - } - - const std::size_t endVertex = vertices.elements(); - const std::size_t vertexCount = endVertex - startVertex; - - if (segments.empty() || segments.back().vertexLength + vertexCount > std::numeric_limits::max()) { - segments.emplace_back(startVertex, triangles.elements()); - } - - auto& segment = segments.back(); - assert(segment.vertexLength <= std::numeric_limits::max()); - const auto index = static_cast(segment.vertexLength); - - for (const auto& triangle : triangleStore) { - triangles.emplace_back(index + triangle.a, index + triangle.b, index + triangle.c); - } - - segment.vertexLength += vertexCount; - segment.indexLength += triangleStore.size() * 3; -} - -void LineBucket::addCurrentVertex(const GeometryCoordinate& currentCoordinate, - double& distance, - const Point& normal, - double endLeft, - double endRight, - bool round, - std::size_t startVertex, - std::vector& triangleStore, - std::optional lineDistances) { - Point extrude = normal; - double scaledDistance = lineDistances ? lineDistances->scaleToMaxLineDistance(distance) : distance; - - if (endLeft) extrude = extrude - (util::perp(normal) * endLeft); - vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, - extrude, - round, - false, - static_cast(endLeft), - static_cast(scaledDistance * LINE_DISTANCE_SCALE))); - e3 = vertices.elements() - 1 - startVertex; - if (e1 >= 0 && e2 >= 0) { - triangleStore.emplace_back(static_cast(e1), static_cast(e2), static_cast(e3)); - } - e1 = e2; - e2 = e3; - - extrude = normal * -1.0; - if (endRight) extrude = extrude - (util::perp(normal) * endRight); - vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, - extrude, - round, - true, - static_cast(-endRight), - static_cast(scaledDistance * LINE_DISTANCE_SCALE))); - e3 = vertices.elements() - 1 - startVertex; - if (e1 >= 0 && e2 >= 0) { - triangleStore.emplace_back(static_cast(e1), static_cast(e2), static_cast(e3)); - } - e1 = e2; - e2 = e3; - - // There is a maximum "distance along the line" that we can store in the - // buffers. When we get close to the distance, reset it to zero and add the - // vertex again with a distance of zero. The max distance is determined by - // the number of bits we allocate to `linesofar`. - if (distance > MAX_LINE_DISTANCE / 2.0f && !lineDistances) { - distance = 0.0; - addCurrentVertex( - currentCoordinate, distance, normal, endLeft, endRight, round, startVertex, triangleStore, lineDistances); - } -} - -void LineBucket::addPieSliceVertex(const GeometryCoordinate& currentVertex, - double distance, - const Point& extrude, - bool lineTurnsLeft, - std::size_t startVertex, - std::vector& triangleStore, - std::optional lineDistances) { - Point flippedExtrude = extrude * (lineTurnsLeft ? -1.0 : 1.0); - if (lineDistances) { - distance = lineDistances->scaleToMaxLineDistance(distance); - } - - vertices.emplace_back(LineProgram::layoutVertex( - currentVertex, flippedExtrude, false, lineTurnsLeft, 0, static_cast(distance * LINE_DISTANCE_SCALE))); - e3 = vertices.elements() - 1 - startVertex; - if (e1 >= 0 && e2 >= 0) { - triangleStore.emplace_back(static_cast(e1), static_cast(e2), static_cast(e3)); - } - - if (lineTurnsLeft) { - e2 = e3; - } else { - e1 = e3; - } + generator.generate(coordinates, options); } void LineBucket::upload([[maybe_unused]] gfx::UploadPass& uploadPass) { diff --git a/src/mbgl/renderer/buckets/line_bucket.hpp b/src/mbgl/renderer/buckets/line_bucket.hpp index 36ee3258989..68757058ae5 100644 --- a/src/mbgl/renderer/buckets/line_bucket.hpp +++ b/src/mbgl/renderer/buckets/line_bucket.hpp @@ -62,37 +62,6 @@ class LineBucket final : public Bucket { private: void addGeometry(const GeometryCoordinates&, const GeometryTileFeature&, const CanonicalTileID&); - struct TriangleElement { - TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) - : a(a_), - b(b_), - c(c_) {} - uint16_t a, b, c; - }; - - class Distances; - void addCurrentVertex(const GeometryCoordinate& currentCoordinate, - double& distance, - const Point& normal, - double endLeft, - double endRight, - bool round, - std::size_t startVertex, - std::vector& triangleStore, - std::optional distances); - - void addPieSliceVertex(const GeometryCoordinate& currentVertex, - double distance, - const Point& extrude, - bool lineTurnsLeft, - std::size_t startVertex, - std::vector& triangleStore, - std::optional distances); - - std::ptrdiff_t e1; - std::ptrdiff_t e2; - std::ptrdiff_t e3; - const float zoom; const uint32_t overscaling; }; diff --git a/src/mbgl/renderer/layer_group.cpp b/src/mbgl/renderer/layer_group.cpp index 48c423d6a82..8ef50ea9e2c 100644 --- a/src/mbgl/renderer/layer_group.cpp +++ b/src/mbgl/renderer/layer_group.cpp @@ -18,10 +18,10 @@ void LayerGroupBase::addDrawable(gfx::UniqueDrawable& drawable) { } } -void LayerGroupBase::runTweakers(const RenderTree& renderTree, PaintParameters& parameters) { +void LayerGroupBase::runTweakers(const RenderTree&, PaintParameters& parameters) { for (auto it = layerTweakers.begin(); it != layerTweakers.end();) { if (auto tweaker = it->lock()) { - tweaker->execute(*this, renderTree, parameters); + tweaker->execute(*this, parameters); ++it; } else { it = layerTweakers.erase(it); diff --git a/src/mbgl/renderer/layer_tweaker.cpp b/src/mbgl/renderer/layer_tweaker.cpp index 682445df762..57e3278fbd5 100644 --- a/src/mbgl/renderer/layer_tweaker.cpp +++ b/src/mbgl/renderer/layer_tweaker.cpp @@ -26,8 +26,7 @@ bool LayerTweaker::checkTweakDrawable(const gfx::Drawable& drawable) const { } mat4 LayerTweaker::getTileMatrix(const UnwrappedTileID& tileID, - const RenderTree& renderTree, - const TransformState& state, + const PaintParameters& parameters, const std::array& translation, style::TranslateAnchorType anchor, bool nearClipped, @@ -35,16 +34,16 @@ mat4 LayerTweaker::getTileMatrix(const UnwrappedTileID& tileID, bool aligned) { // from RenderTile::prepare mat4 tileMatrix; - state.matrixFor(/*out*/ tileMatrix, tileID); + parameters.state.matrixFor(/*out*/ tileMatrix, tileID); - const auto& transformParams = renderTree.getParameters().transformParams; // nearClippedMatrix has near plane moved further, to enhance depth buffer precision - const auto& projMatrix = aligned - ? transformParams.alignedProjMatrix - : (nearClipped ? transformParams.nearClippedProjMatrix : transformParams.projMatrix); + const auto& projMatrix = aligned ? parameters.transformParams.alignedProjMatrix + : (nearClipped ? parameters.transformParams.nearClippedProjMatrix + : parameters.transformParams.projMatrix); matrix::multiply(tileMatrix, projMatrix, tileMatrix); - return RenderTile::translateVtxMatrix(tileID, tileMatrix, translation, anchor, state, inViewportPixelUnits); + return RenderTile::translateVtxMatrix( + tileID, tileMatrix, translation, anchor, parameters.state, inViewportPixelUnits); } void LayerTweaker::updateProperties(Immutable newProps) { diff --git a/src/mbgl/renderer/layers/background_layer_tweaker.cpp b/src/mbgl/renderer/layers/background_layer_tweaker.cpp index 5b063d8e71c..8675161a0b6 100644 --- a/src/mbgl/renderer/layers/background_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/background_layer_tweaker.cpp @@ -23,7 +23,7 @@ static const StringIdentity idBackgroundDrawableUBOName = stringIndexer().get("B static const StringIdentity idBackgroundLayerUBOName = stringIndexer().get("BackgroundLayerUBO"); static const StringIdentity idTexUniformName = stringIndexer().get("u_image"); -void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const RenderTree&, const PaintParameters& parameters) { +void BackgroundLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { const auto& state = parameters.state; auto& context = parameters.context; diff --git a/src/mbgl/renderer/layers/background_layer_tweaker.hpp b/src/mbgl/renderer/layers/background_layer_tweaker.hpp index 8c750f0a943..8d970171b3f 100644 --- a/src/mbgl/renderer/layers/background_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/background_layer_tweaker.hpp @@ -25,7 +25,7 @@ class BackgroundLayerTweaker : public LayerTweaker { public: ~BackgroundLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: gfx::UniformBufferPtr backgroundLayerBuffer; diff --git a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp index ad37fb0520c..321040a70bf 100644 --- a/src/mbgl/renderer/layers/circle_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/circle_layer_tweaker.cpp @@ -29,9 +29,7 @@ static const StringIdentity idCircleEvaluatedPropsUBOName = stringIndexer().get( static const StringIdentity idExpressionInputsUBOName = stringIndexer().get("ExpressionInputsUBO"); static const StringIdentity idCirclePermutationUBOName = stringIndexer().get("CirclePermutationUBO"); -void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& evaluated = static_cast(*evaluatedProperties).evaluated; @@ -123,8 +121,7 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup, const auto anchor = evaluated.get(); constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix constexpr bool nearClipped = false; - const auto matrix = getTileMatrix( - tileID, renderTree, parameters.state, translation, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); const auto pixelsToTileUnits = tileID.pixelsToTileUnits(1.0f, static_cast(zoom)); const auto extrudeScale = pitchWithMap ? std::array{pixelsToTileUnits, pixelsToTileUnits} diff --git a/src/mbgl/renderer/layers/circle_layer_tweaker.hpp b/src/mbgl/renderer/layers/circle_layer_tweaker.hpp index 5bdfd24fa03..e78f30003be 100644 --- a/src/mbgl/renderer/layers/circle_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/circle_layer_tweaker.hpp @@ -16,7 +16,7 @@ class CircleLayerTweaker : public LayerTweaker { : LayerTweaker(std::move(id_), properties) {} ~CircleLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: gfx::UniformBufferPtr paintParamsUniformBuffer; diff --git a/src/mbgl/renderer/layers/collision_layer_tweaker.cpp b/src/mbgl/renderer/layers/collision_layer_tweaker.cpp index 160a46df83c..c9622a0aa1d 100644 --- a/src/mbgl/renderer/layers/collision_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/collision_layer_tweaker.cpp @@ -24,15 +24,12 @@ using namespace shaders; const StringIdentity CollisionLayerTweaker::idCollisionCircleUBOName = stringIndexer().get(CollisionCircleUBOName); const StringIdentity CollisionLayerTweaker::idCollisionBoxUBOName = stringIndexer().get(CollisionBoxUBOName); -void CollisionLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void CollisionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { if (layerGroup.empty()) { return; } auto& context = parameters.context; - const auto& state = parameters.state; #if !defined(NDEBUG) const auto label = layerGroup.getName() + "-update-uniforms"; @@ -52,8 +49,7 @@ void CollisionLayerTweaker::execute(LayerGroupBase& layerGroup, const auto anchor = data.translateAnchor; constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; - const auto matrix = getTileMatrix( - tileID, renderTree, state, translate, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix(tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits); // extrude scale const auto pixelRatio = tileID.pixelsToTileUnits(1.0f, static_cast(parameters.state.getZoom())); diff --git a/src/mbgl/renderer/layers/collision_layer_tweaker.hpp b/src/mbgl/renderer/layers/collision_layer_tweaker.hpp index 6a47bf7a1de..42c26c9c6fc 100644 --- a/src/mbgl/renderer/layers/collision_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/collision_layer_tweaker.hpp @@ -18,7 +18,7 @@ class CollisionLayerTweaker : public LayerTweaker { public: ~CollisionLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; static constexpr auto CollisionCircleUBOName = "CollisionCircleUBO"; static const StringIdentity idCollisionCircleUBOName; diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp index 2e83f28642a..55161b7a332 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp @@ -40,9 +40,7 @@ const StringIdentity FillExtrusionLayerTweaker::idFillExtrusionTilePropsUBOName const StringIdentity FillExtrusionLayerTweaker::idFillExtrusionInterpolateUBOName = stringIndexer().get( "FillExtrusionInterpolateUBO"); -void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& props = static_cast(*evaluatedProperties); const auto& evaluated = props.evaluated; @@ -124,8 +122,7 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, const auto anchor = evaluated.get(); constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix constexpr bool nearClipped = true; - const auto matrix = getTileMatrix( - tileID, renderTree, parameters.state, translation, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); const auto tileRatio = 1 / tileID.pixelsToTileUnits(1, state.getIntegerZoom()); const auto zoomScale = state.zoomScale(tileID.canonical.z); diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp index 4d3cef45c89..85304035920 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp @@ -18,7 +18,7 @@ class FillExtrusionLayerTweaker : public LayerTweaker { public: ~FillExtrusionLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; static const StringIdentity idFillExtrusionTilePropsUBOName; static const StringIdentity idFillExtrusionInterpolateUBOName; diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp index e5b0fac8d5d..52e7e8b1f43 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.cpp @@ -61,9 +61,7 @@ static const StringIdentity idExpressionInputsUBOName = stringIndexer().get("Exp static const StringIdentity idTexImageName = stringIndexer().get("u_image"); using namespace shaders; -void FillLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void FillLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& props = static_cast(*evaluatedProperties); const auto& evaluated = props.evaluated; @@ -242,8 +240,7 @@ void FillLayerTweaker::execute(LayerGroupBase& layerGroup, constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix constexpr bool nearClipped = false; - const auto matrix = getTileMatrix( - tileID, renderTree, parameters.state, translation, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); // from FillPatternProgram::layoutUniformValues const auto tileRatio = 1.0f / tileID.pixelsToTileUnits(1.0f, intZoom); diff --git a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp index e0495507b46..cd11ba0fa87 100644 --- a/src/mbgl/renderer/layers/fill_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_layer_tweaker.hpp @@ -18,7 +18,7 @@ class FillLayerTweaker : public LayerTweaker { public: ~FillLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; static const StringIdentity idFillTilePropsUBOName; static const StringIdentity idFillInterpolateUBOName; diff --git a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp index 2867468f42f..70b7bc2e666 100644 --- a/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_layer_tweaker.cpp @@ -25,9 +25,7 @@ static const StringIdentity idHeatmapEvaluatedPropsUBOName = stringIndexer().get static const StringIdentity idHeatmapPermutationUBOName = stringIndexer().get("HeatmapPermutationUBO"); static const StringIdentity idExpressionInputsUBOName = stringIndexer().get("ExpressionInputsUBO"); -void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& evaluated = static_cast(*evaluatedProperties).evaluated; @@ -91,13 +89,8 @@ void HeatmapLayerTweaker::execute(LayerGroupBase& layerGroup, constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; - const auto matrix = getTileMatrix(tileID, - renderTree, - parameters.state, - {0.f, 0.f}, - TranslateAnchorType::Viewport, - nearClipped, - inViewportPixelUnits); + const auto matrix = getTileMatrix( + tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, nearClipped, inViewportPixelUnits); const HeatmapDrawableUBO drawableUBO = { /* .matrix = */ util::cast(matrix), /* .extrude_scale = */ tileID.pixelsToTileUnits(1.0f, static_cast(zoom)), diff --git a/src/mbgl/renderer/layers/heatmap_layer_tweaker.hpp b/src/mbgl/renderer/layers/heatmap_layer_tweaker.hpp index 5a1859f71b1..875c1cd8edf 100644 --- a/src/mbgl/renderer/layers/heatmap_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/heatmap_layer_tweaker.hpp @@ -15,7 +15,7 @@ class HeatmapLayerTweaker : public LayerTweaker { public: ~HeatmapLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: gfx::UniformBufferPtr evaluatedPropsUniformBuffer; diff --git a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp index 8efc1645dce..99fb62e04e7 100644 --- a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.cpp @@ -18,9 +18,7 @@ using namespace shaders; static const StringIdentity idHeatmapTextureDrawableUBOName = stringIndexer().get("HeatmapTextureDrawableUBO"); -void HeatmapTextureLayerTweaker::execute(LayerGroupBase& layerGroup, - [[maybe_unused]] const RenderTree& renderTree, - const PaintParameters& parameters) { +void HeatmapTextureLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; if (layerGroup.empty()) { diff --git a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp index 7f4b47aa9ee..62c5a12f98b 100644 --- a/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/heatmap_texture_layer_tweaker.hpp @@ -15,7 +15,7 @@ class HeatmapTextureLayerTweaker : public LayerTweaker { public: ~HeatmapTextureLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: }; diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp index 786858516ed..0886ab272de 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp @@ -32,9 +32,7 @@ std::array getLight(const PaintParameters& parameters, return {{evaluated.get(), azimuthal}}; } -void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; if (layerGroup.empty()) { @@ -64,7 +62,7 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, drawable.mutableUniformBuffers().addOrReplace(idHillshadeEvaluatedPropsUBOName, evaluatedPropsUniformBuffer); const auto matrix = getTileMatrix( - tileID, renderTree, parameters.state, {0.f, 0.f}, TranslateAnchorType::Viewport, false, false, true); + tileID, parameters, {0.f, 0.f}, TranslateAnchorType::Viewport, false, false, true); HillshadeDrawableUBO drawableUBO = {/* .matrix = */ util::cast(matrix), /* .latrange = */ getLatRange(tileID), /* .light = */ getLight(parameters, evaluated), diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.hpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.hpp index 3bb4958660a..3c7d1016663 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.hpp @@ -15,7 +15,7 @@ class HillshadeLayerTweaker : public LayerTweaker { public: ~HillshadeLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: gfx::UniformBufferPtr evaluatedPropsUniformBuffer; diff --git a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp index d445c192bf9..446ffbc250d 100644 --- a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.cpp @@ -27,9 +27,7 @@ const std::array& getUnpackVector(Tileset::DEMEncoding encoding) { return encoding == Tileset::DEMEncoding::Terrarium ? unpackTerrarium : unpackMapbox; } -void HillshadePrepareLayerTweaker::execute(LayerGroupBase& layerGroup, - [[maybe_unused]] const RenderTree& renderTree, - const PaintParameters& parameters) { +void HillshadePrepareLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { // const auto& evaluated = static_cast(*evaluatedProperties).evaluated; if (layerGroup.empty()) { diff --git a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.hpp b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.hpp index 85afa3fe1a0..47c4352f10b 100644 --- a/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/hillshade_prepare_layer_tweaker.hpp @@ -15,7 +15,7 @@ class HillshadePrepareLayerTweaker : public LayerTweaker { public: ~HillshadePrepareLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: }; diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.cpp b/src/mbgl/renderer/layers/line_layer_tweaker.cpp index b1bf897f497..6a0e42cb8d5 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.cpp @@ -37,9 +37,7 @@ static const StringIdentity idTexImageName = stringIndexer().get("u_image"); static const StringIdentity idExpressionInputsUBOName = stringIndexer().get("ExpressionInputsUBO"); static const StringIdentity idLinePermutationUBOName = stringIndexer().get("LinePermutationUBO"); -void LineLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void LineLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& evaluated = static_cast(*evaluatedProperties).evaluated; const auto& crossfade = static_cast(*evaluatedProperties).crossfade; @@ -155,8 +153,7 @@ void LineLayerTweaker::execute(LayerGroupBase& layerGroup, constexpr bool inViewportPixelUnits = false; // from RenderTile::translatedMatrix auto& uniforms = drawable.mutableUniformBuffers(); - const auto matrix = getTileMatrix( - tileID, renderTree, parameters.state, translation, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix(tileID, parameters, translation, anchor, nearClipped, inViewportPixelUnits); const LineType type = static_cast(drawable.getType()); diff --git a/src/mbgl/renderer/layers/line_layer_tweaker.hpp b/src/mbgl/renderer/layers/line_layer_tweaker.hpp index bb7f9eb68e3..75a0b641802 100644 --- a/src/mbgl/renderer/layers/line_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/line_layer_tweaker.hpp @@ -28,7 +28,7 @@ class LineLayerTweaker : public LayerTweaker { ~LineLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: gfx::UniformBufferPtr linePropertiesBuffer; diff --git a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp index 6d4a38ed8e7..501b119a3a9 100644 --- a/src/mbgl/renderer/layers/raster_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/raster_layer_tweaker.cpp @@ -20,7 +20,6 @@ using namespace shaders; static const StringIdentity idRasterDrawableUBOName = stringIndexer().get("RasterDrawableUBO"); void RasterLayerTweaker::execute([[maybe_unused]] LayerGroupBase& layerGroup, - [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; diff --git a/src/mbgl/renderer/layers/raster_layer_tweaker.hpp b/src/mbgl/renderer/layers/raster_layer_tweaker.hpp index 2d39ead75c6..e56be3c5bee 100644 --- a/src/mbgl/renderer/layers/raster_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/raster_layer_tweaker.hpp @@ -20,7 +20,7 @@ class RasterLayerTweaker : public LayerTweaker { public: ~RasterLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; protected: gfx::UniformBufferPtr evaluatedPropsUniformBuffer = nullptr; diff --git a/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp b/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp new file mode 100644 index 00000000000..b05965a750c --- /dev/null +++ b/src/mbgl/renderer/layers/render_custom_drawable_layer.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if MLN_DRAWABLE_RENDERER +#include +#include +#include +#endif + +namespace mbgl { + +using namespace style; + +namespace { + +inline const CustomDrawableLayer::Impl& impl(const Immutable& impl) { + assert(impl->getTypeInfo() == CustomDrawableLayer::Impl::staticTypeInfo()); + return static_cast(*impl); +} + +} // namespace + +RenderCustomDrawableLayer::RenderCustomDrawableLayer(Immutable _impl) + : RenderLayer(makeMutable(std::move(_impl))), + host(impl(baseImpl).host) { + assert(gfx::BackendScope::exists()); + host->initialize(); +} + +RenderCustomDrawableLayer::~RenderCustomDrawableLayer() { + assert(gfx::BackendScope::exists()); + host->deinitialize(); +} + +void RenderCustomDrawableLayer::evaluate(const PropertyEvaluationParameters&) { + passes = RenderPass::Translucent; + // It is fine to not update `evaluatedProperties`, as `baseImpl` should never be updated for this layer. +} + +bool RenderCustomDrawableLayer::hasTransition() const { + return false; +} +bool RenderCustomDrawableLayer::hasCrossfade() const { + return false; +} + +void RenderCustomDrawableLayer::markContextDestroyed() { + contextDestroyed = true; +} + +void RenderCustomDrawableLayer::prepare(const LayerPrepareParameters&) {} + +#if MLN_LEGACY_RENDERER +void RenderCustomDrawableLayer::render(PaintParameters&) {} +#endif + +#if MLN_DRAWABLE_RENDERER +void RenderCustomDrawableLayer::update(gfx::ShaderRegistry& shaders, + gfx::Context& context, + const TransformState& state, + const std::shared_ptr& updateParameters, + const RenderTree& renderTree, + UniqueChangeRequestVec& changes) { + std::unique_lock guard(mutex); + + // check if host changed and update + bool hostChanged = (host != impl(baseImpl).host); + if (hostChanged) { + // deinitialize the previous one before initializing the new one. + if (host) { + host->deinitialize(); + } + host = impl(baseImpl).host; + host->initialize(); + } + + // delegate the call to the custom layer + if (host) { + host->update(*this, shaders, context, state, updateParameters, renderTree, changes); + } +} +#endif + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_custom_drawable_layer.hpp b/src/mbgl/renderer/layers/render_custom_drawable_layer.hpp new file mode 100644 index 00000000000..bd03b6e8ec8 --- /dev/null +++ b/src/mbgl/renderer/layers/render_custom_drawable_layer.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace mbgl { + +class RenderCustomDrawableLayer final : public RenderLayer { +public: + explicit RenderCustomDrawableLayer(Immutable); + ~RenderCustomDrawableLayer() override; + +#if MLN_DRAWABLE_RENDERER + /// Generate any changes needed by the layer + void update(gfx::ShaderRegistry&, + gfx::Context&, + const TransformState&, + const std::shared_ptr&, + const RenderTree&, + UniqueChangeRequestVec&) override; +#endif + +private: + void transition(const TransitionParameters&) override {} + void evaluate(const PropertyEvaluationParameters&) override; + bool hasTransition() const override; + bool hasCrossfade() const override; + void markContextDestroyed() override; + void prepare(const LayerPrepareParameters&) override; + +#if MLN_LEGACY_RENDERER + void render(PaintParameters&) override; +#endif + + bool contextDestroyed = false; + std::shared_ptr host; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp index 40c9d32a909..915229657be 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.cpp @@ -72,9 +72,7 @@ const StringIdentity SymbolLayerTweaker::idSymbolDrawableTilePropsUBOName = stri const StringIdentity SymbolLayerTweaker::idSymbolDrawableInterpolateUBOName = stringIndexer().get( "SymbolDrawableInterpolateUBO"); -void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, - const RenderTree& renderTree, - const PaintParameters& parameters) { +void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { auto& context = parameters.context; const auto& state = parameters.state; const auto& evaluated = static_cast(*evaluatedProperties).evaluated; @@ -148,8 +146,7 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, : evaluated.get(); constexpr bool nearClipped = false; constexpr bool inViewportPixelUnits = false; - const auto matrix = getTileMatrix( - tileID, renderTree, state, translate, anchor, nearClipped, inViewportPixelUnits); + const auto matrix = getTileMatrix(tileID, parameters, translate, anchor, nearClipped, inViewportPixelUnits); // from symbol_program, makeValues const auto currentZoom = static_cast(zoom); diff --git a/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp b/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp index a632da3ffe9..4e0f6b5563d 100644 --- a/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/symbol_layer_tweaker.hpp @@ -18,7 +18,7 @@ class SymbolLayerTweaker : public LayerTweaker { public: ~SymbolLayerTweaker() override = default; - void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override; + void execute(LayerGroupBase&, const PaintParameters&) override; static const StringIdentity idSymbolDrawableUBOName; static const StringIdentity idSymbolDynamicUBOName; diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 33bdca1019b..441e2947eec 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -386,7 +386,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, #endif // MLN_LEGACY_RENDERER #if MLN_DRAWABLE_RENDERER - // Give the layers a chance to do cleanup + // Give the layers a chance to do cleanup orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.postRender(orchestrator, parameters); }); #endif diff --git a/src/mbgl/style/layers/custom_drawable_layer.cpp b/src/mbgl/style/layers/custom_drawable_layer.cpp new file mode 100644 index 00000000000..a70dd0d9b85 --- /dev/null +++ b/src/mbgl/style/layers/custom_drawable_layer.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +namespace { +const LayerTypeInfo typeInfoCustomDrawable{"custom-drawable", + LayerTypeInfo::Source::NotRequired, + LayerTypeInfo::Pass3D::NotRequired, + LayerTypeInfo::Layout::NotRequired, + LayerTypeInfo::FadingTiles::NotRequired, + LayerTypeInfo::CrossTileIndex::NotRequired, + LayerTypeInfo::TileKind::NotRequired}; +} // namespace + +CustomDrawableLayer::CustomDrawableLayer(const std::string& layerID, std::unique_ptr host) + : Layer(makeMutable(layerID, std::move(host))) {} + +CustomDrawableLayer::~CustomDrawableLayer() = default; + +const CustomDrawableLayer::Impl& CustomDrawableLayer::impl() const { + return static_cast(*baseImpl); +} + +Mutable CustomDrawableLayer::mutableImpl() const { + return makeMutable(impl()); +} + +std::unique_ptr CustomDrawableLayer::cloneRef(const std::string&) const { + assert(false); + return nullptr; +} + +using namespace conversion; + +std::optional CustomDrawableLayer::setPropertyInternal(const std::string&, const Convertible&) { + return Error{"layer doesn't support this property"}; +} + +StyleProperty CustomDrawableLayer::getProperty(const std::string&) const { + return {}; +} + +Mutable CustomDrawableLayer::mutableBaseImpl() const { + return staticMutableCast(mutableImpl()); +} + +// static +const LayerTypeInfo* CustomDrawableLayer::Impl::staticTypeInfo() noexcept { + return &typeInfoCustomDrawable; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/custom_drawable_layer_impl.cpp b/src/mbgl/style/layers/custom_drawable_layer_impl.cpp new file mode 100644 index 00000000000..92e7bccbb2b --- /dev/null +++ b/src/mbgl/style/layers/custom_drawable_layer_impl.cpp @@ -0,0 +1,18 @@ +#include + +namespace mbgl { +namespace style { + +CustomDrawableLayer::Impl::Impl(const std::string& id_, std::unique_ptr host_) + : Layer::Impl(id_, std::string()) { + host = std::move(host_); +} + +bool CustomDrawableLayer::Impl::hasLayoutDifference(const Layer::Impl&) const { + return false; +} + +void CustomDrawableLayer::Impl::stringifyLayout(rapidjson::Writer&) const {} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/custom_drawable_layer_impl.hpp b/src/mbgl/style/layers/custom_drawable_layer_impl.hpp new file mode 100644 index 00000000000..7d74decd4c2 --- /dev/null +++ b/src/mbgl/style/layers/custom_drawable_layer_impl.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include + +namespace mbgl { + +class TransformState; + +namespace style { + +class CustomDrawableLayer::Impl : public Layer::Impl { +public: + Impl(const std::string& id, std::unique_ptr host); + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer&) const override; + + std::shared_ptr host; + + DECLARE_LAYER_TYPE_INFO; +}; + +class CustomDrawableLayerProperties final : public LayerProperties { +public: + explicit CustomDrawableLayerProperties(Immutable impl) + : LayerProperties(std::move(impl)) {} +}; + +} // namespace style +} // namespace mbgl diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a11b5858798..bd46c6209ef 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -128,6 +128,7 @@ if(MLN_WITH_OPENGL) mbgl-test PRIVATE ${PROJECT_SOURCE_DIR}/test/api/custom_layer.test.cpp + ${PROJECT_SOURCE_DIR}/test/api/custom_drawable_layer.test.cpp ${PROJECT_SOURCE_DIR}/test/gl/bucket.test.cpp ${PROJECT_SOURCE_DIR}/test/gl/enum.test.cpp ${PROJECT_SOURCE_DIR}/test/gl/context.test.cpp diff --git a/test/api/custom_drawable_layer.test.cpp b/test/api/custom_drawable_layer.test.cpp new file mode 100644 index 00000000000..990d7bceb75 --- /dev/null +++ b/test/api/custom_drawable_layer.test.cpp @@ -0,0 +1,243 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if MLN_DRAWABLE_RENDERER + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace mbgl; +using namespace mbgl::style; + +class TestDrawableTweaker : public gfx::DrawableTweaker { +public: + TestDrawableTweaker() {} + ~TestDrawableTweaker() override = default; + + void init(gfx::Drawable&) override{}; + + void execute(gfx::Drawable& drawable, const PaintParameters& parameters) override { + if (!drawable.getTileID().has_value()) { + return; + } + + const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); + const auto zoom = parameters.state.getZoom(); + mat4 tileMatrix; + parameters.state.matrixFor(/*out*/ tileMatrix, tileID); + + const auto matrix = LayerTweaker::getTileMatrix( + tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, false); + + static const StringIdentity idLineUBOName = stringIndexer().get("LineUBO"); + const shaders::LineUBO lineUBO{ + /*matrix = */ util::cast(matrix), + /*units_to_pixels = */ {1.0f / parameters.pixelsToGLUnits[0], 1.0f / parameters.pixelsToGLUnits[1]}, + /*ratio = */ 1.0f / tileID.pixelsToTileUnits(1.0f, zoom), + /*device_pixel_ratio = */ parameters.pixelRatio}; + + static const StringIdentity idLinePropertiesUBOName = stringIndexer().get("LinePropertiesUBO"); + const shaders::LinePropertiesUBO linePropertiesUBO{/*color =*/Color(1.f, 0.f, 1.f, 1.f), + /*blur =*/0.f, + /*opacity =*/1.f, + /*gapwidth =*/0.f, + /*offset =*/0.f, + /*width =*/8.f, + 0, + 0, + 0}; + + static const StringIdentity idLineInterpolationUBOName = stringIndexer().get("LineInterpolationUBO"); + const shaders::LineInterpolationUBO lineInterpolationUBO{/*color_t =*/0.f, + /*blur_t =*/0.f, + /*opacity_t =*/0.f, + /*gapwidth_t =*/0.f, + /*offset_t =*/0.f, + /*width_t =*/0.f, + 0, + 0}; + auto& uniforms = drawable.mutableUniformBuffers(); + uniforms.createOrUpdate(idLineUBOName, &lineUBO, parameters.context); + uniforms.createOrUpdate(idLinePropertiesUBOName, &linePropertiesUBO, parameters.context); + uniforms.createOrUpdate(idLineInterpolationUBOName, &lineInterpolationUBO, parameters.context); + +#if MLN_RENDER_BACKEND_METAL + static const StringIdentity idExpressionInputsUBOName = stringIndexer().get("ExpressionInputsUBO"); + const auto expressionUBO = LayerTweaker::buildExpressionUBO(zoom, parameters.frameCount); + uniforms.createOrUpdate(idExpressionInputsUBOName, &expressionUBO, parameters.context); + + static const StringIdentity idLinePermutationUBOName = stringIndexer().get("LinePermutationUBO"); + const shaders::LinePermutationUBO permutationUBO = { + /* .color = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .blur = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .opacity = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .gapwidth = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .offset = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .width = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .floorwidth = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .pattern_from = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .pattern_to = */ {/*.source=*/shaders::AttributeSource::Constant, /*.expression=*/{}}, + /* .overdrawInspector = */ false, + /* .pad = */ 0, + 0, + 0, + 0}; + uniforms.createOrUpdate(idLinePermutationUBOName, &permutationUBO, parameters.context); +#endif // MLN_RENDER_BACKEND_METAL + }; +}; + +class TestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { +public: + void initialize() override {} + + /** + * @brief Create Drawables for the Custom Drawable Layer + * + * @param proxyLayer + * @param shaders + * @param context + * @param state + * @param updateParameters + * @param renderTree + * @param changes + */ + void update(RenderLayer& proxyLayer, + gfx::ShaderRegistry& shaders, + gfx::Context& context, + [[maybe_unused]] const TransformState& state, + [[maybe_unused]] const std::shared_ptr& updateParameters, + [[maybe_unused]] const RenderTree& renderTree, + UniqueChangeRequestVec& changes) override { + // Set up a layer group + if (!layerGroup) { + if (auto layerGroup_ = context.createTileLayerGroup( + /*layerIndex*/ proxyLayer.getLayerIndex(), /*initialCapacity=*/2, proxyLayer.getID())) { + changes.emplace_back(std::make_unique(layerGroup_)); + layerGroup = std::move(layerGroup_); + } + } + + if (!layerGroup) return; + + // if we have build our drawable(s) already, either update or skip + if (layerGroup->getDrawableCount()) return; + + // create drawable(s) + const OverscaledTileID tileID{11, 327, 791}; + + auto createLineBuilder = [&](const std::string& name, + gfx::ShaderPtr shader) -> std::unique_ptr { + std::unique_ptr builder = context.createDrawableBuilder(name); + builder->setShader(std::static_pointer_cast(shader)); + builder->setSubLayerIndex(0); + builder->setEnableDepth(false); + builder->setColorMode(gfx::ColorMode::alphaBlended()); + builder->setCullFaceMode(gfx::CullFaceMode::disabled()); + builder->setEnableStencil(false); + builder->setRenderPass(RenderPass::Translucent); + + return builder; + }; + + gfx::ShaderGroupPtr lineShaderGroup = shaders.getShaderGroup("LineShader"); + + const std::unordered_set propertiesAsUniforms{ + stringIndexer().get("a_color"), + stringIndexer().get("a_blur"), + stringIndexer().get("a_opacity"), + stringIndexer().get("a_gapwidth"), + stringIndexer().get("a_offset"), + stringIndexer().get("a_width"), + }; + + auto shader = lineShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + auto builder = createLineBuilder("thick-lines", shader); + + auto* tileLayerGroup = static_cast(layerGroup.get()); + + // add polylines + const auto size{util::EXTENT}; + GeometryCoordinates geom{{0, 0}, {size, 0}, {0, size}, {size, size}, {size / 3, size / 3}}; + + gfx::PolylineGeneratorOptions options; + options.beginCap = style::LineCapType::Round; + options.endCap = style::LineCapType::Round; + options.joinType = style::LineJoinType::Round; + builder->addPolyline(geom, options); + + // create tweaker + auto tweaker = std::make_shared(); + + // finish + builder->flush(); + for (auto& drawable : builder->clearDrawables()) { + drawable->setTileID(tileID); + drawable->addTweaker(tweaker); + + tileLayerGroup->addDrawable(RenderPass::Translucent, tileID, std::move(drawable)); + } + } + + void deinitialize() override { + // layerGroup->reset(); + } + +private: + std::shared_ptr layerGroup; +}; + +TEST(CustomDrawableLayer, Basic) { + util::RunLoop loop; + + HeadlessFrontend frontend{1}; + Map map(frontend, + MapObserver::nullObserver(), + MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize()), + ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets")); + + // load style + map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); + map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.4426032}).withZoom(10.0)); + + // add fill layer + auto layer = std::make_unique("landcover", "mapbox"); + layer->setSourceLayer("landcover"); + layer->setFillColor(Color{1.0, 1.0, 0.0, 1.0}); + map.getStyle().addLayer(std::move(layer)); + + // add custom drawable layer + map.getStyle().addLayer( + std::make_unique("custom-drawable", std::make_unique())); + + // render and test + test::checkImage("test/fixtures/custom_drawable_layer/basic", frontend.render(map).image, 0.0006, 0.1); +} + +#endif // MLN_DRAWABLE_RENDERER diff --git a/test/fixtures/custom_drawable_layer/basic/expected.png b/test/fixtures/custom_drawable_layer/basic/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..11898f329ef72b4315a0e055cc5044d5b77c09ff GIT binary patch literal 9867 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 literal 0 HcmV?d00001