Converting from Glsurfaceview to Textureview (Via Gltextureview)

How to replace GLSurfaceView with TextureView in Android Ice Cream Sandwich?

A moderator deleted this answer so adding it back for posterity:

See Romain Guy's answer (Nov 23rd 2011) from the android-dev google group:

http://groups.google.com/group/android-developers/browse_thread/thread/539457146a401cf1 (mirrored: http://grokbase.com/t/gg/android-developers/11bqmgb7sw/how-to-replace-glsurfaceview-with-textureview-in-android-ice-cream-sandwich)

GLSurfaceView handles GL setup for you, which TextureView will not do.
A TextureView can be used as the native window when you create an EGL
surface. Here is an example (the interesting part is the call to
eglCreateWindowSurface()):

 @Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mRenderThread = new RenderThread(getResources(), surface);
mRenderThread.start();
}

private static class RenderThread extends Thread {
private static final String LOG_TAG = "GLTextureView";

static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
static final int EGL_OPENGL_ES2_BIT = 4;

private volatile boolean mFinished;

private final Resources mResources;
private final SurfaceTexture mSurface;

private EGL10 mEgl;
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
private EGLContext mEglContext;
private EGLSurface mEglSurface;
private GL mGL;

RenderThread(Resources resources, SurfaceTexture surface) {
mResources = resources;
mSurface = surface;
}

private static final String sSimpleVS =
"attribute vec4 position;\n" +
"attribute vec2 texCoords;\n" +
"varying vec2 outTexCoords;\n" +
"\nvoid main(void) {\n" +
" outTexCoords = texCoords;\n" +
" gl_Position = position;\n" +
"}\n\n";
private static final String sSimpleFS =
"precision mediump float;\n\n" +
"varying vec2 outTexCoords;\n" +
"uniform sampler2D texture;\n" +
"\nvoid main(void) {\n" +
" gl_FragColor = texture2D(texture, outTexCoords);\n" +
"}\n\n";

private static final int FLOAT_SIZE_BYTES = 4;
private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
private final float[] mTriangleVerticesData = {
// X, Y, Z, U, V
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
};

@Override
public void run() {
initGL();

FloatBuffer triangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
triangleVertices.put(mTriangleVerticesData).position(0);

int texture = loadTexture(R.drawable.large_photo);
int program = buildProgram(sSimpleVS, sSimpleFS);

int attribPosition = glGetAttribLocation(program, "position");
checkGlError();

int attribTexCoords = glGetAttribLocation(program, "texCoords");
checkGlError();

int uniformTexture = glGetUniformLocation(program, "texture");
checkGlError();

glBindTexture(GL_TEXTURE_2D, texture);
checkGlError();

glUseProgram(program);
checkGlError();

glEnableVertexAttribArray(attribPosition);
checkGlError();

glEnableVertexAttribArray(attribTexCoords);
checkGlError();

glUniform1i(uniformTexture, texture);
checkGlError();

while (!mFinished) {
checkCurrent();

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
checkGlError();

glClear(GL_COLOR_BUFFER_BIT);
checkGlError();

// drawQuad

triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);

triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
throw new RuntimeException("Cannot swap buffers");
}
checkEglError();

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// Ignore
}
}

finishGL();
}

private int loadTexture(int resource) {
int[] textures = new int[1];

glActiveTexture(GL_TEXTURE0);
glGenTextures(1, textures, 0);
checkGlError();

int texture = textures[0];
glBindTexture(GL_TEXTURE_2D, texture);
checkGlError();

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Bitmap bitmap = BitmapFactory.decodeResource(mResources, resource);

GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
checkGlError();

bitmap.recycle();

return texture;
}

private int buildProgram(String vertex, String fragment) {
int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
if (vertexShader == 0) return 0;

int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
if (fragmentShader == 0) return 0;

int program = glCreateProgram();
glAttachShader(program, vertexShader);
checkGlError();

glAttachShader(program, fragmentShader);
checkGlError();

glLinkProgram(program);
checkGlError();

int[] status = new int[1];
glGetProgramiv(program, GL_LINK_STATUS, status, 0);
if (status[0] != GL_TRUE) {
String error = glGetProgramInfoLog(program);
Log.d(LOG_TAG, "Error while linking program:\n" + error);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
glDeleteProgram(program);
return 0;
}

return program;
}

private int buildShader(String source, int type) {
int shader = glCreateShader(type);

glShaderSource(shader, source);
checkGlError();

glCompileShader(shader);
checkGlError();

int[] status = new int[1];
glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
if (status[0] != GL_TRUE) {
String error = glGetShaderInfoLog(shader);
Log.d(LOG_TAG, "Error while compiling shader:\n" + error);
glDeleteShader(shader);
return 0;
}

return shader;
}

private void checkEglError() {
int error = mEgl.eglGetError();
if (error != EGL10.EGL_SUCCESS) {
Log.w(LOG_TAG, "EGL error = 0x" + Integer.toHexString(error));
}
}

private void checkGlError() {
int error = glGetError();
if (error != GL_NO_ERROR) {
Log.w(LOG_TAG, "GL error = 0x" + Integer.toHexString(error));
}
}

private void finishGL() {
mEgl.eglDestroyContext(mEglDisplay, mEglContext);
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
}

private void checkCurrent() {
if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||

!mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throw new RuntimeException("eglMakeCurrent failed " + GLUtils.getEGLErrorString(mEgl.eglGetError()));
}
}
}

private void initGL() {
mEgl = (EGL10) EGLContext.getEGL();

mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed "
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
}

int[] version = new int[2];
if (!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
}

mEglConfig = chooseEglConfig();
if (mEglConfig == null) {
throw new RuntimeException("eglConfig not initialized");
}

mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);

mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);

if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE)
{
int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
return;
}
throw new RuntimeException("createWindowSurface failed "
+ GLUtils.getEGLErrorString(error));
}

if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throw new RuntimeException("eglMakeCurrent failed "
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
}

mGL = mEglContext.getGL();
}

EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
}

private EGLConfig chooseEglConfig() {
int[] configsCount = new int[1];
EGLConfig[] configs = new EGLConfig[1];
int[] configSpec = getConfig();
if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
throw new IllegalArgumentException("eglChooseConfig failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
} else if (configsCount[0] > 0) {
return configs[0];
}
return null;
}

private int[] getConfig() {
return new int[] {
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 8,
EGL10.EGL_DEPTH_SIZE, 0,
EGL10.EGL_STENCIL_SIZE, 0,
EGL10.EGL_NONE
};
}

void finish() {
mFinished = true;
}
}

TextureView vs. GLSurfaceView or How to use GLSurfaceView with EGL14

What you probably want to do is use a plain SurfaceView.

Here's the short version:

  • SurfaceView has two parts, the Surface and a bit of fake stuff in the View. The Surface gets passed directly to the surface compositor (SurfaceFlinger), so when you draw on it with OpenGL there's relatively little overhead. This makes it fast, but it also makes it not play quite right with the View hierarchy, because the Surface is on one layer and the View-based UI is on a different layer.
  • TextureView also has two parts, but the part you draw on lives behind the scenes (that's where the SurfaceTexture comes in). When the frame is complete, the stuff you drew is blitted onto the View layer. The GPU can do this quickly, but "some work" is always slower than "no work".
  • GLSurfaceView is a SurfaceView with a wrapper class that does all the EGL setup and inter-thread messaging for you.

Edit: the long version is available here.

If you can do the GL/EGL setup and thread management yourself -- which, if you're now running on a TextureView, you clearly can -- then you should probably use a plain SurfaceView.

Having said all that, it should be possible to make your original code work with GLSurfaceView. I expect you want to call eglPresentationTimeANDROID() on the EGL context that's shared with the GLSurfaceView, not from within GLSurfaceView itself, so it doesn't matter that GLSurfaceView is using EGL10 internally. What matters for sharing the context is the context client version (e.g. GLES2 vs. GLES3), not the EGL interface version used to configure the context.

You can see examples of all of this working in Grafika. In particular:

  • "Show + capture camera" uses a GLSurfaceView, the camera, and the video encoder. Note the EGL context is shared. The example is convoluted and somewhat painful, mostly because it's deliberately trying to use GLSurfaceView and a shared EGL context. (Update: note this issue about race conditions with shared contexts.)
  • "Play video (TextureView)" and "Basic GL in TextureView" show TextureView in action.
  • "Record GL app with FBO" uses a plain SurfaceView.

ANR TextureView on Galaxy S2 (GPU driver issue?)

Got an official response from Google that this is indeed a driver bug from Samsung (supposedly fixed) for this very specific device.

Capture screen of GLSurfaceView to bitmap

SurfaceView and GLSurfaceView punch holes in their windows to allow their surfaces to be displayed. In other words, they have transparent areas.

So you cannot capture an image by calling GLSurfaceView.getDrawingCache().

If you want to get an image from GLSurfaceView, you should invoke gl.glReadPixels() in GLSurfaceView.onDrawFrame().

I patched createBitmapFromGLSurface method and call it in onDrawFrame().

(The original code might be from skuld's code.)

private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl)
throws OutOfMemoryError {
int bitmapBuffer[] = new int[w * h];
int bitmapSource[] = new int[w * h];
IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
intBuffer.position(0);

try {
gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
int offset1, offset2;
for (int i = 0; i < h; i++) {
offset1 = i * w;
offset2 = (h - i - 1) * w;
for (int j = 0; j < w; j++) {
int texturePixel = bitmapBuffer[offset1 + j];
int blue = (texturePixel >> 16) & 0xff;
int red = (texturePixel << 16) & 0x00ff0000;
int pixel = (texturePixel & 0xff00ff00) | red | blue;
bitmapSource[offset2 + j] = pixel;
}
}
} catch (GLException e) {
return null;
}

return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
}

Android OpenGL ES Transparent Background

Just some simple changes that I did to get this to work.

On my GLSurfaceView.Renderer:

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glDisable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
GL10.GL_FASTEST);

gl.glClearColor(0,0,0,0);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glShadeModel(GL10.GL_SMOOTH);
gl.glEnable(GL10.GL_DEPTH_TEST);
}

On my GLSurfaceView:

setEGLConfigChooser(8, 8, 8, 8, 16, 0);
getHolder().setFormat(PixelFormat.TRANSLUCENT);

Get Bitmap from TextureView efficiently

A TextureView receives frames on a SurfaceTexture, which takes frames sent to its Surface and converts them to a GLES texture. To get the pixel data out, the texture must be rendered to a framebuffer, then read out with glReadPixels(). The pixel data can then be wrapped with a Bitmap object (which may or may not involve copying the pixel data).

Using the NDK isn't going to do you much good, as all of the code that needs to run quickly is already implemented natively.

You may see some improvement by sending the data directly to a SurfaceTexture and doing the GLES work yourself, but presumably you want to display the incoming frames in the TextureView, so all you'd potentially save is the Bitmap overhead (which may or may not be significant).

It might help if you explained in your question where the frames are coming from and what it is you want to do with them.



Related Topics



Leave a reply



Submit