I have developed a simple game for Android called Dots where the user is tasked with pressing dots on screen before they disappear. The game works correctly on all of the devices it's been played on so far except for one of them. Despite running on some devices with Android 4.1.2, the game will not render any of the circles (drawn with OpenGL ES 2.0) on a Samsung Galaxy Note 10.1 (2012 edition, I believe) running android 4.1.2. All other facets of the app work correctly, but the circles just aren't drawn on the screen. I have, at my disposal, a Nexus 10 and a HTC One m7 for testing, so I know the game will run just fine on devices larger than phones. I apologize if this is similar to some other question(s) on stack overflow, but I have searched around stack overflow and other websites trying to find a solution. As I do not possess the device in question, it's harder for me to test if a fix works or not without rolling out a patch to Google Play. I appreciate any help you guys have, and I'm willing to post code to help if need be! Thanks in advance.
For reference, here's my Circle constructor:
public class Circle {
public FloatBuffer vertexBuffer;
public float[] circleCoords = new float[364*3]; //changed to public
public final int mProgram;
public int mPositionHandle;
public int mColorHandle;
public int mMVPMatrixHandle;
public final int vertexCount = circleCoords.length / 3;
float color[] = {0.0f, 0.0f, 0.0f, 1.0f};
public Circle(int w, int h){
final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// the matrix must be included as a modifier of gl_Position
// Note that the uMVPMatrix factor *must be first* in order
// for the matrix multiplication product to be correct.
" gl_Position = uMVPMatrix * vPosition;" + //took out uMVPMatrix part, adding it back in makes the circle unrenderable
"}";
final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
circleCoords[0] = (float) -0.82*w/h + (float)(Math.random() * (0.72*w/h + 0.82*w/h));
circleCoords[1] = (float) -0.82 + (float)(Math.random() * (0.72 + 0.82));
circleCoords[2] = 0;
//set up values for all coordinates (2-D)
for (int i = 1; i < vertexCount; i++){
circleCoords[(i*3)] = (float) (0.12*Math.cos((3.1416/180) * (float) i) + circleCoords[0]);
circleCoords[(i*3)+1] = (float) (0.12*Math.sin((3.1416/180) * (float) i) + circleCoords[1]);
circleCoords[(i*3)+2] = 0 + circleCoords[2];
}
//allocate number of coords * 4 bytes per coord
//changing this from allocate to allocateDirect fixed the rendering issue about using native order buffer
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(circleCoords.length * 4);
//use device hardware's native byteorder
byteBuffer.order(ByteOrder.nativeOrder());
//create floating point buffer from the ByteBuffer
vertexBuffer = byteBuffer.asFloatBuffer();
//add coordinates to float buffer
vertexBuffer.put(circleCoords);
//set buffer to read from first position
vertexBuffer.position(0);
int vertexShader = CircleRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = CircleRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
}
public void draw(float[] mvpMatrix){
//add program to opengles environment
GLES20.glUseProgram(mProgram);
//get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//enable a handle to the circle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
//prepare the circle's coordinate data
GLES20.glVertexAttribPointer(
mPositionHandle, 3, //coords per vertex is 3
GLES20.GL_FLOAT, false,
12, vertexBuffer); //colors per coord * coords per vertex
//enable a handle to the circle vertices
//GLES20.glEnableVertexAttribArray(mPositionHandle);
// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
//CircleRenderer.checkGlError("glGetUniformLocation"); //if errors persist, uncomment this out
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
//CircleRenderer.checkGlError("glUniformMatrix4fv");
// Draw the circle as filled shape
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, vertexCount);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}
And here's my CircleRenderer
public class CircleRenderer implements GLSurfaceView.Renderer {
//private static final String TAG = "CircleRenderer";
//changed to public
public Circle mCircle;
//public Circle m2Circle;
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
public void onSurfaceCreated(GL10 unused, EGLConfig config){
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
//mCircle = new Circle();
}
public void onDrawFrame(GL10 unused){
//draw background frame color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//set camera position (View matrix)
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
//Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0);
// Draw circle
mCircle.draw(mMVPMatrix);
}
public void onSurfaceChanged(GL10 unused, int width, int height){
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//Log.d("Width in onSurfaceChanged is", width+"");
mCircle = new Circle(width, height);
//m2Circle = new Circle(width, height);
}
public static int loadShader(int type, String shaderCode){
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
/**
* Utility method for debugging OpenGL calls. Provide the name of the call
* just after making it:
*
* If the operation is not successful, the check throws an error.
*
* param glOperation - Name of the OpenGL call to check.
*/
//public static void checkGlError(String glOperation) {
//int error;
//while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
//Log.e(TAG, glOperation + ": glError " + error);
//throw new RuntimeException(glOperation + ": glError " + error);
//}
//}
public void clearCircle(){ //does clear screen
mCircle.color[0] = 1.0f;
mCircle.color[1] = 1.0f;
mCircle.color[2] = 1.0f;
}
public void newCircle(float radius, String newColor, int w, int h){
float max = (float)(1 - radius*0.12 - 0.06); //initially 0.83
float min = (float)(0.9 - radius*0.12 - 0.06); //initially 0.73
mCircle.circleCoords[0] = -max*w/h + (float)(Math.random() * (min*w/h + max*w/h));
mCircle.circleCoords[1] = -max + (float)(Math.random() * (min + max));
mCircle.circleCoords[2] = 0;
//set up values for all coordinates (2-D)
for (int i = 1; i < mCircle.vertexCount; i++){
mCircle.circleCoords[(i*3)] = (float) (0.12*radius*Math.cos((3.1416/180) * (float) i) + mCircle.circleCoords[0]);
mCircle.circleCoords[(i*3)+1] = (float) (0.12*radius*Math.sin((3.1416/180) * (float) i) + mCircle.circleCoords[1]);
mCircle.circleCoords[(i*3)+2] = 0 + mCircle.circleCoords[2];
}
int color = 0;
if (newColor.equals("Black"))
color = 0;
else if(newColor.equals("Red"))
color = 1;
else if (newColor.equals("Green"))
color = 2;
else if (newColor.equals("Blue"))
color = 3;
else if (newColor.equals("Yellow"))
color = 4;
else if (newColor.equals("Purple"))
color = 5;
else if (newColor.equals("LightBlue"))
color = 6;
else if (newColor.equals("darkRed"))
color = 7;
else if (newColor.equals("darkRed"))
color = 8;
else if (newColor.equals("darkBlue"))
color = 9;
else if (newColor.equals("brownGreen"))
color = 10;
else if (newColor.equals("darkPurple"))
color = 11;
else if (newColor.equals("Teal"))
color = 12;
switch (color) { //set colors
case 0:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 0.0f;
break;
case 1:
mCircle.color[0] = 1.0f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 0.0f;
break;
case 2:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 1.0f;
mCircle.color[2] = 0.0f;
break;
case 3:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 1.0f;
break;
case 4:
mCircle.color[0] = 1.0f;
mCircle.color[1] = 1.0f;
mCircle.color[2] = 0.0f;
break;
case 5:
mCircle.color[0] = 1.0f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 1.0f;
break;
case 6:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 1.0f;
mCircle.color[2] = 1.0f;
break;
case 7:
mCircle.color[0] = 0.5f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 0.0f;
break;
case 8:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 0.5f;
mCircle.color[2] = 0.0f;
break;
case 9:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 0.5f;
break;
case 10:
mCircle.color[0] = 0.5f;
mCircle.color[1] = 0.5f;
mCircle.color[2] = 0.0f;
break;
case 11:
mCircle.color[0] = 0.5f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 0.5f;
break;
case 12:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 0.5f;
mCircle.color[2] = 0.5f;
break;
default:
mCircle.color[0] = 0.0f;
mCircle.color[1] = 0.0f;
mCircle.color[2] = 0.0f;
break;
}
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(mCircle.circleCoords.length * 4);
//use device hardware's native byte order
byteBuffer.order(ByteOrder.nativeOrder());
//create floating point buffer from the ByteBuffer
mCircle.vertexBuffer = byteBuffer.asFloatBuffer();
//add coordinates to float buffer
mCircle.vertexBuffer.put(mCircle.circleCoords);
//set buffer to read from first position
mCircle.vertexBuffer.position(0);
}
Last but not least, here's my GLSurfaceView class
class CircleGLSurfaceView extends GLSurfaceView {
public CircleRenderer circleRend;
//these two variables are for controlling spawn timer
public Handler spawnHandler;
public timerRunnable timeRunnable;
public CircleGLSurfaceView(Context context) {
super(context);
super.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
setEGLContextClientVersion(2);
circleRend = new CircleRenderer();
setRenderer(circleRend);
//render view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
//start a new
spawnHandler = new Handler();
timeRunnable = new timerRunnable();
spawnHandler.postDelayed(timeRunnable, visibleTime);
}
Edit: I added the super.setEGLConfigChooser method call since I had read elsewhere on SO that it helped with a somewhat related issue on a galaxy note 2
--EDIT: Found solution--
The solution found here ended up solving the problem that I was having with rendering shapes on devices running Android 4.1.x and possessing an ARM Mali 400MP GPU
Android OpenGL ES not rasterizing - Matrix multiplication switched
Thank you all for your help!