androidopengl-es-2.0glreadpixelsarcoregles20

How can i run GLES20.glReadPixels on separate thread in android?


I am currently working with ARCore to classify images and put objects on the images. But it seems like ARCore camera doesn't provide any way to get the pixelbuffer. then i came accross How to take picture with camera using ARCore and according this we can copy the frame from OpenGL using GLES20.glReadPixels. if i pass each frame at a time my classifier works fine but when i put GLES20.glReadPixels to get the pixel buffer in a separate thread i am getting all zeros. so basically it gives me black image. So is there a way to run GLES20.glReadPixels on a separate thread.


Solution

  • OpenGL, and for Android platform OpenGL ES was designed to be a single thread oriented library. This does not mean that you can not work with multiple threads with OpenGL ES, but this is not a standard way to work with OpenGL.

    In my experience, I needed to load texture asynchronously. To do this it is needed to create an OpenGL context in another thread. Note that no every device supports the capability to create two OpenGL contexts. I create the following class to manage async texture loading. I think you can easily convert this to your needs.

    public class AsyncOperationManager {
    
        public boolean asyncMode;
    
        public interface AsyncTextureInfoLoader {
            TextureInfo load(Texture texture);
        }
    
        public class TextureLoaderThread extends Thread {
            public TextureLoaderThread() {
                super("GLThread-AsyncOperation");
            }
    
            public Handler handler;
    
            @SuppressLint("HandlerLeak")
            public void run() {
                Looper.prepare();
    
                int pbufferAttribs[] = { EGL10.EGL_WIDTH, 1, EGL10.EGL_HEIGHT, 1, EGL_TEXTURE_TARGET, EGL_NO_TEXTURE, EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE, EGL10.EGL_NONE };
    
                surfaceForTextureLoad = egl.eglCreatePbufferSurface(display, eglConfig, pbufferAttribs);
                egl.eglMakeCurrent(display, surfaceForTextureLoad, surfaceForTextureLoad, textureContext);
    
                handler = new Handler() {
                    public void handleMessage(Message msg) {
                        MessageContent content = (MessageContent) msg.obj;
                        long start2 = Clock.now();
                        Logger.debug("context switch for async texture load stopped ");
    
                        long start1 = Clock.now();
                        Logger.debug("async texture load stopped ");
                        content.texture.updateInfo(content.execute.load(content.texture));
                        Logger.debug("async texture load ended in %s ms", (Clock.now() - start1));
    
                        Logger.debug("context switch for async texture load ended in %s ms", (Clock.now() - start2));
    
                        if (content.listener != null)
                            content.listener.onTextureReady(content.texture);
                    }
                };
    
                Looper.loop();
            }
        }
    
        final static int EGL_TEXTURE_TARGET = 12417;
        final static int EGL_NO_TEXTURE = 12380;
        final static int EGL_TEXTURE_FORMAT = 12416;
    
        private static AsyncOperationManager instance = new AsyncOperationManager();
    
        public static AsyncOperationManager instance() {
            return instance;
        }
    
        private EGLContext textureContext;
        private EGL10 egl;
        private EGLDisplay display;
        private EGLConfig eglConfig;
        protected EGLSurface surfaceForTextureLoad;
        private TextureLoaderThread textureLoaderThread;
    
        public AsyncOperationManager() {
        }
    
        public void init(EGL10 egl, EGLContext renderContext, EGLDisplay display, EGLConfig eglConfig) {
            // la versione usata è la 2!
            int[] attrib_list = { XenonEGL.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
    
            this.egl = egl;
            this.display = display;
            this.eglConfig = eglConfig;
    
            textureContext = egl.eglCreateContext(display, eglConfig, renderContext, attrib_list);
    
            if (textureContext != EGL10.EGL_NO_CONTEXT) {
                Logger.info("Context for async operation asyncMode.");
                asyncMode = true;
                // creiamo il thread per le operazioni async su opengl
                textureLoaderThread = new TextureLoaderThread();
                textureLoaderThread.start();
            } else {
                asyncMode = false;
                Logger.fatal("Try to enable context for async operation, but failed.");
            }
        }
    
        public int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
    
        public void init(android.opengl.EGLContext renderContext, android.opengl.EGLDisplay display, android.opengl.EGLConfig eglConfig) {
            // la versione usata è la 2!
            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
    
            if (textureContext != EGL10.EGL_NO_CONTEXT) {
                Logger.info("Context for async operation asyncMode.");
                asyncMode = true;
                textureLoaderThread = new TextureLoaderThread();
                textureLoaderThread.start();
            } else {
                asyncMode = false;
                Logger.fatal("Try to enable context for async operation, but failed.");
            }
        }
    
    
        public boolean destroy(EGL10 egl) {
            return egl.eglDestroyContext(display, textureContext);
        }
    
        public boolean destroy() {
            return false;
        }
    
        public class MessageContent {
            public MessageContent(Texture textureValue, AsyncTextureInfoLoader executeValue, TextureAsyncLoaderListener listenerValue) {
                texture = textureValue;
                execute = executeValue;
                listener = listenerValue;
            }
    
            public Texture texture;
    
            public AsyncTextureInfoLoader execute;
    
            public TextureAsyncLoaderListener listener;
        }
    
        public boolean isEnabled() {
            return asyncMode;
        }
    
        public TextureInfo load(final Texture texture, final AsyncTextureInfoLoader execute, final TextureAsyncLoaderListener listener) {
            if (asyncMode) {
                MessageContent content = new MessageContent(texture, execute, listener);
                Message msg = textureLoaderThread.handler.obtainMessage(25, content);
                textureLoaderThread.handler.sendMessage(msg);
    
                return null;
            } else {
                Logger.error("async operations on textures are disabled! This device support multiple opengl context?");
                Logger.warn("run texture update in single thread!");
                execute.load(texture);
                if (listener != null)
                    listener.onTextureReady(texture);
    
                return texture.info;
            }
        }
    
        public void init() {
            asyncMode = false;
        }
    }
    

    For more information about this argument, i suggest you to read: