/******************************************************************************* * Copyright 2012 Geoscience Australia * * 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 au.gov.ga.earthsci.worldwind.common.render; import gov.nasa.worldwind.util.Logging; import java.io.IOException; import java.io.InputStream; import java.util.logging.Level; import javax.media.opengl.GL; import javax.media.opengl.GL2; import au.gov.ga.earthsci.worldwind.common.util.IOUtil; /** * Abstract base class for shaders. Handles the OpenGL setup for GLSL shaders. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public abstract class Shader { protected int shaderProgram = 0; protected int vertexShader = 0; protected int fragmentShader = 0; protected int geometryShader = 0; protected boolean creationFailed = false; /** * @return An {@link InputStream} containing the GLSL vertex shader string */ protected abstract InputStream getVertexSource(); /** * @return An {@link InputStream} containing the GLSL fragment shader string */ protected abstract InputStream getFragmentSource(); /** * @return An {@link InputStream} containing the GLSL geometry shader string */ protected InputStream getGeometrySource() { return null; } /** * Locate the uniforms for this shader * * @param gl */ protected abstract void getUniformLocations(GL2 gl); /** * @return Value to pass to glProgramParameteriARB for * GL_GEOMETRY_INPUT_TYPE_ARB */ protected int getGeometryInputType() { return GL2.GL_TRIANGLES; } /** * @return Value to pass to glProgramParameteriARB for * GL_GEOMETRY_OUTPUT_TYPE_ARB */ protected int getGeometryOutputType() { return GL2.GL_TRIANGLE_STRIP; } /** * @return Value to pass to glProgramParameteriARB for * GL_GEOMETRY_VERTICES_OUT_ARB */ protected int getGeometryVerticesOut() { return 3; } /** * Setup this GLSL shader in OpenGL * * @param gl * @return Was this shader created? */ public final boolean create(GL2 gl) { if (isCreated() || creationFailed) { return false; } try { InputStream vertex = getVertexSource(); InputStream fragment = getFragmentSource(); InputStream geometry = getGeometrySource(); String vsrc = null, fsrc = null, gsrc = null; try { vsrc = IOUtil.readStreamToStringKeepingNewlines(vertex, null); fsrc = IOUtil.readStreamToStringKeepingNewlines(fragment, null); gsrc = geometry == null ? null : IOUtil.readStreamToStringKeepingNewlines(geometry, null); } catch (IOException e) { throw new ShaderException("Error reading shader source", e); } String[] defines = getDefines(); vsrc = insertDefines(vsrc, defines, "_VERTEX_"); fsrc = insertDefines(fsrc, defines, "_FRAGMENT_"); gsrc = gsrc == null ? null : insertDefines(gsrc, defines, "_GEOMETRY_"); vertexShader = gl.glCreateShader(GL2.GL_VERTEX_SHADER); if (vertexShader <= 0) { delete(gl); throw new ShaderException("Error creating vertex shader"); } fragmentShader = gl.glCreateShader(GL2.GL_FRAGMENT_SHADER); if (fragmentShader <= 0) { delete(gl); throw new ShaderException("Error creating fragment shader"); } if (gsrc != null) { geometryShader = gl.glCreateShader(GL2.GL_GEOMETRY_SHADER_ARB); if (geometryShader <= 0) { delete(gl); throw new ShaderException("Error creating geometry shader"); } } gl.glShaderSource(vertexShader, 1, new String[] { vsrc }, new int[] { vsrc.length() }, 0); gl.glCompileShader(vertexShader); gl.glShaderSource(fragmentShader, 1, new String[] { fsrc }, new int[] { fsrc.length() }, 0); gl.glCompileShader(fragmentShader); if (gsrc != null) { gl.glShaderSource(geometryShader, 1, new String[] { gsrc }, new int[] { gsrc.length() }, 0); gl.glCompileShader(geometryShader); } shaderProgram = gl.glCreateProgram(); if (shaderProgram <= 0) { delete(gl); throw new ShaderException("Error creating shader program"); } gl.glAttachShader(shaderProgram, vertexShader); gl.glAttachShader(shaderProgram, fragmentShader); if (gsrc != null) { gl.glAttachShader(shaderProgram, geometryShader); gl.glProgramParameteriARB(shaderProgram, GL2.GL_GEOMETRY_INPUT_TYPE_ARB, getGeometryInputType()); gl.glProgramParameteriARB(shaderProgram, GL2.GL_GEOMETRY_OUTPUT_TYPE_ARB, getGeometryOutputType()); gl.glProgramParameteriARB(shaderProgram, GL2.GL_GEOMETRY_VERTICES_OUT_ARB, getGeometryVerticesOut()); } gl.glLinkProgram(shaderProgram); gl.glValidateProgram(shaderProgram); int[] status = new int[1]; gl.glGetProgramiv(shaderProgram, GL2.GL_VALIDATE_STATUS, status, 0); if (status[0] != GL2.GL_TRUE) { StringBuilder message = new StringBuilder(); int maxLength = 10240; int[] length = new int[1]; byte[] bytes = new byte[maxLength]; gl.glGetProgramInfoLog(shaderProgram, maxLength, length, 0, bytes, 0); if (length[0] > 0) { String programInfo = new String(bytes, 0, length[0]); message.append("Program info: " + programInfo); } gl.glGetShaderInfoLog(vertexShader, maxLength, length, 0, bytes, 0); if (length[0] > 0) { String vertexInfo = new String(bytes, 0, length[0]); message.append("Vertex shader info: " + vertexInfo); } gl.glGetShaderInfoLog(fragmentShader, maxLength, length, 0, bytes, 0); if (length[0] > 0) { String fragmentInfo = new String(bytes, 0, length[0]); message.append("Fragment shader info: " + fragmentInfo); } if (gsrc != null) { gl.glGetShaderInfoLog(geometryShader, maxLength, length, 0, bytes, 0); if (length[0] > 0) { String geometryInfo = new String(bytes, 0, length[0]); message.append("Geometry shader info: " + geometryInfo); } } delete(gl); throw new ShaderException(message.toString()); } gl.glUseProgram(shaderProgram); getUniformLocations(gl); gl.glUseProgram(0); return true; } catch (ShaderException e) { creationFailed = true; Logging.logger().log(Level.SEVERE, "Shader creation failed", e); return false; } } /** * @return List of strings to #define at the top of the shader source */ protected String[] getDefines() { return null; } protected String insertDefines(String src, String[] defines, String extraDefine) { StringBuffer sb = new StringBuffer(src); int defineIndex = 0; int versionIndex = src.indexOf("#version"); if (versionIndex >= 0) { defineIndex = src.indexOf('\n', versionIndex) + 1; } if (defines != null) { for (int i = defines.length - 1; i >= 0; i--) { sb.insert(defineIndex, "#define " + defines[i] + "\n"); } } sb.insert(defineIndex, "#define " + extraDefine + "\n"); return sb.toString(); } /** * @return Has this shader been created yet (and not * {@link #isCreationFailed()})? */ public final boolean isCreated() { return shaderProgram > 0 && !isCreationFailed(); } /** * @return Did this shader fail to be created (due to compile error, etc)? */ public final boolean isCreationFailed() { return creationFailed; } /** * Tell OpenGL to use this shader program. This method must be called by * {@link Shader} subclasses. * * @param gl * @return <code>True</code> if this shader was created and used */ protected boolean use(GL2 gl) { if (isCreated()) { gl.glUseProgram(shaderProgram); return true; } return false; } /** * Tell OpenGL to stop using this shader. * * @param gl */ public void unuse(GL2 gl) { gl.glUseProgram(0); } /** * Delete any OpenGL resources associated with this shader. * * @param gl */ public void delete(GL2 gl) { if (shaderProgram > 0) { gl.glDeleteProgram(shaderProgram); } if (vertexShader > 0) { gl.glDeleteShader(vertexShader); } if (fragmentShader > 0) { gl.glDeleteShader(fragmentShader); } if (geometryShader > 0) { gl.glDeleteShader(geometryShader); } shaderProgram = 0; vertexShader = 0; fragmentShader = 0; geometryShader = 0; } /** * Create this shader if not created already. * * @param gl * @return Is this shader created, ie {@link #isCreated()} * @see Shader#create(GL) */ public boolean createIfRequired(GL2 gl) { if (!isCreated()) { create(gl); return true; } return false; } /** * Delete if created. * * @param gl * @see Shader#delete(GL) */ public void deleteIfCreated(GL2 gl) { if (isCreated()) { delete(gl); } } }