Speed up args.Display.Draw3dText

I have created a custom component which draws labels adjacent to points. I draw these labels using Draw3dText, and then orient them towards the camera position so that they always stand up when the user pans/rotates.

Unfortunately I need to draw a fair number of these (likely order ~300 user input points, 3 labels per point :. 900 labels).

I can do a little bit of work cacheing some of the maths I use in my routine (bounding boxes to draw a shaded brep background to the label), however I have profiled and found that the main bottleneck is calls to Draw3dText.

I can’t cache everything as if the user updates the viewport all of the 3dText must be drawn again, or does it? Are there any other inventive things I could do e.g. multi-threading the calls to Draw3dText?

Any help much appreciated. The nuclear option is just ask my user to supply a plane to draw the labels on and then only update when that changes, however it is not quite as user friendly as the current arrangement.

For a bit more context here is the method I am trying to optimise:

public void DrawLabel(Point3d basePt, double heightAbove, string text, double textHeight, System.Drawing.Color bgColour, System.Drawing.Color textColour, Plane plane, IGH_PreviewArgs args)
    {
        //Can only extract bounding box of text3D in world co-ordinates
        //Deal with background first
        Rhino.Display.Text3d t3dMeasure = new Rhino.Display.Text3d(text, Plane.WorldXY, textHeight);
        BoundingBox bbMeasure = t3dMeasure.BoundingBox;
        double width = bbMeasure.Corner(false, true, true).X - bbMeasure.Corner(true, true, true).X;

        //New point in world for drawing text
        //Moved so that the text will be centered over the origin
        //Moved Y by the offset amount
        //Pushed forward a bit so there is no clipping
        double xMove = -width / 2;
        double yMove = heightAbove;
        double zMove = 0.01;
        Point3d pWorld = new Point3d(xMove, yMove, zMove);
        Plane plWorld = Plane.WorldXY;
        plWorld.Origin = pWorld;

        //Draw text again so that a bounding box in world can be extracted
        Rhino.Display.Text3d t3dWorld = new Rhino.Display.Text3d(text, plWorld, textHeight);
        BoundingBox bbWorld = t3dWorld.BoundingBox;

        //Inflate box so that it fits around text nicely
        bbWorld.Inflate(textHeight / 10, textHeight / 10, 0);

        //Push box back slightly so text is in front
        Transform tBack = new Transform();
        tBack = Transform.Translation(-0.001 * Plane.WorldXY.ZAxis);
        bbWorld.Transform(tBack);

        //Gather points for a polygon
        List<Point3d> polyPoints = new List<Point3d>();
        polyPoints.Add(bbWorld.Corner(true, true, true));
        polyPoints.Add(bbWorld.Corner(true, false, true));
        polyPoints.Add(bbWorld.Corner(false, false, true));
        polyPoints.Add(bbWorld.Corner(false, true, true));

        //Calculate change of basis from world origin to the column we care about
        plane.Origin = basePt;
        Transform tBasis = new Transform();
        tBasis = Transform.PlaneToPlane(Plane.WorldXY, plane);

        //Align polygon plane of interest
        List<Point3d> polyPoints2 = new List<Point3d>();
        foreach (Point3d p in polyPoints)
        {
            p.Transform(tBasis);
            polyPoints2.Add(p);
        }

        //Draw text
        //Move world start to in-plane
        Plane pl = plWorld;
        pl.Transform(tBasis);
        Rhino.Display.Text3d t3d = new Rhino.Display.Text3d(text, pl, textHeight);

        //Draw everything
        args.Display.Draw3dText(t3d, textColour);
        args.Display.DrawPolygon(polyPoints2, bgColour, true);
    }

Display code in general does not support multithreaded access so I would recommend staying away from any attempts to draw things using multiple threads. Doing as little work as possible during the draw operation is going to be the best route to take which means caching information that repeats between frames.