37

I'm drawing a rather large path in my view and I'm running into some performance problems. The path is currently 32,000 points long, but my application should scale to at least 128,000 points. I can't really do anything about the size of the path, as the datasets are just that large and I need to be able to display the whole path at once and allow zooming in.

I'm using a Nexus 10 running Android 4.2, which has hardware acceleration enabled by default for applications that don't explicitly disable it.

The path is created with the following code (I omitted some setup and other irrelevant parts):

dataPath.moveTo(0, offset - (float) data[leftLimit]/ scalingFactor);
        for (int i = leftLimit; i < rightLimit; ++i) {
            x = (i - leftLimit) * dx;
            y = offset - (float) data[i]/ scalingFactor;
            dataPath.lineTo(x, y);
        }

And then drawn in the onDraw() method:

canvas.drawColor(Color.WHITE);
canvas.drawPath(dataPath, linePaint);

I measured the time it takes to draw my view using adb shell dumpsys gfxinfo with and without hardware acceleration, and to my suprise the hardware acceleration is much slower:

With hardware acceleration:

enter image description here

Without hardware acceleration:

enter image description here

The hardware accelerated version takes around 200-300 ms per frame, most spent in the Process stage. The non-accelerated version takes around 50 ms, with 2/3 in the Draw stage and 1/3 in the process stage.

Obviously even my faster version without hardware acceleration is still too slow to achieve 60 fps, or to be even barely useable when I move to larger datasets.

The idea to render the path to a bitmap and then only transform that bitmap to fit the screen is also problematic in my case. I need to support zooming in very far onto the path, and to enable zooming in without the path quality getting much worse I would have to render oversized bitmaps of the path (and would likely run into memory limits and the texture size limits). And when zooming in far I would have to either create newer images of only parts of the path, or switch to just rendering the path directly, which likely would lead to delays greater than the framerate if the performance is still similar to what I have right now.

What I'm wondering now is

  • Is drawing lines/paths just something the GPU is bad at and that one should not try to hardware accelerate, or am I likely doing something wrong that causes the bad performance?
  • Is there anything I can do to draw such huge paths with acceptable performance?
Mad Scientist
  • 18,090
  • 12
  • 83
  • 109
  • How does OpenGL fit into this exactly? – Nicol Bolas Feb 23 '13 at 11:47
  • The hardware acceleration uses OpenGL, as far as I understand it. But I wasn't sure which tag to use there. – Mad Scientist Feb 23 '13 at 11:52
  • 1
    How are you drawing this? If you plot out 1 point at a time to the GPU then you will probably get worse performance, but if you batch it all at ones you should see an increase, at least in theory. – Automatico Feb 23 '13 at 11:52
  • 1
    @Cort3z I added the code that draws the path, I'm creating the complete path and then draw it in one go. – Mad Scientist Feb 23 '13 at 12:01
  • 1
    If you are zoomed out, is there really any point in drawing 128k points? Most likely, these will be indistinguishable. I recommend if you are zoomed out you can skip or average some of these points. – Pedro Loureiro Mar 06 '13 at 15:54
  • @PedroLoureiro I've thought about that, but it gets rather complicated. I can't just average the points, as that could hide the amount of noise in the data, which would lead to a wrong display of the data. I'll probably have to go in that direction in the end, I was hoping that the power of current GPUs would be enough and I could implement a simple solution. – Mad Scientist Mar 06 '13 at 16:06
  • @MadScientist hehe good try, that's how you make bad code :) – Warpzit Mar 06 '13 at 17:43
  • It can be Worth checking out Douglas-Pecker algorithm: http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm – Just another metaprogrammer Mar 06 '13 at 18:05
  • In addition R*-trees (or something similar) can efficiently answer the question: "What Points are inside this rect" – Just another metaprogrammer Mar 06 '13 at 18:06
  • @MadScientist in that case, this kind of charts might help you: http://en.wikipedia.org/wiki/Candlestick_chart. Maybe it's not applicable for your case, but maybe you get the idea and came up with something else. – Pedro Loureiro Mar 06 '13 at 22:18

3 Answers3

51

Is drawing lines/paths just something the GPU is bad at and that one should not try to hardware accelerate, or am I likely doing something wrong that causes the bad performance?

Paths are always rendered using the CPU. When the app is hardware accelerated this means the renderer will first draw your path using the CPU into a bitmap, then upload that bitmap as a texture to the GPU and finally draw the texture on screen.

Lines are entirely hardware accelerated. Instead of using a Path I recommend you use Canvas.drawLines() in this case.

Is there anything I can do to draw such huge paths with acceptable performance?

You should either use Canvas.drawLines() or render the path yourself into a Bitmap that you manage.

Romain Guy
  • 97,993
  • 18
  • 219
  • 200
  • 2
    As for the drawing paths manually into the `Bitmap` and allowing them to be moved along over the screen, I pushed simple code [here](https://github.com/barthand/android-pathbitmap). I used the same technique to achieve drawing such paths on the GPU-accelerated MapView as an overlay (where usual GPU-accelerated method of drawing Path on the Overlay was causing `Shape path too large to be rendered into a texture` error due to texture size limitation on the GPU). – barthand May 06 '13 at 21:19
  • For me, using `Canvas.drawLines(float[], Paint)` with a batch of points, instead of multiple calls to `Canvas.drawLine(int. int, int, int)` with single point, increased the performance significantly. – Robert Oct 31 '17 at 17:16
  • Is there an official site or document which states that Path uses CPU and `Canvas.drawLines()` uses hardware acceleration ? I am asking this because I had very bad performance, searched for performance tips but did not find something meaningful, till I read this answer. – Robert Nov 01 '17 at 09:09
  • There is a mention here at the very bottom: https://developer.android.com/guide/topics/graphics/hardware-accel.html – Romain Guy Nov 07 '17 at 18:52
  • @RomainGuy I started using drawLines instead of drawPath and saw significant performance improvements. However, I cannot find a way to have nicely connected lines as I had with drawPath, check this image: https://i.stack.imgur.com/NMpFo.png. Do you know how to fix this? – makovkastar Mar 19 '19 at 15:23
5

It seems like you might be running into general bottleneck in the GPU. Have a look at the following links:

How to make route drawing more efficient

Android canvas path real time performance

Switching from canvas to OpenGL

Especially the first one, which suggest that you make a bitmap and draw that instead. It looks like you can get some better performance if you change to openGL. There might be some magic way of getting the existing method to work, but I am not aware of that at this moment.

Why you are seeing the behavior you do is probably because you send all the data to the GPU between each draw. You should cache it on the GPU and use translation in stead. Not recreating the data and sending it all to the GPU. You are probably I/O bound, meaning the transmission rate between the CPU and GPU is what is limiting your performance. I can not be 100% sure of this based on the data you have provided, but that is my best guess. Try different caching techniques, mainly the png-cache from link #1.

Community
  • 1
  • 1
Automatico
  • 12,420
  • 9
  • 82
  • 110
3

Drawing a path will involve more than just "telling" the GPU to draw lines. A path has many characteristics, for example how ends of lines are treated or that a line is dotted, something that is not GPU accelerated. There is probably then another hickup when you encounter that many lines that makes the combination of CPU and GPU acceleration go slower. The suggested solution above to move to canvas.drawLines instead of canvs.drawPath and try to not use a fancy Paint method might help.

If that does not help you can try to use a GLSurfaceView and render lines directly into that instead. That will involve some OpenGL ES knowledge, but should not be incredibly hard to do and might be a lot more effective.

  • Although not directly relevant to the hardware acceleration question (but to the example given), I have found that using the Matrix class for scaling and translation of lists of points and paths is many times faster than looping in your code. Like 20 to 50 times faster. I had some complex Areas that needed Paths. Scaling went from 240 ms with loops, to 6 ms with a Matrix. matrix.setScale(factor, factor, centerX, centerY) and then path.transform(matrix). Or matrix.mapPoints(pointsarray). – Dave Hubbard Apr 06 '18 at 14:51