Add wide vector shader option to Custom Drawable Layer (#2183)

This commit is contained in:
Stefan Karschti
2024-04-15 20:37:26 +03:00
committed by GitHub
parent 2022a38945
commit c6bcb76818
56 changed files with 1545 additions and 162 deletions

View File

@@ -1150,6 +1150,7 @@ if(MLN_WITH_OPENGL)
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/raster_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_defines.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/symbol_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/widevector_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl_builder.hpp

View File

@@ -88,6 +88,7 @@ MLN_GENERATED_OPENGL_SHADER_HEADERS = [
"include/mbgl/shaders/gl/drawable_symbol_icon.hpp",
"include/mbgl/shaders/gl/drawable_symbol_sdf.hpp",
"include/mbgl/shaders/gl/drawable_symbol_text_and_icon.hpp",
"include/mbgl/shaders/gl/drawable_wide_vector.hpp",
"include/mbgl/shaders/gl/fill_extrusion_pattern.hpp",
"include/mbgl/shaders/gl/fill_extrusion.hpp",
"include/mbgl/shaders/gl/fill_outline_pattern.hpp",
@@ -1000,6 +1001,7 @@ MLN_DRAWABLES_HEADERS = [
"include/mbgl/shaders/shader_defines.hpp",
"include/mbgl/shaders/shader_program_base.hpp",
"include/mbgl/shaders/symbol_layer_ubo.hpp",
"include/mbgl/shaders/widevector_ubo.hpp",
"include/mbgl/util/suppress_copies.hpp",
"include/mbgl/style/layers/custom_drawable_layer.hpp",
"include/mbgl/layermanager/custom_drawable_layer_factory.hpp",
@@ -1123,4 +1125,5 @@ MLN_DRAWABLES_MTL_HEADERS = [
"include/mbgl/shaders/mtl/symbol_sdf.hpp",
"include/mbgl/shaders/mtl/symbol_text_and_icon.hpp",
"include/mbgl/style/layers/mtl/custom_layer_render_parameters.hpp",
"include/mbgl/shaders/mtl/widevector.hpp",
]

View File

@@ -189,6 +189,12 @@ public:
/// Set vertex attribute array
void setVertexAttributes(gfx::VertexAttributeArrayPtr value) noexcept { vertexAttributes = std::move(value); }
/// Get the instance attributes
const gfx::VertexAttributeArrayPtr& getInstanceAttributes() const noexcept { return instanceAttributes; }
/// Set instance attribute array
void setInstanceAttributes(gfx::VertexAttributeArrayPtr value) noexcept { instanceAttributes = std::move(value); }
/// Provide raw data for vertices. Incompatible with adding primitives
virtual void setVertices(std::vector<uint8_t>&&, std::size_t, AttributeDataType) = 0;
@@ -232,6 +238,12 @@ public:
void setLayerTweaker(LayerTweakerPtr tweaker) { layerTweaker = std::move(tweaker); }
const LayerTweakerPtr& getLayerTweaker() const { return layerTweaker; }
/// Get origin point
const std::optional<mbgl::Point<double>>& getOrigin() const { return origin; }
/// Set origin point
void setOrigin(std::optional<Point<double>> p) { origin = std::move(p); }
protected:
bool enabled = true;
bool enableColor = true;
@@ -251,6 +263,7 @@ protected:
DepthMaskType depthType; // = DepthMaskType::ReadOnly;
UniqueDrawableData drawableData{};
gfx::VertexAttributeArrayPtr vertexAttributes;
gfx::VertexAttributeArrayPtr instanceAttributes;
struct Impl;
std::unique_ptr<Impl> impl;
@@ -260,6 +273,7 @@ protected:
LayerTweakerPtr layerTweaker;
std::size_t type = 0;
std::optional<mbgl::Point<double>> origin;
};
using DrawablePtr = std::shared_ptr<Drawable>;

View File

@@ -160,6 +160,13 @@ public:
/// A set of attribute values to be added for each vertex
void setVertexAttributes(VertexAttributeArrayPtr attrs) { vertexAttrs = std::move(attrs); }
/// Get the instance attributes that override default values in the shader program
VertexAttributeArrayPtr& getInstanceAttributes() { return instanceAttrs; }
const VertexAttributeArrayPtr& getInstanceAttributes() const { return instanceAttrs; }
/// A set of attribute values to be added for each instance
void setInstanceAttributes(VertexAttributeArrayPtr attrs) { instanceAttrs = std::move(attrs); }
/// Add some vertex elements, returns the index of the first one added
std::size_t addVertices(const std::vector<std::array<int16_t, 2>>& vertices,
std::size_t vertexOffset,
@@ -200,6 +207,12 @@ public:
/// Add a polyline. If the last point equals the first it will be closed, otherwise open
void addPolyline(const GeometryCoordinates& coordinates, const gfx::PolylineGeneratorOptions&);
/// Add a polyline in Tile coordinates, using wide vectors.
void addWideVectorPolylineLocal(const GeometryCoordinates& coordinates, const gfx::PolylineGeneratorOptions&);
/// Add a polyline in geographic coordinates, using wide vectors.
void addWideVectorPolylineGlobal(const LineString<double>& coordinates, const gfx::PolylineGeneratorOptions&);
/// return the curent vertex count
std::size_t curVertexCount() const;
@@ -232,6 +245,8 @@ protected:
gfx::Drawable::Textures textures;
std::vector<DrawableTweakerPtr> tweakers;
gfx::VertexAttributeArrayPtr vertexAttrs;
gfx::VertexAttributeArrayPtr instanceAttrs;
std::optional<mbgl::Point<double>> origin;
class Impl;
std::unique_ptr<Impl> impl;

View File

@@ -319,7 +319,7 @@ public:
/// Do something with each attribute
template <typename Func /* void(VertexAttribute&) */>
void visitAttributes(Func f) {
void visitAttributes(Func f) const {
std::for_each(attrs.begin(), attrs.end(), [&](const auto& attr) {
if (attr) {
f(*attr);

View File

@@ -64,6 +64,8 @@ protected:
void bindAttributes(RenderPass&) const noexcept;
void unbindAttributes(RenderPass&) const noexcept {}
void bindInstanceAttributes(RenderPass&) const noexcept;
void bindUniformBuffers(RenderPass&) const noexcept;
void unbindUniformBuffers(RenderPass&) const noexcept {}

View File

@@ -4,6 +4,7 @@
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/util/immutable.hpp>
#include <mbgl/util/containers.hpp>
#include <mbgl/util/mat4.hpp>
#include <array>
#include <memory>
@@ -28,8 +29,6 @@ class PaintParameters;
class RenderTree;
class UnwrappedTileID;
using mat4 = std::array<double, 16>;
/**
Base class for layer tweakers, which manipulate layer group per frame
*/

View File

@@ -0,0 +1,16 @@
// Generated code, do not modify this file!
#pragma once
#include <mbgl/shaders/shader_source.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::OpenGL> {
static constexpr const char* name = "WideVectorShader";
static constexpr const char* vertex = R"()";
static constexpr const char* fragment = R"()";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -46,6 +46,8 @@ public:
const gfx::VertexAttributeArray& getVertexAttributes() const override { return vertexAttributes; }
const gfx::VertexAttributeArray& getInstanceAttributes() const override { return instanceAttributes; }
ProgramID getGLProgramID() const { return glProgram; }
protected:
@@ -56,6 +58,7 @@ protected:
UniformBlockArrayGL uniformBlocks;
VertexAttributeArrayGL vertexAttributes;
VertexAttributeArrayGL instanceAttributes;
SamplerLocationArray samplerLocations;
};

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::BackgroundShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 2> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -12,6 +12,7 @@ struct ShaderSource<BuiltIn::BackgroundPatternShader, gfx::Backend::Type::Metal>
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 2> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::CircleShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 8> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 4> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -21,6 +21,7 @@ struct ShaderSource<BuiltIn::ClippingMaskProgram, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 1> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::CollisionBoxShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 5> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 1> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::CollisionCircleShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 4> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 1> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::CustomSymbolIconShader, gfx::Backend::Type::Metal>
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 2> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::DebugShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 1> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::FillShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 3> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 3> uniforms;
static const std::array<TextureInfo, 0> textures;
@@ -101,6 +102,7 @@ struct ShaderSource<BuiltIn::FillOutlineShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 3> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 3> uniforms;
static const std::array<TextureInfo, 0> textures;
@@ -191,6 +193,7 @@ struct ShaderSource<BuiltIn::FillPatternShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 4> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 4> uniforms;
static const std::array<TextureInfo, 1> textures;
@@ -349,6 +352,7 @@ struct ShaderSource<BuiltIn::FillOutlinePatternShader, gfx::Backend::Type::Metal
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 4> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 4> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::FillExtrusionShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 5> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 3> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::FillExtrusionPatternShader, gfx::Backend::Type::Met
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 6> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 4> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::HeatmapShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 3> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 3> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::HeatmapTextureShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 1> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 1> uniforms;
static const std::array<TextureInfo, 2> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::HillshadeShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 2> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::HillshadePrepareShader, gfx::Backend::Type::Metal>
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 1> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::LineShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 8> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 4> uniforms;
static const std::array<TextureInfo, 0> textures;
@@ -179,6 +180,7 @@ struct ShaderSource<BuiltIn::LinePatternShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 9> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 5> uniforms;
static const std::array<TextureInfo, 1> textures;
@@ -430,6 +432,7 @@ struct ShaderSource<BuiltIn::LineSDFShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 9> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 4> uniforms;
static const std::array<TextureInfo, 1> textures;
@@ -658,6 +661,7 @@ struct ShaderSource<BuiltIn::LineBasicShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 2> uniforms;
static const std::array<TextureInfo, 0> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::LineGradientShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 7> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 4> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::RasterShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 2> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 1> uniforms;
static const std::array<TextureInfo, 2> textures;

View File

@@ -78,6 +78,9 @@ public:
shader->initAttribute(attrib);
}
}
for (const auto& attrib : ShaderClass::instanceAttributes) {
shader->initInstanceAttribute(attrib);
}
for (const auto& uniform : ShaderClass::uniforms) {
shader->initUniformBlock(uniform);
}

View File

@@ -14,13 +14,21 @@
namespace mbgl {
namespace shaders {
struct AttributeInfo {
AttributeInfo(std::size_t index, gfx::AttributeDataType dataType, std::size_t id);
constexpr AttributeInfo(std::size_t index_, gfx::AttributeDataType dataType_, std::size_t id_)
: index(index_),
dataType(dataType_),
id(id_) {}
std::size_t index;
gfx::AttributeDataType dataType;
std::size_t id;
};
struct UniformBlockInfo {
UniformBlockInfo(std::size_t index, bool vertex, bool fragment, std::size_t size, std::size_t id);
constexpr UniformBlockInfo(std::size_t index_, bool vertex_, bool fragment_, std::size_t size_, std::size_t id_)
: index(index_),
vertex(vertex_),
fragment(fragment_),
size(size_),
id(id_) {}
std::size_t index;
bool vertex;
bool fragment;
@@ -28,7 +36,9 @@ struct UniformBlockInfo {
std::size_t id;
};
struct TextureInfo {
TextureInfo(std::size_t index, std::size_t id);
constexpr TextureInfo(std::size_t index_, std::size_t id_)
: index(index_),
id(id_) {}
std::size_t index;
std::size_t id;
};
@@ -58,10 +68,13 @@ public:
const gfx::VertexAttributeArray& getVertexAttributes() const override { return vertexAttributes; }
const gfx::VertexAttributeArray& getInstanceAttributes() const override { return instanceAttributes; }
const gfx::UniformBlockArray& getUniformBlocks() const override { return uniformBlocks; }
gfx::UniformBlockArray& mutableUniformBlocks() override { return uniformBlocks; }
void initAttribute(const shaders::AttributeInfo&);
void initInstanceAttribute(const shaders::AttributeInfo&);
void initUniformBlock(const shaders::UniformBlockInfo&);
void initTexture(const shaders::TextureInfo&);
@@ -72,6 +85,7 @@ protected:
MTLFunctionPtr fragmentFunction;
UniformBlockArray uniformBlocks;
VertexAttributeArray vertexAttributes;
VertexAttributeArray instanceAttributes;
std::array<std::optional<size_t>, shaders::maxTextureCountPerShader> textureBindings;
};

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::SymbolIconShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 6> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 5> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::SymbolSDFIconShader, gfx::Backend::Type::Metal> {
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 10> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 5> uniforms;
static const std::array<TextureInfo, 1> textures;

View File

@@ -15,6 +15,7 @@ struct ShaderSource<BuiltIn::SymbolTextAndIconShader, gfx::Backend::Type::Metal>
static constexpr auto fragmentMainFunction = "fragmentMain";
static const std::array<AttributeInfo, 9> attributes;
static constexpr std::array<AttributeInfo, 0> instanceAttributes{};
static const std::array<UniformBlockInfo, 5> uniforms;
static const std::array<TextureInfo, 2> textures;

View File

@@ -0,0 +1,587 @@
#pragma once
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/mtl/common.hpp>
#include <mbgl/shaders/mtl/shader_program.hpp>
#include <mbgl/shaders/widevector_ubo.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::Metal> {
static constexpr auto name = "WideVectorShader";
static constexpr auto vertexMainFunction = "vertexTri_wideVecPerf";
static constexpr auto fragmentMainFunction = "fragmentTri_wideVecPerf";
static constexpr std::array<AttributeInfo, 3> attributes{
AttributeInfo{0, gfx::AttributeDataType::Float3, idWideVectorScreenPos},
AttributeInfo{1, gfx::AttributeDataType::Float4, idWideVectorColor},
AttributeInfo{2, gfx::AttributeDataType::Int, idWideVectorIndex}};
static constexpr std::array<UniformBlockInfo, 2> uniforms{
UniformBlockInfo{3, true, false, sizeof(WideVectorUniformsUBO), idWideVectorUniformsUBO},
UniformBlockInfo{4, true, false, sizeof(WideVectorUniformWideVecUBO), idWideVectorUniformWideVecUBO},
};
static constexpr std::array<AttributeInfo, 4> instanceAttributes{
AttributeInfo{5, gfx::AttributeDataType::Float3, idWideVectorInstanceCenter},
AttributeInfo{5, gfx::AttributeDataType::Float4, idWideVectorInstanceColor},
AttributeInfo{5, gfx::AttributeDataType::Int, idWideVectorInstancePrevious},
AttributeInfo{5, gfx::AttributeDataType::Int, idWideVectorInstanceNext}};
static constexpr std::array<TextureInfo, 0> textures{};
static constexpr auto source = R"(
#include <metal_stdlib>
namespace WhirlyKitShader
{
/** Expressions are used to change values like width and opacity over zoom levels. **/
#define WKSExpStops 8
// Line Joins
// These are assumed to match WideVectorLineJoinType
typedef enum {
WKSVertexLineJoinMiter = 0,
WKSVertexLineJoinBevel = 1,
WKSVertexLineJoinRound = 2,
WKSVertexLineJoinMiterClip = 3,
WKSVertexLineJoinMiterSimple = 4,
WKSVertexLineJoinNone = 5,
} WKSVertexLineJoinType;
// Line Caps
// These are assumed to match WideVectorLineCapType
typedef enum {
WKSVertexLineCapRound = 0,
WKSVertexLineCapButt = 1,
WKSVertexLineCapSquare = 2,
} WKSVertexLineCapType;
// Uniforms for the basic case.
struct Uniforms
{
simd::float4x4 mvpMatrix;
simd::float4x4 mvpMatrixDiff;
simd::float4x4 mvMatrix;
simd::float4x4 mvMatrixDiff;
simd::float4x4 pMatrix;
simd::float4x4 pMatrixDiff;
simd::float2 frameSize;
};
// Instructions to the wide vector shaders, usually per-drawable
struct UniformWideVec {
simd::float4 color; // Color
float w2; // Width / 2.0 in screen space
float offset; // Offset from center in screen space
float edge; // Edge falloff control
float texRepeat; // Texture scaling specific to wide vectors
simd::float2 texOffset; // Texture offset.
float miterLimit; // Miter join limit, multiples of width
WKSVertexLineJoinType join; // Line joins
WKSVertexLineCapType cap; // Line endcaps
bool hasExp; // Look for a UniformWideVecExp structure for color, opacity, and width
float interClipLimit; // Allow clipping of out-of-bounds intersection points
// Value is the multiple of distance-squared that is allowed.
};
// Instance info for the wide vector (new) vertex shader
typedef struct
{
// Center of the point on the line
simd::float3 center;
// Color
simd::float4 color;
// Used to track loops and such
int prev,next; // set to -1 for non-loops
} VertexTriWideVecInstance;
}
// These have syntax that only the shader language can grok
// We put them here so we can include them in other Metal libraries
#ifdef __METAL_VERSION__
/**
Wide Vector Shaders
These work to build/render objects in 2D space, but based
on 3D locations.
*/
// Vertex definition for wide vector (new version)
struct VertexTriWideVecB
{
// x, y offset around the center
float3 screenPos [[attribute(0)]];
float4 color [[attribute(1)]];
int index [[attribute(2)]];
};
// Wide vector vertex passed to fragment shader (new version)
struct ProjVertexTriWideVecPerf {
float4 position [[invariant]] [[position]]; // transformed to NDC
float2 screenPos; // un-transformed vertex position
float2 centerPos; // un-transformed circle center
float2 midDir; // Turn direction
float4 color;
float2 texCoord;
float w2;
float edge;
uint2 maskIDs;
bool roundJoin;
//uint whichVert; // helpful for debugging
};
#else
#endif
using namespace metal;
using namespace WhirlyKitShader;
struct IntersectInfo {
bool valid;
float2 interPt;
float c;
float ta,tb;
};
// wedge product (2D cross product)
// if A ^ B > 0, A is to the right of B
float wedge(float2 a, float2 b) {
return a.x * b.y - a.y * b.x;
}
// Intersect two lines
IntersectInfo intersectLines(float2 a0, float2 a1, float2 b0, float2 b1)
{
const float2 dA = a0 - a1;
const float2 dB = b0 - b1;
// Solve the system of equations formed by equating the lines to get their common point.
const float denom = wedge(dA, dB);
if (denom == 0.0) {
// If the denominator comes out zero, the lines are parallel and do not intersect
return { .valid = false };
}
const float tA = wedge(a0, a1);
const float tB = wedge(b0, b1);
const float2 inter = float2((tA * dB.x - dA.x * tB), (tA * dB.y - dA.y * tB)) / denom;
return {
.valid = true,
.interPt = inter,
.ta = 0.0,
.tb = 0.0,
};
}
// Intersect two offset lines
IntersectInfo intersectWideLines(float2 p0,float2 p1,float2 p2,
float2 n0,float2 n1)
{
return intersectLines(p0 + n0, p1 + n0, p1 + n1, p2 + n1);
}
// Used to track what info we have about a center point
struct CenterInfo {
/// Screen coordinates of the line segment endpoint
float2 screenPos;
/// Length of the segment (in screen coordinates)
float len;
/// Normalized direction of the segment
float2 nDir;
/// Normalized plane normal, perpendicular to the segment
float2 norm;
};
float3 viewPos(constant simd::float4x4 &mat, float3 vec) {
const float4 p = mat * float4(vec,1.0);
return p.xyz; // / p.w; ?
}
float2 screenPos_MVP(constant Uniforms &u, float3 viewPos) {
const float4 p4 = float4(viewPos, 1.0);
// Use the MVP matrix
const float4 s = u.mvpMatrix * p4;
return s.xy/s.w;
}
constant constexpr float wideVecMinTurnThreshold = 1e-5;
constant constexpr float wideVecMaxTurnThreshold = 0.99999998476; // sin(89.99 deg)
constant constexpr int WideVecPolyStartGeom = 0;
constant constexpr int WideVecPolyBodyGeom = 1;
constant constexpr int WideVecPolyEndGeom = 2;
constant constexpr float4 discardPt(0,0,-1e6,NAN);
// Performance version of wide vector shader
vertex ProjVertexTriWideVecPerf vertexTri_wideVecPerf(
thread const VertexTriWideVecB vert [[ stage_in ]],
constant Uniforms &uniforms [[ buffer(3) ]],
constant UniformWideVec &wideVec [[ buffer(4) ]],
uint instanceID [[ instance_id ]],
constant VertexTriWideVecInstance *wideVecInsts [[ buffer(5) ]])
{
ProjVertexTriWideVecPerf outVert = {
.position = discardPt,
.roundJoin = false,
};
// Vertex index within the instance, 0-11
// Odd indexes are on the left, evens are on the right.
const int whichVert = (vert.index >> 16) & 0xffff;
// Polygon index within the segment. 0=Start cap, 1=body, 2=end cap
const int whichPoly = vert.index & 0xffff;
// Are we on the left edge, or the right?
const bool isLeft = (whichVert & 1);
// Are we on the starting end of the segment or the end?
const bool isEnd = (whichVert > 5);
// Track vertex for debugging
//outVert.whichVert = whichVert;
// Pull out the width and possibly calculate one
float w2 = wideVec.w2;
// w2 includes edge-blend, strokeWidth does not
const float strokeWidth = 2 * w2;
if (w2 > 0.0) {
w2 = w2 + wideVec.edge;
}
// Disable joins for narrow lines
auto joinType = (w2 >= 1) ? wideVec.join : WKSVertexLineJoinNone;
// Find the various instances representing center points.
// We need one behind and one ahead of us.
// previous segment
// | this segment
// | | next segment
// | | |
bool instValid[4] = { false, true, false, false };
VertexTriWideVecInstance inst[4] = { {}, wideVecInsts[instanceID], {}, {} };
if (inst[1].prev != -1 && joinType != WKSVertexLineJoinNone) {
inst[0] = wideVecInsts[inst[1].prev];
instValid[0] = true;
}
if (inst[1].next != -1) {
inst[2] = wideVecInsts[inst[1].next];
instValid[2] = true;
if (inst[2].next != -1 && joinType != WKSVertexLineJoinNone) {
inst[3] = wideVecInsts[inst[2].next];
instValid[3] = true;
}
} else {
// We need at least this and next
return outVert;
}
const auto capType = wideVec.cap;
const auto isStartCap = (whichPoly == WideVecPolyStartGeom && !instValid[0]);
const auto isEndCap = (whichPoly == WideVecPolyEndGeom && !instValid[3]);
// Butt is the default cap style, the line ends at the point.
if ((isStartCap || isEndCap) && capType == WhirlyKitShader::WKSVertexLineCapButt) {
return outVert;
}
// Figure out position on the screen for each center point.
// centers[1] represents the segment leading to the current point.
// centers[2] represents the segment leading from the current point.
// centers[X] = vector from centers[X-1] to centers[X]
CenterInfo centers[4];
for (unsigned int ii=0;ii<4;ii++) {
if (!instValid[ii]) {
continue;
}
centers[ii].screenPos = screenPos_MVP(uniforms, inst[ii].center);
}
const float2 screenScale(2.0/uniforms.frameSize.x,2.0/uniforms.frameSize.y); // ~(0.001,0.001)
// Calculate directions and normals. Done in isotropic coords to
// avoid skewing everything when later multiplying by `screenScale`.
for (unsigned int ii=1;ii<4;ii++) {
if (instValid[ii-1]) {
const float2 scaledDir = (centers[ii].screenPos - centers[ii-1].screenPos) / screenScale;
centers[ii].len = length(scaledDir);
centers[ii].nDir = normalize(scaledDir);
centers[ii].norm = float2(-centers[ii].nDir.y, centers[ii].nDir.x);
}
}
// Pull out the center line offset, or calculate one
float centerLine = wideVec.offset;
// Intersect on the left or right depending
const float interSgn = (whichVert & 1) ? 1 : -1;
// Do the offset intersection
bool intersectValid = false;
bool turningLeft = false;
const int interIdx = isEnd ? 1 : 0;
float2 offsetCenter = centers[interIdx + 1].screenPos + centers[2].norm * centerLine * screenScale;
float2 interPt(0, 0), realInterPt(0, 0);
float dotProd = 0, theta = 0, miterLength = 0;
// If we're on the far end of the body segment, we need this and the next two segments.
// Otherwise we need the previous, this, and the next segment.
if (instValid[interIdx] && instValid[interIdx+1] && instValid[interIdx+2]) {
// Don't even bother computing intersections for very acute angles or very small turns
dotProd = dot(centers[interIdx+1].nDir, centers[interIdx+2].nDir);
if (-wideVecMaxTurnThreshold < dotProd &&
dotProd < wideVecMaxTurnThreshold &&
abs(abs(dotProd) - 1) >= wideVecMinTurnThreshold) {
// Interior turn angle, both vectors are normalized.
theta = M_PI_F - acos(dotProd);
// "If the miter length divided by the stroke width exceeds the miterlimit then:
// miter: the join is converted to a bevel
// miter-clip: the miter is clipped at half the miter length from the intersection"
if (joinType == WKSVertexLineJoinMiter || joinType == WKSVertexLineJoinMiterClip) {
miterLength = abs(1 / sin(theta / 2));
if (miterLength > wideVec.miterLimit) {
if (joinType == WKSVertexLineJoinMiter) {
joinType = WKSVertexLineJoinBevel;
} else if (joinType == WKSVertexLineJoinMiterClip) {
miterLength = wideVec.miterLimit;
}
}
}
// Intersect the left or right sides of prev-this and this-next, plus offset
thread const CenterInfo &prev = centers[interIdx+0];
thread const CenterInfo &cur = centers[interIdx+1];
thread const CenterInfo &next = centers[interIdx+2];
const float2 edgeDist = screenScale * (interSgn * w2 + centerLine);
const float2 n0 = edgeDist * cur.norm;
const float2 n1 = edgeDist * next.norm;
const IntersectInfo interInfo = intersectLines(prev.screenPos + n0, cur.screenPos + n0,
cur.screenPos + n1, next.screenPos + n1);
if (interInfo.valid) {
const float c = wedge(next.screenPos - cur.screenPos, cur.screenPos - prev.screenPos);
turningLeft = (c < 0);
realInterPt = interPt = interInfo.interPt;
// Limit the distance to the smaller of half way back along the previous segment
// or half way forward along the next one to keep consecutive segments from colliding.
const float maxAdjDist = min(cur.len, next.len) / 2;
// If we're using offsets, we also need to know the point where the offset lines
// intersect, as the distance to the original intersection point isn't helpful.
// todo: this doesn't make sense for small angles, but what's the actual threshold?
if (centerLine != 0) {
const float2 cn0 = cur.norm * centerLine * screenScale;
const float2 cn1 = next.norm * centerLine * screenScale;
const IntersectInfo i2 = intersectLines(prev.screenPos + cn0, cur.screenPos + cn0,
cur.screenPos + cn1, next.screenPos + cn1);
if (i2.valid && length_squared((i2.interPt - offsetCenter)/screenScale) < maxAdjDist*maxAdjDist) {
offsetCenter = i2.interPt;
}
}
const float2 interVec = (interPt - offsetCenter) / screenScale;
const float interDist2 = length_squared(interVec);
const float maxClipDist2 = (maxAdjDist * wideVec.interClipLimit) *
(maxAdjDist * wideVec.interClipLimit);
// Limit intersection distance.
// For up to a multiple of that, adjust the intersection point back along its
// length to that limit, effectively narrowing the line instead of just giving up.
if (interDist2 <= maxAdjDist*maxAdjDist) {
intersectValid = true;
} else if (wideVec.interClipLimit > 0 && interDist2 <= maxClipDist2) {
interPt = offsetCenter + normalize(interVec) * sqrt(interDist2) * screenScale;
intersectValid = true;
}
}
}
}
// Endcaps not used for miter case, discard them.
if ((joinType == WKSVertexLineJoinMiter || joinType == WKSVertexLineJoinMiterSimple) &&
whichPoly != WideVecPolyBodyGeom && !isStartCap && !isEndCap) {
return outVert;
}
// Get the color from the uniform:
float4 color = wideVec.color;
outVert.color = color;
outVert.w2 = w2;
outVert.edge = wideVec.edge;
// Work out the corner positions by extending the normals
const float2 realEdge = interSgn * w2 * screenScale;
const float2 corner = offsetCenter + centers[2].norm * realEdge;
const float turnSgn = turningLeft ? -1 : 1;
const bool isInsideEdge = (isLeft == turningLeft);
// Current corner is the default result for all points.
float2 pos = corner;
bool discardTri = false;
if (isStartCap || isEndCap) {
// Square extends beyond the point by half a width.
// Round uses the same geometry but rounds it off with the fragment shader.
if (capType == WhirlyKitShader::WKSVertexLineCapSquare ||
capType == WhirlyKitShader::WKSVertexLineCapRound) {
switch (whichVert) {
case 0: case 1: case 10: case 11:
pos = corner + centers[2].nDir * w2 * screenScale * (isEnd ? 1 : -1);
break;
}
}
if (capType == WhirlyKitShader::WKSVertexLineCapRound) {
outVert.roundJoin = true;
outVert.centerPos = offsetCenter / screenScale;
outVert.screenPos = pos / screenScale;
outVert.midDir = centers[2].nDir * (isEnd ? 1 : -1);
}
} else {
if (joinType == WKSVertexLineJoinNone || !intersectValid) {
// Trivial case, just use the corner
outVert.position = float4(pos, 0, 1);
return outVert;
}
// Since there is one, use the intersect point by default
pos = interPt;
// We'll need the corner on the opposite side for several things.
const float2 realOtherEdge = -interSgn * w2 * screenScale;
const float2 otherCorner = offsetCenter + centers[2].norm * realOtherEdge;
// For a bevel, use the intersect point for the inside of the turn, but not the outside.
// Round piggypacks on bevel.
if (joinType == WKSVertexLineJoinBevel || joinType == WKSVertexLineJoinRound) {
switch (whichVert) {
// Start cap, 0-3-1, 0-2-3
case 2: case 10:
discardTri = true; // Not using triangle #2
break;
// Merge inside corners to avoid overlap, use default outside corner.
case 0: case 3: case 8: case 9:
case 4: case 5: case 6: case 7:
pos = isInsideEdge ? interPt : corner;
break;
case 1: case 11: {
// Use the point halfway between the outside corner and the one on the opposite side.
const float2 norm = centers[(whichVert == 1) ? 1 : 3].norm;
const float2 c = offsetCenter + centers[2].norm * realEdge * turnSgn;
const float2 e = turnSgn * interSgn * w2 * screenScale;
pos = (offsetCenter + c + norm * e) / 2;
break;
}
}
// For the round case, extend the center of the bevel out into a "tip," which will be
// turned into a round extension by the fragment shader. This isn't exactly right, but
// I think we need more geometry to do better.
// todo: fix texture Y-coords
if (joinType == WKSVertexLineJoinRound && whichPoly != WideVecPolyBodyGeom && !discardTri) {
outVert.roundJoin = true;
outVert.centerPos = offsetCenter / screenScale;
outVert.screenPos = pos / screenScale;
// Direction bisecting the turn toward the outside (right for a left turn)
const float2 mid = isEnd ? (centers[2].nDir - centers[3].nDir) :
(centers[1].nDir - centers[2].nDir);
outVert.midDir = normalize(mid / screenScale);
if (whichVert == 1 || whichVert == 11) {
// Extend the corner far enough to cover the necessary round-ness.
// This should probably be related to the turn angle, we're just fudging it.
const float extend = 2 * w2;
pos += outVert.midDir * extend * screenScale;
outVert.screenPos = pos / screenScale;
}
}
} else if (joinType == WKSVertexLineJoinMiterClip) {
// Direction of intersect point (bisecting the segment directions)
const float2 interVec = (realInterPt - offsetCenter) / screenScale;
const float2 interDir = normalize(interVec) * turnSgn * interSgn;
// And the perpendicular
const float2 interNorm = float2(-interDir.y, interDir.x);
// Distance from centerline intersect and edge intersect
const float interDist = length(interVec);
// "the miter is clipped by a line perpendicular to the line bisecting
// the angle between the two path segments at a distance of half the
// value of miter length from the intersection of the two path segments."
const float midExt = miterLength / 2 * strokeWidth + wideVec.edge;
switch (whichVert) {
// Start cap, 0-3-1, 0-2-3
case 0: case 8: {
// Out to the miter cap, then perpendicular to the line edge.
// The half-width of the clipped miter cap is the tangent of half the interior
// turn angle times the amount the original intersection extends beyond the cap.
const float miterEdgeW2 = (interDist - midExt) * tan(theta / 2);
pos = offsetCenter + screenScale * (interDir * midExt -
interNorm * miterEdgeW2 * turnSgn * (whichVert ? -1 : 1));
break;
}
case 1: case 10:
// Extend the turn bisector to the miter clip edge
pos = turningLeft ? (offsetCenter + interDir * midExt * screenScale) :
((whichVert == 1) ? corner : otherCorner);
break;
case 2: case 9:
// Extend segment endpoint outward along the intersection angle by the miter length
pos = turningLeft ? ((whichVert == 2) ? corner : otherCorner) :
(offsetCenter + interDir * midExt * screenScale);
break;
case 3: case 11:
// The edge intersect, or the one on the other side
pos = turningLeft ? interPt : (offsetCenter + (offsetCenter - interPt));
break;
// Body segment, 4-7-5, 4-6-7
// Merge inside corners to avoid overlap, use default outside corner.
case 4: case 5: case 6: case 7:
pos = isInsideEdge ? interPt : corner;
break;
}
}
}
if (!discardTri) {
outVert.position = float4(pos, 0, 1);
}
return outVert;
}
// Fragment shader that takes the back of the globe into account
fragment float4 fragmentTri_wideVecPerf(
ProjVertexTriWideVecPerf vert [[stage_in]],
constant Uniforms &uniforms [[ buffer(3) ]])
{
float patternAlpha = 1.0;
// Reduce alpha of the "tip" for round joins to get a round look.
float roundAlpha = 1.0;
if (vert.roundJoin && dot(vert.screenPos - vert.centerPos, vert.midDir) > 0) {
const float r = length_squared(vert.screenPos - vert.centerPos) / vert.w2 / vert.w2;
roundAlpha = clamp((r > 0.95) ? (1 - r) * 20 : 1.0, 0.0, 1.0);
}
// Reduce alpha along the edges to get smooth blending.
const float edgeAlpha = (vert.edge > 0) ? clamp((1 - abs(vert.texCoord.x)) * vert.w2 / vert.edge, 0.0, 1.0) : 1.0;
return vert.color * float4(1,1,1,edgeAlpha * patternAlpha * roundAlpha);
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -14,6 +14,7 @@
#include <mbgl/shaders/line_layer_ubo.hpp>
#include <mbgl/shaders/raster_layer_ubo.hpp>
#include <mbgl/shaders/symbol_layer_ubo.hpp>
#include <mbgl/shaders/widevector_ubo.hpp>
#include <algorithm>
@@ -47,7 +48,8 @@ static constexpr auto maxUBOCountPerShader = std::max({static_cast<size_t>(backg
static_cast<size_t>(lineSDFUBOCount),
static_cast<size_t>(lineBasicUBOCount),
static_cast<size_t>(rasterUBOCount),
static_cast<size_t>(symbolUBOCount)});
static_cast<size_t>(symbolUBOCount),
static_cast<size_t>(wideVectorUBOCount)});
// Texture defines
enum {
@@ -259,8 +261,25 @@ enum {
symbolVertexAttributeCount
};
static constexpr auto maxVertexAttributeCountPerShader = std::max(
{static_cast<size_t>(backgroundVertexAttributeCount),
enum {
idWideVectorScreenPos,
idWideVectorColor,
idWideVectorIndex,
wideVectorAttributeCount
};
enum {
idWideVectorInstanceCenter,
idWideVectorInstanceColor,
idWideVectorInstancePrevious,
idWideVectorInstanceNext,
wideVectorInstanceAttributeCount
};
static constexpr auto maxVertexAttributeCountPerShader = std::max({
static_cast<size_t>(backgroundVertexAttributeCount),
static_cast<size_t>(circleVertexAttributeCount),
static_cast<size_t>(clippingMaskVertexAttributeCount),
static_cast<size_t>(collisionVertexAttributeCount),
@@ -272,7 +291,10 @@ static constexpr auto maxVertexAttributeCountPerShader = std::max(
static_cast<size_t>(hillshadeVertexAttributeCount),
static_cast<size_t>(lineVertexAttributeCount),
static_cast<size_t>(rasterVertexAttributeCount),
static_cast<size_t>(symbolVertexAttributeCount)});
static_cast<size_t>(symbolVertexAttributeCount),
static_cast<size_t>(wideVectorAttributeCount),
static_cast<size_t>(wideVectorInstanceAttributeCount),
});
} // namespace shaders
} // namespace mbgl

View File

@@ -29,6 +29,7 @@
#include <mbgl/shaders/gl/drawable_symbol_sdf.hpp>
#include <mbgl/shaders/gl/drawable_symbol_text_and_icon.hpp>
#include <mbgl/shaders/gl/drawable_custom_symbol_icon.hpp>
#include <mbgl/shaders/gl/drawable_wide_vector.hpp>
#include <mbgl/shaders/gl/prelude.hpp>
#include <mbgl/shaders/gl/background.hpp>
#include <mbgl/shaders/gl/background_pattern.hpp>

View File

@@ -40,6 +40,9 @@ public:
/// Get the available vertex attributes and their default values
virtual const gfx::VertexAttributeArray& getVertexAttributes() const = 0;
/// Get the available instance attributes and their default values
virtual const gfx::VertexAttributeArray& getInstanceAttributes() const = 0;
protected:
virtual gfx::UniformBlockArray& mutableUniformBlocks() = 0;

View File

@@ -35,6 +35,7 @@ enum class BuiltIn {
SymbolSDFIconShader,
SymbolTextAndIconShader,
CustomSymbolIconShader,
WideVectorShader,
Prelude,
BackgroundProgram,
BackgroundPatternProgram,

View File

@@ -0,0 +1,58 @@
#pragma once
#include <mbgl/shaders/layer_ubo.hpp>
namespace mbgl {
namespace shaders {
struct alignas(16) WideVectorUniformsUBO {
std::array<float, 4 * 4> mvpMatrix;
std::array<float, 4 * 4> mvpMatrixDiff;
std::array<float, 4 * 4> mvMatrix;
std::array<float, 4 * 4> mvMatrixDiff;
std::array<float, 4 * 4> pMatrix;
std::array<float, 4 * 4> pMatrixDiff;
std::array<float, 2> frameSize;
};
static_assert(sizeof(WideVectorUniformsUBO) % 16 == 0);
struct alignas(16) WideVectorUniformWideVecUBO {
std::array<float, 4> color;
float w2;
float offset;
float edge;
float texRepeat;
std::array<float, 2> texOffset;
float miterLimit;
int32_t join;
int32_t cap;
int32_t hasExp;
float interClipLimit;
};
static_assert(sizeof(WideVectorUniformWideVecUBO) % 16 == 0);
enum {
idWideVectorUniformsUBO,
idWideVectorUniformWideVecUBO,
wideVectorUBOCount
};
struct VertexTriWideVecB {
// x, y offset around the center
std::array<float, 3> screenPos;
std::array<float, 4> color;
int index;
};
static_assert(sizeof(VertexTriWideVecB) == 32);
struct VertexTriWideVecInstance {
std::array<float, 4> center;
std::array<float, 4> color;
int32_t prev;
int32_t next;
int64_t pad_;
};
static_assert(sizeof(VertexTriWideVecInstance) == 48);
} // namespace shaders
} // namespace mbgl

View File

@@ -33,14 +33,20 @@ public:
class CustomDrawableLayerHost::Interface {
public:
enum class LineShaderType {
Classic,
MetalWideVector
};
struct LineOptions {
Color color;
gfx::PolylineGeneratorOptions geometry;
float blur = 0.f;
float opacity = 1.f;
float gapWidth = 0.f;
float offset = 0.f;
float width = 1.f;
gfx::PolylineGeneratorOptions geometry;
LineShaderType shaderType = LineShaderType::Classic;
Color color;
};
struct FillOptions {
@@ -106,24 +112,34 @@ public:
/**
* @brief Add a polyline
*
* @param coordinates
* @param options Polyline options
* @param coordinates in tile range
* @return true if the polyline was added
*/
void addPolyline(const GeometryCoordinates& coordinates);
bool addPolyline(const GeometryCoordinates& coordinates);
/**
* @brief Add a polyline
*
* @param coordinates Geographic coordinates
* @return true if the polyline was added
*/
bool addPolyline(const LineString<double>& coordinates);
/**
* @brief Add a multipolygon area fill
*
* @param geometry a collection of rings with optional holes
* @return true if the fill was added
*/
void addFill(const GeometryCollection& geometry);
bool addFill(const GeometryCollection& geometry);
/**
* @brief Add a symbol
*
* @param point
* @return true if the symbol was added
*/
void addSymbol(const GeometryCoordinate& point);
bool addSymbol(const GeometryCoordinate& point);
/**
* @brief Finish the current drawable building session
@@ -143,10 +159,20 @@ public:
private:
gfx::ShaderPtr lineShaderDefault() const;
gfx::ShaderPtr lineShaderWideVector() const;
gfx::ShaderPtr fillShaderDefault() const;
gfx::ShaderPtr symbolShaderDefault() const;
enum class BuilderType {
None,
LineClassic,
LineWideVector,
Fill,
Symbol
};
std::unique_ptr<gfx::DrawableBuilder> createBuilder(const std::string& name, gfx::ShaderPtr shader) const;
bool updateBuilder(BuilderType type, const std::string& name, gfx::ShaderPtr shader);
std::unique_ptr<gfx::DrawableBuilder> builder;
std::optional<OverscaledTileID> tileID;
@@ -158,6 +184,8 @@ private:
LineOptions lineOptions;
FillOptions fillOptions;
SymbolOptions symbolOptions;
BuilderType builderType{BuilderType::None};
};
class CustomDrawableLayer final : public Layer {

View File

@@ -43,6 +43,7 @@ public:
static std::optional<Color> parse(const std::string&);
std::string stringify() const;
std::array<double, 4> toArray() const;
operator std::array<float, 4>() const { return {r, g, b, a}; }
mbgl::Value toObject() const;
mbgl::Value serialize() const;
};

View File

@@ -47,30 +47,31 @@ public:
if (interface.getDrawableCount())
return;
// set tile
interface.setTileID({11, 327, 791});
constexpr float extent = mbgl::util::EXTENT;
// add polylines
// add classic polylines
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines] {
{/*color=*/Color::red(), /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, {} },
{/*color=*/Color::blue(), /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, {} },
{/*color=*/Color(1.f, 0.5f, 0, 0.5f), /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, {} },
{/*color=*/Color(1.f, 1.f, 0, 0.3f), /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, {} },
{/*color=*/Color::black(), /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, {} },
{/*color=*/Color(1.f, 0, 1.f, 0.2f), /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, {} },
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*shaderType=*/{}, /*color=*/Color::red() },
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*shaderType=*/{}, /*color=*/Color::blue() },
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*shaderType=*/{}, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*shaderType=*/{}, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*shaderType=*/{}, /*color=*/Color::black() },
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*shaderType=*/{}, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
};
for(auto& opt: options) {
opt.geometry.beginCap = style::LineCapType::Round;
opt.geometry.endCap = style::LineCapType::Round;
opt.geometry.joinType = style::LineJoinType::Round;
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 100;
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
@@ -89,10 +90,64 @@ public:
}
}
// add wide vector polylines with tile coordinates
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines] {
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/Color::red() },
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/Color::blue() },
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/Color::black() },
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
};
for(auto& opt: options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index {0}; index < numLines; ++index) {
for(auto &p : polyline) {
if (0 == index) p.y += 0.25f * extent / numLines;
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline);
// add clone
for(auto &p : polyline) {
p.y += 0.05f * extent / numLines;
}
interface.addPolyline(polyline);
for(auto &p : polyline) {
p.y -= 0.05f * extent / numLines;
}
}
}
// add fill polygon
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 790});
GeometryCollection geometry{
{
// ring 1
@@ -122,6 +177,10 @@ public:
// add symbol
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 789});
GeometryCoordinate position {static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)};
// load image
@@ -149,6 +208,74 @@ public:
interface.addSymbol(position);
}
// add polylines using wide vectors using geographic coordinates
{
using namespace mbgl;
// add polyline with geographic coordinates
Interface::LineOptions options = {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/12.0f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/{.0f, .0f, .0f, .5f} };
options.geometry.beginCap = style::LineCapType::Square;
options.geometry.endCap = style::LineCapType::Square;
options.geometry.joinType = style::LineJoinType::Bevel;
options.geometry.type = FeatureType::LineString;
interface.setLineOptions(options);
LineString<double> polyline_geo {
// San Francisco
{-122.38186800073211, 37.77466003457463},
{-122.3869373450997, 37.774352128895615},
{-122.38680767979824, 37.773294612880306},
{-122.38476465260224, 37.773350946288765},
{-122.38146223043374, 37.77194168964067},
{-122.6813560305925, 37.666084247570964},
{-122.26765538866474, 37.65037232584494},
{-122.42528413673159, 38.020443518012584},
// Seattle
{-122.34927775216809, 47.62050663596438},
// New York
{-74.04454331829972, 40.6892168305434},
};
interface.addPolyline(polyline_geo);
}
// add polylines using wide vectors in tile coordinates
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 790});
Interface::LineOptions options
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/7.0f, /*shaderType=*/Interface::LineShaderType::MetalWideVector, /*color=*/{1.0f, 0, 0, .5f} };
options.geometry.beginCap = style::LineCapType::Round;
options.geometry.endCap = style::LineCapType::Round;
options.geometry.joinType = style::LineJoinType::Round;
// add polyline with tile coordinates
GeometryCollection polyline_tile{
{
// ring 1
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)},
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)},
{static_cast<int16_t>(extent* 0.7f), static_cast<int16_t>(extent* 0.5f)},
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 1.0f)},
{static_cast<int16_t>(extent* 0.0f), static_cast<int16_t>(extent* 0.5f)},
},
{
// ring 2
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)},
{static_cast<int16_t>(extent* 0.15f), static_cast<int16_t>(extent* 0.5f)},
{static_cast<int16_t>(extent* 0.25f), static_cast<int16_t>(extent* 0.45f)},
},
};
options.geometry.type = FeatureType::Polygon;
interface.setLineOptions(options);
interface.addPolyline(polyline_tile[0]);
interface.addPolyline(polyline_tile[1]);
}
// finish
interface.finish();
}

View File

@@ -247,4 +247,16 @@ class iosapp_UITests: XCTestCase {
}
}
}
/// Make sure the Custom Drawable Layer does not crash
func testCustomDrawableLayer() {
app.windows.children(matching: .other).element.children(matching: .other).element.children(matching: .other).element.doubleTap()
let mapSettingsButton = app.navigationBars["MapLibre Basic"].buttons["Map settings"]
mapSettingsButton.tap()
let tablesQuery = app.tables
tablesQuery.staticTexts["Add Custom Drawable Layer"].tap()
sleep(5)
}
}

0
shaders/_empty.glsl Normal file
View File

View File

@@ -181,6 +181,13 @@
"glsl_frag": "drawable.custom.symbol_icon.fragment.glsl",
"uses_ubos": true
},
{
"name": "WideVectorShader",
"header": "drawable_wide_vector",
"glsl_vert": "_empty.glsl",
"glsl_frag": "_empty.glsl",
"uses_ubos": false
},
{
"name": "Prelude",

View File

@@ -44,6 +44,9 @@ void DrawableBuilder::flush(gfx::Context& context) {
if (Impl::Mode::Polylines == impl->getMode()) {
// setup for polylines
impl->setupForPolylines(context, *this);
} else if (Impl::Mode::WideVectorLocal == impl->getMode() || Impl::Mode::WideVectorGlobal == impl->getMode()) {
// setup for wide vectors
impl->setupForWideVectors(context, *this);
}
const auto& draw = getCurrentDrawable(/*createIfNone=*/true);
@@ -62,6 +65,7 @@ void DrawableBuilder::flush(gfx::Context& context) {
draw->setShader(shader);
draw->setTextures(textures);
draw->setTweakers(tweakers);
draw->setOrigin(origin);
if (vertexAttrs) {
draw->setVertexAttributes(vertexAttrs);
@@ -69,10 +73,17 @@ void DrawableBuilder::flush(gfx::Context& context) {
draw->setVertexAttributes(context.createVertexAttributeArray());
}
if (instanceAttrs) {
draw->setInstanceAttributes(instanceAttrs);
}
init();
// reset mode
impl->setMode(Impl::Mode::Custom);
// reset origin
origin.reset();
}
if (currentDrawable) {
drawables.emplace_back(std::move(currentDrawable));
@@ -280,5 +291,23 @@ void DrawableBuilder::addPolyline(const GeometryCoordinates& coordinates,
impl->addPolyline(*this, coordinates, options);
}
void DrawableBuilder::addWideVectorPolylineLocal(const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options) {
// mark the current mode
if (!impl->checkAndSetMode(Impl::Mode::WideVectorLocal)) return;
// append polyline
impl->addWideVectorPolylineLocal(*this, coordinates, options);
}
void DrawableBuilder::addWideVectorPolylineGlobal(const LineString<double>& coordinates,
const gfx::PolylineGeneratorOptions& options) {
// mark the current mode
if (!impl->checkAndSetMode(Impl::Mode::WideVectorGlobal)) return;
// append polyline
origin = impl->addWideVectorPolylineGlobal(*this, coordinates, options);
}
} // namespace gfx
} // namespace mbgl

View File

@@ -5,6 +5,7 @@
#include <mbgl/style/types.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/util/projection.hpp>
#include <string>
@@ -13,6 +14,8 @@ namespace gfx {
using namespace shaders;
// MARK: - Polylines
DrawableBuilder::Impl::LineLayoutVertex DrawableBuilder::Impl::layoutVertex(
Point<int16_t> p, Point<double> e, bool round, bool up, int8_t dir, int32_t linesofar /*= 0*/) {
/*
@@ -91,6 +94,186 @@ void DrawableBuilder::Impl::setupForPolylines(gfx::Context& context, gfx::Drawab
sharedIndexes = std::make_shared<gfx::IndexVectorBase>(std::move(polylineIndexes));
}
// MARK: - Wide Vector Polylines
namespace {
inline void genInstanceLinks(
int32_t& outPrev, int32_t& outNext, const int base, const int coord_size, const int index, bool loop) {
if (loop) {
// loop line
outPrev = base + (index - 1 + coord_size) % coord_size;
outNext = base + (index + 1) % coord_size;
} else {
// line string
outPrev = (0 == index) ? -1 : base + index - 1;
outNext = (index + 1 >= coord_size) ? -1 : base + index + 1;
}
}
} // namespace
void DrawableBuilder::Impl::addWideVectorPolylineLocal(gfx::DrawableBuilder& /*builder*/,
const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options) {
// add instance data
const int base = static_cast<int>(wideVectorInstanceData.elements());
const int coord_size = static_cast<int>(coordinates.size());
int index = 0;
for (const auto& coord : coordinates) {
VertexTriWideVecInstance data;
data.center = {static_cast<float>(coord.x), static_cast<float>(coord.y), 0};
genInstanceLinks(data.prev, data.next, base, coord_size, index, FeatureType::Polygon == options.type);
wideVectorInstanceData.emplace_back(std::move(data));
++index;
}
}
mbgl::Point<double> DrawableBuilder::Impl::addWideVectorPolylineGlobal(gfx::DrawableBuilder& /*builder*/,
const LineString<double>& coordinates,
const gfx::PolylineGeneratorOptions& options) {
constexpr int32_t zoom = 0;
// get center
constexpr double maxd = std::numeric_limits<double>::max();
constexpr double mind = std::numeric_limits<double>::min();
Point<double> minPoint{maxd, maxd}, maxPoint{mind, mind};
for (const auto& coord : coordinates) {
auto merc = Projection::project(LatLng(coord.y, coord.x), zoom);
Point<double> pSource{merc.x * mbgl::util::EXTENT, merc.y * mbgl::util::EXTENT};
minPoint.x = std::min(pSource.x, minPoint.x);
minPoint.y = std::min(pSource.y, minPoint.y);
maxPoint.x = std::max(pSource.x, maxPoint.x);
maxPoint.y = std::max(pSource.y, maxPoint.y);
}
Point<double> pCenter{(minPoint.x + maxPoint.x) / 2.0, (minPoint.y + maxPoint.y) / 2.0};
// add centerline instance data
const int base = static_cast<int>(wideVectorInstanceData.elements());
const int coord_size = static_cast<int>(coordinates.size());
int index = 0;
for (const auto& coord : coordinates) {
auto merc = Projection::project(LatLng(coord.y, coord.x), zoom);
Point<double> pSource{merc.x * mbgl::util::EXTENT, merc.y * mbgl::util::EXTENT};
pSource.x -= pCenter.x;
pSource.y -= pCenter.y;
VertexTriWideVecInstance data;
data.center = {static_cast<float>(pSource.x), static_cast<float>(pSource.y), 0};
genInstanceLinks(data.prev, data.next, base, coord_size, index, FeatureType::Polygon == options.type);
wideVectorInstanceData.emplace_back(std::move(data));
++index;
}
return pCenter;
}
void DrawableBuilder::Impl::setupForWideVectors(gfx::Context& context, gfx::DrawableBuilder& builder) {
// vertices
if (!wideVectorVertices) {
constexpr shaders::VertexTriWideVecB kWideVectorVertices[]{
{{0}, {0}, (0 << 16) + 0},
{{0}, {0}, (1 << 16) + 0},
{{0}, {0}, (2 << 16) + 0},
{{0}, {0}, (3 << 16) + 0},
{{0}, {0}, (4 << 16) + 1},
{{0}, {0}, (5 << 16) + 1},
{{0}, {0}, (6 << 16) + 1},
{{0}, {0}, (7 << 16) + 1},
{{0}, {0}, (8 << 16) + 2},
{{0}, {0}, (9 << 16) + 2},
{{0}, {0}, (10 << 16) + 2},
{{0}, {0}, (11 << 16) + 2},
};
wideVectorVertices = std::make_shared<gfx::VertexVector<shaders::VertexTriWideVecB>>();
for (auto& v : kWideVectorVertices) {
wideVectorVertices->emplace_back(v);
}
}
// instance data
using InstanceVector = gfx::VertexVector<VertexTriWideVecInstance>;
const std::shared_ptr<InstanceVector> sharedInstanceData = std::make_shared<InstanceVector>(
std::move(wideVectorInstanceData));
// indexes
if (!wideVectorIndexes) {
wideVectorIndexes = std::make_shared<gfx::IndexVector<gfx::Triangles>>();
wideVectorIndexes->emplace_back(
0, 3, 1, 0, 2, 3, 4 + 0, 4 + 3, 4 + 1, 4 + 0, 4 + 2, 4 + 3, 8 + 0, 8 + 3, 8 + 1, 8 + 0, 8 + 2, 8 + 3);
}
// segments
SegmentVector<VertexTriWideVecB> triangleSegments;
triangleSegments.emplace_back(Segment<VertexTriWideVecB>{0, 0, 12, 18});
// add to builder
{
// add vertex attributes
auto vertexAttributes = context.createVertexAttributeArray();
if (const auto& attr = vertexAttributes->set(idWideVectorScreenPos)) {
attr->setSharedRawData(wideVectorVertices,
offsetof(VertexTriWideVecB, screenPos),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecB),
gfx::AttributeDataType::Float3);
}
if (const auto& attr = vertexAttributes->set(idWideVectorColor)) {
attr->setSharedRawData(wideVectorVertices,
offsetof(VertexTriWideVecB, color),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecB),
gfx::AttributeDataType::Float4);
}
if (const auto& attr = vertexAttributes->set(idWideVectorIndex)) {
attr->setSharedRawData(wideVectorVertices,
offsetof(VertexTriWideVecB, index),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecB),
gfx::AttributeDataType::Int);
}
builder.setVertexAttributes(std::move(vertexAttributes));
}
{
// add instance attributes
auto instanceAttributes = context.createVertexAttributeArray();
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceCenter)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, center),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Float3);
}
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceColor)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, color),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Float4);
}
if (const auto& attr = instanceAttributes->set(idWideVectorInstancePrevious)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, prev),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Int);
}
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceNext)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, next),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Int);
}
builder.setInstanceAttributes(std::move(instanceAttributes));
}
builder.setRawVertices({}, wideVectorVertices->elements(), gfx::AttributeDataType::Float2);
builder.setSegments(gfx::Triangles(), wideVectorIndexes, triangleSegments.data(), triangleSegments.size());
}
// MARK: - Common
bool DrawableBuilder::Impl::checkAndSetMode(Mode target) {
if (target != mode && vertexCount()) {
// log error
@@ -101,6 +284,10 @@ bool DrawableBuilder::Impl::checkAndSetMode(Mode target) {
return "Mode::Primitives";
case Mode::Polylines:
return "Mode::Polylines";
case Mode::WideVectorLocal:
return "Mode::WideVectorLocal";
case Mode::WideVectorGlobal:
return "Mode::WideVectorGlobal";
case Mode::Custom:
return "Mode::Custom";
default:

View File

@@ -7,6 +7,7 @@
#include <mbgl/programs/segment.hpp>
#include <mbgl/gfx/drawable_builder.hpp>
#include <mbgl/gfx/polyline_generator.hpp>
#include <mbgl/shaders/widevector_ubo.hpp>
#include <cstdint>
#include <cstddef>
@@ -23,6 +24,8 @@ public:
enum class Mode {
Primitives, ///< building primitive drawables. Not implemented
Polylines, ///< building drawables for thick polylines
WideVectorLocal, ///< building drawables for thick polylines using wide vectors in local coordinates
WideVectorGlobal, ///< building drawables for thick polylines using wide vectors in global coordinates
Custom ///< building custom drawables.
};
struct LineLayoutVertex {
@@ -39,6 +42,10 @@ public:
gfx::VertexVector<LineLayoutVertex> polylineVertices;
gfx::IndexVector<gfx::Triangles> polylineIndexes;
std::shared_ptr<gfx::VertexVector<shaders::VertexTriWideVecB>> wideVectorVertices;
gfx::VertexVector<shaders::VertexTriWideVecInstance> wideVectorInstanceData;
std::shared_ptr<gfx::IndexVector<gfx::Triangles>> wideVectorIndexes;
std::vector<uint16_t> buildIndexes;
std::shared_ptr<gfx::IndexVectorBase> sharedIndexes;
std::vector<std::unique_ptr<Drawable::DrawSegment>> segments;
@@ -47,12 +54,23 @@ public:
gfx::ColorMode colorMode = gfx::ColorMode::disabled();
gfx::CullFaceMode cullFaceMode = gfx::CullFaceMode::disabled();
// methods
void addPolyline(gfx::DrawableBuilder& builder,
const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options);
void setupForPolylines(gfx::Context&, gfx::DrawableBuilder&);
void addWideVectorPolylineLocal(gfx::DrawableBuilder& builder,
const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options);
mbgl::Point<double> addWideVectorPolylineGlobal(gfx::DrawableBuilder& builder,
const LineString<double>& coordinates,
const gfx::PolylineGeneratorOptions& options);
void setupForWideVectors(gfx::Context&, gfx::DrawableBuilder&);
bool checkAndSetMode(Mode);
Mode getMode() const { return mode; };
@@ -60,7 +78,8 @@ public:
bool setMode(Mode value) { return mode == value; };
std::size_t vertexCount() const {
return std::max(rawVerticesCount, std::max(vertices.elements(), polylineVertices.elements()));
return std::max(
{rawVerticesCount, vertices.elements(), polylineVertices.elements(), wideVectorInstanceData.elements()});
}
void clear() {

View File

@@ -186,6 +186,7 @@ void Drawable::draw(PaintParameters& parameters) const {
#endif
bindAttributes(renderPass);
bindInstanceAttributes(renderPass);
bindUniformBuffers(renderPass);
bindTextures(renderPass);
@@ -256,7 +257,7 @@ void Drawable::draw(PaintParameters& parameters) const {
const auto primitiveType = getPrimitiveType(mode.type);
constexpr auto indexType = MTL::IndexType::IndexTypeUInt16;
constexpr auto indexSize = sizeof(std::uint16_t);
constexpr NS::UInteger instanceCount = 1;
const NS::UInteger instanceCount = instanceAttributes ? instanceAttributes->getMaxCount() : 1;
constexpr NS::UInteger baseInstance = 0;
const NS::UInteger indexOffset = static_cast<NS::UInteger>(indexSize *
mlSegment.indexOffset); // in bytes, not indexes
@@ -360,6 +361,25 @@ void Drawable::bindAttributes(RenderPass& renderPass) const noexcept {
}
}
void Drawable::bindInstanceAttributes(RenderPass& renderPass) const noexcept {
const auto& encoder = renderPass.getMetalEncoder();
NS::UInteger attributeIndex = 0;
for (const auto& binding : impl->instanceBindings) {
if (binding.has_value()) {
const auto* buffer = static_cast<const mtl::VertexBufferResource*>(binding->vertexBufferResource);
if (buffer && buffer->get()) {
renderPass.bindVertex(buffer->get(), /*offset=*/0, attributeIndex);
} else {
const auto* shaderMTL = static_cast<const ShaderProgram*>(shader.get());
auto& context = renderPass.getCommandEncoder().getContext();
renderPass.bindVertex(context.getEmptyBuffer(), /*offset=*/0, attributeIndex);
}
}
attributeIndex += 1;
}
}
void Drawable::bindUniformBuffers(RenderPass& renderPass) const noexcept {
if (shader) {
const auto& uniformBlocks = shader->getUniformBlocks();
@@ -584,6 +604,30 @@ void Drawable::upload(gfx::UploadPass& uploadPass_) {
}
}
// build instance buffer
const bool buildInstanceBuffer = (instanceAttributes && instanceAttributes->isDirty());
if (buildInstanceBuffer) {
// Build instance attribute buffers
std::vector<std::unique_ptr<gfx::VertexBufferResource>> instanceBuffers;
auto instanceBindings_ = uploadPass.buildAttributeBindings(instanceAttributes->getMaxCount(),
/*vertexType*/ gfx::AttributeDataType::Byte,
/*vertexAttributeIndex=*/-1,
/*vertexData=*/{},
shader->getInstanceAttributes(),
*instanceAttributes,
usage,
instanceBuffers);
impl->instanceBuffers = std::move(instanceBuffers);
// clear dirty flag
instanceAttributes->visitAttributes([](gfx::VertexAttribute& attrib) { attrib.setDirty(false); });
if (impl->instanceBindings != instanceBindings_) {
impl->instanceBindings = std::move(instanceBindings_);
}
}
const bool texturesNeedUpload = std::any_of(
textures.begin(), textures.end(), [](const auto& texture) { return texture && texture->needsUpload(); });

View File

@@ -45,6 +45,10 @@ public:
gfx::AttributeDataType vertexType = gfx::AttributeDataType::Invalid;
std::vector<gfx::UniqueVertexBufferResource> attributeBuffers;
gfx::AttributeBindingArray attributeBindings;
std::vector<gfx::UniqueVertexBufferResource> instanceBuffers;
gfx::AttributeBindingArray instanceBindings;
UniformBufferArray uniformBuffers;
@@ -56,8 +60,6 @@ public:
VertexBufferResource* noBindingBuffer = nullptr;
gfx::AttributeBindingArray attributeBindings;
MTLRenderPipelineStatePtr pipelineState;
std::optional<gfx::RenderPassDescriptor> renderPassDescriptor;

View File

@@ -33,6 +33,7 @@
#include <mbgl/shaders/mtl/symbol_icon.hpp>
#include <mbgl/shaders/mtl/symbol_sdf.hpp>
#include <mbgl/shaders/mtl/symbol_text_and_icon.hpp>
#include <mbgl/shaders/mtl/widevector.hpp>
#include <cassert>
#include <string>
@@ -150,7 +151,8 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar
shaders::BuiltIn::RasterShader,
shaders::BuiltIn::SymbolIconShader,
shaders::BuiltIn::SymbolSDFIconShader,
shaders::BuiltIn::SymbolTextAndIconShader>(shaders, programParameters);
shaders::BuiltIn::SymbolTextAndIconShader,
shaders::BuiltIn::WideVectorShader>(shaders, programParameters);
}
} // namespace mtl

View File

@@ -37,6 +37,9 @@ mat4 LayerTweaker::getTileMatrix(const UnwrappedTileID& tileID,
// from RenderTile::prepare
mat4 tileMatrix;
parameters.state.matrixFor(/*out*/ tileMatrix, tileID);
if (const auto& origin{drawable.getOrigin()}; origin.has_value()) {
matrix::translate(tileMatrix, tileMatrix, origin->x, origin->y, 0);
}
// nearClippedMatrix has near plane moved further, to enhance depth buffer precision
auto projMatrix = aligned ? parameters.transformParams.alignedProjMatrix

View File

@@ -23,23 +23,6 @@ using namespace std::string_literals;
namespace mbgl {
shaders::AttributeInfo::AttributeInfo(std::size_t index_, gfx::AttributeDataType dataType_, std::size_t id_)
: index(index_),
dataType(dataType_),
id(id_) {}
shaders::UniformBlockInfo::UniformBlockInfo(
std::size_t index_, bool vertex_, bool fragment_, std::size_t size_, std::size_t id_)
: index(index_),
vertex(vertex_),
fragment(fragment_),
size(size_),
id(id_) {}
shaders::TextureInfo::TextureInfo(std::size_t index_, std::size_t id_)
: index(index_),
id(id_) {}
namespace mtl {
namespace {
MTL::BlendOperation metalBlendOperation(const gfx::ColorBlendEquationType& colorBlend) {
@@ -194,16 +177,30 @@ void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) {
#if !defined(NDEBUG)
// Indexes must be unique, if there's a conflict check the `attributes` array in the shader
vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); });
instanceAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); });
uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); });
#endif
vertexAttributes.set(info.id, index, info.dataType, 1);
}
void ShaderProgram::initInstanceAttribute(const shaders::AttributeInfo& info) {
// Index is the block index of the instance attribute
const auto index = static_cast<int>(info.index);
#if !defined(NDEBUG)
// Indexes must not be reused by regular attributes or uniform blocks
// More than one instance attribute can have the same index, if they share the block
vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); });
uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); });
#endif
instanceAttributes.set(info.id, index, info.dataType, 1);
}
void ShaderProgram::initUniformBlock(const shaders::UniformBlockInfo& info) {
const auto index = static_cast<int>(info.index);
#if !defined(NDEBUG)
// Indexes must be unique, if there's a conflict check the `attributes` array in the shader
vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); });
instanceAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); });
uniformBlocks.visit([&](const gfx::UniformBlock& block) { assert(block.getIndex() != index); });
#endif
if (const auto& block_ = uniformBlocks.set(info.id, index, info.size)) {

View File

@@ -22,6 +22,13 @@
#include <mbgl/util/containers.hpp>
#include <mbgl/gfx/fill_generator.hpp>
#include <mbgl/shaders/custom_drawable_layer_ubo.hpp>
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/gfx/renderer_backend.hpp>
#include <mbgl/shaders/widevector_ubo.hpp>
#include <mbgl/util/projection.hpp>
#include <mbgl/util/mat4.hpp>
#include <mbgl/renderer/render_tile.hpp>
#include <cmath>
@@ -84,7 +91,6 @@ class LineDrawableTweaker : public gfx::DrawableTweaker {
public:
LineDrawableTweaker(const shaders::LinePropertiesUBO& properties)
: linePropertiesUBO(properties) {}
~LineDrawableTweaker() override = default;
void init(gfx::Drawable&) override {};
@@ -129,12 +135,74 @@ private:
shaders::LinePropertiesUBO linePropertiesUBO;
};
class WideVectorDrawableTweaker : public gfx::DrawableTweaker {
public:
WideVectorDrawableTweaker(const CustomDrawableLayerHost::Interface::LineOptions& options)
: options(options) {}
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();
mat4 tileMatrix;
parameters.state.matrixFor(/*out*/ tileMatrix, tileID);
if (const auto& origin{drawable.getOrigin()}; origin.has_value()) {
matrix::translate(tileMatrix, tileMatrix, origin->x, origin->y, 0);
}
mat4 projMatrix = parameters.transformParams.projMatrix;
const auto matrix = LayerTweaker::getTileMatrix(
tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false);
matf4 mvpMatrix, mvpMatrixDiff, mvMatrix, mvMatrixDiff, pMatrix, pMatrixDiff;
matrix::diffsplit(mvpMatrix, mvpMatrixDiff, matrix);
matrix::diffsplit(mvMatrix, mvMatrixDiff, tileMatrix);
matrix::diffsplit(pMatrix, pMatrixDiff, projMatrix);
const auto renderableSize = parameters.backend.getDefaultRenderable().getSize();
shaders::WideVectorUniformsUBO uniform{
/*mvpMatrix */ mvpMatrix,
/*mvpMatrixDiff */ mvpMatrixDiff,
/*mvMatrix */ mvMatrix,
/*mvMatrixDiff */ mvMatrixDiff,
/*pMatrix */ pMatrix,
/*pMatrixDiff */ pMatrixDiff,
/*frameSize */ {(float)renderableSize.width, (float)renderableSize.height}};
shaders::WideVectorUniformWideVecUBO wideVec{
/*color */ options.color,
/*w2 */ options.width,
/*offset */ options.offset,
/*edge */ 0.0f, // TODO: MLN does not provide a value. Analyze impact.
/*texRepeat */ 0.0f, // N/A
/*texOffset */ {}, // N/A
/*miterLimit */ options.geometry.miterLimit,
/*join */ static_cast<int32_t>(options.geometry.joinType),
/*cap */ static_cast<int32_t>(options.geometry.beginCap), // TODO: MLN option for endCap to be
// implemented in the shader!
/*hasExp */ false, // N/A
/*interClipLimit*/ 0.0f // N/A
};
auto& uniforms = drawable.mutableUniformBuffers();
uniforms.createOrUpdate(idWideVectorUniformsUBO, &uniform, parameters.context);
uniforms.createOrUpdate(idWideVectorUniformWideVecUBO, &wideVec, parameters.context);
};
private:
CustomDrawableLayerHost::Interface::LineOptions options;
};
class FillDrawableTweaker : public gfx::DrawableTweaker {
public:
FillDrawableTweaker(const Color& color_, float opacity_)
: color(color_),
opacity(opacity_) {}
~FillDrawableTweaker() override = default;
void init(gfx::Drawable&) override {};
@@ -181,7 +249,6 @@ class SymbolDrawableTweaker : public gfx::DrawableTweaker {
public:
SymbolDrawableTweaker(const CustomDrawableLayerHost::Interface::SymbolOptions& options_)
: options(options_) {}
~SymbolDrawableTweaker() override = default;
void init(gfx::Drawable&) override {};
@@ -261,6 +328,7 @@ std::size_t CustomDrawableLayerHost::Interface::getDrawableCount() const {
}
void CustomDrawableLayerHost::Interface::setTileID(OverscaledTileID tileID_) {
if (tileID != tileID_) finish();
tileID = tileID_;
}
@@ -279,27 +347,65 @@ void CustomDrawableLayerHost::Interface::setSymbolOptions(const SymbolOptions& o
symbolOptions = options;
}
void CustomDrawableLayerHost::Interface::addPolyline(const GeometryCoordinates& coordinates) {
if (!lineShader) lineShader = lineShaderDefault();
assert(lineShader);
if (!builder || builder->getShader() != lineShader) {
bool CustomDrawableLayerHost::Interface::updateBuilder(BuilderType type,
const std::string& name,
gfx::ShaderPtr shader) {
if (!shader) return false;
if (type != builderType || !builder || builder->getShader() != shader) {
finish();
builder = createBuilder("lines", lineShader);
builder = createBuilder(name, shader);
builderType = type;
}
assert(builder);
assert(builder->getShader() == lineShader);
builder->addPolyline(coordinates, lineOptions.geometry);
assert(builder->getShader() == shader);
return true;
};
bool CustomDrawableLayerHost::Interface::addPolyline(const LineString<double>& coordinates) {
switch (lineOptions.shaderType) {
case LineShaderType::Classic: {
// TODO: build classic polyline with Geo coordinates
return false;
} break;
case LineShaderType::MetalWideVector: {
// build wide vector polyline with Geo coordinates
if (!updateBuilder(BuilderType::LineWideVector, "custom-lines-widevector", lineShaderWideVector()))
return false;
// geographic coordinates require tile {0, 0, 0}
setTileID({0, 0, 0});
builder->addWideVectorPolylineGlobal(coordinates, lineOptions.geometry);
} break;
}
void CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geometry) {
if (!fillShader) fillShader = fillShaderDefault();
assert(fillShader);
if (!builder || builder->getShader() != fillShader) {
finish();
builder = createBuilder("fill", fillShader);
return true;
}
assert(builder);
assert(builder->getShader() == fillShader);
bool CustomDrawableLayerHost::Interface::addPolyline(const GeometryCoordinates& coordinates) {
switch (lineOptions.shaderType) {
case LineShaderType::Classic: {
// build classic polyline with Tile coordinates
if (!updateBuilder(BuilderType::LineClassic, "custom-lines", lineShaderDefault())) return false;
builder->addPolyline(coordinates, lineOptions.geometry);
} break;
case LineShaderType::MetalWideVector: {
// build wide vector polyline
if (!updateBuilder(BuilderType::LineWideVector, "custom-lines-widevector", lineShaderWideVector()))
return false;
builder->addWideVectorPolylineLocal(coordinates, lineOptions.geometry);
} break;
}
return true;
}
bool CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geometry) {
// build fill
if (!updateBuilder(BuilderType::Fill, "custom-fill", fillShaderDefault())) return false;
// provision buffers for fill vertices, indexes and segments
using VertexVector = gfx::VertexVector<FillLayoutVertex>;
@@ -330,17 +436,13 @@ void CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geome
// flush current builder drawable
builder->flush(context);
return true;
}
void CustomDrawableLayerHost::Interface::addSymbol(const GeometryCoordinate& point) {
if (!symbolShader) symbolShader = symbolShaderDefault();
assert(symbolShader);
if (!builder || builder->getShader() != symbolShader) {
finish();
builder = createBuilder("symbol", symbolShader);
}
assert(builder);
assert(builder->getShader() == symbolShader);
bool CustomDrawableLayerHost::Interface::addSymbol(const GeometryCoordinate& point) {
// build symbol
if (!updateBuilder(BuilderType::Symbol, "custom-symbol", symbolShaderDefault())) return false;
// temporary: buffers
struct CustomSymbolIcon {
@@ -403,11 +505,16 @@ void CustomDrawableLayerHost::Interface::addSymbol(const GeometryCoordinate& poi
// flush current builder drawable
builder->flush(context);
return true;
}
void CustomDrawableLayerHost::Interface::finish() {
if (builder && !builder->empty()) {
// finish
// flush current builder drawable
builder->flush(context);
// finish function
const auto finish_ = [this](gfx::DrawableTweakerPtr tweaker) {
builder->flush(context);
for (auto& drawable : builder->clearDrawables()) {
@@ -422,8 +529,10 @@ void CustomDrawableLayerHost::Interface::finish() {
}
};
if (builder->getShader() == lineShader) {
// finish building lines
// what were we building?
switch (builderType) {
case BuilderType::LineClassic: {
// finish building classic lines
// create line tweaker
const shaders::LinePropertiesUBO linePropertiesUBO{lineOptions.color,
@@ -439,7 +548,17 @@ void CustomDrawableLayerHost::Interface::finish() {
// finish drawables
finish_(tweaker);
} else if (builder->getShader() == fillShader) {
} break;
case BuilderType::LineWideVector: {
// finish building wide vector lines
// create line tweaker
auto tweaker = std::make_shared<WideVectorDrawableTweaker>(lineOptions);
// finish drawables
finish_(tweaker);
} break;
case BuilderType::Fill: {
// finish building fills
// create fill tweaker
@@ -447,17 +566,21 @@ void CustomDrawableLayerHost::Interface::finish() {
// finish drawables
finish_(tweaker);
} else if (builder->getShader() == symbolShader) {
} break;
case BuilderType::Symbol:
// finish building symbols
// finish drawables
finish_(nullptr);
break;
default:
break;
}
}
}
gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const {
gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("LineShader");
assert(shaderGroup);
if (!shaderGroup) return gfx::ShaderPtr();
const StringIDSetsPair propertiesAsUniforms{{"a_color", "a_blur", "a_opacity", "a_gapwidth", "a_offset", "a_width"},
{idLineColorVertexAttribute,
@@ -470,6 +593,13 @@ gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const {
return shaderGroup->getOrCreateShader(context, propertiesAsUniforms);
}
gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderWideVector() const {
gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("WideVectorShader");
if (!shaderGroup) return gfx::ShaderPtr();
return shaderGroup->getOrCreateShader(context, {});
}
gfx::ShaderPtr CustomDrawableLayerHost::Interface::fillShaderDefault() const {
gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("FillShader");

View File

@@ -407,6 +407,13 @@ void transformMat4(vec4& out, const vec4& a, const mat4& m) {
out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
}
void diffsplit(matf4& outValue, matf4& outDiff, const mat4& source) {
for (auto i = 0; i < 16; ++i) {
outValue[i] = static_cast<float>(source[i]);
outDiff[i] = source[i] - outValue[i];
}
}
} // namespace matrix
} // namespace mbgl

View File

@@ -53,5 +53,6 @@ void multiply(mat4& out, const mat4& a, const mat4& b);
void transformMat4(vec4& out, const vec4& a, const mat4& m);
void diffsplit(matf4& outValue, matf4& outDiff, const mat4& source);
} // namespace matrix
} // namespace mbgl

View File

@@ -38,48 +38,66 @@ public:
constexpr auto numLines = 6;
Interface::LineOptions options[numLines]{
{/*color=*/Color::red(),
{
/*geometry=*/{},
/*blur=*/0.0f,
/*opacity=*/1.0f,
/*gapWidth=*/0.0f,
/*offset=*/0.0f,
/*width=*/8.0f,
{}},
{/*color=*/Color::blue(),
/*shaderType*/ {},
/*color=*/Color::red(),
},
{
/*geometry=*/{},
/*blur=*/4.0f,
/*opacity=*/1.0f,
/*gapWidth=*/2.0f,
/*offset=*/-1.0f,
/*width=*/4.0f,
{}},
{/*color=*/Color(1.f, 0.5f, 0, 0.5f),
{},
/*color=*/Color::blue(),
},
{
/*geometry=*/{},
/*blur=*/16.0f,
/*opacity=*/1.0f,
/*gapWidth=*/1.0f,
/*offset=*/2.0f,
/*width=*/16.0f,
{}},
{/*color=*/Color(1.f, 1.f, 0, 0.3f),
{},
/*color=*/Color(1.f, 0.5f, 0, 0.5f),
},
{
/*geometry=*/{},
/*blur=*/2.0f,
/*opacity=*/1.0f,
/*gapWidth=*/1.0f,
/*offset=*/-2.0f,
/*width=*/2.0f,
{}},
{/*color=*/Color::black(),
{},
/*color=*/Color(1.f, 1.f, 0, 0.3f),
},
{
/*geometry=*/{},
/*blur=*/0.5f,
/*opacity=*/0.5f,
/*gapWidth=*/1.0f,
/*offset=*/0.5f,
/*width=*/0.5f,
{}},
{/*color=*/Color(1.f, 0, 1.f, 0.2f),
{},
/*color=*/Color::black(),
},
{
/*geometry=*/{},
/*blur=*/24.0f,
/*opacity=*/0.5f,
/*gapWidth=*/1.0f,
/*offset=*/-5.0f,
/*width=*/24.0f,
{}},
{},
/*color=*/Color(1.f, 0, 1.f, 0.2f),
},
};
for (auto& opt : options) {
opt.geometry.beginCap = style::LineCapType::Round;