I'm working on a camera app, i'm showing the preview image luminosity histogram in a small rect (128x128 pix) overlying the live preview.
Sometimes an ANR happened, so i started using traceview to optimize my code (i'm doing some image manipulation on the fly, but it's very quick NEON asm & native code, no problem with it).
Using traceview i discovered that Canvas.drawLine() method is terribly slow. I have to update histogram 30 times per second in customView.onDraw(), drawing just 128 lines every frame. Incredibly, drawing 128 lines takes >8% cpu time (!!), when the entire native code to manipulate-convert the whole frame (720x480 yuv to ARGB_8888) takes <18%
I tried to draw the histogram on a new bitmap canvas then drawBitmap() it to the view's canvas but drawLine()s still take a lot of CPU.
I'm looking for an idea to avoid drawLine()...
I juts have to draw a small histogram from a int[128] normalized to 128
Here's my customView.onDraw (more or less...)
#Override
protected void onDraw(Canvas canvas) {
int size = 128;
int y = pos_y + size;
int x;
for(int i=0;i<size;i++) {
if(histogram_data[i]>1) {
x = pos_x+i;
// this is the slow call!!
canvas.drawLine(x, y, x, y-histogram_data[i], paint_histogram);
}
}
}
You could try to use Path instead of Lines.
http://developer.android.com/reference/android/graphics/Path.html
If you read HERE they say the following:
Animating large Paths
When Canvas.drawPath() is called on the hardware accelerated Canvas
passed to Views, Android draws these paths first on CPU, and uploads
them to the GPU. If you have large paths, avoid editing them from
frame to frame, so they can be cached and drawn efficiently.
drawPoints(), drawLines(), and drawRect/Circle/Oval/RoundRect() are
more efficient – it's better to use them even if you end up using more
draw calls.
So you can try calling drawLines() once for drawing multiple lines at the same time instead of calling drawLine() for each line separately.
For me personally I had to draw 500+ lines, and I tried all available method for drawing line in canvas: drawPath(), drawLine() and drawLines(), but with all of them I felt enormous lagging on my phone, so the only available alternative was to use OpenGL ES. Here is a example I wrote using OpenGL for drawing large amount of lines, I tested it on my phone and it is smooth as butter😄. You can check the source code it is written in Kotlin, it also support finger gestures, that way you can Scale, Translate and Rotate the whole scene with your fingers. And I have included all basic shapes Rectangle, Triangle, Circle, Line, Polygon, and you can set shape coordinates as values between [-1,1], or with the familiar way using pixel values. If you want to learn more about OpenGL you can check this post by the android team.
Here is the result of the OpenGL example I wrote:
maybe you can add new layer on top and draw these 128 lines only once while leaving other pixels transparent.
Related
Use case:
I need to draw hundred lines and a few pieces of text on my view. I need to give a scrolling effect, for which I capture the ACTION_MOVE event and redraw all the lines with the updated points. To get the desire result I tried with different approaches but none works as intended.
Approach 1
I made a custom class which extends View. All the drawing and calculation is done directly in my onDraw() method. Since there is so much of operation done in onDraw() method, the performance of the app is very poor. I even checked the performance using Profile GPU rendering and I can see the lines are very tall.
Approach 2
I created a Bitmap and after drawing all the lines onto my bitmap in another thread, I used postInvalidate() to draw the bitmap in onDraw() method:
mBufferedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mBufferedBitmap.eraseColor(Color.TRANSPARENT);
Canvas mBufferedCanvas = new Canvas(mBufferedBitmap);
drawLines(mBufferedCanvas)
postInvalidate();
Since I erase all the previous drawing on the bitmap and draw new lines with updated points, there is a flickering on the screen.
Approach 3
I tried extending my custom class to SurfaceView and performing all the operations on canvas object in another thread. But since SurfaceView uses CPU for drawing operations, the performance will be poor in low configuration mobiles.
Can anyone guide me how to achieve this task with better performance?
It is possible to use your approach 1 to achieve good performance.
An example that sounds close to your use case (drawing lines, a little text and having these update on gesture movement) is MPAndroidChart. This is an open source library that achieves high performance (see the following comparison if you want stats)
The classes to examine are the Renderer classes as these contain code draw in the onDraw(Canvas c) of the chart subtypes. You can see some of the tricks used to achieve high performance there:
Don't allocate in a render loop. Instead, allocate outside the loop and reuse/recycle variables. See LineChartRenderer line 199
Use buffering. For example, in MPAndroidChart, the points for the four corners of the bars in the bar chart are buffered and the buffer array is reused. See the BarBuffer class.
Use the native Canvas drawing functions (drawPath, drawLine etc.)
A complete list of tips for optimising rendering can be found in the Android Performance Slow Rendering Guide
Approach 2 is the best one. If you see flickering, it means that the bitmap is drawn to screen after you erase it and before you draw all the lines. If this is the case use another bitmap and do double buffering:
ScreenBitmap is what is drawn to screen
OffScreenBitmap is used for drawing in background.
Draw all your lines and text to OffScreenBitmap, and once finished, copy it to ScreenBitmap.
in onDraw, draw the ScreenBitmap.
Create these bitmaps once (typically in onSizeChanged), so that there is no alloocation in onDraw
I have imported a model (e.g. a teapot) using Rajawali into my scene.
What I would like is to label parts of the model (e.g. the lid, body, foot, handle and the spout)
using plain Android views, but I have no idea how this could be achieved. Specifically, positioning
the labels on the right place seems challenging. The idea is that when I transform my model's position in the scene, the tips of the labels are still correctly positioned
Rajawali tutorial show how Android views can be placed on top of the scene here https://github.com/Rajawali/Rajawali/wiki/Tutorial-08-Adding-User-Interface-Elements
. I also understand how using the transformation matrices a 3D coordinate on the model can be
transformed into a 2D coordinate on the screen, but I have no idea how to determine the exact 3D coordinates
on the model itself. The model is exported to OBJ format using Blender, so I assume there is some clever way of determining
the coordinates in Blender and exporting them to a separate file or include them somehow in the OBJ file (but not
render those points, only include them as metadata), but I have no idea how I could do that.
Any ideas are very appreciated! :)
I would use a screenquad, not a view. This is a general GL solution, and will also work with iOS.
You must determine the indices of the desired model vertices. Using the text rendering algo below, you can just fiddle them until you hit the right ones.
Create a reasonable ARGB bitmap with same aspect ratio as the screen.
Create the screenquad texture using this bitmap
Create a canvas using this bitmap
The rest happens in onDrawFrame(). Clear the canvas using clear paint.
Use the MVP matrix to convert desired model vertices to canvas coordinates.
Draw your desired text at the canvas coordinates
Update the texture.
Your text will render very precisely at the vertices you specfied. The GL thread will double-buffer and loop you back to #4. Super smooth 3D text animation!
Use double floating point math to avoid loss of precision during coordinate conversion, which results in wobbly text. You could even use the z value of the vertex to scale the text. Fancy!
The performance bottleneck is #7 since the entire bitmap must be copied to GL texture memory, every frame. Try to keep the bitmap as small as possible, maintaining aspect ratio. Maybe let the user toggle the labels.
Note that the copy to GL texture memory is redundant since in OpenGL-ES, GL memory is just regular memory. For compatibility reasons, a redundant chunk of regular memory is reserved to artificially enforce the copy.
I'm trying to create a bullethell game and Ive run into a bit of a trouble. I can't get more than 17 fps after about 500 bullets. The update logic code takes around 1-4ms for all of them while the render code takes around 40ms
For now my code is
private void drawEntities(Canvas canvas) {
for (HashMap<UUID, Spatial> h: spatialList) {
for (Spatial spatial: h.values()) {
spatial.render(canvas);
if(spatial.life > 0)
spatial.life--;
else if (spatial.life == 0)
engine.deleteEntity(spatial.owner);
}
}
}
spatialList is an arrayList where each index is a zLevel
The spatial which displays the actual bullet is
public void render(Canvas canvas) {
float angle = (float) (vel.getAngle() * (180 / Math.PI));
matrix.reset();
matrix.setTranslate(pos.x - bullet.getWidth() / 2, pos.y - bullet.getHeight() / 2);
matrix.postRotate(angle + 90, pos.x, pos.y);
canvas.drawBitmap(bullet, matrix, paint);
canvas.drawCircle(pos.x, pos.y, col.getRadius(), paint);
}
I can provide more code but these seem to be the main issue. I've tried everything I can think of and can't find much else online. The only thing I can think of to fix this is to switch from a surfaceview to a GLSurfaceview but I really think there is a better way and I'm just using bad code.
Edit: I noticed my timer was off and removed the drawcircle and after running it again I get 40ms~ around 500 which is still a bit too low for reasonable performance.
TLDR; 500 entities = 17 fps.
You may be limited by pixel fill rate. How large (in pixels) is the display on your test device?
One simple thing to play with is to use setFixedSize() to reduce the size of the SurfaceView's Surface. That will reduce the number of pixels you're touching. Example here, video here, blog post here.
It's generally a good idea to do this, as newer devices seem to be racing toward absurd pixel counts. A full-screen game that performs all rendering in software is going to struggle on a 2560x1440 display, and flail badly at 4K. "Limiting" the game to 1080p and letting the display scaler do the heavy lifting should help. Depending on the nature of the game you could set the resolution even lower with no apparent loss of quality.
Another thing to try is to eliminate the drawBitmap() call, check your timings, then restore it and eliminate the drawCircle() call, to see if one or the other is chewing up the bulk of the time.
You may find switching to OpenGL ES not so bad. The "hardware scaler exerciser" activity in Grafika (from the video linked above) shows some simple bitmap rendering. Replace drawCircle() with a scaled bitmap and you might be most of the way done. (Note it uses SurfaceView, not GLSurfaceView, for GLES in that activity.)
I had the same issue. The problem will be solved to a large extent if you do all the drawing on a bitmap framebuffer and then draw the framebuffer to the canvas. If you are directly drawing on the canvas then there are several overheads. I looked around and found this tutorial
"http://www.kilobolt.com/day-6-the-android-game-framework-part-ii.html"
Look at the implementation for AndroidGraphics and AndroidFastRenderView and see how he uses AndroidGraphics to do all the actual drawing in a buffer and paints that buffer to the canvas in AndroidFastRenderView.
Try the performance when you remove the rotate and/or drawCircle parts from your render() method. Both of them might be quite time consuming as they probably contain sin() / cos() calculations.
If that helps, you'll have to figure out how to replace them with something faster, if not, well...
If you always have to draw the same rectangle, it is faster to do it with a static bitmap or with canvas.drawRect()?
For this example, the are four layered rectangles. So a boarder with a fill color, and then a boarder between a middle color and the fill color.
So four paint.setColor() commands and four canvas.drawRect commands or one canvas.drawBitmap().
I strongly recommend drawRect().
Bitmaps take up a huge chunk of memory, and can lead to Out Of Memory Exceptions if not used correctly.
Written by android:
Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices.
To prevent headache, and unexpected crashes. Use drawRect();
If you are doing these 4 draws on a regular basis for different objects, consider writing a method that does all 4 for you. So you are not causing massive repetition.
For example:
public void DrawMyRect(Canvas canvas, int x, int y)
{
canvas.drawRect(x, y ,x + 15, y + 40, paint);
// Draw its line etc etc..
}
Alternatively, if you do go for drawing bitmap, as it does have advantages:
See this epic Link by Android, on how to properly use Bitmaps
The performance difference is probably negligible. The bitmap will use more memory, the canvas draw calls will use slightly more CPU. You can probably use a ShapeDrawable if you want to reduce the calls without the overhead of a bitmap.
I have a byte buffer in my Android application,consider it as a vector.The data in the buffer is changed dynamically(There is a separate thread to update the buffer).I want to draw these data dynamically.
Every data represents a point's Y coordinate in the View,connect the consecutive points to form a curve.As the buffer is updated periodically,the curve looks like moving forward smoothly.
Firstly,I implement this by drawing lines in the View's onDraw(Canvas canvas) method,but it is very ineffective.When calling invalidate method is too frequently, the CPU consume is very heavy.
So I change to use the SurfaceView, draw the dynamic curve in the separate thread, but It is still not satisfactory.
I want to know whether there is any good methods to achieve this.Whether is OpenGL ES a choice?
In OpenGL ES 1.0 you can use glDrawArrays in GL_LINES mode. It will do exactly what the Canvas is doing with your data, but considerably faster