aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Göthel <[email protected]>2024-02-17 20:26:10 +0100
committerSven Göthel <[email protected]>2024-02-17 20:26:10 +0100
commit8bb2f6dec8ab731b07387b947715fa1959c680e4 (patch)
tree673236ebd39cc69b05579522c118010571b205f4
parent82288c112e910feae10ef3cfcded50e35395ed2b (diff)
Bug 1489: Lock-Free Double-Buffered 'renderedShapes' causes data-race between rendering & input-edt, use synchronized tripple-buffering
Tripple-buffering _almost_ produces zero data-race collisions, however .. it still does rarely -> hence synchronize on the used ArrayList<>. This adds a minimal chance for blocking the input-EDT, but gives correct code & results. Double-buffered 'renderedShapes' was introduced to resolve Bug 1489 in commit 5f9fb7159fa33bc979e5050d384b6939658049bd This solution is tested by passing '-swapInterval 0' via CommandlineOptions for FontView01, UIMediaGrid01 .., i.e. rendering faster than picking and hence provoking the data-race condition.
-rw-r--r--make/scripts/tests.sh1
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/FontView01.java4
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UIMediaGrid01.java4
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java4
-rw-r--r--src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java4
-rw-r--r--src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java6
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/Group.java85
-rw-r--r--src/graphui/classes/com/jogamp/graph/ui/Scene.java76
-rw-r--r--src/graphui/classes/jogamp/graph/ui/TreeTool.java46
9 files changed, 143 insertions, 87 deletions
diff --git a/make/scripts/tests.sh b/make/scripts/tests.sh
index abb0b912c..4447847cf 100644
--- a/make/scripts/tests.sh
+++ b/make/scripts/tests.sh
@@ -1032,7 +1032,6 @@ testnoawt com.jogamp.opengl.demos.graph.ui.FontView01 $*
#testawt com.jogamp.opengl.test.junit.jogl.acore.TestDestroyGLAutoDrawableNewtAWT $*
#testnoawt com.jogamp.opengl.demos.av.MovieSimple $*
#testnoawt com.jogamp.opengl.demos.av.MovieCube $*
-#testnoawt com.jogamp.opengl.demos.graph.ui.UIMediaGrid00 $*
#testnoawt com.jogamp.opengl.demos.graph.ui.UIMediaGrid01 $*
#testnoawt com.jogamp.opengl.demos.graph.ui.FontView01 $*
#testawt com.jogamp.opengl.demos.graph.ui.UISceneDemo20 $*
diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/FontView01.java b/src/demos/com/jogamp/opengl/demos/graph/ui/FontView01.java
index 770b1b724..5804c09c0 100644
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/FontView01.java
+++ b/src/demos/com/jogamp/opengl/demos/graph/ui/FontView01.java
@@ -167,6 +167,10 @@ public class FontView01 {
final Animator animator = new Animator(0 /* w/o AWT */);
animator.setUpdateFPSFrames(1*60, null); // System.err);
final GLWindow window = GLWindow.create(reqCaps);
+ window.invoke(false, (final GLAutoDrawable glad) -> {
+ glad.getGL().setSwapInterval(options.swapInterval);
+ return true;
+ } );
window.setSize(options.surface_width, options.surface_height);
window.setTitle(FontView01.class.getSimpleName()+": "+font.getFullFamilyName()+", "+window.getSurfaceWidth()+" x "+window.getSurfaceHeight());
window.setVisible(true);
diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/UIMediaGrid01.java b/src/demos/com/jogamp/opengl/demos/graph/ui/UIMediaGrid01.java
index 29ee9ab2c..7b9053ab0 100644
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/UIMediaGrid01.java
+++ b/src/demos/com/jogamp/opengl/demos/graph/ui/UIMediaGrid01.java
@@ -216,6 +216,10 @@ public class UIMediaGrid01 {
final Animator animator = new Animator(0 /* w/o AWT */);
animator.setUpdateFPSFrames(1*60, null); // System.err);
final GLWindow window = GLWindow.create(reqCaps);
+ window.invoke(false, (final GLAutoDrawable glad) -> {
+ glad.getGL().setSwapInterval(options.swapInterval);
+ return true;
+ } );
window.setSize(options.surface_width, options.surface_height);
window.setVisible(true);
System.out.println("Chosen: " + window.getChosenGLCapabilities());
diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java
index 98ea52771..0e7ddbeb8 100644
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java
+++ b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo03.java
@@ -194,6 +194,10 @@ public class UISceneDemo03 {
animator.setUpdateFPSFrames(1 * 60, null); // System.err);
final GLWindow window = GLWindow.create(reqCaps);
+ window.invoke(false, (final GLAutoDrawable glad) -> {
+ glad.getGL().setSwapInterval(options.swapInterval);
+ return true;
+ } );
window.setSize(options.surface_width, options.surface_height);
window.setTitle(UISceneDemo03.class.getSimpleName() + ": " + window.getSurfaceWidth() + " x " + window.getSurfaceHeight());
window.setVisible(true);
diff --git a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java
index 47c84c914..6336bb2bc 100644
--- a/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java
+++ b/src/demos/com/jogamp/opengl/demos/graph/ui/UISceneDemo20.java
@@ -155,6 +155,10 @@ public class UISceneDemo20 implements GLEventListener {
System.out.println("Requested: " + caps);
final GLWindow window = GLWindow.create(screen, caps);
+ window.invoke(false, (final GLAutoDrawable glad) -> {
+ glad.getGL().setSwapInterval(options.swapInterval);
+ return true;
+ } );
if( 0 == options.sceneMSAASamples ) {
window.setCapabilitiesChooser(new NonFSAAGLCapsChooser(false));
}
diff --git a/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java b/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java
index 3a3f2f365..60de2c1f0 100644
--- a/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java
+++ b/src/demos/com/jogamp/opengl/demos/util/CommandlineOptions.java
@@ -44,6 +44,7 @@ public class CommandlineOptions {
public boolean wait_to_start = false;
public boolean keepRunning = false;
public boolean stayOpen = false;
+ public int swapInterval = -1; // auto
public float total_duration = 0f; // [s]
static {
@@ -153,6 +154,9 @@ public class CommandlineOptions {
stayOpen = true;
} else if (args[idx[0]].equals("-stay")) {
stayOpen = true;
+ } else if (args[idx[0]].equals("-swapInterval")) {
+ ++idx[0];
+ swapInterval = MiscUtils.atoi(args[idx[0]], swapInterval);
} else if (args[idx[0]].equals("-duration")) {
++idx[0];
total_duration = MiscUtils.atof(args[idx[0]], total_duration);
@@ -180,7 +184,7 @@ public class CommandlineOptions {
return "Options{surface[width "+surface_width+" x "+surface_height+"], glp "+glProfileName+
", renderModes "+Region.getRenderModeString(renderModes)+", aa-q "+graphAAQuality+
", gmsaa "+graphAASamples+", smsaa "+sceneMSAASamples+
- ", exclusiveContext "+exclusiveContext+", wait "+wait_to_start+", keep "+keepRunning+", stay "+stayOpen+", dur "+total_duration+"s"+
+ ", exclusiveContext "+exclusiveContext+", wait "+wait_to_start+", keep "+keepRunning+", stay "+stayOpen+", swap-ival "+swapInterval+", dur "+total_duration+"s"+
"}";
}
}
diff --git a/src/graphui/classes/com/jogamp/graph/ui/Group.java b/src/graphui/classes/com/jogamp/graph/ui/Group.java
index f482605ea..eeaabaa3f 100644
--- a/src/graphui/classes/com/jogamp/graph/ui/Group.java
+++ b/src/graphui/classes/com/jogamp/graph/ui/Group.java
@@ -84,7 +84,9 @@ public class Group extends Shape implements Container {
private Shape[] drawShapeArray = new Shape[0]; // reduce memory re-alloc @ display
private final List<Shape> renderedShapesB0 = new ArrayList<Shape>();
private final List<Shape> renderedShapesB1 = new ArrayList<Shape>();
+ private final List<Shape> renderedShapesB2 = new ArrayList<Shape>();
private volatile List<Shape> renderedShapes = renderedShapesB1;
+ private int renderedShapesIdx = 1;
/** Enforced fixed size. In case z-axis is NaN, its 3D z-axis will be adjusted. */
private final Vec3f fixedSize = new Vec3f();
private Layout layouter;
@@ -313,6 +315,9 @@ public class Group extends Shape implements Container {
drawShapeArray = new Shape[0];
renderedShapesB0.clear();
renderedShapesB1.clear();
+ renderedShapesB2.clear();
+ renderedShapes = renderedShapesB1;
+ renderedShapesIdx = 1;
}
@Override
@@ -326,6 +331,9 @@ public class Group extends Shape implements Container {
drawShapeArray = new Shape[0];
renderedShapesB0.clear();
renderedShapesB1.clear();
+ renderedShapesB2.clear();
+ renderedShapes = renderedShapesB1;
+ renderedShapesIdx = 1;
if( null != border ) {
border.destroy(gl, renderer);
border = null;
@@ -401,9 +409,13 @@ public class Group extends Shape implements Container {
Arrays.sort(shapeArray, 0, shapeCount, Shape.ZAscendingComparator);
// TreeTool.cullShapes(shapeArray, shapeCount);
- final List<Shape> iShapes = renderedShapes == renderedShapesB0 ? renderedShapesB1 : renderedShapesB0;
- iShapes.clear();
-
+ final List<Shape> iShapes;
+ final int iShapeIdx;
+ switch(renderedShapesIdx) {
+ case 0: iShapeIdx = 1; iShapes = renderedShapesB1; break;
+ case 1: iShapeIdx = 2; iShapes = renderedShapesB2; break;
+ default: iShapeIdx = 0; iShapes = renderedShapesB0; break;
+ }
final boolean useClipFrustum = null != clipFrustum;
if( useClipFrustum || clipOnBounds ) {
final Frustum origClipFrustum = renderer.getClipFrustum();
@@ -411,46 +423,53 @@ public class Group extends Shape implements Container {
final Frustum frustumMv = useClipFrustum ? clipFrustum : tempC00.set( box ).transform( pmv.getMv() ).updateFrustumPlanes(tempF00);
renderer.setClipFrustum( frustumMv );
- for(int i=0; i<shapeCount; i++) {
- final Shape shape = shapeArray[i];
- if( shape.isVisible() ) { // && !shape.isDiscarded() ) {
- pmv.pushMv();
- shape.applyMatToMv(pmv);
-
- final AABBox shapeBox = shape.getBounds();
- final Cube shapeMv = tempC01.set( shapeBox ).transform( pmv.getMv() );
-
- if( ( !frustumMv.isOutside( shapeMv ) ) &&
- ( !doFrustumCulling || !pmv.getFrustum().isOutside( shapeBox ) ) )
- {
- shape.draw(gl, renderer);
- iShapes.add(shape);
- shape.setDiscarded(false);
- } else {
- shape.setDiscarded(true);
+ synchronized( iShapes ) { // tripple-buffering is just almost enough
+ iShapes.clear();
+ for(int i=0; i<shapeCount; i++) {
+ final Shape shape = shapeArray[i];
+ if( shape.isVisible() ) { // && !shape.isDiscarded() ) {
+ pmv.pushMv();
+ shape.applyMatToMv(pmv);
+
+ final AABBox shapeBox = shape.getBounds();
+ final Cube shapeMv = tempC01.set( shapeBox ).transform( pmv.getMv() );
+
+ if( ( !frustumMv.isOutside( shapeMv ) ) &&
+ ( !doFrustumCulling || !pmv.getFrustum().isOutside( shapeBox ) ) )
+ {
+ shape.draw(gl, renderer);
+ iShapes.add(shape);
+ shape.setDiscarded(false);
+ } else {
+ shape.setDiscarded(true);
+ }
+ pmv.popMv();
}
- pmv.popMv();
}
}
renderer.setClipFrustum(origClipFrustum);
} else {
- for(int i=0; i<shapeCount; i++) {
- final Shape shape = shapeArray[i];
- if( shape.isVisible() ) { // && !shape.isDiscarded() ) {
- pmv.pushMv();
- shape.applyMatToMv(pmv);
- if( !doFrustumCulling || !pmv.getFrustum().isOutside( shape.getBounds() ) ) {
- shape.draw(gl, renderer);
- iShapes.add(shape);
- shape.setDiscarded(false);
- } else {
- shape.setDiscarded(true);
+ synchronized( iShapes ) { // tripple-buffering is just almost enough
+ iShapes.clear();
+ for(int i=0; i<shapeCount; i++) {
+ final Shape shape = shapeArray[i];
+ if( shape.isVisible() ) { // && !shape.isDiscarded() ) {
+ pmv.pushMv();
+ shape.applyMatToMv(pmv);
+ if( !doFrustumCulling || !pmv.getFrustum().isOutside( shape.getBounds() ) ) {
+ shape.draw(gl, renderer);
+ iShapes.add(shape);
+ shape.setDiscarded(false);
+ } else {
+ shape.setDiscarded(true);
+ }
+ pmv.popMv();
}
- pmv.popMv();
}
}
}
renderedShapes = iShapes;
+ renderedShapesIdx = iShapeIdx;
if( null != border && border.isVisible() ) {
border.draw(gl, renderer);
}
diff --git a/src/graphui/classes/com/jogamp/graph/ui/Scene.java b/src/graphui/classes/com/jogamp/graph/ui/Scene.java
index 56756de18..55033286c 100644
--- a/src/graphui/classes/com/jogamp/graph/ui/Scene.java
+++ b/src/graphui/classes/com/jogamp/graph/ui/Scene.java
@@ -134,7 +134,9 @@ public final class Scene implements Container, GLEventListener {
private Shape[] displayShapeArray = new Shape[0]; // reduce memory re-alloc @ display
private final List<Shape> renderedShapesB0 = new ArrayList<Shape>();
private final List<Shape> renderedShapesB1 = new ArrayList<Shape>();
+ private final List<Shape> renderedShapesB2 = new ArrayList<Shape>();
private volatile List<Shape> renderedShapes = renderedShapesB1;
+ private int renderedShapesIdx = 1;
private final AtomicReference<Tooltip> toolTipActive = new AtomicReference<Tooltip>();
private final AtomicReference<Shape> toolTipHUD = new AtomicReference<Shape>();
private final List<Group> topLevel = new ArrayList<Group>();
@@ -490,37 +492,47 @@ public final class Scene implements Container, GLEventListener {
Arrays.sort(shapeArray, 0, shapeCount, Shape.ZAscendingComparator);
// TreeTool.cullShapes(shapeArray, shapeCount);
- final List<Shape> iShapes = renderedShapes == renderedShapesB0 ? renderedShapesB1 : renderedShapesB0;
- iShapes.clear();
-
final GL2ES2 gl = drawable.getGL().getGL2ES2();
+ final PMVMatrix4f pmv = renderer.getMatrix();
+ final List<Shape> iShapes;
+ final int iShapeIdx;
+ switch(renderedShapesIdx) {
+ case 0: iShapeIdx = 1; iShapes = renderedShapesB1; break;
+ case 1: iShapeIdx = 2; iShapes = renderedShapesB2; break;
+ default: iShapeIdx = 0; iShapes = renderedShapesB0; break;
+ }
if( null != clearColor ) {
gl.glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
gl.glClear(clearMask);
}
- final PMVMatrix4f pmv = renderer.getMatrix();
-
renderer.enable(gl, true);
- for(int i=0; i<shapeCount; i++) {
- final Shape shape = shapeArray[i];
- if( shape.isVisible() ) { // && !shape.isDiscarded() ) {
- pmv.pushMv();
- shape.applyMatToMv(pmv);
+ synchronized( iShapes ) { // tripple-buffering is just almost enough
+ iShapes.clear();
- if( !doFrustumCulling || !pmv.getFrustum().isOutside( shape.getBounds() ) ) {
- shape.draw(gl, renderer);
- iShapes.add(shape);
- shape.setDiscarded(false);
- } else {
- shape.setDiscarded(true);
+ for(int i=0; i<shapeCount; i++) {
+ final Shape shape = shapeArray[i];
+ if( shape.isVisible() ) { // && !shape.isDiscarded() ) {
+ pmv.pushMv();
+ shape.applyMatToMv(pmv);
+
+ if( !doFrustumCulling || !pmv.getFrustum().isOutside( shape.getBounds() ) ) {
+ shape.draw(gl, renderer);
+ iShapes.add(shape);
+ shape.setDiscarded(false);
+ } else {
+ shape.setDiscarded(true);
+ }
+ pmv.popMv();
}
- pmv.popMv();
}
}
- renderedShapes = iShapes;
+
renderer.enable(gl, false);
+ renderedShapes = iShapes;
+ renderedShapesIdx = iShapeIdx;
+
synchronized ( syncDisplayedOnce ) {
displayedOnce = true;
syncDisplayedOnce.notifyAll();
@@ -616,6 +628,9 @@ public final class Scene implements Container, GLEventListener {
displayShapeArray = new Shape[0];
renderedShapesB0.clear();
renderedShapesB1.clear();
+ renderedShapesB2.clear();
+ renderedShapes = renderedShapesB1;
+ renderedShapesIdx = 1;
disposeActions.clear();
if( drawable == cDrawable ) {
cDrawable = null;
@@ -1057,21 +1072,22 @@ public final class Scene implements Container, GLEventListener {
Shape visit(Shape s, final PMVMatrix4f pmv);
}
private static Shape pickForAllRenderedDesc(final Container cont, final PMVMatrix4f pmv, final PickVisitor v) {
- final List<Shape> shapes = cont.getRenderedShapes();
Shape picked = null;
-
- for(int i=shapes.size()-1; null == picked && i>=0; --i) {
- final Shape s = shapes.get(i);
- pmv.pushMv();
- s.applyMatToMv(pmv);
- picked = v.visit(s, pmv);
- if( s instanceof Container ) {
- final Shape childPick = pickForAllRenderedDesc((Container)s, pmv, v);
- if( null != childPick ) {
- picked = childPick; // child picked overrides group parent!
+ final List<Shape> shapes = cont.getRenderedShapes();
+ synchronized( shapes ) { // tripple-buffering is just almost enough
+ for(int i=shapes.size()-1; null == picked && i>=0; --i) {
+ final Shape s = shapes.get(i);
+ pmv.pushMv();
+ s.applyMatToMv(pmv);
+ picked = v.visit(s, pmv);
+ if( s instanceof Container ) {
+ final Shape childPick = pickForAllRenderedDesc((Container)s, pmv, v);
+ if( null != childPick ) {
+ picked = childPick; // child picked overrides group parent!
+ }
}
+ pmv.popMv();
}
- pmv.popMv();
}
return picked;
}
diff --git a/src/graphui/classes/jogamp/graph/ui/TreeTool.java b/src/graphui/classes/jogamp/graph/ui/TreeTool.java
index 1309775a0..d225ead1f 100644
--- a/src/graphui/classes/jogamp/graph/ui/TreeTool.java
+++ b/src/graphui/classes/jogamp/graph/ui/TreeTool.java
@@ -174,36 +174,38 @@ public class TreeTool {
}
}
private static boolean forAllRenderedAscn(final Container cont, final PMVMatrix4f pmv, final Visitor2 v) {
- final List<Shape> shapes = cont.getRenderedShapes();
boolean res = false;
-
- for(int i=0; !res && i<shapes.size(); ++i) {
- final Shape s = shapes.get(i);
- pmv.pushMv();
- s.applyMatToMv(pmv);
- res = v.visit(s, pmv);
- if( !res && s instanceof Container ) {
- final Container c = (Container)s;
- res = forAllRenderedAscn(c, pmv, v);
+ final List<Shape> shapes = cont.getRenderedShapes();
+ synchronized( shapes ) { // tripple-buffering is just almost enough
+ for(int i=0; !res && i<shapes.size(); ++i) {
+ final Shape s = shapes.get(i);
+ pmv.pushMv();
+ s.applyMatToMv(pmv);
+ res = v.visit(s, pmv);
+ if( !res && s instanceof Container ) {
+ final Container c = (Container)s;
+ res = forAllRenderedAscn(c, pmv, v);
+ }
+ pmv.popMv();
}
- pmv.popMv();
}
return res;
}
private static boolean forAllRenderedDesc(final Container cont, final PMVMatrix4f pmv, final Visitor2 v) {
- final List<Shape> shapes = cont.getRenderedShapes();
boolean res = false;
-
- for(int i=shapes.size()-1; !res && i>=0; --i) {
- final Shape s = shapes.get(i);
- pmv.pushMv();
- s.applyMatToMv(pmv);
- res = v.visit(s, pmv);
- if( !res && s instanceof Container ) {
- final Container c = (Container)s;
- res = forAllRenderedDesc(c, pmv, v);
+ final List<Shape> shapes = cont.getRenderedShapes();
+ synchronized( shapes ) { // tripple-buffering is just almost enough
+ for(int i=shapes.size()-1; !res && i>=0; --i) {
+ final Shape s = shapes.get(i);
+ pmv.pushMv();
+ s.applyMatToMv(pmv);
+ res = v.visit(s, pmv);
+ if( !res && s instanceof Container ) {
+ final Container c = (Container)s;
+ res = forAllRenderedDesc(c, pmv, v);
+ }
+ pmv.popMv();
}
- pmv.popMv();
}
return res;
}