Commit 1f893749 authored by Matthias Betz's avatar Matthias Betz
Browse files

Guarantee redraw delivery for on-demand GL rendering



openglfx's requestRepaint is an unconditional notify with no pending flag, so a
repaint() arriving while a frame is mid-render is dropped. When a scene was
built, the setScene repaint rendered an index-less (empty) frame and the
following setIndexSet repaint was lost, so the scene only appeared after the
next rotate/zoom. Route all repaints through requestRender(), which re-delivers
via a small AnimationTimer pump until a frame consumes the request, then stops
itself (idle stays allocation-free).

Co-Authored-By: default avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 8111efcf
......@@ -8,6 +8,7 @@ import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL3;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import javafx.animation.AnimationTimer;
import javafx.application.Platform;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
......@@ -55,6 +56,22 @@ public class GLViewport {
private double lastX;
private double lastY;
// on-demand render request. openglfx can drop a repaint() that arrives while a frame is rendering
// (its requestRepaint is an unconditional notify with no pending flag), so a single repaint is not
// reliable. This flag + pump re-deliver repaint() each pulse until a frame consumes the request,
// then the pump stops itself so an idle viewport has no per-pulse cost.
private volatile boolean renderRequested;
private final AnimationTimer repaintPump = new AnimationTimer() {
@Override
public void handle(long now) {
if (renderRequested) {
canvas.repaint();
} else {
stop();
}
}
};
public GLViewport() {
// args: executor, profile, flipY=false, msaa=4, fps=0.0
// fps=0 -> on-demand rendering: a frame is drawn only when repaint() is called (every state
......@@ -72,8 +89,8 @@ public class GLViewport {
canvas.addOnRenderEvent(ev -> render((JOGLRenderEvent) ev));
canvas.addOnReshapeEvent(ev -> { /* aspect handled per-frame from width/height */ });
// on-demand mode: repaint when the canvas is resized so the framebuffer tracks the new size
canvas.widthProperty().addListener((obs, ov, nv) -> canvas.repaint());
canvas.heightProperty().addListener((obs, ov, nv) -> canvas.repaint());
canvas.widthProperty().addListener((obs, ov, nv) -> requestRender());
canvas.heightProperty().addListener((obs, ov, nv) -> requestRender());
installInputHandlers();
}
......@@ -81,15 +98,15 @@ public class GLViewport {
// ---- public API (called from FX / worker threads) ----
public void setScene(SceneData data) { pendingScene.set(data); canvas.repaint(); }
public void setScene(SceneData data) { pendingScene.set(data); requestRender(); }
public void setIndexSet(int[] indices) { pendingIndexSet.set(indices); canvas.repaint(); }
public void setIndexSet(int[] indices) { pendingIndexSet.set(indices); requestRender(); }
public void setWireframe(boolean on) { wireframe = on; canvas.repaint(); }
public void setWireframe(boolean on) { wireframe = on; requestRender(); }
public void setCulling(boolean on) { cull = on; canvas.repaint(); }
public void setCulling(boolean on) { cull = on; requestRender(); }
public void setPointScale(float scale) { pointScale = scale; canvas.repaint(); }
public void setPointScale(float scale) { pointScale = scale; requestRender(); }
/** points: xyz, pointColors rgb-per-point; lines: xyz pairs, lineColors rgb-per-line-vertex. */
public void setHighlights(float[] points, float[] pointColors, float[] lines, float[] lineColors) {
......@@ -98,20 +115,32 @@ public class GLViewport {
this.hlLines = lines;
this.hlLineColors = lineColors;
this.highlightsDirty = true;
canvas.repaint();
requestRender();
}
public OrbitCamera camera() { return camera; }
/**
* Requests a redraw. Sets the render flag, wakes the canvas immediately (best effort), and starts
* the pump so the request is re-delivered until a frame actually consumes it. Call on the FX thread.
*/
private void requestRender() {
renderRequested = true;
canvas.repaint();
repaintPump.start(); // idempotent while running
}
public void requestPick(double x, double y, Consumer<Object> callback) {
pickRequest = new double[]{x, y};
pickCallback = callback;
canvas.repaint();
requestRender();
}
// ---- render callback (GL thread) ----
private void render(JOGLRenderEvent ev) {
// consume the render request; if new state arrives during this frame, requestRender re-arms it
renderRequested = false;
GL3 gl = ev.getGl();
int w = ev.width;
int h = ev.height;
......@@ -221,13 +250,13 @@ public class GLViewport {
double speed = Math.abs(camera.distance()) / 1000.0;
camera.pan(-dx * speed, dy * speed);
}
canvas.repaint();
requestRender();
});
canvas.setOnScroll(e -> {
camera.zoom(e.getDeltaY() < 0 ? 1.05 : 0.95);
canvas.repaint();
requestRender();
});
}
public void resetCamera() { camera.reset(); canvas.repaint(); }
public void resetCamera() { camera.reset(); requestRender(); }
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment