app/src/main/java/puscas/mobilertapp/utils/UtilsShader.java
package puscas.mobilertapp.utils;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.util.Map;
import java.util.logging.Logger;
import puscas.mobilertapp.configs.ConfigGlAttribute;
import puscas.mobilertapp.exceptions.FailureException;
/**
* Utility class with some helper methods to create GLSL programs and load GLSL shaders.
*/
public final class UtilsShader {
/**
* Logger for this class.
*/
private static final Logger logger = Logger.getLogger(UtilsShader.class.getSimpleName());
/**
* Private constructor to avoid creating instances.
*/
private UtilsShader() {
throw new UnsupportedOperationException("Not implemented.");
}
/**
* Helper method that attaches some GLSL shaders into an OpenGL program.
*
* @param shaderProgram The index of OpenGL shader program.
* @param shadersCode The shaders' code.
*/
public static void attachShaders(final int shaderProgram,
@NonNull final Map<Integer, String> shadersCode) {
logger.info("attachShaders");
loadAndAttachShaders(shaderProgram, shadersCode);
final int[] attachedShaders = new int[1];
UtilsGL.run(() -> GLES20.glGetProgramiv(shaderProgram, GLES20.GL_ATTACHED_SHADERS, attachedShaders, 0));
checksShaderLinkStatus(shaderProgram);
}
/**
* Helper method that checks the OpenGL shader link status in a program.
*
* @param shaderProgram The OpenGL shader program index.
*/
public static void checksShaderLinkStatus(final int shaderProgram) {
logger.info("checksShaderLinkStatus");
final int[] linkStatus = new int[1];
UtilsGL.run(() -> GLES20.glGetProgramiv(shaderProgram, GLES20.GL_LINK_STATUS, linkStatus, 0));
if (linkStatus[0] != GLES20.GL_TRUE) {
final String strError = UtilsGL.run(shaderProgram, GLES20::glGetProgramInfoLog);
final String msg = "Could not link program shader: " + strError;
UtilsGL.run(() -> GLES20.glDeleteProgram(shaderProgram));
throw new FailureException(msg);
}
}
/**
* Helper method which loads an OpenGL shader.
*
* @param shaderType The type of the shader (vertex or fragment shader).
* @param source The code of the shader.
* @return The OpenGL index of the shader.
*/
@VisibleForTesting
public static int loadShader(final int shaderType,
@NonNull final String source) {
logger.info("loadShader");
final int shader = UtilsGL.run(() -> GLES20.glCreateShader(shaderType));
if (shader == 0) {
logger.info("loadShader error 1");
final int glError = GLES20.glGetError();
final String msgError = GLUtils.getEGLErrorString(glError);
final String msg = "There was an error while creating the shader object: (" + glError + ") " + msgError;
throw new FailureException(msg);
}
UtilsGL.run(() -> GLES20.glShaderSource(shader, source));
UtilsGL.run(() -> GLES20.glCompileShader(shader));
final int[] compiled = new int[1];
UtilsGL.run(() -> GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0));
if (compiled[0] == 0) {
logger.info("loadShader error 2");
final int glError = GLES20.glGetError();
final String informationLog = UtilsGL.run(() -> GLES20.glGetShaderInfoLog(shader));
final String msg = "Could not compile shader " + shaderType + ": " + informationLog;
final String msgError = GLUtils.getEGLErrorString(glError);
logger.severe(msg);
logger.severe(source);
logger.severe("Error: " + msgError);
UtilsGL.run(() -> GLES20.glDeleteShader(shader));
UtilsGL.run(GLES20::glReleaseShaderCompiler);
throw new FailureException(informationLog);
}
logger.info("loadShader finish");
return shader;
}
/**
* Helper method that initializes a GLSL program.
* It deletes the previous GLSL program if it was created.
*
* @param shaderProgram The OpenGL shader program index to recreate.
* @return A new created OpenGL shader program index.
*/
public static int reCreateProgram(final int shaderProgram) {
logger.info("reCreateProgram");
if (shaderProgram != 0) {
final String deleteProgramMessage = "Deleting GL program: " + shaderProgram;
logger.info(deleteProgramMessage);
UtilsGL.run(() -> GLES20.glDeleteProgram(shaderProgram));
}
final int newShaderProgram = UtilsGL.<Integer>run(GLES20::glCreateProgram);
if (newShaderProgram == 0) {
logger.severe("Could not create GL program.");
final String programInfo = GLES20.glGetProgramInfoLog(0);
throw new FailureException(programInfo);
}
final String createdProgramMessage = "GL program created: " + newShaderProgram;
logger.info(createdProgramMessage);
return newShaderProgram;
}
/**
* Helper method that binds and enables an OpenGL attribute.
*
* @param shaderProgram The shader program index.
* @param config The {@link ConfigGlAttribute} configurator.
*/
public static void connectOpenGlAttribute(final int shaderProgram,
@NonNull final ConfigGlAttribute config) {
logger.info("connectOpenGlAttribute");
UtilsGL.run(() -> GLES20.glBindAttribLocation(
shaderProgram, config.getAttributeLocation(), config.getAttributeName()));
UtilsGL.run(() -> GLES20.glVertexAttribPointer(config.getAttributeLocation(),
config.getComponentsInBuffer(), GLES20.GL_FLOAT, false, 0, config.getBuffer()));
UtilsGL.run(() -> GLES20.glEnableVertexAttribArray(config.getAttributeLocation()));
}
/**
* Helper method that loads a vertex and a fragment shaders and attach those
* to a GLSL program.
*
* @param shaderProgram The index of OpenGL shader program.
* @param shadersCode The shaders' code.
*/
public static void loadAndAttachShaders(final int shaderProgram,
@NonNull final Map<Integer, String> shadersCode) {
logger.info("loadAndAttachShaders");
final int vertexShader = getShaderIndex(shadersCode, GLES20.GL_VERTEX_SHADER);
final int fragmentShader = getShaderIndex(shadersCode, GLES20.GL_FRAGMENT_SHADER);
// Attach and link shaders to program
UtilsGL.run(() -> GLES20.glAttachShader(shaderProgram, vertexShader));
UtilsGL.run(() -> GLES20.glAttachShader(shaderProgram, fragmentShader));
UtilsGL.run(() -> GLES20.glLinkProgram(shaderProgram));
UtilsGL.run(() -> GLES20.glDeleteShader(vertexShader));
UtilsGL.run(() -> GLES20.glDeleteShader(fragmentShader));
}
/**
* Helper method which loads an OpenGL shader and returns the OpenGL index of the shader.
*
* @param shadersCode The shaders' code.
* @param shaderType The type of the shader (vertex or fragment shader).
* @return The OpenGL index of the shader.
*/
private static int getShaderIndex(@NonNull final Map<Integer, String> shadersCode, final int shaderType) {
final String shaderCode = shadersCode.get(shaderType);
Preconditions.checkNotNull(shaderCode, "shaderCode shouldn't be null");
return loadShader(shaderType, shaderCode);
}
}