Android: canvas keeps alternating between previous and current drawing commands - android

So, I'm developing a game and I'm using Canvas with SurfaceHolder to update the screen every time an object is supposed to move. That much is working fine so far. Now, the problem happens when I want to stop drawing to the Canvas and just leave it as it is based on the last drawing commands.
So one way that I tried was to simply return from the function that I call when drawing when the end condition is met. However, when I do this, the canvas starts rapidly alternating between the commands sent right when the condition was met and the commands sent one iteration before. I have no idea how or why this is happening since the drawing function is not executing any of its draw commands after the condition is met. Can anyone explain how the canvas can keep refreshing itself when it doesn't get any draw commands?
The code in the thread for locking and unlocking is pretty simple:
public void run() {
Canvas c = null;
try {
c = sh.lockCanvas(null);
synchronized(sh) {
drawCan(c);
}
}
finally {
if(c!=null) {
sh.unlockCanvasAndPost(c);
}
}
}
and the drawCan function is structured like this:
public void drawCan(Canvas c) {
/* Check if user's health is greater than 0. Don't draw anything if it is less */
if(userHealth<=0) {
return;
}
/* Drawing commands - drawRect(), drawBitmap(), etc are run here */
}
Now normally, this runs fine. But when the userHealth condition is met, the Canvas constantly alternates between the last commands sent and the commands right before that. I know that the draw functions are not being called because I used Log.d() in that area of the code and no messages appeared on LogCat after the condition was met. Can someone explain why this is happening and what the solution would be?

The Canvas is double- or triple- buffered and not erased between frames. When you call lock/unlock, you're switching between previously-rendered buffers.
If you move your if(userHealth<=0) test into run() and use it to avoid calling lock/unlock, you should get the desired effect.
For a much longer explanation about what's going on, see this post.
Update: I realized today that I'd omitted a detail (from the answer and ensuing comments). It doesn't change the answer but it may be useful to know.
The lockCanvas() method takes an optional "dirty" rect that allows you to do partial updates. If you use this, and Surface is able to keep track of the "front" buffer you just rendered, the system will copy the non-dirty contents of the front buffer to the back buffer as part of locking the Surface (see copyBlt() in Surface.cpp).
The system doesn't guarantee that this will work, which is why the "dirty" rect is an in-out parameter. If the front buffer isn't available to copy from, the lock method will just expand the dirty rect to cover the entire screen. In either case, your app is responsible for updating every pixel in the "dirty" rect; if you don't, you get the effects you observed.
This does mean that the Surface is explicitly trying to be double-buffered when used with a Canvas, which would explain why you're seeing two frames alternating rather than three even though SurfaceView is generally triple-buffered. (Which is the thing that has been nagging at me since I wrote this up.) It's also possible to be double-buffered if you're just not generating frames fast enough to require triple-buffering.

Related

Android OpenGl Renderer finish event

In OpenGL Renderer onDrawFrame is called several time, until the page is completely rendered. I cannot find an event that my page is completeley rendered in order to take a snapshot of the OpenGL page and animate it.
I have the solution to take snapshot on at the animation trigger (specific button), but this will imply a specific delay, until Bitmap is created, such as i would like to keep in memory a mutable copy of every page.
Do you know other way to animate GLSurfaceView with rendered content?
Snippet for triggering snapshot.
glSurfaceView.queueEvent(new Runnable() {
#Override
public void run() {
glSurfaceView.getRenderer().takeGlSnapshot();
}
});
EGLContext for passing the GL11 object.
public void takeGlSnapshot() {
EGL10 egl = (EGL10) EGLContext.getEGL();
GL11 gl = (GL11) egl.eglGetCurrentContext().getGL();
takeSnapshot(gl);
}
onDrawFrame(Gl10 gl) {
//is last call for this page event ????????????
No such event exists, as I will explain below.
OpenGL is designed as a client/server architecture, with the two running asynchronously. In a modern implementation, you can generally think of the client as the API front-end that you use to issue commands and the server as the GPU/driver back-end. API calls will do a little bit of work to validate input parameters etc, but save for a few exceptions (like glReadPixels (...)) they buffer up a command for the server to execute at a later point. You never truly know when your commands are finished, unless you explicitly call glFinish (...).
Calling glFinish (...) at the end of each frame is an awful idea, as it will create a CPU/GPU synchronization point and undo the benefits of having the CPU and GPU run asynchronously. But, if you just want to take a screenshot of the current frame every once in a while, then glFinish (...) could be an acceptable practice.
Another thing to consider, if you are using double-buffered rendering is that you may be able to access the last fully rendered frame by reading the front-buffer. This is implementation specific behavior however, as some systems are designed to discard the contents of the front-buffer after the buffer swap operation, others make reading its contents an undefined operation. In any case, if you do attempt this solution, be aware that the image returned will have a 1 frame latency (however, the process of reading it will not require you to finish your current frame), which may be unacceptable.

AndroidOpenGL drawing loop and passing drawing information

I am using GLSurfaceView for my application's core drawing process.
I do my drawing onDrawFrame ( a function that gets called as often as possible, like thread loop ).
However when touch event occurs I use different class that manages all the user input.
Problem rises when the this "input manager" tried to talk to "draw manager".
my input manager tells draw manager a new set of coordinates (distorted shape, modified by the user) that draw manager should draw.
However since input manager passes this new drawing items very frequently I think this unexpected behavior occurs :
The shape that's being rendered flickers (disappears for a sec) or at some point, when "drawing manager" receives and tried to set a new buffer data, it throws out of index error(since I need to move buffer index as I read the buffer; if I try to set a new buffer data while I am reading also as well, it results in an unexpected behavior).
I lack experience to deal with this situation that I am facing. What will be a way to make sure "storing" and"reading" do not occur at the same time?

How to interrupt GLSurfaceView rendering phase and start a new one?

Hi Android Developers,
What is the best way to interrupt a current rendering phase of GLSurfaceView and start a new one when mode is equal to "Render_when_dirty"? I artificially stop rendering in "onDraw" method by checking a flag and returning from actual rendering method which is called in "onDraw" method; then, in main thread's context i call "requestRender()" to refresh the scene. However, due to a reason that i am not aware of, some of the intermediary old frames are displayed for a very very short period of time(on the other hand, they endure for so long period of time that users can realize the transition); before actual scene is rendered by opengl es 2.x engine. It doesn't affect anything at all; but troublesome to be fixed. What do you suggest?
P.S. Throwing InterruptedException within onDraw method is useless due to the destruction of actual rendering thread of GLSurfaveView.
Kind Regards.
When you say some of the old frames are drawn - do you mean part of the frame that is drawn is old or multiple calls of onDraw() still lead to some of the old information being shown on the display.
There are a few things I can see happening here. If you have a onDraw() like this:
onDrawFrame(){
... stuff ...
if (stateVariableSet)
return;
... stuff ...
my understanding is that when the function is done being run, that the back/front buffer get swapped and drawn. One thing that could be happening here is that you see a few calls of onDrawFrame() being rendered while you try to update the state/State variable.
On the other hand, if you have something like this:
onDrawFrame(){
... stuff..
semaphore.acquire(); // lock the thread waiting for the state to update
... stuff ...
then the things that have been drawn before the lock will be stale (for that frame only though - at least that's what I'd anticipate).
Also are you running on a multi-core system?

Android implement undo stack in drawing app

I started working on the sample Finger Paint app in the Android SDK to get more familiar with graphics. Lately I've been trying to implement undo/redo, and I've ran into road blocks every way I've tried. I've found a few threads about this, but none have gotten me past these issues. Here are my main 2 trials:
Strategy 1:
Save a stack of the paths (or canvases) and on undo clear the screen and redraw each path except the last one (or reinstate the most recent canvas).
The problem here is likely simple, but I just can't get the view to redraw anything. How do I draw saved paths (or restore a saved canvas)?
Strategy 2:
Save a stack of Bitmaps using getDrawingCache() after each touch. On undo, put the last bitmap back.
The saving has to be ran via post() from a runnable so it executes after onDraw() finishes (post adds it to the system message line after invalidate()). The issue is that when ran from the runnable getDrawingCache() always returns the initial version of the painting, like it can't see any changes after the first.
Why does getDrawingCache(), when called from a runnable, not see the current state of the view?
I've been fighting with this a while. Thanks.
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
Runnable r = new Runnable(){
#Override
public void run() {
myView.storeView();
}
};
myView.post(r);
}
public void storeView(){
historyCount++;
if(historyCount > historySize) historyCount = 6; //We don't want more than 6
history.add(Bitmap.createBitmap(myView.getDrawingCache()),historyCount);
}
For strategy 1, you need to define a data structure that represents all the information needed to render a part of the drawing. So, for instance, if you draw a segment of the drawing by tracking the user's touch position, define a data structure that consists of the current color, drawing shape, and an ArrayList of coordinates used generated by one user gesture. As you follow the user's touch, drawing to the screen, also append the touch coordinates to the ArrayList for the current gesture. When the touch ends, push the data structure onto the undo stack and wait for the next user gesture to create the next structure instance and start populating it.
If you have various gestures (fill, freehand trace, straight line, etc.), you can have a separate structure for each. They can all inherit from an abstract class so they can all go on the stack.
The second strategy strikes me as a horrible memory hog. I think we'd have to see your code to understand why it isn't working as intended.

best way to stop ondraw() opengl for a time

I use opengl to draw values from an array. The values are from the 3 Android sensors. I want to turn of the renderer during I collect the values. It seems that drawing and gathering at the same time have differenet results, so I want to turn off rendering while I gathering sensorvalues. At the moment I use a boolean in the ondraw() to stop drawing. But is there a better way to do this? There happen freaky things when I stop drawing , like the stuff I draw jittering a little bit some times, but it can't be because there are no drawing( ondraw(){ if(boolean){.........}}
Thank you so much for your help :-)
You can ask GLSurfaceView to only draw when asked via setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY), then call requestRender() when you're ready to draw.
I have a suggestion: Create a thread to collect the sensor data, and make it available as an object. This thread would gather all the data, and synchronize on the public value object. Blocking time would be minimized.
collectLocalData = getData();
synchronized(pubData) {
pubData.set(collectLocalData);
}
Your render thread can then Do the same when copying the public values for local consumption.
synchronized(pubData) {
renderLocalData.set( pubData );
}
renderData( renderLocalData );
If polling the sensors takes longer than rendering time, the same data will be rendered more than once. The animation might get a little twitchy, but no more so than what the data represents.
PS: your jitters may be a result of the data itself rather than your code.

Categories

Resources