I am working on a live wallpaper with a scrolling background. I have two bitmap objects which I alternate between in order to keep the previously drawn pixels for the next frame. I draw a new line at the top of the canvas, then call drawBitmap to copy the rest of the pixels onto the canvas.
I am using a Runnable object to do the heavy lifting. It does all copying and calculations required and then locks the canvas, enters a synchronous block on the holder, and makes a single call to Canvas.drawBitmap(bitmap,rect,rect,paint). Occasionally there will be a white flash on the screen, which seems to correlate with high CPU activity. In using traceview, I found that the drawBitmap operation, specifically Canvas.native_drawBitmap(), is taking much longer than normal. Typically it completes in 2-4msec, but when I see a white flash, it can take anywhere from 10 to 100 msec.
private void draw() {
SurfaceHolder holder = getSurfaceHolder();
Canvas canvas = null;
prepareFrame();
try {
canvas = holder.lockCanvas();
synchronized (holder) {
if (canvas != null) {
drawFrame(canvas);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null)
holder.unlockCanvasAndPost(canvas);
}
afterDrawFrame();
handler.removeCallbacks(drawRunner);
if (visible) {
handler.post(drawRunner);
}
}
The draw() function is called in the run() of the Runnable.
private void prepareFrame() {
num++;
if (num%2 == 0) {
mainBmp = mainBmp1;
mainCan.setBitmap(mainBmp1);
mainCan.drawBitmap(mainBmp2, source, destination, null);
} else {
mainBmp = mainBmp2;
mainCan.setBitmap(mainBmp2);
mainCan.drawBitmap(mainBmp1, source, destination, null);
}
}
The prepareFrame() function is how I keep hold of the previous pixels I've drawn. The Rect called source is one row short of full screen sized at the bottom, where as destination is one row short at the top. The drawBitmap() calls in prepareFrame() are never longer than 2-4msec.
private void drawFrame(Canvas can) {
can.drawBitmap(mainBmp, source, destination,null);
}
This single operation is done on the canvas while holding the lock.
private void afterDrawFrame() {
ca.calcNextRow();
mainBmp.setPixels(ca.getRow(), 0, canWidth, 0, 0, canWidth, 1);
}
Then the next new row of pixels is drawn onto one of my bitmaps in memory.
I have tried using the various signatures of drawBitmap() but only found them slower on average and still resulting in the anomalous white flashes.
My overall speed is great. Without the intermittent flashes, it works really well. Does anyone have suggestions on how to eliminate the flashes?
It's kind of hard to know exactly what's going on here because you're not including the definition or use of some central variables like "mainCan" or "ca". A more complete source reference would be great.
But...
What's probably happening is that since drawFrame(canvas) is synchronized on holder, but
handler.post(drawRunner);
is not, there will be occurences where you are trying to draw mainBmp to the system canvas at the same time as you are writing to it in prepareFrame().
The best solution to this problem would probably be some kind of double buffering, where you do something like
1) Write to a temporary bitmap
2) Change the ref of that bitmap to the double buffer i.e. mainBmp = tempBitmap;
The main objective is to never do long writes to the variables you are using for system canvas rendering, just change the object reference.
Hope this helps.
Related
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.
I am experimenting with SurfaceView. My requirement is to simply render a node (simple drawable) first. Then, render more nodes at a later point in time.
The snippets of my thread's run method & my doDraw method are below. I am just trying to render 2 different drawables in subsequent passes while retaining both. The problem is it wipes away whatever gets written in 1st pass (see comment in code). How to retain the previously drawn object?
public void run() {
Canvas canvas;
while (_running) {
canvas = null;
try {
canvas = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
doDraw(canvas, isUpdate);
}
} finally {
if (canvas != null) {
_surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
public void doDraw(Canvas canvas, boolean update){
if(update){
//goes here 2nd pass & wipes away things done in 1st pass
//I want to retain whatever was drawn in 1st pass
Bitmap thumb = BitmapFactory.decodeResource(getResources(),R.drawable.icon);
//canvas.drawColor(Color.RED);
canvas.drawBitmap(thumb, 0, 0, null);
} else{
//goes here 1st pass
Bitmap thumb = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
//canvas.drawColor(Color.BLACK);
canvas.drawBitmap(thumb, 300, 300, null);
this.isUpdate = true;
}
}
UPDATE 1:
Still does not seem to work. I changed the run code to this passing a non:
public void run() {
Canvas canvas;
while (_running) {
canvas = null;
try {
Rect dty = null;
if(isUpdate == true){
//--> In 2nd pass, I was hoping that only these co-ordinates will be updated
dty = new Rect(0,0,100,100);
canvas = _surfaceHolder.lockCanvas(dty);
}else{
canvas = _surfaceHolder.lockCanvas(null);
}
synchronized (_surfaceHolder) {
doDraw(canvas, isUpdate);
}
} finally {
if (canvas != null) {
_surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
Later I tried passing 0,0,1,1 to dirty rectangle. Could not get it to work yet...
The SurfaceView is double- or triple-buffered. The previous contents are "preserved" in the sense that the system doesn't go out of its way to clear older buffers, but you can't rely on that behavior.
If you specify a dirty rect, the framework will render whatever you ask, then copy the non-dirty region from the previous buffer on top of the new buffer.
The system is allowed to expand the dirty rectangle -- the Rect you pass to lockCanvas() may be updated. You're required to redraw every pixel inside it.
For a (somewhat eye-searing) example of this in action, see "Simple Canvas in TextureView" in Grafika.
For more details on how the system works, see this article.
I found this interesting note in the Android documentation:
The content of the Surface is never preserved between unlockCanvas()
and lockCanvas(), for this reason, every pixel within the Surface area
must be written. The only exception to this rule is when a dirty
rectangle is specified, in which case, non-dirty pixels will be
preserved.
So to do what you are trying to do, it looks like you need to provide a non-null dirty rectangle in your lockCanvas call. Also, this will only work as long as none of your node pixels intersect.
I use a SurfaceView to create a marquee feature, but sometimes after the drawing thread in SurfaceView starts running, the UI thread is blocked, my touch on the BACK or MENU button is not dispatched, and an ANR is produced. This happens now and then.
I guess it is because the drawing in SurfaceView starts too early(of course I ensure the drawing happens between surfaceCreated() and surfaceDestroyed()), I guess the drawing thread should starts after something fully initialized, maybe something related to Activity?
When I add Thread.sleep(100) before the code that actually uses Canvas returned by SurfaceHolder.lockCanvas() to start drawing, the problem almost disappears, it still happens, but the frequency is low. If I make the drawing thread sleep longer enough before actually drawing something on the canvas, the problem never occurs again.
It looks like I should start drawing after something is fully initialized, but I have no idea about what that something is.
This SurfaceView is used as a normal View that is put in the layout file, the following is the code used to draw on the surface.
public void run() {
try {
// this is extremely crucial, without this line, surfaceView.lockCanvas() may
// produce ANR from now and then. Looks like the reason is that we can not start
// drawing on the surface too early
Thread.sleep(100);
} catch (Exception e) {}
while (running) {
Canvas canvas = null;
try{
long ts = System.currentTimeMillis();
canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
synchronized (surfaceHolder) {
doDraw(canvas);
}
ts = System.currentTimeMillis() - ts;
if (ts < delayInterval) {
Thread.sleep(delayInterval - ts);
}
}
} catch (InterruptedException e) {
// do nothing
} finally {
if (canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
You shouldn't call Thread.sleep between SurfaceHolder.lockCanvas and SurfaceHolder.unlockCanvasAndPost, it should be called only after canvas is unlocked.
In your code example canvas remains locked almost all the time and cause starvation. There is only a little window for SurfaceFlinger to take a canvas a process it. So sometimes this code could fail and that's why ANR errors were sporadic.
I have a live wallpaper which I created using the android canvas. Upon testing, I felt it necessary to harness the power of OpenGL, and so am experimenting with AndEngine. I am wondering how I can achieve the following.
I have a background image that fills the whole screen, with many smaller bitmaps floating over the top (not animated movements)
So far I have this for the background image:
#Override
public void onLoadResources()
{
mtexture = new Texture(1024, 1024, TextureOptions.BILINEAR);
TextureRegionFactory.setAssetBasePath("gfx/");
mtextureRegion = TextureRegionFactory.createFromResource(mtexture , this, R.drawable.background1, 0, 0);
this.mEngine.getTextureManager().loadTexture(this.mtexture );
}
#Override
public Scene onLoadScene(){
final Scene scene = new Scene(1);
Sprite background = new Sprite(0, 0, CAMERA_WIDTH*2, CAMERA_HEIGHT, mtextureRegion )
SpriteBackground sb = new SpriteBackground(background);
scene.setBackground(sb);
scene.setBackgroundEnabled(true);
return scene;
}
This works fine for the background, but I require moving sprites.
In my canvas code, I do the following to update the position & physics of the moving objects and draw the canvas every few ms
private final Runnable drawScreen = new Runnable() {
public void run() {
drawFrame();
}};
-
void drawFrame() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
//draw
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
mHandler.removeCallbacks(drawScreen);
mHandler.postDelayed(drawScreen, 10);
}
What is the appropriate way to do this on AndEngine? do I use the same code and substitute openGL calls?
I had a look at GLEngine, am I supposed to send Runnables to the GlThread queue?
EDIT - I think I found the answer...an UpdateHandler. But how can I inform the handler of an update (i.e. to call the onUpdate method). If I make a timed Handler, what happens if I call too often, does a queue of requests build up?
First of all, don't use the constructor Scene(int), it's deprecated. Use Scene() instead.
Correct, you should use an update handler.
You can create an UpdateHandler, and then register it to your scene:
scene.registerUpdateHandler(mUpdateHandler);
This way, the code in mUpdateHandler.onUpdate method is executed each time the scene updates (Each frame.). You don't call it manually. If you want to stop it, call:
scene.unregisterUpdateHandler(mUpdateHandler);
So, the onUpdate method is always executed in the UpdateThread, so you can be sure you can do any change to entities you want there. So you can move around and sprite you want, etc...
By the way, why is the background's width CAMERA_WIDTH*2? It means that only the left half of your sprite is shown. If you don't plan moving the camera, then the right half won't ever show.
I created a custom view from scratch. Extended View and overrided onDraw().
When comes down in animating the view i generate a custom animation using offsets.
eg.
while(!isOnTop){
mOffset++;
//draw the component a a it higher using the offset
if(position == 0)
isOnTop==true;
invalidate();
}
The thinking is that my frames come from invalidate it self. The problem is that invalidation of this view can come just by scrolling a listview at the same screen.
This "shared invalidation()" causes lag to my animation.So is there a way out of that lag?
Do you have any other suggestion of performing animations in that shared enviroment?
Creating an animation using a seperate thread that calculates the offset also needs forced invalidation() calls to display the animation (correct me if i'm wrong).
Is the only solution to perform the animation in eg 10 invalidation requests with a larger step? It will ease the lag out but i think i can use a different approach on that.
"What is best" of course depends greatly on exactly what you are trying to do. You haven't said what you are trying to accomplish, so we can only guess at what may be best for you.
Here are some simple things:
If you want to animate bitmap frames, use AnimationDrawable: http://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html
If you want to animate the movement of views within your hierarchy, use the view animation framework: http://developer.android.com/guide/topics/graphics/view-animation.html
The new more general animation framework can do a lot more stuff an is often easier to use: http://developer.android.com/guide/topics/graphics/animation.html. This is natively available in Android 3.0+ but can also be used in Android API level 7 with the support v7 library.
If you want to write a custom widget that is an integrated part of its view hierarchy and manually does its own animation drawing, you can use a Handler to time the updates (usually you'll want 60fps or 20ms between each invalidate()) and then in your onDraw() method draw your view's state based on SystemClock.uptimeMillis() as a delta from when the animation started.
Here's a simple repeated invalidate using Handler:
long mAnimStartTime;
Handler mHandler = new Handler();
Runnable mTick = new Runnable() {
public void run() {
invalidate();
mHandler.postDelayed(this, 20); // 20ms == 60fps
}
}
void startAnimation() {
mAnimStartTime = SystemClock.uptimeMillis();
mHandler.removeCallbacks(mTick);
mHandler.post(mTick);
}
void stopAnimation() {
mHandler.removeCallbacks(mTick);
}
Since this question has some interest I will reply.
The best way to to that is to have a separate canvas thread. A "separate" canvas can only be achieved with a SurfaceView. LunarLanding is an excelent example of that use. Each frame is calculated separately than the main view sharing only CPU time, not drawing time. Therefore is faster, even with the combination of for e.g a regular view at the top and an animating view at the bottom.
But you have to set an interval if you are in that shared environment. That interval is used for the FPS cap. If you don't set FPS cap then the CPU will running wild managing to get good animation to the SurfaceView if it was alone. Capping it at 60fps or even less will do the trick to draw all views efficiently with no CPU overload.
So see the drawing thread of the Lunar Landing from the API demos and set a FPS cap.
private long timeNow;
private long timeDelta;
private long timePrevFrame;
private void capFps(int fps) {
timeNow = System.currentTimeMillis();
timeDelta = timeNow - timePrevFrame;
try {
//ps you can always set 16 instead of 1000/fps for 60FPS to avoid the calculation every time
Thread.sleep((1000 / fps) - timeDelta);
} catch (InterruptedException e) {
}
timePrevFrame = System.currentTimeMillis();
}
and then the drawing thread will look something like this:
#Override
public void run() {
Canvas c;
while (run) {
c = null;
sleepFps(60, false);
try {
synchronized (surfaceHolder) {
c = surfaceHolder.lockCanvas(null);
widgetView.doDraw(c);
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}