Пример #1
0
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();
}
Пример #2
0
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);
    }
}
Пример #3
0
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);
                }
            }
        }
    }
}
Пример #4
0
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();
}
Пример #5
0
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);
    }

}
Пример #7
0
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);

    }
}