/** * Copyright 2010 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.graph.curve.opengl; import java.io.IOException; import java.util.Iterator; import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLException; import javax.media.opengl.fixedfunc.GLMatrixFunc; import jogamp.graph.curve.opengl.shader.AttributeNames; import com.jogamp.opengl.GLExtensions; import com.jogamp.opengl.util.glsl.ShaderCode; import com.jogamp.opengl.util.glsl.ShaderProgram; import com.jogamp.opengl.util.PMVMatrix; import com.jogamp.common.util.IntObjectHashMap; import com.jogamp.graph.curve.Region; /** * OpenGL {@link Region} renderer *

* All OpenGL related operations regarding {@link Region}s * are passed through an instance of this class. *

*/ public class RegionRenderer { protected static final boolean DEBUG = Region.DEBUG; protected static final boolean DEBUG_INSTANCE = Region.DEBUG_INSTANCE; public interface GLCallback { /** * @param gl a current GL object * @param renderer {@link RegionRenderer} calling this method. */ void run(GL gl, RegionRenderer renderer); } /** * Default {@link GL#GL_BLEND} enable {@link GLCallback}, * turning on the {@link GL#GL_BLEND} state and setting up * {@link GL#glBlendFunc(int, int) glBlendFunc}({@link GL#GL_SRC_ALPHA}, {@link GL#GL_ONE_MINUS_SRC_ALPHA}). *

* Implementation also sets {@link RegionRenderer#getRenderState() RenderState}'s {@link RenderState#BITHINT_BLENDING_ENABLED blending bit-hint}. *

* @see #create(RenderState, GLCallback, GLCallback) * @see #enable(GL2ES2, boolean) */ public static final GLCallback defaultBlendEnable = new GLCallback() { @Override public void run(final GL gl, final RegionRenderer renderer) { gl.glEnable(GL.GL_BLEND); gl.glBlendEquation(GL.GL_FUNC_ADD); // default gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); renderer.rs.setHintMask(RenderState.BITHINT_BLENDING_ENABLED); } }; /** * Default {@link GL#GL_BLEND} disable {@link GLCallback}, * simply turning off the {@link GL#GL_BLEND} state. *

* Implementation also clears {@link RegionRenderer#getRenderState() RenderState}'s {@link RenderState#BITHINT_BLENDING_ENABLED blending bit-hint}. *

* @see #create(RenderState, GLCallback, GLCallback) * @see #enable(GL2ES2, boolean) */ public static final GLCallback defaultBlendDisable = new GLCallback() { @Override public void run(final GL gl, final RegionRenderer renderer) { renderer.rs.clearHintMask(RenderState.BITHINT_BLENDING_ENABLED); gl.glDisable(GL.GL_BLEND); } }; /** * Create a Hardware accelerated Region Renderer. *

* The optional {@link GLCallback}s enableCallback and disableCallback * maybe used to issue certain tasks at {@link #enable(GL2ES2, boolean)}.
* For example, instances {@link #defaultBlendEnable} and {@link #defaultBlendDisable} * can be utilized to enable and disable {@link GL#GL_BLEND}. *

* @param rs the used {@link RenderState} * @param enableCallback optional {@link GLCallback}, if not null will be issued at * {@link #init(GL2ES2, int) init(gl)} and {@link #enable(GL2ES2, boolean) enable(gl, true)}. * @param disableCallback optional {@link GLCallback}, if not null will be issued at * {@link #enable(GL2ES2, boolean) enable(gl, false)}. * @return an instance of Region Renderer * @see #enable(GL2ES2, boolean) */ public static RegionRenderer create(final RenderState rs, final GLCallback enableCallback, final GLCallback disableCallback) { return new RegionRenderer(rs, enableCallback, disableCallback); } private final RenderState rs; private final GLCallback enableCallback; private final GLCallback disableCallback; private int vp_width; private int vp_height; private boolean initialized; private boolean vboSupported = false; public final boolean isInitialized() { return initialized; } /** Return width of current viewport */ public final int getWidth() { return vp_width; } /** Return height of current viewport */ public final int getHeight() { return vp_height; } public final PMVMatrix getMatrix() { return rs.getMatrix(); } public final PMVMatrix getMatrixMutable() { return rs.getMatrixMutable(); } public final void setMatrixDirty() { rs.setMatrixDirty(); } public final boolean isMatrixDirty() { return rs.isMatrixDirty(); } ////////////////////////////////////// /** * @param rs the used {@link RenderState} */ protected RegionRenderer(final RenderState rs, final GLCallback enableCallback, final GLCallback disableCallback) { this.rs = rs; this.enableCallback = enableCallback; this.disableCallback = disableCallback; } public final boolean isVBOSupported() { return vboSupported; } /** * Initialize shader and bindings for GPU based rendering bound to the given GL object's GLContext * if not initialized yet. *

Leaves the renderer enabled, ie ShaderState.

*

Shall be called by a {@code draw()} method, e.g. {@link RegionRenderer#draw(GL2ES2, Region, int)}

* * @param gl referencing the current GLContext to which the ShaderState is bound to * @param renderModes TODO * @throws GLException if initialization failed */ public final void init(final GL2ES2 gl, final int renderModes) throws GLException { if(initialized){ return; } vboSupported = gl.isFunctionAvailable("glGenBuffers") && gl.isFunctionAvailable("glBindBuffer") && gl.isFunctionAvailable("glBufferData") && gl.isFunctionAvailable("glDrawElements") && gl.isFunctionAvailable("glVertexAttribPointer") && gl.isFunctionAvailable("glDeleteBuffers"); if(DEBUG) { System.err.println("TextRendererImpl01: VBO Supported = " + isVBOSupported()); } if(!vboSupported){ throw new GLException("VBO not supported"); } rs.attachTo(gl); if( null != enableCallback ) { enableCallback.run(gl, this); } useShaderProgram(gl, renderModes, true, 0, 0); initialized = rs.update(gl, true, renderModes, true); if(!initialized) { throw new GLException("Shader initialization failed"); } } public final void destroy(GL2ES2 gl) { if(!initialized){ if(DEBUG_INSTANCE) { System.err.println("TextRenderer: Not initialized!"); } return; } for(final Iterator i = shaderPrograms.iterator(); i.hasNext(); ) { final ShaderProgram sp = (ShaderProgram) i.next().getValue(); sp.destroy(gl); } shaderPrograms.clear(); rs.destroy(gl); initialized = false; } public final RenderState getRenderState() { return rs; } /** * Enabling or disabling the {@link #getRenderState() RenderState}'s * {@link RenderState#getShaderProgram() shader program}. *

* In case enable and disable {@link GLCallback}s are setup via {@link #create(RenderState, GLCallback, GLCallback)}, * they will be called before toggling the shader program. *

* @see #create(RenderState, GLCallback, GLCallback) */ public final void enable(GL2ES2 gl, boolean enable) { if( enable ) { if( null != enableCallback ) { enableCallback.run(gl, this); } } else { if( null != disableCallback ) { disableCallback.run(gl, this); } } if( !enable ) { final ShaderProgram sp = rs.getShaderProgram(); if( null != sp ) { sp.useProgram(gl, false); } } } /** No PMVMatrix operation is performed here. PMVMatrix is marked dirty. */ public final void reshapeNotify(int width, int height) { this.vp_width = width; this.vp_height = height; rs.setMatrixDirty(); } public final void reshapePerspective(float angle, int width, int height, float near, float far) { this.vp_width = width; this.vp_height = height; final float ratio = (float)width/(float)height; final PMVMatrix p = rs.getMatrixMutable(); p.glMatrixMode(GLMatrixFunc.GL_PROJECTION); p.glLoadIdentity(); p.gluPerspective(angle, ratio, near, far); } public final void reshapeOrtho(int width, int height, float near, float far) { this.vp_width = width; this.vp_height = height; final PMVMatrix p = rs.getMatrixMutable(); p.glMatrixMode(GLMatrixFunc.GL_PROJECTION); p.glLoadIdentity(); p.glOrthof(0, width, 0, height, near, far); } // // Shader Management // private static final String SHADER_SRC_SUB = ""; private static final String SHADER_BIN_SUB = "bin"; private static String USE_COLOR_CHANNEL = "#define USE_COLOR_CHANNEL 1\n"; private static String USE_COLOR_TEXTURE = "#define USE_COLOR_TEXTURE 1\n"; private static String DEF_SAMPLE_COUNT = "#define SAMPLE_COUNT "; private String getVersionedShaderName() { return "curverenderer01"; } // FIXME: Really required to have sampler2D def. precision ? If not, we can drop getFragmentShaderPrecision(..) and use default ShaderCode .. private static final String es2_precision_fp = "\nprecision mediump float;\nprecision mediump int;\nprecision mediump sampler2D;\n"; private final String getFragmentShaderPrecision(GL2ES2 gl) { if( gl.isGLES() ) { return es2_precision_fp; } if( ShaderCode.requiresGL3DefaultPrecision(gl) ) { return ShaderCode.gl3_default_precision_fp; } return null; } private static enum ShaderModeSelector1 { /** Pass-1: Curve Simple */ PASS1_SIMPLE("curve", "_simple", 0), /** Pass-1: Curve Varying Weight */ PASS1_WEIGHT("curve", "_weight", 0), /** Pass-2: MSAA */ PASS2_MSAA("msaa", "", 0), /** Pass-2: VBAA Flipquad3, 1 sample */ PASS2_VBAA_QUAL0_SAMPLES1("vbaa", "_flipquad3", 1), /** Pass-2: VBAA Flipquad3, 2 samples */ PASS2_VBAA_QUAL0_SAMPLES2("vbaa", "_flipquad3", 2), /** Pass-2: VBAA Flipquad3, 4 samples */ PASS2_VBAA_QUAL0_SAMPLES4("vbaa", "_flipquad3", 4), /** Pass-2: VBAA Flipquad3, 8 samples */ PASS2_VBAA_QUAL0_SAMPLES8("vbaa", "_flipquad3", 8), /** Pass-2: VBAA All-Equal, 2 samples */ PASS2_VBAA_QUAL1_SAMPLES2("vbaa", "_allequal", 2), /** Pass-2: VBAA All-Equal, 4 samples */ PASS2_VBAA_QUAL1_SAMPLES4("vbaa", "_allequal", 4), /** Pass-2: VBAA All-Equal, 6 samples */ PASS2_VBAA_QUAL1_SAMPLES6("vbaa", "_allequal", 6), /** Pass-2: VBAA All-Equal, 8 samples */ PASS2_VBAA_QUAL1_SAMPLES8("vbaa", "_allequal", 8); public final String tech; public final String sub; public final int sampleCount; ShaderModeSelector1(final String tech, final String sub, final int sampleCount) { this.tech = tech; this.sub= sub; this.sampleCount = sampleCount; } public static ShaderModeSelector1 selectPass1(final int renderModes) { return Region.hasVariableWeight(renderModes) ? PASS1_WEIGHT : PASS1_SIMPLE; } public static ShaderModeSelector1 selectPass2(final int renderModes, final int quality, final int sampleCount) { if( Region.isMSAA(renderModes) ) { return PASS2_MSAA; } else if( Region.isVBAA(renderModes) ) { if( 0 == quality ) { if( sampleCount < 2 ) { return PASS2_VBAA_QUAL0_SAMPLES1; } else if( sampleCount < 4 ) { return PASS2_VBAA_QUAL0_SAMPLES2; } else if( sampleCount < 8 ) { return PASS2_VBAA_QUAL0_SAMPLES4; } else { return PASS2_VBAA_QUAL0_SAMPLES8; } } else { if( sampleCount < 4 ) { return PASS2_VBAA_QUAL1_SAMPLES2; } else if( sampleCount < 6 ) { return PASS2_VBAA_QUAL1_SAMPLES4; } else if( sampleCount < 8 ) { return PASS2_VBAA_QUAL1_SAMPLES6; } else { return PASS2_VBAA_QUAL1_SAMPLES8; } } } else { return null; } } } private final IntObjectHashMap shaderPrograms = new IntObjectHashMap(); private static final int HIGH_MASK = Region.COLORCHANNEL_RENDERING_BIT | Region.COLORTEXTURE_RENDERING_BIT; /** * @param gl * @param renderModes * @param pass1 * @param quality * @param sampleCount * @return true if a new shader program is being used and hence external uniform-data and -location, * as well as the attribute-location must be updated, otherwise false. */ public final boolean useShaderProgram(final GL2ES2 gl, final int renderModes, final boolean pass1, final int quality, final int sampleCount) { final ShaderModeSelector1 sel1 = pass1 ? ShaderModeSelector1.selectPass1(renderModes) : ShaderModeSelector1.selectPass2(renderModes, quality, sampleCount); final int shaderKey = sel1.ordinal() | ( HIGH_MASK & renderModes ); /** if(DEBUG) { System.err.printf("RegionRendererImpl01.useShaderProgram.0: renderModes %s, sel1 %s, key 0x%X (pass1 %b, q %d, samples %d) - Thread %s%n", Region.getRenderModeString(renderModes), sel1, shaderKey, pass1, quality, sampleCount, Thread.currentThread()); } */ ShaderProgram sp = (ShaderProgram) shaderPrograms.get( shaderKey ); if( null != sp ) { final boolean spChanged = getRenderState().setShaderProgram(gl, sp); if(DEBUG) { if( spChanged ) { System.err.printf("RegionRendererImpl01.useShaderProgram.X1: GOT renderModes %s, sel1 %s, key 0x%X (changed)%n", Region.getRenderModeString(renderModes), sel1, shaderKey); } } return spChanged; } final String versionedBaseName = getVersionedShaderName(); final String vertexShaderName; if( Region.isTwoPass( renderModes ) ) { vertexShaderName = versionedBaseName+"-pass"+(pass1?1:2); } else { vertexShaderName = versionedBaseName+"-single"; } final ShaderCode rsVp = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, AttributeNames.class, SHADER_SRC_SUB, SHADER_BIN_SUB, vertexShaderName, true); final ShaderCode rsFp = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, AttributeNames.class, SHADER_SRC_SUB, SHADER_BIN_SUB, versionedBaseName+"-segment-head", true); int posVp = rsVp.defaultShaderCustomization(gl, true, true); // rsFp.defaultShaderCustomization(gl, true, true); int posFp = rsFp.addGLSLVersion(gl); if( gl.isGLES2() && ! gl.isGLES3() ) { posFp = rsFp.insertShaderSource(0, posFp, ShaderCode.createExtensionDirective(GLExtensions.OES_standard_derivatives, ShaderCode.ENABLE)); } final String rsFpDefPrecision = getFragmentShaderPrecision(gl); if( null != rsFpDefPrecision ) { rsFp.insertShaderSource(0, posFp, rsFpDefPrecision); } if( Region.hasColorChannel( renderModes ) ) { posVp = rsVp.insertShaderSource(0, posVp, USE_COLOR_CHANNEL); posFp = rsFp.insertShaderSource(0, posFp, USE_COLOR_CHANNEL); } if( Region.hasColorTexture( renderModes ) ) { posVp = rsVp.insertShaderSource(0, posVp, USE_COLOR_TEXTURE); posFp = rsFp.insertShaderSource(0, posFp, USE_COLOR_TEXTURE); } if( !pass1 ) { posFp = rsFp.insertShaderSource(0, posFp, DEF_SAMPLE_COUNT+sel1.sampleCount+"\n"); } final String passS = pass1 ? "-pass1-" : "-pass2-"; final String shaderSegment = versionedBaseName+passS+sel1.tech+sel1.sub+".glsl"; if(DEBUG) { System.err.printf("RegionRendererImpl01.useShaderProgram.1: segment %s%n", shaderSegment); } try { posFp = rsFp.insertShaderSource(0, -1, AttributeNames.class, shaderSegment); } catch (IOException ioe) { throw new RuntimeException("Failed to read: "+shaderSegment, ioe); } if( 0 > posFp ) { throw new RuntimeException("Failed to read: "+shaderSegment); } posFp = rsFp.insertShaderSource(0, -1, "}\n"); sp = new ShaderProgram(); sp.add(rsVp); sp.add(rsFp); if( !sp.init(gl) ) { throw new GLException("RegionRenderer: Couldn't init program: "+sp); } if( !sp.link(gl, System.err) ) { throw new GLException("could not link program: "+sp); } getRenderState().setShaderProgram(gl, sp); shaderPrograms.put(shaderKey, sp); if(DEBUG) { System.err.printf("RegionRendererImpl01.useShaderProgram.X1: PUT renderModes %s, sel1 %s, key 0x%X -> SP %s (changed)%n", Region.getRenderModeString(renderModes), sel1, shaderKey, sp); } return true; } }