/* * Copyright 2015 Brandon Borkholder * * 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 org.jogamp.glg2d.impl.shader; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import com.jogamp.opengl.GL; import com.jogamp.opengl.GL2ES2; import com.jogamp.opengl.GL2GL3; import com.jogamp.opengl.GL3ES3; public abstract class AbstractShaderPipeline implements ShaderPipeline { protected int vertexShaderId = 0; protected int geometryShaderId = 0; protected int fragmentShaderId = 0; protected String vertexShaderFileName; protected String geometryShaderFileName; protected String fragmentShaderFileName; protected int programId = 0; protected int transformLocation = -1; protected int colorLocation = -1; public AbstractShaderPipeline(String vertexShaderFileName, String geometryShaderFileName, String fragmentShaderFileName) { this.vertexShaderFileName = vertexShaderFileName; this.geometryShaderFileName = geometryShaderFileName; this.fragmentShaderFileName = fragmentShaderFileName; } @Override public void setup(GL2ES2 gl) { createProgramAndAttach(gl); setupUniformsAndAttributes(gl); } @Override public boolean isSetup() { return programId > 0; } public void setColor(GL2ES2 gl, float[] rgba) { if (colorLocation >= 0) { gl.glUniform4fv(colorLocation, 1, rgba, 0); } } public void setTransform(GL2ES2 gl, float[] glMatrixData) { if (transformLocation >= 0) { gl.glUniformMatrix4fv(transformLocation, 1, false, glMatrixData, 0); } } protected void createProgramAndAttach(GL2ES2 gl) { if (gl.glIsProgram(programId)) { delete(gl); } programId = gl.glCreateProgram(); attachShaders(gl); gl.glLinkProgram(programId); checkProgramThrowException(gl, programId, GL2ES2.GL_LINK_STATUS); } protected void setupUniformsAndAttributes(GL2ES2 gl) { // nop } protected void attachShaders(GL2ES2 gl) { if (vertexShaderFileName != null) { vertexShaderId = compileShader(gl, GL2ES2.GL_VERTEX_SHADER, getClass(), vertexShaderFileName); gl.glAttachShader(programId, vertexShaderId); } if (geometryShaderFileName != null) { geometryShaderId = compileShader(gl, GL3ES3.GL_GEOMETRY_SHADER, getClass(), geometryShaderFileName); gl.glAttachShader(programId, geometryShaderId); } if (fragmentShaderFileName != null) { fragmentShaderId = compileShader(gl, GL2ES2.GL_FRAGMENT_SHADER, getClass(), fragmentShaderFileName); gl.glAttachShader(programId, fragmentShaderId); } } @Override public void use(GL2ES2 gl, boolean use) { gl.glUseProgram(use ? programId : 0); } @Override public void delete(GL2ES2 gl) { gl.glDeleteProgram(programId); deleteShaders(gl); programId = 0; } protected void deleteShaders(GL2ES2 gl) { if (gl.glIsShader(vertexShaderId)) { gl.glDeleteShader(vertexShaderId); vertexShaderId = 0; } if (gl.glIsShader(geometryShaderId)) { gl.glDeleteShader(geometryShaderId); geometryShaderId = 0; } if (gl.glIsShader(fragmentShaderId)) { gl.glDeleteShader(fragmentShaderId); fragmentShaderId = 0; } } protected int compileShader(GL2ES2 gl, int type, Class<?> context, String name) throws ShaderException { String[] source = readShader(context, name); int id = compileShader(gl, type, source); checkShaderThrowException(gl, id); return id; } protected int compileShader(GL2ES2 gl, int type, String[] source) throws ShaderException { int id = gl.glCreateShader(type); int[] lineLengths = new int[source.length]; for (int i = 0; i < source.length; i++) { lineLengths[i] = source[i].length(); } gl.glShaderSource(id, source.length, source, lineLengths, 0); int err = gl.glGetError(); if (err != GL.GL_NO_ERROR) { throw new ShaderException("Shader source failed, GL Error: 0x" + Integer.toHexString(err)); } gl.glCompileShader(id); if (err != GL.GL_NO_ERROR) { throw new ShaderException("Compile failed, GL Error: 0x" + Integer.toHexString(err)); } return id; } protected String[] readShader(Class<?> context, String name) throws ShaderException { try { InputStream stream = null; if (context != null) { stream = context.getResourceAsStream(name); } if (stream == null) { stream = AbstractShaderPipeline.class.getResourceAsStream(name); } if (stream == null) { stream = AbstractShaderPipeline.class.getClassLoader().getResourceAsStream(name); } if (stream == null) { throw new NullPointerException("InputStream for " + name + " is null"); } BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); String line = null; List<String> lines = new ArrayList<String>(); while ((line = reader.readLine()) != null) { lines.add(line + "\n"); } stream.close(); return lines.toArray(new String[lines.size()]); } catch (IOException e) { throw new ShaderException("Error reading from stream", e); } } protected void checkShaderThrowException(GL2ES2 gl, int shaderId) { int[] result = new int[1]; gl.glGetShaderiv(shaderId, GL2ES2.GL_COMPILE_STATUS, result, 0); if (result[0] == GL.GL_TRUE) { return; } gl.glGetShaderiv(shaderId, GL2ES2.GL_INFO_LOG_LENGTH, result, 0); int size = result[0]; byte[] data = new byte[size]; gl.glGetShaderInfoLog(shaderId, size, result, 0, data, 0); String error = new String(data, 0, result[0]); throw new ShaderException(error); } protected void checkProgramThrowException(GL2ES2 gl, int programId, int statusFlag) { int[] result = new int[1]; gl.glGetProgramiv(programId, statusFlag, result, 0); if (result[0] == GL.GL_TRUE) { return; } gl.glGetProgramiv(programId, GL2ES2.GL_INFO_LOG_LENGTH, result, 0); int size = result[0]; byte[] data = new byte[size]; gl.glGetProgramInfoLog(programId, size, result, 0, data, 0); String error = new String(data, 0, result[0]); throw new ShaderException(error); } }