Better frame rate drawing bitmaps in a Canvas? - android

I'm shooting for an animation in a live wallpaper. Here's the code. It pretty much follows the CubeWallpaper demo:
void drawFrame() {
final SurfaceHolder holder = getSurfaceHolder();
final BufferedInputStream buf;
final Bitmap bitmap, rbitmap;
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
try {
buf = new
BufferedInputStream(assets.
open(folder+"/"
+imageList[ilen++])
);
bitmap = BitmapFactory.
decodeStream(buf);
rbitmap = Bitmap.createBitmap
(bitmap,
0,0,imageWidth,imageHeight,
transMatrix,false);
c.drawBitmap(rbitmap,
paddingX,
paddingY,
null);
if ( ilen >= imageCount ) ilen=0;
}
catch (Exception e) { e.printStackTrace(); }
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
// Reschedule the next redraw
mHandler.removeCallbacks(mDrawCube);
if (mVisible) {
mHandler.postDelayed(mDrawCube, fps);
}
}
where "transMatrix" is a scaling and rotation matrix predefined before.
It's supposed to render at 30fps but of course it doesn't do that. My initial guess is that the BufferedInputStream is one factor. I should probably cache a few of these as I go along along with the Bitmaps. But any other ideas? Will I have to use the OpenGL hack for live wallpapers?

Yes, the BufferedInputStream and BitmapFactory really shouldn't be in drawFrame() at all. You're loading and creating resources on every single frame, and that's a huge waste. Like you said, cache as many as you can beforehand, and if you find the need to load more during drawing, use a separate thread to do it so it doesn't slow the drawing.

I had the same problem: slow canvas rendering in context of live wallpapers.
I agree with others saying that you shouldn't do any cpu/io heavy while rendering e.g. loading images especially on the UI thread.
However there is one more thing you should note. You request a redraw (mHandler.postDelayed(...)) AFTER the frame was rendered. If you desire a 30 fps and thus you request a redraw in (1000 / 30) 33ms then it will NOT result in 30 frames per sec. Why?
Let's assume it takes 28ms to render all your stuff to the canvas. After it's done you request a redraw after 33 millis. That is, the period between redraws is 61 ms which equals with 16 frames per sec.
You have two options to solve it:
1) Put the mHandler.postDelayed(...) code to the beginning of the drawFrame(...) method. This seems OK but it has some disadvantages: If your actual FPS is very close to the maximal possible FPS on an actual device - with other words the UI thread is busy all the time with you canvas rendering - then there won't be time for the UI thread to do other stuff. It doesn't necesseraly mean that your LWP or the home screen will lag but you (your LWP) might start missing some touch events (as my LWP did).
2) The better solution is to start a separate thread when the surface is created and pass it the reference to the SurfaceHolder. Do the rendering in this separate thread. The render method in this thread would look like this:
private static final int DESIRED_FPS = 25;
private static final int DESIRED_PERIOD_BETWEEN_FRAMES_MS = (int) (1000.0 / DESIRED_FPS + 0.5);
#Override
public void run() {
while (mRunning) {
long beforeRenderMs = SystemClock.currentThreadTimeMillis();
// do actual canvas rendering
long afterRenderMs = SystemClock.currentThreadTimeMillis();
long renderLengthMs = afterRenderMs - beforeRenderMs;
sleep(Math.max(DESIRED_PERIOD_BETWEEN_FRAMES_MS - renderLengthMs, 0));
}
}

Related

Android game stutters; gc under control and frame dropping in place of Thread.sleep

I'm facing the quite famous issue (for newbies like me) of game stuttering. It happens almost continuosly and I can't find the root cause.
I already found issues related to object creation and gc activity thanks to memory monitor of Android Studio and now they are solved.
Thanks to some answers found here on the site, I substituted the approach of Thread.sleep to control the frame rate with a frame dropping-like logic to avoid unresponsiveness due to os schedule.
Moreover, I noticed that switching to "Performance" on my Xiaomi MI 4, so with a stable and high processor frequency, it runs smoothly; this let me think of "unexpected" picks on the processor load. Sadly, lowering the target frame rate to 30 doens't change anything.
I would like to begin showing the main loop that includes the above mentioned logic of frame-dropping:
private static final float TARGET_FPS=60;
private final SurfaceHolder mSurfaceHolder;
private Paint mBackgroundPaint;
boolean mIsOnRun;
/**
* This is the main nucleus of our program.
* From here will be called all the method that are associated with the display in GameEngine object
* */
#Override
public void run()
{
long currTime, lastFrameTime=0;
float nFrameTot=1, nUpdateTot=1;
float totalRenderTime=0.002f, totalUpdateTime=0.002f;
float estimatedFrameRenderTime=0.002f, estimatedUpdateTime;
//Looping until the boolean is false
while (mIsOnRun)
{
currTime=System.nanoTime();
//Updates the game objects business logic
AppConstants.getEngine().update();
if ((nUpdateTot + 1) > Float.MAX_VALUE || (totalUpdateTime + 0.002f) > Float.MAX_VALUE) {
nUpdateTot=1;
totalUpdateTime=0.002f;
}
nUpdateTot++;
totalUpdateTime=totalUpdateTime + ((System.nanoTime() - currTime) / 1000000000f);
estimatedUpdateTime = totalUpdateTime / nUpdateTot;
currTime=System.nanoTime();
if ((currTime - lastFrameTime)/1000000000f > (1/TARGET_FPS)-estimatedFrameRenderTime-estimatedUpdateTime) {
lastFrameTime=currTime;
//locking the canvas
Canvas canvas = mSurfaceHolder.lockCanvas(null);
if (canvas != null)
{
//Clears the screen with black paint and draws object on the canvas
synchronized (mSurfaceHolder)
{
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
AppConstants.getEngine().draw(canvas);
}
//unlocking the Canvas
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
nFrame++;
if ((nFrameTot + 1) > Float.MAX_VALUE || (totalRenderTime + 0.002f) > Float.MAX_VALUE) {
nFrameTot=1;
totalRenderTime=0.002f;
}
nFrameTot++;
totalRenderTime=totalRenderTime + ((System.nanoTime() - lastFrameTime) / 1000000000f);
estimatedFrameRenderTime = totalRenderTime / nFrameTot;
}
}
}
Now, speaking of what the game does, it's a card game and a color-filled background and at most 16 bitmaps at a time are drawn; the only "animations" are these bitmaps moving around.
I honestly don't know whether I might be doing something easy in a stupid way and would be happy to share more code snippets if it can help you understand more on my problem.
Thank you for any time you lose on this!

Drawing 200 textures in LibGdx based Android game

When I draw more around 100-200 textures all in the same screen, the device becomes very slow and the app crashes without any exceptions. Could you please let me know any best way to have 100 textures without compromising the performance.
I am using the TextureRegion from TextureAtlas.
MainGame
public void render(SpriteBatch sb) {
// TODO Auto-generated method stub
// System.out.println("BallPoolGame Screen - render");
batch = sb;
sb.setProjectionMatrix(camera.combined);
sb.begin();
sb.draw(BACKGROUND_BALL_POOL, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
cellManager.draw(sb);
ballManager.draw(sb);
sb.end();
}
private void setGameTextures() {
gameScreenAtlas = new TextureAtlas("data/texturetutorialpack.pack");
RED_BALL = gameScreenAtlas.findRegion("redball");
// RED_BALL.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
BLUE_BALL = gameScreenAtlas.findRegion("blueball");
// BLUE_BALL.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
GREEN_BALL = gameScreenAtlas.findRegion("greenball");
// GREEN_BALL.getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
}
CellManager
public void draw(SpriteBatch sb){
batch=sb;
showImageTexture(MODEL1,207,1);
if(showSelectedCell){
if(allPossiblePathSize>0)
setupBoardCellTexture();
showImage(CELL_SELECTED, rowCoordinate[cellRow], colCoordinate[cellCol]);
}
}
private void setupBoardCellTexture(){
for(CellGrid c : masterGrid){
if(cellTextureIndicator[c.getRow()][c.getCol()]==1){
showImage(CELL_ALL_PATH_TEXTURE,c.getRowCoordinate() ,c.getColCoordinate() );
}
}
}
private void showImage(TextureRegion tr, float rowCoordinate, float colCoordinate) {
batch.draw(tr, colCoordinate,rowCoordinate);
}
BallManager
public void draw(SpriteBatch sb) {
batch = sb;
setupBoardBallTexture();
if (moveTheBall) {
updateBallPosition();
showImage(ball.getTextureRegion(), moveRow + 6, moveCol + 6);
}
squeezeBalls.draw(sb);
}
You are missing some essential data about your app to answer than question:
How big is one texture on average (Size: widthxheight)
On which device is this error occuring (some devices might have less fillrate than others)
What texture filter does the TextureAtlas use (LINEAR, NEAREST, ...)
I guess that you are trying to draw many textures event if they are out of sight. If that is the case you have to implement a check if the cell is visible to the camera.
Another guess would be that you are trying to draw too many elements with the LINEAR TextureFilter. When using linear as a texture filter the gpu needs to sample way more points then with nearest (i think it was 4 times the samples; so in theory your gpu draws 400-800 textures; depending on images size that are too much for mobile gpu fillrates)
Try to describe more circumstances then i can give probably more insight in your problem.

Canvas seems to draw on 2 different views

I am pretty new to Android development and I am trying to make a game in which my character moves on tiles across the screen. Since each tile is a bitmap of its own I redraw it after the character has moved on it.
Now for some reason every time I draw the character sprite the entire view sort of flickers.
It seems like on every even draw the background I created is visible and on every odd draw the background is black. Also, the character sprite leaves a trail but only half of it is visible, depending on whether it's an even or an odd draw. My guess is that for some reason there are two views or something on which the canvas is drawing.
I would've uploaded images but I can't :(
Does anyone have a clue what I'm doing wrong? Thanks a lot for any kind of help.
Here is the method moving the character:
// Moves sprite on screen
private void MoveSprite()
{
// Run as long as the sprite's location didn't reach its destination
while (m_gameView.m_playerControl.m_MoveDestination.x != m_gameView.m_playerControl.m_CharSprite.m_SpriteLocationOnMatrix.x ||
m_gameView.m_playerControl.m_MoveDestination.y != m_gameView.m_playerControl.m_CharSprite.m_SpriteLocationOnMatrix.y)
{
// Draw sprite with FPS control
Canvas c = null;
startTime = System.currentTimeMillis();
try
{
c = m_gameView.getHolder().lockCanvas();
synchronized (m_gameView.getHolder())
{
// Draws player and refreshes the tiles it was previously on
m_gameView.m_playerControl.onDraw(c);
}
}
finally
{
if (c != null)
{
m_gameView.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try
{
if (sleepTime > 0)
Thread.sleep(sleepTime);
else
Thread.sleep(10);
}
catch (Exception e)
{
}
}
// After drawing char movement, changing flag to false
m_fIsCharMoved = false;
Check out screen width and height based drawing.
you should draw inside the screen.
I think u r drawing the characters out of the screen width or height

Flickering when drawing bitmaps with canvas in Android

I'm trying to do a menu based on bitmaps. The menu itself should be movable through screentouch move events, basically I want to drag the buttons around on the view. The button also includes collision detection, so whenever they touch they bounce from each other.
But I have some problems when it comes to drawing my bitmaps. Currently I'm using a rectangle to scale my bitmap to fit the window of my device. Want i want and can not get currently is for smoother movements of my bitmaps without flickering. Is the only option to move to open gl? Or have I missed something big in my code?
This is in my surfaceview for drawing each button, where MenuButton is the class that holds the bitmap and updates its position according to a touch and drag move.
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
for(MenuButton menuButton : menuButtonSprites) {
menuButton.onDraw(canvas);
}
}
I want the bitmaps to scale to each device's width and for that i use a rectangle for the bitmap to fit in.
public MenuButton(MenuView v, Bitmap bmp, int yPosition){
this.menuView = v;
this.menuButton = bmp;
this.xMax = v.getWidth();
this.yPosistion = yPosition;
menuButtonRectangle = new Rect(xMin, this.yPosistion-yMin, xMax, this.yPosistion+yMax);
}
public void update(int y){
if(menuButtonPressed)
{
this.yPosistion = y;
menuButtonRectangle.set(xMin, yPosistion-yMin, xMax, yPosistion+yMax);
}
}
public void onDraw(Canvas canvas){
canvas.drawBitmap(menuButton, null, menuButtonRectangle, null);
}
I also have a thread that updates the draw
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
Canvas c = null;
while (running) {
startTime = System.currentTimeMillis();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
}
finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
}
catch (Exception e) {
}
}
}
I don't really know what I'm doing wrong and why i can't manage to get a smooth movements of my buttons. Is it a downside for using canvas or have I missed something really important :D?
Usually This problem occurs when there is sync problem exists while painting. This may due to the higher Frame rate or also may be the lower frame rate. These kind of issue can be fixed by Double buffering or adjusting the Frame Rate.
Double buffering means, Instead of drawing the Image directly on to the main canvas, we will be creating an empty bitmap of screen size and getting the graphics object. Drawing every thing on to the bitmap then directly drawing this bitmap to the main canvas.

How is Animation implemented in Android

I had a small question.If i want to make a man run in android one way of doing this is to get images of the man in different position and display them at different positions.But often,this does not work very well and it appears as two different images are being drawn.Is there any other way through which i can implement custom animation.(Like create a custom image and telling one of the parts of this image to move).
The way i do it is to use sprite sheets for example (Not my graphics!):
You can then use a class like this to handle your animation:
public class AnimSpriteClass {
private Bitmap mAnimation;
private int mXPos;
private int mYPos;
private Rect mSRectangle;
private int mFPS;
private int mNoOfFrames;
private int mCurrentFrame;
private long mFrameTimer;
private int mSpriteHeight;
private int mSpriteWidth;
public AnimSpriteClass() {
mSRectangle = new Rect(0,0,0,0);
mFrameTimer =0;
mCurrentFrame =0;
mXPos = 80;
mYPos = 200;
}
public void Initalise(Bitmap theBitmap, int Height, int Width, int theFPS, int theFrameCount) {
mAnimation = theBitmap;
mSpriteHeight = Height;
mSpriteWidth = Width;
mSRectangle.top = 0;
mSRectangle.bottom = mSpriteHeight;
mSRectangle.left = 0;
mSRectangle.right = mSpriteWidth;
mFPS = 1000 /theFPS;
mNoOfFrames = theFrameCount;
}
public void Update(long GameTime) {
if(GameTime > mFrameTimer + mFPS ) {
mFrameTimer = GameTime;
mCurrentFrame +=1;
if(mCurrentFrame >= mNoOfFrames) {
mCurrentFrame = 0;
}
}
mSRectangle.left = mCurrentFrame * mSpriteWidth;
mSRectangle.right = mSRectangle.left + mSpriteWidth;
}
public void draw(Canvas canvas) {
Rect dest = new Rect(getXPos(), getYPos(), getXPos() + mSpriteWidth,
getYPos() + mSpriteHeight);
canvas.drawBitmap(mAnimation, mSRectangle, dest, null);
}
mAnimation - This is will hold the actual bitmap containing the animation.
mXPos/mYPos - These hold the X and Y screen coordinates for where we want the sprite to be on the screen. These refer to the top left hand corner of the image.
mSRectangle - This is the source rectangle variable and controls which part of the image we are rendering for each frame.
mFPS - This is the number of frames we wish to show per second. 15-20 FPS is enough to fool the human eye into thinking that a still image is moving. However on a mobile platform it’s unlikely you will have enough memory 3 – 10 FPS which is fine for most needs.
mNoOfFrames -This is simply the number of frames in the sprite sheet we are animating.
mCurrentFrame - We need to keep track of the current frame we are rendering so we can move to the next one in order.~
mFrameTimer - This controls how long between frames.
mSpriteHeight/mSpriteWidth -These contain the height and width of an Individual Frame not the entire bitmap and are used to calculate the size of the source rectangle.
Now in order to use this class you have to add a few things to your graphics thread. First declare a new variable of your class and then it can be initialised in the constructor as below.
Animation = new OurAnimatedSpriteClass();
Animation.Initalise(Bitmap.decodeResource(res, R.drawable.stick_man), 62, 39, 20, 20);
In order to pass the value of the bitmap you first have to use the Bitmap Factory class to decode the resource. It decodes a bitmap from your resources folder and allows it to be passed as a variable. The rest of the values depend on your bitmap image.
In order to be able to time the frames correctly you first need to add a Game timer to the game code. You do this by first adding a variable to store the time as show below.
private long mTimer;
We now need this timer to be updated with the correct time every frame so we need to add a line to the run function to do this.
public void run() {
while (mRun) {
Canvas c = null;
mTimer = System.currentTimeMillis(); /////This line updates timer
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
Animation.update(mTimer);
doDraw(c);
}....
then you just have to add Animation.draw(canvas); your Draw function and the animation will draw the current frame in the right place.
When you describe : " one way of doing this is to get images of the man in different position and display them at different positions", this is indeed not only a programming technique to render animation but a general principle that is applied in every form of animation : it applies to making movies, making comics, computer gaming, etc, etc.
Our eyes see at the frequency of 24 images per second. Above 12 frames per second, your brain gets the feeling of real, fluid, movement.
So, yes, this is the way, if you got the feeling movement is not fuild, then you have to increase frame rate. But that works.
Moving only one part of an image is not appropriate for a small sprite representing a man running. Nevertheless, keep this idea in mind for later, when you will be more at ease with animation programming, you will see that this applies to bigger areas that are not entirely drawn at every frame in order to decresase the number of computations needed to "make a frame". Some parts of a whole screen are not "recomputed" every time, this technique is called double buffer and you should soon be introduced to it when making games.
But for now, you should start by making your man run, replacing quickly one picture by another. If movement is not fuild either increase frame rate (optimize your program) or choose images that are closer to each other.
Regards,
Stéphane

Categories

Resources