Android OpenGL ES Issue only with a specific GPU without any Error in LogCat

621 views Asked by At

I have accurately tested my app before release it, on Emulator set with different screen size (and with different Android SDK and CPU emulations), and many real devices. No problems, everything works fine. Now an user has reported a bug with his tablet.

I'm testing the app on tons of devices and the issue happens only if the devices use some kind of soc ARM with PowerVR SGX544 and Android 4.x.

The app doesn't use any texture, only GL11, GL10 and GLView to plot some graph, and runs smooth also on old cheap smartphones with at least Gingerbread... but with this Power VR the result of the plot is an unreadable and laggy graphic glitch

No error in the Eclipse logs, No crash or code deprecation warning

Must I assume that the bug is in the GPU driver?

The code of the section with the bug (I cannot be more syntetic because I get no error)

public class My3dView extends GLView implements 
                                            Grapher,
                                            TouchHandler.TouchHandlerInterface
{

    private float lastTouchX, lastTouchY;
    private TouchHandler touchHandler;
    private float zoomLevel = 1, targetZoom, zoomStep = 0, currentZoom;
    private FPS fps = new FPS();
    private Graph3d graph;

    public My3dView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public My3dView(Context context) {
        super(context);
        touchHandler = new TouchHandler(this);
        init();
    }

    private void init() {
        startLooping();
        zoomController.setOnZoomListener(this);

        Matrix.setIdentityM(matrix1, 0);
        Matrix.rotateM(matrix1, 0, -75, 1, 0, 0);
    }

    public void onVisibilityChanged(boolean visible) {
    }

    @Override
    protected void glDraw() {
        if ((zoomStep < 0 && zoomLevel > targetZoom) ||
            (zoomStep > 0 && zoomLevel < targetZoom)) {
            zoomLevel += zoomStep;
        } else if (zoomStep != 0) {
            zoomStep = 0;
            zoomLevel = targetZoom;
            isDirty = true;
            if (!shouldRotate()) {
                stopLooping();
            }
        }
        super.glDraw();
    }

    @Override
    public void onDetachedFromWindow() {
        zoomController.setVisible(false);
        super.onDetachedFromWindow();
    }

    public void onTouchDown(float x, float y) {
        zoomController.setVisible(true);
        stopLooping();
        lastTouchX = x;
        lastTouchY = y;
    }

    public void onTouchMove(float x, float y) {
        float deltaX = x - lastTouchX;
        float deltaY = y - lastTouchY;
        if (deltaX > 1 || deltaX < -1 || deltaY > 1 || deltaY < -1) {
            setRotation(deltaX, deltaY);
            glDraw();
            lastTouchX = x;
            lastTouchY = y;
        }
    }

    public void onTouchUp(float x, float y) {
        float vx = touchHandler.velocityTracker.getXVelocity();
        float vy = touchHandler.velocityTracker.getYVelocity();
        setRotation(vx/100, vy/100);
        if (shouldRotate()) {
            startLooping();
        }
    }

    public void onTouchZoomDown(float x1, float y1, float x2, float y2) {

    }

    public void onTouchZoomMove(float x1, float y1, float x2, float y2) {

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return touchHandler != null ? touchHandler.onTouchEvent(event) : super.onTouchEvent(event);
    }

    private float[] matrix1 = new float[16], matrix2 = new float[16], matrix3 = new float[16];
    private float angleX, angleY;
    private boolean isDirty;
    private Function function;
    private static final float DISTANCE = 15f;

    void setRotation(float x, float y) {
        angleX = x;
        angleY = y;
    }

    boolean shouldRotate() {
        final float limit = .5f;
        return angleX < -limit || angleX > limit || angleY < -limit || angleY > limit;
    }

    public void setFunction(Function f) {
        function = f;
        zoomLevel = 1;
        isDirty = true;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, int width, int height) {
        gl.glDisable(GL10.GL_DITHER);
        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);               
        gl.glClearColor(0, 0, 0, 1);
        gl.glShadeModel(GL10.GL_SMOOTH);
        gl.glDisable(GL10.GL_LIGHTING);
        graph = new Graph3d((GL11) gl);
        isDirty = true;
        angleX = .5f;
        angleY = 0;

        gl.glViewport(0, 0, width, height);
        initFrustum(gl, DISTANCE * zoomLevel);
        currentZoom = zoomLevel;
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        GL11 gl = (GL11) gl10;
        if (currentZoom != zoomLevel) {
            initFrustum(gl, DISTANCE * zoomLevel);
            currentZoom = zoomLevel;
        }
        if (isDirty) {
            graph.update(gl, function, zoomLevel);
            isDirty = false;
        }

        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glTranslatef(0, 0, -DISTANCE*zoomLevel);

        Matrix.setIdentityM(matrix2, 0);
        float ax = Math.abs(angleX);
        float ay = Math.abs(angleY);
        if (ay * 3 < ax) {
            Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
        } else if (ax * 3 < ay) {
            Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
        } else {
            if (ax > ay) {
                Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
                Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
            } else {
                Matrix.rotateM(matrix2, 0, angleY, 1, 0, 0);
                Matrix.rotateM(matrix2, 0, angleX, 0, 1, 0);
            }
        }
        Matrix.multiplyMM(matrix3, 0, matrix2, 0, matrix1, 0);
        gl.glMultMatrixf(matrix3, 0);
        System.arraycopy(matrix3, 0, matrix1, 0, 16);
        graph.draw(gl);
    }

    private void initFrustum(GL10 gl, float distance) {
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        float near = distance * (1/3f);
        float far  = distance * 3f;
        float dimen = near/5f;
        float h = dimen * height / width;
        gl.glFrustumf(-dimen, dimen, -h, h, near, far);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    }
}

The code where I setup FloatBuffers :

class Graph3d {
    private final int N = 48;
    private ShortBuffer verticeIdx;
    private FloatBuffer vertexBuf;
    private ByteBuffer colorBuf;
    private int vertexVbo, colorVbo, vertexElementVbo;
    private boolean useVBO;
    private int nVertex;

    Graph3d(GL11 gl) {
        short[] b = new short[N*N];
        int p = 0;
        for (int i = 0; i < N; i++) {
            short v = 0;
            for (int j = 0; j < N; v += N+N, j+=2) {
                b[p++] = (short)(v+i);
                b[p++] = (short)(v+N+N-1-i);
            }
            v = (short) (N*(N-2));
            i++;
            for (int j = N-1; j >= 0; v -= N+N, j-=2) {
                b[p++] = (short)(v+N+N-1-i);
                b[p++] = (short)(v+i);
            } 
        }
        verticeIdx = buildBuffer(b);

        String extensions = gl.glGetString(GL10.GL_EXTENSIONS);
        useVBO = extensions.indexOf("vertex_buffer_object") != -1;
        Calculator.log("VBOs support: " + useVBO + " version " + gl.glGetString(GL10.GL_VERSION));

        if (useVBO) {
            int[] out = new int[3];
            gl.glGenBuffers(3, out, 0);        
            vertexVbo = out[0];
            colorVbo  = out[1];
            vertexElementVbo = out[2];
        }
    }

    private static FloatBuffer buildBuffer(float[] b) {
        ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 2);
        bb.order(ByteOrder.nativeOrder());
        FloatBuffer sb = bb.asFloatBuffer();
        sb.put(b);
        sb.position(0);
        return sb;
    }

    private static ShortBuffer buildBuffer(short[] b) {
        ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 1);
        bb.order(ByteOrder.nativeOrder());
        ShortBuffer sb = bb.asShortBuffer();
        sb.put(b);
        sb.position(0);
        return sb;
    }

    private static ByteBuffer buildBuffer(byte[] b) {
        ByteBuffer bb = ByteBuffer.allocateDirect(b.length << 1);
        bb.order(ByteOrder.nativeOrder());
        bb.put(b);
        bb.position(0);
        return bb;
    }

    public void update(GL11 gl, Function f, float zoom) {
        final int NTICK = Calculator.useHighQuality3d ? 5 : 0;
        final float size = 4*zoom;
        final float minX = -size, maxX = size, minY = -size, maxY = size;

        Calculator.log("update VBOs " + vertexVbo + ' ' + colorVbo + ' ' + vertexElementVbo);
        nVertex = N*N+6+8 + NTICK*6;
        int nFloats = nVertex * 3;
        float vertices[] = new float[nFloats];
        byte colors[] = new byte[nVertex << 2];
        if (f != null) {
            Calculator.log("Graph3d update");
            float sizeX = maxX - minX;
            float sizeY = maxY - minY;
            float stepX = sizeX / (N-1);
            float stepY = sizeY / (N-1);
            int pos = 0;
            double sum = 0;
            float y = minY;
            float x = minX - stepX;
            int nRealPoints = 0;
            for (int i = 0; i < N; i++, y+=stepY) {
                float xinc = (i & 1) == 0 ? stepX : -stepX;
                x += xinc;
                for (int j = 0; j < N; ++j, x+=xinc, pos+=3) {
                    float z = (float) f.eval(x, y);
                    vertices[pos] = x;
                    vertices[pos+1] = y;
                    vertices[pos+2] = z;
                    if (z == z) { // not NAN
                        sum += z * z;
                        ++nRealPoints;
                    }
                }
            }
            float maxAbs = (float) Math.sqrt(sum / nRealPoints);
            maxAbs *= .9f;
            maxAbs = Math.min(maxAbs, 15);
            maxAbs = Math.max(maxAbs, .001f);

            final int limitColor = N*N*4;
            for (int i = 0, j = 2; i < limitColor; i+=4, j+=3) {
                float z = vertices[j];
                if (z == z) {
                    final float a = z / maxAbs;
                    final float abs = a < 0 ? -a : a;
                    colors[i]   = floatToByte(a);
                    colors[i+1] = floatToByte(1-abs*.3f);
                    colors[i+2] = floatToByte(-a);
                    colors[i+3] = (byte) 255;
                } else {
                    vertices[j] = 0;
                    z = 0;
                    colors[i]   = 0;
                    colors[i+1] = 0;
                    colors[i+2] = 0;
                    colors[i+3] = 0;
                }
            }
        }
        int base = N*N*3;
        int colorBase = N*N*4;
        int p = base;
        final int baseSize = 2;
        for (int i = -baseSize; i <= baseSize; i+=2*baseSize) {
            vertices[p] = i; vertices[p+1] = -baseSize; vertices[p+2] = 0;
            p += 3;
            vertices[p] = i; vertices[p+1] = baseSize; vertices[p+2] = 0;
            p += 3;
            vertices[p] = -baseSize; vertices[p+1] = i; vertices[p+2] = 0;
            p += 3;
            vertices[p] = baseSize; vertices[p+1] = i; vertices[p+2] = 0;
            p += 3;
        }
        for (int i = colorBase; i < colorBase+8*4; i += 4) {
            colors[i] = 0;
            colors[i+1] = 0;
            colors[i+2] = (byte) 255;
            colors[i+3] = (byte) 255;
        }
        base += 8*3;
        colorBase += 8*4;

        final float unit = 2;
        final float axis[] = {
            0, 0, 0,
            unit, 0, 0,
            0, 0, 0,
            0, unit, 0,
            0, 0, 0,
            0, 0, unit,
        };
        System.arraycopy(axis, 0, vertices, base, 6*3);
        for (int i = colorBase; i < colorBase+6*4; i+=4) {
            colors[i] = (byte) 255;
            colors[i+1] = (byte) 255;
            colors[i+2] = (byte) 255;
            colors[i+3] = (byte) 255;
        }                
        base += 6*3;
        colorBase += 6*4;

        p = base;
        final float tick = .03f;
        final float offset = .01f;
        for (int i = 1; i <= NTICK; ++i) {
            vertices[p]   = i-tick;
            vertices[p+1] = -offset;
            vertices[p+2] = -offset;

            vertices[p+3] = i+tick;
            vertices[p+4] = offset;
            vertices[p+5] = offset;
            p += 6;

            vertices[p]   = -offset;
            vertices[p+1] = i-tick;
            vertices[p+2] = -offset;

            vertices[p+3] = offset;
            vertices[p+4] = i+tick;
            vertices[p+5] = offset;
            p += 6;

            vertices[p]   = -offset;
            vertices[p+1] = -offset;
            vertices[p+2] = i-tick;

            vertices[p+3] = offset;
            vertices[p+4] = offset;
            vertices[p+5] = i+tick;
            p += 6;

        }
        for (int i = colorBase+NTICK*6*4-1; i >= colorBase; --i) {
            colors[i] = (byte) 255;
        }

        vertexBuf = buildBuffer(vertices);
        colorBuf  = buildBuffer(colors);

        if (useVBO) {
            gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo);
            gl.glBufferData(GL11.GL_ARRAY_BUFFER, vertexBuf.capacity()*4, vertexBuf, GL11.GL_STATIC_DRAW);           
            vertexBuf = null;

            gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo);
            gl.glBufferData(GL11.GL_ARRAY_BUFFER, colorBuf.capacity(), colorBuf, GL11.GL_STATIC_DRAW);
            gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
            colorBuf = null;            

            gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);
            gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, verticeIdx.capacity()*2, verticeIdx, GL11.GL_STATIC_DRAW);
            gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
        }
    }

    private byte floatToByte(float v) {
        return (byte) (v <= 0 ? 0 : v >= 1 ? 255 : (int)(v*255));
    }

    public void draw(GL11 gl) {
        if (useVBO) {
            gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vertexVbo);
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, 0);

            gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, colorVbo);
            gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, 0);

            gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
            // gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N*N);

            gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, vertexElementVbo);        
            gl.glDrawElements(GL10.GL_LINE_STRIP, N*N, GL10.GL_UNSIGNED_SHORT, 0);
            gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
        } else {
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuf);
            gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, colorBuf);
            gl.glDrawElements(GL10.GL_LINE_STRIP, N*N, GL10.GL_UNSIGNED_SHORT, verticeIdx);
        }
        final int N2 = N*N;
        gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, N2);
        gl.glDrawArrays(GL10.GL_LINES, N2, nVertex - N2);
    }
}
0

There are 0 answers