I'm currently writing an Android app for one my customers that has a GLSurfaceView with a GLSurfaceView.Renderer.
My entire OpenGL stuff works just fine (it's basically a port of what another developer has written on iOS first). Except for one thing.
When the view is loaded and thus the OpenGL stuff is getting loaded my background quickly flashes black, and then the OpenGL starts to render correctly (with my background). So what I do is:
In the onSurfaceCreated I start with this:
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.9f + 0.1f * (21.0f / 255),
0.9f + 0.1f * (36.0f / 255),
0.9f + 0.1f * (31.0f / 255), 1.0f);
// Here goes my other stuff, if I comment all my other stuff out I still get the flash at startup
}
In my onDrawFrame method I do this:
#Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
// My stuff, again, if I comment all this stuff out I still get the flash...
}
So if I remove all lines of code except for the glClearColor(..) in onSurfaceCreated I still see a black flash before my actual background color is set. If I only remove the glClearColor(..) from my code (and thus leave all other OpenGL stuff in place) everything is rendered on a black background.
What I would like to see is that I just get rid of the black flash and thus that my background color is initialised correctly at startup...
Any ideas how I can achieve that?
Dirk
I just hit the same problem and have worked around it by using the GLSurfaceView's background property. Here's my XML :
<view class="android.opengl.GLSurfaceView"
android:id="#+id/glview"
android:background="#drawable/window_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Where window_bg is the same drawable as the window background specified in the activity theme.
Having done this you then remove the background drawable in the first call to onDrawFrame(), using a boolean to track whether it's already been done or not :
boolean initialRenderHack;
//
// GLSurfaceView.Renderer
//
#Override
public void onDrawFrame(GL10 gl10) {
// ... drawing code goes here ...
// Remove the initial background
if (!initialRenderHack) {
initialRenderHack = true;
view.post(new Runnable() {
#Override
public void run() {
view.setBackgroundResource(0);
}
});
}
Note that you can only touch a View's background property from the UI thread, not the rendering thread that onDrawFrame() runs on, hence the need to post a runnable.
On Android 4.4 this gives a perfectly fluid startup with no horrible jarring black frame. I haven't yet tried it with older Androids.
Related
I am writing a game for Android using the Java for the user interface and C++ with the NDK for the render thread. It uses Vulkan if available and otherwise uses OpenGL ES. This bug occurs in OpenGL ES and Vulkan.
If I pause the app and quickly resume it before either onPause, onStop, or SurfaceHolder.Callback.surfaceDestroyed completes, then there will be an afterimage of what was shown on the surface immediately before the render thread stops. The afterimage behaves like a background to any new thing I draw. When performing the render pass I set the clear color to: black with 0.0 for alpha. I do this so that the app's background will be the background for the game. If I set the alpha value to 1.0, then the symptoms of this bug will not present themselves.
This problem can be viewed as a generalization of the problem described in:
phantom images after going from split screen to full screen
But the solution to that problem (i.e. avoiding shutting down the app) will not work for this more general case.
I am stopping the render thread during onPause or SurfaceHolder.Callback.surfaceDestroyed, which ever one happens first. These functions will not return until the render thread is joined (if there is one).
If I clear the screen by using an empty render pass with the clear color being black and 0.0 for alpha (in Vulkan) or by calling glClear (in OpenGL ES) before the render thread shuts down, then the bug does not manifest. But this stinks of something being done wrong with the surface (or perhaps some sort of race condition). I am worried that this solution just hides the bug instead of fixing it by freeing the resources associated with the afterimage. Is there something else I need to do to ensure that the surface is reset correctly?
Beginning the render pass (vulkan):
std::array<VkClearValue, 2> clearValues = {};
clearValues[0].color = {0.0f, 0.0f, 0.0f, 0.0f};
clearValues[1].depthStencil = {1.0, 0};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
During initialization in OpenGL ES:
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
clearing the screen in the drawing loop (OpenGL):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
In the render thread, this is the set up and tear down of the ANativeWindow. I checked in the debugger that the shared pointer deleter was actually called:
ANativeWindow *window = ANativeWindow_fromSurface(env, jsurface);
if (window == nullptr) {
notify->sendError("Unable to acquire window from surface.");
return;
}
auto deleter = [](WindowType *windowRaw) {
/* release the java window object */
if (windowRaw != nullptr) {
ANativeWindow_release(windowRaw);
}
};
std::shared_ptr<WindowType> surface(window, deleter);
in onPause and SurfaceHolder.Callback.surfaceDestroyed:
if (drawer == null) {
return;
}
try {
Draw.tellDrawerStop();
drawer.join();
} catch (InterruptedException e) {
}
drawer = null;
The following is done in onCreate to make the SurfaceView see through:
SurfaceView drawSurfaceView = findViewById(R.id.drawingSurface);
drawSurfaceView.setZOrderOnTop(true);
SurfaceHolder drawSurfaceHolder = drawSurfaceView.getHolder();
drawSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
drawSurfaceHolder.addCallback(new MySurfaceCallback(this));
My Android App is an Open GL ES 2.0 App. For one particular scene, I am overlaying a few textViews on top of my GL Surfaceview along with the some OpenGL textured quads.
I need one of my textViews to 'flash' - I'm targeting Gingerbread, therefore, I can't use animations, so I've created a method which does this:
public void flashText(){
if(myText.getVisibility()==View.VISIBLE)
myText.setVisibility(View.GONE);
else
myText.setVisibility(View.VISIBLE);
}
Then, from my OpenGL thread, I do the following:
void updateLogic(
if (System.currentTimeMillis()>(flashTimer+250)){
flashTimer=System.currentTimeMillis();
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
activity.flashText();
}
});
}
}
The above method (updateLogic) is called 60 times a second. The timer is set to 250ms, so I get a 'flashing' animation 4 times a second, - or 4 times a second, FlashText is called via runOnUiThread.
This does work, however, it affects the animation of my openGL objects enough for it to be a problem.
My question is, is there a better way to do this? (because the method I'm using is clearly not efficient enough).
Summary
I'm trying to reliably remove a view from my layout, from my GL Rendering thread.
Details
I've got a splash screen which is added to my main layout, so my views are in this order:
GLSurfaceView
Splashscreen (which is a simple Android View)
All my objects and anything that can be loaded in the background is loaded from an AsyncTask, then, my GL Textures are loaded on the GL Thread (as is required), I'm then removing the Splashscreen from my layout to reveal my app's main menu screen.
90% of the time it works perfectly, (load time on my device is approx 3.5 seconds), however, occasionally, it appears to either never remove the view, or can take up to 8 seconds.
This is my current implementation
In my Activity class, I have the following method setup
public void dismissSplashScreen(){
removeSplash = new Runnable(){
public void run(){
//Is splashscreen currently visible?
if (splash.isShown()){
//Remove the splashscreen
layout.removeView(splash);
//Recycle splash screen as it's no longer required
recycle();
}
}
};
//Remove it
handler.post(removeSplash);
}
And then in my GLRenderer's onSurfaceCreated method, I have this:
public void onSurfaceChanged(GL10 gl, int deviceWidth, int deviceHeight) {
GLES20.glViewport(offsetX, offsetY, width, height);
Matrix.orthoM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
loadGLTextures();
//Everything is done! Dismiss the splash to reveal main menu
activity.dismissSplashScreen();
}
I also attempted to use runOnUIThread, but the results were the same, if not worse. Is my implementation correct here? What things can I check and is there a simpler way to remove this view from my layout once everything is done? (I can't dismiss it from the activity class directly as I have to wait for the GL textures to load on the GL thread).
Sorted.
The reason was because I am calling activity.dismissSplashScreen from onSurfaceChanged and onSurfaceChanged is being called multiple times (a common problem I gather and one which I've not been able to get to the bottom of yet).
So what I think was happening, was it was posting my runnable to the queue and then posting another before the first one had either started or completed, sometimes a third time.
So everything was getting a bit confused.
I simply added a boolean flag to guarantee that the runnable was posted only once like so:
if (!splashIsGone){
activity.dismissSplash();
splashIsGone = true;
}
I'm playing around with AndEngine. No docs available for reading, so I'm just shooting in the dark here.
Finally got a splash screen showing. Now I'm trying to add some transitions to it, but no luck here. Here's the code:
#Override
public void onLoadComplete() {
mHandler.postDelayed(fadeAway, 2500);
}
protected Runnable fadeAway = new Runnable() {
#Override
public void run() {
// The only child of the scene is our splash sprite
scene.getLastChild().registerEntityModifier(new SequenceEntityModifier(
new ScaleModifier(2500, 100.0f, 200.0f),
new RotationModifier(2500, 0.0f, 78.0f),
new AlphaModifier(2500, 1.0f, 0.0f)
));
}
};
What happens is that the postDelayed() runs fine (waiting for 2.5 secs), but then everything goes black immediately. What I was expecting was that the splash screen should zoom in to 200%, then rotate 78 degrees, then fade out, but since everything goes black it feels that the duration of the modifiers is not working.
Is there an apparent error here?
EDIT: Alright, found the errors: 1) Apparently the pDuration (first argument) is supposed to be in seconds, not milliseconds as everywhere else 2) In ScaleModifier(), 1.0f equals the original size, so that argument is not in percent as expected.
(No flame, but I'm truly amazed how people managed to learn how to use this library without any documentation. There's not a single comment or note in the whole source code. Did people trial-and-error-reverse-engineered everything to find out how it's supposed to work? Can't believe the author put this vast amount of work for this library and never supplied any docs.)
Your errors is listed below:
All durations in AndEngine are in seconds.
Normal scale is 1.0f not 100.0f.
There is no need for Android Handler. You should delay your works in onUpdate methods of the engine or other AndEngine stuff.
You should apply animations only in update thread not anywhere else. mEngine.runOnUpdateThread(...)
I have a UI where the root layout is a RelativeLayout. It has a number of Views, such as fields, buttons, etc.
There are also two other panels that are initially invisible. When the user clicks a button, one of these panels slides in from the left, another slides in from the bottom. The problem is the frame rate is pathetic on a Nexus S.
I want to use setDrawingCacheEnabled(true) in order to (I hope) speed up the animation of these two panels. As a simplified example, here is roughly how I slide in one of these panels. I'll call it "detailPanel".
public class MyActivity extends Activity {
// Initially invisible.
private View detailPanel;
// A simple slide animation.
private Animation detailEnterAnimation;
...
public void someButtonClicked(View view) {
detailPanel.startAnimation(detailEnterAnimation);
detailPanel.setVisibility(View.VISIBLE);
detailPanel.setDrawingCacheEnabled(true);
}
}
Now in the DetailPanel I try to use the cache. I was hoping that bitmap rendering via the cache would improve performance:
public class DetailPanel extends LinearLayout {
public void draw(Canvas canvas) {
Bitmap cache = getDrawingCache();
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, null);
} else {
super.draw(canvas);
}
}
}
The Bitmap is non-null and is the right size, but it is completely black.
Why is my bitmap black? That code may be obscenely wrong; I've tried a million different things and just cannot figure it out.
From Android documentation (http://developer.android.com/reference/android/view/View.html#setDrawingCacheEnabled%28boolean%29):
Enabling the drawing cache is similar to setting a layer when hardware acceleration is turned off. When hardware acceleration is turned on, enabling the drawing cache has no effect on rendering because the system uses a different mechanism for acceleration which ignores the flag. If you want to use a Bitmap for the view, even when hardware acceleration is enabled, see setLayerType(int, android.graphics.Paint) for information on how to enable software and hardware layers.
Maybe this is your case?
You may want to build your drawing cache before using it, otherwise It will give you a blank bitmap
public void someButtonClicked(View view) {
detailPanel.startAnimation(detailEnterAnimation);
detailPanel.setVisibility(View.VISIBLE);
detailPanel.setDrawingCacheEnabled(true);
detailPanel.buildDrawingCache(); //may be you need to add this?
}