I'm creating a game with the Android NDK and OpenGLES (and not using NativeActivity / native_app_glue).
When the user rotates their phone, I don't want the whole activity to be destroyed and restarted, so I've added the necessary part to the manifest file:
android:configChanges="keyboardHidden|keyboard|orientation|screenSize"
and get an onConfigurationChanged() callback as expected. I also get a surfaceRedrawNeeded() callback, which is fine.
However, I would also expect to get a surfaceChanged() callback, but this never happens.
The java code looks like this:
public class MyActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
surface = new MySurface();
getWindow().takeSurface(surface);
view = new MyView(this);
getWindow().setContentView(view);
... native setup
}
...
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
...
}
...
}
class MyView extends View
{
public MyView(Context context)
{
super(context);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) { ... }
... other input stuff
}
class MySurface implements SurfaceHolder.Callback2
{
#Override
public void surfaceCreated(SurfaceHolder holder) { ... }
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { ... }
#Override
public void surfaceRedrawNeeded(SurfaceHolder holder) { ... }
#Override
public void surfaceDestroyed(SurfaceHolder holder) { ... }
...
}
Questions:
Should I be getting a surfaceChanged() callback in the surface when the orientation changes?
If not, I guess I have two options:
Rotate my camera and adjust the viewport width / height (not really something I want to keep track of).
Call ANativeWindow_setBuffersGeometry (would this actually give me a rotated display?) and / or eglCreateWindowSurface again? Would I then need to recreate my EGLContext (and reload all my OpenGL stuff... ick)?
So in general, what's the "correct" way of handling orientation changes with regard to the ANativeWindow, EGLSurface and EGLContext?
Related
I am facing some issues when orientation is changed from Landscape to Portrait or vice versa.
I have a scenario whether the orientation needs to be changed only in tablets not in phone. Means in Phone, the view needs to be locked and in Tablet, the view will be changed depending on the orientation.
Till now what I have done is I have created a class which extends Application
public class BaseApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
#Override
public void onActivityPreCreated(#NonNull Activity activity, #Nullable Bundle savedInstanceState) {
if (UIHelper.NeedLandscapeConstraint(activity)) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
} else {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
#Override
public void onActivityCreated(#NonNull Activity activity, #Nullable Bundle bundle) {
}
#Override
public void onActivityStarted(#NonNull Activity activity) {
}
#Override
public void onActivityResumed(#NonNull Activity activity) {
}
#Override
public void onActivityPaused(#NonNull Activity activity) {
}
#Override
public void onActivityStopped(#NonNull Activity activity) {
}
#Override
public void onActivitySaveInstanceState(#NonNull Activity activity, #NonNull Bundle bundle) {
}
#Override
public void onActivityDestroyed(#NonNull Activity activity) {
}
});
}
}
and in UIHelper.java I have a method which returns the type of device
public static boolean NeedLandscapeConstraint(Activity activity) {
Pair<Double, Double> screenMetrics = UIHelper.GetScreenMetrics(activity);
if (screenMetrics.first > 3.5 && screenMetrics.second > 3.5) {
// Tablet
return true;
}
return false;
}
In the AndroidManifest.xml, if I android:screenOrientation="unspecified"then everything is working fine but I cannot lock the view as portrait in Phone and if I specify android:screenOrientation="portrait" Phone view is working fine but in Tablet Portrait is loading after that the orientation gets changed to Landscape for all the Activity.
I am not getting how to solve this issue. Can anyone help me out.
Thanks in advance
In the original (now deprecated) camera API, we used to be able to get preview frames in the Camera.PreviewCallback and be able to process it (taking possibly very long) and release the buffer to be able to receive another frame, without lagging the screen preview, with some code like the following:
public void onPreviewFrame(final byte[] data, Camera camera) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
(... do some slow processing ...)
}
#Override
protected void onPostExecute(Void aVoid) {
mCamera.addCallbackBuffer(data); // free the buffer to be able
// to process another frame
}
}.execute();
}
The API would only callback with a new frame if there was another buffer available to receive it, without lagging the screen preview.
I'm trying to replicate the same behaviour on the new Camera2 API, but I can't find a way to do it without lagging the screen preview. If I add a second target (same resolution as the screen one, YUV_420_888) to the preview request:
mPreviewRequestBuilder.addTarget(surface);
mPreviewRequestBuilder.addTarget(previewImageReader.getSurface());
mCameraDevice.createCaptureSession(
Arrays.asList(surface, previewImageReader.getSurface()), ...
the screen preview will lag, even if I just close the image as soon as I get it:
#Override
public void onImageAvailable(ImageReader reader) {
reader.acquireNextImage().close();
}
What's the correct way to use Camera2 to emulate the original camera API behaviour (i.e having a new buffer whenever one is free and not slowing the screen preview)?
Update: In case anyone is wondering how the rest of the code looks like, it is just a modified version of the standard android-camera2Basic sample, here's what I've changed.
If anyone is still interested.
Create a SurfaceTextureListener and call your async function from the onSurfaceTextureUpdated method. I have used this successfully when checking frames for barcodes with the BarcodeDetection API and the Camera 2 API.
Here is a sample of an async function launched from the onSurfaceTextureUpdated method. If you only want to run one async task in the background at a time, you can use a flag to check if the previous task has completed.
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
boolean processing;
#Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
if (processing) {
return;
}
processing = true;
Bitmap photo = mTextureView.getBitmap();
new ImageTask(photo, new ImageResponse() {
#Override
public void processFinished() {
processing = false;
}
}).execute();
}
};
private interface ImageResponse {
void processFinished();
}
private class ImageTask extends AsyncTask<Void, Void, Exception> {
private Bitmap photo;
private ImageResponse imageResponse;
ImageTask(Bitmap photo, ImageResponse imageResponse) {
this.photo = photo;
this.imageResponse = imageResponse;
}
#Override
protected Exception doInBackground(Void... params) {
// do background work here
imageResponse.processFinished();
return null;
}
#Override
protected void onPostExecute(Exception result) {
}
}
I am quite new to Android and OpenGL ES. I have to create a GUI in OpenGL and I would like to use it as a Fragment in the main activity. In order to learn how to do this, I tried 2 tutorials - this Fragment tutorial and the Android developer tutorial on OpenGL ES.
But still I don't understand how exactly do I include an OpenGL view in a Fragment. OpenGL doesn't use XML layout files so this process is quite confusing for me. I would like to do something like this: inside the main activity from the Fragment tutorial I want to include a third Fragment with OpenGL. Go easy on me I am a beginner :)
If the developer tutorial is anything to go by, then the following setup would work:
Activity:
public class MainActivity extends FragmentActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0)
{
finish();
}
}
});
if (savedInstanceState == null)
{
getSupportFragmentManager().beginTransaction().add(R.id.main_container, new OpenGLFragment()).addToBackStack(null).commit();
}
}
}
Activity XML (activity_main.xml):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Fragment:
public class OpenGLFragment extends Fragment
{
private GLSurfaceView mGLView;
public OpenGLFragment()
{
super();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
mGLView = new MyGLSurfaceView(this.getActivity()); //I believe you may also use getActivity().getApplicationContext();
return mGLView;
}
}
And I guess you need to make your own GLSurfaceView as the tutorial says:
class MyGLSurfaceView extends GLSurfaceView {
public MyGLSurfaceView(Context context){
super(context);
setEGLContextClientVersion(2);
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(new MyRenderer());
}
}
And as the tutorial says, make your renderer:
public class MyGLRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
public void onDrawFrame(GL10 unused) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
}
I've seen lots of examples using TextureView in a main Activity but I'm trying to put it into a Fragment.
I've created the simplest example possible and its onCreateView is being called, onActivityCreated as well but onSurfaceTextureAvailable isn't being called after passing back the TextureView.
What am I missing ?
Thanks
G
public class FullscreenActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
}
}
public class TextureViewFragment extends Fragment
implements TextureView.SurfaceTextureListener {
private TextureView mTextureView;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mTextureView = new TextureView(getActivity());
mTextureView.setSurfaceTextureListener(this);
mTextureView.setOpaque(false);
return mTextureView;
}
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// TODO Auto-generated method stub
return false;
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// TODO Auto-generated method stub
}
}
activity_fullscreen.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
tools:context=".FullscreenActivity" >
<fragment class="com.example.test.TextureViewFragment"
android:id="#+id/graphTextureView"
android:layout_width="0px" android:layout_height="match_parent" />
</FrameLayout>
This is because the TextureView must have a nonzero size and be visible. Based on the comment, the layout_width was set to 0px. Change that to a nonzero value.
(Old question but posting answer for posterity)
You should add 'android:hardwareAccelerated=true' to your AndroidManifest.xml.
TextureView needs hardware acceleration.
Your TextureView should be in a layout xml file. Instead of using new to instantiate it you should get it using something like this:
View view = inflater.inflate(R.layout.layout_video, container, false);
mTextureView = (TextureView) view.findViewById(R.id.video_texture_view);
Then in your layout_video.xml file you need a TextureView with id video_texture_view
im trying to make a simple game with Gles on android.
i've googled a lot for a solution but i couldnt find anyone mentioning how to implement ontouchevent in GLES.
here i how i implemented it so far... i know it's a silly way to do it :D but that was only way i could come up to set positionx variable free while there is no tapping
a brief summary of my Renderer Class...
public class GLmain implements Renderer {
public float mX,mY;
public boolean clicked;
public GLmain()
{
clicked=false;
}
public void onDrawFrame(GL10 gl) {
if(clicked)
{
positionx=mX;
positiony=mY;
clicked=false
}
}
}
in and Activity class
public class Alpha extends Activity {
private GLSurfaceView glSurface;
public float mousex,mousey;
public GLmain glrend=new GLmain();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glSurface = new GLSurfaceView(this);
glSurface.setRenderer(glrend);
setContentView(glSurface);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
glrend.clicked=true;
glrend.mX=event.getX();
glrend.mY=event.getY();
return true;
}}
It's not a silly way to do it! You can't call directly into the Renderer because it's on a different thread so you need to have the Renderer poll for the information when it needs it.