void Painter::drawClippingMasks(PaintParameters& parameters, const std::map<UnwrappedTileID, ClipID>& stencils) {
    MBGL_DEBUG_GROUP("clipping masks");

    auto& plainShader = parameters.shaders.plain;
    auto& arrayCoveringPlain = parameters.shaders.coveringPlainArray;

    mat4 matrix;
    const GLuint mask = 0b11111111;

    config.program = plainShader.getID();
    config.stencilOp.reset();
    config.stencilTest = GL_TRUE;
    config.depthTest = GL_FALSE;
    config.depthMask = GL_FALSE;
    config.colorMask = { GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE };
    config.stencilMask = mask;

    arrayCoveringPlain.bind(plainShader, tileStencilBuffer, BUFFER_OFFSET_0, store);

    for (const auto& stencil : stencils) {
        const auto& id = stencil.first;
        const auto& clip = stencil.second;

        MBGL_DEBUG_GROUP(std::string{ "mask: " } + util::toString(id));
        state.matrixFor(matrix, id);
        matrix::multiply(matrix, projMatrix, matrix);
        plainShader.u_matrix = matrix;

        const GLint ref = (GLint)(clip.reference.to_ulong());
        config.stencilFunc = { GL_ALWAYS, ref, mask };
        MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index()));
    }
}
void Painter::renderTileDebug(const RenderTile& renderTile) {
    if (frame.debugOptions == MapDebugOptions::NoDebug)
        return;

    MBGL_DEBUG_GROUP(std::string { "debug " } + util::toString(renderTile.id));

    auto draw = [&] (Color color, const auto& vertexBuffer, const auto& indexBuffer, const auto& segments, auto drawMode) {
        programs->debug.draw(
            context,
            drawMode,
            gl::DepthMode::disabled(),
            stencilModeForClipping(renderTile.clip),
            gl::ColorMode::unblended(),
            DebugProgram::UniformValues {
                uniforms::u_matrix::Value{ renderTile.matrix },
                uniforms::u_color::Value{ color }
            },
            vertexBuffer,
            indexBuffer,
            segments
        );
    };

    if (frame.debugOptions & (MapDebugOptions::Timestamps | MapDebugOptions::ParseStatus)) {
        Tile& tile = renderTile.tile;
        if (!tile.debugBucket || tile.debugBucket->renderable != tile.isRenderable() ||
            tile.debugBucket->complete != tile.isComplete() ||
            !(tile.debugBucket->modified == tile.modified) ||
            !(tile.debugBucket->expires == tile.expires) ||
            tile.debugBucket->debugMode != frame.debugOptions) {
            tile.debugBucket = std::make_unique<DebugBucket>(
                tile.id, tile.isRenderable(), tile.isComplete(), tile.modified,
                tile.expires, frame.debugOptions, context);
        }

        draw(Color::white(),
             *tile.debugBucket->vertexBuffer,
             *tile.debugBucket->indexBuffer,
             tile.debugBucket->segments,
             gl::Lines { 4.0f * frame.pixelRatio });

        draw(Color::black(),
             *tile.debugBucket->vertexBuffer,
             *tile.debugBucket->indexBuffer,
             tile.debugBucket->segments,
             gl::Lines { 2.0f * frame.pixelRatio });
    }

    if (frame.debugOptions & MapDebugOptions::TileBorders) {
        draw(Color::red(),
             tileVertexBuffer,
             tileBorderIndexBuffer,
             tileBorderSegments,
             gl::LineStrip { 4.0f * frame.pixelRatio });
    }
}
void Painter::renderTileDebug(const RenderTile& tile) {
    MBGL_DEBUG_GROUP(std::string { "debug " } + util::toString(tile.id));
    if (frame.debugOptions != MapDebugOptions::NoDebug) {
        setClipping(tile.clip);
        if (frame.debugOptions & (MapDebugOptions::Timestamps | MapDebugOptions::ParseStatus)) {
            renderDebugText(tile.tile, tile.matrix);
        }
        if (frame.debugOptions & MapDebugOptions::TileBorders) {
            renderDebugFrame(tile.matrix);
        }
    }
}
void Painter::renderDebugFrame(const mat4 &matrix) {
    MBGL_DEBUG_GROUP("debug frame");

    // Disable depth test and don't count this towards the depth buffer,
    // but *don't* disable stencil test, as we want to clip the red tile border
    // to the tile viewport.
    config.depthTest = GL_FALSE;
    config.stencilOp.reset();
    config.stencilTest = GL_TRUE;

    auto& plainShader = *shader.plain;
    config.program = plainShader.getID();
    plainShader.u_matrix = matrix;
    plainShader.u_opacity = 1.0f;

    // draw tile outline
    tileBorderArray.bind(plainShader, tileBorderBuffer, BUFFER_OFFSET_0, store);
    plainShader.u_color = { 1.0f, 0.0f, 0.0f, 1.0f };
    config.lineWidth = 4.0f * frame.pixelRatio;
    MBGL_CHECK_ERROR(glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index()));
}
void Painter::renderDebugText(Tile& tile, const mat4 &matrix) {
    MBGL_DEBUG_GROUP("debug text");

    config.depthTest = GL_FALSE;

    if (!tile.debugBucket || tile.debugBucket->renderable != tile.isRenderable() ||
        tile.debugBucket->complete != tile.isComplete() ||
        !(tile.debugBucket->modified == tile.modified) ||
        !(tile.debugBucket->expires == tile.expires) ||
        tile.debugBucket->debugMode != frame.debugOptions) {
        tile.debugBucket = std::make_unique<DebugBucket>(
            tile.id, tile.isRenderable(), tile.isComplete(), tile.modified,
            tile.expires, frame.debugOptions);
    }

    auto& plainShader = *shader.plain;
    config.program = plainShader.getID();
    plainShader.u_matrix = matrix;
    plainShader.u_opacity = 1.0f;

    // Draw white outline
    plainShader.u_color = Color::white();
    config.lineWidth = 4.0f * frame.pixelRatio;
    tile.debugBucket->drawLines(plainShader, store);

#ifndef GL_ES_VERSION_2_0
    // Draw line "end caps"
    MBGL_CHECK_ERROR(glPointSize(2));
    tile.debugBucket->drawPoints(plainShader, store);
#endif

    // Draw black text.
    plainShader.u_color = Color::black();
    config.lineWidth = 2.0f * frame.pixelRatio;
    tile.debugBucket->drawLines(plainShader, store);

    config.depthFunc.reset();
    config.depthTest = GL_TRUE;
}