diff options
author | Sven Göthel <[email protected]> | 2024-02-17 20:26:10 +0100 |
---|---|---|
committer | Sven Göthel <[email protected]> | 2024-02-17 20:26:10 +0100 |
commit | 8bb2f6dec8ab731b07387b947715fa1959c680e4 (patch) | |
tree | 673236ebd39cc69b05579522c118010571b205f4 /src/graphui/classes/com/jogamp/graph | |
parent | 82288c112e910feae10ef3cfcded50e35395ed2b (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.
Diffstat (limited to 'src/graphui/classes/com/jogamp/graph')
-rw-r--r-- | src/graphui/classes/com/jogamp/graph/ui/Group.java | 85 | ||||
-rw-r--r-- | src/graphui/classes/com/jogamp/graph/ui/Scene.java | 76 |
2 files changed, 98 insertions, 63 deletions
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; } |