void IconBucket::addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas) { std::string field; if (properties.icon.size()) { field = util::replaceTokens(properties.icon, feature.properties); } if (!field.size()) { field = "<circle>"; } const Rect<uint16_t> rect = sprite_atlas.getIcon(properties.size, field); const uint16_t tx = rect.x + rect.w / 2; const uint16_t ty = rect.y + rect.h / 2; Geometry::command cmd; pbf geom = feature.geometry; Geometry geometry(geom); int32_t x, y; while ((cmd = geometry.next(x, y)) != Geometry::end) { if (cmd == Geometry::move_to) { vertexBuffer.add(x, y, tx, ty); } else { if (debug::tileParseWarnings) { fprintf(stderr, "[WARNING] other command than move_to in icon geometry\n"); } } } vertex_end = vertexBuffer.index(); }
void ResourceLoader::update(MapData& data, const TransformState& transform, GlyphAtlas& glyphAtlas, GlyphStore& glyphStore, SpriteAtlas& spriteAtlas, TexturePool& texturePool) { if (!style_) { return; } const float pixelRatio = transform.getPixelRatio(); if (!sprite_ || !sprite_->hasPixelRatio(pixelRatio)) { sprite_ = util::make_unique<Sprite>(style_->getSpriteURL(), pixelRatio); sprite_->setObserver(this); spriteAtlas.resize(pixelRatio); spriteAtlas.setSprite(sprite_); } for (const auto& source : style_->sources) { source->update( data, transform, *style_, glyphAtlas, glyphStore, spriteAtlas, sprite_, texturePool); } }
void SymbolBucket::addFeatures(const GeometryTileLayer& layer, const FilterExpression& filter, uintptr_t tileUID, SpriteAtlas& spriteAtlas, Sprite& sprite, GlyphAtlas& glyphAtlas, GlyphStore& glyphStore) { const std::vector<SymbolFeature> features = processFeatures(layer, filter, glyphStore); // Stop if we still need glyphs because the // bucket will be discarded. if (needsGlyphs()) { return; } float horizontalAlign = 0.5; float verticalAlign = 0.5; switch (layout.text.anchor) { case TextAnchorType::Top: case TextAnchorType::Bottom: case TextAnchorType::Center: break; case TextAnchorType::Right: case TextAnchorType::TopRight: case TextAnchorType::BottomRight: horizontalAlign = 1; break; case TextAnchorType::Left: case TextAnchorType::TopLeft: case TextAnchorType::BottomLeft: horizontalAlign = 0; break; } switch (layout.text.anchor) { case TextAnchorType::Left: case TextAnchorType::Right: case TextAnchorType::Center: break; case TextAnchorType::Bottom: case TextAnchorType::BottomLeft: case TextAnchorType::BottomRight: verticalAlign = 1; break; case TextAnchorType::Top: case TextAnchorType::TopLeft: case TextAnchorType::TopRight: verticalAlign = 0; break; } float justify = 0.5; if (layout.text.justify == TextJustifyType::Right) justify = 1; else if (layout.text.justify == TextJustifyType::Left) justify = 0; auto* fontStack = glyphStore.getFontStack(layout.text.font); for (const auto& feature : features) { if (!feature.geometry.size()) continue; Shaping shaping; Rect<uint16_t> image; GlyphPositions face; // if feature has text, shape the text if (feature.label.length()) { shaping = fontStack->getShaping( /* string */ feature.label, /* maxWidth: ems */ layout.placement != PlacementType::Line ? layout.text.max_width * 24 : 0, /* lineHeight: ems */ layout.text.line_height * 24, /* horizontalAlign */ horizontalAlign, /* verticalAlign */ verticalAlign, /* justify */ justify, /* spacing: ems */ layout.text.letter_spacing * 24, /* translate */ vec2<float>(layout.text.offset[0], layout.text.offset[1])); // Add the glyphs we need for this label to the glyph atlas. if (shaping.size()) { glyphAtlas.addGlyphs(tileUID, feature.label, layout.text.font, *fontStack, face); } } // if feature has icon, get sprite atlas position if (feature.sprite.length()) { image = spriteAtlas.getImage(feature.sprite, false); if (sprite.getSpritePosition(feature.sprite).sdf) { sdfIcons = true; } } // if either shaping or icon position is present, add the feature if (shaping.size() || image.hasArea()) { for (const auto& line : feature.geometry) { if (line.size()) { addFeature(line, shaping, face, image); } } } } }
void SymbolBucket::addFeatures(uintptr_t tileUID, SpriteAtlas& spriteAtlas, GlyphAtlas& glyphAtlas, GlyphStore& glyphStore) { float horizontalAlign = 0.5; float verticalAlign = 0.5; switch (layout.text.anchor) { case TextAnchorType::Top: case TextAnchorType::Bottom: case TextAnchorType::Center: break; case TextAnchorType::Right: case TextAnchorType::TopRight: case TextAnchorType::BottomRight: horizontalAlign = 1; break; case TextAnchorType::Left: case TextAnchorType::TopLeft: case TextAnchorType::BottomLeft: horizontalAlign = 0; break; } switch (layout.text.anchor) { case TextAnchorType::Left: case TextAnchorType::Right: case TextAnchorType::Center: break; case TextAnchorType::Bottom: case TextAnchorType::BottomLeft: case TextAnchorType::BottomRight: verticalAlign = 1; break; case TextAnchorType::Top: case TextAnchorType::TopLeft: case TextAnchorType::TopRight: verticalAlign = 0; break; } const float justify = layout.text.justify == TextJustifyType::Right ? 1 : layout.text.justify == TextJustifyType::Left ? 0 : 0.5; auto fontStack = glyphStore.getFontStack(layout.text.font); for (const auto& feature : features) { if (feature.geometry.empty()) continue; Shaping shapedText; PositionedIcon shapedIcon; GlyphPositions face; // if feature has text, shape the text if (feature.label.length()) { shapedText = fontStack->getShaping( /* string */ feature.label, /* maxWidth: ems */ layout.placement != PlacementType::Line ? layout.text.maxWidth * 24 : 0, /* lineHeight: ems */ layout.text.lineHeight * 24, /* horizontalAlign */ horizontalAlign, /* verticalAlign */ verticalAlign, /* justify */ justify, /* spacing: ems */ layout.text.letterSpacing * 24, /* translate */ vec2<float>(layout.text.offset.value[0], layout.text.offset.value[1])); // Add the glyphs we need for this label to the glyph atlas. if (shapedText) { glyphAtlas.addGlyphs(tileUID, feature.label, layout.text.font, **fontStack, face); } } // if feature has icon, get sprite atlas position if (feature.sprite.length()) { auto image = spriteAtlas.getImage(feature.sprite, false); if (image) { shapedIcon = shapeIcon(*image, layout); assert((*image).spriteImage); if ((*image).spriteImage->sdf) { sdfIcons = true; } if ((*image).relativePixelRatio != 1.0f) { iconsNeedLinear = true; } } } // if either shapedText or icon position is present, add the feature if (shapedText || shapedIcon) { addFeature(feature.geometry, shapedText, shapedIcon, face); } } features.clear(); }
void Painter::render(const Style& style, const FrameData& frame_, SpriteAtlas& annotationSpriteAtlas) { frame = frame_; glyphAtlas = style.glyphAtlas.get(); spriteAtlas = style.spriteAtlas.get(); lineAtlas = style.lineAtlas.get(); RenderData renderData = style.getRenderData(); const std::vector<RenderItem>& order = renderData.order; const std::set<Source*>& sources = renderData.sources; const Color& background = renderData.backgroundColor; // Update the default matrices to the current viewport dimensions. state.getProjMatrix(projMatrix); // The extrusion matrix. matrix::ortho(extrudeMatrix, 0, state.getWidth(), state.getHeight(), 0, 0, -1); // The native matrix is a 1:1 matrix that paints the coordinates at the // same screen position as the vertex specifies. matrix::identity(nativeMatrix); matrix::multiply(nativeMatrix, projMatrix, nativeMatrix); // - UPLOAD PASS ------------------------------------------------------------------------------- // Uploads all required buffers and images before we do any actual rendering. { MBGL_DEBUG_GROUP("upload"); tileStencilBuffer.upload(glObjectStore); tileBorderBuffer.upload(glObjectStore); spriteAtlas->upload(glObjectStore); lineAtlas->upload(glObjectStore); glyphAtlas->upload(glObjectStore); annotationSpriteAtlas.upload(glObjectStore); for (const auto& item : order) { if (item.bucket && item.bucket->needsUpload()) { item.bucket->upload(glObjectStore); } } } // - CLEAR ------------------------------------------------------------------------------------- // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any // tiles whatsoever. { MBGL_DEBUG_GROUP("clear"); config.stencilFunc.reset(); config.stencilTest = GL_TRUE; config.stencilMask = 0xFF; config.depthTest = GL_FALSE; config.depthMask = GL_TRUE; config.colorMask = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE }; config.clearColor = { background[0], background[1], background[2], background[3] }; config.clearStencil = 0; config.clearDepth = 1; MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); } // - CLIPPING MASKS ---------------------------------------------------------------------------- // Draws the clipping masks to the stencil buffer. { MBGL_DEBUG_GROUP("clip"); // Update all clipping IDs. ClipIDGenerator generator; for (const auto& source : sources) { generator.update(source->getLoadedTiles()); source->updateMatrices(projMatrix, state); } drawClippingMasks(generator.getStencils()); } frameHistory.record(data.getAnimationTime(), state.getZoom()); // Actually render the layers if (debug::renderTree) { Log::Info(Event::Render, "{"); indent++; } // TODO: Correctly compute the number of layers recursively beforehand. depthRangeSize = 1 - (order.size() + 2) * numSublayers * depthEpsilon; // - OPAQUE PASS ------------------------------------------------------------------------------- // Render everything top-to-bottom by using reverse iterators. Render opaque objects first. renderPass(RenderPass::Opaque, order.rbegin(), order.rend(), 0, 1); // - TRANSLUCENT PASS -------------------------------------------------------------------------- // Make a second pass, rendering translucent objects. This time, we render bottom-to-top. renderPass(RenderPass::Translucent, order.begin(), order.end(), static_cast<GLsizei>(order.size()) - 1, -1); if (debug::renderTree) { Log::Info(Event::Render, "}"); indent--; } // - DEBUG PASS -------------------------------------------------------------------------------- // Renders debug overlays. { MBGL_DEBUG_GROUP("debug"); // Finalize the rendering, e.g. by calling debug render calls per tile. // This guarantees that we have at least one function per tile called. // When only rendering layers via the stylesheet, it's possible that we don't // ever visit a tile during rendering. for (const auto& source : sources) { source->finishRender(*this); } } // TODO: Find a better way to unbind VAOs after we're done with them without introducing // unnecessary bind(0)/bind(N) sequences. { MBGL_DEBUG_GROUP("cleanup"); MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, 0)); MBGL_CHECK_ERROR(VertexArrayObject::Unbind()); } if (data.contextMode == GLContextMode::Shared) { config.setDirty(); } }
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(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); } }