diff options
Diffstat (limited to 'src/jogl/classes')
70 files changed, 4657 insertions, 2346 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); diff --git a/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java b/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java index 827145654..d14ada48b 100644 --- a/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java +++ b/src/jogl/classes/javax/media/opengl/GLAnimatorControl.java @@ -33,6 +33,38 @@ package javax.media.opengl; * which implementation may drive a {@link javax.media.opengl.GLAutoDrawable} animation. */ public interface GLAnimatorControl extends FPSCounter { + /** + * A {@link GLAnimatorControl#setUncaughtExceptionHandler(UncaughtExceptionHandler) registered} + * {@link UncaughtExceptionHandler} instance is invoked when an {@link GLAnimatorControl animator} abruptly {@link #stop() stops} + * due to an uncaught exception from one of its {@link GLAutoDrawable}s. + * @see #uncaughtException(GLAnimatorControl, GLAutoDrawable, Throwable) + * @see GLAnimatorControl#setUncaughtExceptionHandler(UncaughtExceptionHandler) + * @since 2.2 + */ + public static interface UncaughtExceptionHandler { + /** + * Method invoked when the given {@link GLAnimatorControl} is {@link GLAnimatorControl#stop() stopped} due to the + * given uncaught exception happened on the given {@link GLAutoDrawable}. + * <p> + * The animator thread can still be retrieved via {@link GLAnimatorControl#getThread()}. + * </p> + * <p> + * All {@link GLAnimatorControl} states already reflect its stopped state. + * </p> + * <p> + * After this handler method is called, the {@link GLAnimatorControl} is stopped. + * </p> + * <p> + * Any exception thrown by this method will be ignored. + * </p> + * @param animator the {@link GLAnimatorControl} + * @param drawable the causing {@link GLAutoDrawable} + * @param cause the uncaught exception + * @see GLAnimatorControl#setUncaughtExceptionHandler(UncaughtExceptionHandler) + * @since 2.2 + */ + void uncaughtException(final GLAnimatorControl animator, final GLAutoDrawable drawable, final Throwable cause); + } /** * Indicates whether this animator has been {@link #start() started}. @@ -181,4 +213,24 @@ public interface GLAnimatorControl extends FPSCounter { * @throws IllegalArgumentException if drawable was not added to this animator */ void remove(GLAutoDrawable drawable); + + /** + * Returns the {@link UncaughtExceptionHandler} invoked when this {@link GLAnimatorControl animator} abruptly {@link #stop() stops} + * due to an uncaught exception from one of its {@link GLAutoDrawable}s. + * <p> + * Default is <code>null</code>. + * </p> + * @since 2.2 + */ + UncaughtExceptionHandler getUncaughtExceptionHandler(); + + /** + * Set the handler invoked when this {@link GLAnimatorControl animator} abruptly {@link #stop() stops} + * due to an uncaught exception from one of its {@link GLAutoDrawable}s. + * @param handler the {@link UncaughtExceptionHandler} to use as this {@link GLAnimatorControl animator}'s uncaught exception + * handler. Pass <code>null</code> to unset the handler. + * @see UncaughtExceptionHandler#uncaughtException(GLAnimatorControl, GLAutoDrawable, Throwable) + * @since 2.2 + */ + void setUncaughtExceptionHandler(final UncaughtExceptionHandler handler); } diff --git a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java index 377dce190..bded88d20 100644 --- a/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java +++ b/src/jogl/classes/javax/media/opengl/GLAutoDrawable.java @@ -42,6 +42,10 @@ package javax.media.opengl; import java.util.List; +import javax.media.nativewindow.NativeSurface; + +import com.jogamp.common.util.locks.RecursiveLock; + import jogamp.opengl.Debug; /** A higher-level abstraction than {@link GLDrawable} which supplies @@ -116,6 +120,28 @@ import jogamp.opengl.Debug; -Djogl.screenchange.action=true Enable the {@link GLDrawable} reconfiguration </PRE> </p> + <h5><a name="locking">GLAutoDrawable Locking</a></h5> + GLAutoDrawable implementations perform locking in the following order: + <ol> + <li> {@link #getUpstreamLock()}.{@link RecursiveLock#lock() lock()}</li> + <li> {@link #getNativeSurface()}.{@link NativeSurface#lockSurface() lockSurface()} </li> + </ol> + and releases the locks accordingly: + <ol> + <li> {@link #getNativeSurface()}.{@link NativeSurface#unlockSurface() unlockSurface()} </li> + <li> {@link #getUpstreamLock()}.{@link RecursiveLock#unlock() unlock()}</li> + </ol> + Above <i>locking order</i> is mandatory to guarantee + atomicity of operation and to avoid race-conditions. + A custom implementation or user applications requiring exclusive access + shall follow the <i>locking order</i>. + See: + <ul> + <li>{@link #getUpstreamLock()}</li> + <li>{@link #invoke(boolean, GLRunnable)}</li> + <li>{@link #invoke(boolean, List)}</li> + </ul> + </p> */ public interface GLAutoDrawable extends GLDrawable { /** Flag reflecting whether the {@link GLDrawable} reconfiguration will be issued in @@ -139,19 +165,22 @@ public interface GLAutoDrawable extends GLDrawable { /** * Associate the new context, <code>newtCtx</code>, to this auto-drawable. * <p> - * The current context will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>, - * otherwise it will be dis-associated from this auto-drawable - * via {@link GLContext#setGLDrawable(GLDrawable, boolean) setGLDrawable(null, true);} first. - * </p> - * <p> - * The new context will be associated with this auto-drawable - * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}. - * </p> - * <p> - * If the old or new context was current on this thread, it is being released before switching the association. - * The new context will be made current afterwards, if it was current before. - * However the user shall take extra care that no other thread - * attempts to make this context current. + * Remarks: + * <ul> + * <li>The currently associated context will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>, + * otherwise it will be disassociated from this auto-drawable + * via {@link GLContext#setGLDrawable(GLDrawable, boolean) setGLDrawable(null, true);} including {@link GL#glFinish() glFinish()}.</li> + * <li>The new context will be associated with this auto-drawable + * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.</li> + * <li>If the old context was current on this thread, it is being released after disassociating this auto-drawable.</li> + * <li>If the new context was current on this thread, it is being released before associating this auto-drawable + * and made current afterwards.</li> + * <li>Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.</li> + * <li>The user shall take extra care of thread synchronization, + * i.e. lock the involved {@link GLAutoDrawable auto-drawable's} + * {@link GLAutoDrawable#getUpstreamLock() upstream-locks} and {@link GLAutoDrawable#getNativeSurface() surfaces} + * to avoid a race condition. See <a href="#locking">GLAutoDrawable Locking</a>.</li> + * </ul> * </p> * * @param newCtx the new context, maybe <code>null</code> for dis-association. @@ -200,6 +229,7 @@ public interface GLAutoDrawable extends GLDrawable { /** * Returns true if all added {@link GLEventListener} are initialized, otherwise false. + * @since 2.2 */ boolean areAllGLEventListenerInitialized(); @@ -410,17 +440,28 @@ public interface GLAutoDrawable extends GLDrawable { * The internal queue of {@link GLRunnable}'s is being flushed with {@link #destroy()} * where all blocked callers are being notified. * </p> + * <p> + * To avoid a deadlock situation which causes an {@link IllegalStateException} one should + * avoid issuing {@link #invoke(boolean, GLRunnable) invoke} while this <a href="#locking">GLAutoDrawable is being locked</a>.<br> + * Detected deadlock situations throwing an {@link IllegalStateException} are: + * <ul> + * <li>{@link #getAnimator() Animator} is running on another thread and waiting and is locked on current thread, but is not {@link #isThreadGLCapable() GL-Thread}</li> + * <li>No {@link #getAnimator() Animator} is running on another thread and is locked on current thread, but is not {@link #isThreadGLCapable() GL-Thread}</li> + * </ul> + * </p> * * @param wait if <code>true</code> block until execution of <code>glRunnable</code> is finished, otherwise return immediately w/o waiting * @param glRunnable the {@link GLRunnable} to execute within {@link #display()} * @return <code>true</code> if the {@link GLRunnable} has been processed or queued, otherwise <code>false</code>. + * @throws IllegalStateException in case of a detected deadlock situation ahead, see above. * * @see #setAnimator(GLAnimatorControl) * @see #display() * @see GLRunnable * @see #invoke(boolean, List) + * @see #flushGLRunnables() */ - public boolean invoke(boolean wait, GLRunnable glRunnable); + public boolean invoke(boolean wait, GLRunnable glRunnable) throws IllegalStateException ; /** * Extends {@link #invoke(boolean, GLRunnable)} functionality @@ -428,9 +469,23 @@ public interface GLAutoDrawable extends GLDrawable { * @param wait if <code>true</code> block until execution of the last <code>glRunnable</code> is finished, otherwise return immediately w/o waiting * @param glRunnables the {@link GLRunnable}s to execute within {@link #display()} * @return <code>true</code> if the {@link GLRunnable}s has been processed or queued, otherwise <code>false</code>. + * @throws IllegalStateException in case of a detected deadlock situation ahead, see {@link #invoke(boolean, GLRunnable)}. * @see #invoke(boolean, GLRunnable) + * @see #flushGLRunnables() */ - public boolean invoke(boolean wait, List<GLRunnable> glRunnables); + public boolean invoke(boolean wait, List<GLRunnable> glRunnables) throws IllegalStateException; + + /** + * Flushes all {@link #invoke(boolean, GLRunnable) enqueued} {@link GLRunnable} of this {@link GLAutoDrawable} + * including notifying waiting executor. + * <p> + * The executor which might have been blocked until notified + * will be unblocked and all tasks removed from the queue. + * </p> + * @see #invoke(boolean, GLRunnable) + * @since 2.2 + */ + public void flushGLRunnables(); /** Destroys all resources associated with this GLAutoDrawable, inclusive the GLContext. @@ -556,4 +611,26 @@ public interface GLAutoDrawable extends GLDrawable { */ public Object getUpstreamWidget(); + /** + * Returns the recursive lock object of the {@link #getUpstreamWidget() upstream widget} + * to synchronize multithreaded access on top of {@link NativeSurface#lockSurface()}. + * <p> + * See <a href="#locking">GLAutoDrawable Locking</a>. + * </p> + * @since 2.2 + */ + public RecursiveLock getUpstreamLock(); + + /** + * Indicates whether the current thread is capable of + * performing OpenGL-related work. + * <p> + * Implementation utilizes this knowledge to determine + * whether {@link #display()} performs the OpenGL commands on the current thread directly + * or spawns them on the dedicated OpenGL thread. + * </p> + * @since 2.2 + */ + public boolean isThreadGLCapable(); + } diff --git a/src/jogl/classes/javax/media/opengl/GLContext.java b/src/jogl/classes/javax/media/opengl/GLContext.java index d5d8792d8..e2498e6f1 100644 --- a/src/jogl/classes/javax/media/opengl/GLContext.java +++ b/src/jogl/classes/javax/media/opengl/GLContext.java @@ -48,6 +48,7 @@ import java.util.List; import java.util.Set; import javax.media.nativewindow.AbstractGraphicsDevice; +import javax.media.nativewindow.NativeSurface; import jogamp.opengl.Debug; import jogamp.opengl.GLContextImpl; @@ -107,6 +108,7 @@ public abstract class GLContext { protected static final boolean FORCE_NO_FBO_SUPPORT = Debug.isPropertyDefined("jogl.fbo.force.none", true); protected static final boolean FORCE_MIN_FBO_SUPPORT = Debug.isPropertyDefined("jogl.fbo.force.min", true); + protected static final boolean FORCE_NO_COLOR_RENDERBUFFER = Debug.isPropertyDefined("jogl.fbo.force.nocolorrenderbuffer", true); /** Reflects property jogl.debug.DebugGL. If true, the debug pipeline is enabled at context creation. */ public static final boolean DEBUG_GL = Debug.isPropertyDefined("jogl.debug.DebugGL", true); @@ -147,6 +149,8 @@ public abstract class GLContext { protected static final VersionNumber Version800 = new VersionNumber(8, 0, 0); + private static final String S_EMPTY = ""; + // // Cached keys, bits [0..15] // @@ -204,7 +208,7 @@ public abstract class GLContext { private final HashMap<String, Object> attachedObjects = new HashMap<String, Object>(); // RecursiveLock maintains a queue of waiting Threads, ensuring the longest waiting thread will be notified at unlock. - protected final RecursiveLock lock = LockFactory.createRecursiveLock(); + protected final RecursiveLock lock = LockFactory.createRecursiveLock(); // FIXME: Move to GLContextImpl when incr. minor version (incompatible change) /** The underlying native OpenGL context */ protected volatile long contextHandle; // volatile: avoid locking for read-only access @@ -249,6 +253,17 @@ public abstract class GLContext { return GLContextShareSet.isShared(this); } + /** + * Returns the shared master GLContext of this GLContext if shared, otherwise return <code>null</code>. + * <p> + * Returns this GLContext, if it is a shared master. + * </p> + * @since 2.2.1 + */ + public final GLContext getSharedMaster() { + return GLContextShareSet.getSharedMaster(this); + } + /** Returns a new list of created GLContext shared with this GLContext. */ public final List<GLContext> getCreatedShares() { return GLContextShareSet.getCreatedShares(this); @@ -282,15 +297,24 @@ public abstract class GLContext { } /** - * Sets the read/write drawable for framebuffer operations. + * Sets the read/write drawable for framebuffer operations, i.e. reassociation of the context's drawable. * <p> * If the arguments reflect the current state of this context * this method is a no-operation and returns the old and current {@link GLDrawable}. * </p> * <p> - * If the context was current on this thread, it is being released before switching the drawable - * and made current afterwards. However the user shall take extra care that not other thread - * attempts to make this context current. Otherwise a race condition may happen. + * Remarks: + * <ul> + * <li>{@link GL#glFinish() glFinish()} is issued if context {@link #isCreated()} and a {@link #getGLDrawable() previous drawable} was bound before disassociation.</li> + * <li>If the context was current on this thread, it is being released before drawable reassociation + * and made current afterwards.</li> + * <li>Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.</li> + * <li>The user shall take extra care of thread synchronization, + * i.e. lock the involved {@link GLDrawable#getNativeSurface() drawable's} {@link NativeSurface}s + * to avoid a race condition. In case {@link GLAutoDrawable auto-drawable's} are used, + * their {@link GLAutoDrawable#getUpstreamLock() upstream-lock} must be locked beforehand + * see <a href="GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li> + * </ul> * </p> * @param readWrite The read/write drawable for framebuffer operations, maybe <code>null</code> to remove association. * @param setWriteOnly Only change the write-drawable, if <code>setWriteOnly</code> is <code>true</code> and @@ -316,6 +340,9 @@ public abstract class GLContext { * If the read-drawable has not been changed manually via {@link #setGLReadDrawable(GLDrawable)}, * it equals to the write-drawable (default). * </p> + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> * @see #setGLDrawable(GLDrawable, boolean) * @see #setGLReadDrawable(GLDrawable) */ @@ -354,6 +381,9 @@ public abstract class GLContext { * If the read-drawable has not been changed manually via {@link #setGLReadDrawable(GLDrawable)}, * it equals to the write-drawable (default). * </p> + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> * @see #isGLReadDrawableAvailable() * @see #setGLReadDrawable(GLDrawable) * @see #getGLReadDrawable() @@ -792,8 +822,8 @@ public abstract class GLContext { * <pre> * #version 110 * .. - * #version 150 - * #version 330 + * #version 150 core + * #version 330 compatibility * ... * </pre> * And for ES: @@ -809,11 +839,20 @@ public abstract class GLContext { */ public final String getGLSLVersionString() { if( ctxGLSLVersion.isZero() ) { - return ""; + return S_EMPTY; } final int minor = ctxGLSLVersion.getMinor(); - final String esSuffix = isGLES() && ctxGLSLVersion.compareTo(Version300) >= 0 ? " es" : ""; - return "#version " + ctxGLSLVersion.getMajor() + ( minor < 10 ? "0"+minor : minor ) + esSuffix + "\n" ; + final String profileOpt; + if( isGLES() ) { + profileOpt = ctxGLSLVersion.compareTo(Version300) >= 0 ? " es" : S_EMPTY; + } else if( isGLCoreProfile() ) { + profileOpt = ctxGLSLVersion.compareTo(Version150) >= 0 ? " core" : S_EMPTY; + } else if( isGLCompatibilityProfile() ) { + profileOpt = ctxGLSLVersion.compareTo(Version150) >= 0 ? " compatibility" : S_EMPTY; + } else { + throw new InternalError("Neither ES, Core nor Compat: "+this); // see validateProfileBits(..) + } + return "#version " + ctxGLSLVersion.getMajor() + ( minor < 10 ? "0"+minor : minor ) + profileOpt + "\n" ; } protected static final VersionNumber getStaticGLSLVersionNumber(final int glMajorVersion, final int glMinorVersion, final int ctxOptions) { @@ -966,8 +1005,7 @@ public abstract class GLContext { * @see GLProfile#isGL4bc() */ public final boolean isGL4bc() { - return 0 != (ctxOptions & CTX_IS_ARB_CREATED) && - 0 != (ctxOptions & CTX_PROFILE_COMPAT) && + return 0 != (ctxOptions & CTX_PROFILE_COMPAT) && ctxVersion.getMajor() >= 4; } @@ -976,8 +1014,7 @@ public abstract class GLContext { * @see GLProfile#isGL4() */ public final boolean isGL4() { - return 0 != (ctxOptions & CTX_IS_ARB_CREATED) && - 0 != (ctxOptions & (CTX_PROFILE_COMPAT|CTX_PROFILE_CORE)) && + return 0 != (ctxOptions & (CTX_PROFILE_COMPAT|CTX_PROFILE_CORE)) && ctxVersion.getMajor() >= 4; } @@ -985,8 +1022,7 @@ public abstract class GLContext { * Indicates whether this GLContext uses a GL4 core profile. <p>Includes [ GL4 ].</p> */ public final boolean isGL4core() { - return 0 != ( ctxOptions & CTX_IS_ARB_CREATED ) && - 0 != ( ctxOptions & CTX_PROFILE_CORE ) && + return 0 != ( ctxOptions & CTX_PROFILE_CORE ) && ctxVersion.getMajor() >= 4; } @@ -995,8 +1031,7 @@ public abstract class GLContext { * @see GLProfile#isGL3bc() */ public final boolean isGL3bc() { - return 0 != (ctxOptions & CTX_IS_ARB_CREATED) && - 0 != (ctxOptions & CTX_PROFILE_COMPAT) && + return 0 != (ctxOptions & CTX_PROFILE_COMPAT) && ctxVersion.compareTo(Version310) >= 0 ; } @@ -1005,8 +1040,7 @@ public abstract class GLContext { * @see GLProfile#isGL3() */ public final boolean isGL3() { - return 0 != (ctxOptions & CTX_IS_ARB_CREATED) && - 0 != (ctxOptions & (CTX_PROFILE_COMPAT|CTX_PROFILE_CORE)) && + return 0 != (ctxOptions & (CTX_PROFILE_COMPAT|CTX_PROFILE_CORE)) && ctxVersion.compareTo(Version310) >= 0 ; } @@ -1014,8 +1048,7 @@ public abstract class GLContext { * Indicates whether this GLContext uses a GL3 core profile. <p>Includes [ GL4, GL3 ].</p> */ public final boolean isGL3core() { - return 0 != ( ctxOptions & CTX_IS_ARB_CREATED ) && - 0 != ( ctxOptions & CTX_PROFILE_CORE ) && + return 0 != ( ctxOptions & CTX_PROFILE_CORE ) && ctxVersion.compareTo(Version310) >= 0; } @@ -1024,8 +1057,7 @@ public abstract class GLContext { */ public final boolean isGLcore() { return ( 0 != ( ctxOptions & CTX_PROFILE_ES ) && ctxVersion.getMajor() >= 2 ) || - ( 0 != ( ctxOptions & CTX_IS_ARB_CREATED ) && - 0 != ( ctxOptions & CTX_PROFILE_CORE ) && + ( 0 != ( ctxOptions & CTX_PROFILE_CORE ) && ctxVersion.compareTo(Version310) >= 0 ) ; } @@ -1041,9 +1073,9 @@ public abstract class GLContext { } /** - * Indicates whether this GLContext's native profile does not implement a default <i>vertex array object</i> (VAO), - * starting w/ OpenGL 3.1 core and GLES3. - * <p>Includes [ GL4, GL3, GLES3 ].</p> + * Indicates whether this GLContext's native profile does not implement a <i>default vertex array object</i> (VAO), + * starting w/ OpenGL 3.1 core. + * <p>Includes [ GL4, GL3 ].</p> * <pre> Due to GL 3.1 core spec: E.1. DEPRECATED AND REMOVED FEATURES (p 296), GL 3.2 core spec: E.2. DEPRECATED AND REMOVED FEATURES (p 331) @@ -1052,8 +1084,17 @@ public abstract class GLContext { More clear is GL 4.3 core spec: 10.4 (p 307). * </pre> * <pre> - GLES3 is included, since upcoming ES releases > 3.0 may behave the same: + ES 3.x is <i>not</i> included here. + Due to it's ES 2.0 backward compatibility it still supports the following features: + <i>client side vertex arrays</i> + <i>default vertex array object</i> + + Binding a custom VAO with ES 3.0 would cause <i>client side vertex arrays</i> via {@link GL2ES1#glVertexPointer(int, int, int, java.nio.Buffer) glVertexPointer} + to produce <code>GL_INVALID_OPERATION</code>. + + However, they are marked <i>deprecated</i>: GL ES 3.0 spec F.1. Legacy Features (p 322). + GL ES 3.1 spec F.1. Legacy Features (p 454). * </pre> * <p> * If no default VAO is implemented in the native OpenGL profile, @@ -1062,7 +1103,7 @@ public abstract class GLContext { * @see #getDefaultVAO() */ public final boolean hasNoDefaultVAO() { - return ( 0 != ( ctxOptions & CTX_PROFILE_ES ) && ctxVersion.getMajor() >= 3 ) || + return // ES 3.x not included, see above. ( 0 != ( ctxOptions & CTX_PROFILE_ES ) && ctxVersion.getMajor() >= 3 ) || ( 0 != ( ctxOptions & CTX_IS_ARB_CREATED ) && 0 != ( ctxOptions & CTX_PROFILE_CORE ) && ctxVersion.compareTo(Version310) >= 0 @@ -1250,6 +1291,9 @@ public abstract class GLContext { /** * Return the framebuffer name bound to this context, * see {@link GL#glBindFramebuffer(int, int)}. + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> */ public abstract int getBoundFramebuffer(int target); @@ -1260,6 +1304,9 @@ public abstract class GLContext { * in case an framebuffer object ({@link com.jogamp.opengl.FBObject}) based drawable * is being used. * </p> + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> */ public abstract int getDefaultDrawFramebuffer(); @@ -1270,6 +1317,9 @@ public abstract class GLContext { * in case an framebuffer object ({@link com.jogamp.opengl.FBObject}) based drawable * is being used. * </p> + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> */ public abstract int getDefaultReadFramebuffer(); @@ -1294,13 +1344,26 @@ public abstract class GLContext { * Note-3: See {@link com.jogamp.opengl.util.GLDrawableUtil#swapBuffersBeforeRead(GLCapabilitiesImmutable) swapBuffersBeforeRead} * for read-pixels and swap-buffers implications. * </p> + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> */ public abstract int getDefaultReadBuffer(); - /** Get the default pixel data type, as required by e.g. {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer)}. */ + /** + * Get the default pixel data type, as required by e.g. {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer)}. + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> + */ public abstract int getDefaultPixelDataType(); - /** Get the default pixel data format, as required by e.g. {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer)}. */ + /** + * Get the default pixel data format, as required by e.g. {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer)}. + * <p> + * Method is only thread-safe while context is {@link #makeCurrent() made current}. + * </p> + */ public abstract int getDefaultPixelDataFormat(); /** diff --git a/src/jogl/classes/javax/media/opengl/GLDrawable.java b/src/jogl/classes/javax/media/opengl/GLDrawable.java index 57883c8ac..5c881ab73 100644 --- a/src/jogl/classes/javax/media/opengl/GLDrawable.java +++ b/src/jogl/classes/javax/media/opengl/GLDrawable.java @@ -60,8 +60,9 @@ public interface GLDrawable extends NativeSurfaceHolder { * The GLContext <code>share</code> need not be associated with this * GLDrawable and may be null if sharing of display lists and other * objects is not desired. See the note in the overview - * documentation on - * <a href="../../../spec-overview.html#SHARING">context sharing</a>. + * documentation + * <a href="../../../overview-summary.html#SHARING">context sharing</a> + * as well as {@link GLSharedContextSetter}. * </p> */ public GLContext createContext(GLContext shareWith); @@ -174,17 +175,37 @@ public interface GLDrawable extends NativeSurfaceHolder { public void swapBuffers() throws GLException; /** Fetches the {@link GLCapabilitiesImmutable} corresponding to the chosen - OpenGL capabilities (pixel format / visual / GLProfile) for this drawable.<br> + OpenGL capabilities (pixel format / visual / GLProfile) for this drawable. + <p> + This query only returns the chosen capabilities if {@link #isRealized()}. + </p> + <p> On some platforms, the pixel format is not directly associated with the drawable; a best attempt is made to return a reasonable - value in this case. <br> + value in this case. + </p> + <p> This object shall be directly associated to the attached {@link NativeSurface}'s {@link AbstractGraphicsConfiguration}, and if changes are necessary, they should reflect those as well. + </p> @return The immutable queried instance. + @see #getRequestedGLCapabilities() */ public GLCapabilitiesImmutable getChosenGLCapabilities(); + /** Fetches the {@link GLCapabilitiesImmutable} corresponding to the user requested + OpenGL capabilities (pixel format / visual / GLProfile) for this drawable. + <p> + If {@link #isRealized() realized}, {@link #getChosenGLCapabilities() the chosen capabilities} + reflect the actual selected OpenGL capabilities. + </p> + @return The immutable queried instance. + @see #getChosenGLCapabilities() + @since 2.2 + */ + public GLCapabilitiesImmutable getRequestedGLCapabilities(); + /** Fetches the {@link GLProfile} for this drawable. Returns the GLProfile object, no copy. */ diff --git a/src/jogl/classes/javax/media/opengl/GLDrawableFactory.java b/src/jogl/classes/javax/media/opengl/GLDrawableFactory.java index 82808a3cb..71568ee76 100644 --- a/src/jogl/classes/javax/media/opengl/GLDrawableFactory.java +++ b/src/jogl/classes/javax/media/opengl/GLDrawableFactory.java @@ -98,23 +98,6 @@ public abstract class GLDrawableFactory { protected static final boolean DEBUG = Debug.debug("GLDrawable"); - /** - * We have to disable support for ANGLE, the D3D ES2 emulation on Windows provided w/ Firefox and Chrome. - * When run in the mentioned browsers, the eglInitialize(..) implementation crashes. - * <p> - * This can be overridden by explicitly enabling ANGLE on Windows by setting the property - * <code>jogl.enable.ANGLE</code>. - * </p> - */ - protected static final boolean enableANGLE = Debug.isPropertyDefined("jogl.enable.ANGLE", true); - - /** - * In case no OpenGL ES implementation is required - * and if the running platform may have a buggy implementation, - * setting the property <code>jogl.disable.opengles</code> disables querying a possible existing OpenGL ES implementation. - */ - protected static final boolean disableOpenGLES = Debug.isPropertyDefined("jogl.disable.opengles", true); - private static volatile boolean isInit = false; private static GLDrawableFactory eglFactory; private static GLDrawableFactory nativeOSFactory; @@ -179,7 +162,7 @@ public abstract class GLDrawableFactory { } tmp = null; - if(!disableOpenGLES) { + if(!GLProfile.disableOpenGLES) { try { tmp = (GLDrawableFactory) ReflectionUtil.createInstance("jogamp.opengl.egl.EGLDrawableFactory", cl); } catch (final Exception jre) { @@ -222,7 +205,7 @@ public abstract class GLDrawableFactory { System.err.println("GLDrawableFactory.shutdownAll["+(i+1)+"/"+gldfCount+"]: "+gldf.getClass().getName()); } try { - gldf.resetDisplayGamma(); + gldf.resetAllDisplayGammaNoSync(); gldf.shutdownImpl(); } catch (final Throwable t) { System.err.println("GLDrawableFactory.shutdownImpl: Caught "+t.getClass().getName()+" during factory shutdown #"+(i+1)+"/"+gldfCount+" "+gldf.getClass().getName()); @@ -259,7 +242,70 @@ public abstract class GLDrawableFactory { protected abstract void shutdownImpl(); - public abstract void resetDisplayGamma(); + /** + * Sets the gamma, brightness, and contrast of the display associated with the given <code>surface</code>. + * <p> + * 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> + * <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(NativeSurface)} or {@link #resetAllDisplayGamma()}. + * </p> + * <p> + * It is recommended to call {@link #resetDisplayGamma(NativeSurface)} or {@link #resetAllDisplayGamma()} + * 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> + * <p> + * This method may be called multiple times during the application's + * execution, but calling {@link #resetDisplayGamma(NativeSurface)} + * will only reset the settings to the values + * before the first call to this method. </p> + * + * @param surface denominates the display device + * @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 + * @see #resetDisplayGamma(NativeSurface) + * @see #resetAllDisplayGamma() + */ + public abstract boolean setDisplayGamma(final NativeSurface surface, final float gamma, final float brightness, final float contrast) throws IllegalArgumentException; + + /** + * Resets the gamma, brightness and contrast values of the display associated with the given <code>surface</code> + * to its original values before {@link #setDisplayGamma(NativeSurface, float, float, float) setDisplayGamma} + * was called the first time. + * <p> + * While it is not explicitly required that this method be called before + * exiting manually, calling it is recommended because of the inevitable + * unspecified behavior during JVM teardown. + * </p> + */ + public abstract void resetDisplayGamma(final NativeSurface surface); + + /** + * Resets the gamma, brightness and contrast values of all modified + * displays to their original values before {@link #setDisplayGamma(NativeSurface, float, float, float) setDisplayGamma} + * was called the first time. + * <p> + * While it is not explicitly required that this method be called before + * exiting manually, calling it is recommended because of the inevitable + * unspecified behavior during JVM teardown. + * </p> + */ + public abstract void resetAllDisplayGamma(); + + protected abstract void resetAllDisplayGammaNoSync(); /** * Retrieve the default <code>device</code> {@link AbstractGraphicsDevice#getConnection() connection}, diff --git a/src/jogl/classes/javax/media/opengl/GLException.java b/src/jogl/classes/javax/media/opengl/GLException.java index 6a287c969..3f76a6299 100644 --- a/src/jogl/classes/javax/media/opengl/GLException.java +++ b/src/jogl/classes/javax/media/opengl/GLException.java @@ -41,7 +41,7 @@ package javax.media.opengl; /** A generic exception for OpenGL errors used throughout the binding as a substitute for {@link RuntimeException}. */ - +@SuppressWarnings("serial") public class GLException extends RuntimeException { /** Constructs a GLException object. */ public GLException() { @@ -65,4 +65,22 @@ public class GLException extends RuntimeException { public GLException(final Throwable cause) { super(cause); } + + /** + * Constructs a GLException object with the specified root + * cause with a decorating message including the current thread name. + * @since 2.2 + */ + public static GLException newGLException(final Throwable t) { + return new GLException("Caught "+t.getClass().getSimpleName()+": "+t.getMessage()+" on thread "+Thread.currentThread().getName(), t); + } + + /** + * Dumps a Throwable in a decorating message including the current thread name, and stack trace. + * @since 2.2 + */ + public static void dumpThrowable(final String additionalDescr, final Throwable t) { + System.err.println("Caught "+additionalDescr+" "+t.getClass().getSimpleName()+": "+t.getMessage()+" on thread "+Thread.currentThread().getName()); + t.printStackTrace(); + } } diff --git a/src/jogl/classes/javax/media/opengl/GLFBODrawable.java b/src/jogl/classes/javax/media/opengl/GLFBODrawable.java index a34fca0fa..524c77e9d 100644 --- a/src/jogl/classes/javax/media/opengl/GLFBODrawable.java +++ b/src/jogl/classes/javax/media/opengl/GLFBODrawable.java @@ -31,7 +31,10 @@ package javax.media.opengl; import javax.media.nativewindow.NativeWindowException; import com.jogamp.opengl.FBObject; +import com.jogamp.opengl.FBObject.Colorbuffer; +import com.jogamp.opengl.FBObject.ColorAttachment; import com.jogamp.opengl.FBObject.TextureAttachment; +import com.jogamp.opengl.GLRendererQuirks; /** * Platform-independent {@link GLDrawable} specialization, @@ -62,12 +65,12 @@ import com.jogamp.opengl.FBObject.TextureAttachment; * </p> * <p> * It would be possible to implement double buffering simply using - * {@link TextureAttachment}s with one {@link FBObject framebuffer}. + * {@link Colorbuffer}s with one {@link FBObject framebuffer}. * This would require mode selection and hence complicate the API. Besides, it would * not support differentiation of read and write framebuffer and hence not be spec compliant. * </p> * <p> - * Actual swapping of the {@link TextureAttachment texture}s and/or {@link FBObject framebuffer} + * Actual swapping of the {@link Colorbuffer}s and/or {@link FBObject framebuffer} * is performed either in the {@link jogamp.opengl.GLContextImpl#contextMadeCurrent(boolean) context current hook} * or when {@link jogamp.opengl.GLDrawableImpl#swapBuffersImpl(boolean) swapping buffers}, whatever comes first. * </p> @@ -75,19 +78,42 @@ import com.jogamp.opengl.FBObject.TextureAttachment; public interface GLFBODrawable extends GLDrawable { // public enum DoubleBufferMode { NONE, TEXTURE, FBO }; // TODO: Add or remove TEXTURE (only) DoubleBufferMode support + /** FBO Mode Bit: Use a {@link TextureAttachment} for the {@link #getColorbuffer(int) render colorbuffer}, see {@link #setFBOMode(int)}. */ + public static final int FBOMODE_USE_TEXTURE = 1 << 0; + /** * @return <code>true</code> if initialized, i.e. a {@link GLContext} is bound and made current once, otherwise <code>false</code>. */ public boolean isInitialized(); /** + * Set the FBO mode bits used for FBO creation. + * <p> + * Default value is: {@link #FBOMODE_USE_TEXTURE}. + * </p> + * <p> + * If {@link GLRendererQuirks#BuggyColorRenderbuffer} is set, + * {@link #FBOMODE_USE_TEXTURE} is always added at initialization. + * </p> + * + * @param modeBits custom FBO mode bits like {@link #FBOMODE_USE_TEXTURE}. + * @throws IllegalStateException if already initialized, see {@link #isInitialized()}. + */ + void setFBOMode(final int modeBits) throws IllegalStateException; + + /** + * @return the used FBO mode bits, mutable via {@link #setFBOMode(int)} + */ + int getFBOMode(); + + /** * Notify this instance about upstream size change * to reconfigure the {@link FBObject}. * @param gl GL context object bound to this drawable, will be made current during operation. * A prev. current context will be make current after operation. * @throws GLException if resize operation failed */ - void resetSize(GL gl) throws GLException; + void resetSize(final GL gl) throws GLException; /** * @return the used texture unit @@ -98,7 +124,7 @@ public interface GLFBODrawable extends GLDrawable { * * @param unit the texture unit to be used */ - void setTextureUnit(int unit); + void setTextureUnit(final int unit); /** * Set the number of sample buffers if using MSAA @@ -108,7 +134,7 @@ public interface GLFBODrawable extends GLDrawable { * @param newSamples new sample size * @throws GLException if resetting the FBO failed */ - void setNumSamples(GL gl, int newSamples) throws GLException; + void setNumSamples(final GL gl, final int newSamples) throws GLException; /** * @return the number of sample buffers if using MSAA, otherwise 0 @@ -124,9 +150,9 @@ public interface GLFBODrawable extends GLDrawable { * Must be called before {@link #isInitialized() initialization}, otherwise an exception is thrown. * </p> * @return the new number of buffers (FBO) used, maybe different than the requested <code>bufferCount</code> (see above) - * @throws GLException if already initialized, see {@link #isInitialized()}. + * @throws IllegalStateException if already initialized, see {@link #isInitialized()}. */ - int setNumBuffers(int bufferCount) throws GLException; + int setNumBuffers(final int bufferCount) throws IllegalStateException, GLException; /** * @return the number of buffers (FBO) being used. 1 if not using {@link GLCapabilities#getDoubleBuffered() double buffering}, @@ -162,19 +188,25 @@ public interface GLFBODrawable extends GLDrawable { * @return the named {@link FBObject} * @throws IllegalArgumentException if an illegal buffer name is being used */ - FBObject getFBObject(int bufferName) throws IllegalArgumentException; + FBObject getFBObject(final int bufferName) throws IllegalArgumentException; /** - * Returns the named texture buffer. + * Returns the named {@link Colorbuffer} instance. * <p> * If MSAA is being used, only the {@link GL#GL_FRONT} buffer is accessible * and an exception is being thrown if {@link GL#GL_BACK} is being requested. * </p> + * <p> + * Depending on the {@link #setFBOMode(int) fbo mode} the resulting {@link Colorbuffer} + * is either a {@link TextureAttachment} if {@link #FBOMODE_USE_TEXTURE} is set, + * otherwise a {@link ColorAttachment}. + * See {@link Colorbuffer#isTextureAttachment()}. + * </p> * @param bufferName {@link GL#GL_FRONT} and {@link GL#GL_BACK} are valid buffer names - * @return the named {@link TextureAttachment} + * @return the named {@link Colorbuffer} * @throws IllegalArgumentException if using MSAA and {@link GL#GL_BACK} is requested or an illegal buffer name is being used */ - FBObject.TextureAttachment getTextureBuffer(int bufferName) throws IllegalArgumentException; + Colorbuffer getColorbuffer(final int bufferName) throws IllegalArgumentException; /** Resizeable {@link GLFBODrawable} specialization */ public interface Resizeable extends GLFBODrawable { diff --git a/src/jogl/classes/javax/media/opengl/GLOffscreenAutoDrawable.java b/src/jogl/classes/javax/media/opengl/GLOffscreenAutoDrawable.java index a69480242..62d10d4cb 100644 --- a/src/jogl/classes/javax/media/opengl/GLOffscreenAutoDrawable.java +++ b/src/jogl/classes/javax/media/opengl/GLOffscreenAutoDrawable.java @@ -39,6 +39,12 @@ import com.jogamp.opengl.FBObject; * This class distinguishes itself from {@link GLAutoDrawable} * with it's {@link #setSurfaceSize(int, int)} functionality. * </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 interface GLOffscreenAutoDrawable extends GLAutoDrawable, GLSharedContextSetter { diff --git a/src/jogl/classes/javax/media/opengl/GLProfile.java b/src/jogl/classes/javax/media/opengl/GLProfile.java index 324fdee92..08712e488 100644 --- a/src/jogl/classes/javax/media/opengl/GLProfile.java +++ b/src/jogl/classes/javax/media/opengl/GLProfile.java @@ -44,6 +44,7 @@ import jogamp.opengl.DesktopGLDynamicLookupHelper; import com.jogamp.common.GlueGenVersion; import com.jogamp.common.jvm.JNILibLoaderBase; import com.jogamp.common.os.Platform; +import com.jogamp.common.util.PropertyAccess; import com.jogamp.common.util.ReflectionUtil; import com.jogamp.common.util.VersionUtil; import com.jogamp.common.util.cache.TempJarCache; @@ -75,11 +76,70 @@ import java.util.Map; */ public class GLProfile { - public static final boolean DEBUG = Debug.debug("GLProfile"); + public static final boolean DEBUG; + + /** + * In case no OpenGL ES profiles are required + * and if one platform may have a buggy implementation, + * setting the property <code>jogl.disable.opengles</code> disables querying possible existing OpenGL ES profiles. + */ + public static final boolean disableOpenGLES; + + /** + * In case no native OpenGL core profiles are required + * and if one platform may have a buggy implementation, + * setting the property <code>jogl.disable.openglcore</code> disables querying possible existing native OpenGL core profiles. + * <p> + * This exclusion is disabled for {@link Platform.OSType#MACOS}. + * </p> + */ + public static final boolean disableOpenGLCore; + + /** + * In case the implementation of the <i>ARB_create_context</i> + * context creation extension is buggy on one platform, + * setting the property <code>jogl.disable.openglarbcontext</code> disables utilizing it. + * <p> + * This exclusion is disabled for {@link Platform.OSType#MACOS}. + * </p> + */ + public static final boolean disableOpenGLARBContext; + + /** + * We have to disable support for ANGLE, the D3D ES2 emulation on Windows provided w/ Firefox and Chrome. + * When run in the mentioned browsers, the eglInitialize(..) implementation crashes. + * <p> + * This can be overridden by explicitly enabling ANGLE on Windows by setting the property + * <code>jogl.enable.ANGLE</code>. + * </p> + */ + public static final boolean enableANGLE; static { // Also initializes TempJarCache if shall be used. Platform.initSingleton(); + final boolean isOSX = Platform.OSType.MACOS == Platform.getOSType(); + + DEBUG = Debug.debug("GLProfile"); + disableOpenGLES = PropertyAccess.isPropertyDefined("jogl.disable.opengles", true); + disableOpenGLCore = PropertyAccess.isPropertyDefined("jogl.disable.openglcore", true) && !isOSX; + disableOpenGLARBContext = PropertyAccess.isPropertyDefined("jogl.disable.openglarbcontext", true) && !isOSX; + enableANGLE = PropertyAccess.isPropertyDefined("jogl.enable.ANGLE", true); + } + + /** + * @return <code>true</code> if JOGL has been initialized, i.e. manually via {@link #initSingleton()} or implicit, + * otherwise returns <code>false</code>. + * + * @since 2.2.1 + */ + public static boolean isInitialized() { + initLock.lock(); + try { + return initialized; + } finally { + initLock.unlock(); + } } /** @@ -425,7 +485,7 @@ public class GLProfile { if(null != map) { for (final Map.Entry<String,GLProfile> entry : map.entrySet()) { - if( !GL_DEFAULT.equals(entry.getKey()) ) { + if( GL_DEFAULT != entry.getKey() ) { if(useIndent) { doIndent(sb.append(Platform.getNewline()), indent, indentCount); } @@ -468,44 +528,46 @@ public class GLProfile { /** The desktop OpenGL compatibility profile 4.x, with x >= 0, ie GL2 plus GL4.<br> <code>bc</code> stands for backward compatibility. */ - public static final String GL4bc = "GL4bc"; + public static final String GL4bc = "GL4bc"; // Implicitly intern(), see Bug 1059 /** The desktop OpenGL core profile 4.x, with x >= 0 */ - public static final String GL4 = "GL4"; + public static final String GL4 = "GL4"; // Implicitly intern(), see Bug 1059 /** The desktop OpenGL compatibility profile 3.x, with x >= 1, ie GL2 plus GL3.<br> <code>bc</code> stands for backward compatibility. */ - public static final String GL3bc = "GL3bc"; + public static final String GL3bc = "GL3bc"; // Implicitly intern(), see Bug 1059 /** The desktop OpenGL core profile 3.x, with x >= 1 */ - public static final String GL3 = "GL3"; + public static final String GL3 = "GL3"; // Implicitly intern(), see Bug 1059 /** The desktop OpenGL profile 1.x up to 3.0 */ - public static final String GL2 = "GL2"; + public static final String GL2 = "GL2"; // Implicitly intern(), see Bug 1059 /** The embedded OpenGL profile ES 1.x, with x >= 0 */ - public static final String GLES1 = "GLES1"; + public static final String GLES1 = "GLES1"; // Implicitly intern(), see Bug 1059 /** The embedded OpenGL profile ES 2.x, with x >= 0 */ - public static final String GLES2 = "GLES2"; + public static final String GLES2 = "GLES2"; // Implicitly intern(), see Bug 1059 /** The embedded OpenGL profile ES 3.x, with x >= 0 */ - public static final String GLES3 = "GLES3"; + public static final String GLES3 = "GLES3"; // Implicitly intern(), see Bug 1059 /** The intersection of the desktop GL2 and embedded ES1 profile */ - public static final String GL2ES1 = "GL2ES1"; + public static final String GL2ES1 = "GL2ES1"; // Implicitly intern(), see Bug 1059 /** The intersection of the desktop GL3, GL2 and embedded ES2 profile */ - public static final String GL2ES2 = "GL2ES2"; + public static final String GL2ES2 = "GL2ES2"; // Implicitly intern(), see Bug 1059 /** The intersection of the desktop GL3 and GL2 profile */ - public static final String GL2GL3 = "GL2GL3"; + public static final String GL2GL3 = "GL2GL3"; // Implicitly intern(), see Bug 1059 /** The intersection of the desktop GL4 and ES3 profile, available only if either ES3 or GL4 w/ <code>GL_ARB_ES3_compatibility</code> is available. */ - public static final String GL4ES3 = "GL4ES3"; + public static final String GL4ES3 = "GL4ES3"; // Implicitly intern(), see Bug 1059 /** The default profile, used for the device default profile map */ - private static final String GL_DEFAULT = "GL_DEFAULT"; + private static final String GL_DEFAULT = "GL_DEFAULT"; // Implicitly intern(), see Bug 1059 + /** The default profile, used for the device default profile map */ + private static final String GL_GL = "GL"; // Implicitly intern(), see Bug 1059 /** * All GL Profiles in the order of default detection. @@ -891,7 +953,7 @@ public class GLProfile { public static GLProfile get(final AbstractGraphicsDevice device, String profile) throws GLException { - if(null==profile || profile.equals("GL")) { + if(null==profile || profile == GL_GL) { profile = GL_DEFAULT; } final HashMap<String /*GLProfile_name*/, GLProfile> glpMap = getProfileMap(device, true); @@ -963,21 +1025,21 @@ public class GLProfile { * This requires an EGL interface. */ public static boolean usesNativeGLES1(final String profileImpl) { - return GLES1.equals(profileImpl); + return GLES1 == profileImpl; } /** Indicates whether the native OpenGL ES3 or ES2 profile is in use. * This requires an EGL, ES3 or ES2 compatible interface. */ public static boolean usesNativeGLES2(final String profileImpl) { - return GLES3.equals(profileImpl) || GLES2.equals(profileImpl); + return GLES3 == profileImpl || GLES2 == profileImpl; } /** Indicates whether the native OpenGL ES2 profile is in use. * This requires an EGL, ES3 compatible interface. */ public static boolean usesNativeGLES3(final String profileImpl) { - return GLES3.equals(profileImpl); + return GLES3 == profileImpl; } /** Indicates whether either of the native OpenGL ES profiles are in use. */ @@ -1529,7 +1591,7 @@ public class GLProfile { @Override public String toString() { - return "GLProfile[" + getName() + "/" + getImplName() + "."+(this.isHardwareRasterizer?"hw":"sw")+"]"; + return "GLProfile[" + getName() + "/" + getImplName() + "."+(this.isHardwareRasterizer?"hw":"sw")+(isCustom?".custom":"")+"]"; } private static /*final*/ boolean isAWTAvailable; @@ -1952,13 +2014,13 @@ public class GLProfile { if( null != profileImpl ) { final GLProfile glProfile; if( profile.equals( profileImpl ) ) { - glProfile = new GLProfile(profile, null, isHardwareRasterizer[0]); + glProfile = new GLProfile(profile, null, isHardwareRasterizer[0], false /* custom */); } else { final GLProfile _mglp = _mappedProfiles.get( profileImpl ); if( null == _mglp ) { throw new InternalError("XXX0 profile["+i+"]: "+profile+" -> profileImpl "+profileImpl+" !!! not mapped "); } - glProfile = new GLProfile(profile, _mglp, isHardwareRasterizer[0]); + glProfile = new GLProfile(profile, _mglp, isHardwareRasterizer[0], false /* custom */); } _mappedProfiles.put(profile, glProfile); if (DEBUG) { @@ -1994,7 +2056,7 @@ public class GLProfile { * Returns the profile implementation */ private static String computeProfileImpl(final AbstractGraphicsDevice device, final String profile, final boolean desktopCtxUndef, final boolean esCtxUndef, final boolean isHardwareRasterizer[]) { - if (GL2ES1.equals(profile)) { + if (GL2ES1 == profile) { final boolean es1HardwareRasterizer[] = new boolean[1]; final boolean gles1Available = hasGLES1Impl && ( esCtxUndef || GLContext.isGLES1Available(device, es1HardwareRasterizer) ); final boolean gles1HWAvailable = gles1Available && es1HardwareRasterizer[0] ; @@ -2019,7 +2081,7 @@ public class GLProfile { isHardwareRasterizer[0] = es1HardwareRasterizer[0]; return GLES1; } - } else if (GL2ES2.equals(profile)) { + } else if (GL2ES2 == profile) { final boolean es2HardwareRasterizer[] = new boolean[1]; final boolean gles2Available = hasGLES3Impl && ( esCtxUndef || GLContext.isGLES2Available(device, es2HardwareRasterizer) ); final boolean gles2HWAvailable = gles2Available && es2HardwareRasterizer[0] ; @@ -2061,7 +2123,7 @@ public class GLProfile { isHardwareRasterizer[0] = es2HardwareRasterizer[0]; return GLES2; } - } else if (GL4ES3.equals(profile)) { + } else if (GL4ES3 == profile) { final boolean gles3CompatAvail = GLContext.isGLES3CompatibleAvailable(device); if( desktopCtxUndef || esCtxUndef || gles3CompatAvail ) { final boolean es3HardwareRasterizer[] = new boolean[1]; @@ -2078,23 +2140,13 @@ public class GLProfile { return GL4bc; } } - if(GLContext.isGL3Available(device, isHardwareRasterizer)) { - if(!gles3HWAvailable || isHardwareRasterizer[0]) { - return GL3; - } - } - if( desktopCtxUndef || GLContext.isGL3bcAvailable(device, isHardwareRasterizer)) { - if(!gles3HWAvailable || isHardwareRasterizer[0]) { - return GL3bc; - } - } } if(gles3Available) { isHardwareRasterizer[0] = es3HardwareRasterizer[0]; return GLES3; } } - } else if(GL2GL3.equals(profile)) { + } else if(GL2GL3 == profile) { if(hasGL234Impl) { if( GLContext.isGL4bcAvailable(device, isHardwareRasterizer)) { return GL4bc; @@ -2108,21 +2160,21 @@ public class GLProfile { return GL2; } } - } else if(GL4bc.equals(profile) && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL4bcAvailable(device, isHardwareRasterizer))) { + } else if(GL4bc == profile && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL4bcAvailable(device, isHardwareRasterizer))) { return desktopCtxUndef ? GL4bc : GLContext.getAvailableGLProfileName(device, 4, GLContext.CTX_PROFILE_COMPAT); - } else if(GL4.equals(profile) && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL4Available(device, isHardwareRasterizer))) { + } else if(GL4 == profile && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL4Available(device, isHardwareRasterizer))) { return desktopCtxUndef ? GL4 : GLContext.getAvailableGLProfileName(device, 4, GLContext.CTX_PROFILE_CORE); - } else if(GL3bc.equals(profile) && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL3bcAvailable(device, isHardwareRasterizer))) { + } else if(GL3bc == profile && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL3bcAvailable(device, isHardwareRasterizer))) { return desktopCtxUndef ? GL3bc : GLContext.getAvailableGLProfileName(device, 3, GLContext.CTX_PROFILE_COMPAT); - } else if(GL3.equals(profile) && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL3Available(device, isHardwareRasterizer))) { + } else if(GL3 == profile && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL3Available(device, isHardwareRasterizer))) { return desktopCtxUndef ? GL3 : GLContext.getAvailableGLProfileName(device, 3, GLContext.CTX_PROFILE_CORE); - } else if(GL2.equals(profile) && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL2Available(device, isHardwareRasterizer))) { + } else if(GL2 == profile && hasGL234Impl && ( desktopCtxUndef || GLContext.isGL2Available(device, isHardwareRasterizer))) { return desktopCtxUndef ? GL2 : GLContext.getAvailableGLProfileName(device, 2, GLContext.CTX_PROFILE_COMPAT); - } else if(GLES3.equals(profile) && hasGLES3Impl && ( esCtxUndef || GLContext.isGLES3Available(device, isHardwareRasterizer))) { + } else if(GLES3 == profile && hasGLES3Impl && ( esCtxUndef || GLContext.isGLES3Available(device, isHardwareRasterizer))) { return esCtxUndef ? GLES3 : GLContext.getAvailableGLProfileName(device, 3, GLContext.CTX_PROFILE_ES); - } else if(GLES2.equals(profile) && hasGLES3Impl && ( esCtxUndef || GLContext.isGLES2Available(device, isHardwareRasterizer))) { + } else if(GLES2 == profile && hasGLES3Impl && ( esCtxUndef || GLContext.isGLES2Available(device, isHardwareRasterizer))) { return esCtxUndef ? GLES2 : GLContext.getAvailableGLProfileName(device, 2, GLContext.CTX_PROFILE_ES); - } else if(GLES1.equals(profile) && hasGLES1Impl && ( esCtxUndef || GLContext.isGLES1Available(device, isHardwareRasterizer))) { + } else if(GLES1 == profile && hasGLES1Impl && ( esCtxUndef || GLContext.isGLES1Available(device, isHardwareRasterizer))) { return esCtxUndef ? GLES1 : GLContext.getAvailableGLProfileName(device, 1, GLContext.CTX_PROFILE_ES); } return null; @@ -2180,13 +2232,19 @@ public class GLProfile { } } - private GLProfile(final String profile, final GLProfile profileImpl, final boolean isHardwareRasterizer) { + private GLProfile(final String profile, final GLProfile profileImpl, final boolean isHardwareRasterizer, final boolean isCustom) { this.profile = profile; this.profileImpl = profileImpl; this.isHardwareRasterizer = isHardwareRasterizer; + this.isCustom = isCustom; + } + + public static GLProfile createCustomGLProfile(final String profile, final GLProfile profileImpl) { + return new GLProfile(profile, profileImpl, profileImpl.isHardwareRasterizer, true); } private final GLProfile profileImpl; private final String profile; private final boolean isHardwareRasterizer; + private final boolean isCustom; } diff --git a/src/jogl/classes/javax/media/opengl/GLSharedContextSetter.java b/src/jogl/classes/javax/media/opengl/GLSharedContextSetter.java index 679898dca..526967d69 100644 --- a/src/jogl/classes/javax/media/opengl/GLSharedContextSetter.java +++ b/src/jogl/classes/javax/media/opengl/GLSharedContextSetter.java @@ -35,9 +35,12 @@ package javax.media.opengl; * and textures among OpenGL contexts is supported with this interface. * </p> * <p> - * A <i>master</i> {@link GLContext} is the {@link GLContext} which is created first, - * shared {@link GLContext} w/ this master are referred as slave {@link GLContext} - * and controls the shared object's lifecycle, i.e. their construction and destruction. + * A <i>master</i> {@link GLContext} is the {@link GLContext} which is created first. + * Subsequent shared {@link GLContext} w/ the <i>master</i> are referred as <i>slave</i> {@link GLContext}. + * </p> + * <p> + * Implementations of this interface control the <i>slave's</i> {@link GLContext} and {@link GLAutoDrawable} realization, + * i.e. the <i>slave</i> {@link GLAutoDrawable} will not be realized before their associated <i>master</i>. * </p> * <p> * Using the nearest or same {@link GLCapabilitiesImmutable#getVisualID(javax.media.nativewindow.VisualIDHolder.VIDType) visual ID} @@ -50,12 +53,17 @@ package javax.media.opengl; * At least this has been experienced w/ OSX 10.9. * </p> * <p> - * Be aware that the <i>master</i> {@link GLContext} and related resources - * <i>shall not</i> be destroyed before it's <i>slave</i> {@link GLContext} instances <i>while they are using them</i>.<br> - * Otherwise the OpenGL driver implementation may crash w/ SIGSEGV, since using already destroyed resources, - * e.g. OpenGL buffer objects, may not be validated by the driver!<br> + * In general, destroying a <i>master</i> {@link GLContext} before their shared <i>slaves</i> + * shall be permissible, i.e. the OpenGL driver needs to handle pending destruction of shared resources. + * This is confirmed to work properly on most platform/driver combinations, + * see unit test <code>com.jogamp.opengl.test.junit.jogl.acore.TestSharedContextVBOES2NEWT3</code> and similar. * </p> * <p> + * However, to avoid scenarios with buggy drivers, users <i>may not</i> destroy the + * <i>master</i> {@link GLContext} before its shared <i>slave</i> {@link GLContext} instances + * <i>as long as they are using them</i>.<br> + * Otherwise the OpenGL driver may crash w/ SIGSEGV, due to using already destroyed shared resources, + * if not handling the pending destruction of the latter!<br> * Either proper lifecycle synchronization is implemented, e.g. by notifying the <i>slaves</i> about the loss of the shared resources, * <i>or</i> the <i>slaves</i> validate whether the resources are still valid. * </p> diff --git a/src/jogl/classes/javax/media/opengl/Threading.java b/src/jogl/classes/javax/media/opengl/Threading.java index 6c64cbe31..c8d8d0071 100644 --- a/src/jogl/classes/javax/media/opengl/Threading.java +++ b/src/jogl/classes/javax/media/opengl/Threading.java @@ -117,10 +117,36 @@ import jogamp.opengl.ThreadingImpl; */ public class Threading { + public static enum Mode { + /** + * Full multithreaded OpenGL, + * i.e. any {@link Threading#invoke(boolean, Runnable, Object) invoke} + * {@link Threading#invokeOnOpenGLThread(boolean, Runnable) commands} + * will be issued on the current thread immediately. + */ + MT(0), + + /** Single-Threaded OpenGL on AWT EDT */ + ST_AWT(1), + + /** Single-Threaded OpenGL on dedicated worker thread. */ + ST_WORKER(2); + + public final int id; + + Mode(final int id){ + this.id = id; + } + } /** No reason to ever instantiate this class */ private Threading() {} + /** Returns the threading mode */ + public static Mode getMode() { + return ThreadingImpl.getMode(); + } + /** If an implementation of the javax.media.opengl APIs offers a multithreading option but the default behavior is single-threading, this API provides a mechanism for end users to disable single-threading @@ -150,10 +176,14 @@ public class Threading { return ThreadingImpl.isToolkitThread(); } - /** Indicates whether the current thread is the single thread on - which this implementation of the javax.media.opengl APIs - performs all of its OpenGL-related work. This method should only - be called if the single-thread model is in effect. */ + /** + * Indicates whether the current thread is capable of + * performing OpenGL-related work. + * <p> + * Method always returns <code>true</code> + * if {@link #getMode()} == {@link Mode#MT} or {@link #isSingleThreaded()} == <code>false</code>. + * </p> + */ public static final boolean isOpenGLThread() throws GLException { return ThreadingImpl.isOpenGLThread(); } @@ -173,7 +203,7 @@ public class Threading { } /** - * If {@link #isSingleThreaded()} <b>and</b> not {@link #isOpenGLThread()} + * If not {@link #isOpenGLThread()} * <b>and</b> the <code>lock</code> is not being hold by this thread, * invoke Runnable <code>r</code> on the OpenGL thread via {@link #invokeOnOpenGLThread(boolean, Runnable)}. * <p> @@ -186,7 +216,7 @@ public class Threading { * @throws GLException */ public static final void invoke(final boolean wait, final Runnable r, final Object lock) throws GLException { - if ( isSingleThreaded() && !isOpenGLThread() && + if ( !isOpenGLThread() && ( null == lock || !Thread.holdsLock(lock) ) ) { invokeOnOpenGLThread(wait, r); } else { diff --git a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java index 2d5e12429..563158a72 100644 --- a/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java +++ b/src/jogl/classes/javax/media/opengl/awt/GLCanvas.java @@ -156,10 +156,12 @@ import jogamp.opengl.awt.AWTTilePainter; * <li><pre>sun.awt.noerasebackground=true</pre></li> * </ul> * + * <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="../../../spec-overview.html#SHARING">context sharing</a> + * <a href="../../../../overview-summary.html#SHARING">context sharing</a> * as well as {@link GLSharedContextSetter}. + * </p> */ @SuppressWarnings("serial") @@ -300,6 +302,12 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing } @Override + public final RecursiveLock getUpstreamLock() { return lock; } + + @Override + public final boolean isThreadGLCapable() { return Threading.isOpenGLThread(); } + + @Override public void setShallUseOffscreenLayer(final boolean v) { shallUseOffscreenLayer = v; } @@ -834,65 +842,74 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing private final Runnable setupPrintOnEDT = new Runnable() { @Override public void run() { - if( !validateGLDrawable() ) { - if(DEBUG) { - System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, drawable not valid yet"); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if( !validateGLDrawable() ) { + if(DEBUG) { + System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, drawable not valid yet"); + } + printActive = false; + return; // not yet available .. } - printActive = false; - return; // not yet available .. - } - if( !isVisible() ) { - if(DEBUG) { - System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, canvas not visible"); + if( !isVisible() ) { + if(DEBUG) { + System.err.println(getThreadName()+": Info: GLCanvas setupPrint - skipped GL render, canvas not visible"); + } + printActive = false; + return; // not yet available .. } - printActive = false; - return; // not yet available .. - } - sendReshape = false; // clear reshape flag - printAnimator = helper.getAnimator(); - if( null != printAnimator ) { - printAnimator.remove(GLCanvas.this); - } - printGLAD = GLCanvas.this; // _not_ default, shall be replaced by offscreen GLAD - final GLCapabilities caps = (GLCapabilities)getChosenGLCapabilities().cloneMutable(); - final int printNumSamples = printAWTTiles.getNumSamples(caps); - GLDrawable printDrawable = printGLAD.getDelegatedDrawable(); - final boolean reqNewGLADSamples = printNumSamples != caps.getNumSamples(); - final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() || - printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight(); - final boolean reqNewGLADOnscrn = caps.isOnscreen(); - // It is desired to use a new offscreen GLAD, however Bug 830 forbids this for AA onscreen context. - // Bug 830: swapGLContextAndAllGLEventListener and onscreen MSAA w/ NV/GLX - final boolean reqNewGLAD = !caps.getSampleBuffers() && ( reqNewGLADOnscrn || reqNewGLADSamples || reqNewGLADSize ); - if( DEBUG ) { - System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ onscreen "+reqNewGLADOnscrn+", samples "+reqNewGLADSamples+", size "+reqNewGLADSize+"], "+ - ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+ - ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+ - ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+ - ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator); - } - if( reqNewGLAD ) { - caps.setDoubleBuffered(false); - caps.setOnscreen(false); - if( printNumSamples != caps.getNumSamples() ) { - caps.setSampleBuffers(0 < printNumSamples); - caps.setNumSamples(printNumSamples); + sendReshape = false; // clear reshape flag + printAnimator = helper.getAnimator(); + if( null != printAnimator ) { + printAnimator.remove(GLCanvas.this); } - final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile()); - printGLAD = factory.createOffscreenAutoDrawable(null, caps, null, - printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE, - printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE); - GLDrawableUtil.swapGLContextAndAllGLEventListener(GLCanvas.this, printGLAD); - printDrawable = printGLAD.getDelegatedDrawable(); - } - printAWTTiles.setGLOrientation(printGLAD.isGLOriented(), printGLAD.isGLOriented()); - printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0); - printAWTTiles.renderer.attachAutoDrawable(printGLAD); - if( DEBUG ) { - System.err.println("AWT print.setup "+printAWTTiles); - System.err.println("AWT print.setup AA "+printNumSamples+", "+caps); - System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD); - System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable); + printGLAD = GLCanvas.this; // _not_ default, shall be replaced by offscreen GLAD + final GLCapabilitiesImmutable gladCaps = getChosenGLCapabilities(); + final int printNumSamples = printAWTTiles.getNumSamples(gladCaps); + GLDrawable printDrawable = printGLAD.getDelegatedDrawable(); + final boolean reqNewGLADSamples = printNumSamples != gladCaps.getNumSamples(); + final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() || + printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight(); + final boolean reqNewGLADOnscrn = gladCaps.isOnscreen(); + + final GLCapabilities newGLADCaps = (GLCapabilities)gladCaps.cloneMutable(); + newGLADCaps.setDoubleBuffered(false); + newGLADCaps.setOnscreen(false); + if( printNumSamples != newGLADCaps.getNumSamples() ) { + newGLADCaps.setSampleBuffers(0 < printNumSamples); + newGLADCaps.setNumSamples(printNumSamples); + } + final boolean reqNewGLADSafe = GLDrawableUtil.isSwapGLContextSafe(getRequestedGLCapabilities(), gladCaps, newGLADCaps); + + final boolean reqNewGLAD = ( reqNewGLADOnscrn || reqNewGLADSamples || reqNewGLADSize ) && reqNewGLADSafe; + + if( DEBUG ) { + System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ onscreen "+reqNewGLADOnscrn+", samples "+reqNewGLADSamples+", size "+reqNewGLADSize+", safe "+reqNewGLADSafe+"], "+ + ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+ + ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+ + ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+ + ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator); + } + if( reqNewGLAD ) { + final GLDrawableFactory factory = GLDrawableFactory.getFactory(newGLADCaps.getGLProfile()); + printGLAD = factory.createOffscreenAutoDrawable(null, newGLADCaps, null, + printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE, + printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE); + GLDrawableUtil.swapGLContextAndAllGLEventListener(GLCanvas.this, printGLAD); + printDrawable = printGLAD.getDelegatedDrawable(); + } + printAWTTiles.setGLOrientation(printGLAD.isGLOriented(), printGLAD.isGLOriented()); + printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0); + printAWTTiles.renderer.attachAutoDrawable(printGLAD); + if( DEBUG ) { + System.err.println("AWT print.setup "+printAWTTiles); + System.err.println("AWT print.setup AA "+printNumSamples+", "+newGLADCaps); + System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD); + System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable); + } + } finally { + _lock.unlock(); } } }; @@ -908,23 +925,29 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing private final Runnable releasePrintOnEDT = new Runnable() { @Override public void run() { - if( DEBUG ) { - System.err.println("AWT print.release "+printAWTTiles); - } - printAWTTiles.dispose(); - printAWTTiles= null; - if( printGLAD != GLCanvas.this ) { - GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLCanvas.this); - printGLAD.destroy(); - } - printGLAD = null; - if( null != printAnimator ) { - printAnimator.add(GLCanvas.this); - printAnimator = null; + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if( DEBUG ) { + System.err.println("AWT print.release "+printAWTTiles); + } + printAWTTiles.dispose(); + printAWTTiles= null; + if( printGLAD != GLCanvas.this ) { + GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLCanvas.this); + printGLAD.destroy(); + } + printGLAD = null; + if( null != printAnimator ) { + printAnimator.add(GLCanvas.this); + printAnimator = null; + } + sendReshape = true; // trigger reshape, i.e. gl-viewport and -listener - this component might got resized! + printActive = false; + display(); + } finally { + _lock.unlock(); } - sendReshape = true; // trigger reshape, i.e. gl-viewport and -listener - this component might got resized! - printActive = false; - display(); } }; @@ -1040,16 +1063,21 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing } @Override - public boolean invoke(final boolean wait, final GLRunnable glRunnable) { + public boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException { return helper.invoke(this, wait, glRunnable); } @Override - public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) { + public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException { return helper.invoke(this, wait, glRunnables); } @Override + public void flushGLRunnables() { + helper.flushGLRunnables(); + } + + @Override public GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) { final RecursiveLock _lock = lock; _lock.lock(); @@ -1137,6 +1165,7 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing return (GLCapabilitiesImmutable)awtConfig.getChosenCapabilities(); } + @Override public GLCapabilitiesImmutable getRequestedGLCapabilities() { if( null == awtConfig ) { return capsReqUser; @@ -1222,26 +1251,32 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing animatorPaused = false; } + GLException exceptionOnDisposeGL = null; + // OLS will be detached by disposeGL's context destruction below if( null != context ) { if( context.isCreated() ) { - // Catch dispose GLExceptions by GLEventListener, just 'print' them - // so we can continue with the destruction. try { helper.disposeGL(GLCanvas.this, context, true); if(DEBUG) { System.err.println(getThreadName()+": destroyOnEDTAction() - post ctx: "+context); } } catch (final GLException gle) { - gle.printStackTrace(); + exceptionOnDisposeGL = gle; } } context = null; } + + Throwable exceptionOnUnrealize = null; if( null != drawable ) { - drawable.setRealized(false); - if(DEBUG) { - System.err.println(getThreadName()+": destroyOnEDTAction() - post drawable: "+drawable); + try { + drawable.setRealized(false); + if(DEBUG) { + System.err.println(getThreadName()+": destroyOnEDTAction() - post drawable: "+drawable); + } + } catch( final Throwable re ) { + exceptionOnUnrealize = re; } drawable = null; } @@ -1250,6 +1285,14 @@ public class GLCanvas extends Canvas implements AWTGLAutoDrawable, WindowClosing animator.resume(); } + // throw exception in order of occurrence .. + if( null != exceptionOnDisposeGL ) { + throw exceptionOnDisposeGL; + } + if( null != exceptionOnUnrealize ) { + throw GLException.newGLException(exceptionOnUnrealize); + } + if(DEBUG) { System.err.println(getThreadName()+": dispose() - END, animator "+animator); } diff --git a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java index 549b6e96f..5bc4c9a60 100644 --- a/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java +++ b/src/jogl/classes/javax/media/opengl/awt/GLJPanel.java @@ -98,6 +98,8 @@ import jogamp.opengl.util.glsl.GLSLTextureRaster; import com.jogamp.common.util.PropertyAccess; import com.jogamp.common.util.awt.AWTEDTExecutor; +import com.jogamp.common.util.locks.LockFactory; +import com.jogamp.common.util.locks.RecursiveLock; import com.jogamp.nativewindow.awt.AWTPrintLifecycle; import com.jogamp.nativewindow.awt.AWTWindowClosingProtocol; import com.jogamp.opengl.FBObject; @@ -128,19 +130,19 @@ import com.jogamp.opengl.util.texture.TextureState; using {@link GLDrawableFactory#createOffscreenDrawable(AbstractGraphicsDevice, GLCapabilitiesImmutable, GLCapabilitiesChooser, int, int) GLDrawableFactory.createOffscreenDrawable(..)}.<br/> </p> <p> - <a name="verticalFlip"> - In case</a> the drawable {@link #isGLOriented()} and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped}, - this component performs the required vertical flip to bring the content from OpenGL's orientation into AWT's orientation. + <a name="verticalFlip">A vertical-flip is required</a>, if the drawable {@link #isGLOriented()} and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped}.<br> + In this case this component performs the required vertical flip to bring the content from OpenGL's orientation into AWT's orientation.<br> + In case <a href="#fboGLSLVerticalFlip">GLSL based vertical-flip</a> is not available, + the CPU intensive {@link System#arraycopy(Object, int, Object, int, int) System.arraycopy(..)} is used line by line. See details about <a href="#fboGLSLVerticalFlip">FBO and GLSL vertical flipping</a>. </p> <p> - The OpenGL path is concluded by copying the rendered pixels an {@link BufferedImage} via {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer) glReadPixels(..)} - for later Java2D composition. + For performance reasons, as well as for <a href="#bug842">GL state sideeffects</a>, + <b>{@link #setSkipGLOrientationVerticalFlip(boolean) skipping vertical flip} is highly recommended</b>! </p> <p> - In case {@link #setSkipGLOrientationVerticalFlip(boolean) vertical-flip is not skipped} and <a href="#fboGLSLVerticalFlip">GLSL based vertical-flip</a> is not performed, - {@link System#arraycopy(Object, int, Object, int, int) System.arraycopy(..)} is used line by line. - This step causes more CPU load per frame and is not hardware-accelerated. + The OpenGL path is concluded by copying the rendered pixels an {@link BufferedImage} via {@link GL#glReadPixels(int, int, int, int, int, int, java.nio.Buffer) glReadPixels(..)} + for later Java2D composition. </p> <p> Finally the Java2D compositioning takes place via via {@link Graphics#drawImage(java.awt.Image, int, int, int, int, java.awt.image.ImageObserver) Graphics.drawImage(...)} @@ -151,7 +153,8 @@ import com.jogamp.opengl.util.texture.TextureState; * </P> * <a name="fboGLSLVerticalFlip"><h5>FBO / GLSL Vertical Flip</h5></a> - In case FBO is used and GLSL is available and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped}, a fragment shader is utilized + If <a href="#verticalFlip">vertical flip is required</a>, + FBO is used, GLSL is available and {@link #setSkipGLOrientationVerticalFlip(boolean) vertical flip is not skipped}, a fragment shader is utilized to flip the FBO texture vertically. This hardware-accelerated step can be disabled via system property <code>jogl.gljpanel.noglsl</code>. <p> The FBO / GLSL code path uses one texture-unit and binds the FBO texture to it's active texture-target, @@ -165,20 +168,23 @@ import com.jogamp.opengl.util.texture.TextureState; The current gl-viewport is preserved. </p> <p> - <i>Warning (Bug 842)</i>: Certain GL states other than viewport and texture (see above) + <a name="bug842"><i>Warning (Bug 842)</i></a>: Certain GL states other than viewport and texture (see above) influencing rendering, will also influence the GLSL vertical flip, e.g. {@link GL#glFrontFace(int) glFrontFace}({@link GL#GL_CCW}). It is recommended to reset those states to default when leaving the {@link GLEventListener#display(GLAutoDrawable)} method! We may change this behavior in the future, i.e. preserve all influencing states. </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="../../../spec-overview.html#SHARING">context sharing</a> + <a href="../../../../overview-summary.html#SHARING">context sharing</a> as well as {@link GLSharedContextSetter}. + </p> */ @SuppressWarnings("serial") public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosingProtocol, AWTPrintLifecycle, GLSharedContextSetter, ScalableSurface { private static final boolean DEBUG; + private static final boolean DEBUG_FRAMES; private static final boolean DEBUG_VIEWPORT; private static final boolean USE_GLSL_TEXTURE_RASTERIZER; private static final boolean SKIP_VERTICAL_FLIP_DEFAULT; @@ -195,6 +201,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing static { Debug.initSingleton(); DEBUG = Debug.debug("GLJPanel"); + DEBUG_FRAMES = PropertyAccess.isPropertyDefined("jogl.debug.GLJPanel.Frames", true); DEBUG_VIEWPORT = PropertyAccess.isPropertyDefined("jogl.debug.GLJPanel.Viewport", true); USE_GLSL_TEXTURE_RASTERIZER = !PropertyAccess.isPropertyDefined("jogl.gljpanel.noglsl", true); SKIP_VERTICAL_FLIP_DEFAULT = PropertyAccess.isPropertyDefined("jogl.gljpanel.noverticalflip", true); @@ -232,6 +239,8 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing return singleAWTGLPixelBufferProvider; } + private final RecursiveLock lock = LockFactory.createRecursiveLock(); + private final GLDrawableHelper helper; private boolean autoSwapBufferMode; @@ -241,10 +250,9 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing // Data used for either pbuffers or pixmap-based offscreen surfaces // private AWTGLPixelBufferProvider customPixelBufferProvider = null; - /** Single buffered offscreen caps */ - private GLCapabilitiesImmutable offscreenCaps; - private final GLProfile glProfile; - private final GLDrawableFactoryImpl factory; + /** Requested single buffered offscreen caps */ + private volatile GLCapabilitiesImmutable reqOffscreenCaps; + private volatile GLDrawableFactoryImpl factory; private final GLCapabilitiesChooser chooser; private int additionalCtxCreationFlags = 0; @@ -349,10 +357,9 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing caps = new GLCapabilities(GLProfile.getDefault(GLProfile.getDefaultDevice())); } caps.setDoubleBuffered(false); - offscreenCaps = caps; + reqOffscreenCaps = caps; } - this.glProfile = offscreenCaps.getGLProfile(); - this.factory = GLDrawableFactoryImpl.getFactoryImpl(glProfile); + this.factory = GLDrawableFactoryImpl.getFactoryImpl( reqOffscreenCaps.getGLProfile() ); // pre-fetch, reqOffscreenCaps may changed this.chooser = chooser; helper = new GLDrawableHelper(); @@ -432,6 +439,12 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } @Override + public final RecursiveLock getUpstreamLock() { return lock; } + + @Override + public final boolean isThreadGLCapable() { return EventQueue.isDispatchThread(); } + + @Override public void display() { if( isShowing || ( printActive && isVisible() ) ) { if (EventQueue.isDispatchThread()) { @@ -449,17 +462,19 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } } - protected void dispose() { + protected void dispose(final Runnable post) { if(DEBUG) { System.err.println(getThreadName()+": GLJPanel.dispose() - start"); // Thread.dumpStack(); } if (backend != null && backend.getContext() != null) { - boolean animatorPaused = false; + final boolean animatorPaused; final GLAnimatorControl animator = getAnimator(); if(null!=animator) { animatorPaused = animator.pause(); + } else { + animatorPaused = false; } if(backend.getContext().isCreated()) { @@ -470,15 +485,14 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing backend.destroy(); isInitialized = false; } + if( null != post ) { + post.run(); + } - if(animatorPaused) { + if( animatorPaused ) { animator.resume(); } } - hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE; - hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE; - nativePixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE; - nativePixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE; if(DEBUG) { System.err.println(getThreadName()+": GLJPanel.dispose() - stop"); @@ -522,28 +536,35 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing return; } - if( !isInitialized ) { - initializeBackendImpl(); - } - - if (!isInitialized || printActive) { - return; - } + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if( !isInitialized ) { + handleReshape = false; + initializeBackendImpl(); + } - // NOTE: must do this when the context is not current as it may - // involve destroying the pbuffer (current context) and - // re-creating it -- tricky to do properly while the context is - // current - if( !printActive ) { - if (handleReshape) { - handleReshape = false; - sendReshape = handleReshape(); + if (!isInitialized || printActive) { + return; } - if( isShowing ) { - updater.setGraphics(g); - backend.doPaintComponent(g); + // NOTE: must do this when the context is not current as it may + // involve destroying the pbuffer (current context) and + // re-creating it -- tricky to do properly while the context is + // current + if( !printActive ) { + if ( handleReshape ) { + handleReshape = false; + sendReshape = handleReshape(); + } + + if( isShowing ) { + updater.setGraphics(g); + backend.doPaintComponent(g); + } } + } finally { + _lock.unlock(); } } @@ -622,7 +643,12 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing public void removeNotify() { awtWindowClosingProtocol.removeClosingListener(); - dispose(); + dispose(null); + hasPixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE; + hasPixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE; + nativePixelScale[0] = ScalableSurface.IDENTITY_PIXELSCALE; + nativePixelScale[1] = ScalableSurface.IDENTITY_PIXELSCALE; + super.removeNotify(); } @@ -643,17 +669,16 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing private void reshapeImpl(final int width, final int height) { final int scaledWidth = width * hasPixelScale[0]; final int scaledHeight = height * hasPixelScale[1]; + if( !printActive && ( scaledWidth != panelWidth || scaledHeight != panelHeight ) ) { + reshapeWidth = scaledWidth; + reshapeHeight = scaledHeight; + handleReshape = true; + } if( DEBUG ) { System.err.println(getThreadName()+": GLJPanel.reshape.0 "+this.getName()+" resize"+(printActive?"WithinPrint":"")+ " [ this "+getWidth()+"x"+getHeight()+", pixelScale "+getPixelScaleStr()+ ", panel "+panelWidth+"x"+panelHeight + - ", reshape: " +reshapeWidth+"x"+reshapeHeight + - "] -> "+(printActive?"[skipped] ":"") + width+"x"+height+" * "+getPixelScaleStr()+" -> "+scaledWidth+"x"+scaledHeight); - } - if( !printActive ) { - reshapeWidth = scaledWidth; - reshapeHeight = scaledHeight; - handleReshape = true; + "] -> "+(handleReshape?"":"[skipped] ") + width+"x"+height+" * "+getPixelScaleStr()+" -> "+scaledWidth+"x"+scaledHeight); } } @@ -673,65 +698,78 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing private final Runnable setupPrintOnEDT = new Runnable() { @Override public void run() { - if( !isInitialized ) { - initializeBackendImpl(); - } - if (!isInitialized) { - if(DEBUG) { - System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, drawable not valid yet"); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if( !isInitialized ) { + initializeBackendImpl(); } - printActive = false; - return; // not yet available .. - } - if( !isVisible() ) { - if(DEBUG) { - System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, panel not visible"); + if (!isInitialized) { + if(DEBUG) { + System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, drawable not valid yet"); + } + printActive = false; + return; // not yet available .. + } + if( !isVisible() ) { + if(DEBUG) { + System.err.println(getThreadName()+": Info: GLJPanel setupPrint - skipped GL render, panel not visible"); + } + printActive = false; + return; // not yet available .. + } + sendReshape = false; // clear reshape flag + handleReshape = false; // ditto + printAnimator = helper.getAnimator(); + if( null != printAnimator ) { + printAnimator.remove(GLJPanel.this); } - printActive = false; - return; // not yet available .. - } - sendReshape = false; // clear reshape flag - handleReshape = false; // ditto - printAnimator = helper.getAnimator(); - if( null != printAnimator ) { - printAnimator.remove(GLJPanel.this); - } - printGLAD = GLJPanel.this; // default: re-use - final GLCapabilities caps = (GLCapabilities)getChosenGLCapabilities().cloneMutable(); - final int printNumSamples = printAWTTiles.getNumSamples(caps); - GLDrawable printDrawable = printGLAD.getDelegatedDrawable(); - final boolean reqNewGLADSamples = printNumSamples != caps.getNumSamples(); - final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() || - printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight(); - final boolean reqNewGLAD = reqNewGLADSamples || reqNewGLADSize ; - if( DEBUG ) { - System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ samples "+reqNewGLADSamples+", size "+reqNewGLADSize+"], "+ - ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+ - ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+ - ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+ - ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator); - } - if( reqNewGLAD ) { - caps.setDoubleBuffered(false); - caps.setOnscreen(false); - caps.setSampleBuffers(0 < printNumSamples); - caps.setNumSamples(printNumSamples); - final GLDrawableFactory factory = GLDrawableFactory.getFactory(caps.getGLProfile()); - printGLAD = factory.createOffscreenAutoDrawable(null, caps, null, - printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE, - printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE); - GLDrawableUtil.swapGLContextAndAllGLEventListener(GLJPanel.this, printGLAD); - printDrawable = printGLAD.getDelegatedDrawable(); - } - printAWTTiles.setGLOrientation( !GLJPanel.this.skipGLOrientationVerticalFlip && printGLAD.isGLOriented(), printGLAD.isGLOriented() ); - printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0); - printAWTTiles.renderer.attachAutoDrawable(printGLAD); - if( DEBUG ) { - System.err.println("AWT print.setup "+printAWTTiles); - System.err.println("AWT print.setup AA "+printNumSamples+", "+caps); - System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD); - System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable); + printGLAD = GLJPanel.this; // default: re-use + final GLCapabilitiesImmutable gladCaps = getChosenGLCapabilities(); + final int printNumSamples = printAWTTiles.getNumSamples(gladCaps); + GLDrawable printDrawable = printGLAD.getDelegatedDrawable(); + final boolean reqNewGLADSamples = printNumSamples != gladCaps.getNumSamples(); + final boolean reqNewGLADSize = printAWTTiles.customTileWidth != -1 && printAWTTiles.customTileWidth != printDrawable.getSurfaceWidth() || + printAWTTiles.customTileHeight != -1 && printAWTTiles.customTileHeight != printDrawable.getSurfaceHeight(); + + final GLCapabilities newGLADCaps = (GLCapabilities)gladCaps.cloneMutable(); + newGLADCaps.setDoubleBuffered(false); + newGLADCaps.setOnscreen(false); + if( printNumSamples != newGLADCaps.getNumSamples() ) { + newGLADCaps.setSampleBuffers(0 < printNumSamples); + newGLADCaps.setNumSamples(printNumSamples); + } + final boolean reqNewGLADSafe = GLDrawableUtil.isSwapGLContextSafe(getRequestedGLCapabilities(), gladCaps, newGLADCaps); + + final boolean reqNewGLAD = ( reqNewGLADSamples || reqNewGLADSize ) && reqNewGLADSafe; + + if( DEBUG ) { + System.err.println("AWT print.setup: reqNewGLAD "+reqNewGLAD+"[ samples "+reqNewGLADSamples+", size "+reqNewGLADSize+", safe "+reqNewGLADSafe+"], "+ + ", drawableSize "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+ + ", customTileSize "+printAWTTiles.customTileWidth+"x"+printAWTTiles.customTileHeight+ + ", scaleMat "+printAWTTiles.scaleMatX+" x "+printAWTTiles.scaleMatY+ + ", numSamples "+printAWTTiles.customNumSamples+" -> "+printNumSamples+", printAnimator "+printAnimator); + } + if( reqNewGLAD ) { + final GLDrawableFactory factory = GLDrawableFactory.getFactory(newGLADCaps.getGLProfile()); + printGLAD = factory.createOffscreenAutoDrawable(null, newGLADCaps, null, + printAWTTiles.customTileWidth != -1 ? printAWTTiles.customTileWidth : DEFAULT_PRINT_TILE_SIZE, + printAWTTiles.customTileHeight != -1 ? printAWTTiles.customTileHeight : DEFAULT_PRINT_TILE_SIZE); + GLDrawableUtil.swapGLContextAndAllGLEventListener(GLJPanel.this, printGLAD); + printDrawable = printGLAD.getDelegatedDrawable(); + } + printAWTTiles.setGLOrientation( !GLJPanel.this.skipGLOrientationVerticalFlip && printGLAD.isGLOriented(), printGLAD.isGLOriented() ); + printAWTTiles.renderer.setTileSize(printDrawable.getSurfaceWidth(), printDrawable.getSurfaceHeight(), 0); + printAWTTiles.renderer.attachAutoDrawable(printGLAD); + if( DEBUG ) { + System.err.println("AWT print.setup "+printAWTTiles); + System.err.println("AWT print.setup AA "+printNumSamples+", "+newGLADCaps); + System.err.println("AWT print.setup printGLAD: "+printGLAD.getSurfaceWidth()+"x"+printGLAD.getSurfaceHeight()+", "+printGLAD); + System.err.println("AWT print.setup printDraw: "+printDrawable.getSurfaceWidth()+"x"+printDrawable.getSurfaceHeight()+", "+printDrawable); + } + } finally { + _lock.unlock(); } } }; @@ -749,43 +787,49 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing private final Runnable releasePrintOnEDT = new Runnable() { @Override public void run() { - if( DEBUG ) { - System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0 "+printAWTTiles); - } - printAWTTiles.dispose(); - printAWTTiles= null; - if( printGLAD != GLJPanel.this ) { - GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLJPanel.this); - printGLAD.destroy(); - } - printGLAD = null; - if( null != printAnimator ) { - printAnimator.add(GLJPanel.this); - printAnimator = null; - } + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if( DEBUG ) { + System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0 "+printAWTTiles); + } + printAWTTiles.dispose(); + printAWTTiles= null; + if( printGLAD != GLJPanel.this ) { + GLDrawableUtil.swapGLContextAndAllGLEventListener(printGLAD, GLJPanel.this); + printGLAD.destroy(); + } + printGLAD = null; + if( null != printAnimator ) { + printAnimator.add(GLJPanel.this); + printAnimator = null; + } - // trigger reshape, i.e. gl-viewport and -listener - this component might got resized! - final int awtWidth = GLJPanel.this.getWidth(); - final int awtHeight= GLJPanel.this.getHeight(); - final int scaledAWTWidth = awtWidth * hasPixelScale[0]; - final int scaledAWTHeight= awtHeight * hasPixelScale[1]; - final GLDrawable drawable = GLJPanel.this.getDelegatedDrawable(); - if( scaledAWTWidth != panelWidth || scaledAWTHeight != panelHeight || - drawable.getSurfaceWidth() != panelWidth || drawable.getSurfaceHeight() != panelHeight ) { - // -> !( awtSize == panelSize == drawableSize ) - if ( DEBUG ) { - System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0: resizeWithinPrint panel " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr()+ - ", draw "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+ - " -> " + awtWidth+"x"+awtHeight+" * "+getPixelScaleStr()+" -> "+scaledAWTWidth+"x"+scaledAWTHeight); + // trigger reshape, i.e. gl-viewport and -listener - this component might got resized! + final int awtWidth = GLJPanel.this.getWidth(); + final int awtHeight= GLJPanel.this.getHeight(); + final int scaledAWTWidth = awtWidth * hasPixelScale[0]; + final int scaledAWTHeight= awtHeight * hasPixelScale[1]; + final GLDrawable drawable = GLJPanel.this.getDelegatedDrawable(); + if( scaledAWTWidth != panelWidth || scaledAWTHeight != panelHeight || + drawable.getSurfaceWidth() != panelWidth || drawable.getSurfaceHeight() != panelHeight ) { + // -> !( awtSize == panelSize == drawableSize ) + if ( DEBUG ) { + System.err.println(getThreadName()+": GLJPanel.releasePrintOnEDT.0: resizeWithinPrint panel " +panelWidth+"x"+panelHeight + " @ scale "+getPixelScaleStr()+ + ", draw "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+ + " -> " + awtWidth+"x"+awtHeight+" * "+getPixelScaleStr()+" -> "+scaledAWTWidth+"x"+scaledAWTHeight); + } + reshapeWidth = scaledAWTWidth; + reshapeHeight = scaledAWTHeight; + sendReshape = handleReshape(); // reshapeSize -> panelSize, backend reshape w/ GL reshape + } else { + sendReshape = true; // only GL reshape } - reshapeWidth = scaledAWTWidth; - reshapeHeight = scaledAWTHeight; - sendReshape = handleReshape(); // reshapeSize -> panelSize, backend reshape w/ GL reshape - } else { - sendReshape = true; // only GL reshape + printActive = false; + display(); + } finally { + _lock.unlock(); } - printActive = false; - display(); } }; @@ -927,22 +971,33 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } @Override - public boolean invoke(final boolean wait, final GLRunnable glRunnable) { + public boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException { return helper.invoke(this, wait, glRunnable); } @Override - public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) { + public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException { return helper.invoke(this, wait, glRunnables); } @Override + public void flushGLRunnables() { + helper.flushGLRunnables(); + } + + @Override public GLContext createContext(final GLContext shareWith) { - final Backend b = backend; - if ( null == b ) { - return null; + final RecursiveLock _lock = lock; + _lock.lock(); + try { + final Backend b = backend; + if ( null == b ) { + return null; + } + return b.createContext(shareWith); + } finally { + _lock.unlock(); } - return b.createContext(shareWith); } @Override @@ -956,14 +1011,20 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing @Override public GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) { - final Backend b = backend; - if ( null == b ) { - return null; + final RecursiveLock _lock = lock; + _lock.lock(); + try { + final Backend b = backend; + if ( null == b ) { + return null; + } + final GLContext oldCtx = b.getContext(); + GLDrawableHelper.switchContext(b.getDrawable(), oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags); + b.setContext(newCtx); + return oldCtx; + } finally { + _lock.unlock(); } - final GLContext oldCtx = b.getContext(); - GLDrawableHelper.switchContext(b.getDrawable(), oldCtx, destroyPrevCtx, newCtx, additionalCtxCreationFlags); - b.setContext(newCtx); - return oldCtx; } @@ -1062,12 +1123,12 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing @Override public int getSurfaceWidth() { - return panelWidth; // FIXME HiDPI: Accurate or: getWidth() * hasPixelScale[0]; + return panelWidth; // scaled surface width in pixel units, current as-from reshape } @Override public int getSurfaceHeight() { - return panelHeight; // FIXME HiDPI: Accurate or: getHeight() * hasPixelScale[1]; + return panelHeight; // scaled surface height in pixel units, current as-from reshape } /** @@ -1089,13 +1150,13 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } /** - * Set skipping {@link #isGLOriented()} based vertical flip, + * Skip {@link #isGLOriented()} based vertical flip, * which usually is required by the offscreen backend, * see details about <a href="#verticalFlip">vertical flip</a> * and <a href="#fboGLSLVerticalFlip">FBO / GLSL vertical flip</a>. * <p> * If set to <code>true</code>, user needs to flip the OpenGL rendered scene - * <i>if {@link #isGLOriented()} == true</i>, e.g. via the PMV matrix.<br/> + * <i>if {@link #isGLOriented()} == true</i>, e.g. via the projection matrix.<br/> * See constraints of {@link #isGLOriented()}. * </p> */ @@ -1117,8 +1178,45 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } @Override + public final GLCapabilitiesImmutable getRequestedGLCapabilities() { + return reqOffscreenCaps; + } + + /** + * Set a new requested {@link GLCapabilitiesImmutable} for this GLJPanel + * allowing reconfiguration. + * <p> + * Method shall be invoked from the {@link #isThreadGLCapable() AWT-EDT thread}. + * In case it is not invoked on the AWT-EDT thread, an attempt is made to do so. + * </p> + * <p> + * Method will dispose a previous {@link #isRealized() realized} GLContext and offscreen backend! + * </p> + * @param caps new capabilities. + */ + public final void setRequestedGLCapabilities(final GLCapabilitiesImmutable caps) { + if( null == caps ) { + throw new IllegalArgumentException("null caps"); + } + Threading.invoke(true, + new Runnable() { + @Override + public void run() { + dispose( new Runnable() { + @Override + public void run() { + // switch to new caps and re-init backend + // after actual dispose, but before resume animator + reqOffscreenCaps = caps; + initializeBackendImpl(); + } } ); + } + }, getTreeLock()); + } + + @Override public final GLProfile getGLProfile() { - return glProfile; + return reqOffscreenCaps.getGLProfile(); } @Override @@ -1209,12 +1307,13 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing if ( oglPipelineUsable() ) { backend = new J2DOGLBackend(); } else { - backend = new OffscreenBackend(glProfile, customPixelBufferProvider); + backend = new OffscreenBackend(customPixelBufferProvider); } isInitialized = false; } if (!isInitialized) { + this.factory = GLDrawableFactoryImpl.getFactoryImpl( reqOffscreenCaps.getGLProfile() ); // reqOffscreenCaps may have changed backend.initialize(); } return isInitialized; @@ -1309,23 +1408,42 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing private final Runnable disposeAction = new Runnable() { @Override public void run() { - if ( null != backend ) { - final GLContext _context = backend.getContext(); - final boolean backendDestroy = !backend.isUsingOwnLifecycle(); - if( null != _context && _context.isCreated() ) { - // Catch dispose GLExceptions by GLEventListener, just 'print' them - // so we can continue with the destruction. - try { - helper.disposeGL(GLJPanel.this, _context, !backendDestroy); - } catch (final GLException gle) { - gle.printStackTrace(); + final RecursiveLock _lock = lock; + _lock.lock(); + try { + if ( null != backend ) { + final GLContext _context = backend.getContext(); + final boolean backendDestroy = !backend.isUsingOwnLifecycle(); + + GLException exceptionOnDisposeGL = null; + if( null != _context && _context.isCreated() ) { + try { + helper.disposeGL(GLJPanel.this, _context, !backendDestroy); + } catch (final GLException gle) { + exceptionOnDisposeGL = gle; + } + } + Throwable exceptionBackendDestroy = null; + if ( backendDestroy ) { + try { + backend.destroy(); + } catch( final Throwable re ) { + exceptionBackendDestroy = re; + } + backend = null; + isInitialized = false; + } + + // throw exception in order of occurrence .. + if( null != exceptionOnDisposeGL ) { + throw exceptionOnDisposeGL; + } + if( null != exceptionBackendDestroy ) { + throw GLException.newGLException(exceptionBackendDestroy); } } - if ( backendDestroy ) { - backend.destroy(); - backend = null; - isInitialized = false; - } + } finally { + _lock.unlock(); } } }; @@ -1483,7 +1601,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing protected IntBuffer readBackIntsForCPUVFlip; // Implementation using software rendering - private volatile GLDrawableImpl offscreenDrawable; // volatile: avoid locking for read-only access + private volatile GLDrawable offscreenDrawable; // volatile: avoid locking for read-only access private boolean offscreenIsFBO; private FBObject fboFlipped; private GLSLTextureRaster glslTextureRaster; @@ -1495,7 +1613,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing // For saving/restoring of OpenGL state during ReadPixels private final GLPixelStorageModes psm = new GLPixelStorageModes(); - OffscreenBackend(final GLProfile glp, final AWTGLPixelBufferProvider custom) { + OffscreenBackend(final AWTGLPixelBufferProvider custom) { if(null == custom) { pixelBufferProvider = getSingleAWTGLPixelBufferProvider(); } else { @@ -1516,19 +1634,20 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing if(DEBUG) { System.err.println(getThreadName()+": OffscreenBackend: initialize() - frameCount "+frameCount); } + GLException glException = null; try { final GLContext[] shareWith = { null }; if( helper.isSharedGLContextPending(shareWith) ) { return; // pending .. } - offscreenDrawable = (GLDrawableImpl) factory.createOffscreenDrawable( + offscreenDrawable = factory.createOffscreenDrawable( null /* default platform device */, - offscreenCaps, + reqOffscreenCaps, chooser, panelWidth, panelHeight); updateWrappedSurfaceScale(offscreenDrawable); offscreenDrawable.setRealized(true); - if( DEBUG ) { + if( DEBUG_FRAMES ) { offscreenDrawable.getNativeSurface().addSurfaceUpdatedListener(new SurfaceUpdatedListener() { @Override public final void surfaceUpdated(final Object updater, final NativeSurface ns, final long when) { @@ -1536,6 +1655,17 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } } ); } + // + // Pre context configuration + // + flipVertical = !GLJPanel.this.skipGLOrientationVerticalFlip && offscreenDrawable.isGLOriented(); + offscreenIsFBO = offscreenDrawable.getRequestedGLCapabilities().isFBO(); + final boolean useGLSLFlip_pre = flipVertical && offscreenIsFBO && reqOffscreenCaps.getGLProfile().isGL2ES2() && USE_GLSL_TEXTURE_RASTERIZER; + if( offscreenIsFBO && !useGLSLFlip_pre ) { + // Texture attachment only required for GLSL vertical flip, hence simply use a color-renderbuffer attachment. + ((GLFBODrawable)offscreenDrawable).setFBOMode(0); + } + offscreenContext = (GLContextImpl) offscreenDrawable.createContext(shareWith[0]); offscreenContext.setContextCreationFlags(additionalCtxCreationFlags); if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) { @@ -1543,28 +1673,32 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing helper.setAutoSwapBufferMode(false); // we handle swap-buffers, see handlesSwapBuffer() final GL gl = offscreenContext.getGL(); - flipVertical = !GLJPanel.this.skipGLOrientationVerticalFlip && offscreenDrawable.isGLOriented(); + // Remedy for Bug 1020, i.e. OSX/Nvidia's FBO needs to be cleared before blitting, + // otherwise first MSAA frame lacks antialiasing. + // Clearing of FBO is performed within GLFBODrawableImpl.initialize(..): + // gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + final GLCapabilitiesImmutable chosenCaps = offscreenDrawable.getChosenGLCapabilities(); - offscreenIsFBO = chosenCaps.isFBO(); final boolean glslCompliant = !offscreenContext.hasRendererQuirk(GLRendererQuirks.GLSLNonCompliant); - final boolean useGLSLFlip = flipVertical && offscreenIsFBO && gl.isGL2ES2() && USE_GLSL_TEXTURE_RASTERIZER && glslCompliant; + final boolean useGLSLFlip = useGLSLFlip_pre && gl.isGL2ES2() && glslCompliant; if( DEBUG ) { System.err.println(getThreadName()+": OffscreenBackend.initialize: useGLSLFlip "+useGLSLFlip+ " [flip "+flipVertical+", isFBO "+offscreenIsFBO+", isGL2ES2 "+gl.isGL2ES2()+ ", noglsl "+!USE_GLSL_TEXTURE_RASTERIZER+", glslNonCompliant "+!glslCompliant+ - ", isGL2ES2 " + gl.isGL2ES2()+"]"); + ", isGL2ES2 " + gl.isGL2ES2()+"\n "+offscreenDrawable+"]"); } if( useGLSLFlip ) { final GLFBODrawable fboDrawable = (GLFBODrawable) offscreenDrawable; fboDrawable.setTextureUnit( GLJPanel.this.requestedTextureUnit ); try { fboFlipped = new FBObject(); - fboFlipped.reset(gl, fboDrawable.getSurfaceWidth(), fboDrawable.getSurfaceHeight(), 0, false); - fboFlipped.attachTexture2D(gl, 0, chosenCaps.getAlphaBits()>0); + fboFlipped.init(gl, panelWidth, panelHeight, 0); + fboFlipped.attachColorbuffer(gl, 0, chosenCaps.getAlphaBits()>0); // fboFlipped.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + gl.glClear(GL.GL_COLOR_BUFFER_BIT); // Bug 1020 (see above), cannot do in FBObject due to unknown 'first bind' state. glslTextureRaster = new GLSLTextureRaster(fboDrawable.getTextureUnit(), true); glslTextureRaster.init(gl.getGL2ES2()); - glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, fboDrawable.getSurfaceWidth(), fboDrawable.getSurfaceHeight()); + glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, panelWidth, panelHeight); } catch (final Exception ex) { ex.printStackTrace(); if(null != glslTextureRaster) { @@ -1584,6 +1718,8 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } else { isInitialized = false; } + } catch( final GLException gle ) { + glException = gle; } finally { if( !isInitialized ) { if(null != offscreenContext) { @@ -1595,6 +1731,9 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing offscreenDrawable = null; } } + if( null != glException ) { + throw new GLException("Caught GLException: "+glException.getMessage(), glException); + } } } @@ -1677,7 +1816,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing @Override public final void postGL(final Graphics g, final boolean isDisplay) { if (isDisplay) { - if(DEBUG) { + if( DEBUG_FRAMES ) { System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.0: - frameCount "+frameCount); } @@ -1772,15 +1911,15 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing } // Must now copy pixels from offscreen context into surface - if(DEBUG) { + if( DEBUG_FRAMES ) { System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL.readPixels: - frameCount "+frameCount); } // Save PACK modes, reset them to defaults and set alignment psm.setPackAlignment(gl, alignment); - if(gl.isGL2ES3()) { + if( gl.isGL2ES3() ) { final GL2ES3 gl2es3 = gl.getGL2ES3(); - gl2es3.glPixelStorei(GL2ES3.GL_PACK_ROW_LENGTH, panelWidth); + psm.setPackRowLength(gl2es3, panelWidth); gl2es3.glReadBuffer(gl2es3.getDefaultReadBuffer()); } @@ -1789,19 +1928,19 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing final int[] usrViewport = new int[] { 0, 0, 0, 0 }; gl.glGetIntegerv(GL.GL_VIEWPORT, usrViewport, 0); viewportChange = 0 != usrViewport[0] || 0 != usrViewport[1] || - offscreenDrawable.getSurfaceWidth() != usrViewport[2] || offscreenDrawable.getSurfaceHeight() != usrViewport[3]; + panelWidth != usrViewport[2] || panelHeight != usrViewport[3]; if( DEBUG_VIEWPORT ) { System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.postGL: "+GLJPanel.this.getName()+" Viewport: change "+viewportChange+ ", "+usrViewport[0]+"/"+usrViewport[1]+" "+usrViewport[2]+"x"+usrViewport[3]+ - " -> 0/0 "+offscreenDrawable.getSurfaceWidth()+"x"+offscreenDrawable.getSurfaceHeight()); + " -> 0/0 "+panelWidth+"x"+panelHeight); } if( viewportChange ) { - gl.glViewport(0, 0, offscreenDrawable.getSurfaceWidth(), offscreenDrawable.getSurfaceHeight()); + gl.glViewport(0, 0, panelWidth, panelHeight); } // perform vert-flipping via OpenGL/FBO final GLFBODrawable fboDrawable = (GLFBODrawable)offscreenDrawable; - final FBObject.TextureAttachment fboTex = fboDrawable.getTextureBuffer(GL.GL_FRONT); + final FBObject.TextureAttachment fboTex = fboDrawable.getColorbuffer(GL.GL_FRONT).getTextureAttachment(); fboFlipped.bind(gl); @@ -1863,7 +2002,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing helper.invokeGL(offscreenDrawable, offscreenContext, updaterDisplayAction, updaterInitAction); if ( null != alignedImage ) { - if( DEBUG ) { + if( DEBUG_FRAMES ) { System.err.println(getThreadName()+": GLJPanel.OffscreenBackend.doPaintComponent.drawImage: - frameCount "+frameCount); } // Draw resulting image in one shot @@ -1879,7 +2018,7 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing @Override public final boolean handleReshape() { - GLDrawableImpl _drawable = offscreenDrawable; + GLDrawableImpl _drawable = (GLDrawableImpl)offscreenDrawable; { final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, offscreenContext, panelWidth, panelHeight); if(_drawable != _drawableNew) { @@ -1899,8 +2038,8 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing if( GLContext.CONTEXT_NOT_CURRENT < offscreenContext.makeCurrent() ) { try { final GL gl = offscreenContext.getGL(); - fboFlipped.reset(gl, _drawable.getSurfaceWidth(), _drawable.getSurfaceHeight(), 0, false); - glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, _drawable.getSurfaceWidth(), _drawable.getSurfaceHeight()); + fboFlipped.reset(gl, panelWidth, panelHeight, 0); + glslTextureRaster.reshape(gl.getGL2ES2(), 0, 0, panelWidth, panelHeight); } finally { offscreenContext.release(); } @@ -2297,29 +2436,29 @@ public class GLJPanel extends JPanel implements AWTGLAutoDrawable, WindowClosing j2dContext.makeCurrent(); final GL gl = j2dContext.getGL(); - if ((getGLInteger(gl, GL.GL_RED_BITS) < offscreenCaps.getRedBits()) || - (getGLInteger(gl, GL.GL_GREEN_BITS) < offscreenCaps.getGreenBits()) || - (getGLInteger(gl, GL.GL_BLUE_BITS) < offscreenCaps.getBlueBits()) || + if ((getGLInteger(gl, GL.GL_RED_BITS) < reqOffscreenCaps.getRedBits()) || + (getGLInteger(gl, GL.GL_GREEN_BITS) < reqOffscreenCaps.getGreenBits()) || + (getGLInteger(gl, GL.GL_BLUE_BITS) < reqOffscreenCaps.getBlueBits()) || // (getGLInteger(gl, GL.GL_ALPHA_BITS) < offscreenCaps.getAlphaBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) < offscreenCaps.getAccumRedBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < offscreenCaps.getAccumGreenBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) < offscreenCaps.getAccumBlueBits()) || - (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < offscreenCaps.getAccumAlphaBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) < reqOffscreenCaps.getAccumRedBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) < reqOffscreenCaps.getAccumGreenBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) < reqOffscreenCaps.getAccumBlueBits()) || + (getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) < reqOffscreenCaps.getAccumAlphaBits()) || // (getGLInteger(gl, GL2.GL_DEPTH_BITS) < offscreenCaps.getDepthBits()) || - (getGLInteger(gl, GL.GL_STENCIL_BITS) < offscreenCaps.getStencilBits())) { + (getGLInteger(gl, GL.GL_STENCIL_BITS) < reqOffscreenCaps.getStencilBits())) { if (DEBUG) { System.err.println(getThreadName()+": GLJPanel: Falling back to pbuffer-based support because Java2D context insufficient"); System.err.println(" Available Required"); - System.err.println("GL_RED_BITS " + getGLInteger(gl, GL.GL_RED_BITS) + " " + offscreenCaps.getRedBits()); - System.err.println("GL_GREEN_BITS " + getGLInteger(gl, GL.GL_GREEN_BITS) + " " + offscreenCaps.getGreenBits()); - System.err.println("GL_BLUE_BITS " + getGLInteger(gl, GL.GL_BLUE_BITS) + " " + offscreenCaps.getBlueBits()); - System.err.println("GL_ALPHA_BITS " + getGLInteger(gl, GL.GL_ALPHA_BITS) + " " + offscreenCaps.getAlphaBits()); - System.err.println("GL_ACCUM_RED_BITS " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) + " " + offscreenCaps.getAccumRedBits()); - System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + " " + offscreenCaps.getAccumGreenBits()); - System.err.println("GL_ACCUM_BLUE_BITS " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) + " " + offscreenCaps.getAccumBlueBits()); - System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + " " + offscreenCaps.getAccumAlphaBits()); - System.err.println("GL_DEPTH_BITS " + getGLInteger(gl, GL.GL_DEPTH_BITS) + " " + offscreenCaps.getDepthBits()); - System.err.println("GL_STENCIL_BITS " + getGLInteger(gl, GL.GL_STENCIL_BITS) + " " + offscreenCaps.getStencilBits()); + System.err.println("GL_RED_BITS " + getGLInteger(gl, GL.GL_RED_BITS) + " " + reqOffscreenCaps.getRedBits()); + System.err.println("GL_GREEN_BITS " + getGLInteger(gl, GL.GL_GREEN_BITS) + " " + reqOffscreenCaps.getGreenBits()); + System.err.println("GL_BLUE_BITS " + getGLInteger(gl, GL.GL_BLUE_BITS) + " " + reqOffscreenCaps.getBlueBits()); + System.err.println("GL_ALPHA_BITS " + getGLInteger(gl, GL.GL_ALPHA_BITS) + " " + reqOffscreenCaps.getAlphaBits()); + System.err.println("GL_ACCUM_RED_BITS " + getGLInteger(gl, GL2.GL_ACCUM_RED_BITS) + " " + reqOffscreenCaps.getAccumRedBits()); + System.err.println("GL_ACCUM_GREEN_BITS " + getGLInteger(gl, GL2.GL_ACCUM_GREEN_BITS) + " " + reqOffscreenCaps.getAccumGreenBits()); + System.err.println("GL_ACCUM_BLUE_BITS " + getGLInteger(gl, GL2.GL_ACCUM_BLUE_BITS) + " " + reqOffscreenCaps.getAccumBlueBits()); + System.err.println("GL_ACCUM_ALPHA_BITS " + getGLInteger(gl, GL2.GL_ACCUM_ALPHA_BITS) + " " + reqOffscreenCaps.getAccumAlphaBits()); + System.err.println("GL_DEPTH_BITS " + getGLInteger(gl, GL.GL_DEPTH_BITS) + " " + reqOffscreenCaps.getDepthBits()); + System.err.println("GL_STENCIL_BITS " + getGLInteger(gl, GL.GL_STENCIL_BITS) + " " + reqOffscreenCaps.getStencilBits()); } isInitialized = false; backend = null; diff --git a/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PMSAAES2.java b/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PMSAAES2.java index fa81e2a3f..fbd40ebdd 100644 --- a/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PMSAAES2.java +++ b/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PMSAAES2.java @@ -427,7 +427,7 @@ public class VBORegion2PMSAAES2 extends GLRegion { gl.glActiveTexture(GL.GL_TEXTURE0 + gcu_FboTexUnit.intValue()); - fbo.use(gl, fbo.getSamplingSink()); + fbo.use(gl, fbo.getSamplingSink().getTextureAttachment()); gca_FboVerticesAttr.enableBuffer(gl, true); gca_FboTexCoordsAttr.enableBuffer(gl, true); indicesFbo.bindBuffer(gl, true); // keeps VBO binding @@ -454,19 +454,22 @@ public class VBORegion2PMSAAES2 extends GLRegion { fboWidth = targetFboWidth; fboHeight = targetFboHeight; fbo = new FBObject(); - fbo.reset(gl, fboWidth, fboHeight, sampleCount[0], false); + fbo.init(gl, fboWidth, fboHeight, sampleCount[0]); sampleCount[0] = fbo.getNumSamples(); fbo.attachColorbuffer(gl, 0, true); - fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + if( !blendingEnabled ) { + // no depth-buffer w/ blending + fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH, FBObject.DEFAULT_BITS); + } final FBObject ssink = new FBObject(); { - ssink.reset(gl, fboWidth, fboHeight); + ssink.init(gl, fboWidth, fboHeight, 0); // FIXME: shall not use bilinear (GL_LINEAR), due to MSAA ??? // ssink.attachTexture2D(gl, 0, true, GL2ES2.GL_LINEAR, GL2ES2.GL_LINEAR, GL2ES2.GL_CLAMP_TO_EDGE, GL2ES2.GL_CLAMP_TO_EDGE); ssink.attachTexture2D(gl, 0, true, GL.GL_NEAREST, GL.GL_NEAREST, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); if( !blendingEnabled ) { // no depth-buffer w/ blending - ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, FBObject.DEFAULT_BITS); } } fbo.setSamplingSink(ssink); @@ -475,7 +478,7 @@ public class VBORegion2PMSAAES2 extends GLRegion { System.err.printf("XXX.createFBO: blending %b, %dx%d%n%s%n", blendingEnabled, fboWidth, fboHeight, fbo.toString()); } } else if( targetFboWidth != fboWidth || targetFboHeight != fboHeight || fbo.getNumSamples() != sampleCount[0] ) { - fbo.reset(gl, targetFboWidth, targetFboHeight, sampleCount[0], true /* resetSamplingSink */); + fbo.reset(gl, targetFboWidth, targetFboHeight, sampleCount[0]); sampleCount[0] = fbo.getNumSamples(); if( DEBUG_FBO_1 ) { System.err.printf("XXX.resetFBO: %dx%d -> %dx%d%n%s%n", fboWidth, fboHeight, targetFboWidth, targetFboHeight, fbo ); diff --git a/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PVBAAES2.java b/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PVBAAES2.java index 24fa09058..8f1de9157 100644 --- a/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PVBAAES2.java +++ b/src/jogl/classes/jogamp/graph/curve/opengl/VBORegion2PVBAAES2.java @@ -573,20 +573,20 @@ public class VBORegion2PVBAAES2 extends GLRegion { fboTexSize.put(1, fboHeight); } fbo = new FBObject(); - fbo.reset(gl, fboWidth, fboHeight); + fbo.init(gl, fboWidth, fboHeight, 0); // Shall not use bilinear (GL_LINEAR), due to own VBAA. Result is smooth w/o it now! // FIXME: FXAA requires bilinear filtering! // texA = fbo.attachTexture2D(gl, 0, true, GL.GL_LINEAR, GL.GL_LINEAR, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); texA = fbo.attachTexture2D(gl, 0, true, GL.GL_NEAREST, GL.GL_NEAREST, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); if( !blendingEnabled ) { // no depth-buffer w/ blending - fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); + fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH, FBObject.DEFAULT_BITS); } if( DEBUG_FBO_1 ) { System.err.printf("XXX.createFBO: %dx%d%n%s%n", fboWidth, fboHeight, fbo.toString()); } } else if( newFboWidth != fboWidth || newFboHeight != fboHeight ) { - fbo.reset(gl, newFboWidth, newFboHeight); + fbo.reset(gl, newFboWidth, newFboHeight, 0); fbo.bind(gl); if( DEBUG_FBO_1 ) { System.err.printf("XXX.resetFBO: %dx%d -> %dx%d, target %dx%d%n", fboWidth, fboHeight, newFboWidth, newFboHeight, targetFboWidth, targetFboHeight); diff --git a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java index 5bd49dce9..97570d605 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/TypecastGlyph.java @@ -32,8 +32,8 @@ import com.jogamp.graph.curve.OutlineShape; import com.jogamp.graph.font.Font; import com.jogamp.opengl.math.geom.AABBox; -public class TypecastGlyph implements Font.Glyph { - public static class Advance +public final class TypecastGlyph implements Font.Glyph { + public static final class Advance { private final Font font; private final float advance; @@ -46,40 +46,42 @@ public class TypecastGlyph implements Font.Glyph { size2advanceI.setKeyNotFoundValue(0); } - public void reset() { + public final void reset() { size2advanceI.clear(); } - public float getScale(final float pixelSize) + public final Font getFont() { return font; } + + public final float getScale(final float pixelSize) { return this.font.getMetrics().getScale(pixelSize); } - public void add(final float advance, final float size) + public final void add(final float advance, final float size) { size2advanceI.put(Float.floatToIntBits(size), Float.floatToIntBits(advance)); } - public float get(final float size, final boolean useFrationalMetrics) + public final float get(final float pixelSize, final boolean useFrationalMetrics) { - final int sI = Float.floatToIntBits(size); + final int sI = Float.floatToIntBits(pixelSize); final int aI = size2advanceI.get(sI); if( 0 != aI ) { return Float.intBitsToFloat(aI); } final float a; if ( useFrationalMetrics ) { - a = this.advance * getScale(size); + a = this.advance * getScale(pixelSize); } else { - // a = Math.ceil(this.advance * getScale(size)); - a = Math.round(this.advance * getScale(size)); // TODO: check whether ceil should be used instead? + // a = Math.ceil(this.advance * getScale(pixelSize)); + a = Math.round(this.advance * getScale(pixelSize)); // TODO: check whether ceil should be used instead? } size2advanceI.put(sI, Float.floatToIntBits(a)); return a; } @Override - public String toString() + public final String toString() { return "\nAdvance:"+ "\n advance: "+this.advance+ @@ -87,7 +89,7 @@ public class TypecastGlyph implements Font.Glyph { } } - public static class Metrics + public static final class Metrics { private final AABBox bbox; private final Advance advance; @@ -98,32 +100,34 @@ public class TypecastGlyph implements Font.Glyph { this.advance = new Advance(font, advance); } - public void reset() { + public final void reset() { advance.reset(); } - public float getScale(final float pixelSize) + public final Font getFont() { return advance.getFont(); } + + public final float getScale(final float pixelSize) { return this.advance.getScale(pixelSize); } - public AABBox getBBox() + public final AABBox getBBox() { return this.bbox; } - public void addAdvance(final float advance, final float size) + public final void addAdvance(final float advance, final float size) { this.advance.add(advance, size); } - public float getAdvance(final float size, final boolean useFrationalMetrics) + public final float getAdvance(final float pixelSize, final boolean useFrationalMetrics) { - return this.advance.get(size, useFrationalMetrics); + return this.advance.get(pixelSize, useFrationalMetrics); } @Override - public String toString() + public final String toString() { return "\nMetrics:"+ "\n bbox: "+this.bbox+ @@ -134,31 +138,21 @@ public class TypecastGlyph implements Font.Glyph { public static final short INVALID_ID = (short)((1 << 16) - 1); public static final short MAX_ID = (short)((1 << 16) - 2); - private final Font font; private final char symbol; private final OutlineShape shape; // in EM units private final short id; - private final int advance; private final Metrics metrics; protected TypecastGlyph(final Font font, final char symbol, final short id, final AABBox bbox, final int advance, final OutlineShape shape) { - this.font = font; this.symbol = symbol; this.shape = shape; this.id = id; - this.advance = advance; - this.metrics = new Metrics(this.font, bbox, this.advance); + this.metrics = new Metrics(font, bbox, advance); } - /** - public void reset(Path2D path) { - this.path = path; - this.metrics.reset(); - } */ - @Override public final Font getFont() { - return this.font; + return this.metrics.getFont(); } @Override @@ -211,7 +205,7 @@ public class TypecastGlyph implements Font.Glyph { @Override public final int hashCode() { // 31 * x == (x << 5) - x - final int hash = 31 + font.getName(Font.NAME_UNIQUNAME).hashCode(); + final int hash = 31 + getFont().getName(Font.NAME_UNIQUNAME).hashCode(); return ((hash << 5) - hash) + id; } } diff --git a/src/jogl/classes/jogamp/graph/font/typecast/ot/OTGlyph.java b/src/jogl/classes/jogamp/graph/font/typecast/ot/OTGlyph.java index 7bfffd58c..8ed450326 100644 --- a/src/jogl/classes/jogamp/graph/font/typecast/ot/OTGlyph.java +++ b/src/jogl/classes/jogamp/graph/font/typecast/ot/OTGlyph.java @@ -65,10 +65,10 @@ import com.jogamp.opengl.math.geom.AABBox; * @version $Id: Glyph.java,v 1.3 2007-02-21 12:23:54 davidsch Exp $ * @author <a href="mailto:[email protected]">David Schweinsberg</a>, Sven Gothel */ -public class OTGlyph { +public final class OTGlyph { - protected short _leftSideBearing; - protected int _advanceWidth; + private final short _leftSideBearing; + private final int _advanceWidth; private Point[] _points; AABBox _bbox; @@ -102,33 +102,32 @@ public class OTGlyph { } } - public void clearPointData() { + public final void clearPointData() { _points = null; } - public AABBox getBBox() { + public final AABBox getBBox() { return _bbox; } - public int getAdvanceWidth() { + public final int getAdvanceWidth() { return _advanceWidth; } - public short getLeftSideBearing() { + public final short getLeftSideBearing() { return _leftSideBearing; } - public Point getPoint(final int i) { + public final Point getPoint(final int i) { return _points[i]; } - public int getPointCount() { + public final int getPointCount() { return null != _points ? _points.length : 0; } /** * @param factor a 16.16 fixed value - */ public void scale(final int factor) { for (int i = 0; i < _points.length; i++) { //points[i].x = ( points[i].x * factor ) >> 6; @@ -139,11 +138,12 @@ public class OTGlyph { _leftSideBearing = (short)(( _leftSideBearing * factor) >> 6); _advanceWidth = (_advanceWidth * factor) >> 6; } + */ /** * Set the points of a glyph from the GlyphDescription */ - private void describe(final GlyphDescription gd) { + private final void describe(final GlyphDescription gd) { int endPtIndex = 0; _points = new Point[gd.getPointCount() /* + 2 */ ]; for (int i = 0; i < gd.getPointCount(); i++) { diff --git a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java index fce5c1fcc..6e6aaf58d 100644 --- a/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java +++ b/src/jogl/classes/jogamp/opengl/GLAutoDrawableBase.java @@ -57,9 +57,11 @@ import com.jogamp.opengl.GLStateKeeper; /** - * Abstract common code for GLAutoDrawable implementations. + * Abstract common code for GLAutoDrawable implementations + * utilizing multithreading, i.e. {@link #isThreadGLCapable()} always returns <code>true</code>. * * @see GLAutoDrawable + * @see GLAutoDrawable#getThreadingMode() * @see GLAutoDrawableDelegate * @see GLOffscreenAutoDrawable * @see GLOffscreenAutoDrawableImpl @@ -123,9 +125,6 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe helper.setSharedAutoDrawable(this, sharedAutoDrawable); } - /** Returns the recursive lock object of the upstream implementation, which synchronizes multithreaded access on top of {@link NativeSurface#lockSurface()}. */ - protected abstract RecursiveLock getLock(); - @Override public final GLStateKeeper.Listener setGLStateKeeperListener(final Listener l) { final GLStateKeeper.Listener pre = glStateKeeperListener; @@ -240,7 +239,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe System.err.println("GLAutoDrawableBase.sizeChanged: ("+getThreadName()+"): "+newWidth+"x"+newHeight+" - surfaceHandle 0x"+Long.toHexString(surfaceHandle)); } if( ! _drawable.getChosenGLCapabilities().isOnscreen() ) { - final RecursiveLock _lock = getLock(); + final RecursiveLock _lock = getUpstreamLock(); _lock.lock(); try { final GLDrawableImpl _drawableNew = GLDrawableHelper.resizeOffscreenDrawable(_drawable, context, newWidth, newHeight); @@ -280,7 +279,13 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe shallClose = true; } if( shallClose ) { - destroyAvoidAwareOfLocking(); + try { + destroyAvoidAwareOfLocking(); + } catch( final Throwable t ) { + // Intentionally catch and ignore exception, + // so the destroy mechanism of the native windowing system is not corrupted! + GLException.dumpThrowable("ignored", t); + } } } @@ -332,7 +337,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe * Calls {@link #destroyImplInLock()} while claiming the lock. */ protected final void defaultDestroy() { - final RecursiveLock lock = getLock(); + final RecursiveLock lock = getUpstreamLock(); lock.lock(); try { destroyImplInLock(); @@ -357,30 +362,52 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe preserveGLStateAtDestroy(false); preserveGLEventListenerState(); } + + 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 { helper.disposeGL(this, context, true); } catch (final GLException gle) { - gle.printStackTrace(); + exceptionOnDisposeGL = gle; } } context = null; } + + Throwable exceptionOnUnrealize = null; + Throwable exceptionOnDeviceClose = null; if( null != drawable ) { final AbstractGraphicsDevice device = drawable.getNativeSurface().getGraphicsConfiguration().getScreen().getDevice(); - drawable.setRealized(false); + try { + drawable.setRealized(false); + } catch( final Throwable re ) { + exceptionOnUnrealize = re; + } drawable = null; - if( ownsDevice ) { - device.close(); + try { + if( ownsDevice ) { + device.close(); + } + } catch (final Throwable re) { + exceptionOnDeviceClose = re; } } + + // 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); + } } public final void defaultSwapBuffers() throws GLException { - final RecursiveLock _lock = getLock(); + final RecursiveLock _lock = getUpstreamLock(); _lock.lock(); try { if(null != drawable) { @@ -421,7 +448,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe destroy(); return; } - final RecursiveLock _lock = getLock(); + final RecursiveLock _lock = getUpstreamLock(); _lock.lock(); try { if( null == context ) { @@ -452,7 +479,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe } protected final GLEventListener defaultDisposeGLEventListener(final GLEventListener listener, final boolean remove) { - final RecursiveLock _lock = getLock(); + final RecursiveLock _lock = getUpstreamLock(); _lock.lock(); try { return helper.disposeGLEventListener(GLAutoDrawableBase.this, drawable, context, listener, remove); @@ -473,7 +500,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe @Override public final GLContext setContext(final GLContext newCtx, final boolean destroyPrevCtx) { - final RecursiveLock lock = getLock(); + final RecursiveLock lock = getUpstreamLock(); lock.lock(); try { final GLContext oldCtx = context; @@ -571,16 +598,21 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe } @Override - public final boolean invoke(final boolean wait, final GLRunnable glRunnable) { + public final boolean invoke(final boolean wait, final GLRunnable glRunnable) throws IllegalStateException { return helper.invoke(this, wait, glRunnable); } @Override - public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) { + public boolean invoke(final boolean wait, final List<GLRunnable> glRunnables) throws IllegalStateException { return helper.invoke(this, wait, glRunnables); } @Override + public void flushGLRunnables() { + helper.flushGLRunnables(); + } + + @Override public final void setAutoSwapBufferMode(final boolean enable) { helper.setAutoSwapBufferMode(enable); } @@ -604,6 +636,15 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe return additionalCtxCreationFlags; } + /** + * {@inheritDoc} + * <p> + * Implementation always supports multithreading, hence method always returns <code>true</code>. + * </p> + */ + @Override + public final boolean isThreadGLCapable() { return true; } + // // FPSCounter // @@ -664,7 +705,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe @Override public final GLContext createContext(final GLContext shareWith) { - final RecursiveLock lock = getLock(); + final RecursiveLock lock = getUpstreamLock(); lock.lock(); try { if(drawable != null) { @@ -680,7 +721,7 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe @Override public final void setRealized(final boolean realized) { - final RecursiveLock _lock = getLock(); + final RecursiveLock _lock = getUpstreamLock(); _lock.lock(); try { final GLDrawable _drawable = drawable; @@ -727,6 +768,12 @@ public abstract class GLAutoDrawableBase implements GLAutoDrawable, GLStateKeepe } @Override + public final GLCapabilitiesImmutable getRequestedGLCapabilities() { + final GLDrawable _drawable = drawable; + return null != _drawable ? _drawable.getRequestedGLCapabilities() : null; + } + + @Override public final GLProfile getGLProfile() { final GLDrawable _drawable = drawable; return null != _drawable ? _drawable.getGLProfile() : null; diff --git a/src/jogl/classes/jogamp/opengl/GLContextImpl.java b/src/jogl/classes/jogamp/opengl/GLContextImpl.java index c175243ae..01478a422 100644 --- a/src/jogl/classes/jogamp/opengl/GLContextImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLContextImpl.java @@ -108,10 +108,17 @@ public abstract class GLContextImpl extends GLContext { private final int[] boundFBOTarget = new int[] { 0, 0 }; // { draw, read } private int defaultVAO = 0; + /** + * <ul> + * <li>[GLAutoDrawable.upstreamLock].lock()</li> + * <li>drawable.surface.lock()</li> + * <li>contextLock.lock()</li> + * </ul> + */ protected GLDrawableImpl drawable; protected GLDrawableImpl drawableRead; - private volatile boolean pixelDataEvaluated; + private boolean pixelDataEvaluated; private int /* pixelDataInternalFormat, */ pixelDataFormat, pixelDataType; protected GL gl; @@ -137,11 +144,16 @@ public abstract class GLContextImpl extends GLContext { public GLContextImpl(final GLDrawableImpl drawable, final GLContext shareWith) { super(); + if( null == drawable ) { + throw new IllegalArgumentException("Null drawable"); + } bufferStateTracker = new GLBufferStateTracker(); if ( null != shareWith ) { GLContextShareSet.registerSharing(this, shareWith); bufferObjectTracker = ((GLContextImpl)shareWith).getBufferObjectTracker(); - assert (bufferObjectTracker != null) : "shared context hash null GLBufferObjectTracker: "+shareWith; + if( null == bufferObjectTracker ) { + throw new InternalError("shared-master context hash null GLBufferObjectTracker: "+toHexString(shareWith.hashCode())); + } } else { bufferObjectTracker = new GLBufferObjectTracker(); } @@ -189,21 +201,28 @@ public abstract class GLContextImpl extends GLContext { @Override public final GLDrawable setGLReadDrawable(final GLDrawable read) { - if(!isGLReadDrawableAvailable()) { - throw new GLException("Setting read drawable feature not available"); - } - final boolean lockHeld = lock.isOwner(Thread.currentThread()); - if(lockHeld) { - release(); - } else if(lock.isLockedByOtherThread()) { // still could glitch .. - throw new GLException("GLContext current by other thread ("+lock.getOwner()+"), operation not allowed."); - } - final GLDrawable old = drawableRead; - drawableRead = ( null != read ) ? (GLDrawableImpl) read : drawable; - if(lockHeld) { - makeCurrent(); - } - return old; + // Validate constraints first! + if(!isGLReadDrawableAvailable()) { + throw new GLException("Setting read drawable feature not available"); + } + final Thread currentThread = Thread.currentThread(); + if( lock.isLockedByOtherThread() ) { + throw new GLException("GLContext current by other thread "+lock.getOwner().getName()+", operation not allowed on this thread "+currentThread.getName()); + } + final boolean lockHeld = lock.isOwner(currentThread); + if( lockHeld && lock.getHoldCount() > 1 ) { + // would need to makeCurrent * holdCount + throw new GLException("GLContext is recursively locked - unsupported for setGLDrawable(..)"); + } + if(lockHeld) { + release(false); + } + final GLDrawable old = drawableRead; + drawableRead = ( null != read ) ? (GLDrawableImpl) read : drawable; + if(lockHeld) { + makeCurrent(); + } + return old; } @Override @@ -213,43 +232,46 @@ public abstract class GLContextImpl extends GLContext { @Override public final GLDrawable setGLDrawable(final GLDrawable readWrite, final boolean setWriteOnly) { - if( drawable == readWrite && ( setWriteOnly || drawableRead == readWrite ) ) { - return drawable; // no change. - } - final Thread currentThread = Thread.currentThread(); - if( lock.isLockedByOtherThread() ) { - throw new GLException("GLContext current by other thread "+lock.getOwner().getName()+", operation not allowed on this thread "+currentThread.getName()); - } - final boolean lockHeld = lock.isOwner(currentThread); - if( lockHeld && lock.getHoldCount() > 1 ) { - // would need to makeCurrent * holdCount - throw new GLException("GLContext is recursively locked - unsupported for setGLDrawable(..)"); - } - final GLDrawableImpl old = drawable; - if( isCreated() && null != old && old.isRealized() ) { - if(!lockHeld) { - makeCurrent(); - } - associateDrawable(false); - if(!lockHeld) { - release(); - } - } - if(lockHeld) { - release(); - } - if( !setWriteOnly || drawableRead == drawable ) { // if !setWriteOnly || !explicitReadDrawable - drawableRead = (GLDrawableImpl) readWrite; - } - drawableRetargeted |= null != drawable && readWrite != drawable; - drawable = (GLDrawableImpl) readWrite ; - if( isCreated() && null != drawable && drawable.isRealized() ) { - makeCurrent(true); // implicit: associateDrawable(true) - if( !lockHeld ) { - release(); - } - } - return old; + // Validate constraints first! + final Thread currentThread = Thread.currentThread(); + if( lock.isLockedByOtherThread() ) { + throw new GLException("GLContext current by other thread "+lock.getOwner().getName()+", operation not allowed on this thread "+currentThread.getName()); + } + final boolean lockHeld = lock.isOwner(currentThread); + if( lockHeld && lock.getHoldCount() > 1 ) { + // would need to makeCurrent * holdCount + throw new GLException("GLContext is recursively locked - unsupported for setGLDrawable(..)"); + } + if( drawable == readWrite && ( setWriteOnly || drawableRead == readWrite ) ) { + return drawable; // no change. + } + final GLDrawableImpl old = drawable; + if( isCreated() && null != old && old.isRealized() ) { + if(!lockHeld) { + makeCurrent(); + } + // sync GL ctx w/ drawable's framebuffer before de-association + gl.glFinish(); + associateDrawable(false); + if(!lockHeld) { + release(false); + } + } + if(lockHeld) { + release(false); + } + if( !setWriteOnly || drawableRead == drawable ) { // if !setWriteOnly || !explicitReadDrawable + drawableRead = (GLDrawableImpl) readWrite; + } + drawableRetargeted |= null != drawable && readWrite != drawable; + drawable = (GLDrawableImpl) readWrite ; + if( isCreated() && null != drawable && drawable.isRealized() ) { + makeCurrent(true); // implicit: associateDrawable(true) + if( !lockHeld ) { + release(false); + } + } + return old; } @Override @@ -258,7 +280,7 @@ public abstract class GLContextImpl extends GLContext { } public final GLDrawableImpl getDrawableImpl() { - return (GLDrawableImpl) getGLDrawable(); + return drawable; } @Override @@ -315,56 +337,57 @@ public abstract class GLContextImpl extends GLContext { release(false); } private void release(final boolean inDestruction) throws GLException { - if( TRACE_SWITCH ) { - System.err.println(getThreadName() +": GLContext.ContextSwitch[release.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+", inDestruction: "+inDestruction+", "+lock); - } - if ( !lock.isOwner(Thread.currentThread()) ) { - final String msg = getThreadName() +": Context not current on thread, obj " + toHexString(hashCode())+", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+", inDestruction: "+inDestruction+", "+lock; - if( DEBUG_TRACE_SWITCH ) { - System.err.println(msg); - if( null != lastCtxReleaseStack ) { - System.err.print("Last release call: "); - lastCtxReleaseStack.printStackTrace(); - } else { - System.err.println("Last release call: NONE"); - } - } - throw new GLException(msg); - } - - Throwable drawableContextMadeCurrentException = null; - final boolean actualRelease = ( inDestruction || lock.getHoldCount() == 1 ) && 0 != contextHandle; - try { - if( actualRelease ) { - if( !inDestruction ) { - try { - contextMadeCurrent(false); - } catch (final Throwable t) { - drawableContextMadeCurrentException = t; - } - } - releaseImpl(); - } - } finally { - // exception prone .. - if( actualRelease ) { - setCurrent(null); + if( TRACE_SWITCH ) { + final long drawH = null != drawable ? drawable.getHandle() : 0; + System.err.println(getThreadName() +": GLContext.ContextSwitch[release.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+(null!=drawable)+" "+toHexString(drawH)+", inDestruction: "+inDestruction+", "+lock); } - drawable.unlockSurface(); - lock.unlock(); - if( DEBUG_TRACE_SWITCH ) { - final String msg = getThreadName() +": GLContext.ContextSwitch[release.X]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+(actualRelease?"switch":"keep ")+" - "+lock; - lastCtxReleaseStack = new Throwable(msg); - if( TRACE_SWITCH ) { + if ( !lock.isOwner(Thread.currentThread()) ) { + final long drawH = null != drawable ? drawable.getHandle() : 0; + final String msg = getThreadName() +": Context not current on thread, obj " + toHexString(hashCode())+", ctx "+toHexString(contextHandle)+", surf "+(null!=drawable)+" "+toHexString(drawH)+", inDestruction: "+inDestruction+", "+lock; + if( DEBUG_TRACE_SWITCH ) { System.err.println(msg); - // Thread.dumpStack(); + if( null != lastCtxReleaseStack ) { + System.err.print("Last release call: "); + lastCtxReleaseStack.printStackTrace(); + } else { + System.err.println("Last release call: NONE"); + } } + throw new GLException(msg); } - } - if(null != drawableContextMadeCurrentException) { - throw new GLException("GLContext.release(false) during GLDrawableImpl.contextMadeCurrent(this, false)", drawableContextMadeCurrentException); - } + Throwable drawableContextMadeCurrentException = null; + final boolean actualRelease = ( inDestruction || lock.getHoldCount() == 1 ) && 0 != contextHandle; + try { + if( actualRelease ) { + if( !inDestruction ) { + try { + contextMadeCurrent(false); + } catch (final Throwable t) { + drawableContextMadeCurrentException = t; + } + } + releaseImpl(); + } + } finally { + // exception prone .. + if( actualRelease ) { + setCurrent(null); + } + lock.unlock(); + drawable.unlockSurface(); + if( DEBUG_TRACE_SWITCH ) { + final String msg = getThreadName() +": GLContext.ContextSwitch[release.X]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+(actualRelease?"switch":"keep ")+" - "+lock; + lastCtxReleaseStack = new Throwable(msg); + if( TRACE_SWITCH ) { + System.err.println(msg); + // Thread.dumpStack(); + } + } + } + if(null != drawableContextMadeCurrentException) { + throw new GLException("GLContext.release(false) during GLDrawableImpl.contextMadeCurrent(this, false)", drawableContextMadeCurrentException); + } } private Throwable lastCtxReleaseStack = null; protected abstract void releaseImpl() throws GLException; @@ -374,7 +397,7 @@ public abstract class GLContextImpl extends GLContext { if ( DEBUG_TRACE_SWITCH ) { final long drawH = null != drawable ? drawable.getHandle() : 0; System.err.println(getThreadName() + ": GLContextImpl.destroy.0: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle) + - ", surf "+toHexString(drawH)+", isShared "+GLContextShareSet.isShared(this)+" - "+lock); + ", surf "+(null!=drawable)+" "+toHexString(drawH)+", isShared "+GLContextShareSet.isShared(this)+" - "+lock); } if ( 0 != contextHandle ) { // isCreated() ? if ( null == drawable ) { @@ -393,9 +416,9 @@ public abstract class GLContextImpl extends GLContext { // Must hold the lock around the destroy operation to make sure we // don't destroy the context while another thread renders to it. lock.lock(); // holdCount++ -> 1 - n (1: not locked, 2-n: destroy while rendering) - if ( lock.getHoldCount() > 2 ) { - final String msg = getThreadName() + ": GLContextImpl.destroy: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle); - if ( DEBUG_TRACE_SWITCH ) { + if ( DEBUG_TRACE_SWITCH ) { + if ( lock.getHoldCount() > 2 ) { + final String msg = getThreadName() + ": GLContextImpl.destroy: obj " + toHexString(hashCode()) + ", ctx " + toHexString(contextHandle); System.err.println(msg+" - Lock was hold more than once - makeCurrent/release imbalance: "+lock); Thread.dumpStack(); } @@ -519,8 +542,16 @@ public abstract class GLContextImpl extends GLContext { } protected final int makeCurrent(boolean forceDrawableAssociation) throws GLException { + final boolean hasDrawable = null != drawable; if( TRACE_SWITCH ) { - System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+toHexString(drawable.getHandle())+" - "+lock); + final long drawH = null != drawable ? drawable.getHandle() : 0; + System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+", surf "+hasDrawable+" "+toHexString(drawH)+" - "+lock); + } + if( !hasDrawable ) { + if( DEBUG_TRACE_SWITCH ) { + System.err.println(getThreadName() +": GLContext.ContextSwitch[makeCurrent.X0]: obj " + toHexString(hashCode()) + ", ctx "+toHexString(contextHandle)+" - NULL Drawable - CONTEXT_NOT_CURRENT - "+lock); + } + return CONTEXT_NOT_CURRENT; } // Note: the surface is locked within [makeCurrent .. swap .. release] @@ -589,12 +620,12 @@ public abstract class GLContextImpl extends GLContext { } } - if (res != CONTEXT_NOT_CURRENT) { + if (res != CONTEXT_NOT_CURRENT) { // still locked! setCurrent(this); if(res == CONTEXT_CURRENT_NEW) { // check if the drawable's and the GL's GLProfile are equal // throws an GLException if not - getGLDrawable().getGLProfile().verifyEquality(gl.getGLProfile()); + drawable.getGLProfile().verifyEquality(gl.getGLProfile()); glDebugHandler.init( isGL2GL3() && isGLDebugEnabled() ); @@ -632,6 +663,10 @@ public abstract class GLContextImpl extends GLContext { return res; } + private final GLContextImpl getOtherSharedMaster() { + final GLContextImpl sharedMaster = (GLContextImpl) GLContextShareSet.getSharedMaster(this); + return this != sharedMaster ? sharedMaster : null; + } private final int makeCurrentWithinLock(final int surfaceLockRes) throws GLException { if (!isCreated()) { if( 0 >= drawable.getSurfaceWidth() || 0 >= drawable.getSurfaceHeight() ) { @@ -646,20 +681,23 @@ public abstract class GLContextImpl extends GLContext { additionalCtxCreationFlags |= GLContext.CTX_OPTION_DEBUG ; } - final GLContextImpl shareWith = (GLContextImpl) GLContextShareSet.getCreatedShare(this); - final long shareWithHandle; - if (null != shareWith) { - shareWith.getDrawableImpl().lockSurface(); - shareWithHandle = shareWith.getHandle(); - if (0 == shareWithHandle) { - throw new GLException("GLContextShareSet returned an invalid OpenGL context: "+this); + final boolean created; + final GLContextImpl sharedMaster = getOtherSharedMaster(); + if ( null != sharedMaster ) { + if ( NativeSurface.LOCK_SURFACE_NOT_READY >= sharedMaster.drawable.lockSurface() ) { + throw new GLException("GLContextShareSet could not lock sharedMaster surface: "+sharedMaster.drawable); } - } else { - shareWithHandle = 0; } - final boolean created; try { - created = createImpl(shareWithHandle); // may throws exception if fails + if ( null != sharedMaster ) { + final long sharedMasterHandle = sharedMaster.getHandle(); + if ( 0 == sharedMasterHandle ) { + throw new GLException("GLContextShareSet returned an invalid sharedMaster context: "+sharedMaster); + } + created = createImpl(sharedMasterHandle); // may throws exception if fails + } else { + created = createImpl(0); // may throws exception if fails + } if( created && hasNoDefaultVAO() ) { final int[] tmp = new int[1]; final GL rootGL = gl.getRootGL(); @@ -671,8 +709,8 @@ public abstract class GLContextImpl extends GLContext { } } } finally { - if (null != shareWith) { - shareWith.getDrawableImpl().unlockSurface(); + if ( null != sharedMaster ) { + sharedMaster.drawable.unlockSurface(); } } if ( DEBUG_TRACE_SWITCH ) { @@ -702,13 +740,32 @@ public abstract class GLContextImpl extends GLContext { } else { reqMajor = ctxVersion.getMajor(); } + final boolean isCompat; if( 0 != ( ctxOptions & GLContext.CTX_PROFILE_CORE) ) { reqProfile = GLContext.CTX_PROFILE_CORE; + isCompat = false; } else { reqProfile = GLContext.CTX_PROFILE_COMPAT; + isCompat = true; + } + GLContext.mapAvailableGLVersion(device, reqMajor, reqProfile, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); + // Perform all required profile mappings + if( isCompat ) { + // COMPAT via non ARB + GLContext.mapAvailableGLVersion(device, reqMajor, GLContext.CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); + if( reqMajor >= 4 ) { + GLContext.mapAvailableGLVersion(device, 3, reqProfile, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); + GLContext.mapAvailableGLVersion(device, 3, GLContext.CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); + } + if( reqMajor >= 3 ) { + GLContext.mapAvailableGLVersion(device, 2, reqProfile, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); + } + } else { + // CORE via non ARB, unlikely, however .. + if( reqMajor >= 4 ) { + GLContext.mapAvailableGLVersion(device, 3, reqProfile, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); + } } - GLContext.mapAvailableGLVersion(device, reqMajor, reqProfile, - ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); GLContext.setAvailableGLVersionsSet(device); if (DEBUG) { @@ -749,8 +806,8 @@ public abstract class GLContextImpl extends GLContext { * * The implementation <b>must</b> leave the context current.<br> * - * @param share the shared context or null - * @return the valid and current context if successful, or null + * @param sharedWithHandle the shared context handle or 0 + * @return true if successful, or false * @throws GLException */ protected abstract boolean createImpl(long sharedWithHandle) throws GLException ; @@ -818,6 +875,9 @@ public abstract class GLContextImpl extends GLContext { */ protected final long createContextARB(final long share, final boolean direct) { + if( GLProfile.disableOpenGLARBContext ) { + return 0; + } final AbstractGraphicsConfiguration config = drawable.getNativeSurface().getGraphicsConfiguration(); final AbstractGraphicsDevice device = config.getScreen().getDevice(); @@ -879,7 +939,7 @@ public abstract class GLContextImpl extends GLContext { /** * OSX 10.9 GLRendererQuirks.GL4NeedsGL3Request, quirk is added as usual @ setRendererQuirks(..) */ - if( !hasGL4 && !hasGL3 ) { + if( !GLProfile.disableOpenGLCore && !hasGL4 && !hasGL3 ) { hasGL3 = createContextARBMapVersionsAvailable(3, CTX_PROFILE_CORE); // GL3 success |= hasGL3; if( hasGL3 ) { @@ -896,25 +956,27 @@ public abstract class GLContextImpl extends GLContext { } } } - if( !hasGL4 ) { - hasGL4 = createContextARBMapVersionsAvailable(4, CTX_PROFILE_CORE); // GL4 - success |= hasGL4; - if( hasGL4 ) { - if( 0 == ( CTX_IMPL_ACCEL_SOFT & ctxOptions ) ) { - // Map hw-accel GL4 to all lower core profiles: GL3 - GLContext.mapAvailableGLVersion(device, 3, CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); - if( PROFILE_ALIASING ) { - hasGL3 = true; + if( !GLProfile.disableOpenGLCore ) { + if( !hasGL4 ) { + hasGL4 = createContextARBMapVersionsAvailable(4, CTX_PROFILE_CORE); // GL4 + success |= hasGL4; + if( hasGL4 ) { + if( 0 == ( CTX_IMPL_ACCEL_SOFT & ctxOptions ) ) { + // Map hw-accel GL4 to all lower core profiles: GL3 + GLContext.mapAvailableGLVersion(device, 3, CTX_PROFILE_CORE, ctxVersion.getMajor(), ctxVersion.getMinor(), ctxOptions); + if( PROFILE_ALIASING ) { + hasGL3 = true; + } } + resetStates(false); // clean context states, since creation was temporary } - resetStates(false); // clean context states, since creation was temporary } - } - if( !hasGL3 ) { - hasGL3 = createContextARBMapVersionsAvailable(3, CTX_PROFILE_CORE); // GL3 - success |= hasGL3; - if( hasGL3 ) { - resetStates(false); // clean this context states, since creation was temporary + if( !hasGL3 ) { + hasGL3 = createContextARBMapVersionsAvailable(3, CTX_PROFILE_CORE); // GL3 + success |= hasGL3; + if( hasGL3 ) { + resetStates(false); // clean this context states, since creation was temporary + } } } if( !hasGL4bc ) { @@ -1372,7 +1434,7 @@ public abstract class GLContextImpl extends GLContext { } if(null==this.gl || !verifyInstance(gl.getGLProfile(), "Impl", this.gl)) { - setGL( createGL( getGLDrawable().getGLProfile() ) ); + setGL( createGL( drawable.getGLProfile() ) ); } updateGLXProcAddressTable(); @@ -1545,6 +1607,10 @@ public abstract class GLContextImpl extends GLContext { ctxProfileBits &= ~ ( GLContext.CTX_IMPL_ES2_COMPAT | GLContext.CTX_IMPL_ES3_COMPAT ) ; } + if(!isCurrentContextHardwareRasterizer()) { + ctxProfileBits |= GLContext.CTX_IMPL_ACCEL_SOFT; + } + final VersionNumberString vendorVersion = GLVersionNumber.createVendorVersion(glVersion); setRendererQuirks(adevice, getDrawableImpl().getFactoryImpl(), @@ -1558,10 +1624,6 @@ public abstract class GLContextImpl extends GLContext { return false; } - if(!isCurrentContextHardwareRasterizer()) { - ctxProfileBits |= GLContext.CTX_IMPL_ACCEL_SOFT; - } - contextFQN = getContextFQN(adevice, major, minor, ctxProfileBits); if (DEBUG) { System.err.println(getThreadName() + ": GLContext.setGLFuncAvail.0 validated FQN: "+contextFQN+" - "+GLContext.getGLVersion(major, minor, ctxProfileBits, glVersion)); @@ -1669,9 +1731,6 @@ public abstract class GLContextImpl extends GLContext { final int reqMajor, final int reqMinor, final int reqCTP, final int major, final int minor, final int ctp, final VersionNumberString vendorVersion, final boolean withinGLVersionsMapping) { - final int[] quirks = new int[GLRendererQuirks.COUNT + 1]; // + 1 ( NoFullFBOSupport ) - int i = 0; - final String MesaSP = "Mesa "; // final String MesaRendererAMDsp = " AMD "; final String MesaRendererIntelsp = "Intel(R)"; @@ -1684,6 +1743,8 @@ public abstract class GLContextImpl extends GLContext { final boolean isDriverATICatalyst = !isDriverMesa && ( glVendor.contains("ATI Technologies") || glRenderer.startsWith("ATI ") ); final boolean isDriverNVIDIAGeForce = !isDriverMesa && ( glVendor.contains("NVIDIA Corporation") || glRenderer.contains("NVIDIA ") ); + final GLRendererQuirks quirks = new GLRendererQuirks(); + // // General Quirks // @@ -1693,14 +1754,14 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: ES req "+reqMajor+" and 2 < "+major); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); if( withinGLVersionsMapping ) { // Thread safe due to single threaded initialization! - GLRendererQuirks.addStickyDeviceQuirks(adevice, quirks, i-1, 1); + GLRendererQuirks.addStickyDeviceQuirk(adevice, quirk); } else { // FIXME: Remove when moving EGL/ES to ARB ctx creation synchronized(GLContextImpl.class) { - GLRendererQuirks.addStickyDeviceQuirks(adevice, quirks, i-1, 1); + GLRendererQuirks.addStickyDeviceQuirk(adevice, quirk); } } } @@ -1718,17 +1779,17 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } if( Platform.getOSVersionNumber().compareTo(Platform.OSXVersion.Mavericks) >= 0 && 3==reqMajor && 4==major ) { final int quirk = GLRendererQuirks.GL4NeedsGL3Request; if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", OS Version "+Platform.getOSVersionNumber()+", req "+reqMajor+"."+reqMinor); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); if( withinGLVersionsMapping ) { // Thread safe due to single threaded initialization! - GLRendererQuirks.addStickyDeviceQuirks(adevice, quirks, i-1, 1); + GLRendererQuirks.addStickyDeviceQuirk(adevice, quirk); } } if( isDriverNVIDIAGeForce ) { @@ -1738,14 +1799,14 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", OS Version "+Platform.getOSVersionNumber()+", Renderer "+glRenderer); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } if( Platform.getOSVersionNumber().compareTo(Platform.OSXVersion.Lion) < 0 ) { // < OSX 10.7.0 w/ NV has unstable GLSL final int quirk = GLRendererQuirks.GLSLNonCompliant; if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", OS Version "+Platform.getOSVersionNumber()+", Renderer "+glRenderer); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } } } else if( isWindows ) { @@ -1757,7 +1818,7 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } if( isDriverATICatalyst ) { @@ -1769,7 +1830,7 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType()+", [Vendor "+glVendor+" or Renderer "+glRenderer+"], driverVersion "+vendorVersion); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } if( Platform.getOSVersionNumber().compareTo(winXPVersionNumber) <= 0 ) { @@ -1777,7 +1838,7 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS-Version "+Platform.getOSType()+" "+Platform.getOSVersionNumber()+", [Vendor "+glVendor+" or Renderer "+glRenderer+"]"); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } } } else if( Platform.OSType.ANDROID == Platform.getOSType() ) { @@ -1790,14 +1851,14 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + ", Renderer " + glRenderer); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } if( glRenderer.contains("Immersion.16") ) { - final int quirk = GLRendererQuirks.GLSharedContextBuggy; - if(DEBUG) { - System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + ", Renderer " + glRenderer); - } - quirks[i++] = quirk; + final int quirk = GLRendererQuirks.GLSharedContextBuggy; + if(DEBUG) { + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + ", Renderer " + glRenderer); + } + quirks.addQuirk( quirk ); } } @@ -1818,21 +1879,21 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 Renderer=" + glRenderer + ", Version=[vendor " + vendorVersion + ", GL " + glVersion+"]"); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } } else if( isDriverATICatalyst ) { { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 Renderer=" + glRenderer); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } } else if( jogamp.nativewindow.x11.X11Util.getMarkAllDisplaysUnclosable() ) { { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11Util Downstream"); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } } } @@ -1843,6 +1904,7 @@ public abstract class GLContextImpl extends GLContext { // RENDERER related quirks // if( isDriverMesa ) { + final VersionNumber mesaSafeFBOVersion = new VersionNumber(8, 0, 0); final VersionNumber mesaIntelBuggySharedCtx921 = new VersionNumber(9, 2, 1); { @@ -1850,88 +1912,103 @@ public abstract class GLContextImpl extends GLContext { if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); } - if( hwAccel /* glRenderer.contains( MesaRendererIntelsp ) || glRenderer.contains( MesaRendererAMDsp ) */ ) - { + if( hwAccel ) { + // hardware-acceleration final int quirk = GLRendererQuirks.NoDoubleBufferedPBuffer; if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); + } else { + // software + if( vendorVersion.compareTo(mesaSafeFBOVersion) < 0 ) { // FIXME: Is it fixed in >= 8.0.0 ? + final int quirk = GLRendererQuirks.BuggyColorRenderbuffer; + if(DEBUG) { + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer + " / Mesa-Version "+vendorVersion); + } + quirks.addQuirk( quirk ); + } } if (compatCtx && (major > 3 || (major == 3 && minor >= 1))) { - // FIXME: Apply vendor version constraints! - final int quirk = GLRendererQuirks.GLNonCompliant; - if(DEBUG) { - System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer); - } - quirks[i++] = quirk; + // FIXME: Apply vendor version constraints! + final int quirk = GLRendererQuirks.GLNonCompliant; + if(DEBUG) { + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: Renderer " + glRenderer); + } + quirks.addQuirk( quirk ); } if( glRenderer.contains( MesaRendererIntelsp ) && vendorVersion.compareTo(mesaIntelBuggySharedCtx921) >= 0 && isX11 ) { // FIXME: When is it fixed ? - final int quirk = GLRendererQuirks.GLSharedContextBuggy; - if(DEBUG) { - System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 / Renderer " + glRenderer + " / Mesa-Version "+vendorVersion); - } - quirks[i++] = quirk; + final int quirk = GLRendererQuirks.GLSharedContextBuggy; + if(DEBUG) { + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 / Renderer " + glRenderer + " / Mesa-Version "+vendorVersion); + } + quirks.addQuirk( quirk ); } if( glVendor.contains( "nouveau" ) // FIXME: && vendorVersion.compareTo(nouveauBuggyMSAAFixed) < 0 ) { - final int quirk = GLRendererQuirks.NoMultiSamplingBuffers; - if(DEBUG) { - System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 / Renderer " + glRenderer + " / Vendor "+glVendor); - } - quirks[i++] = quirk; - if( withinGLVersionsMapping ) { - // Thread safe due to single threaded initialization! - GLRendererQuirks.addStickyDeviceQuirks(adevice, quirks, i-1, 1); - } + final int quirk = GLRendererQuirks.NoMultiSamplingBuffers; + if(DEBUG) { + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: X11 / Renderer " + glRenderer + " / Vendor "+glVendor); + } + quirks.addQuirk( quirk ); + if( withinGLVersionsMapping ) { + // Thread safe due to single threaded initialization! + GLRendererQuirks.addStickyDeviceQuirk(adevice, quirk); + } } - if( isWindows && glRenderer.contains("SVGA3D") ) { - final VersionNumber mesaSafeFBOVersion = new VersionNumber(8, 0, 0); - if ( vendorVersion.compareTo(mesaSafeFBOVersion) < 0 ) { // includes: vendorVersion.isZero() - final int quirk = GLRendererQuirks.NoFullFBOSupport; - if(DEBUG) { - System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + " / Renderer " + glRenderer + " / Mesa-Version "+vendorVersion); - } - quirks[i++] = quirk; + if( isWindows && glRenderer.contains("SVGA3D") && vendorVersion.compareTo(mesaSafeFBOVersion) < 0 ) { + final int quirk = GLRendererQuirks.NoFullFBOSupport; + if(DEBUG) { + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: OS "+Platform.getOSType() + " / Renderer " + glRenderer + " / Mesa-Version "+vendorVersion); } + quirks.addQuirk( quirk ); } } // // Property related quirks // - if( FORCE_MIN_FBO_SUPPORT ) { - final int quirk = GLRendererQuirks.NoFullFBOSupport; + if( FORCE_NO_COLOR_RENDERBUFFER ) { + final int quirk = GLRendererQuirks.BuggyColorRenderbuffer; if(DEBUG) { System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: property"); } - quirks[i++] = quirk; + quirks.addQuirk( quirk ); + } + if( FORCE_MIN_FBO_SUPPORT || quirks.exist(GLRendererQuirks.BuggyColorRenderbuffer) ) { + final int quirk = GLRendererQuirks.NoFullFBOSupport; + if(DEBUG) { + final String causeProps = FORCE_MIN_FBO_SUPPORT ? "property, " : ""; + final String causeQuirk = quirks.exist(GLRendererQuirks.BuggyColorRenderbuffer) ? "BuggyColorRenderbuffer" : ""; + System.err.println("Quirk: "+GLRendererQuirks.toString(quirk)+": cause: "+causeProps+causeQuirk); + } + quirks.addQuirk( quirk ); } - glRendererQuirks = new GLRendererQuirks(quirks, 0, i); if(DEBUG) { - System.err.println("Quirks local.0: "+glRendererQuirks); + System.err.println("Quirks local.0: "+quirks); } { // Merge sticky quirks, thread safe due to single threaded initialization! - GLRendererQuirks.pushStickyDeviceQuirks(adevice, glRendererQuirks); + GLRendererQuirks.pushStickyDeviceQuirks(adevice, quirks); final AbstractGraphicsDevice factoryDefaultDevice = factory.getDefaultDevice(); if( !GLRendererQuirks.areSameStickyDevice(factoryDefaultDevice, adevice) ) { - GLRendererQuirks.pushStickyDeviceQuirks(factoryDefaultDevice, glRendererQuirks); + GLRendererQuirks.pushStickyDeviceQuirks(factoryDefaultDevice, quirks); } if( esCtx ) { final AbstractGraphicsDevice eglFactoryDefaultDevice = GLDrawableFactory.getEGLFactory().getDefaultDevice(); if( !GLRendererQuirks.areSameStickyDevice(eglFactoryDefaultDevice, adevice) && !GLRendererQuirks.areSameStickyDevice(eglFactoryDefaultDevice, factoryDefaultDevice) ) { - GLRendererQuirks.pushStickyDeviceQuirks(eglFactoryDefaultDevice, glRendererQuirks); + GLRendererQuirks.pushStickyDeviceQuirks(eglFactoryDefaultDevice, quirks); } } } + glRendererQuirks = quirks; if(DEBUG) { System.err.println("Quirks local.X: "+glRendererQuirks); System.err.println("Quirks sticky on "+adevice+": "+GLRendererQuirks.getStickyDeviceQuirks(adevice)); @@ -2106,35 +2183,31 @@ public abstract class GLContextImpl extends GLContext { } private final void evalPixelDataType() { - if(!pixelDataEvaluated) { - synchronized(this) { - if(!pixelDataEvaluated) { - boolean ok = false; - /* if(isGL2GL3() && 3 == components) { - pixelDataInternalFormat=GL.GL_RGB; - pixelDataFormat=GL.GL_RGB; - pixelDataType = GL.GL_UNSIGNED_BYTE; - ok = true; - } else */ if( isGLES2Compatible() || isExtensionAvailable(GLExtensions.OES_read_format) ) { - final int[] glImplColorReadVals = new int[] { 0, 0 }; - gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_FORMAT, glImplColorReadVals, 0); - gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_TYPE, glImplColorReadVals, 1); - // pixelDataInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB; - pixelDataFormat = glImplColorReadVals[0]; - pixelDataType = glImplColorReadVals[1]; - ok = 0 != pixelDataFormat && 0 != pixelDataType; - } - if( !ok ) { - // RGBA read is safe for all GL profiles - // pixelDataInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB; - pixelDataFormat=GL.GL_RGBA; - pixelDataType = GL.GL_UNSIGNED_BYTE; - } - // TODO: Consider: - // return gl.isGL2GL3()?GL2GL3.GL_UNSIGNED_INT_8_8_8_8_REV:GL.GL_UNSIGNED_SHORT_5_5_5_1; - pixelDataEvaluated = true; - } - } + if(!pixelDataEvaluated) { // only valid while context is made current + boolean ok = false; + /* if(isGL2GL3() && 3 == components) { + pixelDataInternalFormat=GL.GL_RGB; + pixelDataFormat=GL.GL_RGB; + pixelDataType = GL.GL_UNSIGNED_BYTE; + ok = true; + } else */ if( isGLES2Compatible() || isExtensionAvailable(GLExtensions.OES_read_format) ) { + final int[] glImplColorReadVals = new int[] { 0, 0 }; + gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_FORMAT, glImplColorReadVals, 0); + gl.glGetIntegerv(GL.GL_IMPLEMENTATION_COLOR_READ_TYPE, glImplColorReadVals, 1); + // pixelDataInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB; + pixelDataFormat = glImplColorReadVals[0]; + pixelDataType = glImplColorReadVals[1]; + ok = 0 != pixelDataFormat && 0 != pixelDataType; + } + if( !ok ) { + // RGBA read is safe for all GL profiles + // pixelDataInternalFormat = (4 == components) ? GL.GL_RGBA : GL.GL_RGB; + pixelDataFormat=GL.GL_RGBA; + pixelDataType = GL.GL_UNSIGNED_BYTE; + } + // TODO: Consider: + // return gl.isGL2GL3()?GL2GL3.GL_UNSIGNED_INT_8_8_8_8_REV:GL.GL_UNSIGNED_SHORT_5_5_5_1; + pixelDataEvaluated = true; } } diff --git a/src/jogl/classes/jogamp/opengl/GLContextShareSet.java b/src/jogl/classes/jogamp/opengl/GLContextShareSet.java index 209707f33..aed611edd 100644 --- a/src/jogl/classes/jogamp/opengl/GLContextShareSet.java +++ b/src/jogl/classes/jogamp/opengl/GLContextShareSet.java @@ -61,21 +61,33 @@ public class GLContextShareSet { // to a share set, containing all shared contexts itself. private static final Map<GLContext, ShareSet> shareMap = new IdentityHashMap<GLContext, ShareSet>(); - private static final Object dummyValue = new Object(); private static class ShareSet { - private final Map<GLContext, Object> allShares = new IdentityHashMap<GLContext, Object>(); - private final Map<GLContext, Object> createdShares = new IdentityHashMap<GLContext, Object>(); - private final Map<GLContext, Object> destroyedShares = new IdentityHashMap<GLContext, Object>(); + private final Map<GLContext, GLContext> createdShares = new IdentityHashMap<GLContext, GLContext>(); + private final Map<GLContext, GLContext> destroyedShares = new IdentityHashMap<GLContext, GLContext>(); - public void add(final GLContext ctx) { - if (allShares.put(ctx, dummyValue) == null) { - if (ctx.isCreated()) { - createdShares.put(ctx, dummyValue); + public final void addNew(final GLContext slave, final GLContext master) { + final GLContext preMaster; + if ( slave.isCreated() ) { + preMaster = createdShares.put(slave, master); } else { - destroyedShares.put(ctx, dummyValue); + preMaster= destroyedShares.put(slave, master); + } + if( null != preMaster ) { + throw new InternalError("State of ShareSet corrupted: Slave "+toHexString(slave.hashCode())+ + " is not new w/ master "+toHexString(preMaster.hashCode())); + } + } + public final void addIfNew(final GLContext slave, final GLContext master) { + final GLContext preMaster = getMaster(master); + if( null == preMaster ) { + addNew(slave, master); } - } + } + + public final GLContext getMaster(final GLContext ctx) { + final GLContext c = createdShares.get(ctx); + return null != c ? c : destroyedShares.get(ctx); } public Set<GLContext> getCreatedShares() { @@ -86,57 +98,55 @@ public class GLContextShareSet { return destroyedShares.keySet(); } - public GLContext getCreatedShare(final GLContext ignore) { - for (final Iterator<GLContext> iter = createdShares.keySet().iterator(); iter.hasNext(); ) { - final GLContext ctx = iter.next(); - if (ctx != ignore) { - return ctx; - } - } - return null; - } - public void contextCreated(final GLContext ctx) { - final Object res = destroyedShares.remove(ctx); - assert res != null : "State of ShareSet corrupted; thought context " + - ctx + " should have been in destroyed set but wasn't"; - final Object res2 = createdShares.put(ctx, dummyValue); - assert res2 == null : "State of ShareSet corrupted; thought context " + - ctx + " shouldn't have been in created set but was"; + final GLContext ctxMaster = destroyedShares.remove(ctx); + if( null == ctxMaster ) { + throw new InternalError("State of ShareSet corrupted: Context "+toHexString(ctx.hashCode())+ + " should have been in destroyed-set"); + } + final GLContext delMaster = createdShares.put(ctx, ctxMaster); + if( null != delMaster ) { + throw new InternalError("State of ShareSet corrupted: Context "+toHexString(ctx.hashCode())+ + " shouldn't have been in created-set"); + } } public void contextDestroyed(final GLContext ctx) { - final Object res = createdShares.remove(ctx); - assert res != null : "State of ShareSet corrupted; thought context " + - ctx + " should have been in created set but wasn't"; - final Object res2 = destroyedShares.put(ctx, dummyValue); - assert res2 == null : "State of ShareSet corrupted; thought context " + - ctx + " shouldn't have been in destroyed set but was"; + final GLContext ctxMaster = createdShares.remove(ctx); + if( null == ctxMaster ) { + throw new InternalError("State of ShareSet corrupted: Context "+toHexString(ctx.hashCode())+ + " should have been in created-set"); + } + final GLContext delMaster = destroyedShares.put(ctx, ctxMaster); + if( null != delMaster ) { + throw new InternalError("State of ShareSet corrupted: Context "+toHexString(ctx.hashCode())+ + " shouldn't have been in destroyed-set"); + } } } - /** Indicate that contexts <code>share1</code> and - <code>share2</code> will share textures and display lists. Both + /** Indicate that contexts <code>slave</code> and + <code>master</code> will share textures and display lists. Both must be non-null. */ - public static synchronized void registerSharing(final GLContext share1, final GLContext share2) { - if (share1 == null || share2 == null) { - throw new IllegalArgumentException("Both share1 and share2 must be non-null"); - } - ShareSet share = entryFor(share1); - if (share == null) { - share = entryFor(share2); - } - if (share == null) { - share = new ShareSet(); - } - share.add(share1); - share.add(share2); - addEntry(share1, share); - addEntry(share2, share); - if (DEBUG) { - System.err.println("GLContextShareSet: registereSharing: 1: " + - toHexString(share1.getHandle()) + ", 2: " + toHexString(share2.getHandle())); - } + public static synchronized void registerSharing(final GLContext slave, final GLContext master) { + if (slave == null || master == null) { + throw new IllegalArgumentException("Both slave and master must be non-null"); + } + ShareSet share = entryFor(slave); + if ( null == share ) { + share = entryFor(master); + } + if ( null == share ) { + share = new ShareSet(); + } + share.addNew(slave, master); + share.addIfNew(master, master); // this master could have a different master shared registered earlier! + addEntry(slave, share); + addEntry(master, share); + if (DEBUG) { + System.err.println("GLContextShareSet: registereSharing: 1: " + + toHexString(slave.hashCode()) + ", 2: " + toHexString(master.hashCode())); + } } public static synchronized void unregisterSharing(final GLContext lastContext) { @@ -157,7 +167,7 @@ public class GLContextShareSet { } if (DEBUG) { System.err.println("GLContextShareSet: unregisterSharing: " + - toHexString(lastContext.getHandle())+", entries: "+s.size()); + toHexString(lastContext.hashCode())+", entries: "+s.size()); } for(final Iterator<GLContext> iter = s.iterator() ; iter.hasNext() ; ) { final GLContext ctx = iter.next(); @@ -176,13 +186,18 @@ public class GLContextShareSet { return share != null; } - /** Returns one created GLContext shared with the given <code>context</code>, otherwise return <code>null</code>. */ - public static synchronized GLContext getCreatedShare(final GLContext context) { + /** + * Returns the shared master GLContext of the given <code>context</code> if shared, otherwise return <code>null</code>. + * <p> + * Returns the given <code>context</code>, if it is a shared master. + * </p> + */ + public static synchronized GLContext getSharedMaster(final GLContext context) { final ShareSet share = entryFor(context); if (share == null) { return null; } - return share.getCreatedShare(context); + return share.getMaster(context); } private static synchronized Set<GLContext> getCreatedSharesImpl(final GLContext context) { diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableFactoryImpl.java b/src/jogl/classes/jogamp/opengl/GLDrawableFactoryImpl.java index 0b119b50d..8d65f16d3 100644 --- a/src/jogl/classes/jogamp/opengl/GLDrawableFactoryImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLDrawableFactoryImpl.java @@ -41,8 +41,14 @@ package jogamp.opengl; import java.nio.Buffer; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import javax.media.nativewindow.AbstractGraphicsConfiguration; import javax.media.nativewindow.AbstractGraphicsDevice; +import javax.media.nativewindow.AbstractGraphicsScreen; import javax.media.nativewindow.NativeSurface; import javax.media.nativewindow.NativeWindowFactory; import javax.media.nativewindow.OffscreenLayerSurface; @@ -516,64 +522,98 @@ public abstract class GLDrawableFactoryImpl extends GLDrawableFactory { * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - /** - * Sets the gamma, brightness, and contrast of the current main - * display. Returns true if the settings were changed, false if - * not. If this method returns true, the display settings will - * automatically be reset 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 resetDisplayGamma(). Throws - * IllegalArgumentException if any of the parameters were - * out-of-bounds. - * - * @param gamma The gamma value, typically > 1.0 (default value is - * 1.0) - * @param brightness The brightness value between -1.0 and 1.0, - * inclusive (default value is 0) - * @param contrast The contrast, greater than 0.0 (default value is 1) - * @throws IllegalArgumentException if any of the parameters were - * out-of-bounds - */ - public boolean setDisplayGamma(final float gamma, final float brightness, final float contrast) throws IllegalArgumentException { + @Override + public synchronized final boolean setDisplayGamma(final NativeSurface surface, final float gamma, final float brightness, final float contrast) throws IllegalArgumentException { if ((brightness < -1.0f) || (brightness > 1.0f)) { throw new IllegalArgumentException("Brightness must be between -1.0 and 1.0"); } if (contrast < 0) { throw new IllegalArgumentException("Contrast must be greater than 0.0"); } - // FIXME: ensure gamma is > 1.0? Are smaller / negative values legal? - final int rampLength = getGammaRampLength(); - if (rampLength == 0) { - return false; + if( NativeSurface.LOCK_SURFACE_NOT_READY >= surface.lockSurface() ) { + return false; + } + try { + // FIXME: ensure gamma is > 1.0? Are smaller / negative values legal? + final int rampLength = getGammaRampLength(surface); + if (rampLength == 0) { + return false; + } + final float[] gammaRamp = new float[rampLength]; + for (int i = 0; i < rampLength; i++) { + final float intensity = (float) i / (float) (rampLength - 1); + // apply gamma + float rampEntry = (float) java.lang.Math.pow(intensity, gamma); + // apply brightness + rampEntry += brightness; + // apply contrast + rampEntry = (rampEntry - 0.5f) * contrast + 0.5f; + // Clamp entry to [0, 1] + if (rampEntry > 1.0f) + rampEntry = 1.0f; + else if (rampEntry < 0.0f) + rampEntry = 0.0f; + gammaRamp[i] = rampEntry; + } + final AbstractGraphicsScreen screen = surface.getGraphicsConfiguration().getScreen(); + final DeviceScreenID deviceScreenID = new DeviceScreenID(screen.getDevice().getConnection(), screen.getIndex()); + if( null == screen2OrigGammaRamp.get(deviceScreenID) ) { + screen2OrigGammaRamp.put(deviceScreenID, getGammaRamp(surface)); // cache original gamma ramp once + if( DEBUG ) { + System.err.println("DisplayGamma: Stored: "+deviceScreenID); + dumpGammaStore(); + } + } + return setGammaRamp(surface, gammaRamp); + } finally { + surface.unlockSurface(); } - final float[] gammaRamp = new float[rampLength]; - for (int i = 0; i < rampLength; i++) { - final float intensity = (float) i / (float) (rampLength - 1); - // apply gamma - float rampEntry = (float) java.lang.Math.pow(intensity, gamma); - // apply brightness - rampEntry += brightness; - // apply contrast - rampEntry = (rampEntry - 0.5f) * contrast + 0.5f; - // Clamp entry to [0, 1] - if (rampEntry > 1.0f) - rampEntry = 1.0f; - else if (rampEntry < 0.0f) - rampEntry = 0.0f; - gammaRamp[i] = rampEntry; + } + + @Override + public synchronized final void resetDisplayGamma(final NativeSurface surface) { + if( NativeSurface.LOCK_SURFACE_NOT_READY >= surface.lockSurface() ) { + return; } - if( !needsGammaRampReset ) { - originalGammaRamp = getGammaRamp(); - needsGammaRampReset = true; + try { + final AbstractGraphicsScreen screen = surface.getGraphicsConfiguration().getScreen(); + final DeviceScreenID deviceScreenID = new DeviceScreenID(screen.getDevice().getConnection(), screen.getIndex()); + final Buffer originalGammaRamp = screen2OrigGammaRamp.remove(deviceScreenID); + if( null != originalGammaRamp ) { + resetGammaRamp(surface, originalGammaRamp); + } + } finally { + surface.unlockSurface(); } - return setGammaRamp(gammaRamp); } @Override - public synchronized void resetDisplayGamma() { - if( needsGammaRampReset ) { - resetGammaRamp(originalGammaRamp); - needsGammaRampReset = false; + public synchronized final void resetAllDisplayGamma() { + resetAllDisplayGammaNoSync(); + } + + @Override + protected final void resetAllDisplayGammaNoSync() { + if( DEBUG ) { + System.err.println("DisplayGamma: Reset"); + dumpGammaStore(); + } + final Set<DeviceScreenID> deviceScreenIDs = screen2OrigGammaRamp.keySet(); + for( final Iterator<DeviceScreenID> i = deviceScreenIDs.iterator(); i.hasNext(); ) { + final DeviceScreenID deviceScreenID = i.next(); + final Buffer originalGammaRamp = screen2OrigGammaRamp.remove(deviceScreenID); + if( null != originalGammaRamp ) { + resetGammaRamp(deviceScreenID, originalGammaRamp); + } + } + } + private void dumpGammaStore() { + final Set<DeviceScreenID> deviceScreenIDs = screen2OrigGammaRamp.keySet(); + int count = 0; + for( final Iterator<DeviceScreenID> i = deviceScreenIDs.iterator(); i.hasNext(); count++) { + final DeviceScreenID deviceScreenID = i.next(); + final Buffer originalGammaRamp = screen2OrigGammaRamp.get(deviceScreenID); + System.err.printf("%4d/%4d: %s -> %s%n", count, deviceScreenIDs.size(), deviceScreenID, originalGammaRamp); } } @@ -582,30 +622,64 @@ public abstract class GLDrawableFactoryImpl extends GLDrawableFactory { // /** Returns the length of the computed gamma ramp for this OS and - hardware. Returns 0 if gamma changes are not supported. */ - protected int getGammaRampLength() { + hardware. Returns 0 if gamma changes are not supported. + * @param surface TODO*/ + protected int getGammaRampLength(final NativeSurface surface) { return 0; } /** Sets the gamma ramp for the main screen. Returns false if gamma - ramp changes were not supported. */ - protected boolean setGammaRamp(final float[] ramp) { + ramp changes were not supported. + * @param surface TODO*/ + protected boolean setGammaRamp(final NativeSurface surface, final float[] ramp) { return false; } /** Gets the current gamma ramp. This is basically an opaque value used only on some platforms to reset the gamma ramp to its - original settings. */ - protected Buffer getGammaRamp() { + original settings. + * @param surface TODO*/ + protected Buffer getGammaRamp(final NativeSurface surface) { return null; } /** Resets the gamma ramp, potentially using the specified Buffer as - data to restore the original values. */ - protected void resetGammaRamp(final Buffer originalGammaRamp) { + data to restore the original values. + * @param surface TODO*/ + protected void resetGammaRamp(final NativeSurface surface, final Buffer originalGammaRamp) { + } + protected void resetGammaRamp(final DeviceScreenID deviceScreenID, final Buffer originalGammaRamp) { } // Shutdown hook mechanism for resetting gamma - private volatile Buffer originalGammaRamp; - private volatile boolean needsGammaRampReset = false; + public final class DeviceScreenID { + public final String deviceConnection; + public final int screenIdx; + DeviceScreenID(final String deviceConnection, final int screenIdx) { + this.deviceConnection = deviceConnection; + this.screenIdx = screenIdx; + } + @Override + public int hashCode() { + // 31 * x == (x << 5) - x + int hash = 31 + deviceConnection.hashCode(); + hash = ((hash << 5) - hash) + screenIdx; + return hash; + } + @Override + public boolean equals(final Object obj) { + if(this == obj) { return true; } + if (obj instanceof DeviceScreenID) { + final DeviceScreenID other = (DeviceScreenID)obj; + return this.deviceConnection.equals(other.deviceConnection) && + this.screenIdx == other.screenIdx; + } + return false; + } + @Override + public String toString() { + return "DeviceScreenID[devCon "+deviceConnection+", screenIdx "+screenIdx+", hash 0x"+Integer.toHexString(hashCode())+"]"; + } + } + private final Map<DeviceScreenID, Buffer> screen2OrigGammaRamp = new HashMap<DeviceScreenID, Buffer>(); } diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java index eac14fdff..3deeafd27 100644 --- a/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java +++ b/src/jogl/classes/jogamp/opengl/GLDrawableHelper.java @@ -40,6 +40,7 @@ package jogamp.opengl; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.HashSet; @@ -77,7 +78,8 @@ public class GLDrawableHelper { private final ArrayList<GLEventListener> listeners = new ArrayList<GLEventListener>(); private final HashSet<GLEventListener> listenersToBeInit = new HashSet<GLEventListener>(); private final Object glRunnablesLock = new Object(); - private volatile ArrayList<GLRunnableTask> glRunnables = new ArrayList<GLRunnableTask>(); + private ArrayList<GLRunnableTask> glRunnables = new ArrayList<GLRunnableTask>(); + private volatile int glRunnableCount = 0; private boolean autoSwapBufferMode; private volatile Thread exclusiveContextThread; /** -1 release, 0 nop, 1 claim */ @@ -102,6 +104,7 @@ public class GLDrawableHelper { exclusiveContextThread = null; exclusiveContextSwitch = 0; synchronized(glRunnablesLock) { + glRunnableCount = 0; glRunnables.clear(); } animatorCtrl = null; @@ -208,19 +211,23 @@ public class GLDrawableHelper { /** * Switch {@link GLContext} / {@link GLDrawable} association. * <p> - * The <code>oldCtx</code> will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>, - * otherwise dis-associate <code>oldCtx</code> from <code>drawable</code> - * via {@link GLContext#setGLDrawable(GLDrawable, boolean) oldCtx.setGLDrawable(null, true);}. - * </p> - * <p> - * Re-associate <code>newCtx</code> with <code>drawable</code> - * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}. - * </p> - * <p> - * If the old or new context was current on this thread, it is being released before switching the drawable. - * </p> - * <p> - * No locking is being performed on the drawable, caller is required to take care of it. + * Remarks: + * <ul> + * <li>The <code>oldCtx</code> will be destroyed if <code>destroyPrevCtx</code> is <code>true</code>, + * otherwise disassociate <code>oldCtx</code> from <code>drawable</code> + * via {@link GLContext#setGLDrawable(GLDrawable, boolean) oldCtx.setGLDrawable(null, true);} including {@link GL#glFinish() glFinish()}.</li> + * <li>Reassociate <code>newCtx</code> with <code>drawable</code> + * via {@link GLContext#setGLDrawable(GLDrawable, boolean) newCtx.setGLDrawable(drawable, true);}.</li> + * <li>If the old context was current on this thread, it is being released after disassociating the drawable.</li> + * <li>If the new context was current on this thread, it is being released before associating the drawable + * and made current afterwards.</li> + * <li>Implementation may issue {@link #makeCurrent()} and {@link #release()} while drawable reassociation.</li> + * <li>The user shall take extra care of thread synchronization, + * i.e. lock the involved {@link GLDrawable#getNativeSurface() drawable's} {@link NativeSurface}s + * to avoid a race condition. In case {@link GLAutoDrawable auto-drawable's} are used, + * their {@link GLAutoDrawable#getUpstreamLock() upstream-lock} must be locked beforehand + * see <a href="../../javax/media/opengl/GLAutoDrawable.html#locking">GLAutoDrawable Locking</a>.</li> + * </ul> * </p> * * @param drawable the drawable which context is changed @@ -277,7 +284,6 @@ public class GLDrawableHelper { if( currentContext != context ) { context.makeCurrent(); } - context.getGL().glFinish(); context.setGLDrawable(null, true); // dis-associate } @@ -295,7 +301,7 @@ public class GLDrawableHelper { } if(null != context) { - context.setGLDrawable(drawable, true); // re-association + context.setGLDrawable(drawable, true); // re-association, implicit glFinish() ctx/drawable sync } if( null != currentContext ) { @@ -506,15 +512,25 @@ public class GLDrawableHelper { * </p> * @param autoDrawable * @return the disposal count + * @throws GLException caused by {@link GLEventListener#dispose(GLAutoDrawable)} */ - public final int disposeAllGLEventListener(final GLAutoDrawable autoDrawable, final boolean remove) { + public final int disposeAllGLEventListener(final GLAutoDrawable autoDrawable, final boolean remove) throws GLException { + Throwable firstCaught = null; int disposeCount = 0; synchronized(listenersLock) { if( remove ) { for (int count = listeners.size(); 0 < count && 0 < listeners.size(); count--) { final GLEventListener listener = listeners.remove(0); if( !listenersToBeInit.remove(listener) ) { - listener.dispose(autoDrawable); + try { + listener.dispose(autoDrawable); + } catch (final Throwable t) { + if( null == firstCaught ) { + firstCaught = t; + } else { + GLException.dumpThrowable("subsequent", t); + } + } disposeCount++; } } @@ -522,13 +538,25 @@ public class GLDrawableHelper { for (int i = 0; i < listeners.size(); i++) { final GLEventListener listener = listeners.get(i); if( !listenersToBeInit.contains(listener) ) { - listener.dispose(autoDrawable); + try { + listener.dispose(autoDrawable); + } catch (final Throwable t) { + if( null == firstCaught ) { + firstCaught = t; + } else { + GLException.dumpThrowable("subsequent", t); + } + } listenersToBeInit.add(listener); disposeCount++; } } } } + if( null != firstCaught ) { + flushGLRunnables(); + throw GLException.newGLException(firstCaught); + } return disposeCount; } @@ -634,7 +662,7 @@ public class GLDrawableHelper { init( listener, drawable, sendReshape, 0==i /* setViewport */); } } else { - // Expose same GL initialization if not using GLEventListener + // Expose same GL initialization if not using any GLEventListener drawable.getGL().glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); } } @@ -643,7 +671,7 @@ public class GLDrawableHelper { public final void display(final GLAutoDrawable drawable) { displayImpl(drawable); // runForAllGLEventListener(drawable, displayAction); - if( glRunnables.size()>0 && !execGLRunnables(drawable) ) { // glRunnables volatile OK; execGL.. only executed if size > 0 + if( glRunnableCount > 0 && !execGLRunnables(drawable) ) { // glRunnableCount volatile OK; execGL.. only executed if size > 0 displayImpl(drawable); // runForAllGLEventListener(drawable, displayAction); } @@ -701,11 +729,10 @@ public class GLDrawableHelper { } } if(setViewport) { - final GL gl = drawable.getGL(); - final int glerr0 = gl.glGetError(); - if( GL.GL_NO_ERROR != glerr0 ) { - System.err.println("Info: GLDrawableHelper.reshape: pre-exisiting GL error 0x"+Integer.toHexString(glerr0)); - if(DEBUG) { + if( GLContext.DEBUG_GL || DEBUG ) { + final int glerr0 = drawable.getGL().glGetError(); + if( GL.GL_NO_ERROR != glerr0 ) { + System.err.println("Info: GLDrawableHelper.reshape: pre-exisiting GL error 0x"+Integer.toHexString(glerr0)); Thread.dumpStack(); } } @@ -723,43 +750,29 @@ public class GLDrawableHelper { } private final boolean execGLRunnables(final GLAutoDrawable drawable) { // glRunnables.size()>0 - boolean res = true; // swap one-shot list asap final ArrayList<GLRunnableTask> _glRunnables; synchronized(glRunnablesLock) { - if(glRunnables.size()>0) { + if( glRunnables.size() > 0 ) { + glRunnableCount = 0; _glRunnables = glRunnables; glRunnables = new ArrayList<GLRunnableTask>(); } else { - _glRunnables = null; + return true; } } - - if(null!=_glRunnables) { - for (int i=0; i < _glRunnables.size(); i++) { - res = _glRunnables.get(i).run(drawable) && res; - } + boolean res = true; + for (int i=0; i < _glRunnables.size(); i++) { + res = _glRunnables.get(i).run(drawable) && res; } return res; } public final void flushGLRunnables() { - if(glRunnables.size()>0) { // volatile OK - // swap one-shot list asap - final ArrayList<GLRunnableTask> _glRunnables; - synchronized(glRunnablesLock) { - if(glRunnables.size()>0) { - _glRunnables = glRunnables; - glRunnables = new ArrayList<GLRunnableTask>(); - } else { - _glRunnables = null; - } - } - - if(null!=_glRunnables) { - for (int i=0; i < _glRunnables.size(); i++) { - _glRunnables.get(i).flush(); - } + synchronized(glRunnablesLock) { + glRunnableCount = 0; + while( glRunnables.size() > 0 ) { + glRunnables.remove(0).flush(); } } } @@ -795,6 +808,30 @@ public class GLDrawableHelper { return ( null != animatorCtrl ) ? animatorCtrl.isAnimating() : false ; } + public static final boolean isLockedByOtherThread(final GLAutoDrawable d) { + final Thread currentThread = Thread.currentThread(); + final Thread upstreamLockOwner = d.getUpstreamLock().getOwner(); + if( null != upstreamLockOwner && currentThread != upstreamLockOwner ) { + return true; + } else { + final NativeSurface s = d.getNativeSurface(); + final Thread surfaceLockOwner = null != s ? s.getSurfaceLockOwner() : null; + return null != surfaceLockOwner && currentThread != surfaceLockOwner; + } + } + + public static final boolean isLockedByThisThread(final GLAutoDrawable d) { + final Thread currentThread = Thread.currentThread(); + final Thread upstreamLockOwner = d.getUpstreamLock().getOwner(); + if( currentThread == upstreamLockOwner ) { + return true; + } else { + final NativeSurface s = d.getNativeSurface(); + final Thread surfaceLockOwner = null != s ? s.getSurfaceLockOwner() : null; + return currentThread == surfaceLockOwner; + } + } + /** * <p> * If <code>wait</code> is <code>true</code> the call blocks until the <code>glRunnable</code> @@ -805,13 +842,32 @@ public class GLDrawableHelper { * the call is ignored and returns <code>false</code>.<br> * This helps avoiding deadlocking the caller. * </p> + * <p> + * <pre> + * 0 == deferredHere && 0 == isGLThread -> display() will issue on GL thread, blocking! + * + * deferredHere wait isGLThread lockedByThisThread Note + * OK 0 x 1 x + * OK 0 x 0 0 + * ERROR 0 x 0 1 Will be deferred on GL thread by display() (blocking), + * but locked by this thread -> ERROR + * + * 1 0 x x All good, due to no wait, non blocking + * + * 1 1 1 0 + * 1 1 0 0 + * SWITCH 1 1 1 1 Run immediately, don't defer since locked by this thread, but isGLThread + * ERROR 1 1 0 1 Locked by this thread, but _not_ isGLThread -> ERROR + * </pre> + * </p> * * @param drawable the {@link GLAutoDrawable} to be used * @param wait if <code>true</code> block until execution of <code>glRunnable</code> is finished, otherwise return immediatly w/o waiting * @param glRunnable the {@link GLRunnable} to execute within {@link #display()} * @return <code>true</code> if the {@link GLRunnable} has been processed or queued, otherwise <code>false</code>. + * @throws IllegalStateException in case the drawable is locked by this thread, no animator is running on another thread and <code>wait</code> is <code>true</code>. */ - public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final GLRunnable glRunnable) { + public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final GLRunnable glRunnable) throws IllegalStateException { if( null == glRunnable || null == drawable || wait && ( !drawable.isRealized() || null==drawable.getContext() ) ) { return false; @@ -821,18 +877,35 @@ public class GLDrawableHelper { final Object rTaskLock = new Object(); Throwable throwable = null; synchronized(rTaskLock) { - final boolean deferred; + boolean deferredHere; synchronized(glRunnablesLock) { - deferred = isAnimatorAnimatingOnOtherThread(); - if(!deferred) { - wait = false; // don't wait if exec immediatly + final boolean isGLThread = drawable.isThreadGLCapable(); + deferredHere = isAnimatorAnimatingOnOtherThread(); + if( deferredHere ) { + if( wait && isLockedByThisThread(drawable) ) { + if( isGLThread ) { + // Run immediately, don't defer since locked by this thread, but isGLThread + deferredHere = false; + wait = false; + } else { + // Locked by this thread, but _not_ isGLThread -> ERROR + throw new IllegalStateException("Deferred, wait, isLocked on current and not GL-Thread: thread "+Thread.currentThread()); + } + } + } else { + if( !isGLThread && isLockedByThisThread(drawable) ) { + // Will be deferred on GL thread by display() (blocking), but locked by this thread -> ERROR + throw new IllegalStateException("Not deferred, isLocked on current and not GL-Thread: thread "+Thread.currentThread()); + } + wait = false; // don't wait if exec immediately } rTask = new GLRunnableTask(glRunnable, wait ? rTaskLock : null, wait /* catch Exceptions if waiting for result */); + glRunnableCount++; glRunnables.add(rTask); } - if( !deferred ) { + if( !deferredHere ) { drawable.display(); } else if( wait ) { try { @@ -851,7 +924,16 @@ public class GLDrawableHelper { return true; } - public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final List<GLRunnable> newGLRunnables) { + /** + * @see #invoke(GLAutoDrawable, boolean, GLRunnable) + * + * @param drawable + * @param wait + * @param newGLRunnables + * @return + * @throws IllegalStateException + */ + public final boolean invoke(final GLAutoDrawable drawable, boolean wait, final List<GLRunnable> newGLRunnables) throws IllegalStateException { if( null == newGLRunnables || newGLRunnables.size() == 0 || null == drawable || wait && ( !drawable.isRealized() || null==drawable.getContext() ) ) { return false; @@ -862,21 +944,39 @@ public class GLDrawableHelper { final Object rTaskLock = new Object(); Throwable throwable = null; synchronized(rTaskLock) { - final boolean deferred; + boolean deferredHere; synchronized(glRunnablesLock) { - deferred = isAnimatorAnimatingOnOtherThread() || !drawable.isRealized(); - if(!deferred) { + final boolean isGLThread = drawable.isThreadGLCapable(); + deferredHere = isAnimatorAnimatingOnOtherThread(); + if( deferredHere ) { + if( wait && isLockedByThisThread(drawable) ) { + if( isGLThread ) { + // Run immediately, don't defer since locked by this thread, but isGLThread + deferredHere = false; + wait = false; + } else { + // Locked by this thread, but _not_ isGLThread -> ERROR + throw new IllegalStateException("Deferred, wait, isLocked on current and not GL-Thread: thread "+Thread.currentThread()); + } + } + } else { + if( !isGLThread && isLockedByThisThread(drawable) ) { + // Will be deferred on GL thread by display() (blocking), but locked by this thread -> ERROR + throw new IllegalStateException("Not deferred, isLocked on current and not GL-Thread: thread "+Thread.currentThread()); + } wait = false; // don't wait if exec immediately } for(int i=0; i<count-1; i++) { + glRunnableCount++; glRunnables.add( new GLRunnableTask(newGLRunnables.get(i), null, false) ); } rTask = new GLRunnableTask(newGLRunnables.get(count-1), wait ? rTaskLock : null, wait /* catch Exceptions if waiting for result */); + glRunnableCount++; glRunnables.add(rTask); } - if( !deferred ) { + if( !deferredHere ) { drawable.display(); } else if( wait ) { try { @@ -900,6 +1000,7 @@ public class GLDrawableHelper { return; } synchronized(glRunnablesLock) { + glRunnableCount++; glRunnables.add( new GLRunnableTask(glRunnable, null, false) ); } } @@ -961,8 +1062,8 @@ public class GLDrawableHelper { try { forceNativeRelease(context); } catch (final Throwable ex) { - ex.printStackTrace(); - throw new GLException(ex); + flushGLRunnables(); + throw GLException.newGLException(ex); } } exclusiveContextThread = t; @@ -980,7 +1081,21 @@ public class GLDrawableHelper { return exclusiveContextThread; } - private static final ThreadLocal<Runnable> perThreadInitAction = new ThreadLocal<Runnable>(); + private static final ThreadLocal<WeakReference<Runnable>> perThreadInitAction = new ThreadLocal<WeakReference<Runnable>>(); + private static final Runnable getLastInitAction() { + final WeakReference<Runnable> lastInitActionWR = perThreadInitAction.get(); + if( null != lastInitActionWR ) { + final Runnable lastInitAction = lastInitActionWR.get(); + if( null == lastInitAction ) { + perThreadInitAction.set(null); + } + return lastInitAction; + } + return null; + } + private static final void setLastInitAction(final Runnable initAction) { + perThreadInitAction.set(new WeakReference<Runnable>(initAction)); + } /** Principal helper method which runs a Runnable with the context made current. This could have been made part of GLContext, but a @@ -1004,8 +1119,7 @@ public class GLDrawableHelper { final Runnable initAction) { if(null==context) { if (DEBUG) { - final Exception e = new GLException(getThreadName()+" Info: GLDrawableHelper " + this + ".invokeGL(): NULL GLContext"); - e.printStackTrace(); + GLException.dumpThrowable("informal", new GLException("Info: GLDrawableHelper " + this + ".invokeGL(): NULL GLContext")); } return; } @@ -1030,9 +1144,11 @@ public class GLDrawableHelper { * @param autoDrawable * @param context * @param destroyContext destroy context in the end while holding the lock + * @throws GLException caused by {@link GLEventListener#dispose(GLAutoDrawable)} or context closing + * */ public final void disposeGL(final GLAutoDrawable autoDrawable, - final GLContext context, final boolean destroyContext) { + final GLContext context, final boolean destroyContext) throws GLException { // Support for recursive makeCurrent() calls as well as calling // other drawables' display() methods from within another one's GLContext lastContext = GLContext.getCurrent(); @@ -1042,20 +1158,27 @@ public class GLDrawableHelper { lastContext = null; } else { // utilize recursive locking - lastInitAction = perThreadInitAction.get(); + lastInitAction = getLastInitAction(); lastContext.release(); } } + GLException disposeCaught = null; + Throwable contextCloseCaught = null; + int res; try { res = context.makeCurrent(); if (GLContext.CONTEXT_NOT_CURRENT != res) { if(GLContext.CONTEXT_CURRENT_NEW == res) { - throw new GLException(getThreadName()+" GLDrawableHelper " + this + ".invokeGL(): Dispose case (no init action given): Native context was not created (new ctx): "+context); + throw new GLException(GLDrawableHelper.getThreadName()+" GLDrawableHelper " + this + ".invokeGL(): Dispose case (no init action given): Native context was not created (new ctx): "+context); } if( listeners.size() > 0 && null != autoDrawable ) { - disposeAllGLEventListener(autoDrawable, false); + try { + disposeAllGLEventListener(autoDrawable, false); + } catch(final GLException t) { + disposeCaught = t; + } } } } finally { @@ -1065,17 +1188,26 @@ public class GLDrawableHelper { } else { forceNativeRelease(context); } - flushGLRunnables(); - } catch (final Exception e) { - System.err.println("Caught exception on thread "+getThreadName()); - e.printStackTrace(); + } catch (final Throwable t) { + contextCloseCaught = t; } + flushGLRunnables(); // always flush GLRunnables at dispose + if (lastContext != null) { final int res2 = lastContext.makeCurrent(); if (null != lastInitAction && res2 == GLContext.CONTEXT_CURRENT_NEW) { lastInitAction.run(); } } + if( null != disposeCaught ) { + if( null != contextCloseCaught ) { + GLException.dumpThrowable("subsequent", contextCloseCaught); + } + throw disposeCaught; + } + if( null != contextCloseCaught ) { + throw GLException.newGLException(contextCloseCaught); + } } } @@ -1085,6 +1217,9 @@ public class GLDrawableHelper { final Runnable initAction) { final Thread currentThread = Thread.currentThread(); + Throwable glEventListenerCaught = null; + Throwable contextReleaseCaught = null; + // Exclusive Cases: // 1: lock - unlock : default // 2: lock - - : exclusive, not locked yet @@ -1116,7 +1251,7 @@ public class GLDrawableHelper { lastContext = null; } else { // utilize recursive locking - lastInitAction = perThreadInitAction.get(); + lastInitAction = getLastInitAction(); lastContext.release(); } } @@ -1131,7 +1266,7 @@ public class GLDrawableHelper { } if (GLContext.CONTEXT_NOT_CURRENT != res) { try { - perThreadInitAction.set(initAction); + setLastInitAction(initAction); if (GLContext.CONTEXT_CURRENT_NEW == res) { if (DEBUG) { System.err.println("GLDrawableHelper " + this + ".invokeGL(): Running initAction"); @@ -1142,6 +1277,8 @@ public class GLDrawableHelper { if ( autoSwapBufferMode ) { drawable.swapBuffers(); } + } catch (final Throwable t) { + glEventListenerCaught = t; } finally { if( _releaseExclusiveThread ) { exclusiveContextThread = null; @@ -1152,9 +1289,8 @@ public class GLDrawableHelper { if( releaseContext ) { try { context.release(); - } catch (final Exception e) { - System.err.println("Caught exception on thread "+getThreadName()); - e.printStackTrace(); + } catch (final Throwable t) { + contextReleaseCaught = t; } } } @@ -1166,6 +1302,17 @@ public class GLDrawableHelper { lastInitAction.run(); } } + if( null != glEventListenerCaught ) { + flushGLRunnables(); + if( null != contextReleaseCaught ) { + GLException.dumpThrowable("subsequent", contextReleaseCaught); + } + throw GLException.newGLException(glEventListenerCaught); + } + if( null != contextReleaseCaught ) { + flushGLRunnables(); + throw GLException.newGLException(contextReleaseCaught); + } } } @@ -1175,6 +1322,9 @@ public class GLDrawableHelper { final Runnable initAction) { final Thread currentThread = Thread.currentThread(); + Throwable glEventListenerCaught = null; + Throwable contextReleaseCaught = null; + // Exclusive Cases: // 1: lock - unlock : default // 2: lock - - : exclusive, not locked yet @@ -1205,7 +1355,7 @@ public class GLDrawableHelper { lastContext = null; } else { // utilize recursive locking - lastInitAction = perThreadInitAction.get(); + lastInitAction = getLastInitAction(); lastContext.release(); } } @@ -1229,7 +1379,7 @@ public class GLDrawableHelper { } if (GLContext.CONTEXT_NOT_CURRENT != res) { try { - perThreadInitAction.set(initAction); + setLastInitAction(initAction); if (GLContext.CONTEXT_CURRENT_NEW == res) { if (DEBUG) { System.err.println("GLDrawableHelper " + this + ".invokeGL(): Running initAction"); @@ -1246,6 +1396,8 @@ public class GLDrawableHelper { tdX = System.currentTimeMillis(); tdS = tdX - tdS; // swapBuffers } + } catch (final Throwable t) { + glEventListenerCaught = t; } finally { if( _releaseExclusiveThread ) { exclusiveContextSwitch = 0; @@ -1258,9 +1410,8 @@ public class GLDrawableHelper { try { context.release(); ctxReleased = true; - } catch (final Exception e) { - System.err.println("Caught exception on thread "+getThreadName()); - e.printStackTrace(); + } catch (final Throwable t) { + contextReleaseCaught = t; } } } @@ -1273,11 +1424,21 @@ public class GLDrawableHelper { lastInitAction.run(); } } + if( null != glEventListenerCaught ) { + flushGLRunnables(); + if( null != contextReleaseCaught ) { + GLException.dumpThrowable("subsequent", contextReleaseCaught); + } + throw GLException.newGLException(glEventListenerCaught); + } + if( null != contextReleaseCaught ) { + flushGLRunnables(); + throw GLException.newGLException(contextReleaseCaught); + } } final long td = System.currentTimeMillis() - t0; System.err.println("td0 "+td+"ms, fps "+(1.0/(td/1000.0))+", td-makeCurrent: "+tdA+"ms, td-render "+tdR+"ms, td-swap "+tdS+"ms, td-release "+tdX+"ms, ctx claimed: "+ctxClaimed+", ctx release: "+ctxReleased+", ctx destroyed "+ctxDestroyed); } protected static String getThreadName() { return Thread.currentThread().getName(); } - } diff --git a/src/jogl/classes/jogamp/opengl/GLDrawableImpl.java b/src/jogl/classes/jogamp/opengl/GLDrawableImpl.java index 3bb22612f..544aaf064 100644 --- a/src/jogl/classes/jogamp/opengl/GLDrawableImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLDrawableImpl.java @@ -130,10 +130,11 @@ public abstract class GLDrawableImpl implements GLDrawable { } @Override - public GLCapabilitiesImmutable getChosenGLCapabilities() { + public final GLCapabilitiesImmutable getChosenGLCapabilities() { return (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); } + @Override public final GLCapabilitiesImmutable getRequestedGLCapabilities() { return requestedCapabilities; } diff --git a/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java b/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java index 6046527d1..991a351e6 100644 --- a/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLFBODrawableImpl.java @@ -15,6 +15,7 @@ import com.jogamp.common.util.PropertyAccess; import com.jogamp.common.util.VersionUtil; import com.jogamp.nativewindow.MutableGraphicsConfiguration; import com.jogamp.opengl.FBObject; +import com.jogamp.opengl.GLRendererQuirks; import com.jogamp.opengl.FBObject.Attachment; import com.jogamp.opengl.FBObject.Colorbuffer; import com.jogamp.opengl.FBObject.TextureAttachment; @@ -44,13 +45,15 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { static { Debug.initSingleton(); DEBUG = GLDrawableImpl.DEBUG || Debug.debug("FBObject"); - DEBUG_SWAP = DEBUG || PropertyAccess.isPropertyDefined("jogl.debug.FBObject.Swap", true); + DEBUG_SWAP = PropertyAccess.isPropertyDefined("jogl.debug.FBObject.Swap", true); } private final GLDrawableImpl parent; private GLCapabilitiesImmutable origParentChosenCaps; private boolean initialized; + private int maxSamples; + private int fboModeBits; private int texUnit; private int samples; private boolean fboResetQuirk; @@ -59,9 +62,9 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { private int fboIBack; // points to GL_BACK buffer private int fboIFront; // points to GL_FRONT buffer private int pendingFBOReset = -1; - /** Indicated whether the FBO is bound. */ + /** Indicates whether the FBO is bound. */ private boolean fboBound; - /** Indicated whether the FBO is swapped, resets to false after makeCurrent -> contextMadeCurrent. */ + /** Indicates whether the FBO is swapped, resets to false after makeCurrent -> contextMadeCurrent. */ private boolean fboSwapped; /** dump fboResetQuirk info only once pre ClassLoader and only in DEBUG mode */ @@ -89,17 +92,79 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { final GLCapabilitiesImmutable fboCaps, final int textureUnit) { super(factory, surface, fboCaps, false); this.initialized = false; + this.fboModeBits = FBOMODE_USE_TEXTURE; this.parent = parent; this.origParentChosenCaps = getChosenGLCapabilities(); // just to avoid null, will be reset at initialize(..) this.texUnit = textureUnit; this.samples = fboCaps.getNumSamples(); - fboResetQuirk = false; + this.fboResetQuirk = false; + this.swapBufferContext = null; + } - // default .. // TODO: Add or remove TEXTURE (only) DoubleBufferMode support - // this.doubleBufferMode = ( samples > 0 || fboCaps.getDoubleBuffered() ) ? DoubleBufferMode.FBO : DoubleBufferMode.NONE ; + private final void setupFBO(final GL gl, final int idx, final int width, final int height, final int samples, + final boolean useAlpha, final int depthBits, final int stencilBits, + final boolean useTexture, final boolean realUnbind) { + final FBObject fbo = new FBObject(); + fbos[idx] = fbo; - this.swapBufferContext = null; + final boolean useDepth = depthBits > 0; + final boolean useStencil = stencilBits > 0; + + fbo.init(gl, width, height, samples); + if(fbo.getNumSamples() != samples) { + throw new InternalError("Sample number mismatch: "+samples+", fbos["+idx+"] "+fbo); + } + if(samples > 0 || !useTexture) { + fbo.attachColorbuffer(gl, 0, useAlpha); + } else { + fbo.attachTexture2D(gl, 0, useAlpha); + } + if( useStencil ) { + if( useDepth ) { + fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, depthBits); + } else { + fbo.attachRenderbuffer(gl, Attachment.Type.STENCIL, stencilBits); + } + } else if( useDepth ) { + fbo.attachRenderbuffer(gl, Attachment.Type.DEPTH, depthBits); + } + if(samples > 0) { + final FBObject ssink = new FBObject(); + { + ssink.init(gl, width, height, 0); + if( !useTexture ) { + ssink.attachColorbuffer(gl, 0, useAlpha); + } else { + ssink.attachTexture2D(gl, 0, useAlpha); + } + if( useStencil ) { + if( useDepth ) { + ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, depthBits); + } else { + ssink.attachRenderbuffer(gl, Attachment.Type.STENCIL, stencilBits); + } + } else if( useDepth ) { + ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, depthBits); + } + } + fbo.setSamplingSink(ssink); + fbo.resetSamplingSink(gl); // validate + } + // Clear the framebuffer allowing defined state not exposing previous content. + // Also remedy for Bug 1020, i.e. OSX/Nvidia's FBO needs to be cleared before blitting, + // otherwise first MSAA frame lacks antialiasing. + fbo.bind(gl); + if( useDepth ) { + gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); + } else { + gl.glClear(GL.GL_COLOR_BUFFER_BIT); + } + if( realUnbind ) { + fbo.unbind(gl); + } else { + fbo.markUnbound(); + } } private final void initialize(final boolean realize, final GL gl) { @@ -116,7 +181,7 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { if(realize) { final GLCapabilities chosenFBOCaps = (GLCapabilities) getChosenGLCapabilities(); // cloned at setRealized(true) - final int maxSamples = gl.getMaxRenderbufferSamples(); + maxSamples = gl.getMaxRenderbufferSamples(); // if > 0 implies fullFBOSupport { final int newSamples = samples <= maxSamples ? samples : maxSamples; if(DEBUG) { @@ -138,25 +203,21 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { fboIBack = 0; // head fboIFront = fbos.length - 1; // tail - for(int i=0; i<fbosN; i++) { - fbos[i] = new FBObject(); - fbos[i].reset(gl, getSurfaceWidth(), getSurfaceHeight(), samples, false); - if(fbos[i].getNumSamples() != samples) { - throw new InternalError("Sample number mismatch: "+samples+", fbos["+i+"] "+fbos[i]); - } - if(samples > 0) { - fbos[i].attachColorbuffer(gl, 0, chosenFBOCaps.getAlphaBits()>0); - } else { - fbos[i].attachTexture2D(gl, 0, chosenFBOCaps.getAlphaBits()>0); - } - if( chosenFBOCaps.getStencilBits() > 0 ) { - fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24); - } else { - fbos[i].attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } + if( 0 == ( FBOMODE_USE_TEXTURE & fboModeBits ) && + gl.getContext().hasRendererQuirk(GLRendererQuirks.BuggyColorRenderbuffer) ) { + // GLRendererQuirks.BuggyColorRenderbuffer also disables MSAA, i.e. full FBO support + fboModeBits |= FBOMODE_USE_TEXTURE; } - fbos[fboIFront].resetSamplingSink(gl); + final boolean useTexture = 0 != ( FBOMODE_USE_TEXTURE & fboModeBits ); + final boolean useAlpha = chosenFBOCaps.getAlphaBits() > 0; + final int width = getSurfaceWidth(); + final int height = getSurfaceHeight(); + + for(int i=0; i<fbosN; i++) { + setupFBO(gl, i, width, height, samples, useAlpha, + chosenFBOCaps.getDepthBits(), chosenFBOCaps.getStencilBits(), useTexture, fbosN-1==i); + } fbos[0].formatToGLCapabilities(chosenFBOCaps); chosenFBOCaps.setDoubleBuffered( chosenFBOCaps.getDoubleBuffered() || samples > 0 ); } else { @@ -180,29 +241,26 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { swapBufferContext = sbc; } - private final void reset(final GL gl, final int idx, final int width, final int height, final int samples, final int alphaBits, final int stencilBits) { + private final void reset(final GL gl, final int idx, final int width, final int height, final int samples, + final boolean useAlpha, final int depthBits, final int stencilBits) { if( !fboResetQuirk ) { try { - fbos[idx].reset(gl, width, height, samples, false); + fbos[idx].reset(gl, width, height, samples); if(fbos[idx].getNumSamples() != samples) { throw new InternalError("Sample number mismatch: "+samples+", fbos["+idx+"] "+fbos[idx]); } return; } catch (final GLException e) { fboResetQuirk = true; - if(DEBUG) { + if( DEBUG ) { if(!resetQuirkInfoDumped) { resetQuirkInfoDumped = true; System.err.println("GLFBODrawable: FBO Reset failed: "+e.getMessage()); System.err.println("GLFBODrawable: Enabling FBOResetQuirk, due to GL driver bug."); final JoglVersion joglVersion = JoglVersion.getInstance(); - if(DEBUG) { - System.err.println(VersionUtil.getPlatformInfo()); - System.err.println(joglVersion.toString()); - System.err.println(JoglVersion.getGLInfo(gl, null)); - } else { - System.err.println(joglVersion.getBriefOSGLBuildInfo(gl, null)); - } + System.err.println(VersionUtil.getPlatformInfo()); + System.err.println(joglVersion.toString()); + System.err.println(JoglVersion.getGLInfo(gl, null)); e.printStackTrace(); } } @@ -211,21 +269,8 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { } // resetQuirk fallback fbos[idx].destroy(gl); - fbos[idx] = new FBObject(); - fbos[idx].reset(gl, getSurfaceWidth(), getSurfaceHeight(), samples, false); - if(fbos[idx].getNumSamples() != samples) { - throw new InternalError("Sample number mismatch: "+samples+", fbos["+idx+"] "+fbos[idx]); - } - if(samples > 0) { - fbos[idx].attachColorbuffer(gl, 0, alphaBits>0); - } else { - fbos[idx].attachTexture2D(gl, 0, alphaBits>0); - } - if( stencilBits > 0 ) { - fbos[idx].attachRenderbuffer(gl, Attachment.Type.DEPTH_STENCIL, 24); - } else { - fbos[idx].attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } + final boolean useTexture = 0 != ( FBOMODE_USE_TEXTURE & fboModeBits ); + setupFBO(gl, idx, width, height, samples, useAlpha, depthBits, stencilBits, useTexture, true); } private final void reset(final GL gl, int newSamples) throws GLException { @@ -248,7 +293,6 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { fboBound = false; // clear bound-flag immediatly, caused by contextMadeCurrent(..) - otherwise we would swap @ release fboSwapped = false; try { - final int maxSamples = gl.getMaxRenderbufferSamples(); newSamples = newSamples <= maxSamples ? newSamples : maxSamples; if(0==samples && 0<newSamples || 0<samples && 0==newSamples) { @@ -270,7 +314,7 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); for(int i=0; i<fbos.length; i++) { if( pendingFBOReset != i ) { - reset(gl, i, nWidth, nHeight, samples, caps.getAlphaBits(), caps.getStencilBits()); + reset(gl, i, nWidth, nHeight, samples, caps.getAlphaBits()>0, caps.getDepthBits(), caps.getStencilBits()); } } final GLCapabilities fboCapsNative = (GLCapabilities) surface.getGraphicsConfiguration().getChosenCapabilities(); @@ -289,10 +333,10 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { } } if(null != tFBO) { - throw new GLException("GLFBODrawableImpl.reset(..) FBObject.reset(..) exception", tFBO); + throw GLException.newGLException(tFBO); } if(null != tGL) { - throw new GLException("GLFBODrawableImpl.reset(..) GLContext.release() exception", tGL); + throw GLException.newGLException(tGL); } if(DEBUG) { System.err.println("GLFBODrawableImpl.reset(newSamples "+newSamples+"): END "+this); @@ -397,7 +441,8 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { // Safely reset the previous front FBO - after completing propagating swap if(0 <= pendingFBOReset) { final GLCapabilitiesImmutable caps = (GLCapabilitiesImmutable) surface.getGraphicsConfiguration().getChosenCapabilities(); - reset(glc.getGL(), pendingFBOReset, getSurfaceWidth(), getSurfaceHeight(), samples, caps.getAlphaBits(), caps.getStencilBits()); + reset(glc.getGL(), pendingFBOReset, getSurfaceWidth(), getSurfaceHeight(), samples, + caps.getAlphaBits()>0, caps.getDepthBits(), caps.getStencilBits()); pendingFBOReset = -1; } } @@ -414,17 +459,16 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { fboIBack = ( fboIBack + 1 ) % fbos.length; final Colorbuffer colorbuffer = samples > 0 ? fbos[fboIFront].getSamplingSink() : fbos[fboIFront].getColorbuffer(0); + if(null == colorbuffer) { + throw new GLException("Front colorbuffer is null: samples "+samples+", "+this); + } final TextureAttachment texAttachment; - if(colorbuffer instanceof TextureAttachment) { - texAttachment = (TextureAttachment) colorbuffer; + if( colorbuffer.isTextureAttachment() ) { + texAttachment = colorbuffer.getTextureAttachment(); + gl.glActiveTexture(GL.GL_TEXTURE0 + texUnit); } else { - if(null == colorbuffer) { - throw new GLException("Front colorbuffer is null: samples "+samples+", "+this); - } else { - throw new GLException("Front colorbuffer is not a texture: "+colorbuffer.getClass().getName()+": samples "+samples+", "+colorbuffer+", "+this); - } + texAttachment = null; } - gl.glActiveTexture(GL.GL_TEXTURE0 + texUnit); fbos[fboIFront].use(gl, texAttachment); /* Included in above use command: @@ -447,6 +491,19 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { } @Override + public final void setFBOMode(final int modeBits) throws IllegalStateException { + if( isInitialized() ) { + throw new IllegalStateException("Already initialized: "+this); + } + this.fboModeBits = modeBits; + } + + @Override + public final int getFBOMode() { + return fboModeBits; + } + + @Override public final void resetSize(final GL gl) throws GLException { reset(gl, samples); } @@ -468,9 +525,12 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { } @Override - public final int setNumBuffers(final int bufferCount) throws GLException { + public final int setNumBuffers(final int bufferCount) throws IllegalStateException, GLException { + if( isInitialized() ) { + throw new IllegalStateException("Already initialized: "+this); + } // FIXME: Implement - return bufferCount; + return GLFBODrawableImpl.bufferCount; } @Override @@ -519,24 +579,24 @@ public class GLFBODrawableImpl extends GLDrawableImpl implements GLFBODrawable { } @Override - public final TextureAttachment getTextureBuffer(final int bufferName) throws IllegalArgumentException { + public final Colorbuffer getColorbuffer(final int bufferName) throws IllegalArgumentException { if(!initialized) { return null; } - final TextureAttachment res; + final Colorbuffer res; switch(bufferName) { case GL.GL_FRONT: if( samples > 0 ) { res = fbos[0].getSamplingSink(); } else { - res = (TextureAttachment) fbos[fboIFront].getColorbuffer(0); + res = fbos[fboIFront].getColorbuffer(0); } break; case GL.GL_BACK: if( samples > 0 ) { throw new IllegalArgumentException("Cannot access GL_BACK buffer of MSAA FBO: "+this); } else { - res = (TextureAttachment) fbos[fboIBack].getColorbuffer(0); + res = fbos[fboIBack].getColorbuffer(0); } break; default: diff --git a/src/jogl/classes/jogamp/opengl/GLOffscreenAutoDrawableImpl.java b/src/jogl/classes/jogamp/opengl/GLOffscreenAutoDrawableImpl.java index 95c4ceb98..721dc7384 100644 --- a/src/jogl/classes/jogamp/opengl/GLOffscreenAutoDrawableImpl.java +++ b/src/jogl/classes/jogamp/opengl/GLOffscreenAutoDrawableImpl.java @@ -102,7 +102,7 @@ public class GLOffscreenAutoDrawableImpl extends GLAutoDrawableDelegate implemen } @Override - public final int setNumBuffers(final int bufferCount) throws GLException { + public final int setNumBuffers(final int bufferCount) throws /* IllegalStateException, */ GLException { return ((GLFBODrawableImpl)drawable).setNumBuffers(bufferCount); } @@ -128,13 +128,24 @@ public class GLOffscreenAutoDrawableImpl extends GLAutoDrawableDelegate implemen } @Override - public final FBObject.TextureAttachment getTextureBuffer(final int bufferName) { - return ((GLFBODrawableImpl)drawable).getTextureBuffer(bufferName); + public final FBObject.Colorbuffer getColorbuffer(final int bufferName) { + return ((GLFBODrawableImpl)drawable).getColorbuffer(bufferName); } @Override public void resetSize(final GL gl) throws GLException { ((GLFBODrawableImpl)drawable).resetSize(gl); } + + @Override + public final void setFBOMode(final int modeBits) throws IllegalStateException { + ((GLFBODrawableImpl)drawable).setFBOMode(modeBits); + + } + + @Override + public final int getFBOMode() { + return ((GLFBODrawableImpl)drawable).getFBOMode(); + } } } diff --git a/src/jogl/classes/jogamp/opengl/GLRunnableTask.java b/src/jogl/classes/jogamp/opengl/GLRunnableTask.java index 0ceef6bf7..ca1c1869e 100644 --- a/src/jogl/classes/jogamp/opengl/GLRunnableTask.java +++ b/src/jogl/classes/jogamp/opengl/GLRunnableTask.java @@ -90,8 +90,10 @@ public class GLRunnableTask implements GLRunnable { /** * Simply flush this task and notify a waiting executor. + * <p> * The executor which might have been blocked until notified * will be unblocked and the task removed from the queue. + * </p> * * @see #isFlushed() * @see #isInQueue() diff --git a/src/jogl/classes/jogamp/opengl/GLStateTracker.java b/src/jogl/classes/jogamp/opengl/GLStateTracker.java index d532a2567..dc49b35f4 100644 --- a/src/jogl/classes/jogamp/opengl/GLStateTracker.java +++ b/src/jogl/classes/jogamp/opengl/GLStateTracker.java @@ -68,7 +68,7 @@ public class GLStateTracker { private IntIntHashMap pixelStateMap; private final ArrayList<SavedState> stack; - private static class SavedState { + static class SavedState { /** * Empty pixel-store state @@ -78,15 +78,14 @@ public class GLStateTracker { /** * set (client) pixel-store state, deep copy */ - private final void setPixelStateMap(final IntIntHashMap pixelStateMap) { + final void setPixelStateMap(final IntIntHashMap pixelStateMap) { this.pixelStateMap = (IntIntHashMap) pixelStateMap.clone(); } /** * get (client) pixel-store state, return reference */ - private final IntIntHashMap getPixelStateMap() { return pixelStateMap; } - + final IntIntHashMap getPixelStateMap() { return pixelStateMap; } } @@ -163,10 +162,11 @@ public class GLStateTracker { if(null==state) { throw new GLException("null stack element (remaining stack size "+stack.size()+")"); } + final IntIntHashMap statePixelStateMap = state.getPixelStateMap(); - if ( null != state.getPixelStateMap() ) { + if ( null != statePixelStateMap ) { // use pulled client pixel-store state from stack - pixelStateMap = state.getPixelStateMap(); + pixelStateMap = statePixelStateMap; } // else: empty-slot, not pushed by GL_CLIENT_PIXEL_STORE_BIT } } diff --git a/src/jogl/classes/jogamp/opengl/ThreadingImpl.java b/src/jogl/classes/jogamp/opengl/ThreadingImpl.java index 2b017e8e9..7b405e524 100644 --- a/src/jogl/classes/jogamp/opengl/ThreadingImpl.java +++ b/src/jogl/classes/jogamp/opengl/ThreadingImpl.java @@ -41,6 +41,7 @@ import java.security.PrivilegedAction; import javax.media.nativewindow.NativeWindowFactory; import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; +import javax.media.opengl.Threading.Mode; import com.jogamp.common.JogampRuntimeException; import com.jogamp.common.util.PropertyAccess; @@ -49,16 +50,6 @@ import com.jogamp.common.util.ReflectionUtil; /** Implementation of the {@link javax.media.opengl.Threading} class. */ public class ThreadingImpl { - public enum Mode { - MT(0), ST_AWT(1), ST_WORKER(2); - - public final int id; - - Mode(final int id){ - this.id = id; - } - } - protected static final boolean DEBUG = Debug.debug("Threading"); private static boolean singleThreaded; @@ -93,28 +84,23 @@ public class ThreadingImpl { _isX11 = NativeWindowFactory.TYPE_X11 == NativeWindowFactory.getNativeWindowType(false); - // default setting - singleThreaded = true; - mode = ( hasAWT ? Mode.ST_AWT : Mode.ST_WORKER ); - if (singleThreadProp != null) { if (singleThreadProp.equals("true") || singleThreadProp.equals("auto")) { - singleThreaded = true; - mode = ( hasAWT ? Mode.ST_AWT : Mode.ST_WORKER ); + mode = ( hasAWT ? Mode.ST_AWT : Mode.MT ); } else if (singleThreadProp.equals("worker")) { - singleThreaded = true; mode = Mode.ST_WORKER; } else if (hasAWT && singleThreadProp.equals("awt")) { - singleThreaded = true; mode = Mode.ST_AWT; } else if (singleThreadProp.equals("false")) { - singleThreaded = false; mode = Mode.MT; } else { throw new RuntimeException("Unsupported value for property jogl.1thread: "+singleThreadProp+", should be [true/auto, worker, awt or false]"); } + } else { + mode = ( hasAWT ? Mode.ST_AWT : Mode.MT ); } + singleThreaded = Mode.MT != mode; ToolkitThreadingPlugin threadingPlugin=null; if(hasAWT) { @@ -155,9 +141,11 @@ public class ThreadingImpl { method. This method should be called as early as possible in an application. */ public static final void disableSingleThreading() { - singleThreaded = false; - if (Debug.verbose()) { - System.err.println("Application forced disabling of single-threading of javax.media.opengl implementation"); + if( Mode.MT != mode ) { + singleThreaded = false; + if (Debug.verbose()) { + System.err.println("Application forced disabling of single-threading of javax.media.opengl implementation"); + } } } @@ -167,22 +155,28 @@ public class ThreadingImpl { return singleThreaded; } - /** Indicates whether the current thread is the single thread on - which this implementation of the javax.media.opengl APIs - performs all of its OpenGL-related work. This method should only - be called if the single-thread model is in effect. */ + /** + * Indicates whether the current thread is capable of + * performing OpenGL-related work. + * <p> + * Method always returns <code>true</code> + * if {@link #getMode()} == {@link Mode#MT} or {@link #isSingleThreaded()} == <code>false</code>. + * </p> + */ public static final boolean isOpenGLThread() throws GLException { - if(null!=threadingPlugin) { + if( Mode.MT == mode || !singleThreaded ) { + return true; + } else if( null != threadingPlugin ) { return threadingPlugin.isOpenGLThread(); - } - - switch (mode) { - case ST_AWT: - throw new InternalError(); - case ST_WORKER: - return GLWorkerThread.isWorkerThread(); - default: - throw new InternalError("Illegal single-threading mode " + mode); + } else { + switch (mode) { + case ST_AWT: + throw new InternalError(); + case ST_WORKER: + return GLWorkerThread.isWorkerThread(); + default: + throw new InternalError("Illegal single-threading mode " + mode); + } } } @@ -213,6 +207,10 @@ public class ThreadingImpl { invokeOnWorkerThread(wait, r); break; + case MT: + r.run(); + break; + default: throw new InternalError("Illegal single-threading mode " + mode); } diff --git a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java index 24b89cd02..495887e0f 100644 --- a/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java +++ b/src/jogl/classes/jogamp/opengl/android/av/AndroidGLMediaPlayerAPI14.java @@ -254,7 +254,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { @Override protected final void initStreamImpl(final int vid, final int aid) throws IOException { - if( null == getURI() ) { + if( null == getUri() ) { return; } if( null == mp && null == cam ) { @@ -263,7 +263,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { } else { int cameraId = 0; try { - cameraId = Integer.parseInt(cameraPath); + cameraId = Integer.parseInt(cameraPath.decode()); } catch (final NumberFormatException nfe) {} if( 0 <= cameraId && cameraId < Camera.getNumberOfCameras() ) { cam = Camera.open(cameraId); @@ -280,7 +280,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { } // else FIXME: Select aid ! // Note: Both FIXMEs seem to be n/a via Android's MediaPlayer -> Switch to API level 16 MediaCodec/MediaExtractor .. try { - final Uri _uri = Uri.parse(getURI().toString()); + final Uri _uri = Uri.parse(getUri().toString()); mp.setDataSource(StaticContext.getContext(), _uri); } catch (final IllegalArgumentException e) { throw new RuntimeException(e); @@ -293,7 +293,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { try { mp.prepare(); } catch (final IOException ioe) { - throw new IOException("MediaPlayer failed to process stream <"+getURI().toString()+">: "+ioe.getMessage(), ioe); + throw new IOException("MediaPlayer failed to process stream <"+getUri().toString()+">: "+ioe.getMessage(), ioe); } final int r_aid = GLMediaPlayer.STREAM_ID_NONE == aid ? GLMediaPlayer.STREAM_ID_NONE : 1 /* fake */; final String icodec = "android"; @@ -381,7 +381,7 @@ public class AndroidGLMediaPlayerAPI14 extends GLMediaPlayerImpl { cam.setPreviewTexture(sTexFrame.surfaceTex); cam.startPreview(); } catch (final IOException ioe) { - throw new RuntimeException("MediaPlayer failed to process stream <"+getURI().toString()+">: "+ioe.getMessage(), ioe); + throw new RuntimeException("MediaPlayer failed to process stream <"+getUri().toString()+">: "+ioe.getMessage(), ioe); } } if( null != surface ) { diff --git a/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java b/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java index 26ec62785..3f8910fb5 100644 --- a/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java +++ b/src/jogl/classes/jogamp/opengl/awt/AWTThreadingPlugin.java @@ -108,6 +108,10 @@ public class AWTThreadingPlugin implements ToolkitThreadingPlugin { ThreadingImpl.invokeOnWorkerThread(wait, r); break; + case MT: + r.run(); + break; + default: throw new InternalError("Illegal single-threading mode " + ThreadingImpl.getMode()); } diff --git a/src/jogl/classes/jogamp/opengl/egl/EGLDrawableFactory.java b/src/jogl/classes/jogamp/opengl/egl/EGLDrawableFactory.java index 5a9a30313..2edb22314 100644 --- a/src/jogl/classes/jogamp/opengl/egl/EGLDrawableFactory.java +++ b/src/jogl/classes/jogamp/opengl/egl/EGLDrawableFactory.java @@ -184,7 +184,7 @@ public class EGLDrawableFactory extends GLDrawableFactoryImpl { } } if( null != eglES2DynamicLookupHelper || null != eglES1DynamicLookupHelper ) { - if(isANGLE && !enableANGLE) { + if(isANGLE && !GLProfile.enableANGLE) { if(DEBUG || GLProfile.DEBUG) { System.err.println("Info: EGLDrawableFactory.init - EGL/ES2 ANGLE disabled"); } diff --git a/src/jogl/classes/jogamp/opengl/glu/mipmap/Mipmap.java b/src/jogl/classes/jogamp/opengl/glu/mipmap/Mipmap.java index 9ff6bd637..51d8ca6fe 100644 --- a/src/jogl/classes/jogamp/opengl/glu/mipmap/Mipmap.java +++ b/src/jogl/classes/jogamp/opengl/glu/mipmap/Mipmap.java @@ -49,6 +49,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.glu.GLU; import javax.media.opengl.GLException; @@ -255,8 +256,8 @@ public class Mipmap { */ public static void closestFit( final GL gl, final int target, final int width, final int height, final int internalFormat, final int format, final int type, final int[] newWidth, final int[] newHeight ) { - // Use proxy textures if OpenGL version >= 1.1 - if( Double.parseDouble( gl.glGetString( GL.GL_VERSION ).trim().substring( 0, 3 ) ) >= 1.1 ) { + // Use proxy textures if OpenGL GL2/GL3 version >= 1.1 + if( gl.isGL2GL3() && gl.getContext().getGLVersionNumber().compareTo(GLContext.Version110) >= 0 ) { int widthPowerOf2 = nearestPower( width ); int heightPowerOf2 = nearestPower( height ); final int[] proxyWidth = new int[1]; diff --git a/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java b/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java index dbca7c2e8..7066a6db5 100644 --- a/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java +++ b/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLContext.java @@ -828,7 +828,7 @@ public class MacOSXCGLContext extends GLContextImpl final long drawableHandle = drawable.getHandle(); if(drawable instanceof GLFBODrawableImpl) { final GLFBODrawableImpl fbod = (GLFBODrawableImpl)drawable; - texID = fbod.getTextureBuffer(GL.GL_FRONT).getName(); + texID = fbod.getColorbuffer(GL.GL_FRONT).getName(); pbufferHandle = 0; fbod.setSwapBufferContext(new GLFBODrawableImpl.SwapBufferContext() { @Override @@ -1043,7 +1043,7 @@ public class MacOSXCGLContext extends GLContextImpl final boolean valid; final boolean isFBO = drawable instanceof GLFBODrawableImpl; if( isFBO ){ - texID = ((GLFBODrawableImpl)drawable).getTextureBuffer(GL.GL_FRONT).getName(); + texID = ((GLFBODrawableImpl)drawable).getColorbuffer(GL.GL_FRONT).getName(); valid = 0 != texID; } else { texID = 0; diff --git a/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLDrawableFactory.java b/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLDrawableFactory.java index 7c05b8eab..045abca4c 100644 --- a/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLDrawableFactory.java +++ b/src/jogl/classes/jogamp/opengl/macosx/cgl/MacOSXCGLDrawableFactory.java @@ -42,6 +42,7 @@ package jogamp.opengl.macosx.cgl; import java.nio.Buffer; import java.nio.FloatBuffer; +import java.nio.ShortBuffer; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -404,24 +405,29 @@ public class MacOSXCGLDrawableFactory extends GLDrawableFactoryImpl { /** Returns the length of the computed gamma ramp for this OS and hardware. Returns 0 if gamma changes are not supported. */ @Override - protected int getGammaRampLength() { + protected int getGammaRampLength(final NativeSurface surface) { return GAMMA_RAMP_LENGTH; } @Override - protected boolean setGammaRamp(final float[] ramp) { + protected boolean setGammaRamp(final NativeSurface surface, final float[] ramp) { final FloatBuffer rampNIO = Buffers.newDirectFloatBuffer(ramp); - return CGL.setGammaRamp(ramp.length, rampNIO, rampNIO, rampNIO); } @Override - protected Buffer getGammaRamp() { - return null; + protected Buffer getGammaRamp(final NativeSurface surface) { + return ShortBuffer.allocate(0); // return a dummy gamma ramp default for reset } @Override - protected void resetGammaRamp(final Buffer originalGammaRamp) { + protected void resetGammaRamp(final NativeSurface surface, final Buffer originalGammaRamp) { CGL.resetGammaRamp(); } + + @Override + protected final void resetGammaRamp(final DeviceScreenID deviceScreenID, final Buffer originalGammaRamp) { + CGL.resetGammaRamp(); + } + } diff --git a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java index 05e192bbc..0969199c6 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java +++ b/src/jogl/classes/jogamp/opengl/util/av/GLMediaPlayerImpl.java @@ -28,7 +28,6 @@ package jogamp.opengl.util.av; import java.io.IOException; -import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -46,7 +45,8 @@ import javax.media.opengl.GLProfile; import jogamp.opengl.Debug; -import com.jogamp.common.net.URIQueryProps; +import com.jogamp.common.net.UriQueryProps; +import com.jogamp.common.net.Uri; import com.jogamp.common.os.Platform; import com.jogamp.common.util.LFRingbuffer; import com.jogamp.common.util.Ringbuffer; @@ -92,15 +92,15 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { private final int[] texWrapST = { GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE }; /** User requested URI stream location. */ - private URI streamLoc = null; + private Uri streamLoc = null; /** * In case {@link #streamLoc} is a {@link GLMediaPlayer#CameraInputScheme}, * {@link #cameraPath} holds the URI's path portion - * as parsed in {@link #initStream(URI, int, int, int)}. + * as parsed in {@link #initStream(Uri, int, int, int)}. * @see #cameraProps */ - protected String cameraPath = null; + protected Uri.Encoded cameraPath = null; /** Optional camera properties, see {@link #cameraPath}. */ protected Map<String, String> cameraProps = null; @@ -530,7 +530,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } @Override - public final void initStream(final URI streamLoc, final int vid, final int aid, final int reqTextureCount) throws IllegalStateException, IllegalArgumentException { + public final void initStream(final Uri streamLoc, final int vid, final int aid, final int reqTextureCount) throws IllegalStateException, IllegalArgumentException { synchronized( stateLock ) { if(State.Uninitialized != state) { throw new IllegalStateException("Instance not in state unintialized: "+this); @@ -556,13 +556,13 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { // Pre-parse for camera-input scheme cameraPath = null; cameraProps = null; - final String streamLocScheme = streamLoc.getScheme(); + final Uri.Encoded streamLocScheme = streamLoc.scheme; if( null != streamLocScheme && streamLocScheme.equals(CameraInputScheme) ) { - final String rawPath = streamLoc.getRawPath(); + final Uri.Encoded rawPath = streamLoc.path; if( null != rawPath && rawPath.length() > 0 ) { // cut-off root fwd-slash cameraPath = rawPath.substring(1); - final URIQueryProps props = URIQueryProps.create(streamLoc, ';'); + final UriQueryProps props = UriQueryProps.create(streamLoc, ';'); cameraProps = props.getProperties(); } else { throw new IllegalArgumentException("Camera path is empty: "+streamLoc.toString()); @@ -1472,7 +1472,7 @@ public abstract class GLMediaPlayerImpl implements GLMediaPlayer { } @Override - public final URI getURI() { return streamLoc; } + public final Uri getUri() { return streamLoc; } @Override public final int getVID() { return vid; } diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 8ac1232b5..4601df67d 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -37,7 +37,6 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLException; -import com.jogamp.common.os.Platform; import com.jogamp.common.util.IOUtil; import com.jogamp.common.util.VersionNumber; import com.jogamp.gluegen.runtime.ProcAddressTable; @@ -292,7 +291,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { System.err.println("initStream: p1 "+this); } - final String streamLocS = IOUtil.decodeURIIfFilePath(getURI()); + final String streamLocS = IOUtil.getUriFilePathOrASCII(getUri()); destroyAudioSink(); if( GLMediaPlayer.STREAM_ID_NONE == aid ) { audioSink = AudioSinkFactory.createNull(); @@ -317,10 +316,10 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { case HPUX: case LINUX: case SUNOS: - resStreamLocS = dev_video_linux + cameraPath; + resStreamLocS = dev_video_linux + cameraPath.decode(); break; case WINDOWS: - resStreamLocS = cameraPath; + resStreamLocS = cameraPath.decode(); break; case MACOS: case OPENKODE: @@ -345,7 +344,7 @@ public class FFMPEGMediaPlayer extends GLMediaPlayerImpl { // setStream(..) issues updateAttributes*(..), and defines avChosenAudioFormat, vid, aid, .. etc if(DEBUG) { System.err.println("initStream: p3 cameraPath "+cameraPath+", isCameraInput "+isCameraInput); - System.err.println("initStream: p3 stream "+getURI()+" -> "+streamLocS+" -> "+resStreamLocS); + System.err.println("initStream: p3 stream "+getUri()+" -> "+streamLocS+" -> "+resStreamLocS); System.err.println("initStream: p3 vid "+vid+", sizes "+sizes+", reqVideo "+rw+"x"+rh+"@"+rr+", aid "+aid+", aMaxChannelCount "+aMaxChannelCount+", aPrefSampleRate "+aPrefSampleRate); } natives.setStream0(moviePtr, resStreamLocS, isCameraInput, vid, sizes, rw, rh, rr, aid, aMaxChannelCount, aPrefSampleRate); diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java index 0eeb54bf6..5baf9e543 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/OMXGLMediaPlayer.java @@ -100,10 +100,10 @@ public class OMXGLMediaPlayer extends EGLMediaPlayerImpl { if(0==moviePtr) { throw new GLException("OMX native instance null"); } - if(!getURI().getScheme().equals("file")) { - throw new IOException("Only file schemes are allowed: "+getURI()); + if( !getUri().isFileScheme() ) { + throw new IOException("Only file schemes are allowed: "+getUri()); } - final String path=getURI().getPath(); + final String path=getUri().path.decode(); if(DEBUG) { System.out.println("initGLStream: clean path "+path); } diff --git a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java index a9848f899..2dde27b1d 100644 --- a/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java +++ b/src/jogl/classes/jogamp/opengl/util/glsl/fixedfunc/FixedFuncHook.java @@ -36,6 +36,7 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLArrayData; import javax.media.opengl.GLException; +import javax.media.opengl.GLProfile; import javax.media.opengl.fixedfunc.GLLightingFunc; import javax.media.opengl.fixedfunc.GLMatrixFunc; import javax.media.opengl.fixedfunc.GLPointerFunc; @@ -50,6 +51,7 @@ import com.jogamp.opengl.util.glsl.fixedfunc.ShaderSelectionMode; public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFunc { public static final int MAX_TEXTURE_UNITS = 8; + protected final GLProfile gl2es1GLProfile; protected FixedFuncPipeline fixedFunction; protected PMVMatrix pmvMatrix; protected boolean ownsPMVMatrix; @@ -61,6 +63,7 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun * @param pmvMatrix optional pass through PMVMatrix for the {@link FixedFuncHook} and {@link FixedFuncPipeline} */ public FixedFuncHook (final GL2ES2 gl, final ShaderSelectionMode mode, final PMVMatrix pmvMatrix) { + this.gl2es1GLProfile = GLProfile.createCustomGLProfile(GLProfile.GL2ES1, gl.getGLProfile().getImpl()); this.gl = gl; if(null != pmvMatrix) { this.ownsPMVMatrix = false; @@ -81,6 +84,7 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun final Class<?> shaderRootClass, final String shaderSrcRoot, final String shaderBinRoot, final String vertexColorFile, final String vertexColorLightFile, final String fragmentColorFile, final String fragmentColorTextureFile) { + this.gl2es1GLProfile = GLProfile.createCustomGLProfile(GLProfile.GL2ES1, gl.getGLProfile().getImpl()); this.gl = gl; if(null != pmvMatrix) { this.ownsPMVMatrix = false; @@ -110,6 +114,24 @@ public class FixedFuncHook implements GLLightingFunc, GLMatrixFunc, GLPointerFun // // FixedFuncHookIf - hooks // + public final boolean isGL4core() { + return false; + } + public final boolean isGL3core() { + return false; + } + public final boolean isGLcore() { + return false; + } + public final boolean isGLES2Compatible() { + return false; + } + public final boolean isGLES3Compatible() { + return false; + } + public final GLProfile getGLProfile() { + return gl2es1GLProfile; + } public void glDrawArrays(final int mode, final int first, final int count) { fixedFunction.glDrawArrays(gl, mode, first, count); } diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java index 30559924d..36e8bc5a5 100644 --- a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java +++ b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDevice.java @@ -41,6 +41,7 @@ import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.FovHVHalves; import com.jogamp.opengl.util.stereo.EyeParameter; import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceFactory; import com.jogamp.opengl.util.stereo.StereoDeviceRenderer; import com.jogamp.opengl.util.stereo.StereoUtil; @@ -207,16 +208,20 @@ public class GenericStereoDevice implements StereoDevice { final float[] DEFAULT_EYE_POSITION_OFFSET_STEREO = { 0.0f, 0.3f, 3.0f }; // 0.3 up, 3 back final float[] DEFAULT_EYE_POSITION_OFFSET_MONO = { 0.0f, 0.0f, 3.0f }; // 3 back + final DimensionImmutable surfaceSizeInPixel = new Dimension(1280, 800); + final float[] screenSizeInMeters = new float[] { 0.1498f, 0.0936f }; + final float interpupillaryDistanceInMeters = 0.0635f; + final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f; final float d2r = FloatUtil.PI / 180.0f; { config01Mono01 = new Config( "Def01Mono01", ShutterType.RollingTopToBottom, - new Dimension(1280, 800), // resolution - new float[] { 0.1498f, 0.0936f }, // screenSize [m] - new Dimension(1280, 800), // eye textureSize - 0.0936f/2f, // pupilCenterFromScreenTop [m] - 0.0635f, // IPD [m] + surfaceSizeInPixel, // resolution + screenSizeInMeters, // screenSize [m] + surfaceSizeInPixel, // eye textureSize + pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m] + interpupillaryDistanceInMeters, // IPD [m] new int[] { 0 }, // eye order new EyeParameter[] { new EyeParameter(0, DEFAULT_EYE_POSITION_OFFSET_MONO, @@ -231,11 +236,7 @@ public class GenericStereoDevice implements StereoDevice { } { - final DimensionImmutable surfaceSizeInPixel = new Dimension(1280, 800); - final float[] screenSizeInMeters = new float[] { 0.1498f, 0.0936f }; final DimensionImmutable eyeTextureSize = new Dimension(surfaceSizeInPixel.getWidth()/2, surfaceSizeInPixel.getHeight()); - final float interpupillaryDistanceInMeters = 0.0635f; - final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f; final float[] horizPupilCenterFromLeft = Config.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters); final float vertPupilCenterFromTop = Config.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters); final float fovy = 45f; @@ -249,7 +250,7 @@ public class GenericStereoDevice implements StereoDevice { surfaceSizeInPixel, // resolution screenSizeInMeters, // screenSize [m] eyeTextureSize, // eye textureSize - 0.0936f/2f, // pupilCenterFromScreenTop [m] + pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m] interpupillaryDistanceInMeters, // IPD [m] new int[] { 0, 1 }, // eye order new EyeParameter[] { @@ -274,11 +275,7 @@ public class GenericStereoDevice implements StereoDevice { if(StereoDevice.DEBUG) { System.err.println("Caught: "+t.getMessage()); t.printStackTrace(); } } - final DimensionImmutable surfaceSizeInPixel = new Dimension(1280, 800); - final float[] screenSizeInMeters = new float[] { 0.1498f, 0.0936f }; final DimensionImmutable eyeTextureSize = new Dimension(1122, 1553); - final float interpupillaryDistanceInMeters = 0.0635f; - final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f; final float[] horizPupilCenterFromLeft = Config.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters); final float vertPupilCenterFromTop = Config.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters); final float fovy = 129f; @@ -313,6 +310,7 @@ public class GenericStereoDevice implements StereoDevice { configs = new Config[] { config01Mono01, config02StereoSBS01, config03StereoSBSLense01 }; } + private final StereoDeviceFactory factory; public final int deviceIndex; public final Config config; @@ -321,7 +319,8 @@ public class GenericStereoDevice implements StereoDevice { private boolean sensorsStarted = false; - public GenericStereoDevice(final int deviceIndex, final StereoDevice.Config customConfig) { + public GenericStereoDevice(final StereoDeviceFactory factory, final int deviceIndex, final StereoDevice.Config customConfig) { + this.factory = factory; this.deviceIndex = deviceIndex; if( customConfig instanceof GenericStereoDevice.Config) { @@ -341,6 +340,9 @@ public class GenericStereoDevice implements StereoDevice { } @Override + public final StereoDeviceFactory getFactory() { return factory; } + + @Override public String toString() { return "GenericStereoDevice["+config+", surfacePos "+surfacePos+"]"; } @@ -431,26 +433,34 @@ public class GenericStereoDevice implements StereoDevice { defaultEyeParam.distNoseToPupilX, defaultEyeParam.distMiddleToPupilY, defaultEyeParam.eyeReliefZ); } + final boolean usePP = null != config.distortionMeshProducer && 0 != distortionBits; // use post-processing + final RectangleImmutable[] eyeViewports = new RectangleImmutable[eyeParam.length]; final DimensionImmutable eyeTextureSize = config.eyeTextureSize; final DimensionImmutable totalTextureSize; if( 1 < eyeParam.length ) { // Stereo SBS totalTextureSize = new Dimension(eyeTextureSize.getWidth()*2, eyeTextureSize.getHeight()); + if( 1 == textureCount ) { // validated in ctor below! eyeViewports[0] = new Rectangle(0, 0, - totalTextureSize.getWidth() / 2, totalTextureSize.getHeight()); + eyeTextureSize.getWidth(), eyeTextureSize.getHeight()); - eyeViewports[1] = new Rectangle((totalTextureSize.getWidth() + 1) / 2, 0, - totalTextureSize.getWidth() / 2, totalTextureSize.getHeight()); + eyeViewports[1] = new Rectangle(eyeTextureSize.getWidth(), 0, + eyeTextureSize.getWidth(), eyeTextureSize.getHeight()); } else { eyeViewports[0] = new Rectangle(0, 0, eyeTextureSize.getWidth(), eyeTextureSize.getHeight()); - eyeViewports[1] = eyeViewports[0]; + if( usePP ) { + eyeViewports[1] = eyeViewports[0]; + } else { + eyeViewports[1] = new Rectangle(eyeTextureSize.getWidth(), 0, + eyeTextureSize.getWidth(), eyeTextureSize.getHeight()); + } } } else { // Mono totalTextureSize = eyeTextureSize; - eyeViewports[0] = new Rectangle(0, 0, totalTextureSize.getWidth(), totalTextureSize.getHeight()); + eyeViewports[0] = new Rectangle(0, 0, eyeTextureSize.getWidth(), eyeTextureSize.getHeight()); } return new GenericStereoDeviceRenderer(this, distortionBits, textureCount, eyePositionOffset, eyeParam, pixelsPerDisplayPixel, textureUnit, eyeTextureSize, totalTextureSize, eyeViewports); diff --git a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java index a59e8d833..f2fa74743 100644 --- a/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java +++ b/src/jogl/classes/jogamp/opengl/util/stereo/GenericStereoDeviceFactory.java @@ -38,6 +38,6 @@ public class GenericStereoDeviceFactory extends StereoDeviceFactory { @Override public final StereoDevice createDevice(final int deviceIndex, final StereoDevice.Config config, final boolean verbose) { - return new GenericStereoDevice(deviceIndex, config); + return new GenericStereoDevice(this, deviceIndex, config); } } diff --git a/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLContext.java b/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLContext.java index 9c5a5b272..33980d663 100644 --- a/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLContext.java +++ b/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLContext.java @@ -51,6 +51,7 @@ import javax.media.nativewindow.NativeSurface; import javax.media.opengl.GLContext; import javax.media.opengl.GLException; import javax.media.opengl.GLCapabilitiesImmutable; +import javax.media.opengl.GLProfile; import com.jogamp.common.nio.Buffers; import com.jogamp.gluegen.runtime.ProcAddressTable; @@ -330,7 +331,7 @@ public class WindowsWGLContext extends GLContextImpl { isProcCreateContextAttribsARBAvailable = false; isExtARBCreateContextAvailable = false; } - if ( isProcCreateContextAttribsARBAvailable && isExtARBCreateContextAvailable ) { + if ( isProcCreateContextAttribsARBAvailable && isExtARBCreateContextAvailable && !GLProfile.disableOpenGLARBContext ) { // initial ARB context creation contextHandle = createContextARB(shareWithHandle, true); createContextARBTried=true; @@ -343,7 +344,9 @@ public class WindowsWGLContext extends GLContextImpl { } } else if (DEBUG) { System.err.println(getThreadName() + ": createContextImpl: NOT OK (ARB, initial) - extension not available - share "+toHexString(shareWithHandle)+ - ", isProcCreateContextAttribsARBAvailable "+isProcCreateContextAttribsARBAvailable+", isExtGLXARBCreateContextAvailable "+isExtARBCreateContextAvailable); + ", isProcCreateContextAttribsARBAvailable "+isProcCreateContextAttribsARBAvailable+ + ", isExtGLXARBCreateContextAvailable "+isExtARBCreateContextAvailable+ + ", disableOpenGLARBContext "+GLProfile.disableOpenGLARBContext); } } } else { @@ -360,16 +363,18 @@ public class WindowsWGLContext extends GLContextImpl { } } } else { - if( glCaps.getGLProfile().isGL3() ) { - WGL.wglMakeCurrent(0, 0); - WGL.wglDeleteContext(temp_ctx); - throw new GLException(getThreadName()+": WindowsWGLContex.createContextImpl ctx !ARB, profile > GL2 requested (OpenGL >= 3.0.1). Requested: "+glCaps.getGLProfile()+", current: "+getGLVersion()); + if( glCaps.getGLProfile().isGL3() && createContextARBTried ) { + // We shall not allow context creation >= GL3 w/ non ARB methods if ARB is used, + // otherwise context of similar profile but different creation method may not be share-able. + WGL.wglMakeCurrent(0, 0); + WGL.wglDeleteContext(temp_ctx); + throw new GLException(getThreadName()+": WindowsWGLContex.createContextImpl ctx !ARB but ARB is used, profile > GL2 requested (OpenGL >= 3.0.1). Requested: "+glCaps.getGLProfile()+", current: "+getGLVersion()); } if(DEBUG) { - System.err.println("WindowsWGLContext.createContext failed, fall back to !ARB context "+getGLVersion()); + System.err.println("WindowsWGLContext.createContext ARB not used, fall back to !ARB context "+getGLVersion()); } - // continue with temp context for GL < 3.0 + // continue with temp context contextHandle = temp_ctx; if ( !wglMakeContextCurrent(drawable.getHandle(), drawableRead.getHandle(), contextHandle) ) { WGL.wglMakeCurrent(0, 0); diff --git a/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLDrawableFactory.java b/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLDrawableFactory.java index 4d8c85137..fa052d784 100644 --- a/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLDrawableFactory.java +++ b/src/jogl/classes/jogamp/opengl/windows/wgl/WindowsWGLDrawableFactory.java @@ -68,6 +68,7 @@ import jogamp.nativewindow.windows.GDI; import jogamp.nativewindow.windows.GDIDummyUpstreamSurfaceHook; import jogamp.nativewindow.windows.GDISurface; import jogamp.nativewindow.windows.RegisteredClassFactory; +import jogamp.opengl.Debug; import jogamp.opengl.DesktopGLDynamicLookupHelper; import jogamp.opengl.GLContextImpl; import jogamp.opengl.GLDrawableFactoryImpl; @@ -77,17 +78,80 @@ import jogamp.opengl.GLGraphicsConfigurationUtil; import jogamp.opengl.SharedResourceRunner; import com.jogamp.common.nio.PointerBuffer; +import com.jogamp.common.util.PropertyAccess; import com.jogamp.common.util.ReflectionUtil; import com.jogamp.nativewindow.windows.WindowsGraphicsDevice; import com.jogamp.opengl.GLExtensions; import com.jogamp.opengl.GLRendererQuirks; public class WindowsWGLDrawableFactory extends GLDrawableFactoryImpl { + /** + * Bug 1036: NVidia Windows Driver 'Threaded optimization' workaround. + * <p> + * https://jogamp.org/bugzilla/show_bug.cgi?id=1036 + * </p> + * <p> + * Since NV driver 260.99 from 2010-12-11 a 'Threaded optimization' feature has been introduced. + * The driver spawns off a dedicated thread to off-load certain OpenGL tasks from the calling thread + * to perform them async and off-thread. + * </p> + * <p> + * If 'Threaded optimization' is manually enabled 'on', the driver may crash with JOGL's consistent + * multi-threaded usage - this is a driver bug. + * </p> + * <p> + * If 'Threaded optimization' is manually disabled 'off', the driver always works correctly. + * </p> + * <p> + * 'Threaded optimization' default setting is 'auto' and the driver may crash without this workaround. + * </p> + * <p> + * If setting the process affinity to '1' (1st CPU) while initialization and launching + * the {@link SharedResourceRunner}, the driver does not crash anymore in 'auto' mode. + * This might be either because the driver does not enable 'Threaded optimization' + * or because the driver's worker thread is bound to the same CPU. + * </p> + * <p> + * Property integer value <code>jogl.windows.cpu_affinity_mode</code>: + * <ul> + * <li>0 - none (no affinity, may cause driver crash with 'Threaded optimization' = ['auto', 'on'])</li> + * <li>1 - process affinity (default, workaround for driver crash for 'Threaded optimization' = 'auto', still crashes if set to 'on')</li> + * </ul> + * </p> + * <p> + * Test case reproducing the crash reliable is: com.jogamp.opengl.test.junit.jogl.caps.TestTranslucencyNEWT<br> + * (don't ask why ..) + * </p> + */ + private static final int CPU_AFFINITY_MODE; + + static { + Debug.initSingleton(); + CPU_AFFINITY_MODE = PropertyAccess.getIntProperty("jogl.windows.cpu_affinity_mode", true, 1); + } + private static DesktopGLDynamicLookupHelper windowsWGLDynamicLookupHelper = null; + private final CPUAffinity cpuAffinity; + public WindowsWGLDrawableFactory() { super(); + switch( CPU_AFFINITY_MODE ) { + case 0: + cpuAffinity = new NopCPUAffinity(); + break; + /** + * Doesn't work ! + case 2: + cpuAffinity = new WindowsThreadAffinity(); + break; + */ + default: + cpuAffinity = new WindowsProcessAffinity(); + break; + } + synchronized(WindowsWGLDrawableFactory.class) { if( null == windowsWGLDynamicLookupHelper ) { windowsWGLDynamicLookupHelper = AccessController.doPrivileged(new PrivilegedAction<DesktopGLDynamicLookupHelper>() { @@ -168,45 +232,23 @@ public class WindowsWGLDrawableFactory extends GLDrawableFactoryImpl { return windowsWGLDynamicLookupHelper; } + /* pp */ static String toHexString(final long l) { return "0x"+Long.toHexString(l); } + private WindowsGraphicsDevice defaultDevice; private SharedResourceRunner sharedResourceRunner; private HashMap<String /*connection*/, SharedResourceRunner.Resource> sharedMap; - private long processAffinityChanges = 0; - private final PointerBuffer procMask = PointerBuffer.allocateDirect(1); - private final PointerBuffer sysMask = PointerBuffer.allocateDirect(1); - @Override protected void enterThreadCriticalZone() { - synchronized (sysMask) { - if( 0 == processAffinityChanges) { - final long pid = GDI.GetCurrentProcess(); - if ( GDI.GetProcessAffinityMask(pid, procMask, sysMask) ) { - if(DEBUG) { - System.err.println("WindowsWGLDrawableFactory.enterThreadCriticalZone() - 0x" + Long.toHexString(pid) + " - " + getThreadName()); - // Thread.dumpStack(); - } - processAffinityChanges = pid; - GDI.SetProcessAffinityMask(pid, 1); - } - } + synchronized (cpuAffinity) { + cpuAffinity.set(1); } } @Override protected void leaveThreadCriticalZone() { - synchronized (sysMask) { - if( 0 != processAffinityChanges) { - final long pid = GDI.GetCurrentProcess(); - if( pid != processAffinityChanges) { - throw new GLException("PID doesn't match: set PID 0x" + Long.toHexString(processAffinityChanges) + - " this PID 0x" + Long.toHexString(pid) ); - } - if(DEBUG) { - System.err.println("WindowsWGLDrawableFactory.leaveThreadCriticalZone() - 0x" + Long.toHexString(pid) + " - " + getThreadName()); - } - GDI.SetProcessAffinityMask(pid, sysMask.get(0)); - } + synchronized (cpuAffinity) { + cpuAffinity.reset(); } } @@ -344,6 +386,7 @@ public class WindowsWGLDrawableFactory extends GLDrawableFactoryImpl { if (null != sr.context) { // may cause JVM SIGSEGV: sharedContext.destroy(); + sr.context.destroy(); // will also pull the dummy MutableSurface sr.context = null; } @@ -556,12 +599,12 @@ public class WindowsWGLDrawableFactory extends GLDrawableFactoryImpl { private static final int GAMMA_RAMP_LENGTH = 256; @Override - protected final int getGammaRampLength() { + protected final int getGammaRampLength(final NativeSurface surface) { return GAMMA_RAMP_LENGTH; } @Override - protected final boolean setGammaRamp(final float[] ramp) { + protected final boolean setGammaRamp(final NativeSurface surface, final float[] ramp) { final short[] rampData = new short[3 * GAMMA_RAMP_LENGTH]; for (int i = 0; i < GAMMA_RAMP_LENGTH; i++) { final short scaledValue = (short) (ramp[i] * 65535); @@ -570,18 +613,26 @@ public class WindowsWGLDrawableFactory extends GLDrawableFactoryImpl { rampData[i + 2 * GAMMA_RAMP_LENGTH] = scaledValue; } - final long screenDC = GDI.GetDC(0); - final boolean res = GDI.SetDeviceGammaRamp(screenDC, ShortBuffer.wrap(rampData)); - GDI.ReleaseDC(0, screenDC); + final long hDC = surface.getSurfaceHandle(); + if( 0 == hDC ) { + return false; + } + // final long screenDC = GDI.GetDC(0); + final boolean res = GDI.SetDeviceGammaRamp(hDC, ShortBuffer.wrap(rampData)); + // GDI.ReleaseDC(0, screenDC); return res; } @Override - protected final Buffer getGammaRamp() { + protected final Buffer getGammaRamp(final NativeSurface surface) { final ShortBuffer rampData = ShortBuffer.wrap(new short[3 * GAMMA_RAMP_LENGTH]); - final long screenDC = GDI.GetDC(0); - final boolean res = GDI.GetDeviceGammaRamp(screenDC, rampData); - GDI.ReleaseDC(0, screenDC); + final long hDC = surface.getSurfaceHandle(); + if( 0 == hDC ) { + return null; + } + // final long screenDC = GDI.GetDC(0); + final boolean res = GDI.GetDeviceGammaRamp(hDC, rampData); + // GDI.ReleaseDC(0, screenDC); if (!res) { return null; } @@ -589,7 +640,22 @@ public class WindowsWGLDrawableFactory extends GLDrawableFactoryImpl { } @Override - protected final void resetGammaRamp(final Buffer originalGammaRamp) { + protected final void resetGammaRamp(final NativeSurface surface, final Buffer originalGammaRamp) { + if (originalGammaRamp == null) { + // getGammaRamp failed earlier + return; + } + final long hDC = surface.getSurfaceHandle(); + if( 0 == hDC ) { + return; + } + // final long screenDC = GDI.GetDC(0); + GDI.SetDeviceGammaRamp(hDC, originalGammaRamp); + // GDI.ReleaseDC(0, hDC); + } + + @Override + protected final void resetGammaRamp(final DeviceScreenID deviceScreenID, final Buffer originalGammaRamp) { if (originalGammaRamp == null) { // getGammaRamp failed earlier return; @@ -598,4 +664,143 @@ public class WindowsWGLDrawableFactory extends GLDrawableFactoryImpl { GDI.SetDeviceGammaRamp(screenDC, originalGammaRamp); GDI.ReleaseDC(0, screenDC); } + + + static interface CPUAffinity { + boolean set(final int newAffinity); + boolean reset(); + } + static final class WindowsThreadAffinity implements CPUAffinity { + private long threadHandle; + private long threadOrigAffinity; + private long threadNewAffinity; + public WindowsThreadAffinity() { + threadHandle = 0; + threadOrigAffinity = 0; + threadNewAffinity = 0; + } + @Override + public boolean set(final int newAffinity) { + final long tid = GDI.GetCurrentThread(); + if( 0 != threadHandle ) { + throw new IllegalStateException("Affinity already set"); + } + final long threadLastAffinity = GDI.SetThreadAffinityMask(tid, newAffinity); + final int werr = GDI.GetLastError(); + final boolean res; + if( 0 != threadLastAffinity ) { + res = true; + this.threadHandle = tid; + this.threadNewAffinity = newAffinity; + this.threadOrigAffinity = threadLastAffinity; + } else { + res = false; + } + if(DEBUG) { + System.err.println("WindowsThreadAffinity.set() - tid " + toHexString(tid) + " - " + getThreadName() + + ": OK "+res+" (werr "+werr+"), Affinity: "+toHexString(threadOrigAffinity) + " -> " + toHexString(newAffinity)); + } + return res; + } + @Override + public boolean reset() { + if( 0 == threadHandle ) { + return true; + } + final long tid = GDI.GetCurrentThread(); + if( tid != threadHandle) { + throw new IllegalStateException("TID doesn't match: set TID " + toHexString(threadHandle) + + " this TID " + toHexString(tid) ); + } + final long preThreadAffinity = GDI.SetThreadAffinityMask(threadHandle, threadOrigAffinity); + final boolean res = 0 != preThreadAffinity; + if(DEBUG) { + System.err.println("WindowsThreadAffinity.reset() - tid " + toHexString(threadHandle) + " - " + getThreadName() + + ": OK "+res+" (werr "+GDI.GetLastError()+"), Affinity: "+toHexString(threadNewAffinity)+" -> orig "+ toHexString(threadOrigAffinity)); + } + this.threadHandle = 0; + this.threadNewAffinity = this.threadOrigAffinity; + return res; + } + } + static final class WindowsProcessAffinity implements CPUAffinity { + private long processHandle; + private long newAffinity; + private final PointerBuffer procMask; + private final PointerBuffer sysMask; + + public WindowsProcessAffinity() { + processHandle = 0; + newAffinity = 0; + procMask = PointerBuffer.allocateDirect(1); + sysMask = PointerBuffer.allocateDirect(1); + } + @Override + public boolean set(final int newAffinity) { + if( 0 != processHandle ) { + throw new IllegalStateException("Affinity already set"); + } + final long pid = GDI.GetCurrentProcess(); + final boolean res; + if ( GDI.GetProcessAffinityMask(pid, procMask, sysMask) ) { + if( GDI.SetProcessAffinityMask(pid, newAffinity) ) { + this.processHandle = pid; + this.newAffinity = newAffinity; + res = true; + } else { + res = false; + } + if(DEBUG) { + System.err.println("WindowsProcessAffinity.set() - pid " + toHexString(pid) + " - " + getThreadName() + + ": OK "+res+" (werr "+GDI.GetLastError()+"), Affinity: procMask "+ toHexString(procMask.get(0)) + ", sysMask "+ toHexString(sysMask.get(0)) + + " -> "+toHexString(newAffinity)); + } + } else { + if(DEBUG) { + System.err.println("WindowsProcessAffinity.set() - pid " + toHexString(pid) + " - " + getThreadName() + + ": Error, could not GetProcessAffinityMask, werr "+GDI.GetLastError()); + } + res = false; + } + return res; + } + @Override + public boolean reset() { + if( 0 == processHandle ) { + return true; + } + final long pid = GDI.GetCurrentProcess(); + if( pid != processHandle) { + throw new IllegalStateException("PID doesn't match: set PID " + toHexString(processHandle) + + " this PID " + toHexString(pid) ); + } + final long origProcAffinity = procMask.get(0); + final boolean res = GDI.SetProcessAffinityMask(processHandle, origProcAffinity); + if(DEBUG) { + final int werr = GDI.GetLastError(); + System.err.println("WindowsProcessAffinity.reset() - pid " + toHexString(processHandle) + " - " + getThreadName() + + ": OK "+res+" (werr "+werr+"), Affinity: "+toHexString(newAffinity)+" -> procMask "+ toHexString(origProcAffinity)); + } + this.processHandle = 0; + this.newAffinity = origProcAffinity; + return res; + } + } + static final class NopCPUAffinity implements CPUAffinity { + public NopCPUAffinity() { } + @Override + public boolean set(final int newAffinity) { + if(DEBUG) { + System.err.println("NopCPUAffinity.set() - " + getThreadName()); + } + return false; + } + @Override + public boolean reset() { + if(DEBUG) { + System.err.println("NopCPUAffinity.reset() - " + getThreadName()); + } + return false; + } + } } diff --git a/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java b/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java index d4c3abc49..9631dbb5b 100644 --- a/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java +++ b/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXContext.java @@ -303,8 +303,8 @@ public class X11GLXContext extends GLContextImpl { if( !config.hasFBConfig() ) { // not able to use FBConfig -> GLX 1.1 forceGLXVersionOneOne(); - if(glp.isGL3()) { - throw new GLException(getThreadName()+": Unable to create OpenGL >= 3.1 context"); + if( glp.isGL3() ) { + throw new GLException(getThreadName()+": Unable to create OpenGL >= 3.1 context w/o FBConfig"); } contextHandle = GLX.glXCreateContext(display, config.getXVisualInfo(), shareWithHandle, direct); if ( 0 == contextHandle ) { @@ -343,7 +343,7 @@ public class X11GLXContext extends GLContextImpl { throw new GLException(getThreadName()+": Unable to create temp OpenGL context(1)"); } if ( !glXMakeContextCurrent(display, drawable.getHandle(), drawableRead.getHandle(), temp_ctx) ) { - throw new GLException(getThreadName()+": Error making temp context(1) current: display "+toHexString(display)+", context "+toHexString(temp_ctx)+", drawable "+drawable); + throw new GLException(getThreadName()+": Error making temp context(1) current: display "+toHexString(display)+", context "+toHexString(temp_ctx)+", drawable "+drawable); } setGLFunctionAvailability(true, 0, 0, CTX_PROFILE_COMPAT, false /* strictMatch */, null == sharedContext /* withinGLVersionsMapping */); // use GL_VERSION glXMakeContextCurrent(display, 0, 0, 0); // release temp context @@ -351,7 +351,7 @@ public class X11GLXContext extends GLContextImpl { // is*Available calls are valid since setGLFunctionAvailability(..) was called final boolean isProcCreateContextAttribsARBAvailable = isFunctionAvailable("glXCreateContextAttribsARB"); final boolean isExtARBCreateContextAvailable = isExtensionAvailable("GLX_ARB_create_context"); - if ( isProcCreateContextAttribsARBAvailable && isExtARBCreateContextAvailable ) { + if ( isProcCreateContextAttribsARBAvailable && isExtARBCreateContextAvailable && !GLProfile.disableOpenGLARBContext ) { // initial ARB context creation contextHandle = createContextARB(shareWithHandle, direct); createContextARBTried=true; @@ -362,9 +362,11 @@ public class X11GLXContext extends GLContextImpl { System.err.println(getThreadName() + ": createContextImpl: NOT OK (ARB, initial) - creation failed - share "+toHexString(shareWithHandle)); } } - } else if (DEBUG) { + } else if( DEBUG ) { System.err.println(getThreadName() + ": createContextImpl: NOT OK (ARB, initial) - extension not available - share "+toHexString(shareWithHandle)+ - ", isProcCreateContextAttribsARBAvailable "+isProcCreateContextAttribsARBAvailable+", isExtGLXARBCreateContextAvailable "+isExtARBCreateContextAvailable); + ", isProcCreateContextAttribsARBAvailable "+isProcCreateContextAttribsARBAvailable+ + ", isExtGLXARBCreateContextAvailable "+isExtARBCreateContextAvailable+ + ", disableOpenGLARBContext "+GLProfile.disableOpenGLARBContext); } } } else { @@ -380,21 +382,24 @@ public class X11GLXContext extends GLContextImpl { } } } else { - if( glp.isGL3() ) { - glXMakeContextCurrent(display, 0, 0, 0); - GLX.glXDestroyContext(display, temp_ctx); - throw new GLException(getThreadName()+": X11GLXContext.createContextImpl ctx !ARB, profile > GL2 requested (OpenGL >= 3.0.1). Requested: "+glp+", current: "+getGLVersion()); + if( glp.isGL3() && createContextARBTried ) { + // We shall not allow context creation >= GL3 w/ non ARB methods if ARB is used, + // otherwise context of similar profile but different creation method may not be share-able. + glXMakeContextCurrent(display, 0, 0, 0); + GLX.glXDestroyContext(display, temp_ctx); + throw new GLException(getThreadName()+": X11GLXContext.createContextImpl ctx !ARB but ARB is used, profile > GL2 requested (OpenGL >= 3.0.1). Requested: "+glp+", current: "+getGLVersion()); } + if(DEBUG) { - System.err.println(getThreadName()+": X11GLXContext.createContextImpl failed, fall back to !ARB context "+getGLVersion()); + System.err.println(getThreadName()+": X11GLXContext.createContextImpl ARB not used, fall back to !ARB context "+getGLVersion()); } - // continue with temp context for GL <= 3.0 + // continue with temp context contextHandle = temp_ctx; if ( !glXMakeContextCurrent(display, drawable.getHandle(), drawableRead.getHandle(), contextHandle) ) { - glXMakeContextCurrent(display, 0, 0, 0); - GLX.glXDestroyContext(display, temp_ctx); - throw new GLException(getThreadName()+": Error making context(1) current: display "+toHexString(display)+", context "+toHexString(contextHandle)+", drawable "+drawable); + glXMakeContextCurrent(display, 0, 0, 0); + GLX.glXDestroyContext(display, temp_ctx); + throw new GLException(getThreadName()+": Error making context(1) current: display "+toHexString(display)+", context "+toHexString(contextHandle)+", drawable "+drawable); } if (DEBUG) { System.err.println(getThreadName() + ": createContextImpl: OK (old-2) share "+toHexString(shareWithHandle)); diff --git a/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXDrawableFactory.java b/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXDrawableFactory.java index fbab32963..60e4438d0 100644 --- a/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXDrawableFactory.java +++ b/src/jogl/classes/jogamp/opengl/x11/glx/X11GLXDrawableFactory.java @@ -72,6 +72,7 @@ import jogamp.opengl.GLDynamicLookupHelper; import jogamp.opengl.GLGraphicsConfigurationUtil; import jogamp.opengl.SharedResourceRunner; +import com.jogamp.common.nio.Buffers; import com.jogamp.common.util.VersionNumber; import com.jogamp.nativewindow.x11.X11GraphicsDevice; import com.jogamp.nativewindow.x11.X11GraphicsScreen; @@ -562,34 +563,34 @@ public class X11GLXDrawableFactory extends GLDrawableFactoryImpl { private boolean gotGammaRampLength; private int gammaRampLength; @Override - protected final synchronized int getGammaRampLength() { + protected final synchronized int getGammaRampLength(final NativeSurface surface) { if (gotGammaRampLength) { return gammaRampLength; } - - final long display = getOrCreateSharedDpy(defaultDevice); + final long display = surface.getDisplayHandle(); if(0 == display) { return 0; } + final int screenIdx = surface.getScreenIndex(); final int[] size = new int[1]; - final boolean res = X11Lib.XF86VidModeGetGammaRampSize(display, - X11Lib.DefaultScreen(display), - size, 0); + final boolean res = X11Lib.XF86VidModeGetGammaRampSize(display, screenIdx, size, 0); if (!res) { return 0; } gotGammaRampLength = true; gammaRampLength = size[0]; + System.err.println("XXX: Gamma ramp size: "+gammaRampLength); return gammaRampLength; } @Override - protected final boolean setGammaRamp(final float[] ramp) { - final long display = getOrCreateSharedDpy(defaultDevice); + protected final boolean setGammaRamp(final NativeSurface surface, final float[] ramp) { + final long display = surface.getDisplayHandle(); if(0 == display) { return false; } + final int screenIdx = surface.getScreenIndex(); final int len = ramp.length; final short[] rampData = new short[len]; @@ -597,36 +598,52 @@ public class X11GLXDrawableFactory extends GLDrawableFactoryImpl { rampData[i] = (short) (ramp[i] * 65535); } - final boolean res = X11Lib.XF86VidModeSetGammaRamp(display, - X11Lib.DefaultScreen(display), + final boolean res = X11Lib.XF86VidModeSetGammaRamp(display, screenIdx, rampData.length, rampData, 0, rampData, 0, rampData, 0); + if( DEBUG ) { + dumpRamp("SET__", rampData.length, rampData, rampData, rampData); + } return res; } + private static void dumpRamp(final String prefix, final int size, final ShortBuffer r, final ShortBuffer g, final ShortBuffer b) { + for(int i=0; i<size; i++) { + if( 0 == i % 4 ) { + System.err.printf("%n%4d/%4d %s: ", i, size, prefix); + } + System.err.printf(" [%04X %04X %04X], ", r.get(i), g.get(i), b.get(i)); + } + System.err.println(); + } + private static void dumpRamp(final String prefix, final int size, final short[] r, final short[] g, final short[] b) { + for(int i=0; i<size; i++) { + if( 0 == i % 4 ) { + System.err.printf("%n%4d/%4d %s: ", i, size, prefix); + } + System.err.printf(" [%04X %04X %04X], ", r[i], g[i], b[i]); + } + System.err.println(); + } + @Override - protected final Buffer getGammaRamp() { - final long display = getOrCreateSharedDpy(defaultDevice); + protected final Buffer getGammaRamp(final NativeSurface surface) { + final long display = surface.getDisplayHandle(); if(0 == display) { return null; } + final int screenIdx = surface.getScreenIndex(); + + final int size = getGammaRampLength(surface); - final int size = getGammaRampLength(); - final ShortBuffer rampData = ShortBuffer.wrap(new short[3 * size]); - rampData.position(0); - rampData.limit(size); - final ShortBuffer redRampData = rampData.slice(); - rampData.position(size); - rampData.limit(2 * size); - final ShortBuffer greenRampData = rampData.slice(); - rampData.position(2 * size); - rampData.limit(3 * size); - final ShortBuffer blueRampData = rampData.slice(); - - final boolean res = X11Lib.XF86VidModeGetGammaRamp(display, - X11Lib.DefaultScreen(display), + final ShortBuffer rampData = Buffers.newDirectShortBuffer(3 * size); + final ShortBuffer redRampData = Buffers.slice(rampData, 0 * size, size); + final ShortBuffer greenRampData = Buffers.slice(rampData, 1 * size, size); + final ShortBuffer blueRampData = Buffers.slice(rampData, 2 * size, size); + + final boolean res = X11Lib.XF86VidModeGetGammaRamp(display, screenIdx, size, redRampData, greenRampData, @@ -634,40 +651,62 @@ public class X11GLXDrawableFactory extends GLDrawableFactoryImpl { if (!res) { return null; } + if( DEBUG ) { + dumpRamp("GET__", size, redRampData, greenRampData, blueRampData); + } return rampData; } @Override - protected final void resetGammaRamp(final Buffer originalGammaRamp) { + protected final void resetGammaRamp(final NativeSurface surface, final Buffer originalGammaRamp) { if (originalGammaRamp == null) { return; // getGammaRamp failed originally } - final long display = getOrCreateSharedDpy(defaultDevice); + final long display = surface.getDisplayHandle(); if(0 == display) { return; } + final int screenIdx = surface.getScreenIndex(); + + resetGammaRamp(display, screenIdx, originalGammaRamp); + } + + @Override + protected final void resetGammaRamp(final DeviceScreenID deviceScreenID, final Buffer originalGammaRamp) { + if (originalGammaRamp == null) { + return; // getGammaRamp failed originally + } + final long display = X11Util.openDisplay(deviceScreenID.deviceConnection); + if( 0 == display ) { + return; + } + try { + resetGammaRamp(display, deviceScreenID.screenIdx, originalGammaRamp); + } finally { + X11Util.closeDisplay(display); + } + } + private static final void resetGammaRamp(final long display, final int screenIdx, final Buffer originalGammaRamp) { final ShortBuffer rampData = (ShortBuffer) originalGammaRamp; final int capacity = rampData.capacity(); if ((capacity % 3) != 0) { throw new IllegalArgumentException("Must not be the original gamma ramp"); } final int size = capacity / 3; - rampData.position(0); - rampData.limit(size); - final ShortBuffer redRampData = rampData.slice(); - rampData.position(size); - rampData.limit(2 * size); - final ShortBuffer greenRampData = rampData.slice(); - rampData.position(2 * size); - rampData.limit(3 * size); - final ShortBuffer blueRampData = rampData.slice(); - - X11Lib.XF86VidModeSetGammaRamp(display, - X11Lib.DefaultScreen(display), - size, - redRampData, - greenRampData, - blueRampData); + + final ShortBuffer redRampData = Buffers.slice(rampData, 0 * size, size); + final ShortBuffer greenRampData = Buffers.slice(rampData, 1 * size, size); + final ShortBuffer blueRampData = Buffers.slice(rampData, 2 * size, size); + if( DEBUG ) { + dumpRamp("RESET", size, redRampData, greenRampData, blueRampData); + } + + X11Lib.XF86VidModeSetGammaRamp(display, screenIdx, + size, + redRampData, + greenRampData, + blueRampData); } + } |