diff options
Diffstat (limited to 'src/jogl/classes/com')
26 files changed, 2107 insertions, 1124 deletions
diff --git a/src/jogl/classes/com/jogamp/gluegen/opengl/BuildComposablePipeline.java b/src/jogl/classes/com/jogamp/gluegen/opengl/BuildComposablePipeline.java index f00f61116..75067a3b7 100644 --- a/src/jogl/classes/com/jogamp/gluegen/opengl/BuildComposablePipeline.java +++ b/src/jogl/classes/com/jogamp/gluegen/opengl/BuildComposablePipeline.java @@ -51,6 +51,8 @@ import java.lang.reflect.Method; import java.nio.Buffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -208,29 +210,39 @@ public class BuildComposablePipeline { final List<Method> publicMethodsRaw = Arrays.asList(classToComposeAround.getMethods()); - final Set<PlainMethod> publicMethodsPlain = new HashSet<PlainMethod>(); + final Set<PlainMethod> publicMethodsPlainSet = new HashSet<PlainMethod>(); for (final Iterator<Method> iter = publicMethodsRaw.iterator(); iter.hasNext();) { final Method method = iter.next(); // Don't hook methods which aren't real GL methods, // such as the synthetic "isGL2ES2" "getGL2ES2" final String name = method.getName(); - if ( !name.startsWith("getGL") && - !name.startsWith("isGL") && - !name.equals("getDownstreamGL") && + if ( !name.equals("getDownstreamGL") && !name.equals("toString") ) { - final boolean runHooks = name.startsWith("gl") || addedGLHooks.containsKey(name); - publicMethodsPlain.add(new PlainMethod(method, runHooks)); + final boolean syntheticIsGL = name.startsWith("isGL"); + final boolean syntheticGetGL = name.startsWith("getGL"); + final boolean runHooks = name.startsWith("gl") || syntheticIsGL || syntheticGetGL || addedGLHooks.containsKey(name); + publicMethodsPlainSet.add(new PlainMethod(method, runHooks, syntheticIsGL, syntheticGetGL)); } } + // sort methods to make them easier to find + final List<PlainMethod> publicMethodsPlainSorted = new ArrayList<PlainMethod>(); + publicMethodsPlainSorted.addAll(publicMethodsPlainSet); + Collections.sort(publicMethodsPlainSorted, new Comparator<PlainMethod>() { + @Override + public int compare(final PlainMethod o1, final PlainMethod o2) { + return o1.getWrappedMethod().getName().compareTo(o2.getWrappedMethod().getName()); + } + }); + if (0 != (mode & GEN_DEBUG)) { - (new DebugPipeline(outputDir, outputPackage, classToComposeAround, classDownstream)).emit(publicMethodsPlain.iterator()); + (new DebugPipeline(outputDir, outputPackage, classToComposeAround, classDownstream)).emit(publicMethodsPlainSorted.iterator()); } if (0 != (mode & GEN_TRACE)) { - (new TracePipeline(outputDir, outputPackage, classToComposeAround, classDownstream)).emit(publicMethodsPlain.iterator()); + (new TracePipeline(outputDir, outputPackage, classToComposeAround, classDownstream)).emit(publicMethodsPlainSorted.iterator()); } if (0 != (mode & GEN_CUSTOM)) { - (new CustomPipeline(mode, outputDir, outputPackage, outputName, classToComposeAround, classPrologOpt, classDownstream)).emit(publicMethodsPlain.iterator()); + (new CustomPipeline(mode, outputDir, outputPackage, outputName, classToComposeAround, classPrologOpt, classDownstream)).emit(publicMethodsPlainSorted.iterator()); } } @@ -255,12 +267,16 @@ public class BuildComposablePipeline { //------------------------------------------------------- protected static class PlainMethod { - Method m; - boolean runHooks; + final Method m; + final boolean runHooks; + final boolean isSynthethicIsGL; + final boolean isSynthethicGetGL; - PlainMethod(final Method m, final boolean runHooks) { + PlainMethod(final Method m, final boolean runHooks, final boolean isSynthethicIsGL, final boolean isSynthethicGetGL) { this.m = m; this.runHooks = runHooks; + this.isSynthethicIsGL = isSynthethicIsGL; + this.isSynthethicGetGL = isSynthethicGetGL; } public Method getWrappedMethod() { @@ -271,6 +287,10 @@ public class BuildComposablePipeline { return runHooks; } + public boolean isSynthetic() { return isSynthethicIsGL || isSynthethicGetGL; } + public boolean isSyntheticIsGL() { return isSynthethicIsGL; } + public boolean isSyntheticGetGL() { return isSynthethicGetGL; } + @Override public boolean equals(final Object obj) { if (obj instanceof PlainMethod) { @@ -309,6 +329,7 @@ public class BuildComposablePipeline { argsString.append(")"); return m.toString() + "\n\tname: " + m.getName() + + "\n\tsynt: isGL " + isSynthethicIsGL+", getGL "+isSynthethicGetGL + "\n\tmods: " + m.getModifiers() + "\n\tretu: " + m.getReturnType() + "\n\targs[" + args.length + "]: " + argsString.toString(); @@ -429,15 +450,14 @@ public class BuildComposablePipeline { constructorHook(output); - emitGLIsMethods(output); - emitGLGetMethods(output); + emitSyntheticGLMethods(output); while (methodsToWrap.hasNext()) { final PlainMethod pm = methodsToWrap.next(); final Method m = pm.getWrappedMethod(); emitMethodDocComment(output, m); emitSignature(output, m); - emitBody(output, m, pm.runHooks()); + emitBody(output, pm); } postMethodEmissionHook(output); @@ -479,12 +499,14 @@ public class BuildComposablePipeline { getArgListAsString(m, true, true)); } - protected void emitBody(final PrintWriter output, final Method m, final boolean runHooks) { + protected void emitBody(final PrintWriter output, final PlainMethod pm) { + final boolean runHooks = pm.runHooks(); + final Method m = pm.getWrappedMethod(); output.println(" {"); final Class<?> retType = m.getReturnType(); - final boolean callPreDownstreamHook = runHooks && hasPreDownstreamCallHook(m); - final boolean callPostDownstreamHook = runHooks && hasPostDownstreamCallHook(m); + final boolean callPreDownstreamHook = runHooks && hasPreDownstreamCallHook(pm); + final boolean callPostDownstreamHook = runHooks && hasPostDownstreamCallHook(pm); final boolean callDownstream = (null != getMethod(downstreamClass, m)) && !(0 != (GEN_PROLOG_XOR_DOWNSTREAM & getMode()) && callPreDownstreamHook); final boolean hasResult = (retType != Void.TYPE); @@ -514,38 +536,44 @@ public class BuildComposablePipeline { output.print(" return "); } } - preDownstreamCallHook(output, m); + preDownstreamCallHook(output, pm); } if (callDownstream) { - if (hasResult) { - if (callPostDownstreamHook) { - output.print(" " + JavaType.createForClass(retType).getName()); - output.print(" _res = "); - } else { - output.print(" return "); + if( pm.isSyntheticIsGL() ) { + emitGLIsMethodBody(output, pm); + } else if( pm.isSyntheticGetGL() ) { + emitGLGetMethodBody(output, pm); + } else { + if (hasResult) { + if (callPostDownstreamHook) { + output.print(" " + JavaType.createForClass(retType).getName()); + output.print(" _res = "); + } else { + output.print(" return "); + } } + else { + output.print(" "); + } + output.print(getDownstreamObjectName()); + output.print('.'); + output.print(m.getName()); + output.print('('); + output.print(getArgListAsString(m, false, true)); + output.println(");"); } - else { - output.print(" "); - } - output.print(getDownstreamObjectName()); - output.print('.'); - output.print(m.getName()); - output.print('('); - output.print(getArgListAsString(m, false, true)); - output.println(");"); } if (callPostDownstreamHook) { - postDownstreamCallHook(output, m); + postDownstreamCallHook(output, pm); } if (hasResult && callDownstream && callPostDownstreamHook) { output.println(" return _res;"); } - output.println(" }"); + output.println(" }"); } protected String getArgListAsString(final Method m, final boolean includeArgTypes, final boolean includeArgNames) { @@ -619,17 +647,17 @@ public class BuildComposablePipeline { /** * Called before the pipeline routes the call to the downstream object. */ - protected abstract void preDownstreamCallHook(PrintWriter output, Method m); + protected abstract void preDownstreamCallHook(PrintWriter output, PlainMethod pm); - protected abstract boolean hasPreDownstreamCallHook(Method m); + protected abstract boolean hasPreDownstreamCallHook(PlainMethod pm); /** * Called after the pipeline has routed the call to the downstream object, * but before the calling function exits or returns a value. */ - protected abstract void postDownstreamCallHook(PrintWriter output, Method m); + protected abstract void postDownstreamCallHook(PrintWriter output, PlainMethod pm); - protected abstract boolean hasPostDownstreamCallHook(Method m); + protected abstract boolean hasPostDownstreamCallHook(PlainMethod pm); protected abstract int getMode(); @@ -643,10 +671,17 @@ public class BuildComposablePipeline { /** * Emits one of the isGL* methods. */ - protected void emitGLIsMethod(final PrintWriter output, final String type) { - output.println(" @Override"); - output.println(" public final boolean is" + type + "() {"); - if( 0 != (GEN_GL_IDENTITY_BY_ASSIGNABLE_CLASS & getMode() ) ) { + protected void emitGLIsMethodBody(final PrintWriter output, final PlainMethod plainMethod) { + final String methodName = plainMethod.getWrappedMethod().getName(); + final String type = methodName.substring(2); + + if( type.equals("GL") ) { + output.println(" return true;"); + } else if( 0 != ( GEN_GL_IDENTITY_BY_ASSIGNABLE_CLASS & getMode() ) && + !type.equals("GLES") && + !type.endsWith("core") && + !type.endsWith("Compatible") ) + { final Class<?> clazz = BuildComposablePipeline.getClass("javax.media.opengl." + type); if (clazz.isAssignableFrom(baseInterfaceClass)) { output.println(" return true;"); @@ -656,111 +691,38 @@ public class BuildComposablePipeline { } else { output.println(" return " + getDownstreamObjectName() + ".is" + type + "();"); } - output.println(" }"); - } - - /** - * Emits all of the isGL* methods. - */ - protected void emitGLIsMethods(final PrintWriter output) { - output.println(" @Override"); - output.println(" public final boolean isGL() {"); - output.println(" return true;"); - output.println(" }"); - emitGLIsMethod(output, "GL4bc"); - emitGLIsMethod(output, "GL4"); - emitGLIsMethod(output, "GL3bc"); - emitGLIsMethod(output, "GL3"); - emitGLIsMethod(output, "GL2"); - emitGLIsMethod(output, "GLES1"); - emitGLIsMethod(output, "GLES2"); - emitGLIsMethod(output, "GLES3"); - emitGLIsMethod(output, "GL2ES1"); - emitGLIsMethod(output, "GL2ES2"); - emitGLIsMethod(output, "GL2ES3"); - emitGLIsMethod(output, "GL3ES3"); - emitGLIsMethod(output, "GL4ES3"); - emitGLIsMethod(output, "GL2GL3"); - if( 0 != (GEN_GL_IDENTITY_BY_ASSIGNABLE_CLASS & getMode() ) ) { - output.println(" @Override"); - output.println(" public final boolean isGLES() {"); - output.println(" return isGLES3() || isGLES2() || isGLES1();"); - output.println(" }"); - } else { - emitGLIsMethod(output, "GLES"); - } - output.println(" @Override"); - output.println(" public final boolean isGL4core() {"); - output.println(" return " + getDownstreamObjectName() + ".isGL4core();"); - output.println(" }"); - output.println(" @Override"); - output.println(" public final boolean isGL3core() {"); - output.println(" return " + getDownstreamObjectName() + ".isGL3core();"); - output.println(" }"); - output.println(" @Override"); - output.println(" public final boolean isGLcore() {"); - output.println(" return " + getDownstreamObjectName() + ".isGLcore();"); - output.println(" }"); - output.println(" @Override"); - output.println(" public final boolean isGLES2Compatible() {"); - output.println(" return " + getDownstreamObjectName() + ".isGLES2Compatible();"); - output.println(" }"); - output.println(" @Override"); - output.println(" public final boolean isGLES3Compatible() {"); - output.println(" return " + getDownstreamObjectName() + ".isGLES3Compatible();"); - output.println(" }"); } /** * Emits one of the getGL* methods. */ - protected void emitGLGetMethod(final PrintWriter output, final String type) { - output.println(" @Override"); - output.println(" public final javax.media.opengl." + type + " get" + type + "() {"); - final Class<?> clazz = BuildComposablePipeline.getClass("javax.media.opengl." + type); - if (clazz.isAssignableFrom(baseInterfaceClass)) { - if( 0 != (GEN_GL_IDENTITY_BY_ASSIGNABLE_CLASS & getMode() ) ) { - output.println(" return this;"); - } else { + protected void emitGLGetMethodBody(final PrintWriter output, final PlainMethod plainMethod) { + final String methodName = plainMethod.getWrappedMethod().getName(); + final String type = methodName.substring(3); + + if( type.equals("GL") ) { + output.println(" return this;"); + } else if( type.equals("GLProfile") ) { + output.println(" return " + getDownstreamObjectName() + ".getGLProfile();"); + } else { + final Class<?> clazz = BuildComposablePipeline.getClass("javax.media.opengl." + type); + if (clazz.isAssignableFrom(baseInterfaceClass)) { output.println(" if( is" + type + "() ) { return this; }"); output.println(" throw new GLException(\"Not a " + type + " implementation\");"); + } else { + output.println(" throw new GLException(\"Not a " + type + " implementation\");"); } - } else { - output.println(" throw new GLException(\"Not a " + type + " implementation\");"); } - output.println(" }"); } /** - * Emits all of the getGL* methods. + * Emits all synthetic GL* methods, but not isGL* nor getGL* */ - protected void emitGLGetMethods(final PrintWriter output) { - output.println(" @Override"); - output.println(" public final javax.media.opengl.GL getGL() {"); - output.println(" return this;"); - output.println(" }"); - emitGLGetMethod(output, "GL4bc"); - emitGLGetMethod(output, "GL4"); - emitGLGetMethod(output, "GL3bc"); - emitGLGetMethod(output, "GL3"); - emitGLGetMethod(output, "GL2"); - emitGLGetMethod(output, "GLES1"); - emitGLGetMethod(output, "GLES2"); - emitGLGetMethod(output, "GLES3"); - emitGLGetMethod(output, "GL2ES1"); - emitGLGetMethod(output, "GL2ES2"); - emitGLGetMethod(output, "GL2ES3"); - emitGLGetMethod(output, "GL3ES3"); - emitGLGetMethod(output, "GL4ES3"); - emitGLGetMethod(output, "GL2GL3"); + protected void emitSyntheticGLMethods(final PrintWriter output) { output.println(" @Override"); output.println(" public final GL getDownstreamGL() throws GLException {"); output.println(" return " + getDownstreamObjectName() + ";"); output.println(" }"); - output.println(" @Override"); - output.println(" public final GLProfile getGLProfile() {"); - output.println(" return " + getDownstreamObjectName() + ".getGLProfile();"); - output.println(" }"); } } // end class PipelineEmitter @@ -868,12 +830,13 @@ public class BuildComposablePipeline { } @Override - protected boolean hasPreDownstreamCallHook(final Method m) { - return null != getMethod(prologClassOpt, m); + protected boolean hasPreDownstreamCallHook(final PlainMethod pm) { + return null != getMethod(prologClassOpt, pm.getWrappedMethod()); } @Override - protected void preDownstreamCallHook(final PrintWriter output, final Method m) { + protected void preDownstreamCallHook(final PrintWriter output, final PlainMethod pm) { + final Method m = pm.getWrappedMethod(); if (null != prologNameOpt) { output.print(getPrologObjectNameOpt()); output.print('.'); @@ -885,12 +848,12 @@ public class BuildComposablePipeline { } @Override - protected boolean hasPostDownstreamCallHook(final Method m) { + protected boolean hasPostDownstreamCallHook(final PlainMethod pm) { return false; } @Override - protected void postDownstreamCallHook(final PrintWriter output, final Method m) { + protected void postDownstreamCallHook(final PrintWriter output, final PlainMethod pm) { } } // end class CustomPipeline @@ -1025,22 +988,23 @@ public class BuildComposablePipeline { } @Override - protected boolean hasPreDownstreamCallHook(final Method m) { - return true; + protected boolean hasPreDownstreamCallHook(final PlainMethod pm) { + return !pm.isSynthetic(); } @Override - protected void preDownstreamCallHook(final PrintWriter output, final Method m) { + protected void preDownstreamCallHook(final PrintWriter output, final PlainMethod pm) { output.println(" checkContext();"); } @Override - protected boolean hasPostDownstreamCallHook(final Method m) { - return true; + protected boolean hasPostDownstreamCallHook(final PlainMethod pm) { + return !pm.isSynthetic(); } @Override - protected void postDownstreamCallHook(final PrintWriter output, final Method m) { + protected void postDownstreamCallHook(final PrintWriter output, final PlainMethod pm) { + final Method m = pm.getWrappedMethod(); if (m.getName().equals("glBegin")) { output.println(" insideBeginEndPair = true;"); output.println(" // NOTE: can't check glGetError(); it's not allowed inside glBegin/glEnd pair"); @@ -1191,12 +1155,13 @@ public class BuildComposablePipeline { } @Override - protected boolean hasPreDownstreamCallHook(final Method m) { - return true; + protected boolean hasPreDownstreamCallHook(final PlainMethod pm) { + return !pm.isSynthetic(); } @Override - protected void preDownstreamCallHook(final PrintWriter output, final Method m) { + protected void preDownstreamCallHook(final PrintWriter output, final PlainMethod pm) { + final Method m = pm.getWrappedMethod(); if (m.getName().equals("glEnd") || m.getName().equals("glEndList")) { output.println(" indent-=2;"); output.println(" printIndent();"); @@ -1210,12 +1175,13 @@ public class BuildComposablePipeline { } @Override - protected boolean hasPostDownstreamCallHook(final Method m) { - return true; + protected boolean hasPostDownstreamCallHook(final PlainMethod pm) { + return !pm.isSynthetic(); } @Override - protected void postDownstreamCallHook(final PrintWriter output, final Method m) { + protected void postDownstreamCallHook(final PrintWriter output, final PlainMethod pm) { + final Method m = pm.getWrappedMethod(); final Class<?> ret = m.getReturnType(); if (ret != Void.TYPE) { output.println(" println(\" = \"+_res);"); diff --git a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java index e7c452fb4..bc500d87f 100644 --- a/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java +++ b/src/jogl/classes/com/jogamp/graph/curve/OutlineShape.java @@ -796,4 +796,10 @@ public class OutlineShape implements Comparable<OutlineShape> { public final int hashCode() { throw new InternalError("hashCode not designed"); } + + @Override + public String toString() { + // Avoid calling this.hashCode() ! + return getClass().getName() + "@" + Integer.toHexString(super.hashCode()); + } } diff --git a/src/jogl/classes/com/jogamp/graph/font/Font.java b/src/jogl/classes/com/jogamp/graph/font/Font.java index 92d35768b..52ad4076d 100644 --- a/src/jogl/classes/com/jogamp/graph/font/Font.java +++ b/src/jogl/classes/com/jogamp/graph/font/Font.java @@ -88,7 +88,7 @@ public interface Font { float getScale(final float pixelSize); /** * @param dest AABBox instance set to this metrics boundary w/ given pixelSize - * @param pixelSize + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} * @param tmpV3 caller provided temporary 3-component vector * @return the given and set AABBox 'dest' */ @@ -113,14 +113,25 @@ public interface Font { public char getSymbol(); public short getID(); public AABBox getBBox(); + /** + * + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} + * @return + */ public float getScale(final float pixelSize); /** * @param dest AABBox instance set to this metrics boundary w/ given pixelSize - * @param pixelSize + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} * @param tmpV3 caller provided temporary 3-component vector * @return the given and set AABBox 'dest' */ public AABBox getBBox(final AABBox dest, final float pixelSize, float[] tmpV3); + /** + * + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link Font#getPixelSize(float, float)} + * @param useFrationalMetrics + * @return + */ public float getAdvance(final float pixelSize, boolean useFrationalMetrics); public OutlineShape getShape(); public int hashCode(); @@ -153,13 +164,37 @@ public interface Font { */ public float getPixelSize(final float fontSize /* points per inch */, final float resolution); + /** + * + * @param glyphID + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} + * @return + */ public float getAdvanceWidth(final int glyphID, final float pixelSize); public Metrics getMetrics(); public Glyph getGlyph(final char symbol); public int getNumGlyphs(); + /** + * + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} + * @return + */ public float getLineHeight(final float pixelSize); + /** + * + * @param string + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} + * @return + */ public float getMetricWidth(final CharSequence string, final float pixelSize); + /** + * + * @param string + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} + * @param tmp + * @return + */ public float getMetricHeight(final CharSequence string, final float pixelSize, final AABBox tmp); /** * Return the <i>layout</i> bounding box as computed by each glyph's metrics. @@ -168,7 +203,7 @@ public interface Font { * See {@link #getPointsBounds(AffineTransform, CharSequence, float, AffineTransform, AffineTransform)} for pixel correct results. * </p> * @param string string text - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} */ public AABBox getMetricBounds(final CharSequence string, final float pixelSize); @@ -176,7 +211,7 @@ public interface Font { * Return the bounding box by taking each glyph's point-based bounding box into account. * @param transform optional given transform * @param string string text - * @param pixelSize Use {@link Font#getPixelSize(float, float)} for resolution correct pixel-size. + * @param pixelSize Use <code>pointSize * resolution</code> for resolution correct pixel-size, see {@link #getPixelSize(float, float)} * @param temp1 temporary AffineTransform storage, mandatory * @param temp2 temporary AffineTransform storage, mandatory */ diff --git a/src/jogl/classes/com/jogamp/graph/geom/Outline.java b/src/jogl/classes/com/jogamp/graph/geom/Outline.java index 4caf06e5d..075d957ea 100644 --- a/src/jogl/classes/com/jogamp/graph/geom/Outline.java +++ b/src/jogl/classes/com/jogamp/graph/geom/Outline.java @@ -278,4 +278,9 @@ public class Outline implements Comparable<Outline> { public final int hashCode() { throw new InternalError("hashCode not designed"); } + @Override + public String toString() { + // Avoid calling this.hashCode() ! + return getClass().getName() + "@" + Integer.toHexString(super.hashCode()); + } } diff --git a/src/jogl/classes/com/jogamp/opengl/FBObject.java b/src/jogl/classes/com/jogamp/opengl/FBObject.java index 8c076e742..03693a688 100644 --- a/src/jogl/classes/com/jogamp/opengl/FBObject.java +++ b/src/jogl/classes/com/jogamp/opengl/FBObject.java @@ -37,12 +37,14 @@ import javax.media.opengl.GL2GL3; import javax.media.opengl.GL3; import javax.media.opengl.GLBase; import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLCapabilitiesImmutable; import javax.media.opengl.GLContext; import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; import jogamp.opengl.Debug; +import com.jogamp.common.util.PropertyAccess; import com.jogamp.opengl.FBObject.Attachment.Type; /** @@ -60,15 +62,21 @@ import com.jogamp.opengl.FBObject.Attachment.Type; * <p>FIXME: Implement support for {@link Type#DEPTH_TEXTURE}, {@link Type#STENCIL_TEXTURE} .</p> */ public class FBObject { - protected static final boolean DEBUG = Debug.debug("FBObject"); + protected static final boolean DEBUG; + private static final int USER_MAX_TEXTURE_SIZE; private static final boolean FBOResizeQuirk = false; + static { + Debug.initSingleton(); + DEBUG = Debug.debug("FBObject"); + USER_MAX_TEXTURE_SIZE = PropertyAccess.getIntProperty("jogl.debug.FBObject.MaxTextureSize", true, 0); + } + private static enum DetachAction { NONE, DISPOSE, RECREATE }; /** - * Marker interface, denotes a color buffer attachment. + * Generic color buffer FBO attachment, either of type {@link ColorAttachment} or {@link TextureAttachment}. * <p>Always an instance of {@link Attachment}.</p> - * <p>Either an instance of {@link ColorAttachment} or {@link TextureAttachment}.</b> */ public static interface Colorbuffer { /** @@ -76,23 +84,55 @@ public class FBObject { * @return <code>true</code> if newly initialized, otherwise <code>false</code>. * @throws GLException if buffer generation or setup fails. The just created buffer name will be deleted in this case. */ - public boolean initialize(GL gl) throws GLException; + boolean initialize(final GL gl) throws GLException; /** * Releases the color buffer if initialized, i.e. name is not <code>zero</code>. * @throws GLException if buffer release fails. */ - public void free(GL gl) throws GLException; + void free(final GL gl) throws GLException; /** * Writes the internal format to the given GLCapabilities object. * @param caps the destination for format bits * @param rgba8Avail whether rgba8 is available */ - public void formatToGLCapabilities(GLCapabilities caps, boolean rgba8Avail); + void formatToGLCapabilities(final GLCapabilities caps, final boolean rgba8Avail); + + /** + * Returns <code>true</code> if instance is of type {@link TextureAttachment} + * and <code>false</code> if instance is of type {@link ColorAttachment}. + */ + boolean isTextureAttachment(); + + /** + * Casts this object to a {@link TextureAttachment} reference, see {@link #isTextureAttachment()}. + * @throws GLException if this object is not of type {@link TextureAttachment} + * @see #isTextureAttachment() + */ + TextureAttachment getTextureAttachment(); + + /** + * Casts this object to a {@link ColorAttachment} reference, see {@link #isTextureAttachment()}. + * @throws GLException if this object is not of type {@link ColorAttachment} + * @see #isTextureAttachment() + */ + ColorAttachment getColorAttachment(); + + /** internal format of colorbuffer */ + int getFormat(); + + /** width of colorbuffer */ + int getWidth(); + + /** height of colorbuffer */ + int getHeight(); + + /** colorbuffer name [1..max] */ + int getName(); } - /** Common super class of all attachments */ + /** Common super class of all FBO attachments */ public static abstract class Attachment { public enum Type { NONE, DEPTH, STENCIL, DEPTH_STENCIL, COLOR, COLOR_TEXTURE, DEPTH_TEXTURE, STENCIL_TEXTURE; @@ -220,6 +260,9 @@ public class FBObject { } } + /** immutable internal format of attachment */ + public final int getFormat() { return format; } + /** width of attachment */ public final int getWidth() { return width; } /** height of attachment */ @@ -242,7 +285,7 @@ public class FBObject { * @return <code>true</code> if newly initialized, otherwise <code>false</code>. * @throws GLException if buffer generation or setup fails. The just created buffer name will be deleted in this case. */ - public abstract boolean initialize(GL gl) throws GLException; + public abstract boolean initialize(final GL gl) throws GLException; /** * Releases the attachment if initialized, i.e. name is not <code>zero</code>. @@ -254,7 +297,7 @@ public class FBObject { * </pre> * @throws GLException if buffer release fails. */ - public abstract void free(GL gl) throws GLException; + public abstract void free(final GL gl) throws GLException; /** * <p> @@ -379,8 +422,10 @@ public class FBObject { public boolean initialize(final GL gl) throws GLException { final boolean init = 0 == getName(); if( init ) { - checkPreGLError(gl); - + final boolean checkError = DEBUG || GLContext.DEBUG_GL; + if( checkError ) { + checkPreGLError(gl); + } final int[] name = new int[] { -1 }; gl.glGenRenderbuffers(1, name, 0); setName(name[0]); @@ -391,11 +436,13 @@ public class FBObject { } else { gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, format, getWidth(), getHeight()); } - final int glerr = gl.glGetError(); - if(GL.GL_NO_ERROR != glerr) { - gl.glDeleteRenderbuffers(1, name, 0); - setName(0); - throw new GLException("GL Error "+toHexString(glerr)+" while creating "+this); + if( checkError ) { + final int glerr = gl.glGetError(); + if(GL.GL_NO_ERROR != glerr) { + gl.glDeleteRenderbuffers(1, name, 0); + setName(0); + throw new GLException("GL Error "+toHexString(glerr)+" while creating "+this); + } } if(DEBUG) { System.err.println("Attachment.init.X: "+this); @@ -423,14 +470,21 @@ public class FBObject { } } - /** Color render buffer attachment */ + /** Color render buffer FBO attachment */ public static class ColorAttachment extends RenderAttachment implements Colorbuffer { public ColorAttachment(final int iFormat, final int samples, final int width, final int height, final int name) { super(Type.COLOR, iFormat, samples, width, height, name); } + @Override + public final boolean isTextureAttachment() { return false; } + @Override + public final TextureAttachment getTextureAttachment() { throw new GLException("Not a TextureAttachment, but ColorAttachment"); } + @Override + public final ColorAttachment getColorAttachment() { return this; } + } - /** Texture attachment */ + /** Texture FBO attachment */ public static class TextureAttachment extends Attachment implements Colorbuffer { /** details of the texture setup */ public final int dataFormat, dataType, magFilter, minFilter, wrapS, wrapT; @@ -478,8 +532,10 @@ public class FBObject { public boolean initialize(final GL gl) throws GLException { final boolean init = 0 == getName(); if( init ) { - checkPreGLError(gl); - + final boolean checkError = DEBUG || GLContext.DEBUG_GL; + if( checkError ) { + checkPreGLError(gl); + } final int[] name = new int[] { -1 }; gl.glGenTextures(1, name, 0); if(0 == name[0]) { @@ -500,17 +556,21 @@ public class FBObject { if( 0 < wrapT ) { gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, wrapT); } - boolean preTexImage2D = true; - int glerr = gl.glGetError(); - if(GL.GL_NO_ERROR == glerr) { - preTexImage2D = false; + if( checkError ) { + boolean preTexImage2D = true; + int glerr = gl.glGetError(); + if(GL.GL_NO_ERROR == glerr) { + preTexImage2D = false; + gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, format, getWidth(), getHeight(), 0, dataFormat, dataType, null); + glerr = gl.glGetError(); + } + if(GL.GL_NO_ERROR != glerr) { + gl.glDeleteTextures(1, name, 0); + setName(0); + throw new GLException("GL Error "+toHexString(glerr)+" while creating (pre TexImage2D "+preTexImage2D+") "+this); + } + } else { gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, format, getWidth(), getHeight(), 0, dataFormat, dataType, null); - glerr = gl.glGetError(); - } - if(GL.GL_NO_ERROR != glerr) { - gl.glDeleteTextures(1, name, 0); - setName(0); - throw new GLException("GL Error "+toHexString(glerr)+" while creating (pre TexImage2D "+preTexImage2D+") "+this); } if(DEBUG) { System.err.println("Attachment.init.X: "+this); @@ -530,6 +590,14 @@ public class FBObject { setName(0); } } + + @Override + public final boolean isTextureAttachment() { return true; } + @Override + public final TextureAttachment getTextureAttachment() { return this; } + @Override + public final ColorAttachment getColorAttachment() { throw new GLException("Not a ColorAttachment, but TextureAttachment"); } + @Override public String toString() { return getClass().getSimpleName()+"[type "+type+", target GL_TEXTURE_2D, level 0, format "+toHexString(format)+ @@ -566,7 +634,7 @@ public class FBObject { * <p> * For GLES3, sampling-sink format <b>must be equal</b> w/ the sampling-source {@link Colorbuffer}, * see details below. Implementation aligns w/ {@link #createColorAttachment(boolean)} - * and is enforced via {@link #sampleSinkFormatMismatch(GL)}. + * and is enforced via {@link #sampleSinkExFormatMismatch(GL)}. * </p> * <p> * ES3 BlitFramebuffer Requirements: OpenGL ES 3.0.2 p194: 4.3.2 Copying Pixels @@ -607,23 +675,40 @@ public class FBObject { */ public static final TextureAttachment createColorTextureAttachment(final GL gl, final boolean alpha, final int width, final int height, final int magFilter, final int minFilter, final int wrapS, final int wrapT) { - final int textureInternalFormat, textureDataFormat, textureDataType; + final int internalFormat, dataFormat, dataType; if(gl.isGLES3()) { - textureInternalFormat = alpha ? GL.GL_RGBA8 : GL.GL_RGB8; - textureDataFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; - textureDataType = GL.GL_UNSIGNED_BYTE; + internalFormat = alpha ? GL.GL_RGBA8 : GL.GL_RGB8; + dataFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; } else if(gl.isGLES()) { - textureInternalFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; - textureDataFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; - textureDataType = GL.GL_UNSIGNED_BYTE; + internalFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; + dataFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; } else { - textureInternalFormat = alpha ? GL.GL_RGBA8 : GL.GL_RGB8; + internalFormat = alpha ? GL.GL_RGBA8 : GL.GL_RGB8; // textureInternalFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; // textureInternalFormat = alpha ? 4 : 3; - textureDataFormat = alpha ? GL.GL_BGRA : GL.GL_RGB; - textureDataType = alpha ? GL2GL3.GL_UNSIGNED_INT_8_8_8_8_REV : GL.GL_UNSIGNED_BYTE; + dataFormat = alpha ? GL.GL_BGRA : GL.GL_RGB; + dataType = alpha ? GL2GL3.GL_UNSIGNED_INT_8_8_8_8_REV : GL.GL_UNSIGNED_BYTE; + } + return createColorTextureAttachment(internalFormat, width, height, dataFormat, dataType, magFilter, minFilter, wrapS, wrapT); + } + + public static final TextureAttachment createColorTextureAttachment(final GL gl, final int internalFormat, final int width, final int height, + final int magFilter, final int minFilter, final int wrapS, final int wrapT) { + final int dataFormat, dataType; + final boolean alpha = hasAlpha(internalFormat); + if( gl.isGLES3() ) { + dataFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; + } else if( gl.isGLES() ) { + dataFormat = alpha ? GL.GL_RGBA : GL.GL_RGB; + dataType = GL.GL_UNSIGNED_BYTE; + } else { + dataFormat = alpha ? GL.GL_BGRA : GL.GL_RGB; + dataType = alpha ? GL2GL3.GL_UNSIGNED_INT_8_8_8_8_REV : GL.GL_UNSIGNED_BYTE; } - return createColorTextureAttachment(textureInternalFormat, width, height, textureDataFormat, textureDataType, magFilter, minFilter, wrapS, wrapT); + return createColorTextureAttachment(internalFormat, width, height, dataFormat, dataType, magFilter, minFilter, wrapS, wrapT); } /** @@ -677,12 +762,14 @@ public class FBObject { private int fbName; private boolean bound; - private int colorAttachmentCount; - private Colorbuffer[] colorAttachmentPoints; // colorbuffer attachment points + private int colorbufferCount; + private int textureAttachmentCount; + private Colorbuffer[] colorbufferAttachments; // colorbuffer attachment points private RenderAttachment depth, stencil; // depth and stencil maybe equal in case of packed-depth-stencil + private boolean modified; // size, sampleCount, or any attachment modified private FBObject samplingSink; // MSAA sink - private TextureAttachment samplingSinkTexture; + private Colorbuffer samplingColorSink; private boolean samplingSinkDirty; // @@ -693,39 +780,60 @@ public class FBObject { if(!initialized) { throw new GLException("FBO not initialized"); } - if(maxColorAttachments != colorAttachmentPoints.length) { - throw new InternalError("maxColorAttachments "+maxColorAttachments+", array.length "+colorAttachmentPoints.length); + if(maxColorAttachments != colorbufferAttachments.length) { + throw new InternalError(String.format("maxColorAttachments %d, array.length %d", + maxColorAttachments, colorbufferAttachments.length) ); } if(0 > point || point >= maxColorAttachments) { - throw new IllegalArgumentException("attachment point out of range: "+point+", should be within [0.."+(maxColorAttachments-1)+"], "+this); + throw new IllegalArgumentException(String.format("attachment point out of range: %d, should be within [0..%d], %s", + point, maxColorAttachments-1, this.toString() ) ); } } private final void validateAddColorAttachment(final int point, final Colorbuffer ca) { validateColorAttachmentPointRange(point); - if( null != colorAttachmentPoints[point] ) { - throw new IllegalArgumentException("Cannot attach "+ca+", attachment point already in use by "+colorAttachmentPoints[point]+", "+this); + if( null != colorbufferAttachments[point] ) { + throw new IllegalStateException(String.format("Cannot attach %s at %d, attachment point already in use by %s, %s", + ca.toString(), point, colorbufferAttachments[point].toString(), this.toString() ) ); } } - private final void addColorAttachment(final int point, final Colorbuffer ca) { - validateColorAttachmentPointRange(point); - final Colorbuffer c = colorAttachmentPoints[point]; - if( null != c && c != ca ) { - throw new IllegalArgumentException("Add failed: requested to add "+ca+" at "+point+", but slot is holding "+c+"; "+this); + private final void addColorAttachment(final int point, final Colorbuffer ca, final boolean validate) { + final Colorbuffer c = colorbufferAttachments[point]; + if( validate ) { + validateColorAttachmentPointRange(point); + if( null == ca ) { + throw new IllegalArgumentException("Colorbuffer is null"); + } + if( null != c ) { + throw new IllegalStateException(String.format("Cannot attach %s at %d, attachment point already in use by %s, %s", + ca.toString(), point, c.toString(), this.toString() ) ); + } + } + colorbufferAttachments[point] = ca; + colorbufferCount++; + if( ca.isTextureAttachment() ) { + textureAttachmentCount++; } - colorAttachmentPoints[point] = ca; - colorAttachmentCount++; + modified = true; } private final void removeColorAttachment(final int point, final Colorbuffer ca) { validateColorAttachmentPointRange(point); - final Colorbuffer c = colorAttachmentPoints[point]; - if( null != c && c != ca ) { - throw new IllegalArgumentException("Remove failed: requested to removed "+ca+" at "+point+", but slot is holding "+c+"; "+this); + if( null == ca ) { + throw new IllegalArgumentException("Colorbuffer is null"); + } + final Colorbuffer c = colorbufferAttachments[point]; + if( c != ca ) { + throw new IllegalStateException(String.format("Cannot detach %s at %d, slot is holding other: %s, %s", + ca.toString(), point, c.toString(), this.toString() ) ); + } + colorbufferAttachments[point] = null; + colorbufferCount--; + if( ca.isTextureAttachment() ) { + textureAttachmentCount--; } - colorAttachmentPoints[point] = null; - colorAttachmentCount--; + modified = true; } /** @@ -738,7 +846,7 @@ public class FBObject { */ public final Colorbuffer getColorbuffer(final int attachmentPoint) { validateColorAttachmentPointRange(attachmentPoint); - return colorAttachmentPoints[attachmentPoint]; + return colorbufferAttachments[attachmentPoint]; } /** @@ -751,8 +859,8 @@ public class FBObject { * @return -1 if the {@link Colorbuffer} could not be found, otherwise [0..{@link #getMaxColorAttachments()}-1] */ public final int getColorbufferAttachmentPoint(final Colorbuffer ca) { - for(int i=0; i<colorAttachmentPoints.length; i++) { - if( colorAttachmentPoints[i] == ca ) { + for(int i=0; i<colorbufferAttachments.length; i++) { + if( colorbufferAttachments[i] == ca ) { return i; } } @@ -783,7 +891,7 @@ public class FBObject { * otherwise false. */ public final boolean hasAttachmentUsingAlpha() { - final int caCount = getColorAttachmentCount(); + final int caCount = getColorbufferCount(); boolean hasAlpha = false; for(int i=0; i<caCount; i++) { final Attachment ca = (Attachment)getColorbuffer(i); @@ -830,19 +938,37 @@ public class FBObject { this.fbName = 0; this.bound = false; - this.colorAttachmentPoints = null; // at init .. - this.colorAttachmentCount = 0; + this.colorbufferAttachments = null; // at init .. + this.colorbufferCount = 0; + this.textureAttachmentCount = 0; this.depth = null; this.stencil = null; + this.modified = true; this.samplingSink = null; - this.samplingSinkTexture = null; + this.samplingColorSink = null; this.samplingSinkDirty = true; } - private void init(final GL gl, int width, int height, final int samples) throws GLException { - if(initialized) { - throw new GLException("FBO already initialized"); + /** + * Initializes this FBO's instance. + * <p> + * The sampling sink is not initializes, allowing manual assignment via {@link #setSamplingSink(FBObject)} + * if {@code newSamples > 0}. + * </p> + * + * <p>Leaves the FBO bound</p> + * + * @param gl the current GL context + * @param newWidth the initial width, it's minimum is capped to 1 + * @param newHeight the initial height, it's minimum is capped to 1 + * @param newSamples if > 0, MSAA will be used, otherwise no multisampling. Will be capped to {@link #getMaxSamples()}. + * @throws IllegalStateException if already initialized + * @throws GLException in case of an error, i.e. size too big, etc .. + */ + public void init(final GL gl, final int newWidth, final int newHeight, final int newSamples) throws IllegalStateException, GLException { + if( initialized ) { + throw new IllegalStateException("FBO already initialized"); } if( !gl.hasBasicFBOSupport() ) { throw new GLException("FBO not supported w/ context: "+gl.getContext()+", "+this); @@ -878,29 +1004,31 @@ public class FBObject { } maxColorAttachments = realMaxColorAttachments <= 8 ? realMaxColorAttachments : 8; // cap to limit array size - colorAttachmentPoints = new Colorbuffer[maxColorAttachments]; - colorAttachmentCount = 0; + colorbufferAttachments = new Colorbuffer[maxColorAttachments]; + colorbufferCount = 0; + textureAttachmentCount = 0; - maxSamples = gl.getMaxRenderbufferSamples(); + maxSamples = gl.getMaxRenderbufferSamples(); // if > 0 implies fullFBOSupport gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, val, 0); - maxTextureSize = val[0]; + final int _maxTextureSize = val[0]; + if( 0 < USER_MAX_TEXTURE_SIZE ) { + maxTextureSize = USER_MAX_TEXTURE_SIZE; + } else { + maxTextureSize = _maxTextureSize; + } gl.glGetIntegerv(GL.GL_MAX_RENDERBUFFER_SIZE, val, 0); maxRenderbufferSize = val[0]; - checkPreGLError(gl); - - if( 0 >= width ) { width = 1; } - if( 0 >= height ) { height = 1; } - this.width = width; - this.height = height; - this.samples = samples <= maxSamples ? samples : maxSamples; + this.width = 0 < newWidth ? newWidth : 1; + this.height = 0 < newHeight ? newHeight : 1; + this.samples = newSamples <= maxSamples ? newSamples : maxSamples; if(DEBUG) { - System.err.println("FBObject "+width+"x"+height+", "+samples+" -> "+this.samples+" samples"); + System.err.println("FBObject.init() START: "+width+"x"+height+", "+newSamples+" -> "+this.samples+" samples"); System.err.println("fullFBOSupport: "+fullFBOSupport); System.err.println("maxColorAttachments: "+maxColorAttachments+"/"+realMaxColorAttachments+" [capped/real]"); System.err.println("maxSamples: "+maxSamples); - System.err.println("maxTextureSize: "+maxTextureSize); + System.err.println("maxTextureSize: "+_maxTextureSize+" -> "+maxTextureSize); System.err.println("maxRenderbufferSize: "+maxRenderbufferSize); System.err.println("rgba8: "+rgba8Avail); System.err.println("depth24: "+depth24Avail); @@ -912,18 +1040,17 @@ public class FBObject { System.err.println("packedDepthStencil: "+packedDepthStencilAvail); System.err.println("NV_fbo_color_attachments: "+NV_fbo_color_attachments); System.err.println(gl.getContext().getGLVersion()); - System.err.println(JoglVersion.getGLStrings(gl, null).toString()); - System.err.println(gl.getContext()); + System.err.println(JoglVersion.getGLStrings(gl, null, false).toString()); } - checkNoError(null, gl.glGetError(), "FBObject Init.pre"); // throws GLException if error + checkPreGLError(gl); - if(width > 2 + maxTextureSize || height> 2 + maxTextureSize || - width > maxRenderbufferSize || height> maxRenderbufferSize ) { - throw new GLException("size "+width+"x"+height+" exceeds on of the maxima [texture "+maxTextureSize+", renderbuffer "+maxRenderbufferSize+"]"); + if( width > maxRenderbufferSize || height > maxRenderbufferSize ) { + throw new GLException("Size "+width+"x"+height+" exceeds on of the maxima renderbuffer size "+maxRenderbufferSize+": \n\t"+this); } - resetSamplingSink(gl); + modified = true; + samplingSinkDirty = true; // generate fbo .. gl.glGenFramebuffers(1, val, 0); @@ -939,48 +1066,26 @@ public class FBObject { checkNoError(gl, GL.GL_INVALID_VALUE, "FBObject Init.isFB"); // throws GLException } bound = true; - samplingSinkDirty = true; initialized = true; vStatus = GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; // always incomplete w/o attachments! if(DEBUG) { - System.err.println("FBObject.init(): "+this); + System.err.println("FBObject.init() END: "+this); + Thread.dumpStack(); } } /** - * Initializes or resets this FBO's instance. - * <p> - * In case the new parameters are compatible with the current ones - * no action will be performed. Otherwise all attachments will be recreated - * to match the new given parameters. - * </p> - * <p> - * Incompatibility and hence recreation is forced if - * the size or sample count doesn't match for subsequent calls. - * </p> - * - * <p>Leaves the FBO bound state untouched</p> - * - * @param gl the current GL context - * @param newWidth - * @param newHeight - * @throws GLException in case of an error - */ - public final void reset(final GL gl, final int newWidth, final int newHeight) { - reset(gl, newWidth, newHeight, 0, false); - } - - /** - * Initializes or resets this FBO's instance. + * Resets this FBO's instance. * <p> * In case the new parameters are compatible with the current ones - * no action will be performed. Otherwise all attachments will be recreated + * no action will be performed and method returns immediately.<br> + * Otherwise all attachments will be recreated * to match the new given parameters. * </p> * <p> - * Currently incompatibility and hence recreation of the attachments will be performed - * if the size or sample count doesn't match for subsequent calls. + * {@link #resetSamplingSink(GL)} is being issued immediately + * to match the new configuration. * </p> * * <p>Leaves the FBO bound state untouched</p> @@ -989,17 +1094,13 @@ public class FBObject { * @param newWidth the new width, it's minimum is capped to 1 * @param newHeight the new height, it's minimum is capped to 1 * @param newSamples if > 0, MSAA will be used, otherwise no multisampling. Will be capped to {@link #getMaxSamples()}. - * @param resetSamplingSink <code>true</code> calls {@link #resetSamplingSink(GL)} immediatly. - * <code>false</code> postpones resetting the sampling sink until {@link #use(GL, TextureAttachment)} or {@link #syncSamplingSink(GL)}, - * allowing to use the samples sink's FBO and texture until then. The latter is useful to benefit - * from implicit double buffering while resetting the sink just before it's being used, eg. at swap-buffer. - * + * @return {@code true} if this instance has been modified, otherwise {@code false}. + * @throws IllegalStateException if not initialized via {@link #init(GL, int, int, int)}. * @throws GLException in case of an error, i.e. size too big, etc .. */ - public final void reset(final GL gl, int newWidth, int newHeight, int newSamples, final boolean resetSamplingSink) { + public final boolean reset(final GL gl, int newWidth, int newHeight, int newSamples) throws GLException, IllegalStateException { if( !initialized ) { - init(gl, newWidth, newHeight, newSamples); - return; + throw new IllegalStateException("FBO not initialized"); } newSamples = newSamples <= maxSamples ? newSamples : maxSamples; // clamp @@ -1007,9 +1108,11 @@ public class FBObject { if( newWidth != width || newHeight != height || newSamples != samples ) { if( 0 >= newWidth ) { newWidth = 1; } if( 0 >= newHeight ) { newHeight = 1; } - if( newWidth > 2 + maxTextureSize || newHeight > 2 + maxTextureSize || - newWidth > maxRenderbufferSize || newHeight > maxRenderbufferSize ) { - throw new GLException("size "+width+"x"+height+" exceeds on of the maxima [texture "+maxTextureSize+", renderbuffer "+maxRenderbufferSize+"]"); + if( textureAttachmentCount > 0 && ( newWidth > 2 + maxTextureSize || newHeight > 2 + maxTextureSize ) ) { + throw new GLException("Size "+newWidth+"x"+newHeight+" exceeds on of the maximum texture size "+maxTextureSize+": \n\t"+this); + } + if( newWidth > maxRenderbufferSize || newHeight > maxRenderbufferSize ) { + throw new GLException("Size "+newWidth+"x"+newHeight+" exceeds on of the maxima renderbuffer size "+maxRenderbufferSize+": \n\t"+this); } if(DEBUG) { @@ -1018,29 +1121,76 @@ public class FBObject { final boolean wasBound = isBound(); + final int sampleCountChange; + if( 0 < samples && 0 < newSamples || 0 == samples && 0 == newSamples ) { + sampleCountChange = 0; // keep MSAA settings + } else if( 0 == samples && 0 < newSamples ) { + sampleCountChange = 1; // add MSAA + } else if( 0 < samples && 0 == newSamples ) { + sampleCountChange = -1; // remove MSAA + } else { + throw new IllegalArgumentException("Error in sampleCount change: "+samples+" -> "+newSamples); + } width = newWidth; height = newHeight; samples = newSamples; - if(0 < samples && null == samplingSink ) { - // needs valid samplingSink for detach*() -> bind() - samplingSink = new FBObject(); - samplingSink.init(gl, width, height, 0); - } - detachAllImpl(gl, true , true); - if(resetSamplingSink) { - resetSamplingSink(gl); - } - + modified = true; samplingSinkDirty = true; + detachAllImpl(gl, true, true, sampleCountChange); + resetSamplingSink(gl); + if(!wasBound) { unbind(gl); } if(DEBUG) { - System.err.println("FBObject.reset - END - "+this); + System.err.println("FBObject.reset - END - wasBound, "+wasBound+", "+this); } + return true; + } else { + return false; + } + } + + /** + * Simply resets this instance's size only, w/o validation. + * + * <p>Leaves the FBO bound</p> + * + * @param gl the current GL context + * @param newWidth the new width, it's minimum is capped to 1 + * @param newHeight the new height, it's minimum is capped to 1 + */ + private final void resetSizeImpl(final GL gl, final int newWidth, final int newHeight) { + if(DEBUG) { + System.err.println("FBObject.resetSize - START - "+width+"x"+height+", "+samples+" -> "+newWidth+"x"+newHeight); + } + + final int sampleCountChange = 0; // keep MSAA settings + width = newWidth; + height = newHeight; + + modified = true; + samplingSinkDirty = true; + + detachAllImpl(gl, true, true, sampleCountChange); + + if(DEBUG) { + System.err.println("FBObject.resetSize - END - "+this); + } + } + + private void validateAttachmentSize(final Attachment a) { + final int aWidth = a.getWidth(); + final int aHeight = a.getHeight(); + + if( a instanceof TextureAttachment && ( aWidth > 2 + maxTextureSize || aHeight > 2 + maxTextureSize ) ) { + throw new GLException("Size "+aWidth+"x"+aHeight+" of "+a+" exceeds on of the maximum texture size "+maxTextureSize+": \n\t"+this); + } + if( aWidth > maxRenderbufferSize || aHeight > maxRenderbufferSize ) { + throw new GLException("Size "+aWidth+"x"+aHeight+" of "+a+" exceeds on of the maxima renderbuffer size "+maxRenderbufferSize+": \n\t"+this); } } @@ -1134,7 +1284,7 @@ public class FBObject { case GL2GL3.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: case GL2ES3.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: case GL3.GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: - if(0 == colorAttachmentCount || null == depth) { + if(0 == colorbufferCount || null == depth) { // we are in transition return true; } @@ -1196,8 +1346,8 @@ public class FBObject { * @see #createColorTextureAttachment(GLProfile, boolean, int, int) */ public final TextureAttachment attachTexture2D(final GL gl, final int attachmentPoint, final boolean alpha) throws GLException { - return (TextureAttachment)attachColorbuffer(gl, attachmentPoint, - createColorTextureAttachment(gl, alpha, width, height)); + return attachColorbuffer(gl, attachmentPoint, + createColorTextureAttachment(gl, alpha, width, height)).getTextureAttachment(); } /** @@ -1218,8 +1368,8 @@ public class FBObject { * @see #createColorTextureAttachment(GLProfile, boolean, int, int, int, int, int, int) */ public final TextureAttachment attachTexture2D(final GL gl, final int attachmentPoint, final boolean alpha, final int magFilter, final int minFilter, final int wrapS, final int wrapT) throws GLException { - return (TextureAttachment)attachColorbuffer(gl, attachmentPoint, - createColorTextureAttachment(gl, alpha, width, height, magFilter, minFilter, wrapS, wrapT)); + return attachColorbuffer(gl, attachmentPoint, + createColorTextureAttachment(gl, alpha, width, height, magFilter, minFilter, wrapS, wrapT)).getTextureAttachment(); } /** @@ -1243,8 +1393,8 @@ public class FBObject { public final TextureAttachment attachTexture2D(final GL gl, final int attachmentPoint, final int internalFormat, final int dataFormat, final int dataType, final int magFilter, final int minFilter, final int wrapS, final int wrapT) throws GLException { - return (TextureAttachment)attachColorbuffer(gl, attachmentPoint, - createColorTextureAttachment(internalFormat, width, height, dataFormat, dataType, magFilter, minFilter, wrapS, wrapT)); + return attachColorbuffer(gl, attachmentPoint, + createColorTextureAttachment(internalFormat, width, height, dataFormat, dataType, magFilter, minFilter, wrapS, wrapT)).getTextureAttachment(); } /** @@ -1252,7 +1402,7 @@ public class FBObject { * <p> * For GLES3, sampling-sink {@link Colorbuffer} format <b>must be equal</b> w/ the sampling-source {@link Colorbuffer}. * Implementation aligns w/ {@link #createColorTextureAttachment(GLProfile, boolean, int, int, int, int, int, int)} - * and is enforced via {@link #sampleSinkFormatMismatch(GL)}. + * and is enforced via {@link #sampleSinkExFormatMismatch(GL)}. * </p> * * @param alpha set to <code>true</code> if you request alpha channel, otherwise <code>false</code>; @@ -1266,12 +1416,34 @@ public class FBObject { } else { internalFormat = alpha ? GL.GL_RGBA4 : GL.GL_RGB565; } - return new ColorAttachment(internalFormat, samples, width, height, 0); + return createColorAttachment(internalFormat, samples, width, height); } /** - * Attaches a {@link Colorbuffer}, i.e. {@link ColorAttachment}, to this FBO's instance at the given attachment point, - * selecting the format automatically. + * Creates a {@link ColorAttachment}, selecting the format automatically. + * <p> + * For GLES3, sampling-sink {@link Colorbuffer} format <b>must be equal</b> w/ the sampling-source {@link Colorbuffer}. + * Implementation aligns w/ {@link #createColorTextureAttachment(GLProfile, boolean, int, int, int, int, int, int)} + * and is enforced via {@link #sampleSinkExFormatMismatch(GL)}. + * </p> + * + * @param alpha set to <code>true</code> if you request alpha channel, otherwise <code>false</code>; + * @return uninitialized ColorAttachment instance describing the new attached colorbuffer + */ + public static final ColorAttachment createColorAttachment(final int internalFormat, final int samples, final int width, final int height) { + return new ColorAttachment(internalFormat, samples, width, height, 0 /* name not yet determined */); + } + + public static final RenderAttachment createRenderAttachment(final Type type, final int internalFormat, final int samples, final int width, final int height) { + return new RenderAttachment(type, internalFormat, samples, width, height, 0 /* name not yet determined */); + } + + /** + * Attaches a newly created and {@link Colorbuffer#initialize(GL) initialized} {@link Colorbuffer}, i.e. a {@link ColorAttachment}, + * at the given attachment point. + * <p> + * The {@link ColorAttachment} is created using {@code alpha} if {@code true} and current {@code sample count} and {@code size}. + * </p> * * <p>Leaves the FBO bound.</p> * @@ -1283,11 +1455,15 @@ public class FBObject { * @see #createColorAttachment(boolean) */ public final ColorAttachment attachColorbuffer(final GL gl, final int attachmentPoint, final boolean alpha) throws GLException { - return (ColorAttachment) attachColorbuffer(gl, attachmentPoint, createColorAttachment(alpha)); + return attachColorbuffer(gl, attachmentPoint, createColorAttachment(alpha)).getColorAttachment(); } /** - * Attaches a {@link Colorbuffer}, i.e. {@link ColorAttachment}, to this FBO's instance at the given attachment point. + * Attaches a newly created and {@link Colorbuffer#initialize(GL) initialized} {@link Colorbuffer}, i.e. a {@link ColorAttachment}, + * at the given attachment point. + * <p> + * The {@link ColorAttachment} is created using the given {@code internalFormat} and current {@code sample count} and {@code size}. + * </p> * * <p>Leaves the FBO bound.</p> * @@ -1304,13 +1480,15 @@ public class FBObject { throw new IllegalArgumentException("colorformat invalid: "+toHexString(internalFormat)+", "+this); } - return (ColorAttachment) attachColorbuffer(gl, attachmentPoint, new ColorAttachment(internalFormat, samples, width, height, 0)); + return attachColorbuffer(gl, attachmentPoint, createColorAttachment(internalFormat, samples, width, height)).getColorAttachment(); } /** - * Attaches a {@link Colorbuffer}, i.e. {@link ColorAttachment} or {@link TextureAttachment}, - * to this FBO's instance at the given attachment point. - * + * Attaches a {@link Colorbuffer} at the given attachment point + * and {@link Colorbuffer#initialize(GL) initializes} it, if not done yet. + * <p> + * {@link Colorbuffer} may be a {@link ColorAttachment} or {@link TextureAttachment}. + * </p> * <p> * If {@link Colorbuffer} is a {@link TextureAttachment} and is uninitialized, i.e. it's texture name is <code>zero</code>, * a new texture name is generated and setup w/ the texture parameter.<br/> @@ -1323,7 +1501,7 @@ public class FBObject { * @param gl * @param attachmentPoint the color attachment point ranging from [0..{@link #getMaxColorAttachments()}-1] * @param colbuf the to be attached {@link Colorbuffer} - * @return newly attached {@link Colorbuffer} instance if bound and configured successfully, otherwise GLException is thrown + * @return given {@link Colorbuffer} instance if bound and configured successfully, otherwise GLException is thrown * @throws GLException in case the colorbuffer couldn't be allocated or MSAA has been chosen in case of a {@link TextureAttachment} */ public final Colorbuffer attachColorbuffer(final GL gl, final int attachmentPoint, final Colorbuffer colbuf) throws GLException { @@ -1333,14 +1511,14 @@ public class FBObject { private final Colorbuffer attachColorbufferImpl(final GL gl, final int attachmentPoint, final Colorbuffer colbuf) throws GLException { validateAddColorAttachment(attachmentPoint, colbuf); + validateAttachmentSize((Attachment)colbuf); final boolean initializedColorbuf = colbuf.initialize(gl); - addColorAttachment(attachmentPoint, colbuf); + addColorAttachment(attachmentPoint, colbuf, false); - if(colbuf instanceof TextureAttachment) { - final TextureAttachment texA = (TextureAttachment) colbuf; - - if(samples>0) { + if( colbuf.isTextureAttachment() ) { + final TextureAttachment texA = colbuf.getTextureAttachment(); + if( samples > 0 ) { removeColorAttachment(attachmentPoint, texA); if(initializedColorbuf) { texA.free(gl); @@ -1360,8 +1538,8 @@ public class FBObject { throw new GLException("attachTexture2D "+texA+" at "+attachmentPoint+" failed "+getStatusString()+", "+this); } } - } else if(colbuf instanceof ColorAttachment) { - final ColorAttachment colA = (ColorAttachment) colbuf; + } else { + final ColorAttachment colA = colbuf.getColorAttachment(); // Attach the color buffer gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, @@ -1382,6 +1560,68 @@ public class FBObject { return colbuf; } + private final int getDepthIFormat(final int reqBits) { + if( 32 <= reqBits && depth32Avail ) { + return GL.GL_DEPTH_COMPONENT32; + } else if( 24 <= reqBits && ( depth24Avail || depth32Avail ) ) { + if( depth24Avail ) { + return GL.GL_DEPTH_COMPONENT24; + } else { + return GL.GL_DEPTH_COMPONENT32; + } + } else { + return GL.GL_DEPTH_COMPONENT16; + } + } + private final int getStencilIFormat(final int reqBits) { + if( 16 <= reqBits && stencil16Avail ) { + return GL2GL3.GL_STENCIL_INDEX16; + } else if( 8 <= reqBits && ( stencil08Avail || stencil16Avail ) ) { + if( stencil08Avail ) { + return GL.GL_STENCIL_INDEX8; + } else { + return GL2GL3.GL_STENCIL_INDEX16; + } + } else if( 4 <= reqBits && ( stencil04Avail || stencil08Avail || stencil16Avail ) ) { + if( stencil04Avail ) { + return GL.GL_STENCIL_INDEX4; + } else if( stencil08Avail ) { + return GL.GL_STENCIL_INDEX8; + } else { + return GL2GL3.GL_STENCIL_INDEX16; + } + } else if( 1 <= reqBits && ( stencil01Avail || stencil04Avail || stencil08Avail || stencil16Avail ) ) { + if( stencil01Avail ) { + return GL.GL_STENCIL_INDEX1; + } else if( stencil04Avail ) { + return GL.GL_STENCIL_INDEX4; + } else if( stencil08Avail ) { + return GL.GL_STENCIL_INDEX8; + } else { + return GL2GL3.GL_STENCIL_INDEX16; + } + } else { + throw new GLException("stencil buffer n/a"); + } + } + + /** Request default bit count for depth- or stencil buffer (depth 24 bits, stencil 8 bits), value {@value} */ + public static final int DEFAULT_BITS = 0; + + /** + * Request current context drawable's <i>requested</i> + * {@link GLCapabilitiesImmutable#getDepthBits() depth-} or {@link GLCapabilitiesImmutable#getStencilBits() stencil-}bits; value {@value} */ + public static final int REQUESTED_BITS = -1; + + /** + * Request current context drawable's <i>chosen</i> + * {@link GLCapabilitiesImmutable#getDepthBits() depth-} or {@link GLCapabilitiesImmutable#getStencilBits() stencil-}bits; value {@value} */ + public static final int CHOSEN_BITS = -2; + + /** Request maximum bit count for depth- or stencil buffer (depth 32 bits, stencil 16 bits), value {@value} */ + public static final int MAXIMUM_BITS = -3; + + /** * Attaches one depth, stencil or packed-depth-stencil buffer to this FBO's instance, * selecting the internalFormat automatically. @@ -1389,7 +1629,8 @@ public class FBObject { * Stencil and depth buffer can be attached only once. * </p> * <p> - * In case the desired type or bit-number is not supported, the next available one is chosen. + * In case the bit-count is not supported, + * the next available one is chosen, i.e. next higher (preferred) or lower bit-count. * </p> * <p> * Use {@link #getDepthAttachment()} and/or {@link #getStencilAttachment()} to retrieve details @@ -1401,68 +1642,58 @@ public class FBObject { * * @param gl * @param atype either {@link Type#DEPTH}, {@link Type#STENCIL} or {@link Type#DEPTH_STENCIL} - * @param reqBits desired bits for depth or -1 for default (24 bits) + * @param reqBits desired bits for depth or stencil, + * may use generic values {@link #DEFAULT_BITS}, {@link #REQUESTED_BITS}, {@link #CHOSEN_BITS} or {@link #MAXIMUM_BITS}. * @throws GLException in case the renderbuffer couldn't be allocated or one is already attached. * @throws IllegalArgumentException * @see #getDepthAttachment() * @see #getStencilAttachment() */ - public final void attachRenderbuffer(final GL gl, final Attachment.Type atype, int reqBits) throws GLException, IllegalArgumentException { - if( 0 > reqBits ) { - reqBits = 24; + public final void attachRenderbuffer(final GL gl, final Attachment.Type atype, final int reqBits) throws GLException, IllegalArgumentException { + final int reqDepth, reqStencil; + if( MAXIMUM_BITS > reqBits ) { + throw new IllegalArgumentException("reqBits out of range, shall be >= "+MAXIMUM_BITS); + } else if( MAXIMUM_BITS == reqBits ) { + reqDepth = 32; + reqStencil = 16; + } else if( CHOSEN_BITS == reqBits ) { + final GLCapabilitiesImmutable caps = gl.getContext().getGLDrawable().getChosenGLCapabilities(); + reqDepth = caps.getDepthBits(); + reqStencil = caps.getStencilBits(); + } else if( REQUESTED_BITS == reqBits ) { + final GLCapabilitiesImmutable caps = gl.getContext().getGLDrawable().getRequestedGLCapabilities(); + reqDepth = caps.getDepthBits(); + reqStencil = caps.getStencilBits(); + } else if( DEFAULT_BITS == reqBits ) { + reqDepth = 24; + reqStencil = 8; + } else { + reqDepth = reqBits; + reqStencil = reqBits; } final int internalFormat; int internalStencilFormat = -1; switch ( atype ) { case DEPTH: - if( 32 <= reqBits && depth32Avail ) { - internalFormat = GL.GL_DEPTH_COMPONENT32; - } else if( 24 <= reqBits && depth24Avail ) { - internalFormat = GL.GL_DEPTH_COMPONENT24; - } else { - internalFormat = GL.GL_DEPTH_COMPONENT16; - } + internalFormat = getDepthIFormat(reqDepth); break; case STENCIL: - if( 16 <= reqBits && stencil16Avail ) { - internalFormat = GL2GL3.GL_STENCIL_INDEX16; - } else if( 8 <= reqBits && stencil08Avail ) { - internalFormat = GL.GL_STENCIL_INDEX8; - } else if( 4 <= reqBits && stencil04Avail ) { - internalFormat = GL.GL_STENCIL_INDEX4; - } else if( 1 <= reqBits && stencil01Avail ) { - internalFormat = GL.GL_STENCIL_INDEX1; - } else { - throw new GLException("stencil buffer n/a"); - } + internalFormat = getStencilIFormat(reqStencil); break; case DEPTH_STENCIL: if( packedDepthStencilAvail ) { internalFormat = GL.GL_DEPTH24_STENCIL8; } else { - if( 24 <= reqBits && depth24Avail ) { - internalFormat = GL.GL_DEPTH_COMPONENT24; - } else { - internalFormat = GL.GL_DEPTH_COMPONENT16; - } - if( stencil08Avail ) { - internalStencilFormat = GL.GL_STENCIL_INDEX8; - } else if( stencil04Avail ) { - internalStencilFormat = GL.GL_STENCIL_INDEX4; - } else if( stencil01Avail ) { - internalStencilFormat = GL.GL_STENCIL_INDEX1; - } else { - throw new GLException("stencil buffer n/a"); - } + internalFormat = getDepthIFormat(reqDepth); + internalStencilFormat = getStencilIFormat(reqStencil); } break; default: throw new IllegalArgumentException("only depth/stencil types allowed, was "+atype+", "+this); } - attachRenderbufferImpl(gl, atype, internalFormat); if(0<=internalStencilFormat) { @@ -1514,32 +1745,36 @@ public class FBObject { } private final void attachRenderbufferImpl2(final GL gl, final Attachment.Type atype, final int internalFormat) throws GLException { + // atype and current depth and stencil instance are already validated in 'attachRenderbufferImpl(..)' if( Attachment.Type.DEPTH == atype ) { if(null == depth) { - depth = new RenderAttachment(Type.DEPTH, internalFormat, samples, width, height, 0); + depth = createRenderAttachment(Type.DEPTH, internalFormat, samples, width, height); } else { depth.setSize(width, height); depth.setSamples(samples); } + validateAttachmentSize(depth); depth.initialize(gl); } else if( Attachment.Type.STENCIL == atype ) { if(null == stencil) { - stencil = new RenderAttachment(Type.STENCIL, internalFormat, samples, width, height, 0); + stencil = createRenderAttachment(Type.STENCIL, internalFormat, samples, width, height); } else { stencil.setSize(width, height); stencil.setSamples(samples); } + validateAttachmentSize(stencil); stencil.initialize(gl); } else if( Attachment.Type.DEPTH_STENCIL == atype ) { if(null == depth) { if(null != stencil) { throw new InternalError("XXX: DEPTH_STENCIL, depth was null, stencil not: "+this.toString()); } - depth = new RenderAttachment(Type.DEPTH_STENCIL, internalFormat, samples, width, height, 0); + depth = createRenderAttachment(Type.DEPTH_STENCIL, internalFormat, samples, width, height); } else { depth.setSize(width, height); depth.setSamples(samples); } + validateAttachmentSize(depth); depth.initialize(gl); // DEPTH_STENCIL shares buffer w/ depth and stencil stencil = depth; @@ -1555,6 +1790,8 @@ public class FBObject { gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_STENCIL_ATTACHMENT, GL.GL_RENDERBUFFER, stencil.getName()); } + modified = true; + if(!ignoreStatus) { updateStatus(gl); if( !isStatusValid() ) { @@ -1581,7 +1818,7 @@ public class FBObject { public final Colorbuffer detachColorbuffer(final GL gl, final int attachmentPoint, final boolean dispose) throws IllegalArgumentException { bind(gl); - final Colorbuffer res = detachColorbufferImpl(gl, attachmentPoint, dispose ? DetachAction.DISPOSE : DetachAction.NONE); + final Colorbuffer res = detachColorbufferImpl(gl, attachmentPoint, dispose ? DetachAction.DISPOSE : DetachAction.NONE, 0); if(null == res) { throw new IllegalArgumentException("ColorAttachment at "+attachmentPoint+", not attached, "+this); } @@ -1591,17 +1828,17 @@ public class FBObject { return res; } - private final Colorbuffer detachColorbufferImpl(final GL gl, final int attachmentPoint, final DetachAction detachAction) { - Colorbuffer colbuf = colorAttachmentPoints[attachmentPoint]; // shortcut, don't validate here + private final Colorbuffer detachColorbufferImpl(final GL gl, final int attachmentPoint, final DetachAction detachAction, final int sampleCountChange) { + final Colorbuffer colbufOld = colorbufferAttachments[attachmentPoint]; // shortcut, don't validate here - if(null == colbuf) { + if(null == colbufOld) { return null; } - removeColorAttachment(attachmentPoint, colbuf); + removeColorAttachment(attachmentPoint, colbufOld); - if(colbuf instanceof TextureAttachment) { - final TextureAttachment texA = (TextureAttachment) colbuf; + if( colbufOld.isTextureAttachment() ) { + final TextureAttachment texA = colbufOld.getTextureAttachment(); if( 0 != texA.getName() ) { gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0 + attachmentPoint, @@ -1616,17 +1853,19 @@ public class FBObject { } } if(DetachAction.RECREATE == detachAction) { - if(samples == 0) { - // stay non MSAA - texA.setSize(width, height); + final Colorbuffer colbufNew; + if( 0 < sampleCountChange ) { + // switch to MSAA: TextureAttachment -> ColorAttachment + colbufNew = createColorAttachment(hasAlpha(texA.format)); } else { - // switch to MSAA - colbuf = createColorAttachment(hasAlpha(texA.format)); + // keep MSAA settings + texA.setSize(width, height); + colbufNew = texA; } - attachColorbufferImpl(gl, attachmentPoint, colbuf); + attachColorbufferImpl(gl, attachmentPoint, colbufNew); } - } else if(colbuf instanceof ColorAttachment) { - final ColorAttachment colA = (ColorAttachment) colbuf; + } else { + final ColorAttachment colA = colbufOld.getColorAttachment(); if( 0 != colA.getName() ) { gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0+attachmentPoint, @@ -1640,37 +1879,42 @@ public class FBObject { } } if(DetachAction.RECREATE == detachAction) { - if(samples > 0) { - // stay MSAA + final Colorbuffer colbufNew; + if( 0 <= sampleCountChange || null == samplingColorSink ) { + // keep ColorAttachment, + // including 'switch to non-MSAA' if no samplingColorSink is available + // to determine whether a TextureAttachment or ColorAttachment is desired! colA.setSize(width, height); colA.setSamples(samples); + colbufNew = colA; } else { // switch to non MSAA - if(null != samplingSinkTexture) { - colbuf = createColorTextureAttachment(samplingSinkTexture.format, width, height, - samplingSinkTexture.dataFormat, samplingSinkTexture.dataType, - samplingSinkTexture.magFilter, samplingSinkTexture.minFilter, - samplingSinkTexture.wrapS, samplingSinkTexture.wrapT); + if( samplingColorSink.isTextureAttachment() ) { + final TextureAttachment samplingTextureSink = samplingColorSink.getTextureAttachment(); + colbufNew = createColorTextureAttachment(samplingTextureSink.format, width, height, + samplingTextureSink.dataFormat, samplingTextureSink.dataType, + samplingTextureSink.magFilter, samplingTextureSink.minFilter, + samplingTextureSink.wrapS, samplingTextureSink.wrapT); } else { - colbuf = createColorTextureAttachment(gl, true, width, height); + colbufNew = createColorAttachment(samplingColorSink.getFormat(), 0, width, height); } } - attachColorbuffer(gl, attachmentPoint, colbuf); + attachColorbuffer(gl, attachmentPoint, colbufNew); } } - return colbuf; + return colbufOld; } private final void freeAllColorbufferImpl(final GL gl) { for(int i=0; i<maxColorAttachments; i++) { - final Colorbuffer colbuf = colorAttachmentPoints[i]; // shortcut, don't validate here + final Colorbuffer colbuf = colorbufferAttachments[i]; // shortcut, don't validate here if(null == colbuf) { return; } - if(colbuf instanceof TextureAttachment) { - final TextureAttachment texA = (TextureAttachment) colbuf; + if( colbuf.isTextureAttachment() ) { + final TextureAttachment texA = colbuf.getTextureAttachment(); if( 0 != texA.getName() ) { gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0 + i, @@ -1678,8 +1922,8 @@ public class FBObject { gl.glBindTexture(GL.GL_TEXTURE_2D, 0); } texA.free(gl); - } else if(colbuf instanceof ColorAttachment) { - final ColorAttachment colA = (ColorAttachment) colbuf; + } else { + final ColorAttachment colA = colbuf.getColorAttachment(); if( 0 != colA.getName() ) { gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0 + i, @@ -1698,7 +1942,10 @@ public class FBObject { */ public final void detachRenderbuffer(final GL gl, final Attachment.Type atype, final boolean dispose) throws IllegalArgumentException { bind(gl); - detachRenderbufferImpl(gl, atype, dispose ? DetachAction.DISPOSE : DetachAction.NONE); + final RenderAttachment res = detachRenderbufferImpl(gl, atype, dispose ? DetachAction.DISPOSE : DetachAction.NONE); + if(null == res) { + throw new IllegalArgumentException("RenderAttachment type "+atype+", not attached, "+this); + } if(DEBUG) { System.err.println("FBObject.detachRenderbuffer.X: [attachmentType "+atype+", dispose "+dispose+"]: "+this); } @@ -1718,7 +1965,7 @@ public class FBObject { return res; } - private final void detachRenderbufferImpl(final GL gl, Attachment.Type atype, final DetachAction detachAction) throws IllegalArgumentException { + private final RenderAttachment detachRenderbufferImpl(final GL gl, Attachment.Type atype, final DetachAction detachAction) throws IllegalArgumentException { switch ( atype ) { case DEPTH: case STENCIL: @@ -1728,23 +1975,25 @@ public class FBObject { throw new IllegalArgumentException("only depth/stencil types allowed, was "+atype+", "+this); } if( null == depth && null == stencil ) { - return ; // nop + return null; // nop } final boolean packed = isDepthStencilPackedFormat(); if( packed ) { // Note: DEPTH_STENCIL shares buffer w/ depth and stencil atype = Attachment.Type.DEPTH_STENCIL; } + final RenderAttachment renderOld; switch ( atype ) { case DEPTH: - if( null != depth ) { - final int format = depth.format; - if( 0 != depth.getName() ) { + renderOld = depth; + if( null != renderOld ) { + final int format = renderOld.format; + if( 0 != renderOld.getName() ) { gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, GL.GL_RENDERBUFFER, 0); switch(detachAction) { case DISPOSE: case RECREATE: - depth.free(gl); + renderOld.free(gl); break; default: } @@ -1757,14 +2006,15 @@ public class FBObject { } break; case STENCIL: - if( null != stencil ) { - final int format = stencil.format; - if(0 != stencil.getName()) { + renderOld = stencil; + if( null != renderOld ) { + final int format = renderOld.format; + if(0 != renderOld.getName()) { gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_STENCIL_ATTACHMENT, GL.GL_RENDERBUFFER, 0); switch(detachAction) { case DISPOSE: case RECREATE: - stencil.free(gl); + renderOld.free(gl); break; default: } @@ -1777,9 +2027,10 @@ public class FBObject { } break; case DEPTH_STENCIL: - if( null != depth ) { - final int format = depth.format; - if(0 != depth.getName()) { + renderOld = depth; + if( null != renderOld ) { + final int format = renderOld.format; + if(0 != renderOld.getName()) { gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT, GL.GL_RENDERBUFFER, 0); if(packed) { gl.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_STENCIL_ATTACHMENT, GL.GL_RENDERBUFFER, 0); @@ -1787,7 +2038,7 @@ public class FBObject { switch(detachAction) { case DISPOSE: case RECREATE: - depth.free(gl); + renderOld.free(gl); break; default: } @@ -1820,8 +2071,11 @@ public class FBObject { } } break; - default: // handled + default: + throw new InternalError("XXX"); // handled by caller } + modified = true; + return renderOld; } private final void freeAllRenderbufferImpl(final GL gl) throws IllegalArgumentException { @@ -1849,7 +2103,7 @@ public class FBObject { * and disposes them. * <p>Leaves the FBO bound, if initialized!</p> * <p> - * An attached sampling sink texture will be detached as well, see {@link #getSamplingSink()}. + * An attached sampling sink texture will be detached as well, see {@link #getSamplingTextureSink()}. * </p> * @param gl the current GL context */ @@ -1857,7 +2111,7 @@ public class FBObject { if(null != samplingSink) { samplingSink.detachAll(gl); } - detachAllImpl(gl, true/* detachNonColorbuffer */, false /* recreate */); + detachAllImpl(gl, true/* detachNonColorbuffer */, false /* recreate */, 0); } /** @@ -1865,7 +2119,7 @@ public class FBObject { * and disposes them. * <p>Leaves the FBO bound, if initialized!</p> * <p> - * An attached sampling sink texture will be detached as well, see {@link #getSamplingSink()}. + * An attached sampling sink texture will be detached as well, see {@link #getSamplingTextureSink()}. * </p> * @param gl the current GL context */ @@ -1873,14 +2127,14 @@ public class FBObject { if(null != samplingSink) { samplingSink.detachAllColorbuffer(gl); } - detachAllImpl(gl, false/* detachNonColorbuffer */, false /* recreate */); + detachAllImpl(gl, false/* detachNonColorbuffer */, false /* recreate */, 0); } /** * Detaches all {@link TextureAttachment}s and disposes them. * <p>Leaves the FBO bound, if initialized!</p> * <p> - * An attached sampling sink texture will be detached as well, see {@link #getSamplingSink()}. + * An attached sampling sink texture will be detached as well, see {@link #getSamplingTextureSink()}. * </p> * @param gl the current GL context */ @@ -1893,8 +2147,8 @@ public class FBObject { } bind(gl); for(int i=0; i<maxColorAttachments; i++) { - if(colorAttachmentPoints[i] instanceof TextureAttachment) { - detachColorbufferImpl(gl, i, DetachAction.DISPOSE); + if( colorbufferAttachments[i].isTextureAttachment() ) { + detachColorbufferImpl(gl, i, DetachAction.DISPOSE, 0); } } if(DEBUG) { @@ -1913,7 +2167,7 @@ public class FBObject { detachRenderbufferImpl(gl, Attachment.Type.DEPTH_STENCIL, DetachAction.DISPOSE); } - private final void detachAllImpl(final GL gl, final boolean detachNonColorbuffer, final boolean recreate) { + private final void detachAllImpl(final GL gl, final boolean detachNonColorbuffer, final boolean recreate, final int sampleCountChange) { if( !isInitialized() ) { return; } @@ -1928,9 +2182,9 @@ public class FBObject { } } for(int i=0; i<maxColorAttachments; i++) { - detachColorbufferImpl(gl, i, recreate ? DetachAction.RECREATE : DetachAction.DISPOSE); + detachColorbufferImpl(gl, i, recreate ? DetachAction.RECREATE : DetachAction.DISPOSE, sampleCountChange); } - if( !recreate && colorAttachmentCount>0 ) { + if( !recreate && colorbufferCount>0 ) { throw new InternalError("Non zero ColorAttachments "+this); } @@ -1969,7 +2223,7 @@ public class FBObject { samplingSink.destroy(gl); } - detachAllImpl(gl, true /* detachNonColorbuffer */, false /* recreate */); + detachAllImpl(gl, true /* detachNonColorbuffer */, false /* recreate */, 0); // cache FB names, preset exposed to zero, // braking ties w/ GL/GLContext link to getReadFramebuffer()/getWriteFramebuffer() @@ -1991,41 +2245,50 @@ public class FBObject { private final boolean sampleSinkSizeMismatch() { return samplingSink.getWidth() != width || samplingSink.getHeight() != height ; } - private final boolean sampleSinkTexMismatch() { - return null == samplingSinkTexture || 0 == samplingSinkTexture.getName() ; - } private final boolean sampleSinkDepthStencilMismatch() { - final boolean depthMismatch = ( null != depth && null == samplingSink.depth ) || - ( null != depth && null != samplingSink.depth && - depth.format != samplingSink.depth.format ); + if ( ( null != depth && ( null == samplingSink.depth || depth.format != samplingSink.depth.format ) ) + || + ( null == depth && null != samplingSink.depth ) + ) { + return true; + } - final boolean stencilMismatch = ( null != stencil && null == samplingSink.stencil ) || - ( null != stencil && null != samplingSink.stencil && - stencil.format != samplingSink.stencil.format ); + if ( ( null != stencil && ( null == samplingSink.stencil || stencil.format != samplingSink.stencil.format ) ) + || + ( null == stencil && null != samplingSink.stencil ) + ) { + return true; + } - return depthMismatch || stencilMismatch; + return false; } /** - * For GLES3, sampling-sink {@link Colorbuffer} format <b>must be equal</b> w/ the sampling-source {@link Colorbuffer}. + * For GLES3, sampling-sink {@link Colorbuffer} <i>internal format</i> <b>must be equal</b> w/ the sampling-source {@link Colorbuffer}. * Implementation aligns w/ {@link #createColorTextureAttachment(GLProfile, boolean, int, int, int, int, int, int)} * and {@link #createColorAttachment(boolean)}. */ - private final boolean sampleSinkFormatMismatch(final GL gl) { - if( null != samplingSinkTexture && getColorAttachmentCount() > 0 && gl.isGL2ES3() ) { + private final boolean sampleSinkExFormatMismatch(final GL gl) { + if( null != samplingColorSink && getColorbufferCount() > 0 && gl.isGL2ES3() ) { final Attachment ca = (Attachment)getColorbuffer(0); // should be at attachment-point 0 - return ( null != ca && ca.format != samplingSinkTexture.format ) || - hasAlpha(samplingSinkTexture.format) != hasAttachmentUsingAlpha(); + // We cannot comply w/ attachment's format other than attachment point 0! + // return ( null != ca && ca.format != samplingColorSink.getFormat() ) || + // hasAlpha(samplingColorSink.getFormat()) != hasAttachmentUsingAlpha(); + return null != ca && ca.format != samplingColorSink.getFormat(); } return false; } /** - * Manually reset the MSAA sampling sink, if used. + * Manually validates the MSAA sampling sink, if used. * <p> * If MSAA is being used and no sampling sink is attached via {@link #setSamplingSink(FBObject)} * a new sampling sink is being created. * </p> * <p> + * If the sampling sink size or attributes differs from the source, its attachments are reset + * to match the source. + * </p> + * <p> * Automatically called by {@link #reset(GL, int, int, int, boolean)} * and {@link #syncSamplingSink(GL)}. * </p> @@ -2033,110 +2296,191 @@ public class FBObject { * It is recommended to call this method after initializing the FBO and attaching renderbuffer etc for the 1st time * if access to sampling sink resources is required. * </p> + * + * <p>Leaves the FBO bound state untouched</p> + * * @param gl the current GL context + * @return {@code true} if this instance has been modified, otherwise {@code false}. * @throws GLException in case of an error, i.e. size too big, etc .. */ - public final void resetSamplingSink(final GL gl) throws GLException { - if(0 == samples) { + public final boolean resetSamplingSink(final GL gl) throws GLException { + if(DEBUG) { + System.err.println("FBObject.resetSamplingSink.0"); + Thread.dumpStack(); + } + + if( 0 == samples ) { + final boolean modifiedInstance; // MSAA off - if(null != samplingSink && samplingSink.initialized) { + if( null != samplingSink ) { // cleanup - samplingSink.detachAll(gl); + if( samplingSink.initialized ) { + samplingSink.detachAll(gl); + } + samplingSink = null; + samplingColorSink = null; + modifiedInstance = true; + } else { + modifiedInstance = false; } - return; + this.modified = false; + if(DEBUG) { + System.err.println("FBObject.resetSamplingSink.X1: zero samples, mod "+modifiedInstance+"\n\tTHIS "+this); + } + return modifiedInstance; } - if(null == samplingSink ) { - samplingSink = new FBObject(); - } + boolean modifiedInstance = false; - if(!samplingSink.initialized) { + if( null == samplingSink ) { + samplingSink = new FBObject(); samplingSink.init(gl, width, height, 0); + samplingColorSink = null; + modifiedInstance = true; + } else if( !samplingSink.initialized ) { + throw new InternalError("InitState Mismatch: samplingSink set, but not initialized "+samplingSink); + } else if( null == samplingColorSink || 0 == samplingColorSink.getName() ) { + throw new InternalError("InitState Mismatch: samplingColorSink set, but not initialized "+samplingColorSink+", "+samplingSink); } - boolean sampleSinkFormatMismatch = sampleSinkFormatMismatch(gl); + if(DEBUG) { + System.err.println("FBObject.resetSamplingSink.1: mod "+modifiedInstance+"\n\tTHIS "+this+",\n\tSINK "+samplingSink); + } + boolean sampleSinkExFormatMismatch = sampleSinkExFormatMismatch(gl); boolean sampleSinkSizeMismatch = sampleSinkSizeMismatch(); - boolean sampleSinkTexMismatch = sampleSinkTexMismatch(); boolean sampleSinkDepthStencilMismatch = sampleSinkDepthStencilMismatch(); - /** if(DEBUG) { - System.err.println("FBObject.resetSamplingSink.0: \n\tTHIS "+this+",\n\tSINK "+samplesSink+ - "\n\t format "+sampleSinkFormatMismatch+", size "+sampleSinkSizeMismatch +", tex "+sampleSinkTexMismatch +", depthStencil "+sampleSinkDepthStencilMismatch); - } */ - - if(!sampleSinkFormatMismatch && !sampleSinkSizeMismatch && !sampleSinkTexMismatch && !sampleSinkDepthStencilMismatch) { - // all properties match .. - return; + if( modifiedInstance ) { + // samplingColorSink == null + // must match size, format and colorbuffer do not exist yet + if( sampleSinkExFormatMismatch || sampleSinkSizeMismatch ) { + throw new InternalError("InitState Mismatch: Matching exFormat "+!sampleSinkExFormatMismatch+ + ", size "+!sampleSinkSizeMismatch +", "+this); + } + } else { + // samplingColorSink != null + if(!sampleSinkExFormatMismatch && !sampleSinkSizeMismatch && !sampleSinkDepthStencilMismatch) { + if(DEBUG) { + System.err.println("FBObject.resetSamplingSink.X2: Matching: exFormat "+!sampleSinkExFormatMismatch+ + ", size "+!sampleSinkSizeMismatch +", depthStencil "+!sampleSinkDepthStencilMismatch+ + ", mod "+modifiedInstance); + } + // all properties match .. + samplingSink.modified = false; + this.modified = false; + return modifiedInstance; + } } - unbind(gl); + final boolean wasBound; + if( isBound() ) { + markUnbound(); // automatic GL unbind by sampleSink binding + wasBound = true; + } else { + wasBound = false; + } if(DEBUG) { - System.err.println("FBObject.resetSamplingSink: BEGIN\n\tTHIS "+this+",\n\tSINK "+samplingSink+ - "\n\t format "+sampleSinkFormatMismatch+", size "+sampleSinkSizeMismatch +", tex "+sampleSinkTexMismatch +", depthStencil "+sampleSinkDepthStencilMismatch); + System.err.println("FBObject.resetSamplingSink.2: wasBound "+wasBound+", matching: exFormat "+!sampleSinkExFormatMismatch+ + ", size "+!sampleSinkSizeMismatch +", depthStencil "+!sampleSinkDepthStencilMismatch); } - if( sampleSinkDepthStencilMismatch ) { + modifiedInstance = true; + + if( sampleSinkDepthStencilMismatch ) { // includes 1st init samplingSink.detachAllRenderbuffer(gl); } - if( sampleSinkFormatMismatch ) { + final boolean samplingColorSinkShallBeTA = null == samplingColorSink || samplingColorSink.isTextureAttachment(); + + if( sampleSinkExFormatMismatch ) { samplingSink.detachAllColorbuffer(gl); - samplingSinkTexture = null; + samplingColorSink = null; } else if( sampleSinkSizeMismatch ) { - samplingSink.reset(gl, width, height); - } - - if(null == samplingSinkTexture) { - final boolean hasAlpha = hasAttachmentUsingAlpha(); - samplingSinkTexture = samplingSink.attachTexture2D(gl, 0, hasAlpha); - } else if( 0 == samplingSinkTexture.getName() ) { - samplingSinkTexture.setSize(width, height); - samplingSink.attachColorbuffer(gl, 0, samplingSinkTexture); + samplingSink.resetSizeImpl(gl, width, height); + samplingColorSink = samplingSink.getColorbuffer(0); + } + + if( null == samplingColorSink ) { // sampleSinkFormatMismatch || 1st init + final Colorbuffer cb0 = getColorbuffer(0); // align with colorbuffer at attachment-point 0 + if( null != cb0 ) { + // match pre-existing format + if( samplingColorSinkShallBeTA ) { + samplingColorSink = createColorTextureAttachment(gl, cb0.getFormat(), width, height, + GL.GL_NEAREST, GL.GL_NEAREST, + GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); + } else { + samplingColorSink = createColorAttachment(cb0.getFormat(), 0, width, height); + } + samplingSink.attachColorbuffer(gl, 0, samplingColorSink); + } else { + // match default format + final boolean hasAlpha = hasAttachmentUsingAlpha(); + if( samplingColorSinkShallBeTA ) { + samplingColorSink = samplingSink.attachTexture2D(gl, 0, hasAlpha); + } else { + samplingColorSink = samplingSink.attachColorbuffer(gl, 0, hasAlpha); + } + } } - if( sampleSinkDepthStencilMismatch ) { + if( sampleSinkDepthStencilMismatch ) { // includes 1st init samplingSink.attachRenderbuffer(gl, depth.format); if( null != stencil && !isDepthStencilPackedFormat() ) { samplingSink.attachRenderbuffer(gl, stencil.format); } } - sampleSinkFormatMismatch = sampleSinkFormatMismatch(gl); + sampleSinkExFormatMismatch = sampleSinkExFormatMismatch(gl); sampleSinkSizeMismatch = sampleSinkSizeMismatch(); - sampleSinkTexMismatch = sampleSinkTexMismatch(); sampleSinkDepthStencilMismatch = sampleSinkDepthStencilMismatch(); - if(sampleSinkFormatMismatch || sampleSinkSizeMismatch || sampleSinkTexMismatch || sampleSinkDepthStencilMismatch) { + if(sampleSinkExFormatMismatch || sampleSinkSizeMismatch || sampleSinkDepthStencilMismatch) { throw new InternalError("Samples sink mismatch after reset: \n\tTHIS "+this+",\n\t SINK "+samplingSink+ - "\n\t format "+sampleSinkFormatMismatch+", size "+sampleSinkSizeMismatch +", tex "+sampleSinkTexMismatch +", depthStencil "+sampleSinkDepthStencilMismatch); + "\n\t Mismatch. Matching: exFormat "+!sampleSinkExFormatMismatch+ + ", size "+!sampleSinkSizeMismatch +", depthStencil "+!sampleSinkDepthStencilMismatch); + } + + samplingSink.modified = false; + samplingSink.unbind(gl); + this.modified = false; + + if(wasBound) { + bind(gl); } if(DEBUG) { - System.err.println("FBObject.resetSamplingSink: END\n\tTHIS "+this+",\n\tSINK "+samplingSink+ - "\n\t format "+sampleSinkFormatMismatch+", size "+sampleSinkSizeMismatch +", tex "+sampleSinkTexMismatch +", depthStencil "+sampleSinkDepthStencilMismatch); + System.err.println("FBObject.resetSamplingSink.XX: END mod "+modifiedInstance+"\n\tTHIS "+this+",\n\tSINK "+samplingSink+ + "\n\t Matching: exFormat "+!sampleSinkExFormatMismatch+ + ", size "+!sampleSinkSizeMismatch +", depthStencil "+!sampleSinkDepthStencilMismatch); } + return modifiedInstance; } /** * Setting this FBO sampling sink. - * @param newSamplingSink the new FBO sampling sink to use, or null to remove current sampling sink + * @param newSamplingSink the new and initialized FBO sampling sink to use, or null to remove current sampling sink * @return the previous sampling sink or null if none was attached * @throws GLException if this FBO doesn't use MSAA or the given sink uses MSAA itself + * @throws IllegalStateException if the {@code newSamplingSink} is not null and not initialized */ - public FBObject setSamplingSink(final FBObject newSamplingSink) throws GLException { + public FBObject setSamplingSink(final FBObject newSamplingSink) throws IllegalStateException, GLException { final FBObject prev = samplingSink; if( null == newSamplingSink) { samplingSink = null; - samplingSinkTexture = null; + samplingColorSink = null; } else if( samples > 0 ) { + if( !newSamplingSink.isInitialized() ) { + throw new IllegalStateException("SamplingSink not initialized: "+newSamplingSink); + } if( newSamplingSink.getNumSamples() > 0 ) { throw new GLException("SamplingSink FBO cannot use MSAA itself: "+newSamplingSink); } samplingSink = newSamplingSink; - samplingSinkTexture = (TextureAttachment) newSamplingSink.getColorbuffer(0); + samplingColorSink = newSamplingSink.getColorbuffer(0); } else { throw new GLException("Setting SamplingSink for non MSAA FBO not allowed: "+this); } + modified = true; samplingSinkDirty = true; return prev; } @@ -2144,9 +2488,9 @@ public class FBObject { /** * Bind this FBO, i.e. bind write framebuffer to {@link #getWriteFramebuffer()}. * - * <p>If multisampling is used, it sets the read framebuffer to the sampling sink {@link #getWriteFramebuffer()}, - * if full FBO is supported.</p> - * + * <p> + * If multisampling is used, it sets the read framebuffer to the sampling sink {@link #getWriteFramebuffer()}. + * </p> * <p> * In case you have attached more than one color buffer, * you may want to setup {@link GL2ES3#glDrawBuffers(int, int[], int)}. @@ -2157,15 +2501,12 @@ public class FBObject { public final void bind(final GL gl) throws GLException { if(!bound || fbName != gl.getBoundFramebuffer(GL.GL_FRAMEBUFFER)) { checkInitialized(); - if(samples > 0 && fullFBOSupport) { - // draw to multisampling - read from samplesSink - gl.glBindFramebuffer(GL2ES3.GL_DRAW_FRAMEBUFFER, getWriteFramebuffer()); - gl.glBindFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER, getReadFramebuffer()); + if( fullFBOSupport ) { + gl.glBindFramebuffer(GL2ES3.GL_DRAW_FRAMEBUFFER, getWriteFramebuffer()); // this fb, msaa or normal + gl.glBindFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER, getReadFramebuffer()); // msaa: sampling sink, normal: this fb } else { - // one for all - gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, getWriteFramebuffer()); + gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, getWriteFramebuffer()); // normal: read/write } - bound = true; samplingSinkDirty = true; } @@ -2213,7 +2554,7 @@ public class FBObject { * @param gl the current GL context */ public final boolean isBound(final GL gl) { - bound = bound && fbName != gl.getBoundFramebuffer(GL.GL_FRAMEBUFFER) ; + bound = bound && fbName == gl.getBoundFramebuffer(GL.GL_FRAMEBUFFER) ; return bound; } @@ -2222,7 +2563,7 @@ public class FBObject { /** * If multisampling is being used and flagged dirty by a previous call of {@link #bind(GL)} or after initialization, - * the msaa-buffers are sampled to it's sink {@link #getSamplingSink()}. + * the msaa-buffers are sampled to it's sink {@link #getSamplingTextureSink()}. * <p> * Method also resets the sampling sink configuration via {@link #resetSamplingSink(GL)} if used and required. * </p> @@ -2246,15 +2587,24 @@ public class FBObject { */ public final void syncSamplingSink(final GL gl) { markUnbound(); - if(samples>0 && samplingSinkDirty) { + if(samples>0 && samplingSinkDirty) { // implies fullFBOSupport samplingSinkDirty = false; - resetSamplingSink(gl); - checkPreGLError(gl); - gl.glBindFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER, fbName); - gl.glBindFramebuffer(GL2ES3.GL_DRAW_FRAMEBUFFER, samplingSink.getWriteFramebuffer()); + if( isModified() ) { + resetSamplingSink(gl); + } + final boolean checkError = DEBUG || GLContext.DEBUG_GL; + if( checkError ) { + checkPreGLError(gl); + } + gl.glBindFramebuffer(GL2ES3.GL_READ_FRAMEBUFFER, fbName); // read from this MSAA fb + gl.glBindFramebuffer(GL2ES3.GL_DRAW_FRAMEBUFFER, samplingSink.getWriteFramebuffer()); // write to sampling sink ((GL2ES3)gl).glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, // since MSAA is supported, casting to GL2ES3 is OK GL.GL_COLOR_BUFFER_BIT, GL.GL_NEAREST); - checkNoError(null, gl.glGetError(), "FBObject syncSampleSink"); // throws GLException if error + if( checkError ) { + checkNoError(null, gl.glGetError(), "FBObject syncSampleSink"); // throws GLException if error + } + } else { + modified = false; } if(fullFBOSupport) { // default read/draw buffers, may utilize GLContext/GLDrawable override of @@ -2267,22 +2617,24 @@ public class FBObject { } /** - * Bind the given texture colorbuffer. + * {@link #syncSamplingSink(GL) Synchronize the sampling sink} and bind the given {@link TextureAttachment}, if not <code>null</code>. * - * <p>If using multiple texture units, ensure you call {@link GL#glActiveTexture(int)} first!</p> + * <p>If using a {@link TextureAttachment} and multiple texture units, ensure you call {@link GL#glActiveTexture(int)} first!</p> * * <p>{@link #syncSamplingSink(GL)} is being called</p> * * <p>Leaves the FBO unbound!</p> * * @param gl the current GL context - * @param ta {@link TextureAttachment} to use, prev. attached w/ {@link #attachTexture2D(GL, int, boolean, int, int, int, int) attachTexture2D(..)} + * @param ta {@link TextureAttachment} to use, prev. attached w/ {@link #attachTexture2D(GL, int, boolean, int, int, int, int) attachTexture2D(..)}, + * may be <code>null</code> in case no {@link TextureAttachment} is used. * @throws IllegalArgumentException */ public final void use(final GL gl, final TextureAttachment ta) throws IllegalArgumentException { - if(null == ta) { throw new IllegalArgumentException("Null TextureAttachment, this: "+toString()); } syncSamplingSink(gl); - gl.glBindTexture(GL.GL_TEXTURE_2D, ta.getName()); // use it .. + if( null != ta ) { + gl.glBindTexture(GL.GL_TEXTURE_2D, ta.getName()); // use it .. + } } /** @@ -2367,10 +2719,14 @@ public class FBObject { /** Returns the framebuffer name to render to. */ public final int getWriteFramebuffer() { return fbName; } /** Returns the framebuffer name to read from. Depending on multisampling, this may be a different framebuffer. */ - public final int getReadFramebuffer() { return ( samples > 0 ) ? samplingSink.getReadFramebuffer() : fbName; } + public final int getReadFramebuffer() { + return 0 < samples ? ( null != samplingSink ? samplingSink.getReadFramebuffer() : 0 ) : fbName; + } public final int getDefaultReadBuffer() { return GL.GL_COLOR_ATTACHMENT0; } - /** Return the number of color/texture attachments */ - public final int getColorAttachmentCount() { return colorAttachmentCount; } + /** Return the number of attached {@link Colorbuffer}s */ + public final int getColorbufferCount() { return colorbufferCount; } + /** Return the number of attached {@link TextureAttachment}s */ + public final int getTextureAttachmentCount() { return textureAttachmentCount; } /** Return the stencil {@link RenderAttachment} attachment, if exist. Maybe share the same {@link Attachment#getName()} as {@link #getDepthAttachment()}, if packed depth-stencil is being used. */ public final RenderAttachment getStencilAttachment() { return stencil; } /** Return the depth {@link RenderAttachment} attachment. Maybe share the same {@link Attachment#getName()} as {@link #getStencilAttachment()}, if packed depth-stencil is being used. */ @@ -2379,8 +2735,9 @@ public class FBObject { /** Return the complete multisampling {@link FBObject} sink, if using multisampling. */ public final FBObject getSamplingSinkFBO() { return samplingSink; } - /** Return the multisampling {@link TextureAttachment} sink, if using multisampling. */ - public final TextureAttachment getSamplingSink() { return samplingSinkTexture; } + /** Return the multisampling {@link Colorbuffer} sink, if using multisampling. */ + public final Colorbuffer getSamplingSink() { return samplingColorSink; } + /** * Returns <code>true</code> if the multisampling colorbuffer (msaa-buffer) * has been flagged dirty by a previous call of {@link #bind(GL)}, @@ -2388,15 +2745,26 @@ public class FBObject { */ public final boolean isSamplingBufferDirty() { return samplingSinkDirty; } + /** + * Returns <code>true</code> if size, sample-count or any attachment of this instance + * or its {@link #getSamplingSink() sampling-sink} has been modified since last {@link #syncSamplingSink(GL) sync}, + * {@link #use(GL, TextureAttachment) use}, {@link #reset(GL, int, int, int) reset} + * or {@link #resetSamplingSink(GL) resetSamplingSink}. + * <p> + * Otherwise method returns <code>false</code>. + * </p> + */ + public final boolean isModified() { return modified || ( null != samplingSink && samplingSink.modified ); } + int objectHashCode() { return super.hashCode(); } @Override public final String toString() { - final String caps = null != colorAttachmentPoints ? Arrays.asList(colorAttachmentPoints).toString() : null ; + final String caps = null != colorbufferAttachments ? Arrays.asList(colorbufferAttachments).toString() : null ; return "FBO[name r/w "+fbName+"/"+getReadFramebuffer()+", init "+initialized+", bound "+bound+", size "+width+"x"+height+ - ", samples "+samples+"/"+maxSamples+", depth "+depth+", stencil "+stencil+ - ", color attachments: "+colorAttachmentCount+"/"+maxColorAttachments+ - ": "+caps+", msaa["+samplingSinkTexture+", hasSink "+(null != samplingSink)+ + ", samples "+samples+"/"+maxSamples+", modified "+modified+"/"+isModified()+", depth "+depth+", stencil "+stencil+ + ", colorbuffer attachments: "+colorbufferCount+"/"+maxColorAttachments+", with "+textureAttachmentCount+" textures"+ + ": "+caps+", msaa["+samplingColorSink+", hasSink "+(null != samplingSink)+ ", dirty "+samplingSinkDirty+"], state "+getStatusString()+", obj "+toHexString(objectHashCode())+"]"; } diff --git a/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java b/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java index f9a7ab029..b9f1fb10c 100644 --- a/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java +++ b/src/jogl/classes/com/jogamp/opengl/GLAutoDrawableDelegate.java @@ -38,6 +38,7 @@ import javax.media.opengl.GLDrawable; import javax.media.opengl.GLDrawableFactory; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; +import javax.media.opengl.GLSharedContextSetter; import com.jogamp.common.util.locks.LockFactory; import com.jogamp.common.util.locks.RecursiveLock; @@ -62,6 +63,12 @@ import jogamp.opengl.GLDrawableImpl; * <p> * See example {@link com.jogamp.opengl.test.junit.jogl.acore.TestGLAutoDrawableDelegateNEWT TestGLAutoDrawableDelegateNEWT}. * </p> + * <p> + * <a name="contextSharing"><h5>OpenGL Context Sharing</h5></a> + * To share a {@link GLContext} see the following note in the documentation overview: + * <a href="../../../overview-summary.html#SHARING">context sharing</a> + * as well as {@link GLSharedContextSetter}. + * </p> */ public class GLAutoDrawableDelegate extends GLAutoDrawableBase implements GLAutoDrawable { /** @@ -143,7 +150,7 @@ public class GLAutoDrawableDelegate extends GLAutoDrawableBase implements GLAuto private final RecursiveLock lock; @Override - protected final RecursiveLock getLock() { return lock; } + public final RecursiveLock getUpstreamLock() { return lock; } @Override public final Object getUpstreamWidget() { diff --git a/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java b/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java index a7ba141bb..bfd5fe115 100644 --- a/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java +++ b/src/jogl/classes/com/jogamp/opengl/GLEventListenerState.java @@ -45,7 +45,9 @@ import javax.media.opengl.GLRunnable; import jogamp.opengl.Debug; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.nativewindow.MutableGraphicsConfiguration; +import com.jogamp.opengl.util.GLDrawableUtil; /** * GLEventListenerState is holding {@link GLAutoDrawable} components crucial @@ -72,11 +74,14 @@ public class GLEventListenerState { private GLEventListenerState(final AbstractGraphicsDevice upstreamDevice, final boolean proxyOwnsUpstreamDevice, final AbstractGraphicsDevice device, final GLCapabilitiesImmutable caps, + final RecursiveLock upstreamLock, final NativeSurface lockedSurface, final GLContext context, final int count, final GLAnimatorControl anim, final boolean animStarted) { this.upstreamDevice = upstreamDevice; this.proxyOwnsUpstreamDevice = proxyOwnsUpstreamDevice; this.device = device; this.caps = caps; + this.upstreamLock = upstreamLock; + this.lockedSurface = lockedSurface; this.context = context; this.listeners = new GLEventListener[count]; this.listenersInit = new boolean[count]; @@ -107,7 +112,29 @@ public class GLEventListenerState { public final GLAnimatorControl anim; public final boolean animStarted; - private boolean owner; + private volatile RecursiveLock upstreamLock; + private volatile NativeSurface lockedSurface; + private volatile boolean owner; + + /** + * Returns a {@link Runnable} {@link NativeSurface#unlockSurface() unlocking} an eventually locked {@link NativeSurface}, + * see {@link #moveFrom(GLAutoDrawable, boolean)} and {@link #moveTo(GLAutoDrawable, Runnable)}. + */ + public Runnable getUnlockSurfaceOp() { return unlockOp; } + + private final Runnable unlockOp = new Runnable() { + public void run() { + final RecursiveLock rl = upstreamLock; + final NativeSurface ls = lockedSurface; + upstreamLock = null; + lockedSurface = null; + if( null != rl ) { + rl.unlock(); + } + if( null != ls ) { + ls.unlockSurface(); + } + } }; /** * Last resort to destroy and loose ownership @@ -119,6 +146,7 @@ public class GLEventListenerState { listeners[i] = null; } // context.destroy(); - NPE (null drawable) + unlockOp.run(); device.close(); owner = false; } @@ -142,92 +170,143 @@ public class GLEventListenerState { * <p> * The returned GLEventListenerState instance is the {@link #isOwner() owner of the components}. * </p> + * <p> + * Locking is performed on the {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}. + * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li> + * </p> * - * @param a {@link GLAutoDrawable} source to move components from + * @param src {@link GLAutoDrawable} source to move components from * @return new GLEventListenerState instance {@link #isOwner() owning} moved components. * * @see #moveTo(GLAutoDrawable) */ - public static GLEventListenerState moveFrom(final GLAutoDrawable a) { - final GLAnimatorControl aAnim = a.getAnimator(); - final boolean aAnimStarted; - if( null != aAnim ) { - aAnimStarted = aAnim.isStarted(); - aAnim.remove(a); // also handles ECT + public static GLEventListenerState moveFrom(final GLAutoDrawable src) { + return GLEventListenerState.moveFrom(src, false); + } + + /** + * Moves all GLEventListenerState components from the given {@link GLAutoDrawable} + * to a newly created instance. + * <p> + * Note that all components are removed from the {@link GLAutoDrawable}, + * i.e. the {@link GLContext}, all {@link GLEventListener}. + * </p> + * <p> + * If the {@link GLAutoDrawable} was added to a {@link GLAnimatorControl}, it is removed + * and the {@link GLAnimatorControl} added to the GLEventListenerState. + * </p> + * <p> + * The returned GLEventListenerState instance is the {@link #isOwner() owner of the components}. + * </p> + * <p> + * Locking is performed on the {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}, + * which is <i>not released</i> if <code>keepLocked</code> is <code>true</code>. + * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li> + * </p> + * <p> + * <code>keepLocked</code> may be utilized if swapping a context between drawables + * and to ensure atomicity of operation. + * Here, the {@link #getUnlockSurfaceOp()} shall be passed to {@link #moveTo(GLAutoDrawable, Runnable)}. + * See {@link GLDrawableUtil#swapGLContextAndAllGLEventListener(GLAutoDrawable, GLAutoDrawable)}. + * </p> + * + * @param src {@link GLAutoDrawable} source to move components from + * @param keepLocked keep {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface} locked, see above + * @return new GLEventListenerState instance {@link #isOwner() owning} moved components. + * + * @see #moveTo(GLAutoDrawable, Runnable) + */ + public static GLEventListenerState moveFrom(final GLAutoDrawable src, final boolean keepLocked) { + final GLAnimatorControl srcAnim = src.getAnimator(); + final boolean srcAnimStarted; + if( null != srcAnim ) { + srcAnimStarted = srcAnim.isStarted(); + srcAnim.remove(src); // also handles ECT } else { - aAnimStarted = false; + srcAnimStarted = false; } final GLEventListenerState glls; - final NativeSurface aSurface = a.getNativeSurface(); - final boolean surfaceLocked = false; // NativeSurface.LOCK_SURFACE_NOT_READY < aSurface.lockSurface(); + final RecursiveLock srcUpstreamLock = src.getUpstreamLock(); + srcUpstreamLock.lock(); try { - final int aSz = a.getGLEventListenerCount(); - - // Create new AbstractGraphicsScreen w/ cloned AbstractGraphicsDevice for future GLAutoDrawable - // allowing this AbstractGraphicsDevice to loose ownership -> not closing display/device! - final AbstractGraphicsConfiguration aCfg = aSurface.getGraphicsConfiguration(); - final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities(); - final AbstractGraphicsDevice aDevice1 = aCfg.getScreen().getDevice(); - final AbstractGraphicsDevice aDevice2 = cloneDevice(aDevice1); - aDevice1.clearHandleOwner(); // don't close device handle - if( DEBUG ) { - System.err.println("GLEventListenerState.moveFrom.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1); - System.err.println("GLEventListenerState.moveFrom.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2); - System.err.println("GLEventListenerState.moveFrom.1: "+aSurface.getClass().getName()/*+", "+aSurface*/); + final NativeSurface srcSurface = src.getNativeSurface(); + final boolean srcSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < srcSurface.lockSurface(); + if( src.isRealized() && !srcSurfaceLocked ) { + throw new GLException("Could not lock realized surface "+src); } - final AbstractGraphicsDevice aUpDevice2; - final boolean proxyOwnsUpstreamDevice; - { - AbstractGraphicsDevice _aUpDevice2 = null; - if(aSurface instanceof ProxySurface) { - final ProxySurface aProxy = (ProxySurface)aSurface; - proxyOwnsUpstreamDevice = aProxy.containsUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE ); - final NativeSurface aUpSurface = aProxy.getUpstreamSurface(); - if(DEBUG && null != aUpSurface) { - System.err.println("GLEventListenerState.moveFrom.2: "+aUpSurface.getClass().getName()+", "+aUpSurface); - } - if(null != aUpSurface) { - final AbstractGraphicsDevice aUpDevice1 = aUpSurface.getGraphicsConfiguration().getScreen().getDevice(); - _aUpDevice2 = cloneDevice(aUpDevice1); - aUpDevice1.clearHandleOwner(); // don't close device handle - if(DEBUG) { - System.err.println("GLEventListenerState.moveFrom.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1); - System.err.println("GLEventListenerState.moveFrom.3b: up-pres 0x"+Integer.toHexString(_aUpDevice2.hashCode())+", "+_aUpDevice2); - System.err.println("GLEventListenerState.moveFrom.3c: "+aSurface.getClass().getName()+", "+aSurface); - System.err.println("GLEventListenerState.moveFrom.3d: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString()); + + try { + final int aSz = src.getGLEventListenerCount(); + + // Create new AbstractGraphicsScreen w/ cloned AbstractGraphicsDevice for future GLAutoDrawable + // allowing this AbstractGraphicsDevice to loose ownership -> not closing display/device! + final AbstractGraphicsConfiguration aCfg = srcSurface.getGraphicsConfiguration(); + final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities(); + final AbstractGraphicsDevice aDevice1 = aCfg.getScreen().getDevice(); + final AbstractGraphicsDevice aDevice2 = cloneDevice(aDevice1); + aDevice1.clearHandleOwner(); // don't close device handle + if( DEBUG ) { + System.err.println("GLEventListenerState.moveFrom.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1); + System.err.println("GLEventListenerState.moveFrom.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2); + System.err.println("GLEventListenerState.moveFrom.1: "+srcSurface.getClass().getName()/*+", "+aSurface*/); + } + final AbstractGraphicsDevice aUpDevice2; + final boolean proxyOwnsUpstreamDevice; + { + AbstractGraphicsDevice _aUpDevice2 = null; + if(srcSurface instanceof ProxySurface) { + final ProxySurface aProxy = (ProxySurface)srcSurface; + proxyOwnsUpstreamDevice = aProxy.containsUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE ); + final NativeSurface aUpSurface = aProxy.getUpstreamSurface(); + if(DEBUG && null != aUpSurface) { + System.err.println("GLEventListenerState.moveFrom.2: "+aUpSurface.getClass().getName()+", "+aUpSurface); + } + if(null != aUpSurface) { + final AbstractGraphicsDevice aUpDevice1 = aUpSurface.getGraphicsConfiguration().getScreen().getDevice(); + _aUpDevice2 = cloneDevice(aUpDevice1); + aUpDevice1.clearHandleOwner(); // don't close device handle + if(DEBUG) { + System.err.println("GLEventListenerState.moveFrom.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1); + System.err.println("GLEventListenerState.moveFrom.3b: up-pres 0x"+Integer.toHexString(_aUpDevice2.hashCode())+", "+_aUpDevice2); + System.err.println("GLEventListenerState.moveFrom.3c: "+srcSurface.getClass().getName()+", "+srcSurface); + System.err.println("GLEventListenerState.moveFrom.3d: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString()); + } } + } else { + proxyOwnsUpstreamDevice = false; } - } else { - proxyOwnsUpstreamDevice = false; + aUpDevice2 = _aUpDevice2; } - aUpDevice2 = _aUpDevice2; - } - glls = new GLEventListenerState(aUpDevice2, proxyOwnsUpstreamDevice, aDevice2, caps, a.getContext(), aSz, aAnim, aAnimStarted); - - // - // remove and cache all GLEventListener and their init-state - // - for(int i=0; i<aSz; i++) { - final GLEventListener l = a.getGLEventListener(0); - glls.listenersInit[i] = a.getGLEventListenerInitState(l); - glls.listeners[i] = a.removeGLEventListener( l ); - } - - // - // trigger glFinish to sync GL ctx - // - a.invoke(true, glFinish); + glls = new GLEventListenerState(aUpDevice2, proxyOwnsUpstreamDevice, aDevice2, caps, + keepLocked ? srcUpstreamLock : null, + srcSurfaceLocked && keepLocked ? srcSurface : null, + src.getContext(), aSz, srcAnim, srcAnimStarted); + + // + // remove and cache all GLEventListener and their init-state + // + for(int i=0; i<aSz; i++) { + final GLEventListener l = src.getGLEventListener(0); + glls.listenersInit[i] = src.getGLEventListenerInitState(l); + glls.listeners[i] = src.removeGLEventListener( l ); + } - a.setContext( null, false ); + src.setContext( null, false ); // implicit glFinish() ctx/drawable sync + } finally { + if( srcSurfaceLocked && !keepLocked ) { + srcSurface.unlockSurface(); + } + } } finally { - if( surfaceLocked ) { - aSurface.unlockSurface(); + if( !keepLocked ) { + srcUpstreamLock.unlock(); } } - return glls; } @@ -240,134 +319,184 @@ public class GLEventListenerState { * This operation is skipped, if the given {@link GLAutoDrawable} is already added to a {@link GLAnimatorControl} instance. * </p> * <p> + * Locking is performed on the {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}. + * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li> + * </p> + * <p> * Note: After this operation, the GLEventListenerState reference should be released. * </p> * - * @param a {@link GLAutoDrawable} destination to move GLEventListenerState components to + * @param dest {@link GLAutoDrawable} destination to move GLEventListenerState components to * - * <!-- @throws GLException if the {@link GLAutoDrawable}'s configuration is incompatible, i.e. different {@link GLCapabilitiesImmutable}. --> + * @throws GLException if a realized surface could not be locked. * @throws GLException if this preserved {@link AbstractGraphicsDevice} is incompatible w/ the given destination one. + * <!-- @throws GLException if the {@link GLAutoDrawable}'s configuration is incompatible, i.e. different {@link GLCapabilitiesImmutable}. --> * * @see #moveFrom(GLAutoDrawable) * @see #isOwner() */ - public final void moveTo(final GLAutoDrawable a) { - final GLAnimatorControl aAnim = a.getAnimator(); - final boolean hasAnimator = null != aAnim; - final boolean aPaused; - if( hasAnimator ) { - aPaused = aAnim.pause(); - aAnim.remove(a); // also handles ECT - if( aPaused ) { - aAnim.resume(); - } + public final void moveTo(final GLAutoDrawable dest) throws GLException { + this.moveTo(dest, null); + } + + /** + * Moves all GLEventListenerState components to the given {@link GLAutoDrawable} + * from this instance, while loosing {@link #isOwner() ownership}. + * <p> + * If the previous {@link GLAutoDrawable} was removed from a {@link GLAnimatorControl} by previous {@link #moveFrom(GLAutoDrawable, boolean)}, + * the given {@link GLAutoDrawable} is added to the cached {@link GLAnimatorControl}. + * This operation is skipped, if the given {@link GLAutoDrawable} is already added to a {@link GLAnimatorControl} instance. + * </p> + * <p> + * Locking is performed on the {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-lock} and {@link GLAutoDrawable#getNativeSurface() surface}. + * See <a href="../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li> + * </p> + * <p> + * If the {@link GLAutoDrawable} <code>dest</code> has been kept locked by {@link #moveFrom(GLAutoDrawable, boolean)}, + * it's {@link #getUnlockSurfaceOp()} shall be passed here to <code>destUnlockOperation</code> to be unlocked. + * </p> + * <p> + * Note: After this operation, the GLEventListenerState reference should be released. + * </p> + * + * @param dest {@link GLAutoDrawable} destination to move GLEventListenerState components to + * @param destUnlockOperation optional unlock operation for <code>dest</code>, see {@link #moveFrom(GLAutoDrawable, boolean)}. + * + * @throws GLException if a realized surface could not be locked. + * @throws GLException if this preserved {@link AbstractGraphicsDevice} is incompatible w/ the given destination one. + * <!-- @throws GLException if the {@link GLAutoDrawable}'s configuration is incompatible, i.e. different {@link GLCapabilitiesImmutable}. --> + * + * @see #moveFrom(GLAutoDrawable, boolean) + * @see #isOwner() + */ + public final void moveTo(final GLAutoDrawable dest, final Runnable destUnlockOperation) throws GLException { + final GLAnimatorControl destAnim = dest.getAnimator(); + final boolean destAnimPaused; + if( null != destAnim ) { + destAnimPaused = destAnim.pause(); + destAnim.remove(dest); // also handles ECT } else { - aPaused = false; + destAnimPaused = false; } final List<GLRunnable> aGLCmds = new ArrayList<GLRunnable>(); final int aSz = listenerCount(); - final NativeSurface aSurface = a.getNativeSurface(); - final boolean surfaceLocked = false; // NativeSurface.LOCK_SURFACE_NOT_READY < aSurface.lockSurface(); - final boolean aRealized; + final RecursiveLock destUpstreamLock = dest.getUpstreamLock(); + destUpstreamLock.lock(); + final boolean destIsRealized; try { - - final MutableGraphicsConfiguration aCfg = (MutableGraphicsConfiguration) aSurface.getGraphicsConfiguration(); - /** - final GLCapabilitiesImmutable aCaps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities(); - if( caps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) != aCaps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) || - caps.getVisualID(VisualIDHolder.VIDType.NATIVE) != aCaps.getVisualID(VisualIDHolder.VIDType.NATIVE) ) { - throw new GLException("Incompatible Capabilities - Prev-Holder: "+caps+", New-Holder "+caps); - } */ - final DefaultGraphicsDevice aDevice1 = (DefaultGraphicsDevice) aCfg.getScreen().getDevice(); - final DefaultGraphicsDevice aDevice2 = (DefaultGraphicsDevice) device; - if( !aDevice1.getUniqueID().equals( aDevice2.getUniqueID() ) ) { - throw new GLException("Incompatible devices: Preserved <"+aDevice2.getUniqueID()+">, target <"+aDevice1.getUniqueID()+">"); - } - - // collect optional upstream surface info - final ProxySurface aProxy; - final NativeSurface aUpSurface; - if(aSurface instanceof ProxySurface) { - aProxy = (ProxySurface)aSurface; - aUpSurface = aProxy.getUpstreamSurface(); - } else { - aProxy = null; - aUpSurface = null; - } - if( DEBUG ) { - System.err.println("GLEventListenerState.moveTo.0 : has aProxy "+(null!=aProxy)); - System.err.println("GLEventListenerState.moveTo.0 : has aUpSurface "+(null!=aUpSurface)); - } - if( null==aUpSurface && null != upstreamDevice ) { - throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = true, New-Holder = false"); - } - - // Destroy and remove currently associated GLContext, if any (will be replaced) - a.setContext( null, true ); - aRealized = a.isRealized(); - if( aRealized && null != aUpSurface ) { - // Unrealize due to device dependencies of an upstream surface, e.g. EGLUpstreamSurfaceHook - a.getDelegatedDrawable().setRealized(false); + final NativeSurface destSurface = dest.getNativeSurface(); + final boolean destSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < destSurface.lockSurface(); + if( dest.isRealized() && !destSurfaceLocked ) { + throw new GLException("Could not lock realized surface "+dest); } + try { + + final MutableGraphicsConfiguration aCfg = (MutableGraphicsConfiguration) destSurface.getGraphicsConfiguration(); + /** + final GLCapabilitiesImmutable aCaps = (GLCapabilitiesImmutable) aCfg.getChosenCapabilities(); + if( caps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) != aCaps.getVisualID(VisualIDHolder.VIDType.INTRINSIC) || + caps.getVisualID(VisualIDHolder.VIDType.NATIVE) != aCaps.getVisualID(VisualIDHolder.VIDType.NATIVE) ) { + throw new GLException("Incompatible Capabilities - Prev-Holder: "+caps+", New-Holder "+caps); + } */ + final DefaultGraphicsDevice aDevice1 = (DefaultGraphicsDevice) aCfg.getScreen().getDevice(); + final DefaultGraphicsDevice aDevice2 = (DefaultGraphicsDevice) device; + if( !aDevice1.getUniqueID().equals( aDevice2.getUniqueID() ) ) { + throw new GLException("Incompatible devices: Preserved <"+aDevice2.getUniqueID()+">, target <"+aDevice1.getUniqueID()+">"); + } - // Set new Screen and close previous one - { - if( DEBUG ) { - System.err.println("GLEventListenerState.moveTo.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1); - System.err.println("GLEventListenerState.moveTo.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2); + // collect optional upstream surface info + final ProxySurface aProxy; + final NativeSurface aUpSurface; + if(destSurface instanceof ProxySurface) { + aProxy = (ProxySurface)destSurface; + aUpSurface = aProxy.getUpstreamSurface(); + } else { + aProxy = null; + aUpSurface = null; } - DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aDevice1, aDevice2); - aDevice2.close(); if( DEBUG ) { - System.err.println("GLEventListenerState.moveTo.1a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1); - System.err.println("GLEventListenerState.moveTo.1b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2); + System.err.println("GLEventListenerState.moveTo.0 : has aProxy "+(null!=aProxy)); + System.err.println("GLEventListenerState.moveTo.0 : has aUpSurface "+(null!=aUpSurface)); + } + if( null==aUpSurface && null != upstreamDevice ) { + throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = true, New-Holder = false"); } - } - // If using a ProxySurface w/ an upstream surface, set new Screen and close previous one on it - if( null != aUpSurface ) { - final MutableGraphicsConfiguration aUpCfg = (MutableGraphicsConfiguration) aUpSurface.getGraphicsConfiguration(); - if( null != upstreamDevice ) { - final DefaultGraphicsDevice aUpDevice1 = (DefaultGraphicsDevice) aUpCfg.getScreen().getDevice(); - final DefaultGraphicsDevice aUpDevice2 = (DefaultGraphicsDevice)upstreamDevice; - if( !aUpDevice1.getUniqueID().equals( aUpDevice2.getUniqueID() ) ) { - throw new GLException("Incompatible updtream devices: Preserved <"+aUpDevice2.getUniqueID()+">, target <"+aUpDevice1.getUniqueID()+">"); - } + // Destroy and remove currently associated GLContext, if any (will be replaced) + dest.setContext( null, true ); + destIsRealized = dest.isRealized(); + if( destIsRealized && null != aUpSurface ) { + // Unrealize due to device dependencies of an upstream surface, e.g. EGLUpstreamSurfaceHook + dest.getDelegatedDrawable().setRealized(false); + } + + // Set new Screen and close previous one + { if( DEBUG ) { - System.err.println("GLEventListenerState.moveTo.2a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1); - System.err.println("GLEventListenerState.moveTo.2b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2); - System.err.println("GLEventListenerState.moveTo.2c: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString()); - } - DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aUpDevice1, aUpDevice2); - aUpDevice2.close(); - if( proxyOwnsUpstreamDevice ) { - aProxy.addUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE ); + System.err.println("GLEventListenerState.moveTo.0a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1); + System.err.println("GLEventListenerState.moveTo.0b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2); } + DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aDevice1, aDevice2); + aDevice2.close(); if( DEBUG ) { - System.err.println("GLEventListenerState.moveTo.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1); - System.err.println("GLEventListenerState.moveTo.3b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2); - System.err.println("GLEventListenerState.moveTo.3c: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString()); + System.err.println("GLEventListenerState.moveTo.1a: orig 0x"+Integer.toHexString(aDevice1.hashCode())+", "+aDevice1); + System.err.println("GLEventListenerState.moveTo.1b: pres 0x"+Integer.toHexString(aDevice2.hashCode())+", "+aDevice2); } - } else { - throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = false, New-Holder = true"); } - } - if( aRealized && null != aUpSurface ) { - a.getDelegatedDrawable().setRealized(true); - } - if( DEBUG ) { - System.err.println("GLEventListenerState.moveTo.X : has aProxy "+(null!=aProxy)); - System.err.println("GLEventListenerState.moveTo.X : has aUpSurface "+(null!=aUpSurface)); + // If using a ProxySurface w/ an upstream surface, set new Screen and close previous one on it + if( null != aUpSurface ) { + final MutableGraphicsConfiguration aUpCfg = (MutableGraphicsConfiguration) aUpSurface.getGraphicsConfiguration(); + if( null != upstreamDevice ) { + final DefaultGraphicsDevice aUpDevice1 = (DefaultGraphicsDevice) aUpCfg.getScreen().getDevice(); + final DefaultGraphicsDevice aUpDevice2 = (DefaultGraphicsDevice)upstreamDevice; + if( !aUpDevice1.getUniqueID().equals( aUpDevice2.getUniqueID() ) ) { + throw new GLException("Incompatible updtream devices: Preserved <"+aUpDevice2.getUniqueID()+">, target <"+aUpDevice1.getUniqueID()+">"); + } + if( DEBUG ) { + System.err.println("GLEventListenerState.moveTo.2a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1); + System.err.println("GLEventListenerState.moveTo.2b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2); + System.err.println("GLEventListenerState.moveTo.2c: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString()); + } + DefaultGraphicsDevice.swapDeviceHandleAndOwnership(aUpDevice1, aUpDevice2); + aUpDevice2.close(); + if( proxyOwnsUpstreamDevice ) { + aProxy.addUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE ); + } + if( DEBUG ) { + System.err.println("GLEventListenerState.moveTo.3a: up-orig 0x"+Integer.toHexString(aUpDevice1.hashCode())+", "+aUpDevice1); + System.err.println("GLEventListenerState.moveTo.3b: up-pres 0x"+Integer.toHexString(aUpDevice2.hashCode())+", "+aUpDevice2); + System.err.println("GLEventListenerState.moveTo.3c: "+aUpSurface.getClass().getName()/*+", "+aUpSurface+", "*/+aProxy.getUpstreamOptionBits(null).toString()); + } + } else { + throw new GLException("Incompatible Surface config - Has Upstream-Surface: Prev-Holder = false, New-Holder = true"); + } + } + + if( destIsRealized && null != aUpSurface ) { + dest.getDelegatedDrawable().setRealized(true); + } + if( DEBUG ) { + System.err.println("GLEventListenerState.moveTo.X : has aProxy "+(null!=aProxy)); + System.err.println("GLEventListenerState.moveTo.X : has aUpSurface "+(null!=aUpSurface)); + } + dest.setContext( context, false ); + } finally { + if( destSurfaceLocked ) { + destSurface.unlockSurface(); + } } - a.setContext( context, false ); } finally { - if( surfaceLocked ) { - aSurface.unlockSurface(); - } + destUpstreamLock.unlock(); + } + if( null != destUnlockOperation ) { + destUnlockOperation.run(); } + owner = false; // @@ -376,36 +505,36 @@ public class GLEventListenerState { aGLCmds.add(setViewport); for(int i=0; i<aSz; i++) { if( listenersInit[i] ) { - aGLCmds.add(new ReshapeGLEventListener( listeners[i] ) ); + aGLCmds.add(new GLDrawableUtil.ReshapeGLEventListener( listeners[i], false ) ); } } aGLCmds.add(glFinish); - a.invoke(aRealized, aGLCmds); // only wait if already realized + dest.invoke(destIsRealized, aGLCmds); // only wait if already realized // add all cached GLEventListener to their destination and fix their init-state for(int i=0; i<aSz; i++) { final GLEventListener l = listeners[i]; - a.addGLEventListener( l ); - a.setGLEventListenerInitState(l, listenersInit[i]); + dest.addGLEventListener( l ); + dest.setGLEventListenerInitState(l, listenersInit[i]); listeners[i] = null; } - if( hasAnimator ) { + if( null != destAnim ) { // prefer already bound animator - aAnim.add(a); - if( aPaused ) { - aAnim.resume(); + destAnim.add(dest); + if( destAnimPaused ) { + destAnim.resume(); } } else if ( null != anim ) { // use previously bound animator - anim.add(a); // also handles ECT + anim.add(dest); // also handles ECT if(animStarted) { anim.start(); } } } - public static final GLRunnable setViewport = new GLRunnable() { + private static final GLRunnable setViewport = new GLRunnable() { @Override public boolean run(final GLAutoDrawable drawable) { drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); @@ -413,23 +542,11 @@ public class GLEventListenerState { } }; - public static final GLRunnable glFinish = new GLRunnable() { + private static final GLRunnable glFinish = new GLRunnable() { @Override public boolean run(final GLAutoDrawable drawable) { drawable.getGL().glFinish(); return true; } }; - - public static class ReshapeGLEventListener implements GLRunnable { - private final GLEventListener listener; - public ReshapeGLEventListener(final GLEventListener listener) { - this.listener = listener; - } - @Override - public boolean run(final GLAutoDrawable drawable) { - listener.reshape(drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); - return true; - } - } } diff --git a/src/jogl/classes/com/jogamp/opengl/GLRendererQuirks.java b/src/jogl/classes/com/jogamp/opengl/GLRendererQuirks.java index 83fceeb2f..e8039edf1 100644 --- a/src/jogl/classes/com/jogamp/opengl/GLRendererQuirks.java +++ b/src/jogl/classes/com/jogamp/opengl/GLRendererQuirks.java @@ -191,7 +191,12 @@ public class GLRendererQuirks { * <li>GL_RENDERER: <i>Gallium 0.4 on SVGA3D; build: RELEASE;</i> </li> * </ul></li> * </ul> + * <p> + * Also enabled via {@link #BuggyColorRenderbuffer}. + * </p> + * <p> * Quirk can also be enabled via property: <code>jogl.fbo.force.min</code>. + * </p> */ public static final int NoFullFBOSupport = 11; @@ -312,8 +317,32 @@ public class GLRendererQuirks { */ public static final int NoMultiSamplingBuffers = 17; - /** Number of quirks known. */ - public static final int COUNT = 18; + /** + * With certain drivers no reliable FBO color renderbuffer target + * is available, read <i>a crash</i> may occur. + * <p> + * Appears on: + * <ul> + * <li>GL_VENDOR Brian Paul</li> + * <li>GL_RENDERER Mesa X11</li> + * <li>GL_VERSION 2.1 Mesa 7.2</li> + * </ul> + * TODO: We have to determine the exact version range, i.e. not adding the quirk with fixed driver version! + * </p> + * <p> + * Note: Also enables {@link #NoFullFBOSupport}. + * </p> + * <p> + * Note: GLFBODrawable always uses texture attachments if set. + * </p> + * <p> + * Quirk can also be enabled via property: <code>jogl.fbo.force.nocolorrenderbuffer</code>. + * </p> + */ + public static final int BuggyColorRenderbuffer = 18; + + /** Return the number of known quirks. */ + public static final int getCount() { return 19; } private static final String[] _names = new String[] { "NoDoubleBufferedPBuffer", "NoDoubleBufferedBitmap", "NoSetSwapInterval", "NoOffscreenBitmap", "NoSetSwapIntervalPostRetarget", "GLSLBuggyDiscard", @@ -321,7 +350,7 @@ public class GLRendererQuirks { "NeedCurrCtx4ARBPixFmtQueries", "NeedCurrCtx4ARBCreateContext", "NoFullFBOSupport", "GLSLNonCompliant", "GL4NeedsGL3Request", "GLSharedContextBuggy", "GLES3ViaEGLES2Config", "SingletonEGLDisplayOnly", - "NoMultiSamplingBuffers" + "NoMultiSamplingBuffers", "BuggyColorRenderbuffer" }; private static final IdentityHashMap<String, GLRendererQuirks> stickyDeviceQuirks = new IdentityHashMap<String, GLRendererQuirks>(); @@ -358,6 +387,17 @@ public class GLRendererQuirks { } /** + * {@link #addQuirk(int) Adding given quirk} of sticky {@link AbstractGraphicsDevice}'s {@link GLRendererQuirks}. + * <p> + * Not thread safe. + * </p> + * @see #getStickyDeviceQuirks(AbstractGraphicsDevice) + */ + public static void addStickyDeviceQuirk(final AbstractGraphicsDevice device, final int quirk) throws IllegalArgumentException { + final GLRendererQuirks sq = getStickyDeviceQuirks(device); + sq.addQuirk(quirk); + } + /** * {@link #addQuirks(int[], int, int) Adding given quirks} of sticky {@link AbstractGraphicsDevice}'s {@link GLRendererQuirks}. * <p> * Not thread safe. @@ -419,6 +459,15 @@ public class GLRendererQuirks { } /** + * @param quirk valid quirk to be added + * @throws IllegalArgumentException if the quirk is out of range + */ + public final void addQuirk(final int quirk) throws IllegalArgumentException { + validateQuirk(quirk); + _bitmask |= 1 << quirk; + } + + /** * @param quirks an array of valid quirks to be added * @param offset offset in quirks array to start reading * @param len number of quirks to read from offset within quirks array @@ -460,7 +509,7 @@ public class GLRendererQuirks { } sb.append("["); boolean first=true; - for(int i=0; i<COUNT; i++) { + for(int i=0; i<getCount(); i++) { final int testmask = 1 << i; if( 0 != ( _bitmask & testmask ) ) { if(!first) { sb.append(", "); } @@ -482,8 +531,8 @@ public class GLRendererQuirks { * @throws IllegalArgumentException if quirk is out of range */ public static void validateQuirk(final int quirk) throws IllegalArgumentException { - if( !( 0 <= quirk && quirk < COUNT ) ) { - throw new IllegalArgumentException("Quirks must be in range [0.."+COUNT+"[, but quirk: "+quirk); + if( !( 0 <= quirk && quirk < getCount() ) ) { + throw new IllegalArgumentException("Quirks must be in range [0.."+getCount()+"[, but quirk: "+quirk); } } diff --git a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java index e58d8c64a..28f572d7d 100644 --- a/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java +++ b/src/jogl/classes/com/jogamp/opengl/swt/GLCanvas.java @@ -85,6 +85,12 @@ import com.jogamp.opengl.JoglVersion; * <p> * Implementation allows use of custom {@link GLCapabilities}. * </p> + * <p> + * <a name="contextSharing"><h5>OpenGL Context Sharing</h5></a> + * To share a {@link GLContext} see the following note in the documentation overview: + * <a href="../../../../overview-summary.html#SHARING">context sharing</a> + * as well as {@link GLSharedContextSetter}. + * </p> */ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextSetter { private static final boolean DEBUG = Debug.debug("GLCanvas"); @@ -205,10 +211,9 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS animatorPaused = false; } - if ( null != context ) { + GLException exceptionOnDisposeGL = null; + if( null != context ) { if( context.isCreated() ) { - // Catch dispose GLExceptions by GLEventListener, just 'print' them - // so we can continue with the destruction. try { if( !GLCanvas.this.isDisposed() ) { helper.disposeGL(GLCanvas.this, context, true); @@ -216,28 +221,50 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS context.destroy(); } } catch (final GLException gle) { - gle.printStackTrace(); + exceptionOnDisposeGL = gle; } } context = null; } - if ( null != drawable ) { - drawable.setRealized(false); + + Throwable exceptionOnUnrealize = null; + if( null != drawable ) { + try { + drawable.setRealized(false); + } catch( final Throwable re ) { + exceptionOnUnrealize = re; + } drawable = null; } - if( 0 != x11Window) { - SWTAccessor.destroyX11Window(screen.getDevice(), x11Window); - x11Window = 0; - } else if( 0 != gdkWindow) { - SWTAccessor.destroyGDKWindow(gdkWindow); - gdkWindow = 0; + + Throwable exceptionOnDeviceClose = null; + try { + if( 0 != x11Window) { + SWTAccessor.destroyX11Window(screen.getDevice(), x11Window); + x11Window = 0; + } else if( 0 != gdkWindow) { + SWTAccessor.destroyGDKWindow(gdkWindow); + gdkWindow = 0; + } + screen.getDevice().close(); + } catch (final Throwable re) { + exceptionOnDeviceClose = re; } - screen.getDevice().close(); if (animatorPaused) { animator.resume(); } + // throw exception in order of occurrence .. + if( null != exceptionOnDisposeGL ) { + throw exceptionOnDisposeGL; + } + if( null != exceptionOnUnrealize ) { + throw GLException.newGLException(exceptionOnUnrealize); + } + if( null != exceptionOnDeviceClose ) { + throw GLException.newGLException(exceptionOnDeviceClose); + } } finally { _lock.unlock(); } @@ -638,6 +665,9 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS } @Override + public final RecursiveLock getUpstreamLock() { return lock; } + + @Override public int getSurfaceWidth() { return clientArea.width; } @@ -755,16 +785,21 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS } @Override - public boolean invoke(final boolean wait, final GLRunnable runnable) { + public boolean invoke(final boolean wait, final GLRunnable runnable) throws IllegalStateException { return helper.invoke(this, wait, runnable); } @Override - public boolean invoke(final boolean wait, final List<GLRunnable> runnables) { + public boolean invoke(final boolean wait, final List<GLRunnable> runnables) throws IllegalStateException { return helper.invoke(this, wait, runnables); } @Override + public void flushGLRunnables() { + helper.flushGLRunnables(); + } + + @Override public void setAnimator(final GLAnimatorControl arg0) throws GLException { helper.setAnimator(arg0); } @@ -829,14 +864,10 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS return null != _drawable ? (GLCapabilitiesImmutable)_drawable.getChosenGLCapabilities() : null; } - /** - * Accessor for the GLCapabilities that were requested (via the constructor parameter). - * - * @return Non-null GLCapabilities. - */ + @Override public GLCapabilitiesImmutable getRequestedGLCapabilities() { final GLDrawable _drawable = drawable; - return null != _drawable ? (GLCapabilitiesImmutable)_drawable.getNativeSurface().getGraphicsConfiguration().getRequestedCapabilities() : null; + return null != _drawable ? (GLCapabilitiesImmutable)_drawable.getRequestedGLCapabilities() : null; } @Override @@ -879,6 +910,15 @@ public class GLCanvas extends Canvas implements GLAutoDrawable, GLSharedContextS } /** + * {@inheritDoc} + * <p> + * Implementation always supports multithreading, hence method always returns <code>true</code>. + * </p> + */ + @Override + public final boolean isThreadGLCapable() { return true; } + + /** * Runs the specified action in an SWT compatible thread, which is: * <ul> * <li>Mac OSX diff --git a/src/jogl/classes/com/jogamp/opengl/util/AWTAnimatorImpl.java b/src/jogl/classes/com/jogamp/opengl/util/AWTAnimatorImpl.java index 496fb88c0..62df3faca 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/AWTAnimatorImpl.java +++ b/src/jogl/classes/com/jogamp/opengl/util/AWTAnimatorImpl.java @@ -41,12 +41,14 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; + import javax.swing.JComponent; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; - import javax.media.opengl.GLAutoDrawable; +import com.jogamp.opengl.util.AnimatorBase.UncaughtAnimatorException; + /** Abstraction to factor out AWT dependencies from the Animator's implementation in a way that still allows the FPSAnimator to pick up this behavior if desired. */ @@ -61,7 +63,7 @@ class AWTAnimatorImpl implements AnimatorBase.AnimatorImpl { @Override public void display(final ArrayList<GLAutoDrawable> drawables, final boolean ignoreExceptions, - final boolean printExceptions) { + final boolean printExceptions) throws UncaughtAnimatorException { for (int i=0; i<drawables.size(); i++) { final GLAutoDrawable drawable = drawables.get(i); if (drawable instanceof JComponent) { @@ -73,13 +75,13 @@ class AWTAnimatorImpl implements AnimatorBase.AnimatorImpl { } else { try { drawable.display(); - } catch (final RuntimeException e) { + } catch (final Throwable t) { if (ignoreExceptions) { if (printExceptions) { - e.printStackTrace(); + t.printStackTrace(); } } else { - throw(e); + throw new UncaughtAnimatorException(drawable, t); } } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/Animator.java b/src/jogl/classes/com/jogamp/opengl/util/Animator.java index ae09d81a4..b38a42ee3 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/Animator.java +++ b/src/jogl/classes/com/jogamp/opengl/util/Animator.java @@ -58,12 +58,12 @@ import javax.media.opengl.GLException; * </p> */ public class Animator extends AnimatorBase { - protected ThreadGroup threadGroup; + private ThreadGroup threadGroup; private Runnable runnable; private boolean runAsFastAsPossible; - protected boolean isAnimating; - protected boolean pauseIssued; - protected volatile boolean stopIssued; + boolean isAnimating; + volatile boolean pauseIssued; + volatile boolean stopIssued; /** * Creates a new, empty Animator. @@ -132,6 +132,9 @@ public class Animator extends AnimatorBase { @Override public void run() { + ThreadDeath caughtThreadDeath = null; + UncaughtAnimatorException caughtException = null; + try { synchronized (Animator.this) { if(DEBUG) { @@ -147,7 +150,7 @@ public class Animator extends AnimatorBase { synchronized (Animator.this) { // Pause; Also don't consume CPU unless there is work to be done and not paused boolean ectCleared = false; - while (!stopIssued && (pauseIssued || drawablesEmpty)) { + while ( !stopIssued && ( pauseIssued || drawablesEmpty ) ) { if( drawablesEmpty ) { pauseIssued = true; } @@ -158,7 +161,13 @@ public class Animator extends AnimatorBase { if ( exclusiveContext && !drawablesEmpty && !ectCleared ) { ectCleared = true; setDrawablesExclCtxState(false); - display(); // propagate exclusive change! + try { + display(); // propagate exclusive context -> off! + } catch (final UncaughtAnimatorException dre) { + caughtException = dre; + stopIssued = true; + break; // end pause loop + } } isAnimating = false; Animator.this.notifyAll(); @@ -180,38 +189,72 @@ public class Animator extends AnimatorBase { // Resume from pause or drawablesEmpty, // implies !pauseIssued and !drawablesEmpty isAnimating = true; - setDrawablesExclCtxState(exclusiveContext); + setDrawablesExclCtxState(exclusiveContext); // may re-enable exclusive context Animator.this.notifyAll(); } } // sync Animator.this - if (!stopIssued) { - display(); - } - if (!stopIssued && !runAsFastAsPossible) { - // Avoid swamping the CPU - Thread.yield(); + if ( !pauseIssued && !stopIssued ) { + try { + display(); + } catch (final UncaughtAnimatorException dre) { + caughtException = dre; + stopIssued = true; + break; // end animation loop + } + if ( !runAsFastAsPossible ) { + // Avoid swamping the CPU + Thread.yield(); + } } } - } catch( final ThreadDeath td) { + } catch(final ThreadDeath td) { if(DEBUG) { System.err.println("Animator caught: "+td.getClass().getName()+": "+td.getMessage()); td.printStackTrace(); } - } finally { - if( exclusiveContext && !drawablesEmpty ) { - setDrawablesExclCtxState(false); - display(); // propagate exclusive change! + caughtThreadDeath = td; + } + if( exclusiveContext && !drawablesEmpty ) { + setDrawablesExclCtxState(false); + try { + display(); // propagate exclusive context -> off! + } catch (final UncaughtAnimatorException dre) { + if( null == caughtException ) { + caughtException = dre; + } else { + System.err.println("Animator.setExclusiveContextThread: caught: "+dre.getMessage()); + dre.printStackTrace(); + } } - synchronized (Animator.this) { - if(DEBUG) { - System.err.println("Animator stop on " + animThread.getName() + ": " + toString()); + } + boolean flushGLRunnables = false; + boolean throwCaughtException = false; + synchronized (Animator.this) { + if(DEBUG) { + System.err.println("Animator stop on " + animThread.getName() + ": " + toString()); + if( null != caughtException ) { + System.err.println("Animator caught: "+caughtException.getMessage()); + caughtException.printStackTrace(); } - stopIssued = false; - pauseIssued = false; - animThread = null; - isAnimating = false; - Animator.this.notifyAll(); } + stopIssued = false; + pauseIssued = false; + isAnimating = false; + if( null != caughtException ) { + flushGLRunnables = true; + throwCaughtException = !handleUncaughtException(caughtException); + } + animThread = null; + Animator.this.notifyAll(); + } + if( flushGLRunnables ) { + flushGLRunnables(); + } + if( throwCaughtException ) { + throw caughtException; + } + if( null != caughtThreadDeath ) { + throw caughtThreadDeath; } } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java b/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java index a5bdb9350..bc159ef5c 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java +++ b/src/jogl/classes/com/jogamp/opengl/util/AnimatorBase.java @@ -67,9 +67,26 @@ public abstract class AnimatorBase implements GLAnimatorControl { */ public static final int MODE_EXPECT_AWT_RENDERING_THREAD = 1 << 0; - public interface AnimatorImpl { - void display(ArrayList<GLAutoDrawable> drawables, boolean ignoreExceptions, boolean printExceptions); - boolean blockUntilDone(Thread thread); + + @SuppressWarnings("serial") + public static class UncaughtAnimatorException extends RuntimeException { + final GLAutoDrawable drawable; + public UncaughtAnimatorException(final GLAutoDrawable drawable, final Throwable cause) { + super(cause); + this.drawable = drawable; + } + public final GLAutoDrawable getGLAutoDrawable() { return drawable; } + } + + public static interface AnimatorImpl { + /** + * @param drawables + * @param ignoreExceptions + * @param printExceptions + * @throws UncaughtAnimatorException as caused by {@link GLAutoDrawable#display()} + */ + void display(final ArrayList<GLAutoDrawable> drawables, final boolean ignoreExceptions, final boolean printExceptions) throws UncaughtAnimatorException; + boolean blockUntilDone(final Thread thread); } protected int modeBits; @@ -83,6 +100,7 @@ public abstract class AnimatorBase implements GLAnimatorControl { protected boolean printExceptions; protected boolean exclusiveContext; protected Thread userExclusiveContextThread; + protected UncaughtExceptionHandler uncaughtExceptionHandler; protected FPSCounterImpl fpsCounter = new FPSCounterImpl(); private final static Class<?> awtAnimatorImplClazz; @@ -313,11 +331,16 @@ public abstract class AnimatorBase implements GLAnimatorControl { } } final Thread dECT = enable ? ( null != _exclusiveContextThread ? _exclusiveContextThread : animThread ) : null ; + UncaughtAnimatorException displayCaught = null; if( propagateState ) { setDrawablesExclCtxState(enable); if( !enable ) { if( Thread.currentThread() == getThread() || Thread.currentThread() == _exclusiveContextThread ) { - display(); + try { + display(); // propagate exclusive context -> off! + } catch (final UncaughtAnimatorException dre) { + displayCaught = dre; + } } else { final boolean resumed = isAnimating() ? false : resume(); int counter = 10; @@ -338,6 +361,13 @@ public abstract class AnimatorBase implements GLAnimatorControl { } if(DEBUG) { System.err.println("AnimatorBase.setExclusiveContextThread: all-GLAD Ok: "+validateDrawablesExclCtxState(dECT)+", "+this); + if( null != displayCaught ) { + System.err.println("AnimatorBase.setExclusiveContextThread: caught: "+displayCaught.getMessage()); + displayCaught.printStackTrace(); + } + } + if( null != displayCaught ) { + throw displayCaught; } return oldExclusiveContext; } @@ -412,7 +442,7 @@ public abstract class AnimatorBase implements GLAnimatorControl { this to get the most optimized painting behavior for the set of components this Animator manages, in particular when multiple lightweight widgets are continually being redrawn. */ - protected final void display() { + protected final void display() throws UncaughtAnimatorException { impl.display(drawables, ignoreExceptions, printExceptions); fpsCounter.tickFPS(); } @@ -482,7 +512,47 @@ public abstract class AnimatorBase implements GLAnimatorControl { this.printExceptions = printExceptions; } - protected interface Condition { + @Override + public final UncaughtExceptionHandler getUncaughtExceptionHandler() { + return uncaughtExceptionHandler; + } + + @Override + public final void setUncaughtExceptionHandler(final UncaughtExceptionHandler handler) { + uncaughtExceptionHandler = handler; + } + + /** + * Should be called in case of an uncaught exception + * from within the animator thread, throws given exception if no handler has been installed. + * @return {@code true} if handled, otherwise {@code false}. In case of {@code false}, + * caller needs to propagate the exception. + */ + protected final synchronized boolean handleUncaughtException(final UncaughtAnimatorException ue) { + if( null != uncaughtExceptionHandler ) { + try { + uncaughtExceptionHandler.uncaughtException(this, ue.getGLAutoDrawable(), ue.getCause()); + } catch (final Throwable t) { /* ignore intentionally */ } + return true; + } else { + return false; + } + } + + /** + * Should be called in case of an uncaught exception + * from within the animator thread to flush all animator. + * <p> + * The animator instance shall not be locked when calling this method! + * </p> + */ + protected final void flushGLRunnables() { + for (int i=0; i<drawables.size(); i++) { + drawables.get(i).flushGLRunnables(); + } + } + + protected static interface Condition { /** * @return true if branching (continue waiting, action), otherwise false */ @@ -493,7 +563,8 @@ public abstract class AnimatorBase implements GLAnimatorControl { * @param waitCondition method will wait until TO is reached or {@link Condition#eval() waitCondition.eval()} returns <code>false</code>. * @param pollPeriod if <code>0</code>, method will wait until TO is reached or being notified. * if > <code>0</code>, method will wait for the given <code>pollPeriod</code> in milliseconds. - * @return <code>true</code> if {@link Condition#eval() waitCondition.eval()} returned <code>false</code>, otherwise <code>false</code>. + * @return <code>true</code> if {@link Condition#eval() waitCondition.eval()} returned <code>false</code> + * or if {@link AnimatorImpl#blockUntilDone(Thread) non-blocking}. Otherwise returns <code>false</code>. */ protected final synchronized boolean finishLifecycleAction(final Condition waitCondition, long pollPeriod) { /** @@ -506,6 +577,7 @@ public abstract class AnimatorBase implements GLAnimatorControl { final boolean blocking; long remaining; boolean nok; + if( impl.blockUntilDone(animThread) ) { blocking = true; remaining = TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION; @@ -539,12 +611,13 @@ public abstract class AnimatorBase implements GLAnimatorControl { nok = waitCondition.eval(); } } + final boolean res = !nok || !blocking; if(DEBUG || blocking && nok) { // Info only if DEBUG or ( blocking && not-ok ) ; !blocking possible if AWT if( blocking && remaining<=0 && nok ) { System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): ++++++ timeout reached ++++++ " + getThreadName()); } System.err.println("finishLifecycleAction(" + waitCondition.getClass().getName() + "): OK "+(!nok)+ - "- pollPeriod "+pollPeriod+", blocking "+blocking+ + "- pollPeriod "+pollPeriod+", blocking "+blocking+" -> res "+res+ ", waited " + (blocking ? ( TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION - remaining ) : 0 ) + "/" + TO_WAIT_FOR_FINISH_LIFECYCLE_ACTION + " - " + getThreadName()); System.err.println(" - "+toString()); @@ -552,7 +625,7 @@ public abstract class AnimatorBase implements GLAnimatorControl { Thread.dumpStack(); } } - return !nok; + return res; } @Override diff --git a/src/jogl/classes/com/jogamp/opengl/util/DefaultAnimatorImpl.java b/src/jogl/classes/com/jogamp/opengl/util/DefaultAnimatorImpl.java index 7fa4011f8..6b1485a6a 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/DefaultAnimatorImpl.java +++ b/src/jogl/classes/com/jogamp/opengl/util/DefaultAnimatorImpl.java @@ -34,8 +34,11 @@ package com.jogamp.opengl.util; import java.util.ArrayList; + import javax.media.opengl.GLAutoDrawable; +import com.jogamp.opengl.util.AnimatorBase.UncaughtAnimatorException; + /** Abstraction to factor out AWT dependencies from the Animator's implementation in a way that still allows the FPSAnimator to pick up this behavior if desired. */ @@ -44,18 +47,18 @@ class DefaultAnimatorImpl implements AnimatorBase.AnimatorImpl { @Override public void display(final ArrayList<GLAutoDrawable> drawables, final boolean ignoreExceptions, - final boolean printExceptions) { + final boolean printExceptions) throws UncaughtAnimatorException { for (int i=0; i<drawables.size(); i++) { final GLAutoDrawable drawable = drawables.get(i); try { drawable.display(); - } catch (final RuntimeException e) { + } catch (final Throwable t) { if (ignoreExceptions) { if (printExceptions) { - e.printStackTrace(); + t.printStackTrace(); } } else { - throw(e); + throw new UncaughtAnimatorException(drawable, t); } } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java b/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java index 746b642c2..54d40f285 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java +++ b/src/jogl/classes/com/jogamp/opengl/util/FPSAnimator.java @@ -60,9 +60,9 @@ public class FPSAnimator extends AnimatorBase { private MainTask task = null; private int fps; private final boolean scheduleAtFixedRate; - private boolean isAnimating; // MainTask feedback - private volatile boolean shouldRun; // MainTask trigger - private volatile boolean shouldStop; // MainTask trigger + private boolean isAnimating; // MainTask feedback + private volatile boolean pauseIssued; // MainTask trigger + private volatile boolean stopIssued; // MainTask trigger @Override protected String getBaseName(final String prefix) { @@ -124,8 +124,9 @@ public class FPSAnimator extends AnimatorBase { public void start(final Timer timer) { fpsCounter.resetFPSCounter(); - shouldRun = true; - shouldStop = false; + pauseIssued = false; + stopIssued = false; + isAnimating = false; justStarted = true; alreadyStopped = false; @@ -143,11 +144,13 @@ public class FPSAnimator extends AnimatorBase { @Override public final String toString() { - return "Task[thread "+animThread+", stopped "+alreadyStopped+", paused "+alreadyPaused+" shouldRun "+shouldRun+", shouldStop "+shouldStop+" -- started "+isStarted()+", animating "+isAnimatingImpl()+", paused "+isPaused()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]"; + return "Task[thread "+animThread+", stopped "+alreadyStopped+", paused "+alreadyPaused+" pauseIssued "+pauseIssued+", stopIssued "+stopIssued+" -- started "+isStarted()+", animating "+isAnimatingImpl()+", paused "+isPaused()+", drawable "+drawables.size()+", drawablesEmpty "+drawablesEmpty+"]"; } @Override public void run() { + UncaughtAnimatorException caughtException = null; + if( justStarted ) { justStarted = false; synchronized (FPSAnimator.this) { @@ -157,59 +160,97 @@ public class FPSAnimator extends AnimatorBase { } isAnimating = true; if( drawablesEmpty ) { - shouldRun = false; // isAnimating:=false @ pause below + pauseIssued = true; // isAnimating:=false @ pause below } else { - shouldRun = true; - setDrawablesExclCtxState(exclusiveContext); - FPSAnimator.this.notifyAll(); + pauseIssued = false; + setDrawablesExclCtxState(exclusiveContext); // may re-enable exclusive context } + FPSAnimator.this.notifyAll(); // Wakes up 'waitForStartedCondition' sync -and resume from pause or drawablesEmpty if(DEBUG) { System.err.println("FPSAnimator P1:" + Thread.currentThread() + ": " + toString()); } } } - if( shouldRun ) { - display(); - } else if( shouldStop ) { // STOP + if( !pauseIssued && !stopIssued ) { // RUN + try { + display(); + } catch (final UncaughtAnimatorException dre) { + caughtException = dre; + stopIssued = true; + } + } else if( pauseIssued && !stopIssued ) { // PAUSE if(DEBUG) { - System.err.println("FPSAnimator P4: "+alreadyStopped+", "+ Thread.currentThread() + ": " + toString()); + System.err.println("FPSAnimator pausing: "+alreadyPaused+", "+ Thread.currentThread() + ": " + toString()); } this.cancel(); - if( !alreadyStopped ) { - alreadyStopped = true; + if( !alreadyPaused ) { // PAUSE + alreadyPaused = true; if( exclusiveContext && !drawablesEmpty ) { setDrawablesExclCtxState(false); - display(); // propagate exclusive change! + try { + display(); // propagate exclusive context -> off! + } catch (final UncaughtAnimatorException dre) { + caughtException = dre; + stopIssued = true; + } } - synchronized (FPSAnimator.this) { - if(DEBUG) { - System.err.println("FPSAnimator stop " + Thread.currentThread() + ": " + toString()); + if( null == caughtException ) { + synchronized (FPSAnimator.this) { + if(DEBUG) { + System.err.println("FPSAnimator pause " + Thread.currentThread() + ": " + toString()); + } + isAnimating = false; + FPSAnimator.this.notifyAll(); } - animThread = null; - isAnimating = false; - FPSAnimator.this.notifyAll(); } } - } else { + } + if( stopIssued ) { // STOP incl. immediate exception handling of 'displayCaught' if(DEBUG) { - System.err.println("FPSAnimator P5: "+alreadyPaused+", "+ Thread.currentThread() + ": " + toString()); + System.err.println("FPSAnimator stopping: "+alreadyStopped+", "+ Thread.currentThread() + ": " + toString()); } this.cancel(); - if( !alreadyPaused ) { // PAUSE - alreadyPaused = true; + if( !alreadyStopped ) { + alreadyStopped = true; if( exclusiveContext && !drawablesEmpty ) { setDrawablesExclCtxState(false); - display(); // propagate exclusive change! + try { + display(); // propagate exclusive context -> off! + } catch (final UncaughtAnimatorException dre) { + if( null == caughtException ) { + caughtException = dre; + } else { + System.err.println("FPSAnimator.setExclusiveContextThread: caught: "+dre.getMessage()); + dre.printStackTrace(); + } + } } + boolean flushGLRunnables = false; + boolean throwCaughtException = false; synchronized (FPSAnimator.this) { if(DEBUG) { - System.err.println("FPSAnimator pause " + Thread.currentThread() + ": " + toString()); + System.err.println("FPSAnimator stop " + Thread.currentThread() + ": " + toString()); + if( null != caughtException ) { + System.err.println("Animator caught: "+caughtException.getMessage()); + caughtException.printStackTrace(); + } } isAnimating = false; + if( null != caughtException ) { + flushGLRunnables = true; + throwCaughtException = !handleUncaughtException(caughtException); + } + animThread = null; FPSAnimator.this.notifyAll(); } + if( flushGLRunnables ) { + flushGLRunnables(); + } + if( throwCaughtException ) { + throw caughtException; + } } } } @@ -224,7 +265,7 @@ public class FPSAnimator extends AnimatorBase { @Override public final synchronized boolean isPaused() { - return animThread != null && ( !shouldRun && !shouldStop ) ; + return animThread != null && pauseIssued; } static int timerNo = 0; @@ -279,8 +320,7 @@ public class FPSAnimator extends AnimatorBase { // start/resume case w/ drawablesEmpty res = true; } else { - shouldRun = false; - shouldStop = true; + stopIssued = true; res = finishLifecycleAction(waitForStoppedCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); } @@ -306,7 +346,7 @@ public class FPSAnimator extends AnimatorBase { @Override public final synchronized boolean pause() { - if ( !isStarted() || ( null != task && isPaused() ) ) { + if ( !isStarted() || pauseIssued ) { return false; } if(DEBUG) { @@ -317,7 +357,7 @@ public class FPSAnimator extends AnimatorBase { // start/resume case w/ drawablesEmpty res = true; } else { - shouldRun = false; + pauseIssued = true; res = finishLifecycleAction(waitForPausedCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); } @@ -334,12 +374,12 @@ public class FPSAnimator extends AnimatorBase { @Override public boolean eval() { // end waiting if stopped as well - return isAnimating && isStarted(); + return isStarted() && isAnimating; } }; @Override public final synchronized boolean resume() { - if ( null != task || !isStarted() || !isPaused() ) { + if ( !isStarted() || !pauseIssued ) { return false; } if(DEBUG) { @@ -349,6 +389,14 @@ public class FPSAnimator extends AnimatorBase { if( drawablesEmpty ) { res = true; } else { + if( null != task ) { + if( DEBUG ) { + System.err.println("FPSAnimator.resume() Ops: !pauseIssued, but task != null: "+toString()); + Thread.dumpStack(); + } + task.cancel(); + task = null; + } task = new MainTask(); task.start(timer); res = finishLifecycleAction(waitForResumeCondition, POLLP_WAIT_FOR_FINISH_LIFECYCLE_ACTION); diff --git a/src/jogl/classes/com/jogamp/opengl/util/GLBuffers.java b/src/jogl/classes/com/jogamp/opengl/util/GLBuffers.java index 44be29957..d4ab4e4f4 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/GLBuffers.java +++ b/src/jogl/classes/com/jogamp/opengl/util/GLBuffers.java @@ -46,6 +46,7 @@ import javax.media.opengl.GL2; import javax.media.opengl.GL2ES2; import javax.media.opengl.GL2ES3; import javax.media.opengl.GL2GL3; +import javax.media.opengl.GLContext; import javax.media.opengl.GLES2; import javax.media.opengl.GLException; @@ -372,25 +373,29 @@ public class GLBuffers extends Buffers { if (pack) { alignment = glGetInteger(gl, GL.GL_PACK_ALIGNMENT, tmp); - if(gl.isGL2GL3()) { + if( gl.isGL2ES3() ) { rowLength = glGetInteger(gl, GL2ES3.GL_PACK_ROW_LENGTH, tmp); skipRows = glGetInteger(gl, GL2ES3.GL_PACK_SKIP_ROWS, tmp); skipPixels = glGetInteger(gl, GL2ES3.GL_PACK_SKIP_PIXELS, tmp); - if (depth > 1) { + if (depth > 1 && gl.isGL2GL3() && gl.getContext().getGLVersionNumber().compareTo(GLContext.Version120) >= 0 ) { imageHeight = glGetInteger(gl, GL2GL3.GL_PACK_IMAGE_HEIGHT, tmp); skipImages = glGetInteger(gl, GL2GL3.GL_PACK_SKIP_IMAGES, tmp); } } } else { alignment = glGetInteger(gl, GL.GL_UNPACK_ALIGNMENT, tmp); - if(gl.isGL2GL3 ()) { + if( gl.isGL2ES3() ) { rowLength = glGetInteger(gl, GL2ES2.GL_UNPACK_ROW_LENGTH, tmp); skipRows = glGetInteger(gl, GL2ES2.GL_UNPACK_SKIP_ROWS, tmp); skipPixels = glGetInteger(gl, GL2ES2.GL_UNPACK_SKIP_PIXELS, tmp); - if (depth > 1) { + if( depth > 1 && + ( gl.isGL3ES3() || + ( gl.isGL2GL3() && gl.getContext().getGLVersionNumber().compareTo(GLContext.Version120) >= 0 ) + ) + ) { imageHeight = glGetInteger(gl, GL2ES3.GL_UNPACK_IMAGE_HEIGHT, tmp); skipImages = glGetInteger(gl, GL2ES3.GL_UNPACK_SKIP_IMAGES, tmp); - } + } } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java b/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java index c74284299..956693c4a 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/util/GLDrawableUtil.java @@ -28,6 +28,7 @@ package com.jogamp.opengl.util; import javax.media.nativewindow.AbstractGraphicsDevice; +import javax.media.nativewindow.NativeSurface; import javax.media.opengl.GLAnimatorControl; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLBase; @@ -36,7 +37,10 @@ import javax.media.opengl.GLContext; import javax.media.opengl.GLDrawable; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; +import javax.media.opengl.GLRunnable; +import javax.media.opengl.Threading; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.opengl.GLEventListenerState; import jogamp.opengl.Debug; @@ -45,148 +49,298 @@ import jogamp.opengl.Debug; * Providing utility functions dealing w/ {@link GLDrawable}s, {@link GLAutoDrawable} and their {@link GLEventListener}. */ public class GLDrawableUtil { - protected static final boolean DEBUG = Debug.debug("GLDrawable"); - - public static final boolean isAnimatorStartedOnOtherThread(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ; - } - - public static final boolean isAnimatorStarted(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isStarted() : false ; - } - - public static final boolean isAnimatorAnimatingOnOtherThread(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() && animatorCtrl.getThread() != Thread.currentThread() : false ; - } - - public static final boolean isAnimatorAnimating(final GLAnimatorControl animatorCtrl) { - return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ; - } - - /** - * Moves the designated {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. - * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved - * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. - * <p> - * Note that it is only legal to pass <code>preserveInitState := true</code>, - * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. - * </p> - * <p> - * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. - * </p> - * @param src - * @param dest - * @param listener - * @param preserveInitState - */ - public static final void moveGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final GLEventListener listener, final boolean preserveInitState) { - final boolean initialized = src.getGLEventListenerInitState(listener); - src.removeGLEventListener(listener); - dest.addGLEventListener(listener); - if(preserveInitState && initialized) { - dest.setGLEventListenerInitState(listener, true); - dest.invoke(false, new GLEventListenerState.ReshapeGLEventListener(listener)); - } // else .. !init state is default - } - - /** - * Moves all {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. - * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved - * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. - * <p> - * Note that it is only legal to pass <code>preserveInitState := true</code>, - * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. - * </p> - * <p> - * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. - * </p> - * @param src - * @param dest - * @param listener - * @param preserveInitState - */ - public static final void moveAllGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final boolean preserveInitState) { - for(int count = src.getGLEventListenerCount(); 0<count; count--) { - final GLEventListener listener = src.getGLEventListener(0); - moveGLEventListener(src, dest, listener, preserveInitState); + protected static final boolean DEBUG = Debug.debug("GLDrawable"); + + public static final boolean isAnimatorStartedOnOtherThread(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isStarted() && animatorCtrl.getThread() != Thread.currentThread() : false ; + } + + public static final boolean isAnimatorStarted(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isStarted() : false ; } - } - - /** - * Swaps the {@link GLContext} and all {@link GLEventListener} between {@link GLAutoDrawable} <code>a</code> and <code>b</code>, - * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}. - * <p> - * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association - * is also swapped. - * </p> - * <p> - * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} <code>a</code> or <code>b</code> - * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. - * </p> - * @param a - * @param b - * @throws GLException if the {@link AbstractGraphicsDevice} are incompatible w/ each other. - */ - public static final void swapGLContextAndAllGLEventListener(final GLAutoDrawable a, final GLAutoDrawable b) { - final GLEventListenerState gllsA = GLEventListenerState.moveFrom(a); - final GLEventListenerState gllsB = GLEventListenerState.moveFrom(b); - - gllsA.moveTo(b); - gllsB.moveTo(a); - } - - /** - * Swaps the {@link GLContext} of given {@link GLAutoDrawable} - * and {@link GLAutoDrawable#disposeGLEventListener(GLEventListener, boolean) disposes} - * each {@link GLEventListener} w/o removing it. - * <p> - * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit. - * </p> - * <p> - * If an {@link GLAnimatorControl} is being attached to GLAutoDrawable src or dest and the current thread is different - * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. - * </p> - * @param src - * @param dest - */ - public static final void swapGLContext(final GLAutoDrawable src, final GLAutoDrawable dest) { - final GLAnimatorControl aAnim = src.getAnimator(); - final GLAnimatorControl bAnim = dest.getAnimator(); - final boolean aIsPaused = isAnimatorAnimatingOnOtherThread(aAnim) && aAnim.pause(); - final boolean bIsPaused = isAnimatorAnimatingOnOtherThread(bAnim) && bAnim.pause(); - - for(int i = src.getGLEventListenerCount() - 1; 0 <= i; i--) { - src.disposeGLEventListener(src.getGLEventListener(i), false); + + public static final boolean isAnimatorAnimatingOnOtherThread(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() && animatorCtrl.getThread() != Thread.currentThread() : false ; + } + + public static final boolean isAnimatorAnimating(final GLAnimatorControl animatorCtrl) { + return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ; } - for(int i = dest.getGLEventListenerCount() - 1; 0 <= i; i--) { - dest.disposeGLEventListener(dest.getGLEventListener(i), false); + + /** + * {@link GLRunnable} to issue {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)}, + * returning <code>true</code> on {@link GLRunnable#run(GLAutoDrawable)}. + */ + public static class ReshapeGLEventListener implements GLRunnable { + private final GLEventListener listener; + private final boolean displayAfterReshape; + /** + * + * @param listener + * @param displayAfterReshape <code>true</code> to issue {@link GLEventListener#display(GLAutoDrawable)} + * after {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int)}, + * otherwise false. + */ + public ReshapeGLEventListener(final GLEventListener listener, final boolean displayAfterReshape) { + this.listener = listener; + this.displayAfterReshape = displayAfterReshape; + } + @Override + public boolean run(final GLAutoDrawable drawable) { + listener.reshape(drawable, 0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); + if( displayAfterReshape ) { + listener.display(drawable); + } + return true; + } + } + + /** + * Moves the designated {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. + * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved + * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. + * <p> + * Note that it is only legal to pass <code>preserveInitState := true</code>, + * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. + * </p> + * <p> + * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. + * </p> + * @param src + * @param dest + * @param listener + * @param preserveInitState + */ + public static final void moveGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final GLEventListener listener, final boolean preserveInitState) { + final boolean initialized = src.getGLEventListenerInitState(listener); + if( preserveInitState ) { + src.removeGLEventListener(listener); + dest.addGLEventListener(listener); + if( initialized ) { + dest.setGLEventListenerInitState(listener, true); + dest.invoke(false, new ReshapeGLEventListener(listener, true)); + } + } else { + src.disposeGLEventListener(listener, true); + dest.addGLEventListener(listener); + } + } + + /** + * Moves all {@link GLEventListener} from {@link GLAutoDrawable} <code>src</code> to <code>dest</code>. + * If <code>preserveInitState</code> is <code>true</code>, it's initialized state is preserved + * and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued w/ the next {@link GLAutoDrawable#display()} call. + * <p> + * Note that it is only legal to pass <code>preserveInitState := true</code>, + * if the {@link GLContext} of both <code>src</code> and <code>dest</code> are shared, or has itself moved from <code>src</code> to <code>dest</code>. + * </p> + * <p> + * Also note that the caller is encouraged to pause an attached {@link GLAnimatorControl}. + * </p> + * @param src + * @param dest + * @param listener + * @param preserveInitState + */ + public static final void moveAllGLEventListener(final GLAutoDrawable src, final GLAutoDrawable dest, final boolean preserveInitState) { + for(int count = src.getGLEventListenerCount(); 0<count; count--) { + final GLEventListener listener = src.getGLEventListener(0); + moveGLEventListener(src, dest, listener, preserveInitState); + } + } + + /** + * Return a heuristic value whether switching the {@link GLContext} is safe between {@lin GLAutoDrawable}s, + * i.e. via {@link #swapGLContext(GLAutoDrawable, GLAutoDrawable)} or {@link #swapGLContextAndAllGLEventListener(GLAutoDrawable, GLAutoDrawable)}. + * <p> + * Method currently returns <code>false</code> if: + * <ul> + * <li>Switching between on- and offscreen and one of the following is <code>true</code>: + * <ul> + * <li>{@link GLCapabilitiesImmutable#getSampleBuffers() MSAA is <i>used</i>} [1] in <code>chosenCapsA</code> or <code>chosenCapsB</code></li> + * <li>{@link GLCapabilitiesImmutable#getStereo() Stereo is <i>used</i>} in <code>chosenCapsA</code> or <code>chosenCapsB</code></li> + * <li>{@link GLCapabilitiesImmutable#getAccumAlphaBits() Accumulator Buffer is <i>requested</i>} [2] in <code>requestedCaps</code></li> + * </ul></li> + * </ul> + * Otherwise method returns <code>true</code> + * </p> + * <pre> + * [1] See Bug 830: swapGLContextAndAllGLEventListener and onscreen MSAA w/ NV/GLX + * On NVidia GPUs w/ it's proprietary driver context swapping does not work if MSAA is involved + * and when swapping on- to offscreen. + * </pre> + * <pre> + * [2] On AMD GPUs w/ it's proprietary driver, requesting an accumulator buffer leads to receive an accumulator buffer configuration, + * for which context swapping does not work when swapping on- to offscreen and vice-versa, i.e. cannot make context current. + * With AMD and Mesa drivers we only receive an accumulator buffer if requested, + * where on NVidia drivers all configurations contain the accumulator buffer. + * On both drivers, NVidia and Mesa, context swapping with accumulator buffer works. + * </pre> + * @param requestedCaps requested {@link GLCapabilitiesImmutable} which are intended for usage by both {@link GLAutoDrawable}s A and B + * @param chosenCapsA chosen {@link GLCapabilitiesImmutable} of {@link GLAutoDrawable} A, which {@link GLContext} is intended to be swapped + * @param chosenCapsB chosen {@link GLCapabilitiesImmutable} of {@link GLAutoDrawable} B, which {@link GLContext} is intended to be swapped + * @see #swapGLContext(GLAutoDrawable, GLAutoDrawable) + * @see #swapGLContextAndAllGLEventListener(GLAutoDrawable, GLAutoDrawable) + */ + public static boolean isSwapGLContextSafe(final GLCapabilitiesImmutable requestedCaps, final GLCapabilitiesImmutable chosenCapsA, final GLCapabilitiesImmutable chosenCapsB) { + final boolean usingAccumulatorBuffer = requestedCaps.getAccumAlphaBits() > 0 || + requestedCaps.getAccumRedBits() > 0 || + requestedCaps.getAccumGreenBits() > 0 || + requestedCaps.getAccumBlueBits() > 0; + if( ( chosenCapsA.isOnscreen() && !chosenCapsB.isOnscreen() || !chosenCapsA.isOnscreen() && chosenCapsB.isOnscreen() ) && // switching between on- and offscreen + ( + ( chosenCapsA.getSampleBuffers() || chosenCapsB.getSampleBuffers() ) || // MSAA involved + ( chosenCapsA.getStereo() || chosenCapsB.getStereo() ) || // Stereo involved + usingAccumulatorBuffer // Using accumulator buffer + ) + ) + { + return false; + } else { + return true; + } + } + /** + * Swaps the {@link GLContext} and all {@link GLEventListener} between {@link GLAutoDrawable} <code>a</code> and <code>b</code>, + * while preserving it's initialized state, resets the GL-Viewport and issuing {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)}. + * <p> + * The {@link GLAutoDrawable} to {@link GLAnimatorControl} association + * is also swapped. + * </p> + * <p> + * If an {@link GLAnimatorControl} is being attached to {@link GLAutoDrawable} <code>a</code> or <code>b</code> + * and the current thread is different than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. + * </p> + * <p> + * During operation, both {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces} are locked, + * hence atomicity of operation is guaranteed, + * see <a href="../../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>. + * </p> + * <p> + * Because of above mentioned locking, if this method is not performed + * on {@link GLAutoDrawable#isThreadGLCapable() a OpenGL capable thread} of <i>both</i> + * {@link GLAutoDrawable}s, it must be invoked on such an OpenGL capable thread, + * e.g. via {@link Threading#invokeOnOpenGLThread(boolean, Runnable)}. + * </p> + * @throws GLException if the {@link AbstractGraphicsDevice} are incompatible w/ each other. + * @see #isSwapGLContextSafe(GLCapabilitiesImmutable, GLCapabilitiesImmutable, GLCapabilitiesImmutable) + */ + public static final void swapGLContextAndAllGLEventListener(final GLAutoDrawable a, final GLAutoDrawable b) { + final GLEventListenerState gllsA = GLEventListenerState.moveFrom(a, true); + final GLEventListenerState gllsB = GLEventListenerState.moveFrom(b, true); + final Runnable gllsAUnlockOp = gllsA.getUnlockSurfaceOp(); + final Runnable gllsBUnlockOp = gllsB.getUnlockSurfaceOp(); + try { + gllsA.moveTo(b, gllsBUnlockOp); + gllsB.moveTo(a, gllsAUnlockOp); + } finally { + // guarantee unlock in case of an exception + gllsBUnlockOp.run(); + gllsAUnlockOp.run(); + } + } + + /** + * Swaps the {@link GLContext} of given {@link GLAutoDrawable} + * and {@link GLAutoDrawable#disposeGLEventListener(GLEventListener, boolean) disposes} + * each {@link GLEventListener} w/o removing it. + * <p> + * The GL-Viewport is reset and {@link GLEventListener#reshape(GLAutoDrawable, int, int, int, int) reshape(..)} issued implicit. + * </p> + * <p> + * If an {@link GLAnimatorControl} is being attached to GLAutoDrawable src or dest and the current thread is different + * than {@link GLAnimatorControl#getThread() the animator's thread}, it is paused during the operation. + * </p> + * <p> + * During operation, both {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces} are locked, + * hence atomicity of operation is guaranteed, + * see <a href="../../../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>. + * </p> + * <p> + * Because of above mentioned locking, if this method is not performed + * on {@link GLAutoDrawable#isThreadGLCapable() a OpenGL capable thread} of <i>both</i> + * {@link GLAutoDrawable}s, it must be invoked on such an OpenGL capable thread, + * e.g. via {@link Threading#invokeOnOpenGLThread(boolean, Runnable)}. + * </p> + * @param a + * @param b + * @see #isSwapGLContextSafe(GLCapabilitiesImmutable, GLCapabilitiesImmutable, GLCapabilitiesImmutable) + */ + public static final void swapGLContext(final GLAutoDrawable a, final GLAutoDrawable b) { + final GLAnimatorControl aAnim = a.getAnimator(); + final GLAnimatorControl bAnim = b.getAnimator(); + final boolean aIsPaused = isAnimatorAnimatingOnOtherThread(aAnim) && aAnim.pause(); + final boolean bIsPaused = isAnimatorAnimatingOnOtherThread(bAnim) && bAnim.pause(); + + final RecursiveLock aUpstreamLock = a.getUpstreamLock(); + final RecursiveLock bUpstreamLock = b.getUpstreamLock(); + aUpstreamLock.lock(); + bUpstreamLock.lock(); + try { + final NativeSurface aSurface = a.getNativeSurface(); + final boolean aSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < aSurface.lockSurface(); + if( a.isRealized() && !aSurfaceLocked ) { + throw new GLException("Could not lock realized a surface "+a); + } + final NativeSurface bSurface = b.getNativeSurface(); + final boolean bSurfaceLocked = NativeSurface.LOCK_SURFACE_NOT_READY < bSurface.lockSurface(); + if( b.isRealized() && !bSurfaceLocked ) { + throw new GLException("Could not lock realized b surface "+b); + } + try { + for(int i = a.getGLEventListenerCount() - 1; 0 <= i; i--) { + a.disposeGLEventListener(a.getGLEventListener(i), false); + } + for(int i = b.getGLEventListenerCount() - 1; 0 <= i; i--) { + b.disposeGLEventListener(b.getGLEventListener(i), false); + } + b.setContext( a.setContext( b.getContext(), false ), false ); + + } finally { + if( bSurfaceLocked ) { + bSurface.unlockSurface(); + } + if( aSurfaceLocked ) { + aSurface.unlockSurface(); + } + } + } finally { + bUpstreamLock.unlock(); + aUpstreamLock.unlock(); + } + a.invoke(true, setViewport); + b.invoke(true, setViewport); + if(aIsPaused) { aAnim.resume(); } + if(bIsPaused) { bAnim.resume(); } + } + + private static final GLRunnable setViewport = new GLRunnable() { + @Override + public boolean run(final GLAutoDrawable drawable) { + drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); + return false; // issue re-display w/ new viewport! + } + }; + + /** + * Determines whether the chosen {@link GLCapabilitiesImmutable} + * requires a {@link GLDrawable#swapBuffers() swap-buffers} + * before reading pixels. + * <p> + * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer} + * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen <b>after</b> calling reading pixels, the default. + * </p> + * <p> + * However, <i>multisampling</i> offscreen {@link javax.media.opengl.GLFBODrawable}s + * utilize {@link GLDrawable#swapBuffers() swap-buffers} to <i>downsample</i> + * the multisamples into the readable sampling sink. + * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} <b>before</b> reading pixels. + * </p> + * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers() + */ + public static final boolean swapBuffersBeforeRead(final GLCapabilitiesImmutable chosenCaps) { + return chosenCaps.isFBO() && chosenCaps.getSampleBuffers(); } - dest.setContext( src.setContext( dest.getContext(), false ), false ); - - src.invoke(true, GLEventListenerState.setViewport); - dest.invoke(true, GLEventListenerState.setViewport); - - if(aIsPaused) { aAnim.resume(); } - if(bIsPaused) { bAnim.resume(); } - } - - /** - * Determines whether the chosen {@link GLCapabilitiesImmutable} - * requires a {@link GLDrawable#swapBuffers() swap-buffers} - * before reading pixels. - * <p> - * Usually one uses the {@link GLBase#getDefaultReadBuffer() default-read-buffer} - * in which case {@link GLDrawable#swapBuffers() swap-buffers} shall happen <b>after</b> calling reading pixels, the default. - * </p> - * <p> - * However, <i>multisampling</i> offscreen {@link javax.media.opengl.GLFBODrawable}s - * utilize {@link GLDrawable#swapBuffers() swap-buffers} to <i>downsample</i> - * the multisamples into the readable sampling sink. - * In this case, we require {@link GLDrawable#swapBuffers() swap-buffers} <b>before</b> reading pixels. - * </p> - * @return chosenCaps.isFBO() && chosenCaps.getSampleBuffers() - */ - public static final boolean swapBuffersBeforeRead(final GLCapabilitiesImmutable chosenCaps) { - return chosenCaps.isFBO() && chosenCaps.getSampleBuffers(); - } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/GLPixelStorageModes.java b/src/jogl/classes/com/jogamp/opengl/util/GLPixelStorageModes.java index 52612d224..290033e99 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/GLPixelStorageModes.java +++ b/src/jogl/classes/com/jogamp/opengl/util/GLPixelStorageModes.java @@ -33,6 +33,7 @@ import javax.media.opengl.GL2; import javax.media.opengl.GL2ES2; import javax.media.opengl.GL2ES3; import javax.media.opengl.GL2GL3; +import javax.media.opengl.GLContext; import javax.media.opengl.GLException; /** @@ -60,7 +61,7 @@ public class GLPixelStorageModes { /** * Sets the {@link GL#GL_PACK_ALIGNMENT}. * <p> - * Saves the PACK pixel storage modes if not saved yet, see {@link #savePack(GL)}. + * Saves the PACK pixel storage modes and {@link #resetPack(GL) resets} them if not saved yet, see {@link #savePack(GL)}. * </p> */ public final void setPackAlignment(final GL gl, final int packAlignment) { @@ -71,7 +72,7 @@ public class GLPixelStorageModes { /** * Sets the {@link GL#GL_UNPACK_ALIGNMENT}. * <p> - * Saves the UNPACK pixel storage modes if not saved yet, see {@link #saveUnpack(GL)}. + * Saves the UNPACK pixel storage modes and {@link #resetUnpack(GL) resets} them if not saved yet, see {@link #saveUnpack(GL)}. * </p> */ public final void setUnpackAlignment(final GL gl, final int unpackAlignment) { @@ -82,7 +83,7 @@ public class GLPixelStorageModes { /** * Sets the {@link GL#GL_PACK_ALIGNMENT} and {@link GL#GL_UNPACK_ALIGNMENT}. * <p> - * Saves the PACK and UNPACK pixel storage modes if not saved yet, see {@link #saveAll(GL)}. + * Saves the PACK and UNPACK pixel storage modes and resets them if not saved yet, see {@link #saveAll(GL)}. * </p> */ public final void setAlignment(final GL gl, final int packAlignment, final int unpackAlignment) { @@ -93,7 +94,7 @@ public class GLPixelStorageModes { /** * Sets the {@link GL2ES3#GL_PACK_ROW_LENGTH}. * <p> - * Saves the PACK pixel storage modes if not saved yet, see {@link #savePack(GL)}. + * Saves the PACK pixel storage modes and {@link #resetPack(GL) resets} them if not saved yet, see {@link #savePack(GL)}. * </p> */ public final void setPackRowLength(final GL2ES3 gl, final int packRowLength) { @@ -104,18 +105,18 @@ public class GLPixelStorageModes { /** * Sets the {@link GL2ES2#GL_UNPACK_ROW_LENGTH}. * <p> - * Saves the UNPACK pixel storage modes if not saved yet, see {@link #saveUnpack(GL)}. + * Saves the UNPACK pixel storage modes and {@link #resetUnpack(GL) resets} them if not saved yet, see {@link #saveUnpack(GL)}. * </p> */ - public final void setUnpackRowLength(final GL2ES2 gl, final int unpackRowLength) { + public final void setUnpackRowLength(final GL2ES3 gl, final int unpackRowLength) { saveUnpack(gl); gl.glPixelStorei(GL2ES2.GL_UNPACK_ROW_LENGTH, unpackRowLength); } /** - * Sets the {@link GL2ES3#GL_PACK_ROW_LENGTH} and {@link GL2ES2#GL_UNPACK_ROW_LENGTH}. + * Sets the {@link GL2ES3#GL_PACK_ROW_LENGTH} and {@link GL2ES2#GL_UNPACK_ROW_LENGTH} if {@link GL#isGL2ES3()}. * <p> - * Saves the PACK and UNPACK pixel storage modes if not saved yet, see {@link #saveAll(GL)}. + * Saves the PACK and UNPACK pixel storage modes and resets them if not saved yet, see {@link #saveAll(GL)}. * </p> */ public final void setRowLength(final GL2ES3 gl, final int packRowLength, final int unpackRowLength) { @@ -171,7 +172,7 @@ public class GLPixelStorageModes { */ public final void resetPack(final GL gl) { // Compared w/ ES2, ES3 and GL3-core spec - gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 4); // es2, es3, gl3 + gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 4); // es2, es3, gl3 if( gl.isGL2ES3() ) { gl.glPixelStorei(GL2ES3.GL_PACK_ROW_LENGTH, 0); // es3, gl3 gl.glPixelStorei(GL2ES3.GL_PACK_SKIP_ROWS, 0); // es3, gl3 @@ -179,8 +180,10 @@ public class GLPixelStorageModes { if( gl.isGL2GL3() ) { gl.glPixelStorei(GL2GL3.GL_PACK_SWAP_BYTES, GL.GL_FALSE); // gl3 gl.glPixelStorei(GL2GL3.GL_PACK_LSB_FIRST, GL.GL_FALSE); // gl3 - gl.glPixelStorei(GL2GL3.GL_PACK_IMAGE_HEIGHT, 0); // gl3 - gl.glPixelStorei(GL2GL3.GL_PACK_SKIP_IMAGES, 0); // gl3 + if( gl.getContext().getGLVersionNumber().compareTo(GLContext.Version120) >= 0 ) { + gl.glPixelStorei(GL2GL3.GL_PACK_IMAGE_HEIGHT, 0); // gl3, GL_VERSION_1_2 + gl.glPixelStorei(GL2GL3.GL_PACK_SKIP_IMAGES, 0); // gl3, GL_VERSION_1_2 + } } } } @@ -242,16 +245,21 @@ public class GLPixelStorageModes { */ public final void resetUnpack(final GL gl) { // Compared w/ ES2, ES3 and GL3-core spec - gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 4); // es2, es3, gl3 + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 4); // es2, es3, gl3 if( gl.isGL2ES3() ) { gl.glPixelStorei(GL2ES2.GL_UNPACK_ROW_LENGTH, 0); // es3, gl3 gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_ROWS, 0); // es3, gl3 gl.glPixelStorei(GL2ES2.GL_UNPACK_SKIP_PIXELS, 0); // es3, gl3 - gl.glPixelStorei(GL2ES3.GL_UNPACK_IMAGE_HEIGHT, 0); // es3, gl3 - gl.glPixelStorei(GL2ES3.GL_UNPACK_SKIP_IMAGES, 0); // es3, gl3 if( gl.isGL2GL3() ) { + if( gl.getContext().getGLVersionNumber().compareTo(GLContext.Version120) >= 0 ) { + gl.glPixelStorei(GL2ES3.GL_UNPACK_IMAGE_HEIGHT, 0); // es3, gl3, GL_VERSION_1_2 + gl.glPixelStorei(GL2ES3.GL_UNPACK_SKIP_IMAGES, 0); // es3, gl3, GL_VERSION_1_2 + } gl.glPixelStorei(GL2GL3.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE); // gl3 gl.glPixelStorei(GL2GL3.GL_UNPACK_LSB_FIRST, GL.GL_FALSE); // gl3 + } else { + gl.glPixelStorei(GL2ES3.GL_UNPACK_IMAGE_HEIGHT, 0); // es3, gl3, GL_VERSION_1_2 + gl.glPixelStorei(GL2ES3.GL_UNPACK_SKIP_IMAGES, 0); // es3, gl3, GL_VERSION_1_2 } } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java b/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java index abdb7c5f9..e84a1d874 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/util/GLReadBufferUtil.java @@ -223,7 +223,7 @@ public class GLReadBufferUtil { psm.setPackAlignment(gl, alignment); if(gl.isGL2ES3()) { final GL2ES3 gl2es3 = gl.getGL2ES3(); - gl2es3.glPixelStorei(GL2ES3.GL_PACK_ROW_LENGTH, width); + psm.setPackRowLength(gl2es3, width); gl2es3.glReadBuffer(gl2es3.getDefaultReadBuffer()); } readPixelBuffer.clear(); diff --git a/src/jogl/classes/com/jogamp/opengl/util/Gamma.java b/src/jogl/classes/com/jogamp/opengl/util/Gamma.java index 4e44c997a..bab85e531 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/Gamma.java +++ b/src/jogl/classes/com/jogamp/opengl/util/Gamma.java @@ -39,69 +39,79 @@ package com.jogamp.opengl.util; -import javax.media.opengl.*; -import jogamp.opengl.*; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLDrawable; +import javax.media.opengl.GLDrawableFactory; -/** Provides control over the primary display's gamma, brightness and - contrast controls via the hardware gamma ramp tables. Not - supported on all platforms or graphics hardware. <P> - - Thanks to the LWJGL project for illustrating how to access gamma - control on the various platforms. -*/ +import com.jogamp.common.util.locks.RecursiveLock; +/** + * Provides convenient wrapper for {@link GLDrawableFactory} control over + * individual display's gamma, brightness and contrast values + * via the hardware gamma ramp tables. + * <p> + * Not supported on all platforms or graphics hardware. + * </p> + * <p> + * Thanks to the LWJGL project for illustrating how to access gamma + * control on the various platforms. + * </p> + */ public class Gamma { private Gamma() {} /** - * Sets the gamma, brightness, and contrast of the current main - * display. This functionality is not available on all platforms and - * graphics hardware. Returns true if the settings were successfully - * changed, false if not. This method may return false for some - * values of the incoming arguments even on hardware which does - * support the underlying functionality. <P> - * - * If this method returns true, the display settings will - * automatically be reset to their original values upon JVM exit - * (assuming the JVM does not crash); if the user wishes to change - * the display settings back to normal ahead of time, use {@link - * #resetDisplayGamma resetDisplayGamma}(). It is recommended to - * call {@link #resetDisplayGamma resetDisplayGamma} before calling - * e.g. <code>System.exit()</code> from the application rather than - * rely on the shutdown hook functionality due to inevitable race - * conditions and unspecified behavior during JVM teardown. <P> - * - * This method may be called multiple times during the application's - * execution, but calling {@link #resetDisplayGamma - * resetDisplayGamma} will only reset the settings to the values - * before the first call to this method. <P> - * - * @param gamma The gamma value, typically > 1.0 (default values - * vary, but typically roughly 1.0) - * @param brightness The brightness value between -1.0 and 1.0, - * inclusive (default values vary, but typically 0) - * @param contrast The contrast, greater than 0.0 (default values - * vary, but typically 1) - * @return true if gamma settings were successfully changed, false - * if not - * @throws IllegalArgumentException if any of the parameters were - * out-of-bounds + * Convenient wrapper for {@link GLDrawableFactory#setDisplayGamma(javax.media.nativewindow.NativeSurface, float, float, float)}. + * <p> + * Use {@link #setDisplayGamma(GLAutoDrawable, float, float, float)} in case of using an {#link GLAutoDrawable}. + * </p> + */ + public static boolean setDisplayGamma(final GLDrawable drawable, final float gamma, final float brightness, final float contrast) throws IllegalArgumentException { + return GLDrawableFactory.getFactory(drawable.getGLProfile()).setDisplayGamma(drawable.getNativeSurface(), gamma, brightness, contrast); + } + + /** + * Convenient wrapper for {@link GLDrawableFactory#setDisplayGamma(javax.media.nativewindow.NativeSurface, float, float, float)} + * locking {@link GLAutoDrawable#getUpstreamLock()} to ensure proper atomic operation. + */ + public static boolean setDisplayGamma(final GLAutoDrawable drawable, final float gamma, final float brightness, final float contrast) throws IllegalArgumentException { + final RecursiveLock lock = drawable.getUpstreamLock(); + lock.lock(); + try { + return GLDrawableFactory.getFactory(drawable.getGLProfile()).setDisplayGamma(drawable.getNativeSurface(), gamma, brightness, contrast); + } finally { + lock.unlock(); + } + } + + /** + * Convenient wrapper for {@link GLDrawableFactory#resetDisplayGamma(javax.media.nativewindow.NativeSurface)}. + * <p> + * Use {@link #resetDisplayGamma(GLAutoDrawable)} in case of using an {#link GLAutoDrawable}. + * </p> + */ + public static void resetDisplayGamma(final GLDrawable drawable) { + GLDrawableFactory.getFactory(drawable.getGLProfile()).resetDisplayGamma(drawable.getNativeSurface()); + } + + /** + * Convenient wrapper for {@link GLDrawableFactory#resetDisplayGamma(javax.media.nativewindow.NativeSurface)} + * locking {@link GLAutoDrawable#getUpstreamLock()} to ensure proper atomic operation. */ - public static boolean setDisplayGamma(final GL gl, final float gamma, final float brightness, final float contrast) throws IllegalArgumentException { - return GLDrawableFactoryImpl.getFactoryImpl(gl.getContext().getGLDrawable().getGLProfile()).setDisplayGamma(gamma, brightness, contrast); + public static void resetDisplayGamma(final GLAutoDrawable drawable) { + final RecursiveLock lock = drawable.getUpstreamLock(); + lock.lock(); + try { + GLDrawableFactory.getFactory(drawable.getGLProfile()).resetDisplayGamma(drawable.getNativeSurface()); + } finally { + lock.unlock(); + } } /** - * Resets the gamma, brightness and contrast values for the primary - * display to their original values before {@link #setDisplayGamma - * setDisplayGamma} was called the first time. {@link - * #setDisplayGamma setDisplayGamma} must be called before calling - * this method or an unspecified exception will be thrown. While it - * is not explicitly required that this method be called before - * exiting, calling it is recommended because of the inevitable - * unspecified behavior during JVM teardown. + * Convenient wrapper for {@link GLDrawableFactory#resetAllDisplayGamma()}. */ - public static void resetDisplayGamma(final GL gl) { - GLDrawableFactoryImpl.getFactoryImpl(gl.getContext().getGLDrawable().getGLProfile()).resetDisplayGamma(); + public static void resetAllDisplayGamma(final GLDrawable drawable) { + GLDrawableFactory.getFactory(drawable.getGLProfile()).resetAllDisplayGamma(); } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java index 4fffd9fab..2ad102235 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java +++ b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java @@ -27,14 +27,13 @@ */ package com.jogamp.opengl.util.av; -import java.net.URI; - import javax.media.opengl.GL; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; import jogamp.opengl.Debug; +import com.jogamp.common.net.Uri; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureSequence; import com.jogamp.opengl.util.TimeFrameI; @@ -46,18 +45,18 @@ import com.jogamp.opengl.util.TimeFrameI; * Audio maybe supported and played back internally or via an {@link AudioSink} implementation. * </p> * <p> - * Audio and video streams can be selected or muted via {@link #initStream(URI, int, int, int)} + * Audio and video streams can be selected or muted via {@link #initStream(Uri, int, int, int)} * using the appropriate <a href="#streamIDs">stream id</a>'s. * </p> * <p> - * Camera input can be selected using the {@link #CameraInputScheme} URI. + * Camera input can be selected using the {@link #CameraInputScheme} Uri. * </p> * * <a name="streamworker"><h5><i>StreamWorker</i> Decoding Thread</h5></a> * <p> * Most of the stream processing is performed on the decoding thread, a.k.a. <i>StreamWorker</i>: * <ul> - * <li>Stream initialization triggered by {@link #initStream(URI, int, int, int) initStream(..)} - User gets notified whether the stream has been initialized or not via {@link GLMediaEventListener#attributesChanged(GLMediaPlayer, int, long) attributesChanges(..)}.</li> + * <li>Stream initialization triggered by {@link #initStream(Uri, int, int, int) initStream(..)} - User gets notified whether the stream has been initialized or not via {@link GLMediaEventListener#attributesChanged(GLMediaPlayer, int, long) attributesChanges(..)}.</li> * <li>Stream decoding - User gets notified of a new frame via {@link GLMediaEventListener#newFrameAvailable(GLMediaPlayer, com.jogamp.opengl.util.texture.TextureSequence.TextureFrame, long) newFrameAvailable(...)}.</li> * <li>Caught <a href="#streamerror">exceptions on the decoding thread</a> are delivered as {@link StreamException}s.</li> * </ul> @@ -83,7 +82,7 @@ import com.jogamp.opengl.util.TimeFrameI; * <p> * <table border="1"> * <tr><th>Action</th> <th>{@link State} Before</th> <th>{@link State} After</th> <th>{@link GLMediaEventListener Event}</th></tr> - * <tr><td>{@link #initStream(URI, int, int, int)}</td> <td>{@link State#Uninitialized Uninitialized}</td> <td>{@link State#Initialized Initialized}<sup><a href="#streamworker">1</a></sup>, {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_INIT EVENT_CHANGE_INIT} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> + * <tr><td>{@link #initStream(Uri, int, int, int)}</td> <td>{@link State#Uninitialized Uninitialized}</td> <td>{@link State#Initialized Initialized}<sup><a href="#streamworker">1</a></sup>, {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_INIT EVENT_CHANGE_INIT} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> * <tr><td>{@link #initGL(GL)}</td> <td>{@link State#Initialized Initialized}</td> <td>{@link State#Paused Paused}, , {@link State#Uninitialized Uninitialized}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE} or ( {@link GLMediaEventListener#EVENT_CHANGE_ERR EVENT_CHANGE_ERR} + {@link GLMediaEventListener#EVENT_CHANGE_UNINIT EVENT_CHANGE_UNINIT} )</td></tr> * <tr><td>{@link #play()}</td> <td>{@link State#Paused Paused}</td> <td>{@link State#Playing Playing}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PLAY EVENT_CHANGE_PLAY}</td></tr> * <tr><td>{@link #pause(boolean)}</td> <td>{@link State#Playing Playing}</td> <td>{@link State#Paused Paused}</td> <td>{@link GLMediaEventListener#EVENT_CHANGE_PAUSE EVENT_CHANGE_PAUSE}</td></tr> @@ -183,6 +182,9 @@ import com.jogamp.opengl.util.TimeFrameI; * <!-- <tr><td> title </td><td colspan=3> url1 </td><td> url2 </td></tr> * </table> * </p> + * <p> + * Since 2.3.0 this interface uses {@link Uri} instead of {@link java.net.URI}. + * </p> */ public interface GLMediaPlayer extends TextureSequence { public static final boolean DEBUG = Debug.debug("GLMediaPlayer"); @@ -200,10 +202,10 @@ public interface GLMediaPlayer extends TextureSequence { public static final int STREAM_ID_AUTO = -1; /** - * {@link URI#getScheme() URI scheme} name {@value} for camera input. E.g. <code>camera:/0</code> + * {@link Uri#scheme Uri scheme} name {@value} for camera input. E.g. <code>camera:/0</code> * for the 1st camera device. * <p> - * The {@link URI#getRawPath() URI path} is being used to identify the camera (<i>ID</i>), + * The {@link Uri#path Uri path} is being used to identify the camera (<i>ID</i>), * where the root fwd-slash is being cut-off. * </p> * <p> @@ -211,7 +213,7 @@ public interface GLMediaPlayer extends TextureSequence { * ranging from [0..<i>max-number</i>]. * </p> * <p> - * The {@link URI#getRawQuery() URI query} is used to pass options to the camera + * The {@link Uri#query Uri query} is used to pass options to the camera * using <i>;</i> as the separator. The latter avoids trouble w/ escaping. * </p> * <pre> @@ -221,13 +223,13 @@ public interface GLMediaPlayer extends TextureSequence { * camera://somewhere/<id>?size=640x480;rate=15 * </pre> * <pre> - * URI: [scheme:][//authority][path][?query][#fragment] + * Uri: [scheme:][//authority][path][?query][#fragment] * w/ authority: [user-info@]host[:port] * Note: 'path' starts w/ fwd slash * </pre> * </p> */ - public static final String CameraInputScheme = "camera"; + public static final Uri.Encoded CameraInputScheme = Uri.Encoded.cast("camera"); /** Camera property {@value}, size as string, e.g. <code>1280x720</code>, <code>hd720</code>. May not be supported on all platforms. See {@link #CameraInputScheme}. */ public static final String CameraPropSizeS = "size"; /** Camera property {@value}. See {@link #CameraInputScheme}. */ @@ -361,8 +363,9 @@ public interface GLMediaPlayer extends TextureSequence { * Ignored if video is muted. * @throws IllegalStateException if not invoked in {@link State#Uninitialized} * @throws IllegalArgumentException if arguments are invalid + * @since 2.3.0 */ - public void initStream(URI streamLoc, int vid, int aid, int textureCount) throws IllegalStateException, IllegalArgumentException; + public void initStream(Uri streamLoc, int vid, int aid, int textureCount) throws IllegalStateException, IllegalArgumentException; /** * Returns the {@link StreamException} caught in the decoder thread, or <code>null</code> if none occured. @@ -379,7 +382,7 @@ public interface GLMediaPlayer extends TextureSequence { * <p> * <a href="#lifecycle">Lifecycle</a>: {@link State#Initialized} -> {@link State#Paused} or {@link State#Initialized} * </p> - * Argument <code>gl</code> is ignored if video is muted, see {@link #initStream(URI, int, int, int)}. + * Argument <code>gl</code> is ignored if video is muted, see {@link #initStream(Uri, int, int, int)}. * * @param gl current GL object. Maybe <code>null</code>, for audio only. * @throws IllegalStateException if not invoked in {@link State#Initialized}. @@ -391,7 +394,7 @@ public interface GLMediaPlayer extends TextureSequence { /** * If implementation uses a {@link AudioSink}, it's instance will be returned. * <p> - * The {@link AudioSink} instance is available after {@link #initStream(URI, int, int, int)}, + * The {@link AudioSink} instance is available after {@link #initStream(Uri, int, int, int)}, * if used by implementation. * </p> */ @@ -536,8 +539,11 @@ public interface GLMediaPlayer extends TextureSequence { @Override public TextureSequence.TextureFrame getNextTexture(GL gl) throws IllegalStateException; - /** Return the stream location, as set by {@link #initStream(URI, int, int, int)}. */ - public URI getURI(); + /** + * Return the stream location, as set by {@link #initStream(Uri, int, int, int)}. + * @since 2.3.0 + */ + public Uri getUri(); /** * <i>Warning:</i> Optional information, may not be supported by implementation. diff --git a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java index 29dce40f5..8eed35ebb 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java +++ b/src/jogl/classes/com/jogamp/opengl/util/glsl/ShaderCode.java @@ -35,6 +35,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.StringReader; +import java.net.URISyntaxException; import java.net.URLConnection; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -46,12 +47,14 @@ import java.util.Set; import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GL3; +import javax.media.opengl.GL4; import javax.media.opengl.GLES2; import javax.media.opengl.GLContext; import javax.media.opengl.GLException; import jogamp.opengl.Debug; +import com.jogamp.common.net.Uri; import com.jogamp.common.nio.Buffers; import com.jogamp.common.util.IOUtil; import com.jogamp.common.util.VersionNumber; @@ -63,33 +66,62 @@ import com.jogamp.common.util.VersionNumber; * {@link #create(GL2ES2, int, Class, String, String, String, boolean) here} and * {@link #create(GL2ES2, int, int, Class, String, String[], String, String) here}. * </p> + * <p> + * Support for {@link GL4#GL_TESS_CONTROL_SHADER} and {@link GL4#GL_TESS_EVALUATION_SHADER} + * was added since 2.2.1. + * </p> */ public class ShaderCode { public static final boolean DEBUG_CODE = Debug.isPropertyDefined("jogl.debug.GLSLCode", true); - /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in source code: <code>vp</code> */ + /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in source code: <code>{@value}</code> */ public static final String SUFFIX_VERTEX_SOURCE = "vp" ; - /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in binary: <code>bvp</code> */ + /** Unique resource suffix for {@link GL2ES2#GL_VERTEX_SHADER} in binary: <code>{@value}</code> */ public static final String SUFFIX_VERTEX_BINARY = "bvp" ; - /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in source code: <code>gp</code> */ + /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in source code: <code>{@value}</code> */ public static final String SUFFIX_GEOMETRY_SOURCE = "gp" ; - /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in binary: <code>bgp</code> */ + /** Unique resource suffix for {@link GL3#GL_GEOMETRY_SHADER} in binary: <code>{@value}</code> */ public static final String SUFFIX_GEOMETRY_BINARY = "bgp" ; - /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in source code: <code>fp</code> */ + /** + * Unique resource suffix for {@link GL4#GL_TESS_CONTROL_SHADER} in source code: <code>{@value}</code> + * @since 2.2.1 + */ + public static final String SUFFIX_TESS_CONTROL_SOURCE = "tcp" ; + + /** + * Unique resource suffix for {@link GL4#GL_TESS_CONTROL_SHADER} in binary: <code>{@value}</code> + * @since 2.2.1 + */ + public static final String SUFFIX_TESS_CONTROL_BINARY = "btcp" ; + + /** + * Unique resource suffix for {@link GL4#GL_TESS_EVALUATION_SHADER} in source code: <code>{@value}</code> + * @since 2.2.1 + */ + public static final String SUFFIX_TESS_EVALUATION_SOURCE = "tep" ; + + /** + * Unique resource suffix for {@link GL4#GL_TESS_EVALUATION_SHADER} in binary: <code>{@value}</code> + * @since 2.2.1 + */ + public static final String SUFFIX_TESS_EVALUATION_BINARY = "btep" ; + + /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in source code: <code>{@value}</code> */ public static final String SUFFIX_FRAGMENT_SOURCE = "fp" ; - /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in binary: <code>bfp</code> */ + /** Unique resource suffix for {@link GL2ES2#GL_FRAGMENT_SHADER} in binary: <code>{@value}</code> */ public static final String SUFFIX_FRAGMENT_BINARY = "bfp" ; - /** Unique relative path for binary shader resources for {@link GLES2#GL_NVIDIA_PLATFORM_BINARY_NV NVIDIA}: <code>nvidia</code> */ + /** Unique relative path for binary shader resources for {@link GLES2#GL_NVIDIA_PLATFORM_BINARY_NV NVIDIA}: <code>{@value}</code> */ public static final String SUB_PATH_NVIDIA = "nvidia" ; /** - * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} + * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, + * {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_SHADER}. * @param count number of shaders * @param source CharSequence array containing the shader sources, organized as <code>source[count][strings-per-shader]</code>. * May be either an immutable <code>String</code> - or mutable <code>StringBuilder</code> array. @@ -104,6 +136,8 @@ public class ShaderCode { case GL2ES2.GL_VERTEX_SHADER: case GL2ES2.GL_FRAGMENT_SHADER: case GL3.GL_GEOMETRY_SHADER: + case GL4.GL_TESS_CONTROL_SHADER: + case GL4.GL_TESS_EVALUATION_SHADER: break; default: throw new GLException("Unknown shader type: "+type); @@ -122,7 +156,8 @@ public class ShaderCode { } /** - * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} + * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, + * {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_SHADER}. * @param count number of shaders * @param binary binary buffer containing the shader binaries, */ @@ -131,6 +166,8 @@ public class ShaderCode { case GL2ES2.GL_VERTEX_SHADER: case GL2ES2.GL_FRAGMENT_SHADER: case GL3.GL_GEOMETRY_SHADER: + case GL4.GL_TESS_CONTROL_SHADER: + case GL4.GL_TESS_EVALUATION_SHADER: break; default: throw new GLException("Unknown shader type: "+type); @@ -148,7 +185,8 @@ public class ShaderCode { * which location is resolved using the <code>context</code> class, see {@link #readShaderSource(Class, String)}. * * @param gl current GL object to determine whether a shader compiler is available. If null, no validation is performed. - * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} + * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, + * {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_SHADER}. * @param count number of shaders * @param context class used to help resolving the source location * @param sourceFiles array of source locations, organized as <code>sourceFiles[count]</code> @@ -192,7 +230,8 @@ public class ShaderCode { * Creates a complete {@link ShaderCode} object while reading the shader binary of <code>binaryFile</code>, * which location is resolved using the <code>context</code> class, see {@link #readShaderBinary(Class, String)}. * - * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} + * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, + * {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_SHADER}. * @param count number of shaders * @param context class used to help resolving the source location * @param binFormat a valid native binary format as they can be queried by {@link ShaderUtil#getShaderBinaryFormats(GL)}. @@ -225,14 +264,21 @@ public class ShaderCode { * <li>Source<ul> * <li>{@link GL2ES2#GL_VERTEX_SHADER vertex}: {@link #SUFFIX_VERTEX_SOURCE}</li> * <li>{@link GL2ES2#GL_FRAGMENT_SHADER fragment}: {@link #SUFFIX_FRAGMENT_SOURCE}</li> - * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_SOURCE}</li></ul></li> + * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_SOURCE}</li> + * <li>{@link GL4#GL_TESS_CONTROL_SHADER tess-ctrl}: {@link #SUFFIX_TESS_CONTROL_SOURCE}</li> + * <li>{@link GL4#GL_TESS_EVALUATION_SHADER tess-eval}: {@link #SUFFIX_TESS_EVALUATION_SOURCE}</li> + * </ul></li> * <li>Binary<ul> * <li>{@link GL2ES2#GL_VERTEX_SHADER vertex}: {@link #SUFFIX_VERTEX_BINARY}</li> * <li>{@link GL2ES2#GL_FRAGMENT_SHADER fragment}: {@link #SUFFIX_FRAGMENT_BINARY}</li> - * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_BINARY}</li></ul></li> + * <li>{@link GL3#GL_GEOMETRY_SHADER geometry}: {@link #SUFFIX_GEOMETRY_BINARY}</li> + * <li>{@link GL4#GL_TESS_CONTROL_SHADER tess-ctrl}: {@link #SUFFIX_TESS_CONTROL_BINARY}</li> + * <li>{@link GL4#GL_TESS_EVALUATION_SHADER tess-eval}: {@link #SUFFIX_TESS_EVALUATION_BINARY}</li> + * </ul></li> * </ul> * @param binary true for a binary resource, false for a source resource - * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} + * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, + * {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_SHADER}. * * @throws GLException if <code>type</code> is not supported * @@ -246,6 +292,10 @@ public class ShaderCode { return binary?SUFFIX_FRAGMENT_BINARY:SUFFIX_FRAGMENT_SOURCE; case GL3.GL_GEOMETRY_SHADER: return binary?SUFFIX_GEOMETRY_BINARY:SUFFIX_GEOMETRY_SOURCE; + case GL4.GL_TESS_CONTROL_SHADER: + return binary?SUFFIX_TESS_CONTROL_BINARY:SUFFIX_TESS_CONTROL_SOURCE; + case GL4.GL_TESS_EVALUATION_SHADER: + return binary?SUFFIX_TESS_EVALUATION_BINARY:SUFFIX_TESS_EVALUATION_SOURCE; default: throw new GLException("illegal shader type: "+type); } @@ -324,7 +374,8 @@ public class ShaderCode { * * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used), * or to determine the shader binary format (if <code>binary</code> is used). - * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} + * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, + * {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_SHADER}. * @param count number of shaders * @param context class used to help resolving the source and binary location * @param srcRoot relative <i>root</i> path for <code>srcBasenames</code> optional @@ -430,7 +481,8 @@ public class ShaderCode { * * @param gl current GL object to determine whether a shader compiler is available (if <code>source</code> is used), * or to determine the shader binary format (if <code>binary</code> is used). - * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER} or {@link GL3#GL_GEOMETRY_SHADER} + * @param type either {@link GL2ES2#GL_VERTEX_SHADER}, {@link GL2ES2#GL_FRAGMENT_SHADER}, {@link GL3#GL_GEOMETRY_SHADER}, + * {@link GL4#GL_TESS_CONTROL_SHADER} or {@link GL4#GL_TESS_EVALUATION_SHADER}. * @param context class used to help resolving the source and binary location * @param srcRoot relative <i>root</i> path for <code>basename</code> optional * @param binRoot relative <i>root</i> path for <code>basename</code> @@ -468,6 +520,10 @@ public class ShaderCode { return "FRAGMENT_SHADER"; case GL3.GL_GEOMETRY_SHADER: return "GEOMETRY_SHADER"; + case GL4.GL_TESS_CONTROL_SHADER: + return "TESS_CONTROL_SHADER"; + case GL4.GL_TESS_EVALUATION_SHADER: + return "TESS_EVALUATION_SHADER"; } return "UNKNOWN_SHADER"; } @@ -778,7 +834,8 @@ public class ShaderCode { URLConnection nextConn = null; // Try relative of current shader location - nextConn = IOUtil.openURL(IOUtil.getRelativeOf(conn.getURL(), includeFile), "ShaderCode.relativeOf "); + final Uri relUri = Uri.valueOf( conn.getURL() ).getRelativeOf(new Uri.Encoded( includeFile, Uri.PATH_LEGAL )); + nextConn = IOUtil.openURL(relUri.toURL(), "ShaderCode.relativeOf "); if (nextConn == null) { // Try relative of class and absolute nextConn = IOUtil.getResource(context, includeFile); @@ -792,6 +849,8 @@ public class ShaderCode { result.append(line + "\n"); } } + } catch (final URISyntaxException e) { + throw new IOException(e); } finally { IOUtil.close(reader, false); } @@ -874,8 +933,12 @@ public class ShaderCode { /** Default precision of {@link GL#isGLES3() ES3} for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}: {@value #es3_default_precision_vp} */ public static final String es3_default_precision_vp = es2_default_precision_vp; - /** Default precision of {@link GL#isGLES3() ES3} for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #es3_default_precision_fp} */ - public static final String es3_default_precision_fp = es2_default_precision_fp; + /** + * Default precision of {@link GL#isGLES3() ES3} for {@link GL2ES2#GL_FRAGMENT_SHADER fragment-shader}: {@value #es3_default_precision_fp}, + * same as for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader}, i.e {@link #es3_default_precision_vp}, + * due to ES 3.x requirements of using same precision for uniforms! + */ + public static final String es3_default_precision_fp = es3_default_precision_vp; /** Default precision of GLSL ≥ 1.30 as required until < 1.50 for {@link GL2ES2#GL_VERTEX_SHADER vertex-shader} or {@link GL3#GL_GEOMETRY_SHADER geometry-shader}: {@value #gl3_default_precision_vp_gp}. See GLSL Spec 1.30-1.50 Section 4.5.3. */ public static final String gl3_default_precision_vp_gp = "\nprecision highp float;\nprecision highp int;\n"; @@ -956,6 +1019,8 @@ public class ShaderCode { switch ( shaderType ) { case GL2ES2.GL_VERTEX_SHADER: case GL3.GL_GEOMETRY_SHADER: + case GL4.GL_TESS_CONTROL_SHADER: + case GL4.GL_TESS_EVALUATION_SHADER: defaultPrecision = gl3_default_precision_vp_gp; break; case GL2ES2.GL_FRAGMENT_SHADER: defaultPrecision = gl3_default_precision_fp; break; diff --git a/src/jogl/classes/com/jogamp/opengl/util/glsl/sdk/CompileShader.java b/src/jogl/classes/com/jogamp/opengl/util/glsl/sdk/CompileShader.java index f113be2b2..1d629131e 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/glsl/sdk/CompileShader.java +++ b/src/jogl/classes/com/jogamp/opengl/util/glsl/sdk/CompileShader.java @@ -109,8 +109,7 @@ public abstract class CompileShader { tmpFile.getAbsolutePath(), outputFile.getAbsolutePath() }); // , null, processorDir); - new StreamMonitor(process.getInputStream()); - new StreamMonitor(process.getErrorStream()); + new IOUtil.StreamMonitor( new InputStream[] { process.getInputStream(), process.getErrorStream() }, System.out, null ); process.waitFor(); // Delete the temporary file // tmpFile.delete(); @@ -153,35 +152,4 @@ public abstract class CompileShader { e.printStackTrace(); } } - - private static class StreamMonitor implements Runnable { - private final InputStream istream; - public StreamMonitor(final InputStream stream) { - istream = stream; - new Thread(this, "Output Reader Thread").start(); - } - - @Override - public void run() - { - final byte[] buffer = new byte[4096]; - try { - int numRead = 0; - do { - numRead = istream.read(buffer); - if (numRead > 0) { - System.out.write(buffer, 0, numRead); - System.out.flush(); - } - } while (numRead >= 0); - } - catch (final IOException e) { - try { - istream.close(); - } catch (final IOException e2) { - } - // Should allow clean exit when process shuts down - } - } - } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoClientRenderer.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoClientRenderer.java index 0801b65fa..f70ebf928 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoClientRenderer.java +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoClientRenderer.java @@ -87,8 +87,7 @@ public class StereoClientRenderer implements GLEventListener { private void initFBOs(final GL gl, final DimensionImmutable size) { for(int i=0; i<fbos.length; i++) { - fbos[i].detachAllColorbuffer(gl); - fbos[i].reset(gl, size.getWidth(), size.getHeight(), numSamples, false); + fbos[i].init(gl, size.getWidth(), size.getHeight(), numSamples); if( i>0 && fbos[i-1].getNumSamples() != fbos[i].getNumSamples()) { throw new InternalError("sample size mismatch: \n\t0: "+fbos[i-1]+"\n\t1: "+fbos[i]); } @@ -96,19 +95,19 @@ public class StereoClientRenderer implements GLEventListener { if(numSamples>0) { fbos[i].attachColorbuffer(gl, 0, true); // MSAA requires alpha - fbos[i].attachRenderbuffer(gl, Type.DEPTH, 24); + fbos[i].attachRenderbuffer(gl, Type.DEPTH, FBObject.DEFAULT_BITS); final FBObject ssink = new FBObject(); { - ssink.reset(gl, size.getWidth(), size.getHeight()); + ssink.init(gl, size.getWidth(), size.getHeight(), 0); ssink.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, FBObject.DEFAULT_BITS); } fbos[i].setSamplingSink(ssink); fbos[i].resetSamplingSink(gl); // validate - fboTexs[i] = fbos[i].getSamplingSink(); + fboTexs[i] = fbos[i].getSamplingSink().getTextureAttachment(); } else { fboTexs[i] = fbos[i].attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - fbos[i].attachRenderbuffer(gl, Type.DEPTH, 24); + fbos[i].attachRenderbuffer(gl, Type.DEPTH, FBObject.DEFAULT_BITS); } fbos[i].unbind(gl); System.err.println("FBO["+i+"]: "+fbos[i]); @@ -119,15 +118,15 @@ public class StereoClientRenderer implements GLEventListener { @SuppressWarnings("unused") private void resetFBOs(final GL gl, final DimensionImmutable size) { for(int i=0; i<fbos.length; i++) { - fbos[i].reset(gl, size.getWidth(), size.getHeight(), numSamples, true); + fbos[i].reset(gl, size.getWidth(), size.getHeight(), numSamples); if( i>0 && fbos[i-1].getNumSamples() != fbos[i].getNumSamples()) { throw new InternalError("sample size mismatch: \n\t0: "+fbos[i-1]+"\n\t1: "+fbos[i]); } numSamples = fbos[i].getNumSamples(); if(numSamples>0) { - fboTexs[i] = fbos[i].getSamplingSink(); + fboTexs[i] = fbos[i].getSamplingSink().getTextureAttachment(); } else { - fboTexs[i] = (TextureAttachment) fbos[i].getColorbuffer(0); + fboTexs[i] = fbos[i].getColorbuffer(0).getTextureAttachment(); } } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDevice.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDevice.java index 2091d0843..d32c981a3 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDevice.java +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDevice.java @@ -46,6 +46,9 @@ public interface StereoDevice { // NOP } + /** Return the factory used to create this device. */ + public StereoDeviceFactory getFactory(); + /** Disposes this {@link StereoDevice}. */ public void dispose(); diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceFactory.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceFactory.java index 46ce82f03..c4180585c 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceFactory.java +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoDeviceFactory.java @@ -45,7 +45,25 @@ public abstract class StereoDeviceFactory { private static final String GenericStereoDeviceClazzName = "jogamp.opengl.util.stereo.GenericStereoDeviceFactory"; private static final String isAvailableMethodName = "isAvailable"; - public static enum DeviceType { Default, Generic, OculusVR }; + /** {@link StereoDevice} type used for {@link StereoDeviceFactory#createFactory(DeviceType) createFactory(type)}. */ + public static enum DeviceType { + /** + * Auto selection of device in the following order: + * <ol> + * <li>{@link DeviceType#OculusVR}</li> + * <li>{@link DeviceType#Generic}</li> + * </ol> + */ + Default, + /** + * Generic software implementation. + */ + Generic, + /** + * OculusVR implementation. + */ + OculusVR + }; public static StereoDeviceFactory createDefaultFactory() { final ClassLoader cl = StereoDeviceFactory.class.getClassLoader(); diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java index 19f2fc05b..6011afe7b 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java +++ b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java @@ -60,7 +60,6 @@ import javax.media.nativewindow.util.DimensionImmutable; import javax.media.nativewindow.util.PixelFormat; import javax.media.opengl.GL; import javax.media.opengl.GL2; -import javax.media.opengl.GL2ES3; import javax.media.opengl.GL2GL3; import javax.media.opengl.GLContext; import javax.media.opengl.GLException; @@ -69,6 +68,7 @@ import javax.media.opengl.GLProfile; import jogamp.opengl.Debug; import com.jogamp.common.util.IOUtil; +import com.jogamp.opengl.util.GLPixelStorageModes; import com.jogamp.opengl.util.PNGPixelRect; import com.jogamp.opengl.util.GLPixelBuffer.GLPixelAttributes; import com.jogamp.opengl.util.texture.spi.DDSImage; @@ -575,6 +575,10 @@ public class TextureIO { * when it is written to disk, regardless of whether the underlying * file format supports multiple mipmaps in a given file. * + * <p> + * Method required a {@link GL2GL3} {@link GLProfile#GL2GL3 profile}. + * </p> + * * @throws IOException if an error occurred during writing or no * suitable writer was found * @throws GLException if no OpenGL context was current or an @@ -590,7 +594,7 @@ public class TextureIO { if (!_gl.isGL2GL3()) { throw new GLException("Implementation only supports GL2GL3 (Use GLReadBufferUtil and the TextureData variant), have: " + _gl); } - final GL2GL3 gl = _gl.getGL2(); + final GL2GL3 gl = _gl.getGL2GL3(); texture.bind(gl); final int internalFormat = glGetTexLevelParameteri(gl, GL.GL_TEXTURE_2D, 0, GL2GL3.GL_TEXTURE_INTERNAL_FORMAT); @@ -630,17 +634,8 @@ public class TextureIO { } // Fetch using glGetTexImage - final int packAlignment = glGetInteger(gl, GL.GL_PACK_ALIGNMENT); - final int packRowLength = glGetInteger(gl, GL2ES3.GL_PACK_ROW_LENGTH); - final int packSkipRows = glGetInteger(gl, GL2ES3.GL_PACK_SKIP_ROWS); - final int packSkipPixels = glGetInteger(gl, GL2ES3.GL_PACK_SKIP_PIXELS); - final int packSwapBytes = glGetInteger(gl, GL2GL3.GL_PACK_SWAP_BYTES); - - gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1); - gl.glPixelStorei(GL2ES3.GL_PACK_ROW_LENGTH, 0); - gl.glPixelStorei(GL2ES3.GL_PACK_SKIP_ROWS, 0); - gl.glPixelStorei(GL2ES3.GL_PACK_SKIP_PIXELS, 0); - gl.glPixelStorei(GL2GL3.GL_PACK_SWAP_BYTES, 0); + final GLPixelStorageModes psm = new GLPixelStorageModes(); + psm.setPackAlignment(gl, 1); final ByteBuffer res = ByteBuffer.allocate((width + (2 * border)) * (height + (2 * border)) * @@ -651,11 +646,7 @@ public class TextureIO { } gl.glGetTexImage(GL.GL_TEXTURE_2D, 0, fetchedFormat, GL.GL_UNSIGNED_BYTE, res); - gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, packAlignment); - gl.glPixelStorei(GL2ES3.GL_PACK_ROW_LENGTH, packRowLength); - gl.glPixelStorei(GL2ES3.GL_PACK_SKIP_ROWS, packSkipRows); - gl.glPixelStorei(GL2ES3.GL_PACK_SKIP_PIXELS, packSkipPixels); - gl.glPixelStorei(GL2GL3.GL_PACK_SWAP_BYTES, packSwapBytes); + psm.restore(gl); data = new TextureData(gl.getGLProfile(), internalFormat, width, height, border, fetchedFormat, GL.GL_UNSIGNED_BYTE, false, false, false, res, null); @@ -1404,12 +1395,6 @@ public class TextureIO { // Helper routines // - private static int glGetInteger(final GL gl, final int pname) { - final int[] tmp = new int[1]; - gl.glGetIntegerv(pname, tmp, 0); - return tmp[0]; - } - private static int glGetTexLevelParameteri(final GL2GL3 gl, final int target, final int level, final int pname) { final int[] tmp = new int[1]; gl.glGetTexLevelParameteriv(target, 0, pname, tmp, 0); |