How to freeze the last frame of an animation in a SurfaceView? - java

I'm drawing on a SurfaceView using a thread that has the following calssic run() method:
#Override
public void run() {
while (mRun) {
Canvas canvas = null;
synchronized (mSurfaceHolder) {
try {
canvas = mSurfaceHolder.lockCanvas();
update();
doDraw(canvas);
} finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
I wanted to save some work if the next frames that are about to be drawn from that moment on, are exactly the same as the previous one, so I skipped the onDraw, but than I got flickering issues due to the double buffering mechanism.
Then I went on and did something like this:
#Override
public void run() {
while (mRun) {
Canvas canvas = null;
synchronized (mSurfaceHolder) {
if (mIsAnimating) { // <---- Look ma! A flag!!!
try {
canvas = mSurfaceHolder.lockCanvas();
update();
doDraw(canvas);
} finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
}
If I turn off the mIsAnimating flag, thus stopping my run loop from locking the canvas and drawing on it, everything worked like a charm - I did some animation and after it was finished, I could freeze the drawing cycle and keep the animation's last frame on screen.
But then I had to go and see the following from Android docs:
The content of the Surface is never preserved between unlockCanvas()
and lockCanvas(), for this reason, every pixel within the Surface area
must be written. The only exception to this rule is when a dirty
rectangle is specified, in which case, non-dirty pixels will be
preserved.
So... how come my code works?

Your question is a good one. The SurfaceView is a strange animal.
First, Views, in general, don't use cached drawing by default (as far as I know, that is still true), and SurfaceView doesn't do anything to enable it. So I'm reasonably sure there's no caching going on.
What the SurfaceView is doing is cutting a hole in the view hierarchy's Window so that you can see another Window or something drawn directly on the frame buffer behind it. Android has added a non-existent Porter-Duff mode into their PorterDuff.Mode enum to accomplish this weird behavior. Because of this, the normal drawing rules don't apply to SurfaceView. Invalidating or requesting a layout simply cuts the hole again. The view hierarchy doesn't know or care anything about what's actually drawn in that area of the screen, so unless you specifically draw on it some other way (e.g., using the SurfaceHolder to procure a Canvas for that same area on a separate Window), there's a reasonable chance it just sits there untouched.
So what I really think is happening is that Android simply isn't redrawing that portion of the frame buffer. What was drawn there from the Surface stays there while the rest of the view hierarchy is drawn around it. The resources of the Surface very well could be deallocated and you would never know.
I'm curious what happens if you switch to a different Activity or the home screen and come back? My guess is that your last frame will no longer be there. If it is, then SurfaceView is even stranger than I am aware...
Edit: Just a brief note... a Window is basically a Surface with a little extra functionality. My habit of using the term Window interchangeably with Surface is because a Surface is basically ANativeWindow on the native side of things.

I think that one of two things are happening, first (very unlikely) you might be accidentally drawing the last frame of the animation repeatedly, IF NOT, then it means that your SurfaceView is not being invalidated by any change on the view hence keeping cached the last buffer of pixels showed, in order to test it, set a button on the screen and when the SurfaceView gets "frozen" tap on button and make the button itself gone, so you will force a redraw over all the elements on the screen, if the animation dissapears mean that it was actually being cached...
Regards!

You redraw only when your application surface changes. That works well. But - and that's what the Android document is refering to - any other App, Service, Toast or whatever can overwrite parts of your surface for a while. Take a Toast from a Service as example. Your App and your SurfaceView must update that part of the screen when the Toast disappears. But in your code you do not update as long as your flag is not set.

Related

LibGDX - setProjectionMatrix() "hiding" a Sprite

I am trying to draw a Sprite but it just appears for a frame when I first run my program, and then immediately disappears. I am working with a Stage, a Tiled map and two Batches, which I hope is not a problem.
It pretty much looked like the sprite was hiding behind something else, but I made completely sure that nothing was being drawn after it. So after a long time messing around, I found out that deleting the setProjectionMatrix() method "solved" my problem, since the sprite showed perfectly somehow.
I don't understand the reason why this happened at all, and I don't want to just delete the method and have sprites following the camera around, so:
Why or how would a setProjectionMatrix() method "hide" a Sprite? Is it altering the order in which my sprites are drawing? And most importantly, how do I fix it?
Here's my render method:
public void render(float delta) {
Render.cleanScreen(); //Render is a class i made with useful static stuff, like the Batch i am using.
//This method is pretty much just a Gdx.gl.glClearColor() method.
tmr.setView(camera); // tileMapRenderer
tmr.render();
b2dr.render(world, camera.combined); // Box2DDebugRenderer
stage.act();
stage.draw();
Render.batch.begin();
sprite2.draw(Render.batch); //The sprite i want to draw
Render.batch.end();
hudBatch.begin();
sprite1.draw(hudBatch); //This works fine
hudBatch.end();
Render.batch.setProjectionMatrix(camera.combined);
Gdx.input.setInputProcessor(stage);
}
Edit: Someone asked for extra info, so:
• This is what my game looks like with the setProjectionMatrix (after the first frame when the Sprite dissapears) and this is what it looks when i delete it. You can clearly see the red square(the Sprite).
• I am currently using a FitViewport.
Stage.draw() calls apply() on the Stage's Viewport, but never restores the OpenGL Viewport to what it was before. I'm guessing that your stage uses FitViewport, so it is cropping part of the screen. If you want different viewport behavior for stuff you draw outside your stage, you should create a separate viewport for that, and call viewport.apply() before you start rendering the other stuff.
Side note: if you are lazy loading anything that uses native memory (like SpriteBatch and Texture, things implementing Disposable) and storing a reference in a static variable (your Render class), make sure you dispose all of it and null it out in your game's destroy() method. Otherwise, on Android you will be leaking that stuff and it will fail to work when you reopen your game.
Another side note: In my opinion, the Sprite class should not be used unless you have hundreds of them and they aren't moving much (in which case it might have slightly better performance). Sprite is a weird conflation of an asset (a TextureRegion) and game state data. As a result, it can create design issues that you have to work around, such as when you want to animate it or flip it. It is better to create your own GameObject class that references a TextureRegion (a single instance of which can be shared by many elements), and has its own variables for position, color, rotation, etc. Then draw it with the appropriate SpriteBatch.draw method using those properties. In my game, I have a wrapper interface around TextureRegion that can alternately wrap Animation<TextureRegion> so my GameObject class can use either and it can be swapped easily. This makes the code more flexible to change.
So... I just realized my coworker accidentally deleted the viewport.update() method.
#Override
public void resize(int width, int height) {
viewport.update(width, height);
}

Is LibGDX SpriteBatch draw() smart enough not to redraw 100% exactly same sprite at each render() call?

Is LibGDX SpriteBatch draw() smart enough not to redraw 100% exactly same sprite at each render() call?
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if (shallFadeOut) { // becomes true when sound message finishes
doFadingOut();
showNextScreen();
dispose();
} else {
batch.begin();
// introSprite is a static image - it never changes a pixel (splash screen)
// I need to paint it once and then just wait until sound message finishes
// DON'T NEED TO REDRAW IT EVERY render() cycle 60 times/sec (~60 fpm)
introSprite.draw(batch);
batch.end();
}
}
In OpenGL, you typically redraw the whole screen on every frame. It doesn’t make sense to wonder if the batch is smart enough to avoid redrawing the same thing, because it has to redraw it to prevent it from disappearing.
If you want to avoid redrawing anything on the screen for a while to save battery on the device, you can use GDX.graphics.setContinuousRendering(false) but that means your render() method will stop getting called so you must set it back to true using a timer or input callback.
You could alternatively use a Boolean to decide whether to clear the screen with glClear and draw stuff, but under the hood, LibGDX will still be requesting OpenGL to copy the screen buffer data between the back buffer and the screen buffer.
Not that drawing a single sprite is super trivial and probably not worth a second of thought about optimizing it.
I am afraid that I do not think any accounting is done by the Batch between batching render steps to see if a texture was already loaded and is ready to be rendered, but the texture itself might.
The batching is intended for cases where you are drawing duplicates of the same thing in the same render step so that you are not unnecessarily context switching between sprites and having to reload data to your graphics card.
The batch is as I am sure you know defined by the begin and end calls you are using, and it depends on you actually rendering all of the sprites of a particular type one after the other without jumping to render something else. This can be seen int he batch draw code here, but for readability the relevant lines are below:
Texture texture = region.texture;
if (texture != lastTexture) {
switchTexture(texture);
} else if (idx == vertices.length) {
flush();
}
To take advantage of the batch - you should be grouping the rendering of all your sprite types, and between steps, the batch does not offer any efficiencies by design as far as I can tell.
But, between batching steps or game loops, when a texture is bound, it uses a fixed target and handle as can be seen here. This means that if all you are drawing is a single sprite for several render loops, libgdx should be using your memory and graphics card as efficiently as can be expected.

How to copy a complicated ViewGroup to a bitmap?

Can not make a bitmap from a complicated view with state machine.
I have a RelativeLayout in which a dynamic tree of views handles touchevents to draw graphics. It uses a state machine to keep track of events like down, move and up and perform various drawing activities.
Now I want to make a copy (bitmap) of this RelativeLayout every time the view changes and display on the second screen. I have tried both methods I found online:
draw(theSecondCanvas);
Bitmap cache = getDrawingCache();
Both work most of time except occasional failure. The problem is both methods will eventually call every child views' draw() one more time to draw on the second canvas. But the state machine has changed to the different state based on the last touch event. It gives different drawing result or sometime error with null object reference because the additional drawing request has no touch event associated with it.
One option is for me to fix the complicated logic of state machine in the tree of views so it can handle an additional stateless drawing on the second canvas.
But I would think it has to be a simpler way to capture a bitmap from a view without drawing everything again. The view has done all the drawing inside already. It doesn't make sense to repeat the same work on the second canvas just to get a copy of bitmap.
Any suggestions? Thanks in advance!
I realized that off-screen rendering is not necessary in my case. I can intercept default canvas and draw on my own canvas with a bitmap. Then copy the bitmap to the default canvas. This solves the problem.

How do I draw adjacent bitmaps to a canvas in Android?

I've spent all night trying to figure out why I can't draw a series of bitmaps completely adjacent to each other (with no gaps in between), using Android.
For context, I am building a spectrogram application which displays a vertical bitmap for each 'window' of audio data that comes in, providing the user with a heatmap of frequencies. At the moment I'm using pre-recorded audio so I can perform all my calculations before I have to display anything - I have an ArrayList of integer arrays, each of which represents one window's bitmap, which is drawn to a canvas using a timer thread.
I am aware that the approach below will ultimately break when the application tries to draw past the dimensions of the screen, but I am not worrying about that for now. The problem I would like to solve is that the below code results in a one-pixel (ish) gap between the drawn bitmaps, when I would actually like them to be absolutely adjacent.
This is the run() method for my timer thread:
public void run() {
Canvas c = null;
try {
c = sh.lockCanvas(null);
synchronized(sh) {
doDraw(c);
}
} finally {
if (c!=null) {
sh.unlockCanvasAndPost(c);
}
}
}
This is the doDraw() method which draws the bitmaps, and then skips along to the end of that drawn bitmap in order to draw the next one. It simply does so by incrementing the 'windowsDrawn' field:
private void doDraw(Canvas canvas) {
canvas.drawBitmap(spec.getBitmapWindow(windowsDrawn), 0, 1, windowsDrawn, 0, 1, h, false, null);
System.out.println("Windows drawn: "+windowsDrawn);
windowsDrawn++;
}
spec.getBitmapWindow(windowsDrawn) simply returns an integer array of pixel values for the vertical window to be drawn.
Here's a screenshot to show what I'm talking about.The image looks as if it is behind tiny prison bars and I would like to get rid of these.
Thanks!
I found out what I was doing wrong. The 'prison bars' effect was actually a side-effect of me writing incremental updates to the back-buffer, which was presumably being flipped every so often (hence losing some of my updates to the other buffer, giving the black vertical lines). The problem was solved by ensuring that I was instead writing to a buffer bitmap and then redrawing the entire frame each time. Incremental updates to the screen (like I was trying to do) are not allowed in Android.

Drawing a bitmap-animation doesn't work

I got the following problem with drawing an animation on a surfaceview in android:
I use a thread to make the onDraw()-method get called regulary, and i use this:
c.drawBitmap(pic, partOfTheImage, destination, null);
to draw the part of the spritesheet i want to draw.
The problem now is that altough i change "partOfTheImage", the only thing thats drawn is what was drawn first. It doesnt change, no animation.
Here is the code in the thread:
try {
c = getGw().getHolder().lockCanvas();
synchronized (getGw().getHolder()) {
getGw().onDraw(c);
}
} finally {
if (c != null) {
getGw().getHolder().unlockCanvasAndPost(c);
}
}
Does anyone know why my Bitmap isnt updating?
Are you sure it's actually drawing? What does getGw() do? I don't think just calling onDraw() will work. Instead, you should call invalidate() on the view. Invalidate will cause the view to be redrawn at some point in the future. Android has a rich set of animation tools. Have you taken a look at them?

Categories