void Painter::renderSymbol(SymbolBucket& bucket, const SymbolLayer& layer, const TileID& id, const mat4& matrix) { // Abort early. if (pass == RenderPass::Opaque) { return; } const auto& properties = layer.paint; const auto& layout = bucket.layout; config.depthMask = GL_FALSE; if (bucket.hasCollisionBoxData()) { config.stencilOp.reset(); config.stencilTest = GL_TRUE; config.program = collisionBoxShader->program; collisionBoxShader->u_matrix = matrix; collisionBoxShader->u_scale = std::pow(2, state.getNormalizedZoom() - id.z); collisionBoxShader->u_zoom = state.getNormalizedZoom() * 10; collisionBoxShader->u_maxzoom = (id.z + 1) * 10; config.lineWidth = 1.0f; setDepthSublayer(0); bucket.drawCollisionBoxes(*collisionBoxShader); } // TODO remove the `true ||` when #1673 is implemented const bool drawAcrossEdges = true || !(layout.text.allowOverlap || layout.icon.allowOverlap || layout.text.ignorePlacement || layout.icon.ignorePlacement); // Disable the stencil test so that labels aren't clipped to tile boundaries. // // Layers with features that may be drawn overlapping aren't clipped. These // layers are sorted in the y direction, and to draw the correct ordering near // tile edges the icons are included in both tiles and clipped when drawing. if (drawAcrossEdges) { config.stencilTest = GL_FALSE; } else { config.stencilOp.reset(); config.stencilTest = GL_TRUE; } if (bucket.hasIconData()) { if (layout.icon.rotationAlignment == RotationAlignmentType::Map) { config.depthFunc.reset(); config.depthTest = GL_TRUE; } else { config.depthTest = GL_FALSE; } bool sdf = bucket.sdfIcons; const float angleOffset = layout.icon.rotationAlignment == RotationAlignmentType::Map ? state.getAngle() : 0; const float fontSize = properties.icon.size; const float fontScale = fontSize / 1.0f; SpriteAtlas* activeSpriteAtlas = layer.spriteAtlas; activeSpriteAtlas->bind(state.isChanging() || layout.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1 || sdf || state.getPitch() != 0); if (sdf) { renderSDF(bucket, id, matrix, layout.icon, properties.icon, 1.0f, {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }}, *sdfIconShader, &SymbolBucket::drawIcons); } else { mat4 vtxMatrix = translatedMatrix(matrix, properties.icon.translate, id, properties.icon.translateAnchor); bool skewed = layout.icon.rotationAlignment == RotationAlignmentType::Map; mat4 exMatrix; float s; if (skewed) { matrix::identity(exMatrix); s = 4096.0f / util::tileSize / id.overscaling / std::pow(2, state.getZoom() - id.z); } else { exMatrix = extrudeMatrix; s = state.getAltitude(); } matrix::scale(exMatrix, exMatrix, s, s, 1); matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f); // calculate how much longer the real world distance is at the top of the screen // than at the middle of the screen. float topedgelength = std::sqrt(std::pow(state.getHeight(), 2) / 4.0f * (1.0f + std::pow(state.getAltitude(), 2))); float x = state.getHeight() / 2.0f * std::tan(state.getPitch()); float extra = (topedgelength + x) / topedgelength - 1; config.program = iconShader->program; iconShader->u_matrix = vtxMatrix; iconShader->u_exmatrix = exMatrix; iconShader->u_texsize = {{ float(spriteAtlas->getWidth()) / 4.0f, float(spriteAtlas->getHeight()) / 4.0f }}; iconShader->u_skewed = skewed; iconShader->u_extra = extra; // adjust min/max zooms for variable font sies float zoomAdjust = std::log(fontSize / layout.icon.size) / std::log(2); iconShader->u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level iconShader->u_fadedist = 0 * 10; iconShader->u_minfadezoom = state.getNormalizedZoom() * 10; iconShader->u_maxfadezoom = state.getNormalizedZoom() * 10; iconShader->u_fadezoom = state.getNormalizedZoom() * 10; iconShader->u_opacity = properties.icon.opacity; setDepthSublayer(0); bucket.drawIcons(*iconShader); } } if (bucket.hasTextData()) { if (layout.text.rotationAlignment == RotationAlignmentType::Map) { config.depthFunc.reset(); config.depthTest = GL_TRUE; } else { config.depthTest = GL_FALSE; } glyphAtlas->bind(); renderSDF(bucket, id, matrix, layout.text, properties.text, 24.0f, {{ float(glyphAtlas->width) / 4, float(glyphAtlas->height) / 4 }}, *sdfGlyphShader, &SymbolBucket::drawGlyphs); } }
void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, const TileID &id, const mat4 &matrix) { // Abort early. if (pass == RenderPass::Opaque) { return; } const auto &properties = layer_desc.getProperties<SymbolProperties>(); const auto &layout = bucket.layout; config.depthTest = true; config.depthMask = GL_FALSE; if (bucket.hasCollisionBoxData() && ( (bucket.hasIconData() && properties.icon.opacity) || (bucket.hasTextData() && properties.text.opacity))) { config.stencilTest = true; useProgram(collisionBoxShader->program); collisionBoxShader->u_matrix = matrix; collisionBoxShader->u_scale = std::pow(2, state.getNormalizedZoom() - id.z); collisionBoxShader->u_zoom = state.getNormalizedZoom() * 10; collisionBoxShader->u_maxzoom = (id.z + 1) * 10; lineWidth(3.0f); config.depthRange = { strata, 1.0f }; bucket.drawCollisionBoxes(*collisionBoxShader); } // TODO remove the `|| true` when #1673 is implemented const bool drawAcrossEdges = !(layout.text.allow_overlap || layout.icon.allow_overlap || layout.text.ignore_placement || layout.icon.ignore_placement) || true; // Disable the stencil test so that labels aren't clipped to tile boundaries. // // Layers with features that may be drawn overlapping aren't clipped. These // layers are sorted in the y direction, and to draw the correct ordering near // tile edges the icons are included in both tiles and clipped when drawing. config.stencilTest = drawAcrossEdges ? false : true; if (bucket.hasIconData()) { bool sdf = bucket.sdfIcons; const float angleOffset = layout.icon.rotation_alignment == RotationAlignmentType::Map ? state.getAngle() : 0; // If layerStyle.size > bucket.info.fontSize then labels may collide const float fontSize = properties.icon.size != 0 ? properties.icon.size : layout.icon.max_size; const float fontScale = fontSize / 1.0f; spriteAtlas->bind(state.isChanging() || layout.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1 || sdf); if (sdf) { renderSDF(bucket, id, matrix, layout.icon, properties.icon, 1.0f, {{ float(spriteAtlas->getWidth()) / 4.0f, float(spriteAtlas->getHeight()) / 4.0f }}, *sdfIconShader, &SymbolBucket::drawIcons); } else { mat4 vtxMatrix = translatedMatrix(matrix, properties.icon.translate, id, properties.icon.translate_anchor); mat4 exMatrix; matrix::copy(exMatrix, projMatrix); if (angleOffset) { matrix::rotate_z(exMatrix, exMatrix, angleOffset); } matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f); useProgram(iconShader->program); iconShader->u_matrix = vtxMatrix; iconShader->u_exmatrix = exMatrix; iconShader->u_texsize = {{ float(spriteAtlas->getWidth()) / 4.0f, float(spriteAtlas->getHeight()) / 4.0f }}; // adjust min/max zooms for variable font sies float zoomAdjust = std::log(fontSize / layout.icon.max_size) / std::log(2); iconShader->u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level iconShader->u_fadedist = 0 * 10; iconShader->u_minfadezoom = state.getNormalizedZoom() * 10; iconShader->u_maxfadezoom = state.getNormalizedZoom() * 10; iconShader->u_fadezoom = state.getNormalizedZoom() * 10; iconShader->u_opacity = properties.icon.opacity; config.depthRange = { strata, 1.0f }; bucket.drawIcons(*iconShader); } } if (bucket.hasTextData()) { glyphAtlas->bind(); renderSDF(bucket, id, matrix, layout.text, properties.text, 24.0f, {{ float(glyphAtlas->width) / 4, float(glyphAtlas->height) / 4 }}, *sdfGlyphShader, &SymbolBucket::drawGlyphs); } }
void Painter::renderSymbol(PaintParameters& parameters, SymbolBucket& bucket, const SymbolLayer& layer, const RenderTile& tile) { // Abort early. if (pass == RenderPass::Opaque) { return; } const auto& paint = layer.impl->paint; const auto& layout = bucket.layout; config.depthMask = GL_FALSE; // TODO remove the `true ||` when #1673 is implemented const bool drawAcrossEdges = (frame.mapMode == MapMode::Continuous) && (true || !(layout.textAllowOverlap || layout.iconAllowOverlap || layout.textIgnorePlacement || layout.iconIgnorePlacement)); // Disable the stencil test so that labels aren't clipped to tile boundaries. // // Layers with features that may be drawn overlapping aren't clipped. These // layers are sorted in the y direction, and to draw the correct ordering near // tile edges the icons are included in both tiles and clipped when drawing. if (drawAcrossEdges) { config.stencilTest = GL_FALSE; } else { config.stencilOp.reset(); config.stencilTest = GL_TRUE; } if (bucket.hasIconData()) { if (layout.iconRotationAlignment == AlignmentType::Map) { config.depthFunc.reset(); config.depthTest = GL_TRUE; } else { config.depthTest = GL_FALSE; } bool sdf = bucket.sdfIcons; const float angleOffset = layout.iconRotationAlignment == AlignmentType::Map ? state.getAngle() : 0; const float fontSize = layer.impl->iconSize; const float fontScale = fontSize / 1.0f; SpriteAtlas* activeSpriteAtlas = layer.impl->spriteAtlas; const bool iconScaled = fontScale != 1 || frame.pixelRatio != activeSpriteAtlas->getPixelRatio() || bucket.iconsNeedLinear; const bool iconTransformed = layout.iconRotationAlignment == AlignmentType::Map || angleOffset != 0 || state.getPitch() != 0; activeSpriteAtlas->bind(sdf || state.isChanging() || iconScaled || iconTransformed, store, config, 0); if (sdf) { renderSDF(bucket, tile, 1.0f, {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }}, parameters.shaders.sdfIcon, &SymbolBucket::drawIcons, layout.iconRotationAlignment, // icon-pitch-alignment is not yet implemented // and we simply inherit the rotation alignment layout.iconRotationAlignment, layout.iconSize, paint.iconOpacity, paint.iconColor, paint.iconHaloColor, paint.iconHaloWidth, paint.iconHaloBlur, paint.iconTranslate, paint.iconTranslateAnchor, layer.impl->iconSize); } else { mat4 vtxMatrix = tile.translatedMatrix(paint.iconTranslate, paint.iconTranslateAnchor, state); std::array<float, 2> extrudeScale; const bool alignedWithMap = layout.iconRotationAlignment == AlignmentType::Map; if (alignedWithMap) { extrudeScale.fill(tile.id.pixelsToTileUnits(1, state.getZoom()) * fontScale); } else { extrudeScale = {{ pixelsToGLUnits[0] * fontScale * state.getAltitude(), pixelsToGLUnits[1] * fontScale * state.getAltitude() }}; } auto& iconShader = parameters.shaders.icon; config.program = iconShader.getID(); iconShader.u_matrix = vtxMatrix; iconShader.u_extrude_scale = extrudeScale; iconShader.u_texsize = {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }}; iconShader.u_rotate_with_map = alignedWithMap; iconShader.u_texture = 0; // adjust min/max zooms for variable font sies float zoomAdjust = std::log(fontSize / layout.iconSize) / std::log(2); iconShader.u_zoom = (state.getZoom() - zoomAdjust) * 10; // current zoom level iconShader.u_opacity = paint.iconOpacity; frameHistory.bind(store, config, 1); iconShader.u_fadetexture = 1; setDepthSublayer(0); bucket.drawIcons(iconShader, store, isOverdraw()); } } if (bucket.hasTextData()) { if (layout.textRotationAlignment == AlignmentType::Map) { config.depthFunc.reset(); config.depthTest = GL_TRUE; } else { config.depthTest = GL_FALSE; } glyphAtlas->bind(store, config, 0); renderSDF(bucket, tile, 24.0f, {{ float(glyphAtlas->width) / 4, float(glyphAtlas->height) / 4 }}, parameters.shaders.sdfGlyph, &SymbolBucket::drawGlyphs, layout.textRotationAlignment, layout.textPitchAlignment, layout.textSize, paint.textOpacity, paint.textColor, paint.textHaloColor, paint.textHaloWidth, paint.textHaloBlur, paint.textTranslate, paint.textTranslateAnchor, layer.impl->textSize); } if (bucket.hasCollisionBoxData()) { config.stencilOp.reset(); config.stencilTest = GL_TRUE; auto& collisionBoxShader = shaders->collisionBox; config.program = collisionBoxShader.getID(); collisionBoxShader.u_matrix = tile.matrix; // TODO: This was the overscaled z instead of the canonical z. collisionBoxShader.u_scale = std::pow(2, state.getZoom() - tile.id.canonical.z); collisionBoxShader.u_zoom = state.getZoom() * 10; collisionBoxShader.u_maxzoom = (tile.id.canonical.z + 1) * 10; config.lineWidth = 1.0f; setDepthSublayer(0); bucket.drawCollisionBoxes(collisionBoxShader, store); } }