diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e624f3fa..00d201df9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -978,7 +978,6 @@ if(MLN_WITH_OPENGL) message(STATUS "Configuring GL-Native with OpenGL renderer backend") target_compile_definitions( mbgl-core - PRIVATE PRIVATE MLN_RENDER_BACKEND_OPENGL=1 PUBLIC "MLN_LEGACY_RENDERER=$" @@ -1115,6 +1114,7 @@ if(MLN_WITH_OPENGL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/background_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/circle_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/fill_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/fill_extrusion_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/raster_layer_ubo.hpp @@ -1171,7 +1171,10 @@ if(MBGL_WITH_METAL) ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/background.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/background_pattern.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/circle.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/clipping_mask.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/common.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill_extrusion.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill_extrusion_pattern.hpp ) list(APPEND SRC_FILES diff --git a/bazel/core.bzl b/bazel/core.bzl index c26bf12a4..14975cdfe 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -963,6 +963,7 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/background_layer_ubo.hpp", "include/mbgl/shaders/circle_layer_ubo.hpp", "include/mbgl/shaders/fill_layer_ubo.hpp", + "include/mbgl/shaders/fill_extrusion_layer_ubo.hpp", "include/mbgl/shaders/layer_ubo.hpp", "include/mbgl/shaders/line_layer_ubo.hpp", "include/mbgl/shaders/raster_layer_ubo.hpp", @@ -1038,7 +1039,10 @@ MLN_DRAWABLES_MTL_HEADERS = [ "include/mbgl/shaders/mtl/background.hpp", "include/mbgl/shaders/mtl/background_pattern.hpp", "include/mbgl/shaders/mtl/circle.hpp", + "include/mbgl/shaders/mtl/clipping_mask.hpp", "include/mbgl/shaders/mtl/common.hpp", + "include/mbgl/shaders/mtl/fill_extrusion.hpp", + "include/mbgl/shaders/mtl/fill_extrusion_pattern.hpp", "include/mbgl/shaders/mtl/line.hpp", "include/mbgl/shaders/mtl/fill.hpp", "include/mbgl/shaders/mtl/line_gradient.hpp", diff --git a/include/mbgl/gfx/uniform_block.hpp b/include/mbgl/gfx/uniform_block.hpp index e96941a04..f56917ee7 100644 --- a/include/mbgl/gfx/uniform_block.hpp +++ b/include/mbgl/gfx/uniform_block.hpp @@ -96,6 +96,15 @@ public: /// @brief Copy assignment operator UniformBlockArray& operator=(const UniformBlockArray&); + /// Do something with each block + void visit(const std::function& f) { + std::for_each(uniformBlockMap.begin(), uniformBlockMap.end(), [&](const auto& kv) { + if (kv.second) { + f(kv.first, *kv.second); + } + }); + } + protected: const std::unique_ptr& add(const StringIdentity id, std::unique_ptr&& uniformBlock) { const auto result = uniformBlockMap.insert(std::make_pair(id, std::unique_ptr())); diff --git a/include/mbgl/mtl/context.hpp b/include/mbgl/mtl/context.hpp index a310ab003..b17de073b 100644 --- a/include/mbgl/mtl/context.hpp +++ b/include/mbgl/mtl/context.hpp @@ -11,6 +11,8 @@ #include #include +#include + #include #include @@ -20,6 +22,7 @@ class ProgramParameters; namespace mtl { +class RenderPass; class RendererBackend; class ShaderProgram; @@ -32,6 +35,8 @@ public: Context(const Context&) = delete; Context& operator=(const Context& other) = delete; + const RendererBackend& getBackend() const { return backend; } + std::unique_ptr createCommandEncoder() override; gfx::RenderingStats& renderingStats() { return stats; } @@ -113,6 +118,10 @@ public: void clearStencilBuffer(int32_t) override; + MTLDepthStencilStatePtr makeDepthStencilState(const gfx::DepthMode& depthMode, + const gfx::StencilMode& stencilMode, + const mtl::RenderPass& renderPass) const; + virtual bool emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr&, const void* data, std::size_t size); private: diff --git a/include/mbgl/mtl/mtl_fwd.hpp b/include/mbgl/mtl/mtl_fwd.hpp index 7093160cc..9f31d01c8 100644 --- a/include/mbgl/mtl/mtl_fwd.hpp +++ b/include/mbgl/mtl/mtl_fwd.hpp @@ -22,6 +22,7 @@ class Buffer; class CommandBuffer; class CommandQueue; class Device; +class DepthStencilState; class Function; class RenderCommandEncoder; class RenderPipelineState; @@ -45,6 +46,7 @@ using MTLBufferPtr = NS::SharedPtr; using MTLCommandBufferPtr = NS::SharedPtr; using MTLCommandQueuePtr = NS::SharedPtr; using MTLDevicePtr = NS::SharedPtr; +using MTLDepthStencilStatePtr = NS::SharedPtr; using MTLFunctionPtr = NS::SharedPtr; using MTLRenderCommandEncoderPtr = NS::SharedPtr; using MTLRenderPassDescriptorPtr = NS::SharedPtr; diff --git a/include/mbgl/renderer/layer_tweaker.hpp b/include/mbgl/renderer/layer_tweaker.hpp index 5e3429fba..2b81f1746 100644 --- a/include/mbgl/renderer/layer_tweaker.hpp +++ b/include/mbgl/renderer/layer_tweaker.hpp @@ -13,6 +13,7 @@ class UniformBuffer; using UniformBufferPtr = std::shared_ptr; } // namespace gfx namespace shaders { +enum class AttributeSource : int32_t; struct ExpressionInputsUBO; } // namespace shaders namespace style { @@ -52,6 +53,7 @@ public: /// @brief Check whether a property name exists within the previously set collection. /// @details The string value provided is expected to have the "a_" prefix, as defined in the shader classes. bool hasPropertyAsUniform(std::string_view) const; + shaders::AttributeSource getAttributeSource(const std::string_view& attribName) const; #endif // MLN_RENDER_BACKEND_METAL void enableOverdrawInspector(bool); diff --git a/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp b/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp new file mode 100644 index 000000000..48adb80b2 --- /dev/null +++ b/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include + +#include + +namespace mbgl { +namespace shaders { + +/// Evaluated properties that depend on the tile +struct alignas(16) FillExtrusionDrawableTilePropsUBO { + /* 0 */ std::array pattern_from; + /* 16 */ std::array pattern_to; + /* 32 */ +}; +static_assert(sizeof(FillExtrusionDrawableTilePropsUBO) == 2 * 16); + +/// Attribute interpolations +struct alignas(16) FillExtrusionInterpolateUBO { + /* 0 */ float base_t; + /* 4 */ float height_t; + /* 8 */ float color_t; + /* 12 */ float pattern_from_t; + /* 16 */ float pattern_to_t; + /* 20 */ float pad1, pad2, pad3; + /* 32 */ +}; +static_assert(sizeof(FillExtrusionInterpolateUBO) == 2 * 16); + +struct alignas(16) FillExtrusionDrawableUBO { + /* 0 */ std::array matrix; + /* 64 */ std::array scale; + /* 80 */ std::array texsize; + /* 88 */ std::array pixel_coord_upper; + /* 96 */ std::array pixel_coord_lower; + /* 104 */ float height_factor; + /* 108 */ float pad; + /* 112 */ +}; +static_assert(sizeof(FillExtrusionDrawableUBO) == 7 * 16); + +/// Evaluated properties that do not depend on the tile +struct alignas(16) FillExtrusionDrawablePropsUBO { + /* 0 */ Color color; + /* 16 */ std::array light_color; + /* 28 */ float pad1; + /* 32 */ std::array light_position; + /* 44 */ float base; + /* 48 */ float height; + /* 52 */ float light_intensity; + /* 56 */ float vertical_gradient; + /* 60 */ float opacity; + /* 64 */ float fade; + /* 68 */ float pad2, pad3, pad4; + /* 80 */ +}; +static_assert(sizeof(FillExtrusionDrawablePropsUBO) == 5 * 16); + +struct alignas(16) FillExtrusionPermutationUBO { + /* 0 */ Attribute color; + /* 8 */ Attribute base; + /* 16 */ Attribute height; + /* 24 */ bool overdrawInspector; + /* 25 */ uint8_t pad1, pad2, pad3; + /* 28 */ float pad4; + /* 32 */ +}; +static_assert(sizeof(FillExtrusionPermutationUBO) == 2 * 16); + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/mtl/clipping_mask.hpp b/include/mbgl/shaders/mtl/clipping_mask.hpp new file mode 100644 index 000000000..5c62c52a7 --- /dev/null +++ b/include/mbgl/shaders/mtl/clipping_mask.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace shaders { + +struct alignas(16) ClipUBO { + /* 0 */ std::array matrix; + /* 64 */ +}; +static_assert(sizeof(ClipUBO) == 4 * 16); + +template <> +struct ShaderSource { + static constexpr auto name = "ClippingMaskProgram"; + static constexpr auto vertexMainFunction = "vertexMain"; + static constexpr auto fragmentMainFunction = "fragmentMain"; + + static constexpr AttributeInfo attributes[] = { + {0, gfx::AttributeDataType::Float3, 1, "a_pos"}, + }; + static constexpr UniformBlockInfo uniforms[] = { + MLN_MTL_UNIFORM_BLOCK(1, true, false, ClipUBO), + }; + static constexpr TextureInfo textures[] = {}; + + static constexpr auto source = R"( +#include +using namespace metal; + +struct alignas(16) ClipUBO { + /* 0 */ float4x4 matrix; + /* 64 */ +}; +static_assert(sizeof(ClipUBO) == 4 * 16, "unexpected padding"); + +struct VertexStage { + short2 position [[attribute(0)]]; +}; + +struct FragmentStage { + float4 position [[position, invariant]]; +}; + +FragmentStage vertex vertexMain(VertexStage in [[stage_in]], + device const ClipUBO& clipUBO [[buffer(1)]]) { + return { + .position = clipUBO.matrix * float4(float2(in.position.xy), 0, 1) + }; +} + +half4 fragment fragmentMain(FragmentStage in [[stage_in]]) { + return half4(1.0); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/mtl/common.hpp b/include/mbgl/shaders/mtl/common.hpp index 3fa44adf4..34c361e0f 100644 --- a/include/mbgl/shaders/mtl/common.hpp +++ b/include/mbgl/shaders/mtl/common.hpp @@ -10,10 +10,21 @@ constexpr auto prelude = R"( #include using namespace metal; +// The maximum allowed miter limit is 2.0 at the moment. the extrude normal is stored +// in a byte (-128..127). We scale regular normals up to length 63, but there are also +// "special" normals that have a bigger length (of up to 126 in this case). +#define LINE_NORMAL_SCALE (1.0 / (127 / 2)) + +// The attribute conveying progress along a line is scaled to [0, 2^15). +#define MAX_LINE_DISTANCE 32767.0 + +#define SDF_PX 8.0 + +// OpenGL `mod` is `x-y*floor(x/y)` where `floor` rounds down. +// Metal `fmod` is `x-y*trunc(x/y)` where `trunc` rounds toward zero. +// This function provides GL-compatible modulus for porting GLSL shaders. template -inline auto mod(T1 x, T2 y) -> decltype(x - y * floor(x/y)) { - return x - y * metal::floor(x/y); -} +inline auto glMod(T1 x, T2 y) { return x - y * metal::floor(x/y); } enum class AttributeSource : int32_t { Constant, @@ -67,9 +78,9 @@ float4 unpack_mix_color(const float4 packedColors, const float t) { } float valueFor(device const Attribute& attrib, - device const float& constValue, + const float constValue, thread const float2& vertexValue, - device const float& t, + const float t, device const ExpressionInputsUBO&) { switch (attrib.source) { case AttributeSource::PerVertex: return unpack_mix_float(vertexValue, t); @@ -93,8 +104,8 @@ float4 colorFor(device const Attribute& attrib, // interpolated packed colors float4 colorFor(device const Attribute& attrib, device const float4& constValue, - thread const float4& vertexValue, - device const float& t, + const float4 vertexValue, + const float t, device const ExpressionInputsUBO&) { switch (attrib.source) { case AttributeSource::PerVertex: return unpack_mix_color(vertexValue, t); @@ -104,14 +115,6 @@ float4 colorFor(device const Attribute& attrib, } } -// The maximum allowed miter limit is 2.0 at the moment. the extrude normal is stored -// in a byte (-128..127). We scale regular normals up to length 63, but there are also -// "special" normals that have a bigger length (of up to 126 in this case). -constant float LINE_NORMAL_SCALE = 1.0 / (127 / 2); - -// The attribute conveying progress along a line is scaled to [0, 2^15). -constant float MAX_LINE_DISTANCE = 32767.0; - struct alignas(16) LineUBO { float4x4 matrix; float2 units_to_pixels; @@ -196,8 +199,7 @@ float4 patternFor(device const Attribute& attrib, // unpack pattern position inline float2 get_pattern_pos(const float2 pixel_coord_upper, const float2 pixel_coord_lower, const float2 pattern_size, const float tile_units_to_pixels, const float2 pos) { - - float2 offset = mod(mod(mod(pixel_coord_upper, pattern_size) * 256.0, pattern_size) * 256.0 + pixel_coord_lower, pattern_size); + const float2 offset = glMod(glMod(glMod(pixel_coord_upper, pattern_size) * 256.0, pattern_size) * 256.0 + pixel_coord_lower, pattern_size); return (tile_units_to_pixels * pos + offset) / pattern_size; } diff --git a/include/mbgl/shaders/mtl/fill.hpp b/include/mbgl/shaders/mtl/fill.hpp index dc542f841..59989e399 100644 --- a/include/mbgl/shaders/mtl/fill.hpp +++ b/include/mbgl/shaders/mtl/fill.hpp @@ -18,6 +18,11 @@ struct ShaderSource { {0, gfx::AttributeDataType::Short2, 1, "a_pos"}, {1, gfx::AttributeDataType::Float2, 1, "a_color"}, {2, gfx::AttributeDataType::Float2, 1, "a_opacity"}, + + // This shader doesn't use it, but we need this so that the layer can assign the same + // attributes to all the drawables. If/When `VertexAttributeArray::resolve` allows it, + // this can be removed. + {8, gfx::AttributeDataType::Float2, 1, "a_outline_color"}, }; static constexpr UniformBlockInfo uniforms[] = { MLN_MTL_UNIFORM_BLOCK(3, true, false, FillDrawableUBO), @@ -103,6 +108,9 @@ struct ShaderSource { {0, gfx::AttributeDataType::Short2, 1, "a_pos"}, {1, gfx::AttributeDataType::Float2, 1, "a_outline_color"}, {2, gfx::AttributeDataType::Float2, 1, "a_opacity"}, + + // See `a_outline_color` in FillShader + {8, gfx::AttributeDataType::Float2, 1, "a_color"}, }; static constexpr UniformBlockInfo uniforms[] = { MLN_MTL_UNIFORM_BLOCK(3, true, false, FillOutlineDrawableUBO), @@ -308,18 +316,18 @@ half4 fragment fragmentMain(FragmentStage in [[stage_in]], return half4(1.0); } - float2 pattern_tl_a = in.pattern_from.xy; - float2 pattern_br_a = in.pattern_from.zw; - float2 pattern_tl_b = in.pattern_to.xy; - float2 pattern_br_b = in.pattern_to.zw; + const float2 pattern_tl_a = in.pattern_from.xy; + const float2 pattern_br_a = in.pattern_from.zw; + const float2 pattern_tl_b = in.pattern_to.xy; + const float2 pattern_br_b = in.pattern_to.zw; - float2 imagecoord = mod(in.v_pos_a, 1.0); - float2 pos = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, imagecoord); - float4 color1 = image0.sample(image0_sampler, pos); - - float2 imagecoord_b = mod(in.v_pos_b, 1.0); - float2 pos2 = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, imagecoord_b); - float4 color2 = image0.sample(image0_sampler, pos2); + const float2 imagecoord = glMod(in.v_pos_a, 1.0); + const float2 pos = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, imagecoord); + const float4 color1 = image0.sample(image0_sampler, pos); + + const float2 imagecoord_b = glMod(in.v_pos_b, 1.0); + const float2 pos2 = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, imagecoord_b); + const float4 color2 = image0.sample(image0_sampler, pos2); return half4(mix(color1, color2, props.fade) * in.opacity); } @@ -450,18 +458,18 @@ half4 fragment fragmentMain(FragmentStage in [[stage_in]], return half4(1.0); } - float2 pattern_tl_a = in.pattern_from.xy; - float2 pattern_br_a = in.pattern_from.zw; - float2 pattern_tl_b = in.pattern_to.xy; - float2 pattern_br_b = in.pattern_to.zw; + const float2 pattern_tl_a = in.pattern_from.xy; + const float2 pattern_br_a = in.pattern_from.zw; + const float2 pattern_tl_b = in.pattern_to.xy; + const float2 pattern_br_b = in.pattern_to.zw; - float2 imagecoord = mod(in.v_pos_a, 1.0); - float2 pos = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, imagecoord); - float4 color1 = image0.sample(image0_sampler, pos); - - float2 imagecoord_b = mod(in.v_pos_b, 1.0); - float2 pos2 = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, imagecoord_b); - float4 color2 = image0.sample(image0_sampler, pos2); + const float2 imagecoord = glMod(in.v_pos_a, 1.0); + const float2 pos = mix(pattern_tl_a / drawable.texsize, pattern_br_a / drawable.texsize, imagecoord); + const float4 color1 = image0.sample(image0_sampler, pos); + + const float2 imagecoord_b = glMod(in.v_pos_b, 1.0); + const float2 pos2 = mix(pattern_tl_b / drawable.texsize, pattern_br_b / drawable.texsize, imagecoord_b); + const float4 color2 = image0.sample(image0_sampler, pos2); // TODO: Should triangate the lines into triangles to support thick line and edge antialiased. //float dist = length(in.v_pos - in.position.xy); diff --git a/include/mbgl/shaders/mtl/fill_extrusion.hpp b/include/mbgl/shaders/mtl/fill_extrusion.hpp new file mode 100644 index 000000000..22a751df0 --- /dev/null +++ b/include/mbgl/shaders/mtl/fill_extrusion.hpp @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr auto name = "FillExtrusionShader"; + static constexpr auto vertexMainFunction = "vertexMain"; + static constexpr auto fragmentMainFunction = "fragmentMain"; + + static constexpr AttributeInfo attributes[] = { + {0, gfx::AttributeDataType::Short2, 1, "a_pos"}, + {1, gfx::AttributeDataType::Short4, 1, "a_normal_ed"}, + {2, gfx::AttributeDataType::Float4, 1, "a_color"}, + {3, gfx::AttributeDataType::Float, 1, "a_base"}, + {4, gfx::AttributeDataType::Float, 1, "a_height"}, + }; + static constexpr UniformBlockInfo uniforms[] = { + MLN_MTL_UNIFORM_BLOCK(5, true, false, FillExtrusionDrawableUBO), + MLN_MTL_UNIFORM_BLOCK(6, true, false, FillExtrusionDrawablePropsUBO), + MLN_MTL_UNIFORM_BLOCK(7, true, false, FillExtrusionInterpolateUBO), + MLN_MTL_UNIFORM_BLOCK(8, true, false, FillExtrusionPermutationUBO), + MLN_MTL_UNIFORM_BLOCK(9, true, false, ExpressionInputsUBO), + }; + static constexpr TextureInfo textures[] = {}; + + static constexpr auto source = R"( +struct alignas(16) FillExtrusionInterpolateUBO { + /* 0 */ float base_t; + /* 4 */ float height_t; + /* 8 */ float color_t; + /* 12 */ float pattern_from_t; + /* 16 */ float pattern_to_t; + /* 20 */ float pad1, pad2, pad3; + /* 32 */ +}; +static_assert(sizeof(FillExtrusionInterpolateUBO) == 2 * 16, "unexpected padding"); + +struct alignas(16) FillExtrusionDrawableUBO { + /* 0 */ float4x4 matrix; + /* 64 */ float4 scale; + /* 80 */ float2 texsize; + /* 88 */ float2 pixel_coord_upper; + /* 96 */ float2 pixel_coord_lower; + /* 104 */ float height_factor; + /* 108 */ float pad; + /* 112 */ +}; +static_assert(sizeof(FillExtrusionDrawableUBO) == 7 * 16, "unexpected padding"); + +struct alignas(16) FillExtrusionDrawablePropsUBO { + /* 0 */ float4 color; + /* 16 */ float4 light_color_pad; + /* 32 */ float4 light_position_base; + /* 48 */ float height; + /* 52 */ float light_intensity; + /* 56 */ float vertical_gradient; + /* 60 */ float opacity; + /* 64 */ float fade; + /* 68 */ float pad2, pad3, pad4; + /* 80 */ +}; +static_assert(sizeof(FillExtrusionDrawablePropsUBO) == 5 * 16, "unexpected padding"); + +struct alignas(16) FillExtrusionPermutationUBO { + /* 0 */ Attribute color; + /* 8 */ Attribute base; + /* 16 */ Attribute height; + /* 24 */ bool overdrawInspector; + /* 25 */ uint8_t pad1, pad2, pad3; + /* 28 */ float pad4; + /* 32 */ +}; +static_assert(sizeof(FillExtrusionPermutationUBO) == 2 * 16, "unexpected padding"); + +struct VertexStage { + short2 pos [[attribute(0)]]; + short4 normal_ed [[attribute(1)]]; + float4 color [[attribute(2)]]; + float base [[attribute(3)]]; + float height [[attribute(4)]]; +}; + +struct FragmentStage { + float4 position [[position, invariant]]; + half4 color; +}; + +struct FragmentOutput { + half4 color [[color(0)]]; + //float depth [[depth(less)]]; // Write depth value if it's less than what's already there +}; + +FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], + device const FillExtrusionDrawableUBO& fill [[buffer(5)]], + device const FillExtrusionDrawablePropsUBO& props [[buffer(6)]], + device const FillExtrusionInterpolateUBO& interp [[buffer(7)]], + device const FillExtrusionPermutationUBO& permutation [[buffer(8)]], + device const ExpressionInputsUBO& expr [[buffer(9)]]) { + + const float u_base = props.light_position_base.w; + const auto base = max(valueFor(permutation.base, u_base, vertx.base, interp.base_t, expr), 0.0); + const auto height = max(valueFor(permutation.height, props.height, vertx.height, interp.height_t, expr), 0.0); + + const float3 normal = float3(vertx.normal_ed.xyz); + const float t = glMod(normal.x, 2.0); + const float z = (t != 0.0) ? height : base; // TODO: This would come out wrong on GL for negative values, check it... + const float4 position = fill.matrix * float4(float2(vertx.pos), z, 1); + + if (permutation.overdrawInspector) { + return { + .position = position, + .color = half4(1.0), + }; + } + + auto color = colorFor(permutation.color, props.color, vertx.color, interp.color_t, expr); + + // Relative luminance (how dark/bright is the surface color?) + const float luminance = color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722; + + float4 vcolor = float4(0.0, 0.0, 0.0, 1.0); + + // Add slight ambient lighting so no extrusions are totally black + color += min(float4(0.03, 0.03, 0.03, 1.0), float4(1.0)); + + // Calculate cos(theta), where theta is the angle between surface normal and diffuse light ray + const float directionalFraction = clamp(dot(normal / 16384.0, props.light_position_base.xyz), 0.0, 1.0); + + // Adjust directional so that the range of values for highlight/shading is + // narrower with lower light intensity and with lighter/brighter surface colors + const float minDirectional = 1.0 - props.light_intensity; + const float maxDirectional = max(1.0 - luminance + props.light_intensity, 1.0); + float directional = mix(minDirectional, maxDirectional, directionalFraction); + + // Add gradient along z axis of side surfaces + if (normal.y != 0.0) { + // This avoids another branching statement, but multiplies by a constant of 0.84 if no + // vertical gradient, and otherwise calculates the gradient based on base + height + // TODO: If we're optimizing to the level of avoiding branches, we should pre-compute + // the square root when height is a uniform. + const float fMin = mix(0.7, 0.98, 1.0 - props.light_intensity); + const float factor = clamp((t + base) * pow(height / 150.0, 0.5), fMin, 1.0); + directional *= (1.0 - props.vertical_gradient) + (props.vertical_gradient * factor); + } + + // Assign final color based on surface + ambient light color, diffuse light directional, + // and light color with lower bounds adjusted to hue of light so that shading is tinted + // with the complementary (opposite) color to the light color + const float3 light_color = props.light_color_pad.rgb; + const float3 minLight = mix(0.0, 0.3, 1.0 - light_color.rgb); + vcolor += float4(clamp(color.rgb * directional * light_color.rgb, minLight, 1.0), 0.0); + + return { + .position = position, + .color = half4(vcolor * props.opacity), + }; +} + +fragment FragmentOutput fragmentMain(FragmentStage in [[stage_in]]) { + return { in.color/*, in.position.z*/ }; +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/mtl/fill_extrusion_pattern.hpp b/include/mbgl/shaders/mtl/fill_extrusion_pattern.hpp new file mode 100644 index 000000000..ec96161af --- /dev/null +++ b/include/mbgl/shaders/mtl/fill_extrusion_pattern.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr auto name = "FillExtrusionPatternShader"; + static constexpr auto vertexMainFunction = "vertexMain"; + static constexpr auto fragmentMainFunction = "fragmentMain"; + + static constexpr AttributeInfo attributes[] = {}; + static constexpr UniformBlockInfo uniforms[] = {}; + static constexpr TextureInfo textures[] = {}; + + static constexpr auto source = R"( +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/util/string_indexer.hpp b/include/mbgl/util/string_indexer.hpp index 461ee14ed..7bac3fe19 100644 --- a/include/mbgl/util/string_indexer.hpp +++ b/include/mbgl/util/string_indexer.hpp @@ -18,7 +18,9 @@ public: StringIndexer(StringIndexer&&) = delete; void operator=(StringIndexer const&) = delete; - static StringIdentity get(const std::string& string); + static StringIdentity get(const char*); + static StringIdentity get(std::string_view); + static StringIdentity get(const std::string&); static const std::string& get(const StringIdentity id); @@ -27,7 +29,7 @@ public: static size_t size(); protected: - StringIndexer() = default; + StringIndexer(); ~StringIndexer() = default; using MapType = std::unordered_map; diff --git a/platform/default/include/mbgl/gfx/headless_backend.hpp b/platform/default/include/mbgl/gfx/headless_backend.hpp index 14bff3eff..a2656c431 100644 --- a/platform/default/include/mbgl/gfx/headless_backend.hpp +++ b/platform/default/include/mbgl/gfx/headless_backend.hpp @@ -25,7 +25,7 @@ public: SwapBehaviour swapBehavior = SwapBehaviour::NoFlush, const gfx::ContextMode contextMode = gfx::ContextMode::Unique) { #if MLN_RENDER_BACKEND_METAL - return nullptr; + throw std::runtime_error("Not yet implemented"); #else // MLN_RENDER_BACKEND_OPENGL return Backend::Create(size, swapBehavior, contextMode); #endif diff --git a/platform/ios/src/MLNMapView+Metal.mm b/platform/ios/src/MLNMapView+Metal.mm index 4547a5af0..8ae82c32f 100644 --- a/platform/ios/src/MLNMapView+Metal.mm +++ b/platform/ios/src/MLNMapView+Metal.mm @@ -76,6 +76,11 @@ public: void swap() override { [commandBuffer presentDrawable:currentDrawable]; [commandBuffer commit]; + + // Un-comment for synchronous, which can help troubleshoot rendering problems, + // particularly those related to resource tracking and multiple queued buffers. + //[commandBuffer waitUntilCompleted]; + commandBuffer = nil; commandBufferPtr.reset(); } @@ -158,7 +163,6 @@ void MLNMapViewMetalImpl::createView() { resource.mtlView.opaque = mapView.opaque; resource.mtlView.layer.opaque = mapView.opaque; resource.mtlView.enableSetNeedsDisplay = YES; - if (@available(iOS 13.0, *)) { CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(resource.mtlView.layer, CAMetalLayer); metalLayer.presentsWithTransaction = presentsWithTransaction; diff --git a/src/mbgl/gfx/vertex_attribute.cpp b/src/mbgl/gfx/vertex_attribute.cpp index fb62719ea..a1862fef9 100644 --- a/src/mbgl/gfx/vertex_attribute.cpp +++ b/src/mbgl/gfx/vertex_attribute.cpp @@ -89,6 +89,15 @@ void VertexAttributeArray::resolve(const VertexAttributeArray& overrides, Resolv for (auto& kv : attrs) { delegate(kv.first, *kv.second, overrides.get(kv.first)); } + // For OpenGL, the shader attributes are established with reflection, and we have extra + // entries when we share attributes between, e.g., fill and fill-outline drawables. +#if !defined(NDEBUG) && MLN_RENDERER_BACKEND_METAL + // Every override should match a defined attribute. + for (const auto& kv : overrides.attrs) { + const auto hit = attrs.find(kv.first); + assert(hit != attrs.end()); + } +#endif } const UniqueVertexAttribute& VertexAttributeArray::add(const StringIdentity id, diff --git a/src/mbgl/mtl/context.cpp b/src/mbgl/mtl/context.cpp index 5166af988..6dc67aac1 100644 --- a/src/mbgl/mtl/context.cpp +++ b/src/mbgl/mtl/context.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -214,7 +215,152 @@ void Context::visualizeStencilBuffer() {} void Context::visualizeDepthBuffer(float depthRangeSize) {} #endif // !defined(NDEBUG) -void Context::clearStencilBuffer(int32_t) {} +void Context::clearStencilBuffer(int32_t) { + // See `PaintParameters::clearStencil` + assert(false); +} + +namespace { + +MTL::CompareFunction mapFunc(const gfx::DepthFunctionType func) { + switch (func) { + default: + case gfx::DepthFunctionType::Never: + return MTL::CompareFunction::CompareFunctionNever; + case gfx::DepthFunctionType::Less: + return MTL::CompareFunction::CompareFunctionLess; + case gfx::DepthFunctionType::Equal: + return MTL::CompareFunction::CompareFunctionEqual; + case gfx::DepthFunctionType::LessEqual: + return MTL::CompareFunction::CompareFunctionLessEqual; + case gfx::DepthFunctionType::Greater: + return MTL::CompareFunction::CompareFunctionGreater; + case gfx::DepthFunctionType::NotEqual: + return MTL::CompareFunction::CompareFunctionNotEqual; + case gfx::DepthFunctionType::GreaterEqual: + return MTL::CompareFunction::CompareFunctionGreaterEqual; + case gfx::DepthFunctionType::Always: + return MTL::CompareFunction::CompareFunctionAlways; + }; +} +MTL::CompareFunction mapFunc(const gfx::StencilFunctionType func) { + switch (func) { + default: + case gfx::StencilFunctionType::Never: + return MTL::CompareFunction::CompareFunctionNever; + case gfx::StencilFunctionType::Less: + return MTL::CompareFunction::CompareFunctionLess; + case gfx::StencilFunctionType::Equal: + return MTL::CompareFunction::CompareFunctionEqual; + case gfx::StencilFunctionType::LessEqual: + return MTL::CompareFunction::CompareFunctionLessEqual; + case gfx::StencilFunctionType::Greater: + return MTL::CompareFunction::CompareFunctionGreater; + case gfx::StencilFunctionType::NotEqual: + return MTL::CompareFunction::CompareFunctionNotEqual; + case gfx::StencilFunctionType::GreaterEqual: + return MTL::CompareFunction::CompareFunctionGreaterEqual; + case gfx::StencilFunctionType::Always: + return MTL::CompareFunction::CompareFunctionAlways; + }; +} + +MTL::StencilOperation mapOperation(const gfx::StencilOpType op) { + switch (op) { + case gfx::StencilOpType::Zero: + return MTL::StencilOperation::StencilOperationZero; + default: + case gfx::StencilOpType::Keep: + return MTL::StencilOperation::StencilOperationKeep; + case gfx::StencilOpType::Replace: + return MTL::StencilOperation::StencilOperationReplace; + case gfx::StencilOpType::Increment: + return MTL::StencilOperation::StencilOperationIncrementClamp; + case gfx::StencilOpType::Decrement: + return MTL::StencilOperation::StencilOperationDecrementClamp; + case gfx::StencilOpType::Invert: + return MTL::StencilOperation::StencilOperationInvert; + case gfx::StencilOpType::IncrementWrap: + return MTL::StencilOperation::StencilOperationIncrementWrap; + case gfx::StencilOpType::DecrementWrap: + return MTL::StencilOperation::StencilOperationDecrementWrap; + } +} + +void applyDepthMode(const gfx::DepthMode& depthMode, MTL::DepthStencilDescriptor* desc) { + desc->setDepthCompareFunction(mapFunc(depthMode.func)); + desc->setDepthWriteEnabled(depthMode.mask == gfx::DepthMaskType::ReadWrite); + // depthMode.range ? +} + +struct StencilModeVisitor { + MTL::StencilDescriptor* desc; + + template + void operator()(const gfx::StencilMode::SimpleTest& mode) { + apply(mode.func, std::numeric_limits::max()); + } + template + void operator()(const gfx::StencilMode::MaskedTest& mode) { + apply(mode.func, mode.mask); + } + + void apply(gfx::StencilFunctionType func, uint32_t mask) { + desc->setStencilCompareFunction(mapFunc(func)); + desc->setReadMask(mask); + desc->setWriteMask(mask); + } +}; + +// helper type for the visitor #4 +void applyStencilMode(const gfx::StencilMode& stencilMode, MTL::StencilDescriptor* desc) { + mapbox::util::apply_visitor(StencilModeVisitor{desc}, stencilMode.test); + + desc->setStencilFailureOperation(mapOperation(stencilMode.fail)); + desc->setDepthFailureOperation(mapOperation(stencilMode.depthFail)); + desc->setDepthStencilPassOperation(mapOperation(stencilMode.pass)); +} + +} // namespace + +MTLDepthStencilStatePtr Context::makeDepthStencilState(const gfx::DepthMode& depthMode, + const gfx::StencilMode& stencilMode, + const mtl::RenderPass& renderPass) const { + auto depthStencilDescriptor = NS::TransferPtr(MTL::DepthStencilDescriptor::alloc()->init()); + if (!depthStencilDescriptor) { + return {}; + } + + auto& device = backend.getDevice(); + const auto& renderPassDescriptor = renderPass.getDescriptor(); + const auto& renderable = renderPassDescriptor.renderable; + const auto& renderableResource = renderable.getResource(); + if (const auto& rpd = renderableResource.getRenderPassDescriptor()) { + // Setting depth/stencil properties when the corresponding target textures aren't set causes, e.g.: + // `Draw Errors Validation MTLDepthStencilDescriptor sets depth test but MTLRenderPassDescriptor has a nil + // depthAttachment texture` + if (auto* depthTarget = rpd->depthAttachment()) { + if (auto* tex = depthTarget->texture()) { + applyDepthMode(depthMode, depthStencilDescriptor.get()); + } + } + if (auto* stencilTarget = rpd->stencilAttachment()) { + if (auto* tex = stencilTarget->texture()) { + auto stencilDescriptor = NS::TransferPtr(MTL::StencilDescriptor::alloc()->init()); + if (!stencilDescriptor) { + return {}; + } + + applyStencilMode(stencilMode, stencilDescriptor.get()); + + depthStencilDescriptor->setFrontFaceStencil(stencilDescriptor.get()); + depthStencilDescriptor->setBackFaceStencil(stencilDescriptor.get()); + } + } + } + + return NS::TransferPtr(device->newDepthStencilState(depthStencilDescriptor.get())); +} } // namespace mtl } // namespace mbgl diff --git a/src/mbgl/mtl/drawable.cpp b/src/mbgl/mtl/drawable.cpp index 4eaa4e480..99c06685e 100644 --- a/src/mbgl/mtl/drawable.cpp +++ b/src/mbgl/mtl/drawable.cpp @@ -1,12 +1,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -15,6 +17,7 @@ #include #include #include +#include #include @@ -95,6 +98,28 @@ MTL::Buffer* getMetalBuffer(const gfx::IndexVectorBasePtr& indexes) { return nullptr; } +MTL::CullMode mapCullMode(const gfx::CullFaceSideType mode) { + switch (mode) { + case gfx::CullFaceSideType::Front: + return MTL::CullModeFront; + case gfx::CullFaceSideType::Back: + return MTL::CullModeBack; + default: + case gfx::CullFaceSideType::FrontAndBack: + return MTL::CullModeNone; + } +} + +MTL::Winding mapWindingMode(const gfx::CullFaceWindingType mode) { + switch (mode) { + case gfx::CullFaceWindingType::Clockwise: + return MTL::Winding::WindingClockwise; + default: + case gfx::CullFaceWindingType::CounterClockwise: + return MTL::Winding::WindingCounterClockwise; + } +} + } // namespace void Drawable::draw(PaintParameters& parameters) const { @@ -160,17 +185,33 @@ void Drawable::draw(PaintParameters& parameters) const { assert(!"Vertex descriptor missing"); } + const auto& cullMode = getCullFaceMode(); + encoder->setCullMode(cullMode.enabled ? mapCullMode(cullMode.side) : MTL::CullModeNone); + encoder->setFrontFacingWinding(mapWindingMode(cullMode.winding)); + + if (auto state = shaderMTL.getRenderPipelineState(renderPassDescriptor, impl->vertexDesc, getColorMode())) { + encoder->setRenderPipelineState(state.get()); + } else { + assert(!"Failed to create render pipeline state"); + return; + } + + // For 3D mode, stenciling is handled by the layer group + if (!is3D) { + const auto depthMode = parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType()); + const auto stencilMode = enableStencil ? parameters.stencilModeForClipping(tileID->toUnwrapped()) + : gfx::StencilMode::disabled(); + if (auto depthStencilState = context.makeDepthStencilState(depthMode, stencilMode, renderPass)) { + encoder->setDepthStencilState(depthStencilState.get()); + encoder->setStencilReferenceValue(stencilMode.ref); + } + } + for (const auto& seg_ : impl->segments) { const auto& segment = static_cast(*seg_); const auto& mlSegment = segment.getSegment(); if (mlSegment.indexLength > 0) { const auto& mode = segment.getMode(); - if (auto state = shaderMTL.getRenderPipelineState(renderPassDescriptor, impl->vertexDesc, getColorMode())) { - encoder->setRenderPipelineState(state.get()); - } else { - assert(!"Failed to create render pipeline state"); - continue; - } const auto primitiveType = getPrimitiveType(mode.type); constexpr auto indexType = MTL::IndexType::IndexTypeUInt16; @@ -180,6 +221,41 @@ void Drawable::draw(PaintParameters& parameters) const { const NS::UInteger indexOffset = static_cast(indexSize * mlSegment.indexOffset); // in bytes, not indexes const NS::Integer baseVertex = static_cast(mlSegment.vertexOffset); + +#if !defined(NDEBUG) + const auto indexBufferLength = indexBuffer->length() / indexSize; + const auto* indexes = static_cast(const_cast(indexBuffer)->contents()); + const auto maxIndex = *std::max_element(indexes + mlSegment.indexOffset, + indexes + mlSegment.indexOffset + mlSegment.indexLength); + + // Uncomment for a detailed accounting of each draw call + // Log::Warning(Event::General, + // util::toString(getID()) + "/" + getName() + + // " => " + util::toString(mlSegment.indexLength) + + // " idxs @ " + util::toString(mlSegment.indexOffset) + + // " (=" + util::toString(mlSegment.indexLength + mlSegment.indexOffset) + + // " of " + util::toString(indexBufferLength) + + // ") max index " + util::toString(maxIndex) + + // " on base vertex " + util::toString(baseVertex) + + // " (" + util::toString(baseVertex + maxIndex) + + // " of " + util::toString(impl->vertexCount) + + // ") indexBuf=" + util::toString((uint64_t)indexBuffer) + + // "/" + util::toString(indexBuffer->gpuAddress())); + + assert(mlSegment.indexOffset + mlSegment.indexLength <= indexBufferLength); + assert(static_cast(maxIndex) < mlSegment.vertexLength); + + for (const auto& binding : attributeBindings) { + if (binding) { + if (const auto buffer = getMetalBuffer(binding ? binding->vertexBufferResource : nullptr)) { + assert((maxIndex + mlSegment.vertexOffset) * binding->vertexStride <= buffer->length()); + } else if (const auto buffer = getMetalBuffer(impl->noBindingBuffer.get())) { + assert(binding->vertexStride <= buffer->length()); + } + } + } +#endif + encoder->drawIndexedPrimitives(primitiveType, mlSegment.indexLength, indexType, @@ -252,7 +328,7 @@ void Drawable::bindAttributes(const RenderPass& renderPass) const { NS::UInteger attributeIndex = 0; for (const auto& binding : attributeBindings) { if (const auto buffer = getMetalBuffer(binding ? binding->vertexBufferResource : nullptr)) { - assert(getBufferSize(binding->vertexBufferResource) == binding->vertexStride * impl->vertexCount); + assert(binding->vertexStride * impl->vertexCount <= getBufferSize(binding->vertexBufferResource)); encoder->setVertexBuffer(buffer, /*offset=*/0, attributeIndex); } else if (const auto buffer = getMetalBuffer(impl->noBindingBuffer.get())) { encoder->setVertexBuffer(buffer, /*offset=*/0, attributeIndex); diff --git a/src/mbgl/mtl/render_pass.cpp b/src/mbgl/mtl/render_pass.cpp index 1118e284f..453e34033 100644 --- a/src/mbgl/mtl/render_pass.cpp +++ b/src/mbgl/mtl/render_pass.cpp @@ -25,6 +25,7 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const if (auto copy = NS::TransferPtr(rpd->copy())) { if (auto* colorTarget = copy->colorAttachments()->object(0)) { const auto& c = *descriptor.clearColor; + colorTarget->setLoadAction(MTL::LoadActionClear); colorTarget->setClearColor(MTL::ClearColor::Make(c.r, c.g, c.b, c.a)); rpd = copy; } diff --git a/src/mbgl/mtl/renderer_backend.cpp b/src/mbgl/mtl/renderer_backend.cpp index d19928a93..b8d448966 100644 --- a/src/mbgl/mtl/renderer_backend.cpp +++ b/src/mbgl/mtl/renderer_backend.cpp @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -112,6 +115,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar registerTypes #include #include +#include #include #include +#include + +#include namespace mbgl { namespace mtl { @@ -39,6 +43,8 @@ void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { } auto& context = static_cast(parameters.context); + const auto& renderPass = static_cast(*parameters.renderPass); + const auto& encoder = renderPass.getMetalEncoder(); // `stencilModeFor3D` uses a different stencil mask value each time its called, so if the // drawables in this layer use 3D stencil mode, we need to set it up here so that all the @@ -79,13 +85,16 @@ void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { // to draw, set up the single-value stencil mask. // If we're doing 2D stenciling and have any drawables with tile IDs, // render each tile into the stencil buffer with a different value. + MTLDepthStencilStatePtr stateWithStencil, stateWithoutStencil; if (features3d) { - stencilMode3d = stencil3d ? parameters.stencilModeFor3D() : gfx::StencilMode::disabled(); + const auto depthMode = parameters.depthModeFor3D(); + if (stencil3d) { + stencilMode3d = parameters.stencilModeFor3D(); + stateWithStencil = context.makeDepthStencilState(depthMode, stencilMode3d, renderPass); + encoder->setStencilReferenceValue(stencilMode3d.ref); + } + stateWithoutStencil = context.makeDepthStencilState(depthMode, gfx::StencilMode::disabled(), renderPass); } else if (!tileIDs.empty()) { -#if !defined(NDEBUG) - const auto debugGroup = parameters.encoder->createDebugGroup("tile-clip-masks"); -#endif - parameters.renderTileClippingMasks(tileIDs); } @@ -101,9 +110,12 @@ void TileLayerGroup::render(RenderOrchestrator&, PaintParameters& parameters) { // For layer groups with 3D features, enable either the single-value // stencil mode for features with stencil enabled or disable stenciling. // 2D drawables will set their own stencil mode within `draw`. - // if (features3d) { - // context.setStencilMode(drawable.getEnableStencil() ? stencilMode3d : gfx::StencilMode::disabled()); - //} + if (features3d) { + const auto& state = drawable.getEnableStencil() ? stateWithStencil : stateWithoutStencil; + if (state) { + encoder->setDepthStencilState(state.get()); + } + } drawable.draw(parameters); }); diff --git a/src/mbgl/mtl/upload_pass.cpp b/src/mbgl/mtl/upload_pass.cpp index c82599a85..629d1b50c 100644 --- a/src/mbgl/mtl/upload_pass.cpp +++ b/src/mbgl/mtl/upload_pass.cpp @@ -177,6 +177,9 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( bindings.resize(std::max(bindings.size(), index + 1)); if (!override_) { + // No attribute was provided, this value will be used from a uniform, but we have to set + // up a valid binding or the validation will complain when the shader code references the + // attribute at compile time, regardless of whether it's ever used at runtime. bindings[index] = { /*.attribute = */ {defaultAttr.getDataType(), /*offset=*/0}, /*.vertexStride = */ static_cast(VertexAttribute::getStrideOf(defaultAttr.getDataType())), @@ -188,6 +191,9 @@ gfx::AttributeBindingArray UploadPass::buildAttributeBindings( // If the attribute references data shared with a bucket, get the corresponding buffer. if (const auto& buffer = getBuffer(effectiveAttr.getSharedRawData(), usage)) { + assert(effectiveAttr.getSharedStride() * effectiveAttr.getSharedVertexOffset() < + effectiveAttr.getSharedRawData()->getRawSize() * effectiveAttr.getSharedRawData()->getRawCount()); + bindings[index] = { /*.attribute = */ {effectiveAttr.getSharedType(), effectiveAttr.getSharedOffset()}, /*.vertexStride = */ effectiveAttr.getSharedStride(), diff --git a/src/mbgl/programs/fill_extrusion_program.hpp b/src/mbgl/programs/fill_extrusion_program.hpp index 7e8f4374a..2f19b743f 100644 --- a/src/mbgl/programs/fill_extrusion_program.hpp +++ b/src/mbgl/programs/fill_extrusion_program.hpp @@ -69,7 +69,7 @@ public: const auto factor = pow(2, 13); return LayoutVertex{{{p.x, p.y}}, - {{// Multiply normal vector components by 2^14 to pack them into + {{// Multiply normal vector components by 2^13 to pack them into // integers We pack a bool (`t`) into the x component indicating // whether it is an upper or lower vertex static_cast(floor(nx * factor) * 2 + t), diff --git a/src/mbgl/renderer/layer_tweaker.cpp b/src/mbgl/renderer/layer_tweaker.cpp index 3474eb134..b982f2978 100644 --- a/src/mbgl/renderer/layer_tweaker.cpp +++ b/src/mbgl/renderer/layer_tweaker.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #if MLN_RENDER_BACKEND_METAL @@ -73,6 +74,11 @@ bool LayerTweaker::hasPropertyAsUniform(const std::string_view attrName) const { return name.size() + 2 == attrName.size() && 0 == std::strcmp(name.data(), attrName.data() + 2); }); } + +using namespace shaders; +AttributeSource LayerTweaker::getAttributeSource(const std::string_view& attribName) const { + return hasPropertyAsUniform(attribName) ? AttributeSource::Constant : AttributeSource::PerVertex; +} #endif // MLN_RENDER_BACKEND_METAL void LayerTweaker::enableOverdrawInspector(bool value) { diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp index 01dd0968b..f11f61a90 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -18,42 +18,16 @@ namespace mbgl { +using namespace shaders; using namespace style; namespace { -struct alignas(16) FillExtrusionDrawableUBO { - /* 0 */ std::array matrix; - /* 64 */ std::array scale; - /* 80 */ std::array texsize; - /* 88 */ std::array pixel_coord_upper; - /* 96 */ std::array pixel_coord_lower; - /* 104 */ float height_factor; - /* 108 */ float pad; - /* 112 */ -}; -static_assert(sizeof(FillExtrusionDrawableUBO) == 7 * 16); - -/// Evaluated properties that do not depend on the tile -struct alignas(16) FillExtrusionDrawablePropsUBO { - /* 0 */ Color color; - /* 16 */ std::array light_color; - /* 28 */ float pad1; - /* 32 */ std::array light_position; - /* 44 */ float base; - /* 48 */ float height; - /* 52 */ float light_intensity; - /* 56 */ float vertical_gradient; - /* 60 */ float opacity; - /* 64 */ float fade; - /* 68 */ float pad2, pad3, pad4; - /* 80 */ -}; -static_assert(sizeof(FillExtrusionDrawablePropsUBO) == 5 * 16); - -static const StringIdentity idFillExtrusionDrawableUBOName = StringIndexer::get("FillExtrusionDrawableUBO"); -static const StringIdentity idFillExtrusionDrawablePropsUBOName = StringIndexer::get("FillExtrusionDrawablePropsUBO"); -static const StringIdentity idTexImageName = StringIndexer::get("u_image"); +const StringIdentity idFillExtrusionDrawableUBOName = StringIndexer::get("FillExtrusionDrawableUBO"); +const StringIdentity idFillExtrusionDrawablePropsUBOName = StringIndexer::get("FillExtrusionDrawablePropsUBO"); +const StringIdentity idExpressionInputsUBOName = StringIndexer::get("ExpressionInputsUBO"); +const StringIdentity idFillExtrusionPermutationUBOName = StringIndexer::get("FillExtrusionPermutationUBO"); +const StringIdentity idTexImageName = StringIndexer::get("u_image"); } // namespace @@ -102,6 +76,32 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, propsBuffer->update(¶msUBO, sizeof(paramsUBO)); } +#if MLN_RENDER_BACKEND_METAL + const auto zoom = parameters.state.getZoom(); + if (propertiesChanged) { + const FillExtrusionPermutationUBO permutationUBO = { + /* .color = */ {/*.source=*/getAttributeSource("a_color"), /*.expression=*/{}}, + /* .base = */ {/*.source=*/getAttributeSource("a_base"), /*.expression=*/{}}, + /* .height = */ {/*.source=*/getAttributeSource("a_height"), /*.expression=*/{}}, + /* .overdrawInspector = */ overdrawInspector, + /* .pad = */ 0, + 0, + 0, + 0}; + + if (permutationUniformBuffer) { + permutationUniformBuffer->update(&permutationUBO, sizeof(permutationUBO)); + } else { + permutationUniformBuffer = context.createUniformBuffer(&permutationUBO, sizeof(permutationUBO)); + } + propertiesChanged = false; + } + if (!expressionUniformBuffer) { + const auto expressionUBO = buildExpressionUBO(zoom, parameters.frameCount); + expressionUniformBuffer = context.createUniformBuffer(&expressionUBO, sizeof(expressionUBO)); + } +#endif + layerGroup.visitDrawables([&](gfx::Drawable& drawable) { auto& uniforms = drawable.mutableUniformBuffers(); uniforms.addOrReplace(idFillExtrusionDrawablePropsUBOName, propsBuffer); @@ -149,6 +149,11 @@ void FillExtrusionLayerTweaker::execute(LayerGroupBase& layerGroup, /* .pad = */ 0}; uniforms.createOrUpdate(idFillExtrusionDrawableUBOName, &drawableUBO, context); + +#if MLN_RENDER_BACKEND_METAL + uniforms.addOrReplace(idExpressionInputsUBOName, expressionUniformBuffer); + uniforms.addOrReplace(idFillExtrusionPermutationUBOName, permutationUniformBuffer); +#endif // MLN_RENDER_BACKEND_METAL }); } diff --git a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp index 7e08ae9a3..4d3cef45c 100644 --- a/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp +++ b/src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.hpp @@ -25,26 +25,11 @@ public: private: gfx::UniformBufferPtr propsBuffer; -}; -/// Evaluated properties that depend on the tile -struct alignas(16) FillExtrusionDrawableTilePropsUBO { - /* 0 */ std::array pattern_from; - /* 16 */ std::array pattern_to; - /* 32 */ +#if MLN_RENDER_BACKEND_METAL + gfx::UniformBufferPtr permutationUniformBuffer; + gfx::UniformBufferPtr expressionUniformBuffer; +#endif // MLN_RENDER_BACKEND_METAL }; -static_assert(sizeof(FillExtrusionDrawableTilePropsUBO) == 2 * 16); - -/// Attribute interpolations -struct alignas(16) FillExtrusionInterpolateUBO { - /* 0 */ float base_t; - /* 4 */ float height_t; - /* 8 */ float color_t; - /* 12 */ float pattern_from_t; - /* 16 */ float pattern_to_t; - /* 20 */ float pad1, pad2, pad3; - /* 32 */ -}; -static_assert(sizeof(FillExtrusionInterpolateUBO) == 2 * 16); } // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp index 9e7ceaf26..178648909 100644 --- a/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -7,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -25,20 +26,19 @@ #include #include #include +#include #include #endif // MLN_DRAWABLE_RENDERER namespace mbgl { using namespace style; +using namespace shaders; namespace { #if MLN_DRAWABLE_RENDERER -constexpr std::string_view FillExtrusionShaderName = "FillExtrusionShader"; -constexpr std::string_view FillExtrusionPatternShaderName = "FillExtrusionPatternShader"; - static const StringIdentity idPosAttribName = StringIndexer::get("a_pos"); static const StringIdentity idNormAttribName = StringIndexer::get("a_normal_ed"); @@ -293,10 +293,10 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, } if (!fillExtrusionGroup) { - fillExtrusionGroup = shaders.getShaderGroup(std::string(FillExtrusionShaderName)); + fillExtrusionGroup = shaders.getShaderGroup("FillExtrusionShader"); } if (!fillExtrusionPatternGroup) { - fillExtrusionPatternGroup = shaders.getShaderGroup(std::string(FillExtrusionPatternShaderName)); + fillExtrusionPatternGroup = shaders.getShaderGroup("FillExtrusionPatternShader"); } auto* tileLayerGroup = static_cast(layerGroup.get()); @@ -410,7 +410,6 @@ void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, const auto shader = std::static_pointer_cast( shaderGroup->getOrCreateShader(context, uniformProps)); if (!shader) { - assert(false); continue; } diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 445f93029..1b2a14615 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -1,12 +1,29 @@ #include -#include + +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include +#include + +#if MLN_RENDER_BACKEND_METAL +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#endif // MLN_RENDER_BACKEND_METAL namespace mbgl { @@ -88,8 +105,18 @@ gfx::DepthMode PaintParameters::depthModeFor3D() const { void PaintParameters::clearStencil() { nextStencilID = 1; - context.clearStencilBuffer(0b00000000); tileClippingMaskIDs.clear(); + +#if MLN_RENDER_BACKEND_METAL + // Metal doesn't have an equivalent of `glClear`, so we clear the buffer by drawing zero to (0:0,0) + std::set ids{{0, 0, 0}}; + auto f = [](const auto& ii) -> const UnwrappedTileID& { + return ii; + }; + renderTileClippingMasks(ids.cbegin(), ids.cend(), std::move(f), /*clear=*/true); +#else + context.clearStencilBuffer(0b00000000); +#endif } namespace { @@ -111,14 +138,17 @@ bool tileIDsIdentical(TIter beg, TIter end, GetTileIDFunc& f, const TileM } // namespace void PaintParameters::renderTileClippingMasks(const std::set& tileIDs) { - renderTileClippingMasks( - tileIDs.cbegin(), tileIDs.cend(), [](const auto& ii) -> const UnwrappedTileID& { return ii; }); + auto f = [](const auto& ii) -> const UnwrappedTileID& { + return ii; + }; + renderTileClippingMasks(tileIDs.cbegin(), tileIDs.cend(), std::move(f), /*clear=*/false); } void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) { - renderTileClippingMasks((*renderTiles).cbegin(), - (*renderTiles).cend(), - [](const auto& ii) -> const UnwrappedTileID& { return ii.get().id; }); + auto f = [](const auto& ii) -> const UnwrappedTileID& { + return ii.get().id; + }; + renderTileClippingMasks((*renderTiles).cbegin(), (*renderTiles).cend(), std::move(f), /*clear=*/false); } void PaintParameters::clearTileClippingMasks() { @@ -128,18 +158,162 @@ void PaintParameters::clearTileClippingMasks() { } template -void PaintParameters::renderTileClippingMasks(TIter beg, TIter end, GetTileIDFunc&& f) { +void PaintParameters::renderTileClippingMasks(TIter beg, TIter end, GetTileIDFunc&& f, bool clear) { if (tileIDsIdentical(beg, end, f, tileClippingMaskIDs)) { // The current stencil mask is for this source already; no need to draw another one. return; } const auto count = std::distance(beg, end); - if (nextStencilID + count > maxStencilValue) { + if (!clear && nextStencilID + count > maxStencilValue) { // we'll run out of fresh IDs so we need to clear and start from scratch clearStencil(); } + if (!renderPass) { + assert(false); + return; + } + +#if !defined(NDEBUG) + const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks"); +#endif + +#if MLN_RENDER_BACKEND_METAL + using ShaderClass = shaders::ShaderSource; + const auto group = staticData.shaders->getShaderGroup("ClippingMaskProgram"); + if (!group) { + return; + } + const auto shader = std::static_pointer_cast(group->getOrCreateShader(context, {})); + if (!shader) { + return; + } + + const auto& mtlContext = static_cast(context); + const auto& mtlShader = static_cast(*shader); + const auto& mtlRenderPass = static_cast(*renderPass); + const auto& encoder = mtlRenderPass.getMetalEncoder(); + const auto colorMode = gfx::ColorMode::disabled(); + + constexpr std::size_t indexCount = 6; + std::optional indexRes; + + // TODO: refactor to use `Context::makeDepthStencilState` + const auto init = [&]() { + // TODO: a lot of this can be cached + // Create a vertex buffer from the fixed tile coordinates + const auto vertices = RenderStaticData::tileVertices(); + constexpr auto vertexSize = sizeof(decltype(vertices)::Vertex::a1); + auto vertexRes = mtlContext.createBuffer(vertices.data(), vertices.bytes(), gfx::BufferUsageType::StaticDraw); + if (!vertexRes) { + return; + } + encoder->setVertexBuffer(vertexRes.getMetalBuffer().get(), /*offset=*/0, ShaderClass::attributes[0].index); + + // A vertex descriptor tells Metal what's in the vertex buffer + auto vertDesc = NS::RetainPtr(MTL::VertexDescriptor::vertexDescriptor()); + auto attribDesc = NS::TransferPtr(MTL::VertexAttributeDescriptor::alloc()->init()); + auto layoutDesc = NS::TransferPtr(MTL::VertexBufferLayoutDescriptor::alloc()->init()); + if (!vertDesc || !attribDesc || !layoutDesc) { + return; + } + attribDesc->setBufferIndex(ShaderClass::attributes[0].index); + attribDesc->setOffset(0); + attribDesc->setFormat(MTL::VertexFormatShort2); + layoutDesc->setStride(static_cast(vertexSize)); + layoutDesc->setStepFunction(MTL::VertexStepFunctionPerVertex); + layoutDesc->setStepRate(1); + vertDesc->attributes()->setObject(attribDesc.get(), 0); + vertDesc->layouts()->setObject(layoutDesc.get(), 0); + + // Create a buffer from the fixed tile indexes + const auto indexes = RenderStaticData::quadTriangleIndices(); + assert(indexes.elements() == indexCount); + indexRes.emplace(mtlContext.createBuffer(indexes.data(), indexes.bytes(), gfx::BufferUsageType::StaticDraw)); + if (!indexRes || !*indexRes) { + return; + } + + // gfx::CullFaceMode::disabled(); + encoder->setCullMode(MTL::CullModeNone); + + auto stencilDescriptor = NS::TransferPtr(MTL::StencilDescriptor::alloc()->init()); + if (!stencilDescriptor) { + return; + } + stencilDescriptor->setStencilCompareFunction(MTL::CompareFunction::CompareFunctionAlways); + stencilDescriptor->setStencilFailureOperation(MTL::StencilOperation::StencilOperationKeep); + stencilDescriptor->setDepthFailureOperation(MTL::StencilOperation::StencilOperationKeep); + stencilDescriptor->setDepthStencilPassOperation(MTL::StencilOperation::StencilOperationReplace); + stencilDescriptor->setReadMask(0); + stencilDescriptor->setWriteMask(0b11111111); + + auto depthStencilDescriptor = NS::TransferPtr(MTL::DepthStencilDescriptor::alloc()->init()); + if (!depthStencilDescriptor) { + return; + } + depthStencilDescriptor->setDepthCompareFunction(MTL::CompareFunction::CompareFunctionAlways); + depthStencilDescriptor->setDepthWriteEnabled(false); + depthStencilDescriptor->setFrontFaceStencil(stencilDescriptor.get()); + depthStencilDescriptor->setBackFaceStencil(stencilDescriptor.get()); + + auto& device = mtlContext.getBackend().getDevice(); + auto depthStencilState = NS::TransferPtr(device->newDepthStencilState(depthStencilDescriptor.get())); + if (!depthStencilState) { + return; + } + encoder->setDepthStencilState(depthStencilState.get()); + + // Create a render pipeline state, telling Metal how to render the primitives + const auto& renderPassDescriptor = mtlRenderPass.getDescriptor(); + if (auto state = mtlShader.getRenderPipelineState(renderPassDescriptor, vertDesc, colorMode)) { + encoder->setRenderPipelineState(state.get()); + } else { + return; + } + }; + + // For each tile in the set... + for (auto i = beg; i != end; ++i) { + const auto& tileID = f(*i); + + const int32_t stencilID = clear ? 0 : nextStencilID; + if (!clear) { + const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID)); + if (result.second) { + // inserted + nextStencilID++; + } else { + // already present + continue; + } + } + + if (!indexRes) { + init(); + } + + encoder->setStencilReferenceValue(stencilID); + + const auto ubo = shaders::ClipUBO{/* .matrix = */ util::cast(matrixForTile(tileID))}; + // Create a buffer for the per-tile UBO data + if (auto uboRes = mtlContext.createBuffer(&ubo, sizeof(ubo), gfx::BufferUsageType::StaticDraw)) { + encoder->setVertexBuffer(uboRes.getMetalBuffer().get(), /*offset=*/0, ShaderClass::uniforms[0].index); + } else { + break; + } + + encoder->drawIndexedPrimitives(MTL::PrimitiveType::PrimitiveTypeTriangle, + static_cast(indexCount), + MTL::IndexType::IndexTypeUInt16, + indexRes->getMetalBuffer().get(), + /*indexOffset=*/0, + /*instanceCount=*/1, + /*baseVertex=*/0, + /*baseInstance=*/0); + } +#else // !MLN_RENDER_BACKEND_METAL auto program = staticData.shaders->getLegacyGroup().get(); if (!program) { @@ -188,6 +362,7 @@ void PaintParameters::renderTileClippingMasks(TIter beg, TIter end, GetTileIDFun ClippingMaskProgram::TextureBindings{}, "clipping/" + util::toString(stencilID)); } +#endif // MLN_RENDER_BACKEND_METAL } gfx::StencilMode PaintParameters::stencilModeForClipping(const UnwrappedTileID& tileID) const { diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 7ccfe8867..16b3ec799 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -118,7 +118,7 @@ private: template using GetTileIDFunc = std::function; template - void renderTileClippingMasks(TIter beg, TIter end, GetTileIDFunc&&); + void renderTileClippingMasks(TIter beg, TIter end, GetTileIDFunc&&, bool clear); // This needs to be an ordered map so that we have the same order as the renderTiles. std::map tileClippingMaskIDs; diff --git a/src/mbgl/shaders/mtl/shader_program.cpp b/src/mbgl/shaders/mtl/shader_program.cpp index 6a77f9ff6..bb07378ae 100644 --- a/src/mbgl/shaders/mtl/shader_program.cpp +++ b/src/mbgl/shaders/mtl/shader_program.cpp @@ -138,6 +138,11 @@ MTLRenderPipelineStatePtr ShaderProgram::getRenderPipelineState(const gfx::Rende colorTarget->setSourceAlphaBlendFactor(srcFactor); colorTarget->setDestinationRGBBlendFactor(destFactor); colorTarget->setDestinationAlphaBlendFactor(destFactor); + + colorTarget->setWriteMask((colorMode.mask.r ? MTL::ColorWriteMaskRed : MTL::ColorWriteMaskNone) | + (colorMode.mask.g ? MTL::ColorWriteMaskGreen : MTL::ColorWriteMaskNone) | + (colorMode.mask.b ? MTL::ColorWriteMaskBlue : MTL::ColorWriteMaskNone) | + (colorMode.mask.a ? MTL::ColorWriteMaskAlpha : MTL::ColorWriteMaskNone)); } if (depthFormat) { @@ -170,12 +175,25 @@ std::optional ShaderProgram::getSamplerLocation(const StringIdentity i } void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) { - vertexAttributes.add(StringIndexer::get(info.name.data()), static_cast(info.index), info.dataType, info.count); + const auto index = static_cast(info.index); +#if !defined(NDEBUG) + // Indexes must be unique, if there's a conflict check the `attributes` array in the shader + vertexAttributes.visitAttributes( + [&](auto, const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); + uniformBlocks.visit([&](auto, const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); +#endif + vertexAttributes.add(StringIndexer::get(info.name), index, info.dataType, info.count); } void ShaderProgram::initUniformBlock(const shaders::UniformBlockInfo& info) { - if (const auto& block_ = uniformBlocks.add( - StringIndexer::get(info.name.data()), static_cast(info.index), info.size)) { + const auto index = static_cast(info.index); +#if !defined(NDEBUG) + // Indexes must be unique, if there's a conflict check the `attributes` array in the shader + vertexAttributes.visitAttributes( + [&](auto, const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); + uniformBlocks.visit([&](auto, const gfx::UniformBlock& block) { assert(block.getIndex() != index); }); +#endif + if (const auto& block_ = uniformBlocks.add(StringIndexer::get(info.name), index, info.size)) { auto& block = static_cast(*block_); block.setBindVertex(info.vertex); block.setBindFragment(info.fragment); diff --git a/src/mbgl/util/string_indexer.cpp b/src/mbgl/util/string_indexer.cpp index 3f2f6d9fb..98f83f4ff 100644 --- a/src/mbgl/util/string_indexer.cpp +++ b/src/mbgl/util/string_indexer.cpp @@ -6,8 +6,25 @@ namespace mbgl { namespace { const std::string empty; +constexpr std::size_t initialCapacity = 100; +} // namespace + +StringIndexer::StringIndexer() + : stringToIdentity(initialCapacity) { + identityToString.reserve(initialCapacity); } +// For now, we have to make a copy to search the string-keyed container. +StringIdentity StringIndexer::get(const char* string) { + return get(std::string(string)); +} +// Once we can search using a string_view this should be the primary version. +StringIdentity StringIndexer::get(std::string_view string) { + return get(std::string(string)); +} + +// We take a const reference rather than an lvalue (copy) under the assumption that most calls will +// find a match and insertions where we could move are rare. StringIdentity StringIndexer::get(const std::string& string) { { std::shared_lock readerLock(instance().sharedMutex); diff --git a/test/util/string_indexer.test.cpp b/test/util/string_indexer.test.cpp index ba1f0f95e..cf4c6eb1d 100644 --- a/test/util/string_indexer.test.cpp +++ b/test/util/string_indexer.test.cpp @@ -14,7 +14,8 @@ TEST(StringIndexer, AddStrings) { EXPECT_EQ(id1, 0); EXPECT_EQ(StringIndexer::size(), 1); - const auto id2 = StringIndexer::get("test string2"); + const std::string_view s2 = "test string2"; + const auto id2 = StringIndexer::get(s2); EXPECT_EQ(id2, 1); EXPECT_EQ(StringIndexer::size(), 2); }