I have created a class that extends SurfaceView in order to cycle a series of ARGB bitmaps.
This mostly works, except that the state of the underlying bitmap is (usually, but not always) preserved for each new frame.
In other words, if the first frame I display is opaque, and subsequent frames are transparent, then the opaque pixels from the original frame are not cleared out when the new frames are drawn.
This behavior confuses me, because the documentation for SurfaceHolder.lockCanvas() specifically states:
"The content of the Surface is never preserved between unlockCanvas() and lockCanvas(), for this reason, every pixel within the Surface area must be written."
If I just had a solid background, then calling canvas.drawARGB(255,0,0,0) succeeds to clear it to black...but I want to have a transparent background, and I can't clear it to a transparent color, because canvas.drawARGB(0,0,0,0) has no effect.
import java.util.ArrayList;
import java.util.Random;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/*
* Accepts a sequence of Bitmap buffers and cycles through them.
*/
class AnimatedBufferView extends SurfaceView implements Runnable
{
Thread thread = null;
SurfaceHolder surfaceHolder;
volatile boolean running = false;
ArrayList<Bitmap> frames;
int curIndex = 0;
public AnimatedBufferView(ArrayList<Bitmap> _frames, Context context)
{
super(context);
surfaceHolder = getHolder();
frames = _frames;
}
public void onResume(){
running = true;
thread = new Thread(this);
thread.start();
}
public void onPause(){
boolean retry = true;
running = false;
while(retry){
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
#Override
public void run()
{
// TODO Auto-generated method stub
while(running)
{
if(surfaceHolder.getSurface().isValid())
{
Canvas canvas = surfaceHolder.lockCanvas();
//clear the buffer?
//canvas.drawARGB(255, 0, 0, 0);
//display the saved frame-buffer..
Matrix identity = new Matrix();
Bitmap frame = frames.get(curIndex);
canvas.drawBitmap(frame, identity, null);
surfaceHolder.unlockCanvasAndPost(canvas);
curIndex = (curIndex + 1) % frames.size();
try {
thread.sleep( 100 );
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Ok, discovered the problem was the default Porter-Duff drawing mode that made drawing a transparent color impossible. Just have to change mode. ie,
Canvas canvas surfaceView.lockCanvas();
canvas.drawColor(0, Mode.CLEAR);
This one confused me for a while. Basically, you're going to have to redraw the whole screen every frame. This is because when you lock the canvas, what you get is a canvas that is two frames behind, and not the last frame that was posted. This is due to the way the frames are buffered. There are only two buffers, the one that is currently being shown, and the one that got posted before the current one. When you lock the canvas you get the one that isn't currently being shown (which means you get a canvas that is a frame behind).
This is why you might hear people refer the buffer as a swap buffer, because the currently display screen gets swapped with the one that you draw everything on. This means you never really get back the latest buffer.
Hope that makes some sort of sense to you.
The google doc says the right thing...since you re locking the pixels of the surface and there are two buffers because android uses tripple or double buffering depending on the speed of the drawing calls it can draw on one frame one buffer with whatever content you have filled it in drawing calls before and on the next it could draw another buffer with another content so you might lock the surface that is being made of data from another buffer....
so better overwrite everything before you draw anything like you said for example with proter duff mode and clear color or just a balck color with canvas.drawColor(Color.BLACK);
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'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.
After searching google, various android blogs, various game dev blogs, and other tutorial sites for surfaceviews in android I'm looking to get a complete understanding of surfaceviews. I've read several books on Safaribooks about android and surface views, but they provide either too little information, or use other SDKs such as AndEngine. I was hoping to learn strictly surface view. I've played with the Lunar Lander sample project as well as other projects I've found and have created some code of a skeleton surfaceview. It consists of 3 classes just for the skeleton.
The MainActivity class:
package com.learning.svlearning;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.Window;
import android.view.WindowManager;
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Set FullScreen Mode - No title bars!!
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Screen created with pure java - Say no to xml (atleast for this demo)
setContentView(new MainGamePanel(this));
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.layout_game_window, menu);
return true;
}
}
This class is pretty straight forward. The main game activity window, requesting full screen with no title bars. How a real game should be :) This class calls our next class for the view by passing "this" (MainActivity) class's context.
MainGamePanel class:
package com.learning.svlearning;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGamePanel extends SurfaceView {
final static public String tag = "Tracer";
private GameThread gameThread; // For our thread needed to do logical processing without holding up the UI thread
private SurfaceHolder holder; // For our CallBacks.. (One of the areas I don't understand!)
public MainGamePanel(Context context) {
super(context);
Log.d(tag, "Inside MainGamePanel");
gameThread = new GameThread(this); //Create the GameThread instance for our logical processing
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
// Since we are using the SurfaceView, we need to use, at very least, the surfaceDestroyed and surfaceCreated methods.
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
Log.d(tag, "Inside SurfaceHolder Callback - surfaceDestroyed");
gameThread.setRunning(false); // Stop the Thread from running because the surface was destroyed. Can't play a game with no surface!!
while (retry) {
try {
Log.d(tag, "Inside SurfaceHolder Callback - surfaceDestroyed - while statement");
gameThread.join();
retry = false; //Loop until game thread is done, making sure the thread is taken care of.
} catch (InterruptedException e) {
// In case of catastrophic failure catch error!!!
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// let there be Surface!
Log.d(tag, "Inside SurfaceHolder Callback - surfaceCreated");
gameThread.setRunning(true); // Now we start the thread
gameThread.start(); // and begin our game's logical processing
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
// The code to resize the screen ratio when it flips from landscape to portrait and vice versa
}
});
}
#Override
protected void onDraw(Canvas canvas) {
//This is where we draw stuff.. since this is just a skeleton demo, we only draw the color Dark Grey so we can visibly see that we actually accomplished something with the surfaceview drawing
Log.d(tag, "Inside onDraw");
canvas.drawColor(Color.DKGRAY); // You can change the Color to whatever color you want, for this demo I just used Color.DKGRAY
}
}
This class mainly deals with the drawing of our resources/images with the onDraw method, handling what happens when our surface is created and destroyed (also when the screen changes, but i didnt write any code to handle it for now), and calls our GameThread class which handles the processing of our game logic.
GameThread class:
package com.learning.svlearning;
import android.graphics.Canvas;
import android.util.Log;
public class GameThread extends Thread{
final static public String tag = "Tracer";
private MainGamePanel view;
private boolean running = false;
static final long FPS = 30; // To help limit the FPS when we draw, otherwise we would kill the CPU and increase the Battery Consumption.
public GameThread(MainGamePanel view){
Log.d(tag, "inside GameThread");
this.view = view;
}
public void setRunning(boolean run){
Log.d(tag, "inside GameThread - setRunning");
running = run; // For starting / stoping our game thread
}
#Override
public void run() {
long ticksPS = 1000 / FPS; // Limit the frames per second
long startTime;
long sleepTime;
Log.d(tag, "inside GameThread - run");
while(running){ // Our Main Game Loop is right here
Canvas c = null; // build our canvas to draw on
Log.d(tag, "inside GameThread - run - while loop");
startTime = System.currentTimeMillis(); //get the current time in milliseconds - this is for helping us limit the FPS
try{
c = view.getHolder().lockCanvas(); //Before we can draw, we always have to lock the canvas, otherwise goblins will invade your app and destroy everything!
synchronized (view.getHolder()){ // we have to synchronize this because we need to make sure that the method runs when at the proper time.
view.onDraw(c); // this is where we pass our drawing information. The canvas gets passed to the onDraw method in our MainGamePanel class.
}
}finally{
if(c != null) {
view.getHolder().unlockCanvasAndPost(c); // Once we are done drawing, we unlock our canvas and post. which means we drew on the canvas, and now the devices screen will display our drawing.
}
}
sleepTime = ticksPS-(System.currentTimeMillis() - startTime); // this is where we calculace how long we need this thread to sleep (again with the FPS) we want it limited to 30 FPS as defined in our FPS variable.
try {
if (sleepTime > 0){
sleep(sleepTime); // night night, sleep to limit the fps and save our batteries!
}
else{
sleep(10); // Incase something goes crazy, we still want to sleep the thread to save the battery.
}
}catch(Exception e){
}
}
}
}
This class deals with processing game logic and sending any drawable information to our draw method in the MainGamePanel class. Such as if we had a character moving, we could send the x and y coordinate to our draw method to draw our character in a different position.
Now some parts of these classes I don't really understand fully. Like the callback, When reading the information on the googles android developers page, i'm just left more confused on what it is and why we use it. Also, if anyone has anything more to put into this, or sees anything I may have mis-understood, feel free to correct me. I enjoy working with android, though it is pretty difficult, when you start to figure things out, it is very rewarding!
The purpose of the SurfaceHolder callback is to handle those 3 events. surfaceCreated(), surfaceDestroyed(), and surfaceChanged().
If you read the code you can see that surfaceCreated() handles things that need to happen when the surface is created and so on...
Was that the answer to your question or was there more?
You should change your drawing method to be named something other than onDraw() when using a SurfaceView. That way the main thread can never accidentally invalidate() it!
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.