Performance problem on Mac when drawing from offscreen context with HiDPI mode active

We’ve gotten complaints from some users about the vertical scrolling of a graph in our plug-in. This only happens when the machine/display in question is in HiDPI mode, apparently. If I drag the plug-in to my monitor that is not using a HiDPI mode, it performs fine. When I drag it back to my monitor that is using HiDPI mode, the scrolling slows way down.

The reason that the vertical scrolling is affected but not the horizontal scrolling is that there is a scrollbar to the left of the graph, and its vertical bounds are not the same as the graph’s. And when the graph is scrolled vertically, the scrollbar invalidates (as well as the graph), and that causes the graph to get two drawRect() calls instead of one: one for the vertical area matching the scrollbar bounds, and one for the vertical area of the graph outside the scrollbar’s vertical bounds.

In trying to figure out what is happening, and why this second call is causing a problem, I found that the difference occurs in the call CGDrawContext::drawBitmap(). I am posting that code below so you can see what I am referring to more easily.

When the first of the two drawRect() calls happens in the graph (or if only one such call is made, as in the horizontal scroll case), the code enters the “if (it == bitmapDrawCount.end ())” block, which calls bitmapDrawCount.insert(). But on the second drawRect() call, it enters the “else” block, which calls cgBitmap->createCGLayer(). It is this call to createCGLayer() that appears to be taking the extra time when drawing after a vertical scroll.

void CGDrawContext::drawBitmap (CBitmap* bitmap, const CRect& inRect, const CPoint& inOffset, float alpha)
	if (bitmap == 0 || alpha == 0.f)
	double transformedScaleFactor = scaleFactor;
	CGraphicsTransform t = getCurrentTransform ();
	if (t.m11 == t.m22 && t.m12 == 0 && t.m21 == 0)
		transformedScaleFactor *= t.m11;
	IPlatformBitmap* platformBitmap = bitmap->getBestPlatformBitmapForScaleFactor (transformedScaleFactor);
	CGBitmap* cgBitmap = platformBitmap ? dynamic_cast<CGBitmap*> (platformBitmap) : 0;
	CGImageRef image = cgBitmap ? cgBitmap->getCGImage () : 0;
	if (image)
		CGContextRef context = beginCGContext (false, false);
		if (context)
			CGLayerRef layer = cgBitmap->getCGLayer ();
			if (layer == 0)
				BitmapDrawCountMap::iterator it = bitmapDrawCount.find (cgBitmap);
				if (it == bitmapDrawCount.end ())
					bitmapDrawCount.insert (std::pair<CGBitmap*, int32_t> (cgBitmap, 1));
					layer = cgBitmap->createCGLayer (context);

			drawCGImageRef (context, image, layer, cgBitmap->getScaleFactor (), inRect, inOffset, alpha, bitmap);

			releaseCGContext (context);

I really don’t know what this code is doing in either case, since it’s not documented here. Can someone let me know if this code is correct? Somehow it seems kind of backwards to me, in that I would expect the first time through that you’d create something and add it to a list or something, so that subsequent calls could skip creating the object. But as I said, I don’t really know what this code is actually doing.

Can anyone help? We’re getting a lot of complaints about this.

Oh, by the way, the bitmap we’re drawing comes from an offscreen context into which we’ve done our actual graph drawing when scrolling. I get it via offscreenContext->getBitmap();, then call bitmap->draw().

I’m also curious why the invalidation rectangles are not merged into one for this view in the first place, since they abut vertically and have the same left and right values.

I found a work-around, at least. I derived a class from CViewContainer, which overrides only invalidRect(), which simply calls CViewContainer(size). This causes the entire view that contains my scrollbar and zoom and scroll buttons to be invalidated, so that the graph to the right never gets broken into separate redraw areas by those items.

Maybe this is related to (Performance issues with Core Graphics).