Run long task on glThread without blocking UI thread on Android - android

I have to run through a lot of initialisation before I can render anything in my GLSurfaceView
Theses must be done on the OpenGL thread.
However this hangs my main thread for the duration of the initialisation.
Here is my code:
#Override
protected void onStart() {
super.onStart();
FrameLayout renderingLayout = (FrameLayout) findViewById(R.id.movie_rendering_layout);
if (renderingLayout != null && mGLView == null) {
mGLView = new MyGLSurfaceView(getApplicationContext());
/** [..] **/
renderingLayout.addView(mGLView, params);
}
}
/*--------------- OPENGL RELATED ---------------*/
protected class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer mRenderer;
public MyGLSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 1.0 context
setEGLContextClientVersion(1);
mRenderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
}
protected class MyGLRenderer implements GLSurfaceView.Renderer {
private int mWidth, mHeight = 0;
private boolean isFinished = false;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
init(mMovieIndex, AssetsUtils.getBinPath(getApplicationContext())); // <----- THIS takes long time
}
public void onDrawFrame(GL10 pGL10) {
GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT);
/* [...] */
}

I found a solution:
The issue is that you must not block in onDrawFrame or onSurfaceCreated, since they are called synchronously by the main thread.
To disable the calls, I used in my surface constructor:
setRenderMode(RENDERMODE_WHEN_DIRTY);
This way, the calls to onDrawFrame will stop once the view is settled.
I performed the initialisation from
public void onWindowFocusChanged(boolean hasFocus)
Be careful it can be called twice. If anyone has a better suggestion I'd gladly listen to it (taken from How to make a callback after the view is completely rendered?)
I also overrided
#Override
public boolean isDirty()
return false;
}
And don't forget to use queueEvent to run code on the GLThread

The usual answer is to create two EGL contexts in the same share group, each bound to a separate thread.
The main thread in the rendering loop does any rendering to screen, which during resource loading is normally something "boring" - i.e. a loading graphic, a progress bar, etc.
The second thread loads any bulk resources in the background, e.g. loading texture files, model meshes, etc and uploading them.
Once loading has completed the main thread can use all of the data resources loaded by the secondary async loading thread.

Related

While loop causes UI to go blank

I'm writing a simple Whack a Mole clone, and I've got my UI elements declared in a GridLayout in a layout.xml, then assigned to ImageView variables in an array programmatically. I've got a startGame() method that simply takes a random int, pulls it from the array and causes it to go visible for a second, then repeats. For some reason, when I put this code in a while() loop, it causes my UI to go blank as soon as it's launched.
I know it's the while() loop because I tried taking the code out of the while() loop, and it ran correctly (once), but turns everything white when placed in a while loop.
Here's the method causing the problem:
public void startGame() {
gameStarted = true;
while(gameStarted) {
randomInt = rand.nextInt(11);
mole[randomInt].setVisibility(View.VISIBLE);
handler.postDelayed(new Runnable() {
#Override
public void run() {
mole[randomInt].setVisibility(View.INVISIBLE);
}
}, 5000);
}
}
All the other relevant code is in onCreate, it's otherwise just a skeleton Activity subclass.
public class WAM_Activity extends Activity {
private ImageView[] mole = new ImageView[11];
private int[] moleId = {R.id.mole1, R.id.mole3, R.id.mole4, R.id.mole5, R.id.mole6, R.id.mole7, R.id.mole8, R.id.mole9, R.id.mole10, R.id.mole11, R.id.mole12};
private boolean gameStarted;
private int randomInt = 0;
private Random rand = new Random();
Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wam_view_layout);
for (int i = 0; i < 11; i++) {
mole[i] = (ImageView) findViewById(moleId[i]);
mole[i].setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//do stuff eventually
}
});
}
gameStarted = true;
startGame();
}
Any idea why this isn't working? I've been staring at it for hours and I'm quite stumped.
Android doesn't work that way, when onCreate is called, it need to be finished in order for the app to keep responding, I'm surprised you are not getting any "App not respopnding" error.
If you want to create a "game loop" you can simply by creating a new Thread and putting the while in there.
Activity's lifecycle must be executed without blocking them for the app to operate correctly, for more info check here.
Do you know about threads? if you want i can post an example of how to do that with threads but it might be long and if you don't know what a Thread is it will be too confusing for you.
Edit: Ok I'll make an example of a Thread
When I create my games I usually have only one Activity that the only thing it does is creating a custom SurfaceView and nothing else.
public class GameActivity extends Activity
{
//This is a custom class that extends SurfaceView - I will write it below
private GameSurface game;
#Override
protected void onCreate(Bundle bundle)
{
super.onCreate(bundle);
//Create a new instance of the game
game = new GameSurface(this);
//Set the View of the game to the activity
setContentView(game);
}
}
You can also add extra stuff like onSaveInstanceState to save game data and restore them later but I don't want to add them now so the code looks simple.
This class was very simple, let's move on to our SurfaceView. The reason I picked a SurfaceView to do that it's because it is made to allow custom graphics to be drawn on it - exactly what we want on a video game. I will try to make the class as simple as possible:
/*SurfaceHolder.Callback will run some functions in our class when
our surface is completed - at that point we can initialize data
that have to do with the View's width/height.
I don't know if you've noticed that on a View's onCreate()
when you call getWidth() or getHeight() you get 0, that's because
the surface is not initialized yet, this is a way to fix that.
Also we need a Runnable to run the Thread inside this class,
no need to make more classes and make it more complicated*/
public class GameSurface extends SurfaceView
implements SurfaceHolder.Callback, Runnable
{
//This is our thread - we need the "running" variable to be
//able to stop the Thread manually, this will go inside our "while" loop
private Thread thread;
private boolean running;
//Right here you can add more variables that draw graphics
//For example you can create a new class that has a function that
//takes Canvas as a parameter and draws stuff into it, I will add
//a Rect in this case which is a class already made by android
//but you can create your own class that draws images or more
//complicated stuff
private Rect myRect;
//Rect needs a paint to give it color
private Paint myPaint;
//Constructor
public GameSurface(Context context)
{
super(context);
//This is the callback to let us know when surface is completed
getHolder().addCallback(this);
}
//When a class implements SurfaceHolder.Callback you are forced to
//create three functions "surfaceCreated", "surfaceChanged" and
//"surfaceDestroyed" these are called when the surface is created,
//when some settings are changed (like the orientation) and when
//it is about to be destroyed
#Override
public void surfaceCreated(Surface holder)
{
//Let's initialize our Rect, lets assume we want it to have 40
//pixels height and fill the screen's width
myRect = new Rect(0, 0, getWidth(), 40);
//Give color to the rect
myPaint = new Paint();
myPaint.setARGB(0, 255, 0, 0);
//In case you are not familiar with the Rect class, as
//parameters it gets Rect(left, top, right, bottom)
//Time to start our Thread - nothing much to explain here if
//you know how threads work, remember this class implements
//Runnable so the Thread's constructor gets "this" as parameter
running = true;
thread = new Thread(this);
thread.start();
}
//We won't use this one for now, but we are forced to type it
//Even if we leave it empty
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
//When the surface is destroyed we just want the Thread to
//terminate - we don't want threads running when our app is not visible!
#Override
public void surfaceDestroyed(SurfaceHolder holder)
//We will type this function later
{destroyThread();}
//Time for the interesting stuff! let's start with input
#Override
public boolean onTouchEvent(MotionEvent event)
{
//The logic is as follows: when our Rect is touched, we want
//it to become smaller
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (myRect.contains((int) event.getX(), (int) event.getY())
{
myRect.right -= 5;
//Return true - we did something with the input
return true;
}
}
return super.onTouchEvent(event);
}
//This is our update, it will run once per frame
private void update()
{
//Let's assume we want our rect to move 1 pixel downwards
//on every frame
myRect.offset(0, 1);
}
//Now for our draw function
public void draw(Canvas canvas)
{
//Here we want to draw a background and our rect
canvas.drawARGB(0, 0, 0, 255);
canvas.drawRect(myRect, myPaint);
}
//The only thing left is our run() function for the Thread
#Override
public void run()
{
//Screen
Canvas canvas;
//Our game cycle (the famous while)
while(running)
{
//Count start time so we can calculate frames
int startTime = System.currentTimeMillis();
//Update our game
update();
//Empty screen so it can obtain new instance
canvas = null;
//Try locking the canvas for pixel editing on surface
try
{
//Try getting screen
canvas = getHolder().lockCanvas();
//Succeeded
if (canvas != null) synchronized (getHolder())
{
//Actual drawing - our draw function
draw(canvas);
}
} finally
{
//Draw changes
if (canvas != null) getHolder().unlockCanvasAndPost(canvas);
}
//End Frame - 1000/30 means 30 frames per second
int frameTime = System.currentTimeMillis() -startTime;
if (frameTime < 1000/30)
try { Thread.sleep(1000/30 -frameTime); } catch (InterruptedException e){}
}
}
//Last but not least, our function for closing the thread
private void destroyThread()
{
//Stop thread's loop
running = false;
//Try to join thread with UI thread
boolean retry = true;
while (retry)
{
try {thread.join(); retry = false;}
catch (InterruptedException e) {}
}
}
}
I may have made some minor mistakes (probably with case sensitive letters) so feel free to correct these, I wrote the code at once so I didn't have time to test it, it should work flawlessly though.
If you have any more questions, need more explanation or something is not working right let me know!

Drawable. Draw doesn't draw image to the screen

public class BackgammonBoardView extends SurfaceView implements SurfaceHolder.Callback {
/***
* #author
* The threads class definition
*/
class BBVThread extends Thread
{
//mSurfaceHolder and mHandler are part of the thread.
//mContext is part of the container SurfaceView
private SurfaceHolder mSurfaceHolder;
private Handler mHandler;
//The threads constructor
public BBVThread(SurfaceHolder surfaceHolder, Context context,
Handler handler) {
Log.d(TAG,"BBVThread. Constructor.");
mSurfaceHolder = surfaceHolder;
mHandler = handler;
mContext = context;
mDiceCup = context.getResources().getDrawable(
R.drawable.dicecup);
}
#Override
public void run() {
Log.d(TAG,"BBVThread. run.");
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
Log.d(TAG,"Draw the dice cup...");
mDiceCup.setBounds(0, 0, 120,120);
mDiceCup.draw(canvas);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}//end run
}
/****
* end of the BBVThread class
*/
/* ########################################
* # BackgammonBoardView Member Variables #
* ######################################## */
private Drawable mDiceCup;
/*
* End BackGammonBoardView Member Variables
*/
public BackgammonBoardView(Context context, AttributeSet attrs)
{
super(context, attrs);
Log.d(TAG,"SurfaceView Constructor.");
//register out interest in hearing about changes to our surface
SurfaceHolder holder = getHolder();
holder.addCallback(this);
thread = new BBVThread(holder, context, new Handler(){
#Override
public void handleMessage(Message m) {
}
});
setFocusable(true); // make sure we get key events
}
#Override
public void surfaceCreated(SurfaceHolder arg0)
{
//Start the thread. This will initiate run which will do the first draw
thread.start();
//Norify that the surface has been created
Log.d(TAG,"SurfaceView. surfaceCreated.");
}
}
I'm following the kind of code that I'm seeing in the LunarLander example. I created a res/drawable folder. Previously the images I had in res/drawable-hdpi however when I didn't see an image drawn to the canvas I created res/drawable and copied the .png's into that folder. My call to R.drawable.dicecup is resolved without any syntax errors. The image is 120 x 120 pixels as I sized it in my image editor. I was surprised when I opened the application and there is no dice cup image drawn anywhere. What could be wrong?
The thread is actually started in the surfaceCreated method. I'm getting Log.d's from run so I know run is executing. Theres no errors as far as I can tell and the program doesn't crash or anything just nothing is drawn.
I've tried moving the draw back into the UI thread but I still see nothing.
P.S. This turned out to be very much my fault. The Bitmap draws fine from the thread it was just being obliterated by the rectangle I am using for my background image and I had not discovered that yet. It should help now that I understand how to draw a bitmap to spot these types of problems. To make things as easy right now I created res/drawables and am loading the images from there.
It looks like you are trying to call draw on a thread other than the UI thread. If that's what you're doing, then that's why its not working. According to the docs, you cant do any UI work on non-UI threads.

Android game loop seems to not work

I'm trying to create a simple game loop (its not really a game yet) that displays a circle, then after it ticks 100 times draws another circle. I also have a text field that should display how many times the loop has ran. Relevant code is as follows:
MainActivity
public class MainActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
DrawView v = new DrawView(this);
v.setBackgroundColor(Color.WHITE);
setContentView(v);
}
}
DrawView
public class DrawView extends SurfaceView implements SurfaceHolder.Callback
{
Paint p = new Paint();
MainThread thread;
private int y=0;
public DrawView(Context c)
{
super(c);
thread = new MainThread(this, getHolder());
thread.running = true;
getHolder().addCallback(this);
setFocusable(true);
}
public void draw(Canvas c)
{
if(c==null)
return;
super.onDraw(c);
p.setColor(Color.RED);
p.setTextSize(32);
p.setTypeface(Typeface.SANS_SERIF);
c.drawCircle(getWidth()/2-100,getHeight()/2, 50, p);
c.drawText("y = " + y, 50, 50, p);
if(y==100)
c.drawCircle(getWidth()/2+100,getHeight()/2, 50, p);
else
y++;
}
public void surfaceCreated(SurfaceHolder p1)
{
thread.start();
}
MainThread
public class MainThread extends Thread
{
private DrawView page;
private SurfaceHolder holder;
public boolean running;
public MainThread(DrawView p, SurfaceHolder h)
{
super();
page = p;
holder = h;
}
#Override
public void run()
{
while(running)
{
Canvas c = holder.lockCanvas();
page.draw(c);
holder.unlockCanvasAndPost(c);
}
}
}
It just displays the first circle and the text saying "y = 2." Nothing seems to update, or its doing it twice and then stopping. I am new to Android programming but not to Java. I'm sure I'm just missing something simple. Thanks for any help.
EDIT: Upon further observation, it seems the thread crashes randomly. Everytime I run the app, it dislays "y = " and then a different number each time. I'd reckon it makes it that many ticks before crashing. After I close the app, I get a message that says "Unfortunately, MyApp has stopped." I don't know enough about how Android works to know why its crashing.
EDIT 2: I've discovered its throwing an IllegalArgumentException on the line holder.unlockCanvasAndPost(c). Again, l'm not sure why. Can anyone explain what's happening and how to fix it?
EDIT 3: Logging the value of y each tick reveals that it couts up correctly and stops when it reaches 100 as intended. What happens onscreen does not reflect that for some reason.
I think what you're looking for is a combination of a Handler and a Runnable.
The handler calls the runnable, which runs once, then decides if it should call again.
something like:
private Handler renderHandler = new Handler();
private Runnable renderRunnable = new Runnable() {
#Override
public void run() {
//... do stuff here;
if(shouldRunAgain){
renderHandler.postDelayed(renderRunnable, 1000); // for 1 second
}
}
};
renderHandler.post(renderRunnable);
edit:
without seeing a log cat, I'm going to make an assumption that the UI updates are happening from a thread other than the main UI thread, causing the crash.
have a look at this, this, and this - I think these are more in line of what you're looking for.

Android: Canvas not being redrawn

There are multiple similar questions like mine, but these questions didn't help me.
I'm making a game. The game thread, SurfaceView and Activity is already finished and works so far. The problem is that the canvas is not redrawn. At startup, it draws the icon on the background, but at every tick, the icon doesn't move (It should move once a second). I want to mention, that I never needed to call postInvalidate. I have a working example where I never called it, but it doesn't work in my current example (I don't want to go into it deeper, since I actually don't need to call it). I copied the current code from my working example, the concept and the way of implementation is exactly the same, but my current code doesn't refresh the canvas. When I log the drawing positions in onDraw method, I see that it's coordinates are updated every second as expected, so I can be sure it's a canvas drawing problem. I have searched for hours but I didn't find what's different to my working example (except that I'm using another Android version and I don't extend thread but implement Runnable, because it's a bad style to extend thread. Nevertheless, I also extended thread to see if there is any difference, but it doesn't help). I already tried to clean the canvas by using canvas.drawColor(Color.BLACK), but that didn't help either. I already tried to use background colors instead of a background image which changes randomly every tick, but it didn't change but stays always the same.
I figured out that the canvas at the very first call has a density of (for example) 240. After the second tick, the canvas density is always 0. I know that the density will not help me here, but maybe it's an important information for someone.
Here are the important classes....
game layout, R.layout.game
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.mydomain.mygame.base.game.GameSurface
android:id="#+id/gameSurface"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:background="#drawable/background_game" >
</com.mydomain.mygame.base.game.GameSurface>
<!-- ...more definitions -->
</LinearLayout>
GameActivity (contains layout)
public class GameActivity extends Activity
{
#SuppressWarnings("unused")
private GameSurface gameSurface;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
gameSurface = (GameSurface)findViewById(R.id.gameSurface);
//TODO on button click -> execute methods
}
}
GameSurface (log in onDraw shows updated coordinates every tick)
public class GameSurface extends SurfaceView implements SurfaceHolder.Callback
{
private GameThread thread;
protected final static int TICK_FREQUENCY = 100;// ms, stays always the same. It's a technical constant which doesn't change
private static final String TAG = GameSurface.class.getSimpleName();
public GameSurface(Context context, AttributeSet attrs)
{
super(context, attrs);
ShapeManager.INSTANCE.init(context);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
setFocusable(true); // make sure we get key events
thread = new GameThread(holder, this);
}
public void updateStatus()
{
GameProcessor.INSTANCE.updateShapes();
}
#Override
protected void onDraw(Canvas canvas)
{
for (Shape shape : GameProcessor.INSTANCE.getShapes())
{
Log.i(TAG, "getX()=" + shape.getX() + ", getY()=" + shape.getY());
canvas.drawBitmap(shape.getBitmap(), shape.getX(), shape.getY(), null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
//will never invoked since we only operate in landscape
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
// start the thread here so we don't busy-wait in run
thread.setRunning(true);
new Thread(thread).start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
Log.i(TAG, "executing surfaceDestroyed()...");
thread.setRunning(false);
}
}
GameThread
public class GameThread implements Runnable
{
private SurfaceHolder surfaceHolder;
private boolean running = false;
private GameSurface gameSurface;
private long lastTick;
public GameThread(SurfaceHolder surfaceHolder, GameSurface gameSurface)
{
this.surfaceHolder = surfaceHolder;
this.gameSurface = gameSurface;
lastTick = System.currentTimeMillis();
}
#Override
public void run()
{
Canvas canvas;
while (running)
{
canvas = null;
if (System.currentTimeMillis() > lastTick + GameSurface.TICK_FREQUENCY)
{
long timeDifference = System.currentTimeMillis() - (lastTick + GameSurface.TICK_FREQUENCY);
try
{
canvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder)
{
gameSurface.updateStatus();
gameSurface.draw(canvas);
}
}
finally
{
if (canvas != null)
{
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
lastTick = System.currentTimeMillis() - timeDifference;
}
}
}
public void setRunning(boolean running)
{
this.running = running;
}
}
Any ideas why this code doesn't update my canvas? I can't explain it. I do not post ShapeManager and GameProcessor since they don't have anything to do with the problem (and they only load and control the current states and speed of the game).
[UPDATE]
I figured out that onDraw() is invoked before the game thread has started. That means that canvas is passed to this method before thread is using it. The interesting thing is that, after the thread has started, it always uses the same canvas, but it's not the canvas reference which is passed the very first time. Although canvas = surfaceHolder.lockCanvas(null); is assigned every tick, it's always the same reference, but it's not the original reference.
In a working example of mine, the reference is always the same, since I create the bitmaps at constructor initialization time. I can't do that in my current implementation, since I have to do calculations with values I get from onMeasure() which is invoked much later than the constructor.
I tried to somehow pass the original canvas to the thread, but the reference still changes. Meanwhile I think this is the problem, but I don't know how to solve it yet.
As often happens, I found the solution on my own.
Obviously it's really a problem that it draws to different canvas instances. I'm still not sure why this happens. Didn't have the problem before. Nevertheless, I can avoid drawing to canvas by setting setWillNotDraw(true); in my GameSurface constructor and I must not invoke gameSurface.draw(canvas) in my thread, but gameSurface.postInvalidate() instead.

Android: OpenGL reloading textures

I am trying to add a loading screen into my app as it takes some time to load off of the textures. This is what I was doing before...
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
super.onSurfaceCreated(gl, config);
if(firstTimeCreate) {
load(); //load settings
Assets.LoadTextures(this);
firstTimeCreate = false;}
else {
//When screen is resumed....
Assets.reloadTextures();}
This way after the app was resumed the else statement would take effect and reload everything...I tried to adapt it to this
else {
//When screen is resumed....
Thread aThread = new Thread()
{
public void run(){
boolean once = true;
while(once)
{
Assets.reloadTexutres();
once = false;
}
}
};
aThread.start();
}
However it just seems now that OpenGL cant bind the textures as the screen is white. What is going wrong by adding a thread to this method? Do I need to wait for all the textures to load before I let OpenGL start to try and render, if so how can I load one and then present a loading screen until the rest are done?
Thanks
OpenGL ES context is assigned to a thread. So, if you want to create context in one thread and use it in another thread, you should call eglMakeCurrent(...) function.

Categories

Resources