Related
Using Android Studio, my code renders an array of floats as a texture passed to GLSL with one float per texel in the range of 0 to 1, like a grayscale texture. For that i use GL_LUMINANCE as the internalFormat and format for glTexImage2D and GL_FLOAT for type. Running the app on an android device emulator works fine (which uses my PC's GPU), but on a real device (Samsung Galaxy S7) calling glTexImage2D gives error 1282, GL_INVALID_OPERATION. I thought it might be a problem with non power of two textures, but the width and height are certainly powers of two.
The code uses Jos Stam fluid simulation C code (compiled with the NDK, not ported) which outputs density values for a grid.
mSizeN is the width (same as height) of the fluid simulation grid, although 2 is added to it by the fluid sim for boundary conditions, so the width of the array returned is mSizeN + 2; 128 in this case.
The coordinate system is set up as an orthographic projection with 0.0,0.0 the top left of the screen, 1.0,1.0 is the bottom right. I just draw a full screen quad and use the interpolated position across the quad in GLSL as texture coordinates to the array containing density values. Nice easy way to render it.
This is the renderer class.
public class GLFluidsimRenderer implements GLWallpaperService.Renderer {
private final String TAG = "GLFluidsimRenderer";
private FluidSolver mFluidSolver = new FluidSolver();
private float[] mProjectionMatrix = new float[16];
private final FloatBuffer mFullScreenQuadVertices;
private Context mActivityContext;
private int mProgramHandle;
private int mProjectionMatrixHandle;
private int mDensityArrayHandle;
private int mPositionHandle;
private int mGridSizeHandle;
private final int mBytesPerFloat = 4;
private final int mStrideBytes = 3 * mBytesPerFloat;
private final int mPositionOffset = 0;
private final int mPositionDataSize = 3;
private int mDensityTexId;
public static int mSizeN = 126;
public GLFluidsimRenderer(final Context activityContext) {
mActivityContext = activityContext;
final float[] fullScreenQuadVerticesData = {
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f,
};
mFullScreenQuadVertices = ByteBuffer.allocateDirect(fullScreenQuadVerticesData.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mFullScreenQuadVertices.put(fullScreenQuadVerticesData).position(0);
}
public void onTouchEvent(MotionEvent event) {
}
#Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
String vertShader = AssetReader.getStringAsset(mActivityContext, "fluidVertShader");
String fragShader = AssetReader.getStringAsset(mActivityContext, "fluidFragDensityShader");
final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertShader);
final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragShader);
mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[] {"a_Position"});
mDensityTexId = TextureHelper.loadTextureLumF(mActivityContext, null, mSizeN + 2, mSizeN + 2);
}
#Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
mFluidSolver.init(width, height, mSizeN);
GLES20.glViewport(0, 0, width, height);
Matrix.setIdentityM(mProjectionMatrix, 0);
Matrix.orthoM(mProjectionMatrix, 0, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f);
}
#Override
public void onDrawFrame(GL10 glUnused) {
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mProgramHandle);
mFluidSolver.step();
TextureHelper.updateTextureLumF(mFluidSolver.get_density(), mDensityTexId, mSizeN + 2, mSizeN + 2);
mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_ProjectionMatrix");
mDensityArrayHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_aDensity");
mGridSizeHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_GridSize");
mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
double start = System.nanoTime();
drawQuad(mFullScreenQuadVertices);
double end = System.nanoTime();
}
private void drawQuad(final FloatBuffer aQuadBuffer) {
// Pass in the position information
aQuadBuffer.position(mPositionOffset);
GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
mStrideBytes, aQuadBuffer);
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Attach density array to texture unit 0
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mDensityTexId);
GLES20.glUniform1i(mDensityArrayHandle, 0);
// Pass in the actual size of the grid.
GLES20.glUniform1i(mGridSizeHandle, mSizeN + 2);
GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
}
Here's the texture helper functions.
public static int loadTextureLumF(final Context context, final float[] data, final int width, final int height) {
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
if (textureHandle[0] != 0) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
(int) width, (int) height, 0, GLES20.GL_LUMINANCE, GLES20.GL_FLOAT,
(data != null ? FloatBuffer.wrap(data) : null));
}
if (textureHandle[0] == 0)
throw new RuntimeException("Error loading texture.");
return textureHandle[0];
}
public static void updateTextureLumF(final float[] data, final int texId, final int w, final int h) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, (int)w, (int)h, GLES20.GL_LUMINANCE, GLES20.GL_FLOAT, (data != null ? FloatBuffer.wrap(data) : null));
}
Fragment shader.
precision mediump float;
uniform sampler2D u_aDensity;
uniform int u_GridSize;
varying vec4 v_Color;
varying vec4 v_Position;
void main()
{
gl_FragColor = texture2D(u_aDensity, vec2(v_Position.x, v_Position.y));
}
Is the combination of GL_FLOAT and GL_LUMINANCE unsupported in OpenGL ES 2?
android emulator pic.
edit:
To add, am i right in saying that each floating point value will be reduced to an 8-bit integer component when transferred with glTexImage2D (etc), so the majority of the floating point precision will be lost? In that case, it might be best to rethink the implementation of the simulator to output fixed point. That can be done easily, Stam even describes it in his paper.
Table 3.4 of the spec shows the "Valid pixel format and type combinations" for use with glTexImage2D. For GL_LUMINANCE, the only option is GL_UNSIGNED_BYTE.
OES_texture_float is the relevant extension you'd need to check for.
An alternative approach which would work on more devices is to pack your data in multiple channels of an RGBA. Here is some discussion about packing a float value into an 8888. Note, however, that not all OpenGLES2 devices even support 8888 render targets, you might have to pack into a 4444.
Or you could use OpenGLES 3. Android is up to 61.3% support of OpenGLES3 according to this.
EDIT: On re-reading more carefully, there probably isn't any benefit in using any higher than an 8-bit texture, because when you write the texture to gl_FragColor in your fragment shader you are copying into a 565 or 8888 framebuffer, so any extra precision is lost anyway at that point.
I wanted to draw a square with OpenGL ES 2.0 and put a dynamic text on it. I am trying to combine the instructions in this post (which I had to port to OpenGL ES 2.0) and the four lesson of Learn OpenGL ES Tutorial.
I have an Activity just using a GLSurfaceView:
public class TexturedSquareDrawActivity extends Activity {
private GLSurfaceView mGLView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new MyGLSurfaceViewTexture(this);
setContentView(mGLView);
}
}
My GLSurfaceView just created the renderer and sets it:
public class MyGLSurfaceViewTexture extends GLSurfaceView {
private final MyGLRendererTexture mRenderer;
public MyGLSurfaceViewTexture(Context context){
super(context);
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
mRenderer = new MyGLRendererTexture(context);
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(mRenderer);
}
}
Then I define a TextureSquare class like this:
public class TexturedSquare {
private final Context mContext;
private FloatBuffer vertexBuffer;
private ShortBuffer drawListBuffer;
private int mProgram;
private 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;" +
"attribute vec2 a_TexCoordinate;" +
"varying vec2 v_TexCoordinate;" +
"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;" +
" v_TexCoordinate = a_TexCoordinate;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform sampler2D u_Texture;" +
"uniform vec4 vColor;" +
"varying vec2 v_TexCoordinate;" +
"void main() {" +
// " gl_FragColor = vColor;" +
" gl_FragColor = vColor * texture2D(u_Texture, v_TexCoordinate);" +
"}";
private int mMVPMatrixHandle;
private int mPositionHandle;
private int mColorHandle;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 3;
private short drawOrder[] = {0, 1, 2, 0, 2, 3}; // order to draw vertices
private final float[] mColor;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
/**
* Store our model data in a float buffer.
*/
private final FloatBuffer mCubeTextureCoordinates;
/**
* This will be used to pass in the texture.
*/
private int mTextureUniformHandle;
/**
* This will be used to pass in model texture coordinate information.
*/
private int mTextureCoordinateHandle;
/**
* Size of the texture coordinate data in elements.
*/
private final int mTextureCoordinateDataSize = 2;
/**
* This is a handle to our texture data.
*/
private int mTextureDataHandle;
// S, T (or X, Y)
// Texture coordinate data.
// Because images have a Y axis pointing downward (values increase as you move down the image) while
// OpenGL has a Y axis pointing upward, we adjust for that here by flipping the Y axis.
// What's more is that the texture coordinates are the same for every face.
final float[] cubeTextureCoordinateData =
{
// Front face
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
};
public TexturedSquare(Context context, final float[] squareCoords, final float[] color) {
mContext = context;
mColor = color;
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
mCubeTextureCoordinates = ByteBuffer.allocateDirect(cubeTextureCoordinateData.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mCubeTextureCoordinates.put(cubeTextureCoordinateData).position(0);
linkShaderCode();
}
private void linkShaderCode() {
int vertexShader = MyGLRendererTexture.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRendererTexture.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();
// add the vertex shader to program
GLES20.glAttachShader(mProgram, vertexShader);
// add the fragment shader to program
GLES20.glAttachShader(mProgram, fragmentShader);
// creates OpenGL ES program executables
GLES20.glLinkProgram(mProgram);
}
public void draw(float[] mvpMatrix) {
// Add program to OpenGL ES environment
GLES20.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the square coordinate data
// Tell OpenGL how to handle the data in the vertexBuffer
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the square
// Pass the color to the shader
GLES20.glUniform4fv(mColorHandle, 1, mColor, 0);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// Pass the projection and view transformation to the shader
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
// Load the texture
// mTextureDataHandle = TextureHelper.loadTexture(mContext, R.drawable.background);
mTextureDataHandle = TextureHelper.loadText(mContext, "01234");
mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, "u_Texture");
mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "a_TexCoordinate");
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false,
0, mCubeTextureCoordinates);
// Set the active texture unit to texture unit 0.
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// Bind the texture to this unit.
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
// Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
GLES20.glUniform1i(mTextureUniformHandle, 0);
GLES20.glDrawElements(
GLES20.GL_TRIANGLES, drawOrder.length,
GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
}
My renderer draws two squares. The first one shall be textured:
public class MyGLRendererTexture implements GLSurfaceView.Renderer {
// mMVPMatrix is an abbreviation for "Model View Projection Matrix"
private final float[] mMVPMatrix = new float[16];
private final float[] mProjectionMatrix = new float[16];
private final float[] mViewMatrix = new float[16];
private final Context mContext;
private TexturedSquare mTexturedSquare;
private TexturedSquare mTexturedSquare2;
static float squareCoords[] = {
-0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f}; // top right
// Set color with red, green, blue and alpha (opacity) values
float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
static float squareCoords2[] = {
-1.0f, 0.7f, 0.0f, // top left
-1.0f, 0.8f, 0.0f, // bottom left
-0.8f, 0.8f, 0.0f, // bottom right
-0.8f, 0.7f, 0.0f}; // top right
// Set color with red, green, blue and alpha (opacity) values
float color2[] = {0.11111111f, 0.26953125f, 0.52265625f, 1.0f};
public MyGLRendererTexture(
Context context) {
mContext = context;
}
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;
}
#Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// initialize a triangle
// initialize a square
mTexturedSquare = new TexturedSquare(mContext, squareCoords, color);
mTexturedSquare2 = new TexturedSquare(mContext, squareCoords2, color2);
}
#Override
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
// in the onDrawFrame() method
Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
}
#Override
public void onDrawFrame(GL10 unused) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// Set the 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);
mTexturedSquare.draw(mMVPMatrix);
mTexturedSquare2.draw(mMVPMatrix);
}
}
And finally I have a helper class defining helper methods I am using in the upper code.
public class TextureHelper {
public static int loadTexture(final Context context, final int resourceId) {
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
if (textureHandle[0] != 0) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false; // No pre-scaling
// Read in the resource
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
// Bind to the texture in OpenGL
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
// Load the bitmap into the bound texture.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// Recycle the bitmap, since its data has been loaded into OpenGL.
bitmap.recycle();
}
if (textureHandle[0] == 0) {
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
public static int loadText(final Context context, String text) {
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
if (textureHandle[0] != 0) {
// Create an empty, mutable bitmap
Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_4444);
// get a canvas to paint over the bitmap
Canvas canvas = new Canvas(bitmap);
bitmap.eraseColor(0);
// get a background image from resources
// note the image format must match the bitmap format
Drawable background = context.getResources().getDrawable(R.drawable.background);
background.setBounds(0, 0, 256, 256);
background.draw(canvas); // draw the background to our bitmap
// Draw the text
Paint textPaint = new Paint();
textPaint.setTextSize(32);
textPaint.setAntiAlias(true);
textPaint.setARGB(0xff, 0x00, 0x00, 0x00);
// draw the text centered
canvas.drawText(text, 16,112, textPaint);
// Bind to the texture in OpenGL
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
// Load the bitmap into the bound texture.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// Recycle the bitmap, since its data has been loaded into OpenGL.
bitmap.recycle();
}
if (textureHandle[0] == 0) {
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
}
But the texture is drawn for both triangles the square consists of. How can I just draw the texture once, placed horizontally within in the square?
I understand that the square is drawn by drawing two triangles. And I understand, that the texture is placed the same way. But I don't know how to tell OpenGL to place this texture only once within the square.
EDIT:
I have now edited the texture coordinates to:
final float[] cubeTextureCoordinateData =
{
-0.5f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f
}
resulting in this:
These coordinates:
-1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
result in this:
These coordinates:
0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
-0.5f, -0.5f
result in this:
And these coordinates:
1.0f, -1.0f,
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f
result in this:
So the 4th approach seems to be "the most right one". There the text is drawn at bottom right. It even seems that my square is divided into 4 smaller squares. Because as a texture I use this picture:
Why is this divided into four parts?
GLES sets textures to repeat by default so you need to change the parameter.
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
Also the tutorials you are using are pretty good here is the opengl es documentation which is pretty helpful. https://www.khronos.org/opengles/sdk/docs/man/
To draw any UI component in openGL you need to create canvas and use that into openGL.
Bitmap textedBitmap = drawTextToBitmap();
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, textedBitmap, 0);
private Bitmap drawTextToBitmap() {
// TODO Auto-generated method stub
Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_4444);
// get a canvas to paint over the bitmap
Canvas canvas = new Canvas(bitmap);
bitmap.eraseColor(android.graphics.Color.TRANSPARENT);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
TextPaint textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
Paint textPaint = new Paint();
textPaint.setStyle(Paint.Style.FILL);
textPaint.setAntiAlias(true);
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(10);
TextView tv = new TextView(context);
tv.setTextColor(Color.BLACK);
tv.setTextSize(10);
String text = "DEMO TEXT";
tv.setText(text);
tv.setEllipsize(TextUtils.TruncateAt.END);
tv.setMaxLines(4);
tv.setGravity(Gravity.BOTTOM);
tv.setPadding(8, 8, 8, 50);
tv.setDrawingCacheEnabled(true);
tv.measure(MeasureSpec.makeMeasureSpec(canvas.getWidth(),
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
canvas.getHeight(), MeasureSpec.EXACTLY));
tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());
LinearLayout parent = null;
if (bitmap != null && !bitmap.isRecycled()) {
parent = new LinearLayout(context);
parent.setDrawingCacheEnabled(true);
parent.measure(MeasureSpec.makeMeasureSpec(canvas.getWidth(),
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
canvas.getHeight(), MeasureSpec.EXACTLY));
parent.layout(0, 0, parent.getMeasuredWidth(),
parent.getMeasuredHeight());
parent.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
parent.setOrientation(LinearLayout.VERTICAL);
parent.setBackgroundColor(context.getResources().getColor(R.color.transpernt));
parent.addView(tv);
} else {
// write code to recreate bitmap from source
// Write code to show bitmap to canvas
}
canvas.drawBitmap(parent.getDrawingCache(), 0, 0, textPaint);
tv.setDrawingCacheEnabled(false);
iv.setDrawingCacheEnabled(false);
parent.setDrawingCacheEnabled(false);
return bitmap;
}
I'm trying to create a render to texture class that has a framebuffer for position and a framebuffer for rotation. That part works great. But when I switch out and back to my app it crashes because checking the framebuffer status returns frameBuffer incomplete missing attachment.
public void setup(Context context) {
final float eyeX = 0.0f;
final float eyeY = 0.0f;
final float eyeZ = 1.5f;
final float lookX = 0.0f;
final float lookY = 0.0f;
final float lookZ = -5.0f;
final float upX = 0.0f;
final float upY = 1.0f;
final float upZ = 0.0f;
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER,
ShaderHelper.readShader(context, R.raw.vertex_shader));
int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER,
ShaderHelper.readShader(context, R.raw.fragment_shader));
mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[] {"a_Position", "a_TexCoord"});
vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER,
ShaderHelper.readShader(context, R.raw.particle_vertex_shader));
fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER,
ShaderHelper.readShader(context, R.raw.particle_fragment_shader));
mPartProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[] {"a_Position"});
// create the ints for the framebuffer, depth render buffer and texture
final int[] fb = new int[2];
// generate
GLES20.glGenFramebuffers(2, fb, 0);
if (!setup) {
posTex = ShaderHelper.genTexture(texW, texH, null);
velTex = ShaderHelper.genTexture(texW, texH, null);
rotTex = ShaderHelper.genTexture(texW, texH, null);
rotVelTex = ShaderHelper.genTexture(texW, texH, null);
atlasTex = ShaderHelper.loadTexture(context, R.drawable.ship_atlas);
}
posFb = fb[0];
rotFb = fb[1];
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, posFb);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, posTex, 0);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, rotFb);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, rotTex, 0);
final int buffers[] = new int[2];
GLES20.glGenBuffers(2, buffers, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, spriteBuffer.capacity() * BYTES_PER_FLOAT, spriteBuffer, GLES20.GL_STATIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[1]);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, renderQuad.capacity() * BYTES_PER_FLOAT, renderQuad, GLES20.GL_STATIC_DRAW);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix");
mAtlasTexHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_TexAtlas");
mPosTexHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_PosTexture");
mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
mTexCoordHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoord");
mXTexHandle = GLES20.glGetUniformLocation(mPartProgramHandle, "u_Values");
mDXTexHandle = GLES20.glGetUniformLocation(mPartProgramHandle, "u_Transforms");
mPassHandle = GLES20.glGetUniformLocation(mPartProgramHandle, "u_Pass");
mFBPositionHandle = GLES20.glGetAttribLocation(mPartProgramHandle, "a_Position");
spriteBufferIdx = buffers[0];
fbQuadBufferIdx = buffers[1];
setup = true;
}
From what I have researched, textures do not need to be reloaded and only the framebuffers do. Which is good because then I would have to somehow put the pixels from the old texture into the new texture. But without remaking the textures it crashes with the framebuffer error. To make it clear, the code is ran everytime onSurfaceChanged is called.
How can I make it so I dont have to generate a new texture each time onSurfaceChanged is called without getting a FrameBuffer Incomplete Missing Attachment error?
Since android recreates the context every time it is paused I needed to add this line
glSurfaceView.setPreserveEGLContextOnPause(true);
I'm trying to create a frameBuffer using a floating point texture and everything has been working fine, the frameBuffer lists as a complete attachment. But now I seem to be getting this error and can not draw into my texture.
07-22 21:00:15.180: W/Adreno-ES20(27203): <validate_render_targets:454>: GL_INVALID_OPERATION
I have no idea what it means by validate render targets or how it could be an invalid operation. Google does not appear to be my friend either in this matter as I return no results as to what it is. I cant seem to find anything within the opengl sdk docs either.
Heres the code for setting up my framebuffer
public void setup(Context context) {
final float eyeX = 0.0f;
final float eyeY = 0.0f;
final float eyeZ = 1.5f;
final float lookX = 0.0f;
final float lookY = 0.0f;
final float lookZ = -5.0f;
final float upX = 0.0f;
final float upY = 1.0f;
final float upZ = 0.0f;
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER,
ShaderHelper.readShader(context, R.raw.vertex_shader));
int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER,
ShaderHelper.readShader(context, R.raw.fragment_shader));
mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[] {"a_Position", "a_TexCoord"});
vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER,
ShaderHelper.readShader(context, R.raw.particle_vertex_shader));
fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER,
ShaderHelper.readShader(context, R.raw.particle_fragment_shader));
mPartProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[] {"a_Position"});
// create the ints for the framebuffer, depth render buffer and texture
int[] fb, renderTex; // the framebuffer, the renderbuffer and the texture to render
fb = new int[1];
renderTex = new int[2];
// generate
GLES20.glGenFramebuffers(1, fb, 0);
GLES20.glGenTextures(2, renderTex, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, renderTex[0]);
// parameters - we have to make sure we clamp the textures to the edges
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
int GL_OES_texture_half_float = 0x8D61;
int GL_RGBA32F = 0x8814;
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GL_RGBA32F, texW, texH, 0, GLES20.GL_RGBA, GLES20.GL_FLOAT, null);
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb[0]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, renderTex[0], 0);
// ShaderHelper.saveTexture(renderTex[0], texW, texH, "test");
posTex = renderTex[0];
posFb = fb[0];
final int buffers[] = new int[2];
GLES20.glGenBuffers(2, buffers, 0);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[0]);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, spriteBuffer.capacity() * BYTES_PER_FLOAT, spriteBuffer, GLES20.GL_STATIC_DRAW);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, buffers[1]);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, renderQuad.capacity() * BYTES_PER_FLOAT, renderQuad, GLES20.GL_STATIC_DRAW);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix");
mTransformsHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_Transforms");
mTexDataHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_TexData");
mTexUniformHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_Texture");
mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
mTexCoordHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoord");
mPosTexHandle = GLES20.glGetUniformLocation(mPartProgramHandle, "u_Positions");
mFBPositionHandle = GLES20.glGetAttribLocation(mPartProgramHandle, "a_Position");
spriteBufferIdx = buffers[0];
fbQuadBufferIdx = buffers[1];
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
and here is the actual draw code
private void renderToTexture() {
GLES20.glViewport(0, 0, this.texW, this.texH);
// Bind the framebuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, posFb);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, fbQuadBufferIdx);
// check status
int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (status != GLES20.GL_FRAMEBUFFER_COMPLETE)
throw new RuntimeException("FrameBuffer Error; Status = " + status);
transforms[0] = Fields.screen_width / 2;
transforms[1] = Fields.screen_height / 2;
transforms[2] = 0;
transforms[3] = Fields.screen_height;
pixels.put(transforms).position(0);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, 1, 1, GLES20.GL_RGBA, GLES20.GL_FLOAT, pixels);
GLES20.glReadPixels(0, 0, 1, 1, GLES20.GL_RGBA, GLES20.GL_FLOAT, pixels);
Log.d("X", "X=" + pixels.get(0));
GLES20.glClearColor(.0f, .0f, .0f, 1.0f);
GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mPartProgramHandle);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, posTex);
GLES20.glUniform1i(mPosTexHandle, 0);
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glVertexAttribPointer(mPositionHandle, 2, GLES20.GL_FLOAT, false, 8, 0);
// Clear the currently bound buffer (so future OpenGL calls do not use this buffer).
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
// Draw the cubes.
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
// if (!saved)
// ShaderHelper.saveTexture(posTex, texW, texH, "test2");
// saved = true;
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
Any idea how I am generating this error or advice for debugging this in the future?
I have small cubes that make up a grid to make 3D cube. On each small cube I use a bitmap to texture the surface, but I want to use more then one picture. I can build more textures within loadTextures and add them tofinal int[] textureHandle = new int[1]; and return them. How do I instantiate them to each small cube I'm drawing though?
#Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{
mLastRequestedCubeFactor = mActualCubeFactor = 3;
generateCubes(mActualCubeFactor, false, false);
// Set the background clear color to black.
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// Use culling to remove back faces.
GLES20.glEnable(GLES20.GL_CULL_FACE);
// Enable depth testing
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
// Position the eye in front of the origin.
final float eyeX = 0.0f;
final float eyeY = 0.0f;
final float eyeZ = -0.5f;
// We are looking toward the distance
final float lookX = 0.0f;
final float lookY = 0.0f;
final float lookZ = -5.0f;
// Set our up vector. This is where our head would be pointing were we holding the camera.
final float upX = 0.0f;
final float upY = 1.0f;
final float upZ = 0.0f;
// Set the view matrix. This matrix can be said to represent the camera position.
// NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
// view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);
final String vertexShader = RawResourceReader.readTextFileFromRawResource(mLessonSevenActivity, R.raw.lesson_seven_vertex_shader);
final String fragmentShader = RawResourceReader.readTextFileFromRawResource(mLessonSevenActivity, R.raw.lesson_seven_fragment_shader);
final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);
final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);
mProgramHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,
new String[] {"a_Position", "a_Normal", "a_TexCoordinate"});
// Load the texture
mAndroidDataHandle = TextureHelper.loadTexture(mLessonSevenActivity, R.drawable.usb_android);
GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mAndroidDataHandle);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mAndroidDataHandle);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
// Initialize the accumulated rotation matrix
Matrix.setIdentityM(mAccumulatedRotation, 0);
}
public class TextureHelper
{
public static int loadTexture(final Context context, final int resourceId)
{
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
if (textureHandle[0] != 0)
{
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false; // No pre-scaling
// Read in the resource
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
// Bind to the texture in OpenGL
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
// Load the bitmap into the bound texture.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// Recycle the bitmap, since its data has been loaded into OpenGL.
bitmap.recycle();
}
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
}
How do I instantiate them to each small cube I'm drawing though?
In short, you don't.
Even in desktop GL, where vertex instancing is a core feature, there is no way to change texture bindings without splitting the draw into multiple draw calls.
You could use a texture atlas, array texture or geometry shader to sample from a different (already bound) texture or a different part of a single texture. Alternatively, you could use bindless textures. Each one of those things I mentioned requires a newer version of GL than the last.
The only way to do this in ES is going to be either multiple draw calls, or a texture atlas/binding textures to multiple texture units. But since instancing is not a core feature, computing the texture coordinate / unit dynamically is a tremendous pain and will involve duplicating vertex data.
The bottom line is, what do you really mean by instantiated cube? Are you trying to draw 500 cubes in a single operation, or are you drawing them separately by calling some method in your cube class? Instancing has different meanings depending on the context.