diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bba574fe..eadad1876 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ endif() if(MLN_DRAWABLE_RENDERER) list(APPEND INCLUDE_FILES + ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/context.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/drawable.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/drawable_data.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/gfx/drawable_impl.hpp @@ -465,7 +466,6 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/attribute.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/color_mode.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/command_encoder.hpp - ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/context.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/cull_face_mode.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/debug_group.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/depth_mode.hpp @@ -1142,6 +1142,7 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/line_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/custom_drawable_layer_ubo.hpp ${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 @@ -1211,6 +1212,7 @@ if(MBGL_WITH_METAL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/common.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/collision_box.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/collision_circle.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/custom_symbol_icon.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/debug.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill_extrusion.hpp @@ -1256,6 +1258,7 @@ if(MBGL_WITH_METAL) ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/collision_box.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/collision_circle.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/clipping_mask.cpp + ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/custom_symbol_icon.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/debug.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/fill.cpp ${PROJECT_SOURCE_DIR}src/mbgl/shaders/mtl/fill_extrusion.cpp diff --git a/bazel/core.bzl b/bazel/core.bzl index b09873d25..6c22564d9 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -67,6 +67,7 @@ MLN_GENERATED_OPENGL_SHADER_HEADERS = [ "include/mbgl/shaders/gl/drawable_circle.hpp", "include/mbgl/shaders/gl/drawable_collision_box.hpp", "include/mbgl/shaders/gl/drawable_collision_circle.hpp", + "include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp", "include/mbgl/shaders/gl/drawable_debug.hpp", "include/mbgl/shaders/gl/drawable_fill.hpp", "include/mbgl/shaders/gl/drawable_fill_outline.hpp", @@ -143,7 +144,6 @@ MLN_CORE_SOURCE = [ "include/mbgl/gfx/backend.hpp", "src/mbgl/gfx/color_mode.hpp", "src/mbgl/gfx/command_encoder.hpp", - "src/mbgl/gfx/context.hpp", "src/mbgl/gfx/cull_face_mode.hpp", "src/mbgl/gfx/debug_group.hpp", "src/mbgl/gfx/depth_mode.hpp", @@ -648,6 +648,7 @@ MLN_CORE_SOURCE = [ ] MLN_CORE_HEADERS = [ + "include/mbgl/gfx/context.hpp", "include/mbgl/actor/actor.hpp", "include/mbgl/actor/actor_ref.hpp", "include/mbgl/actor/aspiring_actor.hpp", @@ -980,6 +981,7 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/background_layer_ubo.hpp", "include/mbgl/shaders/circle_layer_ubo.hpp", "include/mbgl/shaders/collision_layer_ubo.hpp", + "include/mbgl/shaders/custom_drawable_layer_ubo.hpp", "include/mbgl/shaders/debug_layer_ubo.hpp", "include/mbgl/shaders/fill_layer_ubo.hpp", "include/mbgl/shaders/fill_extrusion_layer_ubo.hpp", @@ -1054,6 +1056,7 @@ MLN_DRAWABLES_MTL_SOURCE = [ "src/mbgl/shaders/mtl/collision_box.cpp", "src/mbgl/shaders/mtl/collision_circle.cpp", "src/mbgl/shaders/mtl/clipping_mask.cpp", + "src/mbgl/shaders/mtl/custom_symbol_icon.cpp", "src/mbgl/shaders/mtl/debug.cpp", "src/mbgl/shaders/mtl/fill.cpp", "src/mbgl/shaders/mtl/fill_extrusion.cpp", @@ -1098,6 +1101,7 @@ MLN_DRAWABLES_MTL_HEADERS = [ "include/mbgl/shaders/mtl/collision_box.hpp", "include/mbgl/shaders/mtl/collision_circle.hpp", "include/mbgl/shaders/mtl/common.hpp", + "include/mbgl/shaders/mtl/custom_symbol_icon.hpp", "include/mbgl/shaders/mtl/debug.hpp", "include/mbgl/shaders/mtl/fill.hpp", "include/mbgl/shaders/mtl/fill_extrusion.hpp", diff --git a/src/mbgl/gfx/context.hpp b/include/mbgl/gfx/context.hpp similarity index 100% rename from src/mbgl/gfx/context.hpp rename to include/mbgl/gfx/context.hpp diff --git a/include/mbgl/shaders/custom_drawable_layer_ubo.hpp b/include/mbgl/shaders/custom_drawable_layer_ubo.hpp new file mode 100644 index 000000000..36259ea81 --- /dev/null +++ b/include/mbgl/shaders/custom_drawable_layer_ubo.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +namespace mbgl { +namespace shaders { + +/// Custom Symbol Icon matrix +struct alignas(16) CustomSymbolIconDrawableUBO { + /* 0 */ std::array matrix; + /* 64 */ +}; +static_assert(sizeof(CustomSymbolIconDrawableUBO) == 4 * 16); + +/// Custom Symbol Icon Parameters +struct alignas(16) CustomSymbolIconParametersUBO { + /* 0 */ std::array extrude_scale; + /* 8 */ std::array anchor; + /* 16 */ float angle_degrees; + /* 20 */ uint32_t scale_with_map; + /* 24 */ uint32_t pitch_with_map; + /* 28 */ float camera_to_center_distance; + /* 32 */ float aspect_ratio; + /* 36 */ float pad0, pad1, pad2; + /* 48 */ +}; +static_assert(sizeof(CustomSymbolIconParametersUBO) == 3 * 16); + +enum { + idCustomSymbolIconDrawableUBO, + idCustomSymbolIconParametersUBO, + customDrawableUBOCount +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp b/include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp new file mode 100644 index 000000000..77b4a76b1 --- /dev/null +++ b/include/mbgl/shaders/gl/drawable_custom_symbol_icon.hpp @@ -0,0 +1,92 @@ +// Generated code, do not modify this file! +#pragma once +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "CustomSymbolIconShader"; + static constexpr const char* vertex = R"(layout(location = 0) in vec2 a_pos; +layout(location = 1) in vec2 a_tex; + +layout(std140) uniform CustomSymbolIconDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform CustomSymbolIconParametersUBO { + highp vec2 u_extrude_scale; + highp vec2 u_anchor; + highp float u_angle_degrees; + bool u_scale_with_map; + bool u_pitch_with_map; + highp float u_camera_to_center_distance; + highp float u_aspect_ratio; + highp float pad0, pad1, pad3; +}; + +out vec2 v_tex; + +vec2 rotateVec2(vec2 v, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + return vec2(v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA); +} + +vec2 ellipseRotateVec2(vec2 v, float angle, float radiusRatio /* A/B */) { + float cosA = cos(angle); + float sinA = sin(angle); + float invRatio = 1.0 / radiusRatio; + return vec2(v.x * cosA - radiusRatio * v.y * sinA, invRatio * v.x * sinA + v.y * cosA); +} + +void main() { + // decode the extrusion vector (-1, -1) to (1, 1) + vec2 extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0); + + // make anchor relative to (0.5, 0.5) and corners in range (-1, -1) to (1, 1) + vec2 anchor = (u_anchor - vec2(0.5, 0.5)) * 2.0; + + // decode center + vec2 center = floor(a_pos * 0.5); + + // rotate extrusion around anchor + float angle = radians(-u_angle_degrees); + vec2 corner = extrude - anchor; + + // compute + if (u_pitch_with_map) { + if (u_scale_with_map) { + corner *= u_extrude_scale; + } else { + vec4 projected_center = u_matrix * vec4(center, 0, 1); + corner *= u_extrude_scale * (projected_center.w / u_camera_to_center_distance); + } + corner = center + rotateVec2(corner, angle); + gl_Position = u_matrix * vec4(corner, 0, 1); + } else { + gl_Position = u_matrix * vec4(center, 0, 1); + if (u_scale_with_map) { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * u_camera_to_center_distance, angle, u_aspect_ratio); + } else { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * gl_Position.w, angle, u_aspect_ratio); + } + } + + // texture coordinates + v_tex = a_tex; +} +)"; + static constexpr const char* fragment = R"(uniform sampler2D u_texture; + +in vec2 v_tex; + +void main() { + fragColor = texture(u_texture, v_tex); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/gl/shader_info.hpp b/include/mbgl/shaders/gl/shader_info.hpp index 7baba27c9..0ce68f3b6 100644 --- a/include/mbgl/shaders/gl/shader_info.hpp +++ b/include/mbgl/shaders/gl/shader_info.hpp @@ -54,6 +54,12 @@ struct ShaderInfo { static const std::vector textures; }; +template <> +struct ShaderInfo { + static const std::vector uniformBlocks; + static const std::vector textures; +}; + template <> struct ShaderInfo { static const std::vector uniformBlocks; diff --git a/include/mbgl/shaders/mtl/custom_symbol_icon.hpp b/include/mbgl/shaders/mtl/custom_symbol_icon.hpp new file mode 100644 index 000000000..229a7b5f2 --- /dev/null +++ b/include/mbgl/shaders/mtl/custom_symbol_icon.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr auto name = "CustomSymbolIconShader"; + static constexpr auto vertexMainFunction = "vertexMain"; + static constexpr auto fragmentMainFunction = "fragmentMain"; + + static const std::array attributes; + static const std::array uniforms; + static const std::array textures; + + static constexpr auto source = R"( +struct alignas(16) CustomSymbolIconDrawableUBO { + float4x4 matrix; +}; + +struct alignas(16) CustomSymbolIconParametersUBO { + float2 extrude_scale; + float2 anchor; + float angle_degrees; + int scale_with_map; + int pitch_with_map; + float camera_to_center_distance; + float aspect_ratio; + float pad0, pad1, pad3; +}; + +struct VertexStage { + float2 a_pos [[attribute(0)]]; + float2 a_tex [[attribute(1)]]; +}; + +struct FragmentStage { + float4 position [[position, invariant]]; + half2 tex; +}; + +float2 rotateVec2(float2 v, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + return float2(v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA); +} + +float2 ellipseRotateVec2(float2 v, float angle, float radiusRatio /* A/B */) { + float cosA = cos(angle); + float sinA = sin(angle); + float invRatio = 1.0 / radiusRatio; + return float2(v.x * cosA - radiusRatio * v.y * sinA, invRatio * v.x * sinA + v.y * cosA); +} + +FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], + device const CustomSymbolIconDrawableUBO& drawable [[buffer(2)]], + device const CustomSymbolIconParametersUBO& parameters [[buffer(3)]]) { + + const float2 extrude = glMod(float2(vertx.a_pos), 2.0) * 2.0 - 1.0; + const float2 anchor = (parameters.anchor - float2(0.5, 0.5)) * 2.0; + const float2 center = floor(float2(vertx.a_pos) * 0.5); + const float angle = radians(-parameters.angle_degrees); + float2 corner = extrude - anchor; + + float4 position; + if (parameters.pitch_with_map) { + if (parameters.scale_with_map) { + corner *= parameters.extrude_scale; + } else { + float4 projected_center = drawable.matrix * float4(center, 0, 1); + corner *= parameters.extrude_scale * (projected_center.w / parameters.camera_to_center_distance); + } + corner = center + rotateVec2(corner, angle); + position = drawable.matrix * float4(corner, 0, 1); + } else { + position = drawable.matrix * float4(center, 0, 1); + const float factor = parameters.scale_with_map ? parameters.camera_to_center_distance : position.w; + position.xy += ellipseRotateVec2(corner * parameters.extrude_scale * factor, angle, parameters.aspect_ratio); + } + + return { + .position = position, + .tex = half2(vertx.a_tex) + }; +} + +half4 fragment fragmentMain(FragmentStage in [[stage_in]], + texture2d image [[texture(0)]], + sampler image_sampler [[sampler(0)]]) { +#if defined(OVERDRAW_INSPECTOR) + return half4(1.0); +#endif + + return half4(image.sample(image_sampler, float2(in.tex))); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/shader_defines.hpp b/include/mbgl/shaders/shader_defines.hpp index 9ee9fc0a9..36e73c877 100644 --- a/include/mbgl/shaders/shader_defines.hpp +++ b/include/mbgl/shaders/shader_defines.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ namespace shaders { static constexpr auto maxUBOCountPerShader = std::max({static_cast(backgroundUBOCount), static_cast(circleUBOCount), static_cast(collisionUBOCount), + static_cast(customDrawableUBOCount), static_cast(debugUBOCount), static_cast(fillUBOCount), static_cast(fillOutlineUBOCount), @@ -53,6 +55,11 @@ enum { collisionTextureCount }; +enum { + idCustomSymbolIconTexture, + customSymbolIconTextureCount +}; + enum { idDebugOverlayTexture, debugTextureCount @@ -99,6 +106,7 @@ enum { static constexpr auto maxTextureCountPerShader = std::max({static_cast(backgroundTextureCount), static_cast(circleTextureCount), static_cast(collisionTextureCount), + static_cast(customSymbolIconTextureCount), static_cast(debugTextureCount), static_cast(fillTextureCount), static_cast(fillExtrusionTextureCount), diff --git a/include/mbgl/shaders/shader_manifest.hpp b/include/mbgl/shaders/shader_manifest.hpp index d769192e2..44882c401 100644 --- a/include/mbgl/shaders/shader_manifest.hpp +++ b/include/mbgl/shaders/shader_manifest.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/include/mbgl/shaders/shader_source.hpp b/include/mbgl/shaders/shader_source.hpp index 8f44c1eec..7facb567c 100644 --- a/include/mbgl/shaders/shader_source.hpp +++ b/include/mbgl/shaders/shader_source.hpp @@ -34,6 +34,7 @@ enum class BuiltIn { SymbolIconShader, SymbolSDFIconShader, SymbolTextAndIconShader, + CustomSymbolIconShader, Prelude, BackgroundProgram, BackgroundPatternProgram, diff --git a/include/mbgl/style/layers/custom_drawable_layer.hpp b/include/mbgl/style/layers/custom_drawable_layer.hpp index 7deb77057..c2ffdea1e 100644 --- a/include/mbgl/style/layers/custom_drawable_layer.hpp +++ b/include/mbgl/style/layers/custom_drawable_layer.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -46,6 +48,16 @@ public: float opacity = 1.f; }; + struct SymbolOptions { + Size size; + gfx::Texture2DPtr texture; + std::array anchor{0.5f, 0.5f}; + std::array, 2> textureCoordinates{{{0, 0}, {1, 1}}}; + float angleDegrees{.0f}; + bool scaleWithMap{false}; + bool pitchWithMap{false}; + }; + public: /// @brief Construct a new Interface object (internal core use only) Interface(RenderLayer& layer, @@ -84,6 +96,13 @@ public: */ void setFillOptions(const FillOptions& options); + /** + * @brief Set the Symbol options + * + * @param options + */ + void setSymbolOptions(const SymbolOptions& options); + /** * @brief Add a polyline * @@ -92,8 +111,20 @@ public: */ void addPolyline(const GeometryCoordinates& coordinates); + /** + * @brief Add a multipolygon area fill + * + * @param geometry a collection of rings with optional holes + */ void addFill(const GeometryCollection& geometry); + /** + * @brief Add a symbol + * + * @param point + */ + void addSymbol(const GeometryCoordinate& point); + /** * @brief Finish the current drawable building session * @@ -113,15 +144,20 @@ public: private: gfx::ShaderPtr lineShaderDefault() const; gfx::ShaderPtr fillShaderDefault() const; + gfx::ShaderPtr symbolShaderDefault() const; std::unique_ptr createBuilder(const std::string& name, gfx::ShaderPtr shader) const; - gfx::ShaderPtr lineShader; - gfx::ShaderPtr fillShader; std::unique_ptr builder; std::optional tileID; + + gfx::ShaderPtr lineShader; + gfx::ShaderPtr fillShader; + gfx::ShaderPtr symbolShader; + LineOptions lineOptions; FillOptions fillOptions; + SymbolOptions symbolOptions; }; class CustomDrawableLayer final : public Layer { diff --git a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm index dce804d73..49f0e3412 100644 --- a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm +++ b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm @@ -2,6 +2,9 @@ #import "MLNStyleLayer.h" #import "MLNCustomDrawableStyleLayer.h" +#import + +#include #include #include #include @@ -81,7 +84,7 @@ public: interface.addPolyline(polyline); } } - + // add fill polygon { using namespace mbgl; @@ -111,6 +114,36 @@ public: // add fill interface.addFill(geometry); } + + // add symbol + { + using namespace mbgl; + GeometryCoordinate position {static_cast(extent* 0.5f), static_cast(extent* 0.5f)}; + + // load image + UIImage *assetImage = [UIImage imageNamed:@"pin"]; + assert(assetImage.CGImage != NULL); + std::shared_ptr image = std::make_shared(MLNPremultipliedImageFromCGImage(assetImage.CGImage)); + + // set symbol options + Interface::SymbolOptions options; + options.texture = interface.context.createTexture2D(); + options.texture->setImage(image); + options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + options.textureCoordinates = {{{0.0f, 0.08f}, {1.0f, 0.9f}}}; + const float xspan = options.textureCoordinates[1][0] - options.textureCoordinates[0][0]; + const float yspan = options.textureCoordinates[1][1] - options.textureCoordinates[0][1]; + assert(xspan > 0.0f && yspan > 0.0f); + options.size = {static_cast(image->size.width * xspan), static_cast(image->size.height * yspan)}; + options.anchor = {0.5f, 0.95f}; + options.angleDegrees = 45.0f; + options.scaleWithMap = true; + options.pitchWithMap = false; + interface.setSymbolOptions(options); + + // add symbol + interface.addSymbol(position); + } // finish interface.finish(); diff --git a/platform/ios/app/Assets.xcassets/Contents.json b/platform/ios/app/Assets.xcassets/Contents.json index da4a164c9..73c00596a 100644 --- a/platform/ios/app/Assets.xcassets/Contents.json +++ b/platform/ios/app/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/platform/ios/app/Assets.xcassets/pin.imageset/Contents.json b/platform/ios/app/Assets.xcassets/pin.imageset/Contents.json new file mode 100644 index 000000000..102c27746 --- /dev/null +++ b/platform/ios/app/Assets.xcassets/pin.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pin1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/app/Assets.xcassets/pin.imageset/pin1.png b/platform/ios/app/Assets.xcassets/pin.imageset/pin1.png new file mode 100644 index 000000000..c4cc5d2f5 Binary files /dev/null and b/platform/ios/app/Assets.xcassets/pin.imageset/pin1.png differ diff --git a/shaders/drawable.custom.symbol_icon.fragment.glsl b/shaders/drawable.custom.symbol_icon.fragment.glsl new file mode 100644 index 000000000..0f8a298e3 --- /dev/null +++ b/shaders/drawable.custom.symbol_icon.fragment.glsl @@ -0,0 +1,7 @@ +uniform sampler2D u_texture; + +in vec2 v_tex; + +void main() { + fragColor = texture(u_texture, v_tex); +} diff --git a/shaders/drawable.custom.symbol_icon.vertex.glsl b/shaders/drawable.custom.symbol_icon.vertex.glsl new file mode 100644 index 000000000..b912f515e --- /dev/null +++ b/shaders/drawable.custom.symbol_icon.vertex.glsl @@ -0,0 +1,69 @@ +layout(location = 0) in vec2 a_pos; +layout(location = 1) in vec2 a_tex; + +layout(std140) uniform CustomSymbolIconDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform CustomSymbolIconParametersUBO { + highp vec2 u_extrude_scale; + highp vec2 u_anchor; + highp float u_angle_degrees; + bool u_scale_with_map; + bool u_pitch_with_map; + highp float u_camera_to_center_distance; + highp float u_aspect_ratio; + highp float pad0, pad1, pad3; +}; + +out vec2 v_tex; + +vec2 rotateVec2(vec2 v, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + return vec2(v.x * cosA - v.y * sinA, v.x * sinA + v.y * cosA); +} + +vec2 ellipseRotateVec2(vec2 v, float angle, float radiusRatio /* A/B */) { + float cosA = cos(angle); + float sinA = sin(angle); + float invRatio = 1.0 / radiusRatio; + return vec2(v.x * cosA - radiusRatio * v.y * sinA, invRatio * v.x * sinA + v.y * cosA); +} + +void main() { + // decode the extrusion vector (-1, -1) to (1, 1) + vec2 extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0); + + // make anchor relative to (0.5, 0.5) and corners in range (-1, -1) to (1, 1) + vec2 anchor = (u_anchor - vec2(0.5, 0.5)) * 2.0; + + // decode center + vec2 center = floor(a_pos * 0.5); + + // rotate extrusion around anchor + float angle = radians(-u_angle_degrees); + vec2 corner = extrude - anchor; + + // compute + if (u_pitch_with_map) { + if (u_scale_with_map) { + corner *= u_extrude_scale; + } else { + vec4 projected_center = u_matrix * vec4(center, 0, 1); + corner *= u_extrude_scale * (projected_center.w / u_camera_to_center_distance); + } + corner = center + rotateVec2(corner, angle); + gl_Position = u_matrix * vec4(corner, 0, 1); + } else { + gl_Position = u_matrix * vec4(center, 0, 1); + if (u_scale_with_map) { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * u_camera_to_center_distance, angle, u_aspect_ratio); + } else { + gl_Position.xy += ellipseRotateVec2(corner * u_extrude_scale * gl_Position.w, angle, u_aspect_ratio); + } + } + + // texture coordinates + v_tex = a_tex; +} diff --git a/shaders/manifest.json b/shaders/manifest.json index 331f3eac2..0280508b0 100644 --- a/shaders/manifest.json +++ b/shaders/manifest.json @@ -174,6 +174,13 @@ "glsl_frag": "drawable.symbol_text_and_icon.fragment.glsl", "uses_ubos": true }, + { + "name": "CustomSymbolIconShader", + "header": "drawable_custom_symbol_icon", + "glsl_vert": "drawable.custom.symbol_icon.vertex.glsl", + "glsl_frag": "drawable.custom.symbol_icon.fragment.glsl", + "uses_ubos": true + }, { "name": "Prelude", diff --git a/src/mbgl/gfx/index_vector.hpp b/src/mbgl/gfx/index_vector.hpp index b42b63321..4f67ab053 100644 --- a/src/mbgl/gfx/index_vector.hpp +++ b/src/mbgl/gfx/index_vector.hpp @@ -108,7 +108,7 @@ public: template void emplace_back(Args&&... args) { - static_assert(sizeof...(args) == groupSize, "wrong buffer element count"); + static_assert(sizeof...(args) % groupSize == 0, "wrong buffer element count"); assert(!released); util::ignore({(v.emplace_back(std::forward(args)), 0)...}); } diff --git a/src/mbgl/gfx/vertex_vector.hpp b/src/mbgl/gfx/vertex_vector.hpp index 3be534149..c46069a37 100644 --- a/src/mbgl/gfx/vertex_vector.hpp +++ b/src/mbgl/gfx/vertex_vector.hpp @@ -63,10 +63,10 @@ public: v(std::move(other.v)) {} ~VertexVector() override = default; - template - void emplace_back(Arg&& vertex) { + template + void emplace_back(Args&&... args) { assert(!released); - v.emplace_back(std::forward(vertex)); + util::ignore({(v.emplace_back(std::forward(args)), 0)...}); dirty = true; } diff --git a/src/mbgl/gl/renderer_backend.cpp b/src/mbgl/gl/renderer_backend.cpp index f84d00212..1e88e1684 100644 --- a/src/mbgl/gl/renderer_backend.cpp +++ b/src/mbgl/gl/renderer_backend.cpp @@ -98,6 +98,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar shaders::BuiltIn::CircleShader, shaders::BuiltIn::CollisionBoxShader, shaders::BuiltIn::CollisionCircleShader, + shaders::BuiltIn::CustomSymbolIconShader, shaders::BuiltIn::DebugShader, shaders::BuiltIn::FillShader, shaders::BuiltIn::FillOutlineShader, diff --git a/src/mbgl/mtl/renderer_backend.cpp b/src/mbgl/mtl/renderer_backend.cpp index e65bcf079..a7351175f 100644 --- a/src/mbgl/mtl/renderer_backend.cpp +++ b/src/mbgl/mtl/renderer_backend.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -129,6 +130,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar shaders::BuiltIn::ClippingMaskProgram, shaders::BuiltIn::CollisionBoxShader, shaders::BuiltIn::CollisionCircleShader, + shaders::BuiltIn::CustomSymbolIconShader, shaders::BuiltIn::DebugShader, shaders::BuiltIn::FillShader, shaders::BuiltIn::FillOutlineShader, diff --git a/src/mbgl/shaders/gl/shader_info.cpp b/src/mbgl/shaders/gl/shader_info.cpp index 637c26db1..704e91585 100644 --- a/src/mbgl/shaders/gl/shader_info.cpp +++ b/src/mbgl/shaders/gl/shader_info.cpp @@ -48,6 +48,15 @@ const std::vector ShaderInfo ShaderInfo::textures = {}; // Collision Circle +const std::vector + ShaderInfo::uniformBlocks = { + UniformBlockInfo{"CustomSymbolIconDrawableUBO", idCustomSymbolIconDrawableUBO}, + UniformBlockInfo{"CustomSymbolIconParametersUBO", idCustomSymbolIconParametersUBO}, +}; +const std::vector ShaderInfo::textures = { + TextureInfo{"u_texture", idSymbolImageTexture}, +}; + const std::vector ShaderInfo::uniformBlocks = { UniformBlockInfo{"CollisionCircleUBO", idCollisionUBO}, diff --git a/src/mbgl/shaders/mtl/custom_symbol_icon.cpp b/src/mbgl/shaders/mtl/custom_symbol_icon.cpp new file mode 100644 index 000000000..4c1afaf79 --- /dev/null +++ b/src/mbgl/shaders/mtl/custom_symbol_icon.cpp @@ -0,0 +1,22 @@ +#include + +namespace mbgl { +namespace shaders { + +const std::array + ShaderSource::attributes = { + // always attributes + AttributeInfo{0, gfx::AttributeDataType::Float2, "a_pos"}, + AttributeInfo{1, gfx::AttributeDataType::Float2, "a_tex"}, +}; +const std::array + ShaderSource::uniforms = { + UniformBlockInfo{2, true, false, sizeof(CustomSymbolIconDrawableUBO), idCustomSymbolIconDrawableUBO}, + UniformBlockInfo{3, true, false, sizeof(CustomSymbolIconParametersUBO), idCustomSymbolIconParametersUBO}, +}; +const std::array ShaderSource::textures = { + TextureInfo{0, idCustomSymbolIconTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/style/layers/custom_drawable_layer.cpp b/src/mbgl/style/layers/custom_drawable_layer.cpp index 9b8525ec0..8e3554326 100644 --- a/src/mbgl/style/layers/custom_drawable_layer.cpp +++ b/src/mbgl/style/layers/custom_drawable_layer.cpp @@ -21,8 +21,10 @@ #include #include #include - #include +#include + +#include namespace mbgl { @@ -176,6 +178,59 @@ private: float opacity; }; +class SymbolDrawableTweaker : public gfx::DrawableTweaker { +public: + SymbolDrawableTweaker(const CustomDrawableLayerHost::Interface::SymbolOptions& options_) + : options(options_) {} + ~SymbolDrawableTweaker() 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(); + mat4 tileMatrix; + parameters.state.matrixFor(/*out*/ tileMatrix, tileID); + + const auto matrix = LayerTweaker::getTileMatrix( + tileID, parameters, {{0, 0}}, style::TranslateAnchorType::Viewport, false, false, drawable, false); + + const shaders::CustomSymbolIconDrawableUBO drawableUBO{/*matrix = */ util::cast(matrix)}; + + const auto pixelsToTileUnits = tileID.pixelsToTileUnits( + 1.0f, options.scaleWithMap ? tileID.canonical.z : parameters.state.getZoom()); + const float factor = options.scaleWithMap + ? static_cast(std::pow(2.f, parameters.state.getZoom() - tileID.canonical.z)) + : 1.0f; + const auto extrudeScale = options.pitchWithMap ? std::array{pixelsToTileUnits, pixelsToTileUnits} + : std::array{parameters.pixelsToGLUnits[0] * factor, + parameters.pixelsToGLUnits[1] * factor}; + + const shaders::CustomSymbolIconParametersUBO parametersUBO{ + /*extrude_scale*/ {extrudeScale[0] * options.size.width, extrudeScale[1] * options.size.height}, + /*anchor*/ options.anchor, + /*angle_degrees*/ options.angleDegrees, + /*scale_with_map*/ options.scaleWithMap, + /*pitch_with_map*/ options.pitchWithMap, + /*camera_to_center_distance*/ parameters.state.getCameraToCenterDistance(), + /*aspect_ratio*/ parameters.pixelsToGLUnits[0] / parameters.pixelsToGLUnits[1], + 0, + 0, + 0}; + + // set UBOs + auto& uniforms = drawable.mutableUniformBuffers(); + uniforms.createOrUpdate(idCustomSymbolIconDrawableUBO, &drawableUBO, parameters.context); + uniforms.createOrUpdate(idCustomSymbolIconParametersUBO, ¶metersUBO, parameters.context); + }; + +private: + CustomDrawableLayerHost::Interface::SymbolOptions options; +}; + CustomDrawableLayerHost::Interface::Interface(RenderLayer& layer_, LayerGroupBasePtr& layerGroup_, gfx::ShaderRegistry& shaders_, @@ -220,10 +275,16 @@ void CustomDrawableLayerHost::Interface::setFillOptions(const FillOptions& optio fillOptions = options; } +void CustomDrawableLayerHost::Interface::setSymbolOptions(const SymbolOptions& options) { + finish(); + symbolOptions = options; +} + void CustomDrawableLayerHost::Interface::addPolyline(const GeometryCoordinates& coordinates) { if (!lineShader) lineShader = lineShaderDefault(); assert(lineShader); if (!builder || builder->getShader() != lineShader) { + finish(); builder = createBuilder("lines", lineShader); } assert(builder); @@ -235,6 +296,7 @@ void CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geome if (!fillShader) fillShader = fillShaderDefault(); assert(fillShader); if (!builder || builder->getShader() != fillShader) { + finish(); builder = createBuilder("fill", fillShader); } assert(builder); @@ -274,15 +336,94 @@ void CustomDrawableLayerHost::Interface::addFill(const GeometryCollection& geome builder->flush(context); } +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); + + // temporary: buffers + struct CustomSymbolIcon { + std::array a_pos; + std::array a_tex; + }; + + // vertices + using VertexVector = gfx::VertexVector; + const std::shared_ptr sharedVertices = std::make_shared(); + VertexVector& vertices = *sharedVertices; + + // encode center and extrude direction into vertices + for (int y = 0; y <= 1; ++y) { + for (int x = 0; x <= 1; ++x) { + vertices.emplace_back( + CustomSymbolIcon{{static_cast(point.x * 2 + x), static_cast(point.y * 2 + y)}, + {symbolOptions.textureCoordinates[x][0], symbolOptions.textureCoordinates[y][1]}}); + } + } + + // indexes + using TriangleIndexVector = gfx::IndexVector; + const std::shared_ptr sharedTriangles = std::make_shared(); + TriangleIndexVector& triangles = *sharedTriangles; + + triangles.emplace_back(0, 1, 2, 1, 2, 3); + + SegmentVector triangleSegments; + triangleSegments.emplace_back(Segment{0, 0, 4, 6}); + + // add to builder + static const StringIdentity idPositionAttribName = stringIndexer().get("a_pos"); + static const StringIdentity idTextureAttribName = stringIndexer().get("a_tex"); + builder->setVertexAttrNameId(idPositionAttribName); + + auto attrs = context.createVertexAttributeArray(); + if (const auto& attr = attrs->add(idPositionAttribName)) { + attr->setSharedRawData(sharedVertices, + offsetof(CustomSymbolIcon, a_pos), + /*vertexOffset=*/0, + sizeof(CustomSymbolIcon), + gfx::AttributeDataType::Float2); + } + if (const auto& attr = attrs->add(idTextureAttribName)) { + attr->setSharedRawData(sharedVertices, + offsetof(CustomSymbolIcon, a_tex), + /*vertexOffset=*/0, + sizeof(CustomSymbolIcon), + gfx::AttributeDataType::Float2); + } + builder->setVertexAttributes(std::move(attrs)); + builder->setRawVertices({}, vertices.elements(), gfx::AttributeDataType::Float2); + builder->setSegments(gfx::Triangles(), sharedTriangles, triangleSegments.data(), triangleSegments.size()); + + // texture + if (symbolOptions.texture) { + builder->setTexture(symbolOptions.texture, idCustomSymbolIconTexture); + } + + // create fill tweaker + auto tweaker = std::make_shared(symbolOptions); + builder->addTweaker(tweaker); + + // flush current builder drawable + builder->flush(context); +} + void CustomDrawableLayerHost::Interface::finish() { if (builder && !builder->empty()) { // finish - const auto finish_ = [this](auto& tweaker) { + const auto finish_ = [this](gfx::DrawableTweakerPtr tweaker) { builder->flush(context); for (auto& drawable : builder->clearDrawables()) { assert(tileID.has_value()); drawable->setTileID(tileID.value()); - drawable->addTweaker(tweaker); + if (tweaker) { + drawable->addTweaker(tweaker); + } TileLayerGroup* tileLayerGroup = static_cast(layerGroup.get()); tileLayerGroup->addDrawable(RenderPass::Translucent, tileID.value(), std::move(drawable)); @@ -314,12 +455,17 @@ void CustomDrawableLayerHost::Interface::finish() { // finish drawables finish_(tweaker); + } else if (builder->getShader() == symbolShader) { + // finish building symbols + + // finish drawables + finish_(nullptr); } } } gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const { - gfx::ShaderGroupPtr lineShaderGroup = shaders.getShaderGroup("LineShader"); + gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("LineShader"); const mbgl::unordered_set propertiesAsUniforms{ stringIndexer().get("a_color"), @@ -330,18 +476,22 @@ gfx::ShaderPtr CustomDrawableLayerHost::Interface::lineShaderDefault() const { stringIndexer().get("a_width"), }; - return lineShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + return shaderGroup->getOrCreateShader(context, propertiesAsUniforms); } gfx::ShaderPtr CustomDrawableLayerHost::Interface::fillShaderDefault() const { - gfx::ShaderGroupPtr fillShaderGroup = shaders.getShaderGroup("FillShader"); + gfx::ShaderGroupPtr shaderGroup = shaders.getShaderGroup("FillShader"); const mbgl::unordered_set propertiesAsUniforms{ stringIndexer().get("a_color"), stringIndexer().get("a_opacity"), }; - return fillShaderGroup->getOrCreateShader(context, propertiesAsUniforms); + return shaderGroup->getOrCreateShader(context, propertiesAsUniforms); +} + +gfx::ShaderPtr CustomDrawableLayerHost::Interface::symbolShaderDefault() const { + return context.getGenericShader(shaders, "CustomSymbolIconShader"); } std::unique_ptr CustomDrawableLayerHost::Interface::createBuilder(const std::string& name, diff --git a/test/api/custom_drawable_layer.test.cpp b/test/api/custom_drawable_layer.test.cpp index c9a2599af..e3c27f58c 100644 --- a/test/api/custom_drawable_layer.test.cpp +++ b/test/api/custom_drawable_layer.test.cpp @@ -14,11 +14,14 @@ #include #include +#include +#include +#include #include #include -class TestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { +class LineTestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { public: void initialize() override {} @@ -104,6 +107,24 @@ public: } } + // finish + interface.finish(); + } + + void deinitialize() override {} +}; + +class FillTestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { +public: + void initialize() override {} + + void update(Interface& interface) override { + // if we have built our drawable(s) already, either update or skip + if (interface.getDrawableCount()) return; + + // set tile + interface.setTileID({11, 327, 791}); + // add fill polygon { using namespace mbgl; @@ -141,7 +162,65 @@ public: void deinitialize() override {} }; -TEST(CustomDrawableLayer, Basic) { +class SymbolIconTestDrawableLayer : public mbgl::style::CustomDrawableLayerHost { +public: + void initialize() override {} + + void update(Interface& interface) override { + // if we have built our drawable(s) already, either update or skip + if (interface.getDrawableCount()) return; + + // set tile + interface.setTileID({11, 327, 791}); + + // add symbol icon + { + using namespace mbgl; + GeometryCoordinate position{static_cast(util::EXTENT * 0.5f), + static_cast(util::EXTENT * 0.5f)}; + + // load image + std::string imageData; // doh! + constexpr auto imagePath = "test/fixtures/custom_drawable_layer/symbol_icon/pin1.png"; + try { + imageData = util::read_file(imagePath); + } catch (std::exception& ex) { + using namespace std::string_literals; + Log::Error(Event::Setup, "Failed to load expected image "s + imagePath + ": " + ex.what()); + throw; + } + std::shared_ptr image = std::make_shared(decodeImage(imageData)); + + // set symbol options + Interface::SymbolOptions options; + options.texture = interface.context.createTexture2D(); + options.texture->setImage(image); + options.texture->setSamplerConfiguration( + {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); + options.textureCoordinates = {{{0.0f, 0.08f}, {1.0f, 0.9f}}}; + const float xspan = options.textureCoordinates[1][0] - options.textureCoordinates[0][0]; + const float yspan = options.textureCoordinates[1][1] - options.textureCoordinates[0][1]; + assert(xspan > 0.0f && yspan > 0.0f); + options.size = {static_cast(image->size.width * 0.2f * xspan), + static_cast(image->size.height * 0.2f * yspan)}; + options.anchor = {0.5f, 0.95f}; + options.angleDegrees = 45.0f; + options.scaleWithMap = false; + options.pitchWithMap = false; + interface.setSymbolOptions(options); + + // add symbol + interface.addSymbol(position); + } + + // finish + interface.finish(); + } + + void deinitialize() override {} +}; + +TEST(CustomDrawableLayer, Line) { using namespace mbgl; using namespace mbgl::style; @@ -154,21 +233,63 @@ TEST(CustomDrawableLayer, Basic) { ResourceOptions().withCachePath(":memory:").withAssetPath("test/fixtures/api/assets")); // load style - map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); + map.getStyle().loadJSON(util::read_file("test/fixtures/api/simple.json")); map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.4426032}).withZoom(10.0)); - // add fill layer - auto layer = std::make_unique("landcover", "mapbox"); - layer->setSourceLayer("landcover"); - layer->setFillColor(Color{1.0, 1.0, 0.0, 1.0}); - map.getStyle().addLayer(std::move(layer)); - // add custom drawable layer map.getStyle().addLayer( - std::make_unique("custom-drawable", std::make_unique())); + std::make_unique("custom-drawable", std::make_unique())); // render and test - test::checkImage("test/fixtures/custom_drawable_layer/basic", frontend.render(map).image, 0.000657, 0.1); + test::checkImage("test/fixtures/custom_drawable_layer/line", frontend.render(map).image, 0.000657, 0.1); +} + +TEST(CustomDrawableLayer, Fill) { + using namespace mbgl; + using namespace mbgl::style; + + 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/simple.json")); + map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.4426032}).withZoom(10.0)); + + // add custom drawable layer + map.getStyle().addLayer( + std::make_unique("custom-drawable", std::make_unique())); + + // render and test + test::checkImage("test/fixtures/custom_drawable_layer/fill", frontend.render(map).image, 0.000657, 0.1); +} + +TEST(CustomDrawableLayer, SymbolIcon) { + using namespace mbgl; + using namespace mbgl::style; + + 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/simple.json")); + map.jumpTo(CameraOptions().withCenter(LatLng{37.8, -122.4426032}).withZoom(10.0)); + + // add custom drawable layer + map.getStyle().addLayer( + std::make_unique("custom-drawable", std::make_unique())); + + // render and test + test::checkImage("test/fixtures/custom_drawable_layer/symbol_icon", frontend.render(map).image, 0.000657, 0.1); } #endif // MLN_DRAWABLE_RENDERER diff --git a/test/fixtures/api/simple.json b/test/fixtures/api/simple.json new file mode 100644 index 000000000..d88273816 --- /dev/null +++ b/test/fixtures/api/simple.json @@ -0,0 +1,25 @@ +{ + "version": 8, + "name": "Water", + "sources": { + "mapbox": { + "type": "vector", + "tiles": ["asset://streets/{z}-{x}-{y}.vector.pbf"] + } + }, + "layers": [{ + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "#3440eb" + } + }] +} diff --git a/test/fixtures/custom_drawable_layer/basic/expected.png b/test/fixtures/custom_drawable_layer/basic/expected.png deleted file mode 100644 index 10572d271..000000000 Binary files a/test/fixtures/custom_drawable_layer/basic/expected.png and /dev/null differ diff --git a/test/fixtures/custom_drawable_layer/fill/expected.png b/test/fixtures/custom_drawable_layer/fill/expected.png new file mode 100644 index 000000000..26a6fa1c2 Binary files /dev/null and b/test/fixtures/custom_drawable_layer/fill/expected.png differ diff --git a/test/fixtures/custom_drawable_layer/line/expected.png b/test/fixtures/custom_drawable_layer/line/expected.png new file mode 100644 index 000000000..217b3e170 Binary files /dev/null and b/test/fixtures/custom_drawable_layer/line/expected.png differ diff --git a/test/fixtures/custom_drawable_layer/symbol_icon/expected.png b/test/fixtures/custom_drawable_layer/symbol_icon/expected.png new file mode 100644 index 000000000..7e2cecc29 Binary files /dev/null and b/test/fixtures/custom_drawable_layer/symbol_icon/expected.png differ diff --git a/test/fixtures/custom_drawable_layer/symbol_icon/pin1.png b/test/fixtures/custom_drawable_layer/symbol_icon/pin1.png new file mode 100644 index 000000000..c4cc5d2f5 Binary files /dev/null and b/test/fixtures/custom_drawable_layer/symbol_icon/pin1.png differ