Load Bitmap to variable in new thread, OpenGL result a white image - android

I'm trying to load images in new thread to reduce lags in UI thread:
public class MyGame extends Activity implements Game, Renderer {
...
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
super.onSurfaceCreated(gl, config);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
if(firstTimeCreate) {
Settings.load(getFileIO());
// Load bitmaps and save to variable Assets.backgroundRegions
Assets.load(this);
firstTimeCreate = false;
} else {
Assets.reload();
}
}
});
t.start();
}
...
}
The problem is when I trying to draw images as soon as the images is loaded, I only get a white images, no error message. This is the method I use to render background (this method run in a loop)
public void renderBackgrounds() {
if (Assets.backgroundRegions.size() > 0) {
batcher.beginBatch(Assets.backgroundRegions.get(0).texture);
batcher.drawSprite(
Assets.backgroundRegions.get(0).position,
Assets.backgroundRegions.get(0)
);
batcher.endBatch();
} // else { background is not loaded yet }
}
The weird thing is when I press Home button and open my app again, then background images are displayed all correctly. It's just like there are "white-images cached version" of all the loaded images, and Android just don't clear the cache until I re-create the Activity, maybe, I don't know.
If I remove the new thread implementation in onSurfaceCreated like this:
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
super.onSurfaceCreated(gl, config);
if(firstTimeCreate) {
Settings.load(getFileIO());
Assets.load(this);
firstTimeCreate = false;
} else {
Assets.reload();
}
}
...then everything works fine except the UI thread is extremely lag.
I have read some posts about loading Bitmaps in new thread (e.g. this and this), they load Bitmap and assign the variable directly to the View (ImageView), but in my case I save the Bitmap to variables and use OpenGL to render. I don't know if this is one of the reasons or not.
There are the possible reasons I could think about:
All loaded images are cached wrong (somehow, I don't know) and I only get white images until I re-create the Activity.
Something wrong with multithreading and the variable I have used to store bitmap data (backgroundRegions).
I did things in wrong way when use OpenGL and Multithreading together, I don't know much about OpenGL, the part with OpenGL is taken from a small framework.

Related

Low frame rate when drawing full screen drawable on canvas

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.

Load and dispose images with Libgdx

I made an android app using libgdx but I have a problem of latency. After a few games, the application become very slow and I don't really know why.
I think it's maybe because the loading of images and when I dispose them but I am not really sure.
This is my code :
I have a class Cell and in this one I load and dispose images:
TextureAtlas dotsAtlas = game.getAssets().get("dots.pack", TextureAtlas.class);
TextureRegion textureRegionRed = dotsAtlas.findRegion("red");
ImageButtonStyleRed = new ImageButton.ImageButtonStyle();
ImageButtonStyleRed.imageUp = new TextureRegionDrawable(textureRegionRed);
public void disposeCell(){
dotsAtlas.dispose();
}
And in the class GameScreen, I have :
public void dispose(){
skin.dispose();
stage.dispose();
batch.dispose();
backgroundSprite.getTexture().dispose();
generatorFont.dispose();
othersAtlas.dispose();
Cell.disposeCell();
}
Do I use Atlas,textures and dispose functions correctly to load images ?

Android - Update Bitmap from timer thread

I got an Android project composed by a single Layout with an ImageView.
public class MainActivity extends AppCompatActivity {
/* original and stretched sized bitmaps */
private Bitmap bitmapOriginal;
private Bitmap bitmapStretched;
/* the only view */
private ImageView iv;
....
}
This ImageView is updated by this runnable function
runnable = new Runnable() {
#Override
public void run() {
iv.setImageBitmap(bitmapStretched);
}
};
and the runnable is ran by a temporized JNI function, running on a background thread, that call it 60 times per second.
public void jniTemporizedCallback(int buf[]) {
/* set data to original sized bitmap */
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
/* calculate the stretched one */
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height, false);
/* tell the main thread to update the image view */
runOnUiThread(runnable);
}
After some frame is drawn, the app crashes with the following message.
A/OpenGLRenderer: Task is already in the queue!
I guess this is because the renderer didn't finish to fully render the previous frame of the ImageView and gets angry.
If i remove runOnUiThread(runnable); the problem disappear (obviously)
How can avoid this? How can i syncronize my application with the openGL renderer?
I also tried to extend ImageView and draw the bitmap on canvas into the onDraw function but i got the same result
I guess you're trying create bitmapOriginal ouside the thread. Therefore, when compiler is trying to call again after 60 seconds, it's getting same objects and couldn't identify the task. I would suggest better as below.
public void jniTemporizedCallback(int buf[]) {
// Initialize
bitmapOriginal = Bitmap.createBitmap(///)
/* set data to original sized bitmap */
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
/* calculate the stretched one */
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height,false);
/* tell the main thread to update the image view */
runOnUiThread(runnable);
}
The proper way to synchronize your drawing logic with the device's frame rate is to use a SurfaceView instead of an ImageView. Instead of pushing frames to the View with your own timer, you should create a rendering Thread that tries to render frames as fast as possible. When you call surfaceHolder.lockCanvas(), the Android system will automatically block until it is time to render the frame. When you unlock the canvas using unlockCanvasAndPost(), the system will draw the buffer to the screen.
See https://developer.android.com/guide/topics/graphics/2d-graphics.html#on-surfaceview for more info. Hope this helps!
Problem was totally unrelated to the Bitmap itself....
It was the real time clock signal that messed with Android RenderThread.
Further explanation here:
Android and JNI real time clock
Provide here purposes of use such method for rendering? What you want to do?, there are great animation functionality in android engine, may be this task can be done with this animation.
One more if you will use codes like yours battery of phone will run to zero very fast coz this will load cpu/gpu to max.
in anyway - try to place blocks from running task, set bool taskRun = true on start and check if (!taskRun){ taskRun = true; //here start your task..} and on ui thread after updating ui you can switch to taskRun = false; Using this you can skip some frames, but should not crash.
The problem is that the Handler of the main thread is keeping a reference to your Runnable. When you want to run your Runnable for the second time, the old Runnable is already in the Message Queue, hence Task is already in the queue message. If you create a Runnable every time u want to execute the Runnable like in the code below, I think the problem will be solved.
public void jniTemporizedCallback(int buf[]) {
/* set data to original sized bitmap */
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
/* calculate the stretched one */
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height, false);
/* tell the main thread to update the image view */
runOnUiThread(new Runnable() {
#Override
public void run() {
iv.setImageBitmap(bitmapStretched);
}
});
}
I think you are right with reason, because you cannot be sure, that Android render images in 60 FPS. And yeah, I think you need just synchronize Bitmap Native Callback with Android Render. So, lets start.
I prefer using Lock from concurrency stack Java. Because you see, when you lock object, and when you unlock. In case of using volatile (for example, sure there also reference restrictions) on Bitmap object, you need to check locking this object in very places, where you using Bitmap.
Also I think you should use Lock from THIS EXAMPLE (to unlock Lock object from any other Thread). So, here is example. Example below will work properly. Just don't forget about Context deleting and stopping task:
public class MainActivity extends AppCompatActivity {
/* Initialize lock (avoid lazy init, with your methods) */
private ReentrantLock lock = new ReentrantLock();
............
private runnableDrawImage = new Runnable() {
#Override
public void run() {
iv.setImageBitmap(bitmapStretched);
lock.unlock();
}
};
..........
public void jniTemporizedCallback(int buf[]) {
/* synchronize by locking state*/
lock.lock();
bitmapOriginal = Bitmap.createBitmap(///)
bitmapOriginal.setPixels(buf, 0, origWidth, 0, 0, origWidth, origHeight);
bitmapStretched = Bitmap.createScaledBitmap(bitmapOriginal, width, height,false);
MainActivity.this.runOnUiThread(runnableDrawImage);
}
}

How to update SurfaceView from another Thread?

I have a thread where I will get some Bitmap one by one. Then whenever I get a new Bitmap I need to update my SurfaceView which in the main thread. How can I do this?
Here is some pseudo code
new Thread() {
public void run() {
while (bitmapIncoming()) {
Bitmap bitmap = getNextBitmapAfterLongOperation();
mActivityReference.runOnUiThread(new Runnable() {
public void run() {
mActivityReference.updateSurfaceView(bitmap);
}
});
}
}
}.start();
In the thread, make a public interface which defines a function which is called every time a bitmap is retrieved(from the internet i assume) like:
public interface ImageRetrievedListener{
public void onImageReceived(Bitmap bitmap);
}
Implement this method inside your Activity where you are using the Surface View and in the onImageReceived callback, add the Bitmap received into the array of Bitmaps which the SurfaceView uses. Should work, if you still have any problems, do let me know.
//EDIT
Not sure if this is the best possible solution for this problem, but i'd solve it this way.

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