ARCore without GLSurface.Renderer and in Foreground Services
SPECIFIC ISSUE ENCOUNTERED
Following up on #1259, #360 #31 #259, it seems that there's a few use cases for getting the motion tracked position of the phone without needing to render the camera image. Currently it seems that a GLContext is required but there's no good documentations or examples as to how this would be done without the use of GLSurface.Renderer (I'm guessing this is not a typical use).
I'm in the process of building an app that would relay the position of a phone to a VR headset to allow of phone tracking after a fixed point calibration. I need the ability to generate the pose from a Foreground service meaning I won't have access to the GLSurface in order to render the image and have the relevant GL context.
I've seen a few recommendations to just rip out the GLThread from GLSurfaceView. But I'm not fully sure what that means or how to do that. When following #360 I get a similar com.google.ar.core.exceptions.MissingGlContextException
Any guidance as to how this could be done would be greatly appreciated (Documentation or examples as so far I haven't found too much)
This is currently what my code looks like
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLSurfaceView;
import android.opengl.EGLConfig;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.google.ar.core.Session;
import com.google.ar.core.exceptions.CameraNotAvailableException;
public class TrackingForegroundService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Thread mGLThread = new CustomGLThread();
mGLThread.start();
// setup notification
Log.d("Foreground Service", "Starting");
final String CHANNELID = "Foreground Service ID";
NotificationChannel channel = new NotificationChannel(
CHANNELID,
CHANNELID,
NotificationManager.IMPORTANCE_LOW
);
getSystemService(NotificationManager.class).createNotificationChannel(channel);
Notification.Builder notification = new Notification.Builder(this, CHANNELID)
.setContentText("Service is running")
.setContentTitle("Service enabled")
.setSmallIcon(R.drawable.ic_launcher_background);
startForeground(1001, notification.build());
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
static class CustomGLThread extends Thread {
private static EGLDisplay mEGLDisplay;
private static boolean mGLContextCreated;
private static android.opengl.EGLConfig[] mEGLConfig;
private static EGLSurface mEGLSurface;
private static EGLContext mEGLContext;
Session session;
@Override
public void run() {
setName("CustomGLThread" + getId());
try {
guardedRun();
} catch (InterruptedException e) {
// fall thru and exit normally
} finally {
Log.e("Thread", "Thread");
// sGLThreadManager.threadExiting(this);
}
}
private void guardedRun() throws InterruptedException {
while (true) {
Log.e("Service", "Running Thread");
try {
InitGLContext();
Frame frame = session.update();
Camera camera = frame.getCamera();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (CameraNotAvailableException e){
//done
}
}
}
private static boolean InitGLContext() {
if (mGLContextCreated) {
Log.e("GL", "Created");
return true;
}
Log.v("GL", "init GL context");
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
Log.e("GL", "gl context init fail! unable to open connection to local window system");
return false;
}
int[] eglMajVersion = new int[10];
int[] eglMinVersion = new int[10];
if (!EGL14.eglInitialize(mEGLDisplay, eglMajVersion, 0, eglMinVersion, 0)) {
Log.e("GL", "unable to initialize EGL");
return false;
}
int[] confAttr = {
EGL14.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT,// very important!
EGL14.EGL_SURFACE_TYPE,
EGL14.EGL_PBUFFER_BIT,//EGL_WINDOW_BIT EGL_PBUFFER_BIT we will create a pixelbuffer surface
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,// if you need the alpha channel
EGL14.EGL_DEPTH_SIZE, 8,// if you need the depth buffer
EGL14.EGL_STENCIL_SIZE, 8,
EGL14.EGL_NONE};
EGLConfig[] mEGLConfig = new EGLConfig[1];
int[] numConfig = new int[1];
if (!EGL14.eglChooseConfig(mEGLDisplay, confAttr, 0, mEGLConfig, 0, 1, numConfig, 0)) {
Log.e("GL", "some config is wrong");
return false;
} else {
Log.v("GL", "all config is ok");
}
int[] surfaceAttr = {
EGL14.EGL_WIDTH, 640,
EGL14.EGL_HEIGHT, 480,
EGL14.EGL_NONE
};
mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig[0], surfaceAttr, 0);
if (mEGLSurface == EGL14.EGL_NO_SURFACE) {
switch (EGL14.eglGetError()) {
case EGL14.EGL_BAD_ALLOC:
// Not enough resources available. Handle and recover
Log.e("GL", "Not enough resources available");
break;
case EGL14.EGL_BAD_CONFIG:
// Verify that provided EGLConfig is valid
Log.e("GL", "provided EGLConfig is invalid");
break;
case EGL14.EGL_BAD_PARAMETER:
// Verify that the EGL_WIDTH and EGL_HEIGHT are
// non-negative values
Log.e("GL", "provided EGL_WIDTH and EGL_HEIGHT is invalid");
break;
case EGL14.EGL_BAD_MATCH:
// Check window and EGLConfig attributes to determine
// compatibility and pbuffer-texture parameters
Log.e("GL", "Check window and EGLConfig attributes");
break;
}
return false;
}
int[] contextAttr = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,// very important!
EGL14.EGL_NONE
};
mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig[0], EGL14.EGL_NO_CONTEXT, contextAttr, 0);
if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
if (EGL14.EGL_BAD_CONFIG == EGL14.eglGetError()) {
Log.e("GL", "EGL_BAD_CONFIG");
}
return false;
}
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
Log.e("GL", "make current fail");
return false;
}
Log.v("GL", "GL initialize succeed");
mGLContextCreated = true;
return true;
}
}
}
VERSIONS USED
- Android Studio: Bumblebee 2021.1.1 Patch 3
- ARCore SDK for Android: 33-rc3
- Device manufacturer, model, and O/S: Google Pixel 4a
@pablovela5620 Hey, did you find a solution to your issue? I am facing a similar situation.