Custom Drawable Layer v1 + DrawableBuilder thick lines (#1699)
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <mbgl/gfx/drawable.hpp>
|
||||
#include <mbgl/gfx/types.hpp>
|
||||
#include <mbgl/util/string_indexer.hpp>
|
||||
#include <mbgl/tile/geometry_tile_data.hpp>
|
||||
#include <mbgl/style/types.hpp>
|
||||
#include <mbgl/gfx/polyline_generator.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
@@ -154,14 +157,6 @@ public:
|
||||
/// 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 @@ public:
|
||||
/// 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<Drawable::DrawSegment> 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<uint16_t>& indexes,
|
||||
std::size_t indexOffset,
|
||||
@@ -192,8 +198,8 @@ public:
|
||||
std::size_t indexLength,
|
||||
std::size_t baseIndex = 0);
|
||||
|
||||
/// Create a segment wrapper
|
||||
virtual std::unique_ptr<Drawable::DrawSegment> 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 @@ protected:
|
||||
gfx::Drawable::Textures textures;
|
||||
std::vector<DrawableTweakerPtr> tweakers;
|
||||
|
||||
struct Impl;
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
|
||||
103
include/mbgl/gfx/polyline_generator.hpp
Normal file
103
include/mbgl/gfx/polyline_generator.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/tile/geometry_tile_data.hpp>
|
||||
#include <mbgl/style/types.hpp>
|
||||
#include <mbgl/gfx/vertex_vector.hpp>
|
||||
#include <mbgl/gfx/index_vector.hpp>
|
||||
#include <mbgl/programs/segment.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
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<PolylineGeneratorDistances> lineDistances;
|
||||
};
|
||||
|
||||
template <class PolylineLayoutVertex, class PolylineSegment>
|
||||
class PolylineGenerator {
|
||||
public:
|
||||
using Vertices = gfx::VertexVector<PolylineLayoutVertex>;
|
||||
using Segments = std::vector<PolylineSegment>;
|
||||
using LayoutVertexFunc = std::function<PolylineLayoutVertex(
|
||||
Point<int16_t> p, Point<double> e, bool round, bool up, int8_t dir, int32_t linesofar /*= 0*/)>;
|
||||
using CreateSegmentFunc = std::function<PolylineSegment(std::size_t vertexOffset, std::size_t indexOffset)>;
|
||||
using GetSegmentFunc = std::function<mbgl::SegmentBase&(PolylineSegment& segment)>;
|
||||
using Indexes = gfx::IndexVector<gfx::Triangles>;
|
||||
|
||||
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<double>& normal,
|
||||
double endLeft,
|
||||
double endRight,
|
||||
bool round,
|
||||
std::size_t startVertex,
|
||||
std::vector<TriangleElement>& triangleStore,
|
||||
std::optional<PolylineGeneratorDistances> lineDistances);
|
||||
void addPieSliceVertex(const GeometryCoordinate& currentVertex,
|
||||
double distance,
|
||||
const Point<double>& extrude,
|
||||
bool lineTurnsLeft,
|
||||
std::size_t startVertex,
|
||||
std::vector<TriangleElement>& triangleStore,
|
||||
std::optional<PolylineGeneratorDistances> 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
|
||||
18
include/mbgl/layermanager/custom_drawable_layer_factory.hpp
Normal file
18
include/mbgl/layermanager/custom_drawable_layer_factory.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/layermanager/layer_factory.hpp>
|
||||
#include <mbgl/style/layer.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class CustomDrawableLayerFactory : public LayerFactory {
|
||||
protected:
|
||||
const style::LayerTypeInfo* getTypeInfo() const noexcept final;
|
||||
std::unique_ptr<style::Layer> createLayer(const std::string& id,
|
||||
const style::conversion::Convertible& value) noexcept final;
|
||||
std::unique_ptr<RenderLayer> createRenderLayer(Immutable<style::Layer::Impl>) noexcept final;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -71,20 +71,15 @@ public:
|
||||
|
||||
void enableOverdrawInspector(bool);
|
||||
|
||||
virtual void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) = 0;
|
||||
virtual void execute(LayerGroupBase&, const PaintParameters&) = 0;
|
||||
|
||||
void updateProperties(Immutable<style::LayerProperties>);
|
||||
|
||||
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<float, 2>& translation,
|
||||
style::TranslateAnchorType,
|
||||
bool nearClipped,
|
||||
@@ -92,6 +87,9 @@ protected:
|
||||
bool aligned = false);
|
||||
|
||||
protected:
|
||||
/// Determine whether this tweaker should apply to the given drawable
|
||||
bool checkTweakDrawable(const gfx::Drawable&) const;
|
||||
|
||||
std::string id;
|
||||
Immutable<style::LayerProperties> evaluatedProperties;
|
||||
|
||||
|
||||
52
include/mbgl/style/layers/custom_drawable_layer.hpp
Normal file
52
include/mbgl/style/layers/custom_drawable_layer.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/gfx/shader_registry.hpp>
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
#include <mbgl/renderer/update_parameters.hpp>
|
||||
#include <mbgl/renderer/render_tree.hpp>
|
||||
#include <mbgl/renderer/change_request.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
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<UpdateParameters>&,
|
||||
const RenderTree& renderTree,
|
||||
UniqueChangeRequestVec& changes) = 0;
|
||||
|
||||
virtual void deinitialize() = 0;
|
||||
};
|
||||
|
||||
class CustomDrawableLayer final : public Layer {
|
||||
public:
|
||||
CustomDrawableLayer(const std::string& id, std::unique_ptr<CustomDrawableLayerHost> host);
|
||||
|
||||
CustomDrawableLayer(const CustomDrawableLayer&) = delete;
|
||||
~CustomDrawableLayer() final;
|
||||
class Impl;
|
||||
const Impl& impl() const;
|
||||
Mutable<Impl> mutableImpl() const;
|
||||
|
||||
private:
|
||||
std::optional<conversion::Error> setPropertyInternal(const std::string& name,
|
||||
const conversion::Convertible& value) final;
|
||||
StyleProperty getProperty(const std::string&) const final;
|
||||
std::unique_ptr<Layer> cloneRef(const std::string& id) const final;
|
||||
Mutable<Layer::Impl> mutableBaseImpl() const final;
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
@@ -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": [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
#include <mbgl/layermanager/symbol_layer_factory.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
|
||||
#if MLN_DRAWABLE_RENDERER
|
||||
#include <mbgl/layermanager/custom_drawable_layer_factory.hpp>
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@@ -69,6 +73,12 @@ LayerManagerDefault::LayerManagerDefault() {
|
||||
addLayerType(std::make_unique<LocationIndicatorLayerFactory>());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if MLN_DRAWABLE_RENDERER
|
||||
#if !defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_ALL)
|
||||
addLayerType(std::make_unique<CustomDrawableLayerFactory>());
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void LayerManagerDefault::addLayerType(std::unique_ptr<LayerFactory> factory) {
|
||||
|
||||
@@ -435,7 +435,9 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
@"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",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mbgl/renderer/render_pass.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
#include <mbgl/util/string_indexer.hpp>
|
||||
#include <mbgl/gfx/gfx_types.hpp>
|
||||
|
||||
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<int32_t>(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<gfx::Texture2D>& 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<uint16_t>(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<uint16_t>& 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
|
||||
|
||||
126
src/mbgl/gfx/drawable_builder_impl.cpp
Normal file
126
src/mbgl/gfx/drawable_builder_impl.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <mbgl/gfx/drawable_builder_impl.hpp>
|
||||
|
||||
#include <mbgl/gfx/drawable_impl.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
#include <mbgl/style/types.hpp>
|
||||
#include <mbgl/gfx/polyline_generator.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
namespace gfx {
|
||||
|
||||
DrawableBuilder::Impl::LineLayoutVertex DrawableBuilder::Impl::layoutVertex(
|
||||
Point<int16_t> p, Point<double> 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<int16_t>((p.x * 2) | (round ? 1 : 0)), static_cast<int16_t>((p.y * 2) | (up ? 1 : 0))}},
|
||||
{{// add 128 to store a byte in an unsigned byte
|
||||
static_cast<uint8_t>(::round(extrudeScale * e.x) + 128),
|
||||
static_cast<uint8_t>(::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<uint8_t>(((dir == 0 ? 0 : (dir < 0 ? -1 : 1)) + 1) | ((linesofar & 0x3F) << 2)),
|
||||
static_cast<uint8_t>(linesofar >> 6)}}};
|
||||
}
|
||||
|
||||
void DrawableBuilder::Impl::addPolyline(gfx::DrawableBuilder& builder,
|
||||
const GeometryCoordinates& coordinates,
|
||||
const gfx::PolylineGeneratorOptions& options) {
|
||||
using namespace std::placeholders;
|
||||
gfx::PolylineGenerator<DrawableBuilder::Impl::LineLayoutVertex, std::unique_ptr<Drawable::DrawSegment>> 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<Drawable::DrawSegment> {
|
||||
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<LineLayoutVertex>;
|
||||
std::shared_ptr<VertexVector> verts = std::make_shared<VertexVector>(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<gfx::IndexVectorBase>(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
|
||||
@@ -5,20 +5,40 @@
|
||||
#include <mbgl/gfx/vertex_attribute.hpp>
|
||||
#include <mbgl/gfx/vertex_vector.hpp>
|
||||
#include <mbgl/programs/segment.hpp>
|
||||
#include <mbgl/gfx/drawable_builder.hpp>
|
||||
#include <mbgl/gfx/polyline_generator.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mbgl {
|
||||
namespace gfx {
|
||||
|
||||
struct DrawableBuilder::Impl {
|
||||
class DrawableBuilder::Impl {
|
||||
public:
|
||||
using VT = gfx::detail::VertexType<gfx::AttributeType<std::int16_t, 2>>;
|
||||
enum class Mode {
|
||||
Primitives, ///< building primitive drawables. Not implemented
|
||||
Polylines, ///< building drawables for thick polylines
|
||||
Custom ///< building custom drawables.
|
||||
};
|
||||
struct LineLayoutVertex {
|
||||
std::array<int16_t, 2> a1;
|
||||
std::array<uint8_t, 4> a2;
|
||||
};
|
||||
|
||||
public:
|
||||
gfx::VertexVector<VT> vertices;
|
||||
|
||||
std::vector<uint8_t> rawVertices;
|
||||
std::size_t rawVerticesCount = 0;
|
||||
|
||||
gfx::VertexVector<LineLayoutVertex> polylineVertices;
|
||||
gfx::IndexVector<gfx::Triangles> polylineIndexes;
|
||||
|
||||
std::vector<uint16_t> buildIndexes;
|
||||
std::shared_ptr<gfx::IndexVectorBase> sharedIndexes;
|
||||
std::vector<std::unique_ptr<Drawable::DrawSegment>> 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<int16_t> p, Point<double> e, bool round, bool up, int8_t dir, int32_t linesofar = 0);
|
||||
|
||||
Mode mode{Mode::Custom};
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
632
src/mbgl/gfx/polyline_generator.cpp
Normal file
632
src/mbgl/gfx/polyline_generator.cpp
Normal file
@@ -0,0 +1,632 @@
|
||||
#include <mbgl/gfx/polyline_generator.hpp>
|
||||
|
||||
#include <mbgl/style/types.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/programs/line_program.hpp>
|
||||
|
||||
#if MLN_DRAWABLE_RENDERER
|
||||
#include <mbgl/gfx/drawable_builder.hpp>
|
||||
#include <mbgl/gfx/drawable_builder_impl.hpp>
|
||||
#include <mbgl/gfx/drawable_impl.hpp>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
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<float>(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<float>((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 <class PLV, class PS>
|
||||
struct PolylineGenerator<PLV, PS>::TriangleElement {
|
||||
TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_)
|
||||
: a(a_),
|
||||
b(b_),
|
||||
c(c_) {}
|
||||
uint16_t a, b, c;
|
||||
};
|
||||
|
||||
template <class PLV, class PS>
|
||||
PolylineGenerator<PLV, PS>::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 <class PLV, class PS>
|
||||
void PolylineGenerator<PLV, PS>::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<GeometryCoordinate> currentCoordinate;
|
||||
std::optional<GeometryCoordinate> prevCoordinate;
|
||||
std::optional<GeometryCoordinate> nextCoordinate;
|
||||
std::optional<Point<double>> prevNormal;
|
||||
std::optional<Point<double>> 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<double>(firstCoordinate - *currentCoordinate)));
|
||||
}
|
||||
|
||||
const std::size_t startVertex = vertices.elements();
|
||||
std::vector<TriangleElement> 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<double>(*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<double> 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<double>::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<double>(*currentCoordinate, *prevCoordinate);
|
||||
if (prevSegmentLength > 2.0 * sharpCornerOffset) {
|
||||
GeometryCoordinate newPrevVertex = *currentCoordinate -
|
||||
convertPoint<int16_t>(util::round(
|
||||
convertPoint<double>(*currentCoordinate - *prevCoordinate) *
|
||||
(sharpCornerOffset / prevSegmentLength)));
|
||||
distance += util::dist<double>(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<double>(*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<float>(-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<unsigned>(::round((approxAngle * 180 / M_PI) / DEG_PER_TRIANGLE));
|
||||
|
||||
for (unsigned m = 1; m < n; ++m) {
|
||||
double t = static_cast<double>(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<double>(*currentCoordinate, *nextCoordinate);
|
||||
if (nextSegmentLength > 2 * sharpCornerOffset) {
|
||||
GeometryCoordinate newCurrentVertex = *currentCoordinate +
|
||||
convertPoint<int16_t>(util::round(
|
||||
convertPoint<double>(*nextCoordinate - *currentCoordinate) *
|
||||
(sharpCornerOffset / nextSegmentLength)));
|
||||
distance += util::dist<double>(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<uint16_t>::max()) {
|
||||
segments.emplace_back(createSegment(startVertex, indexes.elements()));
|
||||
}
|
||||
|
||||
auto& segment = getSegment(segments.back());
|
||||
assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
|
||||
const uint16_t index = static_cast<uint16_t>(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 <class PLV, class PS>
|
||||
void PolylineGenerator<PLV, PS>::addCurrentVertex(const GeometryCoordinate& currentCoordinate,
|
||||
double& distance,
|
||||
const Point<double>& normal,
|
||||
double endLeft,
|
||||
double endRight,
|
||||
bool round,
|
||||
std::size_t startVertex,
|
||||
std::vector<TriangleElement>& triangleStore,
|
||||
std::optional<PolylineGeneratorDistances> lineDistances) {
|
||||
Point<double> 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<int8_t>(endLeft),
|
||||
static_cast<int32_t>(scaledDistance * LINE_DISTANCE_SCALE)));
|
||||
e3 = vertices.elements() - 1 - startVertex;
|
||||
if (e1 >= 0 && e2 >= 0) {
|
||||
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(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<int8_t>(-endRight),
|
||||
static_cast<int32_t>(scaledDistance * LINE_DISTANCE_SCALE)));
|
||||
e3 = vertices.elements() - 1 - startVertex;
|
||||
if (e1 >= 0 && e2 >= 0) {
|
||||
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(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 <class PLV, class PS>
|
||||
void PolylineGenerator<PLV, PS>::addPieSliceVertex(const GeometryCoordinate& currentVertex,
|
||||
double distance,
|
||||
const Point<double>& extrude,
|
||||
bool lineTurnsLeft,
|
||||
std::size_t startVertex,
|
||||
std::vector<TriangleElement>& triangleStore,
|
||||
std::optional<PolylineGeneratorDistances> lineDistances) {
|
||||
Point<double> flippedExtrude = extrude * (lineTurnsLeft ? -1.0 : 1.0);
|
||||
if (lineDistances) {
|
||||
distance = lineDistances->scaleToMaxLineDistance(distance);
|
||||
}
|
||||
|
||||
vertices.emplace_back(layoutVertex(
|
||||
currentVertex, flippedExtrude, false, lineTurnsLeft, 0, static_cast<int32_t>(distance * LINE_DISTANCE_SCALE)));
|
||||
e3 = vertices.elements() - 1 - startVertex;
|
||||
if (e1 >= 0 && e2 >= 0) {
|
||||
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(e3));
|
||||
}
|
||||
|
||||
if (lineTurnsLeft) {
|
||||
e2 = e3;
|
||||
} else {
|
||||
e1 = e3;
|
||||
}
|
||||
}
|
||||
|
||||
#if MLN_DRAWABLE_RENDERER
|
||||
template class PolylineGenerator<gfx::DrawableBuilder::Impl::LineLayoutVertex,
|
||||
std::unique_ptr<gfx::Drawable::DrawSegment>>;
|
||||
#endif
|
||||
|
||||
template class PolylineGenerator<LineLayoutVertex, Segment<LineAttributes>>;
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mbgl
|
||||
@@ -15,8 +15,9 @@ gfx::UniqueDrawable DrawableGLBuilder::createDrawable() const {
|
||||
return std::make_unique<DrawableGL>(drawableName.empty() ? name : drawableName);
|
||||
};
|
||||
|
||||
std::unique_ptr<gfx::Drawable::DrawSegment> DrawableGLBuilder::createSegment(gfx::DrawMode mode, SegmentBase&& seg) {
|
||||
return std::make_unique<DrawableGL::DrawSegmentGL>(mode, std::move(seg), VertexArray{{nullptr, false}});
|
||||
std::unique_ptr<gfx::Drawable::DrawSegment> DrawableGLBuilder::createSegment(gfx::DrawMode drawMode,
|
||||
SegmentBase&& seg) {
|
||||
return std::make_unique<DrawableGL::DrawSegmentGL>(drawMode, std::move(seg), VertexArray{{nullptr, false}});
|
||||
}
|
||||
|
||||
void DrawableGLBuilder::init() {
|
||||
|
||||
@@ -406,6 +406,7 @@ platform::GLenum Enum<gfx::TexturePixelType>::sizedFor<>(const gfx::TexturePixel
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
23
src/mbgl/layermanager/custom_drawable_layer_factory.cpp
Normal file
23
src/mbgl/layermanager/custom_drawable_layer_factory.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
||||
#include <mbgl/layermanager/custom_drawable_layer_factory.hpp>
|
||||
#include <mbgl/style/layers/custom_drawable_layer_impl.hpp>
|
||||
#include <mbgl/renderer/layers/render_custom_drawable_layer.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
const style::LayerTypeInfo* CustomDrawableLayerFactory::getTypeInfo() const noexcept {
|
||||
return style::CustomDrawableLayer::Impl::staticTypeInfo();
|
||||
}
|
||||
|
||||
std::unique_ptr<style::Layer> CustomDrawableLayerFactory::createLayer(const std::string&,
|
||||
const style::conversion::Convertible&) noexcept {
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderLayer> CustomDrawableLayerFactory::createRenderLayer(
|
||||
Immutable<style::Layer::Impl> impl) noexcept {
|
||||
return std::make_unique<RenderCustomDrawableLayer>(staticImmutableCast<style::CustomDrawableLayer::Impl>(impl));
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <mbgl/style/layers/line_layer_impl.hpp>
|
||||
#include <mbgl/util/math.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/gfx/polyline_generator.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
@@ -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<float>(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<float>(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<LineLayoutVertex, Segment<LineAttributes>> generator(
|
||||
vertices,
|
||||
LineProgram::layoutVertex,
|
||||
segments,
|
||||
[](std::size_t vertexOffset, std::size_t indexOffset) -> Segment<LineAttributes> {
|
||||
return Segment<LineAttributes>(vertexOffset, indexOffset);
|
||||
},
|
||||
[](auto& seg) -> Segment<LineAttributes>& { 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<Distances> 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<double>(coordinates[i], coordinates[i + 1]);
|
||||
}
|
||||
|
||||
lineDistances = Distances{
|
||||
options.lineDistances = gfx::PolylineGeneratorDistances{
|
||||
*numericValue<double>(clip_start_it->second), *numericValue<double>(clip_end_it->second), total_length};
|
||||
}
|
||||
|
||||
const LineJoinType joinType = layout.evaluate<LineJoin>(zoom, feature, canonical);
|
||||
options.joinType = layout.evaluate<LineJoin>(zoom, feature, canonical);
|
||||
options.miterLimit = options.joinType == LineJoinType::Bevel ? 1.05f
|
||||
: static_cast<float>(layout.get<LineMiterLimit>());
|
||||
options.beginCap = layout.get<LineCap>();
|
||||
options.endCap = options.type == FeatureType::Polygon ? LineCapType::Butt : LineCapType(layout.get<LineCap>());
|
||||
options.roundLimit = layout.get<LineRoundLimit>();
|
||||
options.overscaling = overscaling;
|
||||
|
||||
const float miterLimit = joinType == LineJoinType::Bevel ? 1.05f : static_cast<float>(layout.get<LineMiterLimit>());
|
||||
|
||||
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<LineCap>();
|
||||
const LineCapType endCap = type == FeatureType::Polygon ? LineCapType::Butt : LineCapType(layout.get<LineCap>());
|
||||
|
||||
double distance = 0.0;
|
||||
bool startOfLine = true;
|
||||
std::optional<GeometryCoordinate> currentCoordinate;
|
||||
std::optional<GeometryCoordinate> prevCoordinate;
|
||||
std::optional<GeometryCoordinate> nextCoordinate;
|
||||
std::optional<Point<double>> prevNormal;
|
||||
std::optional<Point<double>> nextNormal;
|
||||
|
||||
// the last three vertices added
|
||||
e1 = e2 = e3 = -1;
|
||||
|
||||
if (type == FeatureType::Polygon) {
|
||||
currentCoordinate = coordinates[len - 2];
|
||||
nextNormal = util::perp(util::unit(convertPoint<double>(firstCoordinate - *currentCoordinate)));
|
||||
}
|
||||
|
||||
const std::size_t startVertex = vertices.elements();
|
||||
std::vector<TriangleElement> 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<double>(*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<double> 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<double>::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<double>(*currentCoordinate, *prevCoordinate);
|
||||
if (prevSegmentLength > 2.0 * sharpCornerOffset) {
|
||||
GeometryCoordinate newPrevVertex = *currentCoordinate -
|
||||
convertPoint<int16_t>(util::round(
|
||||
convertPoint<double>(*currentCoordinate - *prevCoordinate) *
|
||||
(sharpCornerOffset / prevSegmentLength)));
|
||||
distance += util::dist<double>(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<LineRoundLimit>()) {
|
||||
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<double>(*currentCoordinate, *prevCoordinate);
|
||||
|
||||
if (middleVertex && currentJoin == LineJoinType::Miter) {
|
||||
joinNormal = joinNormal * miterLength;
|
||||
addCurrentVertex(
|
||||
*currentCoordinate, distance, joinNormal, 0, 0, false, startVertex, triangleStore, lineDistances);
|
||||
|
||||
} 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<float>(-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<unsigned>(::round((approxAngle * 180 / M_PI) / DEG_PER_TRIANGLE));
|
||||
|
||||
for (unsigned m = 1; m < n; ++m) {
|
||||
double t = static_cast<double>(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<double>(*currentCoordinate, *nextCoordinate);
|
||||
if (nextSegmentLength > 2 * sharpCornerOffset) {
|
||||
GeometryCoordinate newCurrentVertex = *currentCoordinate +
|
||||
convertPoint<int16_t>(util::round(
|
||||
convertPoint<double>(*nextCoordinate - *currentCoordinate) *
|
||||
(sharpCornerOffset / nextSegmentLength)));
|
||||
distance += util::dist<double>(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<uint16_t>::max()) {
|
||||
segments.emplace_back(startVertex, triangles.elements());
|
||||
}
|
||||
|
||||
auto& segment = segments.back();
|
||||
assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
|
||||
const auto index = static_cast<uint16_t>(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<double>& normal,
|
||||
double endLeft,
|
||||
double endRight,
|
||||
bool round,
|
||||
std::size_t startVertex,
|
||||
std::vector<TriangleElement>& triangleStore,
|
||||
std::optional<Distances> lineDistances) {
|
||||
Point<double> 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<int8_t>(endLeft),
|
||||
static_cast<int32_t>(scaledDistance * LINE_DISTANCE_SCALE)));
|
||||
e3 = vertices.elements() - 1 - startVertex;
|
||||
if (e1 >= 0 && e2 >= 0) {
|
||||
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(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<int8_t>(-endRight),
|
||||
static_cast<int32_t>(scaledDistance * LINE_DISTANCE_SCALE)));
|
||||
e3 = vertices.elements() - 1 - startVertex;
|
||||
if (e1 >= 0 && e2 >= 0) {
|
||||
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(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<double>& extrude,
|
||||
bool lineTurnsLeft,
|
||||
std::size_t startVertex,
|
||||
std::vector<TriangleElement>& triangleStore,
|
||||
std::optional<Distances> lineDistances) {
|
||||
Point<double> 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<int32_t>(distance * LINE_DISTANCE_SCALE)));
|
||||
e3 = vertices.elements() - 1 - startVertex;
|
||||
if (e1 >= 0 && e2 >= 0) {
|
||||
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(e3));
|
||||
}
|
||||
|
||||
if (lineTurnsLeft) {
|
||||
e2 = e3;
|
||||
} else {
|
||||
e1 = e3;
|
||||
}
|
||||
generator.generate(coordinates, options);
|
||||
}
|
||||
|
||||
void LineBucket::upload([[maybe_unused]] gfx::UploadPass& uploadPass) {
|
||||
|
||||
@@ -62,37 +62,6 @@ public:
|
||||
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<double>& normal,
|
||||
double endLeft,
|
||||
double endRight,
|
||||
bool round,
|
||||
std::size_t startVertex,
|
||||
std::vector<LineBucket::TriangleElement>& triangleStore,
|
||||
std::optional<Distances> distances);
|
||||
|
||||
void addPieSliceVertex(const GeometryCoordinate& currentVertex,
|
||||
double distance,
|
||||
const Point<double>& extrude,
|
||||
bool lineTurnsLeft,
|
||||
std::size_t startVertex,
|
||||
std::vector<TriangleElement>& triangleStore,
|
||||
std::optional<Distances> distances);
|
||||
|
||||
std::ptrdiff_t e1;
|
||||
std::ptrdiff_t e2;
|
||||
std::ptrdiff_t e3;
|
||||
|
||||
const float zoom;
|
||||
const uint32_t overscaling;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<float, 2>& 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<style::LayerProperties> newProps) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
public:
|
||||
~BackgroundLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override;
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
|
||||
protected:
|
||||
gfx::UniformBufferPtr backgroundLayerBuffer;
|
||||
|
||||
@@ -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<const CircleLayerProperties&>(*evaluatedProperties).evaluated;
|
||||
|
||||
@@ -123,8 +121,7 @@ void CircleLayerTweaker::execute(LayerGroupBase& layerGroup,
|
||||
const auto anchor = evaluated.get<CircleTranslateAnchor>();
|
||||
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<float>(zoom));
|
||||
const auto extrudeScale = pitchWithMap ? std::array<float, 2>{pixelsToTileUnits, pixelsToTileUnits}
|
||||
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
: 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;
|
||||
|
||||
@@ -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<float>(parameters.state.getZoom()));
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
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;
|
||||
|
||||
@@ -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<const FillExtrusionLayerProperties&>(*evaluatedProperties);
|
||||
const auto& evaluated = props.evaluated;
|
||||
@@ -124,8 +122,7 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup,
|
||||
const auto anchor = evaluated.get<FillExtrusionTranslateAnchor>();
|
||||
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);
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
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;
|
||||
|
||||
@@ -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<const FillLayerProperties&>(*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);
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
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;
|
||||
|
||||
@@ -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<const HeatmapLayerProperties&>(*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<float>(matrix),
|
||||
/* .extrude_scale = */ tileID.pixelsToTileUnits(1.0f, static_cast<float>(zoom)),
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
public:
|
||||
~HeatmapLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override;
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
|
||||
protected:
|
||||
gfx::UniformBufferPtr evaluatedPropsUniformBuffer;
|
||||
|
||||
@@ -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<const HeatmapLayerProperties&>(*evaluatedProperties).evaluated;
|
||||
|
||||
if (layerGroup.empty()) {
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
public:
|
||||
~HeatmapTextureLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override;
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
@@ -32,9 +32,7 @@ std::array<float, 2> getLight(const PaintParameters& parameters,
|
||||
return {{evaluated.get<HillshadeExaggeration>(), 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<const HillshadeLayerProperties&>(*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<float>(matrix),
|
||||
/* .latrange = */ getLatRange(tileID),
|
||||
/* .light = */ getLight(parameters, evaluated),
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
public:
|
||||
~HillshadeLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override;
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
|
||||
protected:
|
||||
gfx::UniformBufferPtr evaluatedPropsUniformBuffer;
|
||||
|
||||
@@ -27,9 +27,7 @@ const std::array<float, 4>& 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<const HillshadeLayerProperties&>(*evaluatedProperties).evaluated;
|
||||
|
||||
if (layerGroup.empty()) {
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
public:
|
||||
~HillshadePrepareLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override;
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
@@ -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<const LineLayerProperties&>(*evaluatedProperties).evaluated;
|
||||
const auto& crossfade = static_cast<const LineLayerProperties&>(*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<LineType>(drawable.getType());
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
|
||||
~LineLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override;
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
|
||||
protected:
|
||||
gfx::UniformBufferPtr linePropertiesBuffer;
|
||||
|
||||
@@ -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<const RasterLayerProperties&>(*evaluatedProperties).evaluated;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
public:
|
||||
~RasterLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const RenderTree&, const PaintParameters&) override;
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
|
||||
protected:
|
||||
gfx::UniformBufferPtr evaluatedPropsUniformBuffer = nullptr;
|
||||
|
||||
92
src/mbgl/renderer/layers/render_custom_drawable_layer.cpp
Normal file
92
src/mbgl/renderer/layers/render_custom_drawable_layer.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include <mbgl/gfx/backend_scope.hpp>
|
||||
#include <mbgl/gfx/renderer_backend.hpp>
|
||||
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
||||
#include <mbgl/style/layers/custom_drawable_layer_impl.hpp>
|
||||
#include <mbgl/renderer/layers/render_custom_drawable_layer.hpp>
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
#include <mbgl/math/angles.hpp>
|
||||
#include <mbgl/renderer/bucket.hpp>
|
||||
#include <mbgl/renderer/paint_parameters.hpp>
|
||||
#include <mbgl/util/mat4.hpp>
|
||||
|
||||
#if MLN_DRAWABLE_RENDERER
|
||||
#include <mbgl/gfx/context.hpp>
|
||||
#include <mbgl/renderer/layer_group.hpp>
|
||||
#include <mbgl/gfx/drawable_builder.hpp>
|
||||
#endif
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
using namespace style;
|
||||
|
||||
namespace {
|
||||
|
||||
inline const CustomDrawableLayer::Impl& impl(const Immutable<style::Layer::Impl>& impl) {
|
||||
assert(impl->getTypeInfo() == CustomDrawableLayer::Impl::staticTypeInfo());
|
||||
return static_cast<const CustomDrawableLayer::Impl&>(*impl);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderCustomDrawableLayer::RenderCustomDrawableLayer(Immutable<style::CustomDrawableLayer::Impl> _impl)
|
||||
: RenderLayer(makeMutable<CustomDrawableLayerProperties>(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>& updateParameters,
|
||||
const RenderTree& renderTree,
|
||||
UniqueChangeRequestVec& changes) {
|
||||
std::unique_lock<std::mutex> 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
|
||||
39
src/mbgl/renderer/layers/render_custom_drawable_layer.hpp
Normal file
39
src/mbgl/renderer/layers/render_custom_drawable_layer.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/layers/custom_drawable_layer_impl.hpp>
|
||||
#include <mbgl/renderer/render_layer.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class RenderCustomDrawableLayer final : public RenderLayer {
|
||||
public:
|
||||
explicit RenderCustomDrawableLayer(Immutable<style::CustomDrawableLayer::Impl>);
|
||||
~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<UpdateParameters>&,
|
||||
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<style::CustomDrawableLayerHost> host;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -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<const SymbolLayerProperties&>(*evaluatedProperties).evaluated;
|
||||
@@ -148,8 +146,7 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup,
|
||||
: evaluated.get<style::IconTranslateAnchor>();
|
||||
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<float>(zoom);
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
57
src/mbgl/style/layers/custom_drawable_layer.cpp
Normal file
57
src/mbgl/style/layers/custom_drawable_layer.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
||||
#include <mbgl/style/layers/custom_drawable_layer_impl.hpp>
|
||||
#include <mbgl/renderer/layers/render_custom_drawable_layer.hpp>
|
||||
#include <mbgl/style/layer_observer.hpp>
|
||||
|
||||
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<CustomDrawableLayerHost> host)
|
||||
: Layer(makeMutable<Impl>(layerID, std::move(host))) {}
|
||||
|
||||
CustomDrawableLayer::~CustomDrawableLayer() = default;
|
||||
|
||||
const CustomDrawableLayer::Impl& CustomDrawableLayer::impl() const {
|
||||
return static_cast<const Impl&>(*baseImpl);
|
||||
}
|
||||
|
||||
Mutable<CustomDrawableLayer::Impl> CustomDrawableLayer::mutableImpl() const {
|
||||
return makeMutable<Impl>(impl());
|
||||
}
|
||||
|
||||
std::unique_ptr<Layer> CustomDrawableLayer::cloneRef(const std::string&) const {
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
using namespace conversion;
|
||||
|
||||
std::optional<Error> CustomDrawableLayer::setPropertyInternal(const std::string&, const Convertible&) {
|
||||
return Error{"layer doesn't support this property"};
|
||||
}
|
||||
|
||||
StyleProperty CustomDrawableLayer::getProperty(const std::string&) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Mutable<Layer::Impl> CustomDrawableLayer::mutableBaseImpl() const {
|
||||
return staticMutableCast<Layer::Impl>(mutableImpl());
|
||||
}
|
||||
|
||||
// static
|
||||
const LayerTypeInfo* CustomDrawableLayer::Impl::staticTypeInfo() noexcept {
|
||||
return &typeInfoCustomDrawable;
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
18
src/mbgl/style/layers/custom_drawable_layer_impl.cpp
Normal file
18
src/mbgl/style/layers/custom_drawable_layer_impl.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <mbgl/style/layers/custom_drawable_layer_impl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
CustomDrawableLayer::Impl::Impl(const std::string& id_, std::unique_ptr<CustomDrawableLayerHost> 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<rapidjson::StringBuffer>&) const {}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
34
src/mbgl/style/layers/custom_drawable_layer_impl.hpp
Normal file
34
src/mbgl/style/layers/custom_drawable_layer_impl.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
||||
#include <mbgl/style/layer_impl.hpp>
|
||||
#include <mbgl/style/layer_properties.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class TransformState;
|
||||
|
||||
namespace style {
|
||||
|
||||
class CustomDrawableLayer::Impl : public Layer::Impl {
|
||||
public:
|
||||
Impl(const std::string& id, std::unique_ptr<CustomDrawableLayerHost> host);
|
||||
|
||||
bool hasLayoutDifference(const Layer::Impl&) const override;
|
||||
void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override;
|
||||
|
||||
std::shared_ptr<CustomDrawableLayerHost> host;
|
||||
|
||||
DECLARE_LAYER_TYPE_INFO;
|
||||
};
|
||||
|
||||
class CustomDrawableLayerProperties final : public LayerProperties {
|
||||
public:
|
||||
explicit CustomDrawableLayerProperties(Immutable<CustomDrawableLayer::Impl> impl)
|
||||
: LayerProperties(std::move(impl)) {}
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
@@ -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
|
||||
|
||||
243
test/api/custom_drawable_layer.test.cpp
Normal file
243
test/api/custom_drawable_layer.test.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
|
||||
#include <mbgl/gfx/headless_frontend.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/map/map_options.hpp>
|
||||
#include <mbgl/storage/resource_options.hpp>
|
||||
#include <mbgl/style/layers/fill_layer.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
#include <mbgl/util/mat4.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
|
||||
#if MLN_DRAWABLE_RENDERER
|
||||
|
||||
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
||||
#include <mbgl/gfx/context.hpp>
|
||||
#include <mbgl/gfx/drawable.hpp>
|
||||
#include <mbgl/gfx/drawable_tweaker.hpp>
|
||||
#include <mbgl/gfx/shader_registry.hpp>
|
||||
#include <mbgl/gfx/color_mode.hpp>
|
||||
#include <mbgl/gfx/cull_face_mode.hpp>
|
||||
#include <mbgl/gfx/drawable_builder.hpp>
|
||||
|
||||
#include <mbgl/shaders/shader_program_base.hpp>
|
||||
#include <mbgl/util/string_indexer.hpp>
|
||||
#include <mbgl/renderer/layer_tweaker.hpp>
|
||||
#include <mbgl/renderer/layer_group.hpp>
|
||||
#include <mbgl/renderer/render_tile.hpp>
|
||||
#include <mbgl/shaders/line_layer_ubo.hpp>
|
||||
#include <mbgl/util/string_indexer.hpp>
|
||||
#include <mbgl/util/convert.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
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<float>(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>& 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<AddLayerGroupRequest>(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<gfx::DrawableBuilder> {
|
||||
std::unique_ptr<gfx::DrawableBuilder> builder = context.createDrawableBuilder(name);
|
||||
builder->setShader(std::static_pointer_cast<gfx::ShaderProgramBase>(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<StringIdentity> 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<TileLayerGroup*>(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<TestDrawableTweaker>();
|
||||
|
||||
// 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<TileLayerGroup> 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<FillLayer>("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<CustomDrawableLayer>("custom-drawable", std::make_unique<TestDrawableLayer>()));
|
||||
|
||||
// render and test
|
||||
test::checkImage("test/fixtures/custom_drawable_layer/basic", frontend.render(map).image, 0.0006, 0.1);
|
||||
}
|
||||
|
||||
#endif // MLN_DRAWABLE_RENDERER
|
||||
BIN
test/fixtures/custom_drawable_layer/basic/expected.png
vendored
Normal file
BIN
test/fixtures/custom_drawable_layer/basic/expected.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
Reference in New Issue
Block a user