Why is lockCanvas() slow? - android

I'm implementing a SurfaceView subclass, where I run a separate thread to draw onto a SurfaceHolders Canvas.
I'm measuring time before and after call to lockCanvas(), and I'm getting from about 70ms to 100ms.
Does anyone could point me why i'm getting such high timings?
Here the relevant part of the code:
public class TestView extends SurfaceView implements SurfaceHolder.Callback {
....
boolean created;
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
mThread = new DrawingThread(mHolder, true);
mThread.onWindowResize(width, height);
mThread.start();
}
public void surfaceCreated(SurfaceHolder holder) {
created = true;
}
public void surfaceDestroyed(SurfaceHolder holder) {
created = false;
}
class DrawingThread extends Thread {
public void run() {
while(created) {
Canvas canvas = null;
try {
long t0 = System.currentTimeMillis();
canvas = holder.lockCanvas(null);
long t1 = System.currentTimeMillis();
Log.i(TAG, "Timing: " + ( t1 - t0) );
} finally {
holder.unlockCanvasAndPost(canvas);
}
}

You're creating a thread every time the surface is changed. You should start your thread in surfaceCreated and kill it in surfaceDestroyed. surfaceChanged is for when the dimensions of your surface changes.
From SurfaceView.surfaceCreated docs:
This is called immediately after the surface is first created. Implementations of this should start up whatever rendering code they desire. Note that only one thread can ever draw into a Surface, so you should not draw into the Surface here if your normal rendering will be in another thread.
The multiple threads are probably getting you throttled. From SurfaceHolder.lockCanvas docs:
If you call this repeatedly when the Surface is not ready (before Callback.surfaceCreated or after Callback.surfaceDestroyed), your calls will be throttled to a slow rate in order to avoid consuming CPU.
However, I'm not convinced this is the only problem. Does surfaceChanged actually get called multiple times?

This is related to how lockCanvas is actually implemented in the android graphic framework.
You should probably already know that lockCanvas will return you an free piece of memory that you will be used to draw to. By free, it means this memory has not be used for composition and not for display. Internally, simply speaking, an SurfaceView is backed up by double buffer, one is for drawing , one is for composition/display. This double buffer is managed by BufferQueque. If composition/display is slow than drawing, we have to wait until we have free buffer available.

read this:
What does lockCanvas mean (elaborate)

Related

Low frame rate when drawing full screen drawable on canvas

The app I'm developing is a Flappy Bird clone.
I'm using a surfaceView object in which I have a gameThread and inside of its run method I draw the various components of the game on the canvas.
Everything runs smoothly as long as I just draw Rects to represent the objects, but as soon as I added the first Drawables i noticed a little bit of a loss in smoothness. If I try to draw the background as a Drawable the game suffers very significant frame rate loss.
What I tried:
Using png and all different kinds of bitmap as assets
Resizing the asset to fit the canvas perfectly, thus avoiding a rescale
None of this had any tangible effect.
Basically:
If I only use drawRect: 60fps
If I draw the back with drawRect and the other components with drawable.draw(canvas): 57fps
If I draw everything (background included) with drawable.draw(canvas): 15fps
Somewhat relevant code:
public class CannonView extends SurfaceView
implements SurfaceHolder.Callback {
private CannonThread cannonThread; // controls the game loop
private Drawable background;
// constructor
public CannonView(Context context, AttributeSet attrs) {
super(context, attrs); // call superclass constructor
getHolder().addCallback(this);
background= ResourcesCompat.getDrawable(getResources(), R.drawable.background, null);
}
public void newGame() {
background.setBounds(0,0, getScreenWidth(),getScreenHeight());
}
public void drawGameElements(Canvas canvas) {
background.draw(canvas);
}
public void stopGame() {
if (cannonThread != null)
cannonThread.setRunning(false); // tell thread to terminate
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (!dialogIsDisplayed) {
newGame(); // set up and start a new game
cannonThread = new CannonThread(holder); // create thread
cannonThread.setRunning(true); // start game running
cannonThread.start(); // start the game loop thread
}
}
private class CannonThread extends Thread {
private SurfaceHolder surfaceHolder; // for manipulating canvas
private boolean threadIsRunning = true; // running by default
// initializes the surface holder
public CannonThread(SurfaceHolder holder) {
surfaceHolder = holder;
setName("CannonThread");
}
// changes running state
public void setRunning(boolean running) {
threadIsRunning = running;
}
// controls the game loop
#Override
public void run() {
Canvas canvas = null; // used for drawing
while (threadIsRunning) {
try {
// get Canvas for exclusive drawing from this thread
canvas = surfaceHolder.lockCanvas(null);
synchronized(surfaceHolder) {
drawGameElements(canvas);
}
}
finally {
if (canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
It seems apparent that the dominant cause of the low frame rate is background.draw(). Switching to a Bitmap improves this somewhat, probably since it cached the output of draw(), and because it can be used with Canvas functions that are guaranteed not to need scaling (e.g., drawBitmap( Bitmap, float, float, Paint))
You also found that switching to RGB_565 as an intermediate format improves performance quite a bit, presumably because it throws away the alpha. (Otherwise, I would've expected this to be somewhat slower, b/c the format has to be converted back to RGBA_8888 as it's blitted into the SurfaceView.)
It's also apparent that Android won't let you go over 60fps. This is almost certainly because lockCanvas() takes part in a triple buffering scheme that throttles the drawing rate, to prevent you from submitting frames that could never be displayed (due to your device's fixed screen refresh rate of 60Hz).
This leaves the question of why you don't get a full 60fps, but something close to it. If drawGameElements() takes the same amount of time to run each time, and it's less than 16ms, then lockCanvas() should be throttling you, and no frames should ever get dropped (60fps continuously). It seems likely that there is a burble in the thread scheduler or something, and every so often, the CannonThread does not execute quickly enough to provide the frame before the triple-buffering scheme needs to page-flip. In this event, the frame must be delayed until the next screen refresh. You might try increasing CannonThread's thread priority, removing any extra processing in drawGameElements() that doesn't absolutely need to happen on CannonThread, or closing other apps running on your device.
As mentioned, OpenGL is the standard way of getting max sprite performance for games like these, because it is able to offload many operations to hardware. You may be approaching the performance limit of a drawBitmap()-based game.

Android: Canvas not being redrawn

There are multiple similar questions like mine, but these questions didn't help me.
I'm making a game. The game thread, SurfaceView and Activity is already finished and works so far. The problem is that the canvas is not redrawn. At startup, it draws the icon on the background, but at every tick, the icon doesn't move (It should move once a second). I want to mention, that I never needed to call postInvalidate. I have a working example where I never called it, but it doesn't work in my current example (I don't want to go into it deeper, since I actually don't need to call it). I copied the current code from my working example, the concept and the way of implementation is exactly the same, but my current code doesn't refresh the canvas. When I log the drawing positions in onDraw method, I see that it's coordinates are updated every second as expected, so I can be sure it's a canvas drawing problem. I have searched for hours but I didn't find what's different to my working example (except that I'm using another Android version and I don't extend thread but implement Runnable, because it's a bad style to extend thread. Nevertheless, I also extended thread to see if there is any difference, but it doesn't help). I already tried to clean the canvas by using canvas.drawColor(Color.BLACK), but that didn't help either. I already tried to use background colors instead of a background image which changes randomly every tick, but it didn't change but stays always the same.
I figured out that the canvas at the very first call has a density of (for example) 240. After the second tick, the canvas density is always 0. I know that the density will not help me here, but maybe it's an important information for someone.
Here are the important classes....
game layout, R.layout.game
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.mydomain.mygame.base.game.GameSurface
android:id="#+id/gameSurface"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:background="#drawable/background_game" >
</com.mydomain.mygame.base.game.GameSurface>
<!-- ...more definitions -->
</LinearLayout>
GameActivity (contains layout)
public class GameActivity extends Activity
{
#SuppressWarnings("unused")
private GameSurface gameSurface;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
gameSurface = (GameSurface)findViewById(R.id.gameSurface);
//TODO on button click -> execute methods
}
}
GameSurface (log in onDraw shows updated coordinates every tick)
public class GameSurface extends SurfaceView implements SurfaceHolder.Callback
{
private GameThread thread;
protected final static int TICK_FREQUENCY = 100;// ms, stays always the same. It's a technical constant which doesn't change
private static final String TAG = GameSurface.class.getSimpleName();
public GameSurface(Context context, AttributeSet attrs)
{
super(context, attrs);
ShapeManager.INSTANCE.init(context);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
setFocusable(true); // make sure we get key events
thread = new GameThread(holder, this);
}
public void updateStatus()
{
GameProcessor.INSTANCE.updateShapes();
}
#Override
protected void onDraw(Canvas canvas)
{
for (Shape shape : GameProcessor.INSTANCE.getShapes())
{
Log.i(TAG, "getX()=" + shape.getX() + ", getY()=" + shape.getY());
canvas.drawBitmap(shape.getBitmap(), shape.getX(), shape.getY(), null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
//will never invoked since we only operate in landscape
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
// start the thread here so we don't busy-wait in run
thread.setRunning(true);
new Thread(thread).start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
Log.i(TAG, "executing surfaceDestroyed()...");
thread.setRunning(false);
}
}
GameThread
public class GameThread implements Runnable
{
private SurfaceHolder surfaceHolder;
private boolean running = false;
private GameSurface gameSurface;
private long lastTick;
public GameThread(SurfaceHolder surfaceHolder, GameSurface gameSurface)
{
this.surfaceHolder = surfaceHolder;
this.gameSurface = gameSurface;
lastTick = System.currentTimeMillis();
}
#Override
public void run()
{
Canvas canvas;
while (running)
{
canvas = null;
if (System.currentTimeMillis() > lastTick + GameSurface.TICK_FREQUENCY)
{
long timeDifference = System.currentTimeMillis() - (lastTick + GameSurface.TICK_FREQUENCY);
try
{
canvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder)
{
gameSurface.updateStatus();
gameSurface.draw(canvas);
}
}
finally
{
if (canvas != null)
{
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
lastTick = System.currentTimeMillis() - timeDifference;
}
}
}
public void setRunning(boolean running)
{
this.running = running;
}
}
Any ideas why this code doesn't update my canvas? I can't explain it. I do not post ShapeManager and GameProcessor since they don't have anything to do with the problem (and they only load and control the current states and speed of the game).
[UPDATE]
I figured out that onDraw() is invoked before the game thread has started. That means that canvas is passed to this method before thread is using it. The interesting thing is that, after the thread has started, it always uses the same canvas, but it's not the canvas reference which is passed the very first time. Although canvas = surfaceHolder.lockCanvas(null); is assigned every tick, it's always the same reference, but it's not the original reference.
In a working example of mine, the reference is always the same, since I create the bitmaps at constructor initialization time. I can't do that in my current implementation, since I have to do calculations with values I get from onMeasure() which is invoked much later than the constructor.
I tried to somehow pass the original canvas to the thread, but the reference still changes. Meanwhile I think this is the problem, but I don't know how to solve it yet.
As often happens, I found the solution on my own.
Obviously it's really a problem that it draws to different canvas instances. I'm still not sure why this happens. Didn't have the problem before. Nevertheless, I can avoid drawing to canvas by setting setWillNotDraw(true); in my GameSurface constructor and I must not invoke gameSurface.draw(canvas) in my thread, but gameSurface.postInvalidate() instead.

How to control the Android SurfaceView (canvas) redraw

I'm have a couple of images in a grid.
Looks something like this:
[http://cdn.thenextweb.com/wp-content/blogs.dir/1/files/2013/06/jolla4.jpg][1]
Instead of using android Gridview i have used a canvas, because the icons need to be animated. I read that i needed to use the canvas for games.
I am using a SurfaceView (in combination with a thread) to draw the images on the screen.
class MySurface extends SurfaceView implements SurfaceHolder.Callback {
#Override
public void onDraw(Canvas canvas) {
//here i create the bitmaps and draw the icons on the screen.
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {}
}
}
This all works fine. Mysurface is called from within my main activity with the code:
setContentView(new MySurface());
The problem is the canvas of the surfaceview is redrawn at random. I want to make it so i can decide when the canvas is redrawn and how many times. How call the onDraw function from within the main activity? I don't know how to do that.
I know in the thread is is done with
mySurface.onDraw(c);
onDraw is called with each screen refresh. Screen refresh rate depends on your device. If refresh rate is 70, then your onDraw is called 70 times a second. onDraw is running on a seperate thread so I don't recommend messing with it by trying to call it from your UI thread. Calculate delta time which is the time passed since the last time onDraw was called. And let's say you want to animate your bitmap 50 pixels per second, then say
x += deltaTime * 50;
this will make your bitmap move only 50 pixels in x axis per second.
If you just say
x += 1;
your bitmap will move screen refresh rate times per second. Since all devices have varying refresh rate, you need to use delta time in order to have screen updated in the same speed in all devices. Otherwise your animation speed would vary from device to device.

Speed up SurfaceView in Android that is only updated once every few seconds

When you use SurfaceViews in Android, especially when setting them as part of layouts (by adding the SurfaceView into the layout XML), performance is a crucial aspect.
In my case, the View is only updated once every few seconds (containing playing cards like in Poker games, with some effects and moving on touch events) and as it is part of a larger layout, I experience the problem that this SurfaceView slows down the rest of my Activity's UI, i.e. the rest of the Activity freezes for a short time and then it is updated so that a short period of time has not been shown.
public class MySurface extends SurfaceView implements SurfaceHolder.Callback {
...
}
In its onDraw() method, there are some Bitmaps drawn to a black background. Inside of that class, a thread is started that continuously calls the View's onDraw() method:
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class SurfaceThread extends Thread {
private SurfaceHolder mSurfaceHolder;
private MySurface mSurface;
private boolean mRunning = false;
public HandThread(SurfaceHolder surfaceHolder, MySurface surface) {
mSurfaceHolder = surfaceHolder;
mSurface = surface;
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
public void setRunning(boolean run) {
mRunning = run;
}
#Override
public void run() {
Canvas c;
while (mRunning) {
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (c != null) {
mSurface.onDraw(c);
}
}
}
finally { // when exception is thrown above we may not leave the surface in an inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
Unfortunately, this slows down my Activity from time to time, that means that there are freezes of ca. 0.5 seconds quite often.
Is there any possibility to speed things up? I've tried to abandon the Thread and call invalidate() in every call of onTouchEvent(), but this did not work, either.
I think there must be a way to improve performance because the SurfaceView is updated that infrequently. There are seconds where nothing happens but when the user touches the View, things are moving around until the finger lifts up.
You'll want to analyze what is really causing those pauses. Trace through a typical run and see what is actually using up the CPU time. Chances are, something is causing the UI to freeze up, you're doing a lot of work that should be moved to another thread.
You also want to avoid unnecessary work. Something you definitely want to look at is to make sure that in your background loop (including in the call to onDraw) you avoid creating new objects as much as possible. This will reduce garbage collection which can also cause hiccups. Also avoid or minimize loading bitmaps here if at all possible too.

SurfaceView onDraw skips drawing

My surfaceview onDraw method sometimes skips drawing some bitmaps on the screen and once it skips it never draws the particular bitmaps again.
My code
public class Board extends SurfaceView implements SurfaceHolder.Callback{
//varaibles declared here
public Board(){
//initaializations here
getHolder().addCallback(this);
}
//my onDraw method
protected void onDraw(Canvas canvas){
for(int u = 0;u<6; u++){
ai.get(u).draw(canvas);//each of these objects draws something on the screen.
human.get(u).draw(canvas);
}
postInvalidate();
}
public void surfaceCreated(SurfaceHolder holder){
gameLoop = new GameLoop(this);
gameLoop.start();
}
The onDraw() method is called every 100milliseconds in a thread that runs the game loop.
public class GameLoop extends Thread{
Board board;
private final int DELAY = 100;
public GameLoop(Board board){
this.board=board;
}
protected void run(){
long beforeTime,timediff,sleep;
beforeTime = System.currentTimeMillis();
while(running)
{
Canvas c = null;
try{
c=board.getHolder.lockCanvas();
synchronized(board.getHolder()){
board.onDraw(c);
}finally{
if(c!=null)
board.getHolder.unlockCanvas(c);
}
timeDiff = System.currentTimeMillis - beforeTime;
sleep = DELAY - timeDiff;
if(sleep<0)
sleep = 10;
try{
Thread.sleep(sleep);
}catch(InterruptedException e){}
beforeTime = System.currentTimeMillis();
}
}
So this skips happen like this: sometimes(in the onDraw() method) when u=0 does not draw or u = 5, it could be u = any of the possible values and the rest draws and once it skips that values it keeps skipping it everytime the onDraw method is called. I hope I have been able to make it clear enough.
I would appreciate help to resolve this problem. Thanks
As is, you are updating your main UI thread from another thread. Android docs indicate that this leads to unpredictable behavior, which you are experiencing.
Consider subclassing AsyncTask to do your threading for you. There are routines for doing background work and posting to the main UI thread from the UI thread itself.
Basically, you call execute on the task, which then calls it's doInBackground routine and when it is done, it calls onPostExecute routine with the results of the task.
These sources should be useful in helping to understand the problem and subclass AsyncTask:
http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html
http://developer.android.com/reference/android/os/AsyncTask.html

Categories

Resources