/******************************************************************************* * Copyright 2013 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.model.core.shader; import java.util.concurrent.atomic.AtomicBoolean; import javax.media.opengl.GL2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jogamp.opengl.util.glsl.ShaderCode; import com.jogamp.opengl.util.glsl.ShaderProgram; import com.jogamp.opengl.util.glsl.ShaderState; import com.jogamp.opengl.util.glsl.ShaderUtil; /** * A base class for high-level shader representations that take care of binding * GL uniforms etc. and present a clean interface to client code. * <p/> * Defines a standard lifecycle that shaders can use: * <ol> * <li>Load shader source * <li>Initialise shader program * <li>Set shader state * <li>Use (bind) shader program * <li><i>Perform drawing in client</i> * <li>Unbind shader program * </ol> * <p/> * A {@link Shader} can be marked as {@link #isDirty()}, which will prompt it to * be re-initialised prior to binding. * * @author James Navin (james.navin@ga.gov.au) * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public abstract class Shader { private static final Logger logger = LoggerFactory.getLogger(Shader.class); /** * Flag indicates that the shader is in a dirty state and needs to be * re-initialised prior to binding */ private AtomicBoolean dirty = new AtomicBoolean(true); private ShaderState shaderState; private ShaderProgram shaderProgram; private ShaderCode vertexShader; private ShaderCode fragmentShader; private Exception lastException; /** * @return The source for the vertex shader */ protected abstract String getVertexShaderSource(); /** * @return The source for the fragment shader */ protected abstract String getFragmentShaderSource(); /** * Bind required uniforms etc. on the provided {@link ShaderState} object. * * @param gl * The current GL context * @param shaderState * The {@link ShaderState} to bind to * * @return <code>true</code> if binding was successful; <code>false</code> * otherwise */ protected abstract boolean bindShaderState(GL2 gl, ShaderState shaderState); /** * Initialise this shader using the provided GL context with current values * as appropriate. * * @param gl * * @return <code>true</code> if initialisation was successful; * <code>false</code> otherwise. */ public boolean initialise(GL2 gl) { if (gl == null) { logger.debug("No GL provided. Unable to initialise shader."); //$NON-NLS-1$ lastException = new IllegalArgumentException("A GL is required for binding"); //$NON-NLS-1$ return false; } try { if (shaderProgram != null) { shaderProgram.destroy(gl); shaderProgram = null; } vertexShader = new ShaderCode(GL2.GL_VERTEX_SHADER, 1, new String[][] { { getVertexShaderSource() } }); fragmentShader = new ShaderCode(GL2.GL_FRAGMENT_SHADER, 1, new String[][] { { getFragmentShaderSource() } }); shaderProgram = new ShaderProgram(); shaderProgram.add(gl, vertexShader, null); shaderProgram.add(gl, fragmentShader, null); if (shaderState == null) { shaderState = new ShaderState(); } shaderState.attachShaderProgram(gl, shaderProgram, true); dirty.set(false); return true; } catch (Exception e) { logger.debug("Unable to initialise shader", e); //$NON-NLS-1$ lastException = e; return false; } } /** * @return Whether this shader has been initialised correctly */ public boolean isInitialised() { return shaderProgram != null; } /** * Mark this shader as dirty. It will be re-initialised prior to the next * draw, or manually using {@link #initialise(GL2)} */ public void markDirty() { this.dirty.set(true); } /** * @return Whether this shader is in a 'dirty' state and needs to be * re-initialised. * @see #markDirty() */ public boolean isDirty() { return dirty.get(); } /** * Bind this shader on the provided GL context, initialising if necessary. * <p/> * Should be called before drawing begins. * * @param gl * The GL context to bind to * @return <code>true</code> if the bind was successful; <code>false</code> * otherwise. */ public boolean bind(GL2 gl) { if (gl == null) { logger.debug("No GL provided. Unable to initialise shader."); //$NON-NLS-1$ lastException = new IllegalArgumentException("A GL is required for binding"); //$NON-NLS-1$ return false; } if (!ShaderUtil.isShaderCompilerAvailable(gl)) { return false; } if (isDirty()) { if (!initialise(gl)) { return false; } } try { shaderState.useProgram(gl, true); boolean ok = bindShaderState(gl, shaderState); if (ok) { // Make sure we don't have left-over exceptions retained lastException = null; } else { if (lastException == null) { // Make sure we have an exception for debugging in case one wasnt raised as part of the binding lastException = new IllegalStateException("Shaderstate binding failed"); //$NON-NLS-1$ } } return ok; } catch (Exception e) { lastException = e; return false; } } /** * Unbind this shader from the provided GL context * <p/> * Should be called after drawing completes. * * @param gl * * @return <code>true</code> if the unbind was successful; * <code>false</code> otherwise. */ public boolean unbind(GL2 gl) { if (gl == null) { logger.debug("No GL provided. Unable to initialise shader."); //$NON-NLS-1$ lastException = new IllegalArgumentException("A GL is required for binding"); //$NON-NLS-1$ return false; } try { shaderState.useProgram(gl, false); return true; } catch (Exception e) { lastException = e; return false; } } /** * @return The last (Java) error that occured (if any). Note: GL errors can * be retrieved from the current GL context as normal using * {@link GL2#glGetError()} or */ public Exception getLastError() { return lastException; } }