diff --git a/include/mbgl/gfx/rendering_stats.hpp b/include/mbgl/gfx/rendering_stats.hpp index 7cc3edca6..4edbc1600 100644 --- a/include/mbgl/gfx/rendering_stats.hpp +++ b/include/mbgl/gfx/rendering_stats.hpp @@ -17,6 +17,9 @@ struct RenderingStats { int memIndexBuffers; int memVertexBuffers; + int stencilClears = 0; + int stencilUpdates = 0; + RenderingStats& operator+=(const RenderingStats& right); }; @@ -30,8 +33,12 @@ inline RenderingStats& RenderingStats::operator+=(const RenderingStats& r) { memTextures += r.memTextures; memIndexBuffers += r.memIndexBuffers; memVertexBuffers += r.memVertexBuffers; + + stencilClears += r.stencilClears; + stencilUpdates += r.stencilUpdates; + return *this; } } // namespace gfx -} // namespace mbgl \ No newline at end of file +} // namespace mbgl diff --git a/include/mbgl/mtl/context.hpp b/include/mbgl/mtl/context.hpp index 3468d4826..00b436d38 100644 --- a/include/mbgl/mtl/context.hpp +++ b/include/mbgl/mtl/context.hpp @@ -11,15 +11,19 @@ #include #include -#include - #include #include #include +#include namespace mbgl { class ProgramParameters; +class RenderStaticData; + +namespace shaders { +struct ClipUBO; +} // namespace shaders namespace mtl { @@ -115,6 +119,10 @@ public: /// Get a reusable buffer containing the standard fixed tile indexes const BufferResource& getTileIndexBuffer(); + bool renderTileClippingMasks(gfx::RenderPass& renderPass, + RenderStaticData& staticData, + const std::vector& tileUBOs); + private: RendererBackend& backend; bool cleanupOnDestruction = true; diff --git a/include/mbgl/shaders/mtl/clipping_mask.hpp b/include/mbgl/shaders/mtl/clipping_mask.hpp index e86707dfe..68da228a1 100644 --- a/include/mbgl/shaders/mtl/clipping_mask.hpp +++ b/include/mbgl/shaders/mtl/clipping_mask.hpp @@ -10,9 +10,11 @@ namespace shaders { struct alignas(16) ClipUBO { /* 0 */ std::array matrix; - /* 64 */ + /* 64 */ std::uint32_t stencil_ref; + /* 68 */ std::uint32_t pad1, pad2, pad3; + /* 80 */ }; -static_assert(sizeof(ClipUBO) == 4 * 16); +static_assert(sizeof(ClipUBO) == 5 * 16); template <> struct ShaderSource { @@ -30,9 +32,11 @@ using namespace metal; struct alignas(16) ClipUBO { /* 0 */ float4x4 matrix; - /* 64 */ + /* 64 */ uint32_t stencil_ref; + /* 68 */ uint32_t pad1, pad2, pad3; + /* 80 */ }; -static_assert(sizeof(ClipUBO) == 4 * 16, "unexpected padding"); +static_assert(sizeof(ClipUBO) == 5 * 16, "unexpected padding"); struct VertexStage { short2 position [[attribute(0)]]; @@ -40,17 +44,30 @@ struct VertexStage { struct FragmentStage { float4 position [[position, invariant]]; + uint8_t stencil_ref; +}; + +struct FragmentResult { + half4 color [[color(0)]]; + // target is `..._stencil8`, but using `uint8_t` here causes a compile error + uint16_t stencil_ref [[stencil]]; }; FragmentStage vertex vertexMain(VertexStage in [[stage_in]], - device const ClipUBO& clipUBO [[buffer(1)]]) { + uint16_t instanceID [[instance_id]], + device const ClipUBO* clipUBOs [[buffer(1)]]) { + device const ClipUBO& clipUBO = clipUBOs[instanceID]; return { - .position = clipUBO.matrix * float4(float2(in.position.xy), 0, 1) + .position = clipUBO.matrix * float4(float2(in.position.xy), 0, 1), + .stencil_ref = static_cast(clipUBO.stencil_ref), }; } -half4 fragment fragmentMain(FragmentStage in [[stage_in]]) { - return half4(1.0); +FragmentResult fragment fragmentMain(FragmentStage in [[stage_in]]) { + return { + .color = half4(1.0), + .stencil_ref = static_cast(in.stencil_ref), + }; } )"; }; diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 1eecdd458..4907dbb30 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -750,6 +750,8 @@ void Context::visualizeDepthBuffer([[maybe_unused]] const float depthRangeSize) void Context::clearStencilBuffer(const int32_t bits) { MBGL_CHECK_ERROR(glClearStencil(bits)); MBGL_CHECK_ERROR(glClear(GL_STENCIL_BUFFER_BIT)); + + stats.stencilClears++; } } // namespace gl diff --git a/src/mbgl/mtl/context.cpp b/src/mbgl/mtl/context.cpp index e41e7d2ab..dc24bfb5b 100644 --- a/src/mbgl/mtl/context.cpp +++ b/src/mbgl/mtl/context.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -202,6 +203,111 @@ const BufferResource& Context::getTileIndexBuffer() { return *tileIndexBuffer; } +namespace { +const auto clipMaskStencilMode = gfx::StencilMode{ + /*.test=*/gfx::StencilMode::Always(), + /*.ref=*/0, + /*.mask=*/0b11111111, + /*.fail=*/gfx::StencilOpType::Keep, + /*.depthFail=*/gfx::StencilOpType::Keep, + /*.pass=*/gfx::StencilOpType::Replace, +}; +const auto clipMaskDepthMode = gfx::DepthMode{ + /*.func=*/gfx::DepthFunctionType::Always, + /*.mask=*/gfx::DepthMaskType::ReadOnly, + /*.range=*/{0, 1}, +}; +} // namespace + +bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass, + RenderStaticData& staticData, + const std::vector& tileUBOs) { + using ShaderClass = shaders::ShaderSource; + const auto group = staticData.shaders->getShaderGroup("ClippingMaskProgram"); + if (!group) { + return false; + } + const auto shader = std::static_pointer_cast(group->getOrCreateShader(*this, {})); + if (!shader) { + return false; + } + + const auto& mtlShader = static_cast(*shader); + const auto& mtlRenderPass = static_cast(renderPass); + const auto& encoder = mtlRenderPass.getMetalEncoder(); + const auto colorMode = gfx::ColorMode::disabled(); + + // Create a vertex buffer from the fixed tile coordinates + constexpr auto vertexSize = sizeof(gfx::Vertex::a1); + const auto& vertexRes = getTileVertexBuffer(); + if (!vertexRes) { + assert(vertexSize == vertexRes.getSizeInBytes()); + return false; + } + + // Create a buffer from the fixed tile indexes + constexpr NS::UInteger indexCount = 6; + const auto indexRes = &getTileIndexBuffer(); + if (!indexRes) { + return false; + } + assert(indexCount * sizeof(std::uint16_t) == indexRes->getSizeInBytes()); + + if (auto depthStencilState = makeDepthStencilState(clipMaskDepthMode, clipMaskStencilMode, mtlRenderPass)) { + encoder->setDepthStencilState(depthStencilState.get()); + } else { + assert(!"Failed to create depth-stencil state"); + return false; + } + + // 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 false; + } + + 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 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 false; + } + + // Create a buffer for the UBO data. + // This could potentially be reused, but `PaintParameters` is recreated for each frame. + constexpr auto uboSize = sizeof(shaders::ClipUBO); + const auto uboRes = createBuffer(tileUBOs.data(), tileUBOs.size() * uboSize, gfx::BufferUsageType::StaticDraw); + if (!uboRes) { + return false; + } + + encoder->setCullMode(MTL::CullModeNone); + encoder->setVertexBuffer(vertexRes.getMetalBuffer().get(), /*offset=*/0, ShaderClass::attributes[0].index); + encoder->setVertexBuffer(uboRes.getMetalBuffer().get(), /*offset=*/0, ShaderClass::uniforms[0].index); + encoder->drawIndexedPrimitives(MTL::PrimitiveType::PrimitiveTypeTriangle, + indexCount, + MTL::IndexType::IndexTypeUInt16, + indexRes->getMetalBuffer().get(), + /*indexOffset=*/0, + /*instanceCount=*/static_cast(tileUBOs.size()), + /*baseVertex=*/0, + /*baseInstance=*/0); + + return true; +} + void Context::setDirtyState() {} std::unique_ptr Context::createOffscreenTexture(Size size, diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index dbde6452e..30097cc82 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -8,21 +8,12 @@ #include #include #include - -#if MLN_RENDER_BACKEND_METAL -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include +#if MLN_RENDER_BACKEND_METAL +#include +#include #endif // MLN_RENDER_BACKEND_METAL namespace mbgl { @@ -103,53 +94,73 @@ gfx::DepthMode PaintParameters::depthModeFor3D() const { return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, gfx::DepthMaskType::ReadWrite, {0.0, depthRangeSize}}; } +namespace { + +template +using GetTileIDFunc = const UnwrappedTileID& (*)(const typename TIter::value_type&); + +using TileMaskIDMap = std::map; + +// `std::includes` adapted to extract tile IDs from arbitrary collection iterators +template +bool includes(I1 beg1, const I1 end1, I2 beg2, const I2 end2, const GetTileIDFunc& unwrap) { + for (; beg2 != end2; ++beg1) { + if (beg1 == end1) { + return false; + } + const auto id2 = unwrap(*beg2); + if (id2 < beg1->first) { + return false; + } else if (!(beg1->first < id2)) { + ++beg2; + } + } + return true; +} + +// Check whether the given set of tile IDs is a subset of the ones already rendered +template +bool tileIDsCovered(TIter beg, TIter end, GetTileIDFunc& unwrap, const TileMaskIDMap& idMap) { + if (idMap.size() < static_cast(std::distance(beg, end))) { + return false; + } + assert(std::is_sorted(beg, end, [=](const auto& a, const auto& b) { return unwrap(a) < unwrap(b); })); + return includes(idMap.cbegin(), idMap.cend(), beg, end, unwrap); +} + +#if MLN_LEGACY_RENDERER +const UnwrappedTileID& unwrapRenderTiles(const RenderTiles::element_type::value_type& iter) { + return iter.get().id; +} +#else // !MLN_LEGACY_RENDERER +const UnwrappedTileID& unwrapIdentity(const UnwrappedTileID& value) { + return value; +} +#endif // MLN_LEGACY_RENDERER +} // namespace + void PaintParameters::clearStencil() { nextStencilID = 1; 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); + static const std::set ids{{0, 0, 0}}; + renderTileClippingMasks(ids.cbegin(), ids.cend(), &unwrapIdentity, /*clear=*/true); #else context.clearStencilBuffer(0b00000000); #endif } -namespace { - -template -using GetTileIDFunc = std::function; -using TileMaskIDMap = std::map; - -// Detects a difference in keys of renderTiles and tileClippingMaskIDs -template -bool tileIDsIdentical(TIter beg, TIter end, GetTileIDFunc& f, const TileMaskIDMap& idMap) { - if (static_cast(std::distance(beg, end)) != idMap.size()) { - return false; - } - assert(std::is_sorted(beg, end, [&f](const auto& a, const auto& b) { return f(a) < f(b); })); - return std::equal(beg, end, idMap.cbegin(), [&f](const auto& ii, const auto& pair) { return f(ii) == pair.first; }); -} - -} // namespace - -void PaintParameters::renderTileClippingMasks(const std::set& tileIDs) { - auto f = [](const auto& ii) -> const UnwrappedTileID& { - return ii; - }; - renderTileClippingMasks(tileIDs.cbegin(), tileIDs.cend(), std::move(f), /*clear=*/false); -} - +#if MLN_LEGACY_RENDERER void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) { - auto f = [](const auto& ii) -> const UnwrappedTileID& { - return ii.get().id; - }; - renderTileClippingMasks((*renderTiles).cbegin(), (*renderTiles).cend(), std::move(f), /*clear=*/false); + renderTileClippingMasks((*renderTiles).cbegin(), (*renderTiles).cend(), &unwrapRenderTiles, /*clear=*/false); } +#else // !MLN_LEGACY_RENDERER +void PaintParameters::renderTileClippingMasks(const std::set& tileIDs) { + renderTileClippingMasks(tileIDs.cbegin(), tileIDs.cend(), &unwrapIdentity, /*clear=*/false); +} +#endif // MLN_LEGACY_RENDERER void PaintParameters::clearTileClippingMasks() { if (!tileClippingMaskIDs.empty()) { @@ -157,135 +168,31 @@ void PaintParameters::clearTileClippingMasks() { } } -#if MLN_RENDER_BACKEND_METAL -namespace { -const auto clipMaskStencilMode = gfx::StencilMode{ - /*.test=*/gfx::StencilMode::Always(), - /*.ref=*/0, - /*.mask=*/0b11111111, - /*.fail=*/gfx::StencilOpType::Keep, - /*.depthFail=*/gfx::StencilOpType::Keep, - /*.pass=*/gfx::StencilOpType::Replace, -}; -const auto clipMaskDepthMode = gfx::DepthMode{ - /*.func=*/gfx::DepthFunctionType::Always, - /*.mask=*/gfx::DepthMaskType::ReadOnly, - /*.range=*/{0, 1}, -}; -} // namespace -#endif // MLN_RENDER_BACKEND_METAL - template -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 (!clear && nextStencilID + count > maxStencilValue) { - // we'll run out of fresh IDs so we need to clear and start from scratch - clearStencil(); - } - +void PaintParameters::renderTileClippingMasks(TIter beg, TIter end, GetTileIDFunc unwrap, bool clear) { if (!renderPass) { assert(false); return; } -#if !defined(NDEBUG) - const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks"); -#endif + if (!clear) { + if (tileIDsCovered(beg, end, unwrap, 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) { + // we'll run out of fresh IDs so we need to clear and start from scratch + clearStencil(); + } + } #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; - } - - 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(); - - const mtl::BufferResource* indexRes = nullptr; - constexpr NS::UInteger indexCount = 6; - - const auto init = [&]() { - // Create a vertex buffer from the fixed tile coordinates - constexpr auto vertexSize = sizeof(gfx::Vertex::a1); - const auto& vertexRes = mtlContext.getTileVertexBuffer(); - if (!vertexRes) { - assert(vertexSize == vertexRes.getSizeInBytes()); - return false; - } - - // Create a buffer from the fixed tile indexes - if (!indexRes) { - indexRes = &mtlContext.getTileIndexBuffer(); - if (!indexRes) { - return false; - } - assert(indexCount * sizeof(std::uint16_t) == indexRes->getSizeInBytes()); - } - - if (auto depthStencilState = mtlContext.makeDepthStencilState( - clipMaskDepthMode, clipMaskStencilMode, mtlRenderPass)) { - encoder->setDepthStencilState(depthStencilState.get()); - } else { - assert(!"Failed to create depth-stencil state"); - return false; - } - - // 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 false; - } - - 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 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 false; - } - - encoder->setCullMode(MTL::CullModeNone); - encoder->setVertexBuffer(vertexRes.getMetalBuffer().get(), /*offset=*/0, ShaderClass::attributes[0].index); - - return true; - }; - - std::vector maskIDs; - std::vector tileIndexes; + // Assign a stencil ID and build a UBO for each tile in the set std::vector tileUBOs; - - const auto tileCount = std::distance(beg, end); - maskIDs.reserve(tileCount); - tileIndexes.reserve(tileCount); - tileUBOs.reserve(tileCount); - - // For each tile in the set, work out the stencil ID for (auto i = beg; i != end; ++i) { - const auto& tileID = f(*i); + const auto& tileID = unwrap(*i); const int32_t stencilID = clear ? 0 : nextStencilID; if (!clear) { @@ -299,38 +206,26 @@ void PaintParameters::renderTileClippingMasks(TIter beg, TIter end, GetTileIDFun } } - maskIDs.push_back(stencilID); - tileUBOs.emplace_back(shaders::ClipUBO{/* .matrix = */ util::cast(matrixForTile(tileID))}); + if (tileUBOs.empty()) { + tileUBOs.reserve(std::distance(beg, end)); + } + + tileUBOs.emplace_back(shaders::ClipUBO{/*.matrix=*/util::cast(matrixForTile(tileID)), + /*.stencil_ref=*/stencilID, + /*.pad=*/0, + 0, + 0}); } - // Set up the encoder if we have anything to draw - if (tileUBOs.empty() || !init()) { - return; + if (!tileUBOs.empty()) { +#if !defined(NDEBUG) + const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks"); +#endif + + auto& mtlContext = static_cast(context); + mtlContext.renderTileClippingMasks(*renderPass, staticData, tileUBOs); } - // Create a buffer for the UBO data. - // This could potentially be reused, but `PaintParameters` is recreated for each frame. - constexpr auto uboSize = sizeof(shaders::ClipUBO); - const auto uboRes = mtlContext.createBuffer( - tileUBOs.data(), tileUBOs.size() * uboSize, gfx::BufferUsageType::StaticDraw); - const auto uboPtr = uboRes.getMetalBuffer().get(); - if (!uboPtr) { - return; - } - - // For each tile in the set... - for (std::size_t ii = 0; ii < maskIDs.size(); ++ii) { - encoder->setStencilReferenceValue(maskIDs[ii]); - encoder->setVertexBuffer(uboPtr, /*offset=*/ii * uboSize, ShaderClass::uniforms[0].index); - encoder->drawIndexedPrimitives(MTL::PrimitiveType::PrimitiveTypeTriangle, - 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(); @@ -338,11 +233,13 @@ void PaintParameters::renderTileClippingMasks(TIter beg, TIter end, GetTileIDFun return; } + static_cast(context).renderingStats().stencilUpdates++; + const style::Properties<>::PossiblyEvaluated properties{}; const ClippingMaskProgram::Binders paintAttributeData(properties, 0); for (auto i = beg; i != end; ++i) { - const auto& tileID = f(*i); + const auto& tileID = unwrap(*i); const int32_t stencilID = nextStencilID; const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID)); diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 16b3ec799..295b1ae81 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -95,8 +95,11 @@ public: // Stencil handling public: +#if MLN_LEGACY_RENDERER void renderTileClippingMasks(const RenderTiles&); +#else void renderTileClippingMasks(const std::set&); +#endif // MLN_LEGACY_RENDERER /// Clear any tile masks and the stencil buffer, if necessary void clearTileClippingMasks(); @@ -116,9 +119,9 @@ public: private: template - using GetTileIDFunc = std::function; + using GetTileIDFunc = const UnwrappedTileID& (*)(const typename TIter::value_type&); template - void renderTileClippingMasks(TIter beg, TIter end, GetTileIDFunc&&, bool clear); + void renderTileClippingMasks(TIter beg, TIter end, GetTileIDFunc unwrap, 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/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp index 3787d6d8b..e1e78b693 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -45,7 +45,7 @@ void RenderCustomGeometrySource::update(Immutable baseImpl_ needsRelayout, parameters, *baseImpl, - util::tileSize_I, + impl().getTileOptions()->tileSize, impl().getZoomRange(), {}, [&](const OverscaledTileID& tileID) { diff --git a/src/mbgl/shaders/mtl/clipping_mask.cpp b/src/mbgl/shaders/mtl/clipping_mask.cpp index 9cd391a1e..107537d95 100644 --- a/src/mbgl/shaders/mtl/clipping_mask.cpp +++ b/src/mbgl/shaders/mtl/clipping_mask.cpp @@ -3,14 +3,15 @@ namespace mbgl { namespace shaders { -const std::array ShaderSource::attributes = { +using ShaderType = ShaderSource; + +const std::array ShaderType::attributes = { AttributeInfo{0, gfx::AttributeDataType::Float3, 1, "a_pos"}, }; -const std::array ShaderSource::uniforms = - { - UniformBlockInfo{1, true, false, sizeof(ClipUBO), "ClipUBO"}, +const std::array ShaderType::uniforms = { + UniformBlockInfo{1, true, false, sizeof(ClipUBO), "ClipUBO"}, }; -const std::array ShaderSource::textures = {}; +const std::array ShaderType::textures = {}; } // namespace shaders } // namespace mbgl diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp index ca0e95198..f668ca0ee 100644 --- a/test/map/map.test.cpp +++ b/test/map/map.test.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1600,3 +1601,40 @@ TEST(Map, ObserveShaderRegistration) { test.frontend.render(test.map); EXPECT_EQ(observedRegistry, false); } + +TEST(Map, StencilOverflow) { + MapTest<> test; + + const auto& backend = test.frontend.getBackend(); + gfx::BackendScope scope{*backend}; + + auto& style = test.map.getStyle(); + style.loadJSON("{}"); + + // Create a tile source with small tiles that produces enough tiles to + // exceed the stencil ID limit and causes a clearing of the stencil buffer. + CustomGeometrySource::Options options; + options.tileOptions.tileSize = util::tileSize_I / 32; + options.fetchTileFunction = [&](const CanonicalTileID& tileID) { + if (auto* customSrc = static_cast(style.getSource("custom"))) { + customSrc->setTileData(tileID, {}); + } + }; + + style.addSource(std::make_unique("custom", std::move(options))); + style.addLayer(std::make_unique("fill", "custom")); + + test.map.jumpTo(CameraOptions().withZoom(5)); + test.frontend.render(test.map); + + // In drawable builds, no drawables are built because no bucket/tiledata is available. +#if !MLN_DRAWABLE_RENDERER + // TODO: Collect stats on Metal context +#if MLN_RENDER_BACKEND_OPENGL + const auto& context = static_cast(backend->getContext()); + ASSERT_LT(0, context.renderingStats().stencilClears); +#endif // MLN_RENDER_BACKEND_OPENGL +#endif // !MLN_DRAWABLE_RENDERER + + // TODO: confirm that the stencil masking actually worked +}