I am working on a simple android game. I came to a point that I would like to optimize my engine with multithreading. I am working in OpenGL ES, Android 2.2
Now UpdateGame() and RenderScene() run in single thread and are executed in onDrawFrame(GL10 gl)
I have a RenderObject class that has Position, Rotation, Scale, and Color members. All RenderObjects are created when I start my game and are stored in RenderObject array.
In UpdateGame() function I go through RenderObjects in RenderObject array and update new Position, Rotation, Scale and Color.
In RenderScene() function I go through RenderObjects in RenderObject array and render them with new Position, Rotation, Scale and Color.
This works ok in single thread.
So then I tried create a thread for UpdateGame().
So my idea is to:
Update Thread:................Render Thread:
Update Frame 0
Update Frame 1................Render Frame 0....Update and Render work in parallel
Update Frame 2................Render Frame 1
Update Frame 3................Render Frame 2
But first I modified RenderObject class so that it has RenderPosition, RenderRotation, RenderScale and RenderColor members.
These members get copied just before rendering previous frame. So that parallel update thread can modify new Position, Rotation, Scale, Color.
Runnable pRunnable;
Thread pThread;
public int renderframe = 0;
piblic int updateframe = 0;
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
// I create a new thread in this function
pRunnable = new Runnable(){
public void run(){
while(true)
{
while( renderframe < updateframe )
{
// WAITING for render to finish so that data does not get corrupterd
}
//
// I update RenderObject Position, Rotation, Scale, Color members here
UpdateGame();
updateframe++;
}
}
};
pThread = new Thread(pRunnable);
pThread.start();
}
public void onDrawFrame(GL10 gl)
{
while( renderframe == updateframe )
{
// Wait for update to finish
}
for(int a=0;a<MAX_RENDER_OBJECTS;a++)
{
RenderObject ro = aRenderObjects[a];
// I do this in a function
ro.RenderPosition[0] = ro.Position[0];
ro.RenderPosition[1] = ro.Position[1];
ro.RenderPosition[2] = ro.Position[2];
ro.RenderPosition[3] = ro.Position[3];
// I do the same for Rotation, Scale, Color
}
renderframe++;
// When rendering scene I use RenderPosition, RenderRotation members when calling OpenGL API
RenderScene();
}
But when I run my game. The graphics are incorrect. It appears as if data is not in sync.
Any suggestion there on how to appropriately sync RenderObject data between Update and Render threads.
Thank you.
This style seems generally inefficient (spin-waiting loops that will block the two threads from working simultaneously) and dangerous (I don't know how Android implements this, but Java memory model does not guarantee that data from one thread is visible to another thread without going through a synchronized block).
My suggestion is this: the main thread is the rendering thread (GL contexts are thread-local, so you can't use it from another thread (though you can create another context on another thread to perform some data sharing)), and spawns the update thread. The main thread then goes on a loop, waiting on a mutex on every iteration. The update thread updates the game logic, and creates a new object representing everything that has to be drawn (e.g. a list of drawing commands). This object is then put on a queue and the main thread is woken (with notify()). The main thread then gets the drawing data and draws it.
You'll probably want to dismiss older sets of data in case the update loop is much faster than the render loop, but this general idea should get you started.
Related
My first attempt at AndroidPlot. The data I want to plot (and update every 5 seconds when a new data point arrives) comes from an ArrayBlockingQueue of up to 720 timestamped points. I have a class that implements the XYSeries and PlotListener interfaces. It has a method updatePlotData that just extracts the data from the queue into an array:
class TempPlotSeries implements XYSeries, PlotListener {
private final static String TAG = TempPlotSeries.class.getSimpleName();
private Pair<Date, Float>[] plotArray;
void updatePlotData( ArrayBlockingQueue<Pair<Date, Float>> dataQueue ) throws InterruptedException {
synchronized ( this ) {
wait(); // don't update data until we're notified that current plot is done (& we can get lock)
plotArray = dataQueue.toArray( new Pair[0] );
if( DEBUG ) Log.d( TAG, "updatePlotData run with " + plotArray.length + " data points" );
notifyAll(); // release lock & let other threads know they can continue
}
}
// XYSeries implementation
#Override
public int size( ) {
return plotArray.length;
}
#Override
public Number getX( int index ) {
return (index - HISTORY_BUFFER_SIZE) / (60/TEMP_UPDATE_SECONDS); // e.g., -60 minutes at left edge of graph, -1/12 min at right
}
#Override
public Number getY( int index ) {
return plotArray[index].second; // the temp value
}
#Override
public String getTitle( ) {
return "Temp History";
}
// PlotListener Implementation
#Override
public void onBeforeDraw( Plot source, Canvas canvas ) {
synchronized ( this ) {
try {
wait(); // wait for data updating to finish if it's in progress on another thread
} catch ( InterruptedException e ) {
// unlikely to be interrupted?
}
}
}
// between these 2 calls the plot is redrawn
#Override
public void onAfterDraw( Plot source, Canvas canvas ) {
synchronized ( this ) {
notifyAll( ); // plot done, OK to update data
}
}
}
I don't have much experience with synchronization--does this look reasonable?
My plot setup is:
tempHistoryPlot = (XYPlot) findViewById(R.id.temp_history);
tempPlotSeries = new TempPlotSeries();
tempHistoryPlot.setRenderMode( Plot.RenderMode.USE_BACKGROUND_THREAD );
tempGraphFormatter = new LineAndPointFormatter(this, R.xml.line_point_formatter_with_labels);
tempHistoryPlot.addSeries(tempPlotSeries, tempGraphFormatter);
tempGraphWidget = tempHistoryPlot.getGraph();
(couldn't find any documentation on the purpose of getGraph() so don't know if I need it.)
I have an Observable (RxJava) that emits the entire data queue when a new sample is available (every 5 seconds). If the queue is full I discard the oldest value. Then I have:
tempPlotSeries.updatePlotData( newTempHistory );
tempHistoryPlot.redraw();
But the plot isn't drawn. When the app first launches, the "dummy" plot appears in its View, but as soon as I try to draw the plot the entire ConstraintLayout containing the XYPlot element (and other UI elements) is completely blanked. What's going on here?
Other questions: it's my understanding that any code affecting the Android UI must run on the main thread. But we're using a background thread to render the plot. How does this work? Do I perhaps need to insert a .observeOn( AndroidSchedulers.mainThread() operator in my Observable chain?
I don't have much experience with synchronization--does this look reasonable?
I don't think you need the wait() inside the synchronized block inside updatePlotData. You can also use SimpleXYSeries as a reference for how to setup synchronization of this sort.
When the app first launches, the "dummy" plot appears in its View, but as soon as I try to draw the plot the entire ConstraintLayout containing the XYPlot element (and other UI elements) is completely blanked.
I'm having trouble visualizing this. Could you add a screenshot of the "dummy" plot and the subsequent blank plot?
it's my understanding that any code affecting the Android UI must run on the main thread. But we're using a background thread to render the plot. How does this work?
The general rules of using the main thread to update the UI still exist, Androidplot is just using a technique to minimize the main thread usage during intensive rendering: A background thread is used to fill a bitmap buffer with the data to be shown, then notifies the main thread when the buffer is ready to be displayed.
Somewhat Unrelated Suggestion: Looking at your TempPlotSeries implementation, I notice that you are modeling your data as a Pair<Date, Float>[] but your getX() implementation does not make use of the Date part. It appears you're trying to model your data using what I assume is your desired display format for your domain, ie. -60 to -1/12 minutes. For simplicity I'd suggest making getX() return the Date's long epoch value instead. You can apply a display format to these values later.
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);
}
}
I am trying to draw points to android canvas slowly. I want to use canvas.drawline function however drawing one point to another i want to have small delay. Can you help me please?
Here's the general pattern:
// PSEUDOCODE
// in a class created in a Looper thread, e.g., the main thread
private final Handler handler = new Handler();
static class DrawTask implements Runnable {
// final fields for start point, end point, number of segments, interval, etc.
// mutable field for progress
// constructor with appropriate params
#Override
public void run() {
// draw the current line segment
if(!finished) {
handler.postDelayed(this, interval);
}
}
}
// in some draw method
handler.post(new DrawTask(...));
You might want to hang onto a reference to the DrawTask so you can cancel it with Handler#removeCallbacks(...) in case it's still running when your activity is paused.
I created a custom view from scratch. Extended View and overrided onDraw().
When comes down in animating the view i generate a custom animation using offsets.
eg.
while(!isOnTop){
mOffset++;
//draw the component a a it higher using the offset
if(position == 0)
isOnTop==true;
invalidate();
}
The thinking is that my frames come from invalidate it self. The problem is that invalidation of this view can come just by scrolling a listview at the same screen.
This "shared invalidation()" causes lag to my animation.So is there a way out of that lag?
Do you have any other suggestion of performing animations in that shared enviroment?
Creating an animation using a seperate thread that calculates the offset also needs forced invalidation() calls to display the animation (correct me if i'm wrong).
Is the only solution to perform the animation in eg 10 invalidation requests with a larger step? It will ease the lag out but i think i can use a different approach on that.
"What is best" of course depends greatly on exactly what you are trying to do. You haven't said what you are trying to accomplish, so we can only guess at what may be best for you.
Here are some simple things:
If you want to animate bitmap frames, use AnimationDrawable: http://developer.android.com/reference/android/graphics/drawable/AnimationDrawable.html
If you want to animate the movement of views within your hierarchy, use the view animation framework: http://developer.android.com/guide/topics/graphics/view-animation.html
The new more general animation framework can do a lot more stuff an is often easier to use: http://developer.android.com/guide/topics/graphics/animation.html. This is natively available in Android 3.0+ but can also be used in Android API level 7 with the support v7 library.
If you want to write a custom widget that is an integrated part of its view hierarchy and manually does its own animation drawing, you can use a Handler to time the updates (usually you'll want 60fps or 20ms between each invalidate()) and then in your onDraw() method draw your view's state based on SystemClock.uptimeMillis() as a delta from when the animation started.
Here's a simple repeated invalidate using Handler:
long mAnimStartTime;
Handler mHandler = new Handler();
Runnable mTick = new Runnable() {
public void run() {
invalidate();
mHandler.postDelayed(this, 20); // 20ms == 60fps
}
}
void startAnimation() {
mAnimStartTime = SystemClock.uptimeMillis();
mHandler.removeCallbacks(mTick);
mHandler.post(mTick);
}
void stopAnimation() {
mHandler.removeCallbacks(mTick);
}
Since this question has some interest I will reply.
The best way to to that is to have a separate canvas thread. A "separate" canvas can only be achieved with a SurfaceView. LunarLanding is an excelent example of that use. Each frame is calculated separately than the main view sharing only CPU time, not drawing time. Therefore is faster, even with the combination of for e.g a regular view at the top and an animating view at the bottom.
But you have to set an interval if you are in that shared environment. That interval is used for the FPS cap. If you don't set FPS cap then the CPU will running wild managing to get good animation to the SurfaceView if it was alone. Capping it at 60fps or even less will do the trick to draw all views efficiently with no CPU overload.
So see the drawing thread of the Lunar Landing from the API demos and set a FPS cap.
private long timeNow;
private long timeDelta;
private long timePrevFrame;
private void capFps(int fps) {
timeNow = System.currentTimeMillis();
timeDelta = timeNow - timePrevFrame;
try {
//ps you can always set 16 instead of 1000/fps for 60FPS to avoid the calculation every time
Thread.sleep((1000 / fps) - timeDelta);
} catch (InterruptedException e) {
}
timePrevFrame = System.currentTimeMillis();
}
and then the drawing thread will look something like this:
#Override
public void run() {
Canvas c;
while (run) {
c = null;
sleepFps(60, false);
try {
synchronized (surfaceHolder) {
c = surfaceHolder.lockCanvas(null);
widgetView.doDraw(c);
}
} finally {
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
I'm making a game that displays some numbers on a canvas (score, time, etc).
The way that I currently do this is with the drawtext command on a canvas
// score is some int
draw(Canvas c) {
c.drawText(score+"", x, y, paintSyle);
}
I hear that object creation and garbage collection are expensive operations, and I think this is creating a new string every time it is called.
Right now my game with all bitmap drawing and everything jumps around from 25 to 60 fps. I'd like it to stay closer to the higher number and I'm trying to find ways to speed it up.
Would it be faster/better to make(or find?) some mutable subclass of string and work around this problem? Is there another way to solve this issue? Or is this just how it is?
Introduce two new private member variables String renderedScoreString and int rederedScore and rewrite your draw()-method like that:
draw(Canvas c) {
if (this.score != this.renderedScore || this.renderedScoreString == null) {
this.renderedScore = this.score;
this.renderedScoreString = Integer.toString(this.renderedScore);
}
c.drawText(this.renderedScore, x, y, paintStyle);
}
that should save you a lot! of object creations. You could also hide the boilerplate code behind a getter method, e.g. String getScoreString() which does the same, so you don't have it in the draw()-method.
A friend of mine tipped me in on a solution to this problem. When you want to draw something over time, one of the best (and simplest) mechanisms to do so is to split up what you need to do into two completely separate processes.
ie. Only use the draw command exclusively for drawing stuff, keep logic/assignment in Draw() to an absolute minimum.
private final long TIMER_PERIOD = 500;
private String timeString;
private Runnable updateRunnable;
private Handler updateHandler = new Handler();
public void onCreate(Bundle savedInstanceState) {
updateRunnable = new Runnable() {
#Override
public void run() {
timeString = GetTimeString();
updateHandler.postDelayed(updateRunnable, TIMER_PERIOD);
}
}
}
Draw(Canvas c) {
c.drawText(timeString, x, y, paintStyle);
}
In this example the Draw command simply takes timeString in its current state and draws it to the screen. This is highly efficient use of the draw function as it does not require any object creation, and no logic is present that is not immediately required for any drawing to occur. . In the background a Runnable is executing the run() function every 500 miliseconds (approximately). Simply update the Run() function with whatever logic you need to calculate the time (example has a dummy function GetTimeString())
I hope this is helpful.
I know I'm resurrecting a dead thread, but there is one extra optimisation you can add to this which restricts String creation to a one-time thing and thus only triggers the GC once at the start and not during the game (which is quite important for an android game).
Somewhere during the start of your game (onCreate, onResume, as part of a singleton during application startup, etc) create a large String[] which can hold the maximum score (my game fills an array of 10000, so the max score would be 9999). Then loop over it with a for loop, filling each index with a String.valueOf(i).
for (int i = 0; i <scoreStrings.length; i++)
{
scoreStrings[i] = String.valueOf(i);
}
Now, when you need to draw the score, just use the int you use to store the score in as an index to that array, and "hey, presto!", you get the correct string for your score.
canvas.drawText(scoreStrings[score], x, y, paint);