blob: cff3d3cd3c1d2ab515a86caad01b378f2d16187f [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.gl;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* Allows copying a GL texture to a {@link RenderTarget}.
*/
// TODO: Document this class a bit more and add a test for the class.
public class CopyShader {
private static final String VERTEX_SHADER =
"attribute vec4 a_position;\n" +
"attribute vec2 a_texcoord;\n" +
"varying vec2 v_texcoord;\n" +
"void main() {\n" +
" gl_Position = a_position;\n" +
" v_texcoord = a_texcoord;\n" +
"}\n";
private static final String FRAGMENT_SHADER =
"precision mediump float;\n" +
"uniform sampler2D tex_sampler;\n" +
"varying vec2 v_texcoord;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" +
"}\n";
private static final String FRAGMENT_SHADER_EXTERNAL =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"uniform samplerExternalOES tex_sampler;\n" +
"varying vec2 v_texcoord;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" +
"}\n";
private static final float[] SOURCE_COORDS = new float[] {
0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f };
private static final float[] TARGET_COORDS = new float[] {
-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f };
private final int mProgram;
private final int mTextureTarget;
private final FloatBuffer mSourceCoords;
private final FloatBuffer mTargetCoords;
private final int mSourceAttrib;
private final int mTargetAttrib;
private final int mTextureUniform;
private CopyShader(int program, int textureTarget) {
mProgram = program;
mTextureTarget = textureTarget;
mSourceCoords = readFloats(SOURCE_COORDS);
mTargetCoords = readFloats(TARGET_COORDS);
mSourceAttrib = GLES20.glGetAttribLocation(mProgram, "a_texcoord");
mTargetAttrib = GLES20.glGetAttribLocation(mProgram, "a_position");
mTextureUniform = GLES20.glGetUniformLocation(mProgram, "tex_sampler");
}
/**
* Compiles a new shader that is valid in the current context.
*
* @return a new shader instance that is valid in the current context
*/
public static CopyShader compileNewShader() {
return new CopyShader(createProgram(VERTEX_SHADER, FRAGMENT_SHADER), GLES20.GL_TEXTURE);
}
/**
* Compiles a new shader that binds textures as GL_TEXTURE_EXTERNAL_OES.
*
* @return a new shader instance that is valid in the current context
*/
public static CopyShader compileNewExternalShader() {
return new CopyShader(createProgram(VERTEX_SHADER, FRAGMENT_SHADER_EXTERNAL),
GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
}
/**
* Sets the 4x4 transform matrix to apply when copying the texture.
* <p/>
* Note: Non-affine components of the transformation are ignored.
*
* @param matrix a 16-length float array containing the transform matrix in
* column-major order.
*/
public void setTransform(float[] matrix) {
/**
* Multiply transformation matrix by column vectors (s,t, 0, 1) for
* coordinates {(0,0),(1,0), (0,1), (1,1) } and store (s,t) values of
* the resulting matrix in column-major order.
*/
float[] coords = new float[] {
matrix[12], matrix[13],
matrix[0] + matrix[12],
matrix[1] + matrix[13],
matrix[4] + matrix[12],
matrix[5] + matrix[13],
matrix[0] + matrix[4] + matrix[12],
matrix[1] + matrix[5] + matrix[13]
};
mSourceCoords.put(coords).position(0);
}
/**
* Renders the specified texture to the specified target.
*
* @param texName name of a valid texture
* @param target to render into
* @param width of output
* @param height of output
*/
public void renderTextureToTarget(int texName, RenderTarget target, int width, int height) {
useProgram();
focusTarget(target, width, height);
assignAttribute(mSourceAttrib, mSourceCoords);
assignAttribute(mTargetAttrib, mTargetCoords);
bindTexture(mTextureUniform, texName, mTextureTarget);
render();
}
/**
* Releases the current shader.
* <p>
* This must be called in the shader's GL thread.
*/
public void release() {
GLES20.glDeleteProgram(mProgram);
}
private void focusTarget(RenderTarget target, int width, int height) {
target.focus();
GLES20.glViewport(0, 0, width, height);
GLToolbox.checkGlError("focus");
}
private void useProgram() {
GLES20.glUseProgram(mProgram);
GLToolbox.checkGlError("glUseProgram");
}
private void render() {
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLToolbox.checkGlError("glDrawArrays");
}
private void bindTexture(int uniformName, int texName, int texTarget) {
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(texTarget, texName);
GLES20.glUniform1i(uniformName, 0);
GLToolbox.checkGlError(
"bindTexture(" + uniformName + "," + texName + "," + texTarget + ")");
}
private void assignAttribute(int index, FloatBuffer values) {
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
GLES20.glVertexAttribPointer(index, 2, GLES20.GL_FLOAT, false, 8, values);
GLES20.glEnableVertexAttribArray(index);
GLToolbox.checkGlError("glVertexAttribPointer(" + index + ")");
}
private static FloatBuffer readFloats(float[] values) {
FloatBuffer result = ByteBuffer.allocateDirect(values.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
result.put(values).position(0);
return result;
}
private static int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader != 0) {
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
String info = GLES20.glGetShaderInfoLog(shader);
GLES20.glDeleteShader(shader);
shader = 0;
throw new RuntimeException("Could not compile shader " + shaderType + ":" + info);
}
}
return shader;
}
private static int createProgram(String vertexSource, String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
throw new RuntimeException("Could not create shader-program as vertex shader "
+ "could not be compiled!");
}
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
throw new RuntimeException("Could not create shader-program as fragment shader "
+ "could not be compiled!");
}
int program = GLES20.glCreateProgram();
if (program != 0) {
GLES20.glAttachShader(program, vertexShader);
GLToolbox.checkGlError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
GLToolbox.checkGlError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
String info = GLES20.glGetProgramInfoLog(program);
GLES20.glDeleteProgram(program);
program = 0;
throw new RuntimeException("Could not link program: " + info);
}
}
GLES20.glDeleteShader(vertexShader);
GLES20.glDeleteShader(pixelShader);
return program;
}
}