Reputation: 1699
I just found out something and I was wondering about how and why. I'm developing a small arcade game for Android. I decided to ignore OpenGL and use the standard SurfaceView and Drawables to do it, since it's suppose to be light (10 sprites or so). I have drawables that I load, and I use the method Draw and passing them my canvas. This how every sprite is drawn to the screen. Well it turns out that drawing 4-5 big sprites (200X400 or so) takes a long time on less-than-brand-new phone models. Long enough to make my game unplayable. We're talking about 50-60 milliseconds to draw a single frame using this method. And I really don't do anything there apart from drawing, nowhere I can cut costs. So I decided to try and use Bitmaps instead. Here, however, I need to pre-set the size, since there's no 'setBounds' method in a bitmap. No prob, I resize them to fit my current screen on load, problem solved.
OK. So I got bitmaps. I use Canvas.DrawBitmap now to draw. I bench the new draw method.. and I get a whooping 400% performance boost! Instead of 50-60ms, the entire draw loop now takes 8-12ms. What the hell?? To rule it out, I timed the setBounds too, it takes <1ms so it's not to blame. It's the actual Drawable.Draw that slows things down.
For me this is great news, since I really didn't want to learn OpenGL to make my game playable, but I can't stop wondering about it - Is it fine? are there problems with my method? Why isn't it mentioned anywhere?
Upvotes: 19
Views: 11927
Reputation: 511916
Canvas.drawBitmap
is doing a lot less work than Drawable.draw
so it is faster.
Drawable.draw
Since Drawable
is an abstract class, let's look at BitmapDrawable
:
public void draw(Canvas canvas) {
final Bitmap bitmap = mBitmapState.mBitmap;
if (bitmap == null) {
return;
}
final BitmapState state = mBitmapState;
final Paint paint = state.mPaint;
if (state.mRebuildShader) {
final Shader.TileMode tmx = state.mTileModeX;
final Shader.TileMode tmy = state.mTileModeY;
if (tmx == null && tmy == null) {
paint.setShader(null);
} else {
paint.setShader(new BitmapShader(bitmap,
tmx == null ? Shader.TileMode.CLAMP : tmx,
tmy == null ? Shader.TileMode.CLAMP : tmy));
}
state.mRebuildShader = false;
}
final int restoreAlpha;
if (state.mBaseAlpha != 1.0f) {
final Paint p = getPaint();
restoreAlpha = p.getAlpha();
p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
} else {
restoreAlpha = -1;
}
final boolean clearColorFilter;
if (mTintFilter != null && paint.getColorFilter() == null) {
paint.setColorFilter(mTintFilter);
clearColorFilter = true;
} else {
clearColorFilter = false;
}
updateDstRectAndInsetsIfDirty();
final Shader shader = paint.getShader();
final boolean needMirroring = needMirroring();
if (shader == null) {
if (needMirroring) {
canvas.save();
// Mirror the bitmap
canvas.translate(mDstRect.right - mDstRect.left, 0);
canvas.scale(-1.0f, 1.0f);
}
canvas.drawBitmap(bitmap, null, mDstRect, paint);
if (needMirroring) {
canvas.restore();
}
} else {
updateShaderMatrix(bitmap, paint, shader, needMirroring);
canvas.drawRect(mDstRect, paint);
}
if (clearColorFilter) {
paint.setColorFilter(null);
}
if (restoreAlpha >= 0) {
paint.setAlpha(restoreAlpha);
}
}
You can see that it even calls canvas.drawBitmap
internally.
Canvas.drawBitmap
Compare that to Canvas.drawBitmap
. It is much shorter.
public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
throwIfCannotDraw(bitmap);
native_drawBitmap(mNativeCanvasWrapper, bitmap, left, top,
paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity, bitmap.mDensity);
}
There are a few different drawBitmap
methods but all of them are shorter than the Drawable.draw
method. Watch out for traps like this to keep your bitmap drawing fast.
Upvotes: 2
Reputation: 26925
The SurfaceView
of your Canvas
is meant to be used when you should iterate constantly and Drawable
is not for that purpose.
Upvotes: 4