#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if MLN_DRAWABLE_RENDERER #include #include #include #include #include #include #include #endif // MLN_DRAWABLE_RENDERER namespace mbgl { using namespace style; using namespace shaders; namespace { inline const FillExtrusionLayer::Impl& impl_cast(const Immutable& impl) { assert(impl->getTypeInfo() == FillExtrusionLayer::Impl::staticTypeInfo()); return static_cast(*impl); } } // namespace RenderFillExtrusionLayer::RenderFillExtrusionLayer(Immutable _impl) : RenderLayer(makeMutable(std::move(_impl))), unevaluated(impl_cast(baseImpl).paint.untransitioned()) { styleDependencies = unevaluated.getDependencies(); } RenderFillExtrusionLayer::~RenderFillExtrusionLayer() = default; void RenderFillExtrusionLayer::transition(const TransitionParameters& parameters) { unevaluated = impl_cast(baseImpl).paint.transitioned(parameters, std::move(unevaluated)); } void RenderFillExtrusionLayer::evaluate(const PropertyEvaluationParameters& parameters) { const auto previousProperties = staticImmutableCast(evaluatedProperties); auto properties = makeMutable( staticImmutableCast(baseImpl), parameters.getCrossfadeParameters(), unevaluated.evaluate(parameters, previousProperties->evaluated)); passes = (properties->evaluated.get() > 0) ? RenderPass::Translucent : RenderPass::None; properties->renderPasses = mbgl::underlying_type(passes); evaluatedProperties = std::move(properties); #if MLN_DRAWABLE_RENDERER if (layerTweaker) { layerTweaker->updateProperties(evaluatedProperties); } #endif // MLN_DRAWABLE_RENDERER } bool RenderFillExtrusionLayer::hasTransition() const { return unevaluated.hasTransition(); } bool RenderFillExtrusionLayer::hasCrossfade() const { return getCrossfade(evaluatedProperties).t != 1; } bool RenderFillExtrusionLayer::is3D() const { return true; } #if MLN_LEGACY_RENDERER void RenderFillExtrusionLayer::render(PaintParameters& parameters) { assert(renderTiles); if (parameters.pass != RenderPass::Translucent) { return; } if (!parameters.shaders.getLegacyGroup().populate(fillExtrusionProgram)) return; if (!parameters.shaders.getLegacyGroup().populate(fillExtrusionPatternProgram)) return; const auto& evaluated = static_cast(*evaluatedProperties).evaluated; const auto& crossfade = static_cast(*evaluatedProperties).crossfade; if (evaluatedProperties->renderPasses == mbgl::underlying_type(RenderPass::None)) { return; } const auto depthMode = parameters.depthModeFor3D(); auto draw = [&](auto& programInstance, const auto& evaluated_, const auto& crossfade_, const gfx::StencilMode& stencilMode, const gfx::ColorMode& colorMode, auto& tileBucket, const auto& uniformValues, const std::optional& patternPositionA, const std::optional& patternPositionB, const auto& textureBindings, const std::string& uniqueName) { auto& paintPropertyBinders = tileBucket.paintPropertyBinders.at(getID()); paintPropertyBinders.setPatternParameters(patternPositionA, patternPositionB, crossfade_); const auto allUniformValues = programInstance.computeAllUniformValues( uniformValues, paintPropertyBinders, evaluated_, static_cast(parameters.state.getZoom())); const auto allAttributeBindings = programInstance.computeAllAttributeBindings( *tileBucket.vertexBuffer, paintPropertyBinders, evaluated_); checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); programInstance.draw(parameters.context, *parameters.renderPass, gfx::Triangles(), depthMode, stencilMode, colorMode, gfx::CullFaceMode::backCCW(), *tileBucket.indexBuffer, tileBucket.triangleSegments, allUniformValues, allAttributeBindings, textureBindings, getID() + "/" + uniqueName); }; if (unevaluated.get().isUndefined()) { // Draw solid color extrusions auto drawTiles = [&](const gfx::StencilMode& stencilMode_, const gfx::ColorMode& colorMode_, const std::string& name) { for (const RenderTile& tile : *renderTiles) { const LayerRenderData* renderData = getRenderDataForPass(tile, parameters.pass); if (!renderData) { continue; } auto& bucket = static_cast(*renderData->bucket); draw(*fillExtrusionProgram, evaluated, crossfade, stencilMode_, colorMode_, bucket, FillExtrusionProgram::layoutUniformValues( tile.translatedClipMatrix(evaluated.get(), evaluated.get(), parameters.state), parameters.state, evaluated.get(), parameters.evaluatedLight, evaluated.get()), {}, {}, FillExtrusionProgram::TextureBindings{}, name); } }; if (evaluated.get() == 1) { // Draw opaque extrusions drawTiles(gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), "color"); } else { // Draw transparent buildings in two passes so that only the closest // surface is drawn. First draw all the extrusions into only the // depth buffer. No colors are drawn. drawTiles(gfx::StencilMode::disabled(), gfx::ColorMode::disabled(), "depth"); // Then draw all the extrusions a second time, only coloring // fragments if they have the same depth value as the closest // fragment in the previous pass. Use the stencil buffer to prevent // the second draw in cases where we have coincident polygons. drawTiles(parameters.stencilModeFor3D(), parameters.colorModeForRenderPass(), "color"); } } else { // Draw textured extrusions const auto fillPatternValue = evaluated.get().constantOr( mbgl::Faded{"", ""}); auto drawTiles = [&](const gfx::StencilMode& stencilMode_, const gfx::ColorMode& colorMode_, const std::string& name) { for (const RenderTile& tile : *renderTiles) { const LayerRenderData* renderData = getRenderDataForPass(tile, parameters.pass); if (!renderData) { continue; } auto& bucket = static_cast(*renderData->bucket); const std::optional patternPosA = tile.getPattern(fillPatternValue.from.id()); const std::optional patternPosB = tile.getPattern(fillPatternValue.to.id()); const auto numTiles = std::pow(2, tile.id.canonical.z); const auto heightFactor = static_cast(-numTiles / util::tileSize_D / 8.0); draw(*fillExtrusionPatternProgram, evaluated, crossfade, stencilMode_, colorMode_, bucket, FillExtrusionPatternProgram::layoutUniformValues( tile.translatedClipMatrix(evaluated.get(), evaluated.get(), parameters.state), tile.getIconAtlasTexture()->getSize(), crossfade, tile.id, parameters.state, evaluated.get(), heightFactor, parameters.pixelRatio, parameters.evaluatedLight, evaluated.get()), patternPosA, patternPosB, FillExtrusionPatternProgram::TextureBindings{ textures::image::Value{tile.getIconAtlasTextureBinding(gfx::TextureFilterType::Linear)}, }, name); } }; // Draw transparent buildings in two passes so that only the closest // surface is drawn. First draw all the extrusions into only the depth // buffer. No colors are drawn. drawTiles(gfx::StencilMode::disabled(), gfx::ColorMode::disabled(), "depth"); // Then draw all the extrusions a second time, only coloring fragments // if they have the same depth value as the closest fragment in the // previous pass. Use the stencil buffer to prevent the second draw in // cases where we have coincident polygons. drawTiles(parameters.stencilModeFor3D(), parameters.colorModeForRenderPass(), "color"); } } #endif // MLN_LEGACY_RENDERER bool RenderFillExtrusionLayer::queryIntersectsFeature(const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float, const TransformState& transformState, const float pixelsToTileUnits, const mat4&, const FeatureState&) const { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry( queryGeometry, evaluated.get(), evaluated.get(), static_cast(transformState.getBearing()), pixelsToTileUnits); return util::polygonIntersectsMultiPolygon(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries()); } #if MLN_DRAWABLE_RENDERER void RenderFillExtrusionLayer::update(gfx::ShaderRegistry& shaders, gfx::Context& context, const TransformState&, const std::shared_ptr&, const RenderTree&, UniqueChangeRequestVec& changes) { if (!renderTiles || renderTiles->empty() || passes == RenderPass::None) { removeAllDrawables(); return; } // Set up a layer group if (!layerGroup) { if (auto layerGroup_ = context.createTileLayerGroup(layerIndex, /*initialCapacity=*/64, getID())) { setLayerGroup(std::move(layerGroup_), changes); } else { return; } } if (!layerTweaker) { layerTweaker = std::make_shared(getID(), evaluatedProperties); layerGroup->addLayerTweaker(layerTweaker); } if (!fillExtrusionGroup) { fillExtrusionGroup = shaders.getShaderGroup("FillExtrusionShader"); } if (!fillExtrusionPatternGroup) { fillExtrusionPatternGroup = shaders.getShaderGroup("FillExtrusionPatternShader"); } auto* tileLayerGroup = static_cast(layerGroup.get()); const auto& evaluated = static_cast(*evaluatedProperties).evaluated; constexpr auto drawPass = RenderPass::Translucent; stats.drawablesRemoved += tileLayerGroup->removeDrawablesIf([&](gfx::Drawable& drawable) { // If the render pass has changed or the tile has dropped out of the cover set, remove it. const auto& tileID = drawable.getTileID(); if (!(drawable.getRenderPass() & drawPass) || (tileID && !hasRenderTile(*tileID))) { return true; } return false; }); const auto layerPrefix = getID() + "/"; const auto hasPattern = !unevaluated.get().isUndefined(); const auto opaque = evaluated.get() >= 1; std::unique_ptr depthBuilder; std::unique_ptr colorBuilder; const auto& shaderGroup = hasPattern ? fillExtrusionPatternGroup : fillExtrusionGroup; if (!shaderGroup) { removeAllDrawables(); return; } tileLayerGroup->setStencilTiles(renderTiles); StringIDSetsPair propertiesAsUniforms; for (const RenderTile& tile : *renderTiles) { const auto& tileID = tile.getOverscaledTileID(); const auto* optRenderData = getRenderDataForPass(tile, drawPass); if (!optRenderData || !optRenderData->bucket || !optRenderData->bucket->hasData()) { removeTile(drawPass, tileID); continue; } const auto& renderData = *optRenderData; auto& bucket = static_cast(*renderData.bucket); const auto prevBucketID = getRenderTileBucketID(tileID); if (prevBucketID != util::SimpleIdentity::Empty && prevBucketID != bucket.getID()) { // This tile was previously set up from a different bucket, drop and re-create any drawables for it. removeTile(drawPass, tileID); } setRenderTileBucketID(tileID, bucket.getID()); gfx::DrawableTweakerPtr tweaker; if (depthBuilder) { depthBuilder->clearTweakers(); } if (colorBuilder) { colorBuilder->clearTweakers(); } const auto vertexCount = bucket.vertices.elements(); auto& binders = bucket.paintPropertyBinders.at(getID()); // If we already have drawables for this tile, update them. auto updateExisting = [&](gfx::Drawable& drawable) { if (drawable.getLayerTweaker() != layerTweaker) { // This drawable was produced on a previous style/bucket, and should not be updated. return false; } return true; }; if (updateTile(drawPass, tileID, std::move(updateExisting))) { continue; } propertiesAsUniforms.first.clear(); propertiesAsUniforms.second.clear(); auto vertexAttrs = context.createVertexAttributeArray(); vertexAttrs->readDataDrivenPaintProperties( binders, evaluated, propertiesAsUniforms, idFillExtrusionBaseVertexAttribute); const auto shader = std::static_pointer_cast( shaderGroup->getOrCreateShader(context, propertiesAsUniforms)); if (!shader) { continue; } // The non-pattern path in `render()` only uses two-pass rendering if there's translucency. // The pattern path always uses two passes. const auto doDepthPass = (!opaque || hasPattern); if (doDepthPass && !depthBuilder) { if (auto builder = context.createDrawableBuilder(layerPrefix + "depth")) { builder->setShader(shader); builder->setIs3D(true); builder->setEnableColor(false); builder->setRenderPass(drawPass); builder->setCullFaceMode(gfx::CullFaceMode::backCCW()); builder->setDrawPriority(0); if (tweaker) { builder->addTweaker(tweaker); } depthBuilder = std::move(builder); } } if (!colorBuilder) { if (auto builder = context.createDrawableBuilder(layerPrefix + "color")) { builder->setShader(shader); builder->setIs3D(true); builder->setEnableColor(true); builder->setColorMode(gfx::ColorMode::alphaBlended()); builder->setRenderPass(drawPass); builder->setCullFaceMode(gfx::CullFaceMode::backCCW()); builder->setDrawPriority(1); if (tweaker) { builder->addTweaker(tweaker); } colorBuilder = std::move(builder); } } if (hasPattern && !tweaker) { if (const auto& atlases = tile.getAtlasTextures()) { tweaker = std::make_shared(atlases, std::nullopt, idFillExtrusionImageTexture, /*isText=*/false, false, style::AlignmentType::Auto, false, false); if (depthBuilder) { depthBuilder->addTweaker(tweaker); } if (colorBuilder) { colorBuilder->addTweaker(tweaker); } } } if (const auto& attr = vertexAttrs->set(idFillExtrusionPosVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(FillExtrusionLayoutVertex, a1), /*vertexOffset=*/0, sizeof(FillExtrusionLayoutVertex), gfx::AttributeDataType::Short2); } if (const auto& attr = vertexAttrs->set(idFillExtrusionNormalEdVertexAttribute)) { attr->setSharedRawData(bucket.sharedVertices, offsetof(FillExtrusionLayoutVertex, a2), /*vertexOffset=*/0, sizeof(FillExtrusionLayoutVertex), gfx::AttributeDataType::Short4); } if (doDepthPass) { depthBuilder->setRawVertices({}, vertexCount, gfx::AttributeDataType::Short2); depthBuilder->setVertexAttributes(vertexAttrs); } colorBuilder->setEnableStencil(doDepthPass); colorBuilder->setRawVertices({}, vertexCount, gfx::AttributeDataType::Short2); colorBuilder->setVertexAttributes(std::move(vertexAttrs)); const auto finish = [&](gfx::DrawableBuilder& builder) { if (!bucket.sharedTriangles->elements()) { return; } builder.setSegments(gfx::Triangles(), bucket.sharedTriangles, bucket.triangleSegments.data(), bucket.triangleSegments.size()); builder.flush(context); for (auto& drawable : builder.clearDrawables()) { drawable->setTileID(tileID); drawable->setType(static_cast(hasPattern)); drawable->setLayerTweaker(layerTweaker); drawable->setBinders(renderData.bucket, &binders); drawable->setRenderTile(renderTilesOwner, &tile); tileLayerGroup->addDrawable(drawPass, tileID, std::move(drawable)); ++stats.drawablesAdded; } }; if (doDepthPass) { finish(*depthBuilder); } finish(*colorBuilder); } } #endif // MLN_DRAWABLE_RENDERER } // namespace mbgl