/*******************************************************************************
* Copyright (c) 2015
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package go.graphics.android;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import android.content.Context;
import android.opengl.GLES10;
import android.opengl.GLES11;
import go.graphics.GLDrawContext;
import go.graphics.GeometryHandle;
import go.graphics.TextureHandle;
import go.graphics.android.AndroidGLHandle.AndroidGeometryHandle;
import go.graphics.android.AndroidGLHandle.AndroidTextureHandle;
import go.graphics.text.EFontSize;
import go.graphics.text.TextDrawer;
public class AndroidContext implements GLDrawContext {
private final Context context;
private TextureHandle lastTexture = null;
public AndroidContext(Context context) {
this.context = context;
}
@Override
public void fillQuad(float x1, float y1, float x2, float y2) {
quadDatas = new float[3 * 6];
quadDatas[0] = x1;
quadDatas[1] = y1;
quadDatas[2] = 0;
quadDatas[3] = x2;
quadDatas[4] = y1;
quadDatas[5] = 0;
quadDatas[6] = x1;
quadDatas[7] = y2;
quadDatas[8] = 0;
quadDatas[9] = x1;
quadDatas[10] = y2;
quadDatas[11] = 0;
quadDatas[12] = x2;
quadDatas[13] = y1;
quadDatas[14] = 0;
quadDatas[15] = x2;
quadDatas[16] = y2;
quadDatas[17] = 0;
glBindTexture(null);
FloatBuffer floatBuff = generateTemporaryFloatBuffer(quadDatas);
GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 3 * 4, floatBuff);
GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, quadDatas.length / 3);
GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
}
@Override
public void glPushMatrix() {
GLES10.glPushMatrix();
}
@Override
public void glTranslatef(float x, float y, float z) {
GLES10.glTranslatef(x, y, z);
}
@Override
public void glScalef(float x, float y, float z) {
GLES10.glScalef(x, y, z);
}
@Override
public void glPopMatrix() {
GLES10.glPopMatrix();
}
@Override
public void color(float red, float green, float blue, float alpha) {
GLES10.glColor4f(red, green, blue, alpha);
}
private FloatBuffer reuseableBuffer = null;
private ByteBuffer quadEleementBuffer;
private FloatBuffer reuseableBufferDuplicate;
private FloatBuffer generateTemporaryFloatBuffer(float[] points) {
int floatCount = points.length;
FloatBuffer b = createReusedBuffer(floatCount);
b.put(points);
b.position(0);
return b;
}
private FloatBuffer createReusedBuffer(int floatCount) {
if (reuseableBuffer == null
|| reuseableBuffer.position(0).capacity() < floatCount) {
ByteBuffer quadPoints = ByteBuffer.allocateDirect(floatCount * 4);
quadPoints.order(ByteOrder.nativeOrder());
reuseableBuffer = quadPoints.asFloatBuffer();
reuseableBufferDuplicate = reuseableBuffer.duplicate();
} else {
reuseableBuffer.position(0);
}
return reuseableBuffer;
}
@Override
public void drawLine(float[] points, boolean loop) {
if (points.length % 3 != 0) {
throw new IllegalArgumentException(
"Point array length needs to be multiple of 3.");
}
glBindTexture(null);
FloatBuffer floatBuff = generateTemporaryFloatBuffer(points);
GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, floatBuff);
GLES10.glDrawArrays(loop ? GLES10.GL_LINE_LOOP : GLES10.GL_LINE_STRIP,
0, points.length / 3);
GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
}
private void glBindTexture(TextureHandle texture) {
if (texture != lastTexture) {
int id;
if (texture == null) {
id = 0;
} else {
id = texture.getInternalId();
}
GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, id);
lastTexture = texture;
}
}
@Override
public void drawQuadWithTexture(TextureHandle textureid, float[] geometry) {
if (quadEleementBuffer == null) {
generateQuadElementBuffer();
}
quadEleementBuffer.position(0);
glBindTexture(textureid);
FloatBuffer buffer = generateTemporaryFloatBuffer(geometry);
GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 5 * 4, buffer);
FloatBuffer texbuffer = reuseableBufferDuplicate;
texbuffer.position(3);
GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 5 * 4, texbuffer);
GLES10.glDrawElements(GLES10.GL_TRIANGLES, 6, GLES10.GL_UNSIGNED_BYTE,
quadEleementBuffer);
}
private void generateQuadElementBuffer() {
quadEleementBuffer = ByteBuffer.allocateDirect(6);
quadEleementBuffer.put((byte) 0);
quadEleementBuffer.put((byte) 1);
quadEleementBuffer.put((byte) 3);
quadEleementBuffer.put((byte) 3);
quadEleementBuffer.put((byte) 1);
quadEleementBuffer.put((byte) 2);
}
@Override
public void drawTrianglesWithTexture(TextureHandle textureid, float[] geometry) {
glBindTexture(textureid);
FloatBuffer buffer = generateTemporaryFloatBuffer(geometry);
GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 5 * 4, buffer);
FloatBuffer texbuffer = reuseableBufferDuplicate;
texbuffer.position(3);
GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 5 * 4, texbuffer);
GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, geometry.length / 5);
}
@Override
public void drawTrianglesWithTextureColored(TextureHandle textureid, float[] geometry) {
glBindTexture(textureid);
GLES10.glEnableClientState(GLES10.GL_COLOR_ARRAY);
FloatBuffer buffer = generateTemporaryFloatBuffer(geometry);
GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 9 * 4, buffer);
FloatBuffer texbuffer = reuseableBufferDuplicate;
texbuffer.position(3);
GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 9 * 4, texbuffer);
FloatBuffer colorbuffer = buffer.duplicate(); // we need it selden enogh
// to allocate a new one.
colorbuffer.position(5);
GLES10.glColorPointer(4, GLES10.GL_FLOAT, 9 * 4, colorbuffer);
GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, geometry.length / 9);
GLES10.glDisableClientState(GLES10.GL_COLOR_ARRAY);
}
@Override
public void drawTrianglesWithTextureColored(TextureHandle textureid,
ByteBuffer byteBuffer, int currentTriangles) {
glBindTexture(textureid);
GLES10.glEnableClientState(GLES10.GL_COLOR_ARRAY);
GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 6 * 4, byteBuffer);
ByteBuffer texbuffer = byteBuffer.duplicate();
texbuffer.position(3 * 4);
GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 6 * 4, texbuffer);
ByteBuffer colorbuffer = byteBuffer.duplicate(); // we need it selden
// enogh
// to allocate a new
// one.
colorbuffer.position(5 * 4);
GLES10.glColorPointer(4, GLES10.GL_UNSIGNED_BYTE, 6 * 4, colorbuffer);
GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, currentTriangles * 3);
GLES10.glDisableClientState(GLES10.GL_COLOR_ARRAY);
}
private static int getPowerOfTwo(int value) {
int guess = 1;
while (guess < value) {
guess *= 2;
}
return guess;
}
@Override
public int makeWidthValid(int width) {
return getPowerOfTwo(width);
}
@Override
public int makeHeightValid(int height) {
return getPowerOfTwo(height);
}
@Override
public TextureHandle generateTexture(int width, int height, ShortBuffer data) {
// 1 byte aligned.
GLES10.glPixelStorei(GLES10.GL_UNPACK_ALIGNMENT, 1);
TextureHandle texture = genTextureIndex();
if (texture == null) {
return null;
}
glBindTexture(texture);
GLES10.glTexImage2D(GLES10.GL_TEXTURE_2D, 0, GLES10.GL_RGBA, width,
height, 0, GLES10.GL_RGBA, GLES10.GL_UNSIGNED_SHORT_5_5_5_1,
data);
setTextureParameters();
return texture;
}
private static AndroidTextureHandle genTextureIndex() {
int[] textureIndexes = new int[1];
GLES10.glGenTextures(1, textureIndexes, 0);
return new AndroidTextureHandle(textureIndexes[0]);
}
/**
* Sets the texture parameters, assuming that the texture was just created and is bound.
*/
private static void setTextureParameters() {
GLES10.glTexParameterf(GLES10.GL_TEXTURE_2D,
GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR);
GLES10.glTexParameterf(GLES10.GL_TEXTURE_2D,
GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR);
GLES10.glTexParameterf(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_WRAP_S,
GLES10.GL_REPEAT);
GLES10.glTexParameterf(GLES10.GL_TEXTURE_2D, GLES10.GL_TEXTURE_WRAP_T,
GLES10.GL_REPEAT);
}
@Override
public void updateTexture(TextureHandle textureIndex, int left, int bottom,
int width, int height, ShortBuffer data) {
glBindTexture(textureIndex);
GLES10.glTexSubImage2D(GLES10.GL_TEXTURE_2D, 0, left, bottom, width,
height, GLES10.GL_RGBA, GLES10.GL_UNSIGNED_SHORT_5_5_5_1, data);
}
public TextureHandle generateTextureAlpha(int width, int height) {
// 1 byte aligned.
GLES10.glPixelStorei(GLES10.GL_UNPACK_ALIGNMENT, 1);
TextureHandle texture = genTextureIndex();
if (texture == null) {
return null;
}
ByteBuffer data = ByteBuffer.allocateDirect(width * height);
while (data.hasRemaining()) {
data.put((byte) 0);
}
data.rewind();
glBindTexture(texture);
GLES10.glTexImage2D(GLES10.GL_TEXTURE_2D, 0, GLES10.GL_ALPHA, width,
height, 0, GLES10.GL_ALPHA, GLES10.GL_UNSIGNED_BYTE, data);
setTextureParameters();
return texture;
}
public void updateTextureAlpha(TextureHandle textureIndex, int left, int bottom,
int width, int height, ByteBuffer data) {
glBindTexture(textureIndex);
GLES10.glTexSubImage2D(GLES10.GL_TEXTURE_2D, 0, left, bottom, width,
height, GLES10.GL_ALPHA, GLES10.GL_UNSIGNED_BYTE, data);
}
@Override
public void glMultMatrixf(float[] matrix, int offset) {
GLES10.glMultMatrixf(matrix, offset);
}
@Override
public TextDrawer getTextDrawer(EFontSize size) {
return AndroidTextDrawer.getInstance(size, this);
}
@Override
public void drawQuadWithTexture(TextureHandle textureid, GeometryHandle geometryindex) {
if (quadEleementBuffer == null) {
generateQuadElementBuffer();
}
quadEleementBuffer.position(0);
glBindTexture(textureid);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, geometryindex.getInternalId());
GLES11.glVertexPointer(3, GLES10.GL_FLOAT, 5 * 4, 0);
GLES11.glTexCoordPointer(2, GLES10.GL_FLOAT, 5 * 4, 3 * 4);
GLES11.glDrawElements(GLES10.GL_TRIANGLES, 6, GLES10.GL_UNSIGNED_BYTE,
quadEleementBuffer);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
}
public void reinit(int width, int height) {
GLES10.glMatrixMode(GLES10.GL_PROJECTION);
GLES10.glLoadIdentity();
GLES10.glMatrixMode(GLES10.GL_MODELVIEW);
GLES10.glLoadIdentity();
GLES10.glScalef(2f / width, 2f / height, -.5f);
// TODO: do not scale depth by 0.
GLES10.glTranslatef(-width / 2, -height / 2, .25f);
GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
GLES10.glAlphaFunc(GLES10.GL_GREATER, 0.1f);
GLES10.glEnable(GLES10.GL_ALPHA_TEST);
GLES10.glEnable(GLES10.GL_BLEND);
GLES10.glBlendFunc(GLES10.GL_SRC_ALPHA, GLES10.GL_ONE_MINUS_SRC_ALPHA);
GLES10.glDepthFunc(GLES10.GL_LEQUAL);
GLES10.glEnable(GLES10.GL_DEPTH_TEST);
GLES10.glEnable(GLES10.GL_TEXTURE_2D);
}
@Override
public void drawTrianglesWithTexture(TextureHandle textureid, GeometryHandle geometryindex,
int triangleCount) {
glBindTexture(textureid);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, geometryindex.getInternalId());
GLES11.glVertexPointer(3, GLES11.GL_FLOAT, 5 * 4, 0);
GLES11.glTexCoordPointer(2, GLES11.GL_FLOAT, 5 * 4, 3 * 4);
GLES11.glDrawArrays(GLES11.GL_TRIANGLES, 0, triangleCount * 3);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
}
@Override
public GeometryHandle generateGeometry(int bytes) {
int[] vertexBuffIds = new int[] {
0
};
GLES11.glGenBuffers(1, vertexBuffIds, 0);
int vertexBufferId = vertexBuffIds[0];
if (vertexBufferId == 0) {
return null;
}
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, vertexBufferId);
GLES11.glBufferData(GLES11.GL_ARRAY_BUFFER, bytes, null,
GLES11.GL_DYNAMIC_DRAW);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
return new AndroidGeometryHandle(vertexBufferId);
}
@Override
public void drawTrianglesWithTextureColored(TextureHandle textureid,
GeometryHandle geometryindex, int triangleCount) {
glBindTexture(textureid);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, geometryindex.getInternalId());
GLES11.glVertexPointer(3, GLES11.GL_FLOAT, 6 * 4, 0);
GLES11.glTexCoordPointer(2, GLES11.GL_FLOAT, 6 * 4, 3 * 4);
GLES11.glColorPointer(4, GLES11.GL_UNSIGNED_BYTE, 6 * 4, 5 * 4);
GLES11.glEnableClientState(GLES11.GL_COLOR_ARRAY);
GLES11.glDrawArrays(GLES11.GL_TRIANGLES, 0, triangleCount * 3);
GLES11.glDisableClientState(GLES11.GL_COLOR_ARRAY);
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
}
@Override
public GeometryHandle storeGeometry(float[] geometry) {
int bytes = 4 * geometry.length;
GeometryHandle vertexBufferId = generateGeometry(bytes);
GLBuffer buffer = startWriteGeometry(vertexBufferId);
for (int i = 0; i < geometry.length; i++) {
buffer.putFloat(geometry[i]);
}
endWriteGeometry(vertexBufferId);
return vertexBufferId;
}
private GraphicsByteBuffer currentBuffer = null;
private float[] quadDatas;
@Override
public GLBuffer startWriteGeometry(GeometryHandle geometryindex) {
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, geometryindex.getInternalId());
currentBuffer = new GraphicsByteBuffer();
return currentBuffer;
}
@Override
public void endWriteGeometry(GeometryHandle geometryindex) {
if (currentBuffer != null) {
currentBuffer.writeBuffer();
currentBuffer.position(0);
}
GLES11.glBindBuffer(GLES11.GL_ARRAY_BUFFER, 0);
}
public static final class GraphicsByteBuffer implements
GLDrawContext.GLBuffer {
private static int BUFFER_LENGTH = 1024;
private static ByteBuffer buffer = ByteBuffer.allocateDirect(
BUFFER_LENGTH).order(ByteOrder.nativeOrder());
private static int bufferstart = 0;
private static int bufferlength = 0;
private void assertBufferHas(int remaining) {
if (bufferlength + remaining > BUFFER_LENGTH) {
writeBuffer();
bufferstart += bufferlength;
bufferlength = 0;
buffer.position(0);
}
}
@Override
public void putFloat(float f) {
assertBufferHas(4);
buffer.putFloat(f);
bufferlength += 4;
}
@Override
public void putByte(byte b) {
assertBufferHas(1);
buffer.put(b);
bufferlength++;
}
@Override
public void position(int position) {
if (bufferstart + bufferlength != position) {
if (bufferlength > 0) {
writeBuffer();
}
bufferstart = position;
bufferlength = 0;
buffer.position(0);
}
}
private void writeBuffer() {
buffer.position(0);
GLES11.glBufferSubData(GLES11.GL_ARRAY_BUFFER, bufferstart,
bufferlength, buffer);
}
}
public Context getAndroidContext() {
return context;
}
public void invalidateContext() {
// TODO invalidate context
}
}