mat4 Painter::translatedMatrix(const mat4& matrix,
                               const std::array<float, 2>& translation,
                               const UnwrappedTileID& id,
                               TranslateAnchorType anchor) {
    if (translation[0] == 0 && translation[1] == 0) {
        return matrix;
    } else {
        mat4 vtxMatrix;
        if (anchor == TranslateAnchorType::Viewport) {
            const double sin_a = std::sin(-state.getAngle());
            const double cos_a = std::cos(-state.getAngle());
            matrix::translate(vtxMatrix, matrix,
                    id.pixelsToTileUnits(translation[0] * cos_a - translation[1] * sin_a, state.getZoom()),
                    id.pixelsToTileUnits(translation[0] * sin_a + translation[1] * cos_a, state.getZoom()),
                    0);
        } else {
            matrix::translate(vtxMatrix, matrix,
                    id.pixelsToTileUnits(translation[0], state.getZoom()),
                    id.pixelsToTileUnits(translation[1], state.getZoom()),
                    0);
        }

        return vtxMatrix;
    }
}
bool coveredByChildren(const UnwrappedTileID& id, Iterator it, const Iterator& end) {
    for (const auto& child : id.children()) {
        it = std::lower_bound(it, end, child, [](auto& a, auto& b) { return std::get<0>(a) < b; });
        if (it == end) {
            return false;
        } else if (std::get<0>(*it) != child) {
            return coveredByChildren(child, it, end);
        }
    }

    // We looked at all four immediate children and verified that they're covered.
    return true;
}
void Painter::renderFill(FillBucket& bucket,
                         const FillLayer& layer,
                         const UnwrappedTileID& tileID,
                         const mat4& matrix) {
    const FillPaintProperties& properties = layer.paint;
    mat4 vtxMatrix =
        translatedMatrix(matrix, properties.fillTranslate, tileID, properties.fillTranslateAnchor);

    Color fill_color = properties.fillColor;
    float opacity = properties.fillOpacity;

    Color stroke_color = properties.fillOutlineColor;
    if (stroke_color[3] < 0) {
        stroke_color = fill_color;
    }

    bool pattern = !properties.fillPattern.value.from.empty();
    bool outline = properties.fillAntialias && !pattern && stroke_color != fill_color;
    bool fringeline = properties.fillAntialias && !pattern && stroke_color == fill_color;

    bool wireframe = frame.debugOptions & MapDebugOptions::Wireframe;
    if (wireframe) {
        fill_color = {{ 1.0f, 1.0f, 1.0f, 1.0f }};
        stroke_color = {{ 1.0f, 1.0f, 1.0f, 1.0f }};
        opacity = 1.0f;
        pattern = false;
        outline = true;
        fringeline = true;
    }

    config.stencilOp.reset();
    config.stencilTest = GL_TRUE;
    config.depthFunc.reset();
    config.depthTest = GL_TRUE;
    config.depthMask = GL_TRUE;

    // Because we're drawing top-to-bottom, and we update the stencil mask
    // befrom, we have to draw the outline first (!)
    if (outline && pass == RenderPass::Translucent) {
        config.program = outlineShader->getID();
        outlineShader->u_matrix = vtxMatrix;
        config.lineWidth = 2.0f; // This is always fixed and does not depend on the pixelRatio!

        outlineShader->u_color = stroke_color;
        outlineShader->u_opacity = opacity;

        // Draw the entire line
        outlineShader->u_world = {{
            static_cast<float>(frame.framebufferSize[0]),
            static_cast<float>(frame.framebufferSize[1])
        }};
        setDepthSublayer(0);
        bucket.drawVertices(*outlineShader, store);
    }

    if (pattern) {
        optional<SpriteAtlasPosition> posA = spriteAtlas->getPosition(properties.fillPattern.value.from, true);
        optional<SpriteAtlasPosition> posB = spriteAtlas->getPosition(properties.fillPattern.value.to, true);

        // Image fill.
        if (pass == RenderPass::Translucent && posA && posB) {
            config.program = patternShader->getID();
            patternShader->u_matrix = vtxMatrix;
            patternShader->u_pattern_tl_a = (*posA).tl;
            patternShader->u_pattern_br_a = (*posA).br;
            patternShader->u_pattern_tl_b = (*posB).tl;
            patternShader->u_pattern_br_b = (*posB).br;
            patternShader->u_opacity = properties.fillOpacity;
            patternShader->u_image = 0;
            patternShader->u_mix = properties.fillPattern.value.t;

            std::array<int, 2> imageSizeScaledA = {{
                (int)((*posA).size[0] * properties.fillPattern.value.fromScale),
                (int)((*posA).size[1] * properties.fillPattern.value.fromScale)
            }};
            std::array<int, 2> imageSizeScaledB = {{
                (int)((*posB).size[0] * properties.fillPattern.value.toScale),
                (int)((*posB).size[1] * properties.fillPattern.value.toScale)
            }};

            patternShader->u_patternscale_a = {
                { 1.0f / tileID.pixelsToTileUnits(imageSizeScaledA[0], state.getIntegerZoom()),
                  1.0f / tileID.pixelsToTileUnits(imageSizeScaledB[1], state.getIntegerZoom()) }
            };
            patternShader->u_patternscale_b = {
                { 1.0f / tileID.pixelsToTileUnits(imageSizeScaledB[0], state.getIntegerZoom()),
                  1.0f / tileID.pixelsToTileUnits(imageSizeScaledB[1], state.getIntegerZoom()) }
            };

            float offsetAx = (std::fmod(util::tileSize, imageSizeScaledA[0]) * tileID.canonical.x) /
                             (float)imageSizeScaledA[0];
            float offsetAy = (std::fmod(util::tileSize, imageSizeScaledA[1]) * tileID.canonical.y) /
                             (float)imageSizeScaledA[1];

            float offsetBx = (std::fmod(util::tileSize, imageSizeScaledB[0]) * tileID.canonical.x) /
                             (float)imageSizeScaledB[0];
            float offsetBy = (std::fmod(util::tileSize, imageSizeScaledB[1]) * tileID.canonical.y) /
                             (float)imageSizeScaledB[1];

            patternShader->u_offset_a = std::array<float, 2>{{offsetAx, offsetAy}};
            patternShader->u_offset_b = std::array<float, 2>{{offsetBx, offsetBy}};

            config.activeTexture = GL_TEXTURE0;
            spriteAtlas->bind(true, store);

            // Draw the actual triangles into the color & stencil buffer.
            setDepthSublayer(0);
            bucket.drawElements(*patternShader, store);

            if (properties.fillAntialias && stroke_color == fill_color) {
                config.program = outlinePatternShader->getID();
                outlinePatternShader->u_matrix = vtxMatrix;
                config.lineWidth = 2.0f;

                // Draw the entire line
                outlinePatternShader->u_world = {{
                    static_cast<float>(frame.framebufferSize[0]),
                    static_cast<float>(frame.framebufferSize[1])
                }};

                outlinePatternShader->u_pattern_tl_a = (*posA).tl;
                outlinePatternShader->u_pattern_br_a = (*posA).br;
                outlinePatternShader->u_pattern_tl_b = (*posB).tl;
                outlinePatternShader->u_pattern_br_b = (*posB).br;
                outlinePatternShader->u_opacity = properties.fillOpacity;
                outlinePatternShader->u_image = 0;
                outlinePatternShader->u_mix = properties.fillPattern.value.t;

                outlinePatternShader->u_patternscale_a = {{
                    1.0f / tileID.pixelsToTileUnits(imageSizeScaledA[0], state.getIntegerZoom()),
                    1.0f / tileID.pixelsToTileUnits(imageSizeScaledB[1], state.getIntegerZoom())
                }};
                outlinePatternShader->u_patternscale_b = {{
                    1.0f / tileID.pixelsToTileUnits(imageSizeScaledB[0], state.getIntegerZoom()),
                    1.0f / tileID.pixelsToTileUnits(imageSizeScaledB[1], state.getIntegerZoom())
                }};

                outlinePatternShader->u_offset_a = std::array<float, 2>{{offsetAx, offsetAy}};
                outlinePatternShader->u_offset_b = std::array<float, 2>{{offsetBx, offsetBy}};

                config.activeTexture = GL_TEXTURE0;
                spriteAtlas->bind(true, store);

                setDepthSublayer(2);
                bucket.drawVertices(*outlinePatternShader, store);
            }
        }
    } else if (!wireframe) {
        // No image fill.
        if ((fill_color[3] >= 1.0f && opacity >= 1.0f) == (pass == RenderPass::Opaque)) {
            // Only draw the fill when it's either opaque and we're drawing opaque
            // fragments or when it's translucent and we're drawing translucent
            // fragments
            // Draw filling rectangle.
            config.program = plainShader->getID();
            plainShader->u_matrix = vtxMatrix;
            plainShader->u_color = fill_color;
            plainShader->u_opacity = opacity;

            // Draw the actual triangles into the color & stencil buffer.
            setDepthSublayer(1);
            bucket.drawElements(*plainShader, store);
        }
    }

    // Because we're drawing top-to-bottom, and we update the stencil mask
    // below, we have to draw the outline first (!)
    if (fringeline && pass == RenderPass::Translucent) {
        config.program = outlineShader->getID();
        outlineShader->u_matrix = vtxMatrix;
        config.lineWidth = 2.0f; // This is always fixed and does not depend on the pixelRatio!

        outlineShader->u_color = fill_color;
        outlineShader->u_opacity = opacity;

        // Draw the entire line
        outlineShader->u_world = {{
            static_cast<float>(frame.framebufferSize[0]),
            static_cast<float>(frame.framebufferSize[1])
        }};

        setDepthSublayer(2);
        bucket.drawVertices(*outlineShader, store);
    }
}
void Painter::renderSDF(SymbolBucket &bucket,
                        const UnwrappedTileID &tileID,
                        const mat4 &matrix,
                        float sdfFontSize,
                        std::array<float, 2> texsize,
                        SDFShader& sdfShader,
                        void (SymbolBucket::*drawSDF)(SDFShader&, gl::ObjectStore&),

                        // Layout
                        RotationAlignmentType rotationAlignment,
                        float layoutSize,

                        // Paint
                        float opacity,
                        Color color,
                        Color haloColor,
                        float haloWidth,
                        float haloBlur,
                        std::array<float, 2> translate,
                        TranslateAnchorType translateAnchor,
                        float paintSize)
{
    mat4 vtxMatrix = translatedMatrix(matrix, translate, tileID, translateAnchor);

    // If layerStyle.size > bucket.info.fontSize then labels may collide
    float fontSize = paintSize;
    float fontScale = fontSize / sdfFontSize;

    float scale = fontScale;
    std::array<float, 2> exScale = extrudeScale;
    bool alignedWithMap = rotationAlignment == RotationAlignmentType::Map;
    float gammaScale = 1.0f;

    if (alignedWithMap) {
        scale *= tileID.pixelsToTileUnits(1, state.getZoom());
        exScale.fill(scale);
        gammaScale /= std::cos(state.getPitch());
    } else {
        exScale = {{ exScale[0] * scale, exScale[1] * scale }};
    }

    config.program = sdfShader.getID();
    sdfShader.u_matrix = vtxMatrix;
    sdfShader.u_extrude_scale = exScale;
    sdfShader.u_texsize = texsize;
    sdfShader.u_skewed = alignedWithMap;
    sdfShader.u_texture = 0;

    // adjust min/max zooms for variable font sies
    float zoomAdjust = std::log(fontSize / layoutSize) / std::log(2);

    sdfShader.u_zoom = (state.getZoom() - zoomAdjust) * 10; // current zoom level

    config.activeTexture = GL_TEXTURE1;
    frameHistory.bind(store);
    sdfShader.u_fadetexture = 1;

    // The default gamma value has to be adjust for the current pixelratio so that we're not
    // drawing blurry font on retina screens.
    const float gamma = 0.105 * sdfFontSize / fontSize / frame.pixelRatio;

    const float sdfPx = 8.0f;
    const float blurOffset = 1.19f;
    const float haloOffset = 6.0f;

    // We're drawing in the translucent pass which is bottom-to-top, so we need
    // to draw the halo first.
    if (haloColor[3] > 0.0f && haloWidth > 0.0f) {
        sdfShader.u_gamma = (haloBlur * blurOffset / fontScale / sdfPx + gamma) * gammaScale;
        sdfShader.u_color = haloColor;
        sdfShader.u_opacity = opacity;
        sdfShader.u_buffer = (haloOffset - haloWidth / fontScale) / sdfPx;

        setDepthSublayer(0);
        (bucket.*drawSDF)(sdfShader, store);
    }

    // Then, we draw the text/icon over the halo
    if (color[3] > 0.0f) {
        sdfShader.u_gamma = gamma * gammaScale;
        sdfShader.u_color = color;
        sdfShader.u_opacity = opacity;
        sdfShader.u_buffer = (256.0f - 64.0f) / 256.0f;

        setDepthSublayer(1);
        (bucket.*drawSDF)(sdfShader, store);
    }
}
void Painter::renderSymbol(SymbolBucket& bucket,
                           const SymbolLayer& layer,
                           const UnwrappedTileID& tileID,
                           const mat4& matrix) {
    // 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 == RotationAlignmentType::Map) {
            config.depthFunc.reset();
            config.depthTest = GL_TRUE;
        } else {
            config.depthTest = GL_FALSE;
        }

        bool sdf = bucket.sdfIcons;

        const float angleOffset =
            layout.iconRotationAlignment == RotationAlignmentType::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 == RotationAlignmentType::Map || angleOffset != 0 || state.getPitch() != 0;
        config.activeTexture = GL_TEXTURE0;
        activeSpriteAtlas->bind(sdf || state.isChanging() || iconScaled || iconTransformed, store);

        if (sdf) {
            renderSDF(bucket,
                      tileID,
                      matrix,
                      1.0f,
                      {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }},
                      *sdfIconShader,
                      &SymbolBucket::drawIcons,
                      layout.iconRotationAlignment,
                      layout.iconSize,
                      paint.iconOpacity,
                      paint.iconColor,
                      paint.iconHaloColor,
                      paint.iconHaloWidth,
                      paint.iconHaloBlur,
                      paint.iconTranslate,
                      paint.iconTranslateAnchor,
                      layer.impl->iconSize);
        } else {
            mat4 vtxMatrix =
                translatedMatrix(matrix, paint.iconTranslate, tileID, paint.iconTranslateAnchor);

            float scale = fontScale;
            std::array<float, 2> exScale = extrudeScale;
            const bool alignedWithMap = layout.iconRotationAlignment == RotationAlignmentType::Map;
            if (alignedWithMap) {
                scale *= tileID.pixelsToTileUnits(1, state.getZoom());
                exScale.fill(scale);
            } else {
                exScale = {{ exScale[0] * scale, exScale[1] * scale }};
            }

            config.program = iconShader->getID();
            iconShader->u_matrix = vtxMatrix;
            iconShader->u_extrude_scale = exScale;
            iconShader->u_texsize = {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }};
            iconShader->u_skewed = 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;

            config.activeTexture = GL_TEXTURE1;
            frameHistory.bind(store);
            iconShader->u_fadetexture = 1;

            setDepthSublayer(0);
            bucket.drawIcons(*iconShader, store);
        }
    }

    if (bucket.hasTextData()) {
        if (layout.textRotationAlignment == RotationAlignmentType::Map) {
            config.depthFunc.reset();
            config.depthTest = GL_TRUE;
        } else {
            config.depthTest = GL_FALSE;
        }

        config.activeTexture = GL_TEXTURE0;
        glyphAtlas->bind(store);

        renderSDF(bucket,
                  tileID,
                  matrix,
                  24.0f,
                  {{ float(glyphAtlas->width) / 4, float(glyphAtlas->height) / 4 }},
                  *sdfGlyphShader,
                  &SymbolBucket::drawGlyphs,
                  layout.textRotationAlignment,
                  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;

        config.program = collisionBoxShader->getID();
        collisionBoxShader->u_matrix = matrix;
        // TODO: This was the overscaled z instead of the canonical z.
        collisionBoxShader->u_scale = std::pow(2, state.getZoom() - tileID.canonical.z);
        collisionBoxShader->u_zoom = state.getZoom() * 10;
        collisionBoxShader->u_maxzoom = (tileID.canonical.z + 1) * 10;
        config.lineWidth = 1.0f;

        setDepthSublayer(0);
        bucket.drawCollisionBoxes(*collisionBoxShader, store);

    }

    config.activeTexture = GL_TEXTURE0;
}
void Painter::renderLine(LineBucket& bucket,
                         const LineLayer& layer,
                         const UnwrappedTileID& tileID,
                         const mat4& matrix) {
    // Abort early.
    if (pass == RenderPass::Opaque) return;

    config.stencilOp.reset();
    config.stencilTest = GL_TRUE;
    config.depthFunc.reset();
    config.depthTest = GL_TRUE;
    config.depthMask = GL_FALSE;

    const auto& properties = layer.impl->paint;
    const auto& layout = bucket.layout;

    // the distance over which the line edge fades out.
    // Retina devices need a smaller distance to avoid aliasing.
    float antialiasing = 1.0 / frame.pixelRatio;

    bool wireframe = frame.debugOptions & MapDebugOptions::Wireframe;

    float blur = properties.lineBlur + antialiasing;

    Color color = Color::white();
    float opacity = 1.0f;
    if (!wireframe) {
        color = properties.lineColor;
        opacity = properties.lineOpacity;
    }

    const float ratio = 1.0 / tileID.pixelsToTileUnits(1.0, state.getZoom());

    mat2 antialiasingMatrix;
    matrix::identity(antialiasingMatrix);
    matrix::scale(antialiasingMatrix, antialiasingMatrix, 1.0, std::cos(state.getPitch()));
    matrix::rotate(antialiasingMatrix, antialiasingMatrix, state.getAngle());

    // 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.0f) / 4.0f  * (1.0f + std::pow(state.getAltitude(), 2.0f)));
    float x = state.getHeight() / 2.0f * std::tan(state.getPitch());
    float extra = (topedgelength + x) / topedgelength - 1.0f;

    mat4 vtxMatrix =
        translatedMatrix(matrix, properties.lineTranslate, tileID, properties.lineTranslateAnchor);

    setDepthSublayer(0);

    if (!properties.lineDasharray.value.from.empty()) {

        config.program = linesdfShader->getID();

        linesdfShader->u_matrix = vtxMatrix;
        linesdfShader->u_linewidth = properties.lineWidth / 2;
        linesdfShader->u_gapwidth = properties.lineGapWidth / 2;
        linesdfShader->u_antialiasing = antialiasing / 2;
        linesdfShader->u_ratio = ratio;
        linesdfShader->u_blur = blur;
        linesdfShader->u_color = color;
        linesdfShader->u_opacity = opacity;

        LinePatternPos posA = lineAtlas->getDashPosition(properties.lineDasharray.value.from, layout.lineCap == LineCapType::Round, store);
        LinePatternPos posB = lineAtlas->getDashPosition(properties.lineDasharray.value.to, layout.lineCap == LineCapType::Round, store);

        const float widthA = posA.width * properties.lineDasharray.value.fromScale * layer.impl->dashLineWidth;
        const float widthB = posB.width * properties.lineDasharray.value.toScale * layer.impl->dashLineWidth;

        float scaleXA = 1.0 / tileID.pixelsToTileUnits(widthA, state.getIntegerZoom());
        float scaleYA = -posA.height / 2.0;
        float scaleXB = 1.0 / tileID.pixelsToTileUnits(widthB, state.getIntegerZoom());
        float scaleYB = -posB.height / 2.0;

        linesdfShader->u_patternscale_a = {{ scaleXA, scaleYA }};
        linesdfShader->u_tex_y_a = posA.y;
        linesdfShader->u_patternscale_b = {{ scaleXB, scaleYB }};
        linesdfShader->u_tex_y_b = posB.y;
        linesdfShader->u_sdfgamma = lineAtlas->width / (std::min(widthA, widthB) * 256.0 * frame.pixelRatio) / 2;
        linesdfShader->u_mix = properties.lineDasharray.value.t;
        linesdfShader->u_extra = extra;
        linesdfShader->u_offset = -properties.lineOffset;
        linesdfShader->u_antialiasingmatrix = antialiasingMatrix;

        linesdfShader->u_image = 0;
        config.activeTexture = GL_TEXTURE0;
        lineAtlas->bind(store);

        bucket.drawLineSDF(*linesdfShader, store);

    } else if (!properties.linePattern.value.from.empty()) {
        optional<SpriteAtlasPosition> imagePosA = spriteAtlas->getPosition(properties.linePattern.value.from, true);
        optional<SpriteAtlasPosition> imagePosB = spriteAtlas->getPosition(properties.linePattern.value.to, true);
        
        if (!imagePosA || !imagePosB)
            return;

        config.program = linepatternShader->getID();

        linepatternShader->u_matrix = vtxMatrix;
        linepatternShader->u_linewidth = properties.lineWidth / 2;
        linepatternShader->u_gapwidth = properties.lineGapWidth / 2;
        linepatternShader->u_antialiasing = antialiasing / 2;
        linepatternShader->u_ratio = ratio;
        linepatternShader->u_blur = blur;

        linepatternShader->u_pattern_size_a = {{
            tileID.pixelsToTileUnits((*imagePosA).size[0] * properties.linePattern.value.fromScale, state.getIntegerZoom()),
            (*imagePosA).size[1]
        }};
        linepatternShader->u_pattern_tl_a = (*imagePosA).tl;
        linepatternShader->u_pattern_br_a = (*imagePosA).br;

        linepatternShader->u_pattern_size_b = {{
            tileID.pixelsToTileUnits((*imagePosB).size[0] * properties.linePattern.value.toScale, state.getIntegerZoom()),
            (*imagePosB).size[1]
        }};
        linepatternShader->u_pattern_tl_b = (*imagePosB).tl;
        linepatternShader->u_pattern_br_b = (*imagePosB).br;

        linepatternShader->u_fade = properties.linePattern.value.t;
        linepatternShader->u_opacity = properties.lineOpacity;
        linepatternShader->u_extra = extra;
        linepatternShader->u_offset = -properties.lineOffset;
        linepatternShader->u_antialiasingmatrix = antialiasingMatrix;

        linepatternShader->u_image = 0;
        config.activeTexture = GL_TEXTURE0;
        spriteAtlas->bind(true, store);

        bucket.drawLinePatterns(*linepatternShader, store);

    } else {
        config.program = lineShader->getID();

        lineShader->u_matrix = vtxMatrix;
        lineShader->u_linewidth = properties.lineWidth / 2;
        lineShader->u_gapwidth = properties.lineGapWidth / 2;
        lineShader->u_antialiasing = antialiasing / 2;
        lineShader->u_ratio = ratio;
        lineShader->u_blur = blur;
        lineShader->u_extra = extra;
        lineShader->u_offset = -properties.lineOffset;
        lineShader->u_antialiasingmatrix = antialiasingMatrix;

        lineShader->u_color = color;
        lineShader->u_opacity = opacity;

        bucket.drawLines(*lineShader, store);
    }
}