mirror of
https://github.com/jamessanford/kegs.git
synced 2025-04-14 02:37:02 +00:00
Move bitmap drawing to separate thread.\n\nBitmap drawing is now triggered by a Handler on the native thread. The bitmap thread runs a Looper that receives an empty message.\n\nAlso, screen size and scaling logic is moving into BitmapSize class.
This commit is contained in:
parent
2c402d03ff
commit
63b7bf1c6d
116
src/com/froop/app/kegs/BitmapSize.java
Normal file
116
src/com/froop/app/kegs/BitmapSize.java
Normal file
@ -0,0 +1,116 @@
|
||||
package com.froop.app.kegs;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
|
||||
class BitmapSize {
|
||||
static class Const {
|
||||
public static final int A2Width = 640 + 32 + 32; // kegs defcomm.h
|
||||
public static final int A2Height = 400 + 32 + 30; // kegs defcomm.h
|
||||
}
|
||||
|
||||
private int mWidth = 0;
|
||||
private int mHeight = 0;
|
||||
|
||||
private boolean mScaled = false;
|
||||
private boolean mCropped = false;
|
||||
private float mScaleFactorX = 1.0f;
|
||||
private float mScaleFactorY = 1.0f;
|
||||
|
||||
public BitmapSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
calculateScale(width, height);
|
||||
}
|
||||
|
||||
public boolean showActionBar() {
|
||||
if (mHeight < ((400 + 64) * mScaleFactorY)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public int getViewWidth() {
|
||||
return (int)(Const.A2Width * mScaleFactorX);
|
||||
}
|
||||
|
||||
public int getViewHeight() {
|
||||
if (!doCropBorder()) {
|
||||
return (int)(Const.A2Height * mScaleFactorY);
|
||||
} else {
|
||||
return (int)((Const.A2Height - 32) * mScaleFactorY);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doCropBorder() {
|
||||
return mCropped;
|
||||
}
|
||||
|
||||
public boolean isScaled() {
|
||||
return (mScaleFactorX != 1.0f || mScaleFactorY != 1.0f);
|
||||
}
|
||||
|
||||
public float getScaleX() {
|
||||
return mScaleFactorX;
|
||||
}
|
||||
|
||||
public float getScaleY() {
|
||||
return mScaleFactorY;
|
||||
}
|
||||
|
||||
public Rect getRectSrc() {
|
||||
if (doCropBorder()) {
|
||||
return new Rect(0, 32, Const.A2Width, Const.A2Height);
|
||||
} else {
|
||||
return new Rect(0, 0, Const.A2Width, Const.A2Height);
|
||||
}
|
||||
}
|
||||
|
||||
public Rect getRectDst() {
|
||||
if (doCropBorder()) {
|
||||
return new Rect(0, 0, Const.A2Width, Const.A2Height - 32);
|
||||
} else {
|
||||
return new Rect(0, 0, Const.A2Width, Const.A2Height);
|
||||
}
|
||||
}
|
||||
|
||||
// If we can fit at least 90% of a scaled screen into the display area, do it.
|
||||
// If we hit less than 100% height, turn off system action bar and title.
|
||||
// If ((400 + 32) * scale) > height, then crop border.
|
||||
private void calculateScale(int width, int height) {
|
||||
float scaleX = 1.0f;
|
||||
float scaleY = 1.0f;
|
||||
boolean crop = false;
|
||||
|
||||
// Force integer scaling on X axis.
|
||||
scaleX = (float)Math.round((width * 0.9) / 640);
|
||||
// TODO: Fix '48' hack being used for system buttons or soft buttons.
|
||||
scaleY = Math.min(scaleX, (height - 48) / 400.0f);
|
||||
|
||||
// If Y would be compressed in a weird way, reduce the scale and use 1:1.
|
||||
if ((scaleX - scaleY) > 0.5) {
|
||||
scaleX = Math.max(1, scaleX - 1);
|
||||
scaleY = scaleX;
|
||||
}
|
||||
|
||||
// TODO: Fix '32' and '64' for software buttons and window decorations.
|
||||
if (height < ((400 + 32 + 64) * scaleY)) {
|
||||
crop = true;
|
||||
}
|
||||
|
||||
mCropped = crop;
|
||||
mScaleFactorX = scaleX;
|
||||
mScaleFactorY = scaleY;
|
||||
Log.w("kegs", "using scale " + scaleX + ":" + scaleY + " " + crop + from screen " + width + "x" + height);
|
||||
}
|
||||
|
||||
// call us when you update your screen size/configuration
|
||||
|
||||
// helper to calculate view area for KegsView:onMeasure
|
||||
|
||||
// helper to create size struct
|
||||
|
||||
// helper struct for scale factors (&isScaled), crop info, source/dest rects
|
||||
// KegsView can get this and pass it into the thread.
|
||||
}
|
96
src/com/froop/app/kegs/BitmapThread.java
Normal file
96
src/com/froop/app/kegs/BitmapThread.java
Normal file
@ -0,0 +1,96 @@
|
||||
package com.froop.app.kegs;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
class BitmapThread extends Thread {
|
||||
public Handler mHandler = new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
updateScreen();
|
||||
}
|
||||
};
|
||||
|
||||
private SurfaceHolder mSurfaceHolder;
|
||||
private Bitmap mBitmap;
|
||||
private ReentrantLock mSurfaceLock;
|
||||
private Canvas mCanvas;
|
||||
private boolean mHaveSurface = false;
|
||||
private boolean mScaled = false;
|
||||
private float mScaleFactorX = 1.0f;
|
||||
private float mScaleFactorY = 1.0f;
|
||||
private Rect mRectSrc = new Rect(0, 0, 0, 0);
|
||||
private Rect mRectDst = new Rect(0, 0, 0, 0);
|
||||
|
||||
private FpsCounter fpsCount = new FpsCounter("kegs", "thread");
|
||||
|
||||
public void setBitmap(SurfaceHolder surfaceHolder, Bitmap bitmap, ReentrantLock surfaceLock) {
|
||||
mSurfaceHolder = surfaceHolder;
|
||||
mBitmap = bitmap;
|
||||
mSurfaceLock = surfaceLock;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
Looper.loop();
|
||||
}
|
||||
|
||||
public Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
public void updateScreen() {
|
||||
mSurfaceLock.lock();
|
||||
try {
|
||||
if (!mHaveSurface) {
|
||||
return; // unlock with 'finally' clause
|
||||
}
|
||||
mCanvas = mSurfaceHolder.lockCanvas(); // Use Rect ?
|
||||
if(mCanvas != null) {
|
||||
if (!mScaled) {
|
||||
mCanvas.drawBitmap(mBitmap, mRectSrc, mRectDst, null);
|
||||
} else {
|
||||
mCanvas.save();
|
||||
mCanvas.scale(mScaleFactorX, mScaleFactorY);
|
||||
mCanvas.drawBitmap(mBitmap, mRectSrc, mRectDst, null);
|
||||
mCanvas.restore();
|
||||
}
|
||||
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
|
||||
mCanvas = null;
|
||||
}
|
||||
} finally {
|
||||
mSurfaceLock.unlock();
|
||||
fpsCount.fps();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateScreenSize(BitmapSize bitmapSize) {
|
||||
// Keep our own copy of the size data, to give atomicity and
|
||||
// possibly help performance with fewer indirections.
|
||||
mSurfaceLock.lock();
|
||||
mScaled = bitmapSize.isScaled();
|
||||
mScaleFactorX = bitmapSize.getScaleX();
|
||||
mScaleFactorY = bitmapSize.getScaleY();
|
||||
mRectSrc = new Rect(bitmapSize.getRectSrc());
|
||||
mRectDst = new Rect(bitmapSize.getRectDst());
|
||||
mSurfaceLock.unlock();
|
||||
updateScreen(); // Note: UI thread.
|
||||
}
|
||||
|
||||
public void setHaveSurface(boolean haveSurface) {
|
||||
mSurfaceLock.lock();
|
||||
mHaveSurface = haveSurface;
|
||||
mSurfaceLock.unlock();
|
||||
|
||||
if (haveSurface) {
|
||||
// Refresh the canvas when we obtain a surface.
|
||||
updateScreen(); // Note: UI thread.
|
||||
}
|
||||
}
|
||||
}
|
25
src/com/froop/app/kegs/FpsCounter.java
Normal file
25
src/com/froop/app/kegs/FpsCounter.java
Normal file
@ -0,0 +1,25 @@
|
||||
package com.froop.app.kegs;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
class FpsCounter {
|
||||
private String mName;
|
||||
private String mIdent;
|
||||
private long fpsLast = System.currentTimeMillis() + 1000;
|
||||
private int fpsCount = 0;
|
||||
|
||||
FpsCounter(String logName, String ident) {
|
||||
mName = logName;
|
||||
mIdent = "fps " + ident + " ";
|
||||
}
|
||||
|
||||
public void fps() {
|
||||
fpsCount += 1;
|
||||
long fpsNow = System.currentTimeMillis();
|
||||
if (fpsNow > fpsLast) {
|
||||
Log.w(mName, mIdent + fpsCount);
|
||||
fpsLast = fpsNow + 1000;
|
||||
fpsCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -186,6 +186,7 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setMessage(getResources().getText(R.string.rom_error));
|
||||
// TODO do getActivity().finish() on button clicks
|
||||
// TODO setCanceledOnTouchOutside(false) ? otherwise can accidentally dismiss the error.
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@ -193,46 +194,23 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
|
||||
private void setScreenSize() {
|
||||
final int width = getResources().getDisplayMetrics().widthPixels;
|
||||
final int height = getResources().getDisplayMetrics().heightPixels;
|
||||
final BitmapSize bitmapSize = new BitmapSize(width, height);
|
||||
|
||||
// If we can fit at least 90% of a scaled screen into the display area, do it.
|
||||
// If we hit less than 100% height, turn off system action bar and title.
|
||||
// If ((400 + 32) * scale) > height, then crop border.
|
||||
mKegsView.updateScreenSize(bitmapSize);
|
||||
|
||||
float scaleX = 1.0f;
|
||||
float scaleY = 1.0f;
|
||||
boolean crop = false;
|
||||
|
||||
// Force integer scaling on X axis.
|
||||
scaleX = (float)Math.round((width * 0.9) / 640);
|
||||
// TODO: Fix '48' hack being used for system buttons or soft buttons.
|
||||
scaleY = Math.min(scaleX, (height - 48) / 400.0f);
|
||||
|
||||
// If Y would be compressed in a weird way, reduce the scale and use 1:1.
|
||||
if ((scaleX - scaleY) > 0.5) {
|
||||
scaleX = Math.max(1, scaleX - 1);
|
||||
scaleY = scaleX;
|
||||
}
|
||||
|
||||
if (height < ((400 + 64) * scaleY)) {
|
||||
ActionBar actionBar = getActionBar();
|
||||
if (actionBar != null && actionBar.isShowing()) {
|
||||
actionBar.hide();
|
||||
}
|
||||
} else {
|
||||
ActionBar actionBar = getActionBar();
|
||||
final ActionBar actionBar = getActionBar();
|
||||
if (bitmapSize.showActionBar()) {
|
||||
if (actionBar != null && !actionBar.isShowing()) {
|
||||
actionBar.show();
|
||||
}
|
||||
} else {
|
||||
if (actionBar != null && actionBar.isShowing()) {
|
||||
actionBar.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix '32' and '64' for software buttons and window decorations.
|
||||
if (height < ((400 + 32 + 64) * scaleY)) {
|
||||
crop = true;
|
||||
}
|
||||
|
||||
Log.w("kegs", "using scale " + scaleX + ":" + scaleY + " " + crop);
|
||||
|
||||
mKegsView.setScale(scaleX, scaleY, crop);
|
||||
// Force another redraw of the bitmap into the canvas. Bug workaround.
|
||||
mKegsView.getThread().updateScreen();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -259,7 +237,8 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
|
||||
setContentView(R.layout.main);
|
||||
|
||||
mKegsView = (KegsView)findViewById(R.id.kegsview);
|
||||
setScreenSize(); // This causes an unnecessary requestLayout of KegsView.
|
||||
|
||||
setScreenSize(); // TODO This causes an unnecessary requestLayout of KegsView.
|
||||
|
||||
mKegsTouch = new KegsTouch(mKegsView.getEventQueue());
|
||||
final GestureDetector inputDetect = new GestureDetector(this, mKegsTouch);
|
||||
|
@ -5,7 +5,8 @@ import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
@ -14,65 +15,64 @@ import android.view.View;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
// This is the primary interface into the native KEGS thread, and also
|
||||
// where the native KEGS thread calls back into Java.
|
||||
|
||||
class KegsView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
private static final int mA2Width = 640 + 32 + 32; // kegs defcomm.h
|
||||
private static final int mA2Height = 400 + 32 + 30; // kegs defcomm.h
|
||||
// Reported area of this view, see updateScreenSize()
|
||||
private int mWidth = 0;
|
||||
private int mHeight = 0;
|
||||
|
||||
// Look also at mPauseLock.
|
||||
private boolean mHaveSurface = false;
|
||||
private boolean mPaused = false;
|
||||
private boolean mReady = false;
|
||||
|
||||
// Bitmap draw options.
|
||||
private boolean mScaled = false;
|
||||
private float mScaleFactorX = 1.0f;
|
||||
private float mScaleFactorY = 1.0f;
|
||||
private boolean mReady = false; // 'true' will begin the native thread.
|
||||
|
||||
protected ConcurrentLinkedQueue<Event.KegsEvent> mEventQueue = new ConcurrentLinkedQueue<Event.KegsEvent>();
|
||||
|
||||
private BitmapThread mBitmapThread;
|
||||
|
||||
class KegsThread extends Thread {
|
||||
private Handler mHandler;
|
||||
private Bitmap mBitmap;
|
||||
private Canvas mCanvas;
|
||||
private SurfaceHolder mSurfaceHolder;
|
||||
private Context mContext;
|
||||
private final ReentrantLock mSurfaceLock = new ReentrantLock();
|
||||
private final ReentrantLock mPauseLock = new ReentrantLock();
|
||||
private Rect mRectSrc = new Rect(0, 0, mA2Width, mA2Height);
|
||||
private Rect mRectDst = new Rect(0, 0, mA2Width, mA2Height);
|
||||
|
||||
public KegsThread(SurfaceHolder surfaceHolder, Context context) {
|
||||
mSurfaceHolder = surfaceHolder;
|
||||
mContext = context;
|
||||
|
||||
mBitmap = Bitmap.createBitmap(mA2Width, mA2Height,
|
||||
mBitmap = Bitmap.createBitmap(BitmapSize.Const.A2Width,
|
||||
BitmapSize.Const.A2Height,
|
||||
Bitmap.Config.ARGB_8888);
|
||||
mBitmap.setHasAlpha(false);
|
||||
|
||||
mBitmapThread = new BitmapThread();
|
||||
mHandler = mBitmapThread.getHandler();
|
||||
|
||||
mBitmapThread.setBitmap(surfaceHolder, mBitmap, mSurfaceLock);
|
||||
}
|
||||
|
||||
// Typically called by the native thread, but this can also be
|
||||
// called on the UI thread via setHaveSurface.
|
||||
private FpsCounter fpsCount = new FpsCounter("kegs", "native");
|
||||
|
||||
// Typically updateScreen is called by the native thread,
|
||||
// but it may also be run on the UI thread.
|
||||
//
|
||||
// We use a Handler to tell the bitmap thread to actually draw
|
||||
// on the canvas. No locking is involved, so it is possible for
|
||||
// the canvas to get a bitmap that is in the process of being updated
|
||||
// by the native thread. This should be relatively uncommon.
|
||||
//
|
||||
// If you wish to draw to the canvas in the native thread, it should
|
||||
// be safe to bypass the Handler and call mBitmapThread.updateScreen()
|
||||
// here instead.
|
||||
protected void updateScreen() {
|
||||
mSurfaceLock.lock();
|
||||
try {
|
||||
if (!mHaveSurface) {
|
||||
return; // unlock with finally
|
||||
}
|
||||
mCanvas = mSurfaceHolder.lockCanvas(); // Use Rect ?
|
||||
if(mCanvas != null) {
|
||||
mCanvas.drawARGB(255, 0, 0, 0); // TODO: Figure out why necessary.
|
||||
if (!mScaled) {
|
||||
mCanvas.drawBitmap(mBitmap, mRectSrc, mRectDst, null);
|
||||
} else {
|
||||
mCanvas.save();
|
||||
mCanvas.scale(mScaleFactorX, mScaleFactorY);
|
||||
mCanvas.drawBitmap(mBitmap, mRectSrc, mRectDst, null);
|
||||
mCanvas.restore();
|
||||
}
|
||||
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
|
||||
mCanvas = null;
|
||||
}
|
||||
} finally {
|
||||
mSurfaceLock.unlock();
|
||||
}
|
||||
// Empty the queue first in case bitmap thread is lagging behind.
|
||||
mHandler.removeMessages(0);
|
||||
mHandler.sendEmptyMessage(0);
|
||||
fpsCount.fps();
|
||||
}
|
||||
|
||||
private void checkForPause() {
|
||||
@ -83,6 +83,7 @@ class KegsView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
// See jni/android_driver.c:mainLoop()
|
||||
private native void mainLoop(Bitmap b, ConcurrentLinkedQueue q);
|
||||
|
||||
@Override
|
||||
@ -111,12 +112,13 @@ class KegsView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
if (!mReady) {
|
||||
return; // bail out, we haven't started doing anything yet
|
||||
}
|
||||
thread.updateScreen();
|
||||
updateScreen();
|
||||
if (mPaused) {
|
||||
mPaused = false;
|
||||
mPauseLock.unlock();
|
||||
} else if (!thread.isAlive()) {
|
||||
thread.start();
|
||||
mBitmapThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,40 +126,8 @@ class KegsView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
public boolean nowPaused() {
|
||||
return mPauseLock.hasQueuedThreads();
|
||||
}
|
||||
|
||||
public void setScale(float scaleFactorX, float scaleFactorY, boolean cropBorder) {
|
||||
mSurfaceLock.lock();
|
||||
if (scaleFactorX == 1.0f && scaleFactorY == 1.0f) {
|
||||
mScaled = false;
|
||||
} else {
|
||||
mScaled = true;
|
||||
}
|
||||
mScaleFactorX = scaleFactorX;
|
||||
mScaleFactorY = scaleFactorY;
|
||||
if (cropBorder) {
|
||||
mRectSrc = new Rect(0, 32, mA2Width, mA2Height);
|
||||
mRectDst = new Rect(0, 0, mA2Width, mA2Height - 32);
|
||||
} else {
|
||||
mRectSrc = new Rect(0, 0, mA2Width, mA2Height);
|
||||
mRectDst = new Rect(0, 0, mA2Width, mA2Height);
|
||||
}
|
||||
mSurfaceLock.unlock();
|
||||
updateScreen();
|
||||
}
|
||||
|
||||
public void setHaveSurface(boolean haveSurface) {
|
||||
mSurfaceLock.lock();
|
||||
mHaveSurface = haveSurface;
|
||||
mSurfaceLock.unlock();
|
||||
|
||||
if (haveSurface) {
|
||||
// Refresh the canvas when we obtain a surface.
|
||||
updateScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private KegsThread thread;
|
||||
|
||||
public KegsView(Context context, AttributeSet attrs) {
|
||||
@ -188,20 +158,9 @@ class KegsView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
return mEventQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
// TODO: check to see if this is necessary, for example during alarms or phone calls.
|
||||
// if (!hasWindowFocus) {
|
||||
// thread.onPause();
|
||||
// } else {
|
||||
// thread.onResume();
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setMeasuredDimension((int)(mA2Width * mScaleFactorX),
|
||||
(int)(mA2Height * mScaleFactorY));
|
||||
setMeasuredDimension(mWidth, mHeight);
|
||||
}
|
||||
|
||||
public native String stringFromJNI();
|
||||
@ -216,9 +175,11 @@ class KegsView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
public void setScale(float scaleFactorX, float scaleFactorY, boolean cropBorder) {
|
||||
thread.setScale(scaleFactorX, scaleFactorY, cropBorder);
|
||||
public void updateScreenSize(BitmapSize bitmapSize) {
|
||||
mWidth = bitmapSize.getViewWidth();
|
||||
mHeight = bitmapSize.getViewHeight();
|
||||
requestLayout();
|
||||
mBitmapThread.updateScreenSize(bitmapSize);
|
||||
}
|
||||
|
||||
public void surfaceChanged(SurfaceHolder holder,
|
||||
@ -228,10 +189,10 @@ class KegsView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
// The surface callbacks are occasionally called in between pause and resume.
|
||||
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
thread.setHaveSurface(true);
|
||||
mBitmapThread.setHaveSurface(true);
|
||||
}
|
||||
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
thread.setHaveSurface(false);
|
||||
mBitmapThread.setHaveSurface(false);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user