All,

Unfortunately I'm still unable to login to oracle.com to sign any OCA,
so the attached patch is just FYI. It seemed to create my account ok but afterward confirming my email it redirected me to a page without being logged in, and then any attempt to login fails. Firefox 140.x ESR using a default profile of I reserve for finicky sites. Any suggestions?

Regardless, I spent a few days poking around including installing
ubuntu 24 lts, and building javafx on windows 10 (in a vm) to see what
was supposed to be happening.

Unlike D3D's SwapChain.Present(flags=0), glXSwapBuffers() is a not a synchronous call[1]. It only inserts a synchronisation point into the command queue which will block any calls that try to render to the back buffer before it is swapped from the front - which means it 'effectively' blocks if there's a backlog, but only at some unknown point in the future (depending on how many buffers the driver sets up).

As per [1] and the specification calling glFinish() immediately after
glXSwapBuffers() will synchronise to the display and make it behave
similarly to D3D's SwapChain.Present(0), but with GLX this only works for a single window. Each additional window adds another whole frame so they all run at 1/N the target rate.

I tried a number of approaches, some of them follow. I was focusing on making it work on both a single and multiple window application, and with consistent pulse rates.

"vsync" timers
--------------

One solution is to change the GtkGlass backend to export a non-zero
staticScreen_getVideoRefreshPeriod() which will cause Quantum to
assume the clock is accurate to vsync. And then implement something that is either accurate or close-enough.

I've tried a few different variations:

0. Use gdk_timeout but use a dynamic timeout each frame based on the
   reported frame-rate, and using logic to maintain long-term
   accuracy.

 Pros: No new mechanisms or races or build changes.
       Fairly accurate over the lont term.
       Can adapt if the monitor refresh rate changes at runtime.
       Avoids vsyncHint() logic.
 Cons: Jittery due to only millisecond precision and g_mainloop.
       gdk_monitor_get_refresh_rate() must be correct (and exist)

1. As with the Mac backend, Gtk GlassTimer starts a thread that runs
   it's own vblank timing.  Use a hidden double-buffered Window and
   call glXSwapBuffer() then glFinish() on a local context before the
   callback.

This works very well for X11 but breaks on XWayland due to [2] where
it runs at 'jittery 60Hz' regardless of the actual frame-rate because
wayland and by extension Xwayland doesn't think hidden windows should
know about vsync.

 Pros: For X11 Correct and accurate, should be well supported.
       Avoids vsyncHint() logic.
 Cons: Requires linking to libGL in gtk3glass.
       Complicates errors/fallback a bit.
       gdk_monitor_get_refresh_rate() must be correct (and exist)
       XWayland is broken.
       It is unclear whether the timer callback needs to run on the
       g_mainloop, it doesn't seem to matter.

2. As with the Mac backend, Gtk GlassTimer starts a thread that runs
   it's own timing.  This time using clock_gettime() and usleep() and
   some logic to avoid drift, handle suspend, and keep consistent
   frame timing.

This works quite well as long as GdkMonitor returns an accurate frame
rate.

 Pros: Simple, not perfect timing but fairly accurate in practice.
       Avoids syncHint() logic.
       Could just be implemented in Java if
       GlassApplication.staticScreen_getVideoRefreshPeriod() is
       accurate, although a frame-rate might be more useful.
 Cons: Complicates errors/fallback a bit (if in native)
       gdk_monitor_get_refresh_rate() must be correct (and exist)
       It is unclear whether the timer callback needs to run on the
       g_mainloop, it doesn't seem to matter.

glFinish at a different spot
----------------------------

After trying all of the above and reading some more internet forums and articles I tried another approach: run a glFinish() once, after all
GlassScene's have been rendered.

 Pros: Seems to work reliably and accurately.
       Pretty simple patch that doesn't touch much.
 Cons: Better Gtk GlassTimer would be nice.

Comments
--------

A better timer interface and for the animation.pulse value would be nice, perhaps internally fps in decimal fixed-point like GdkMonitor uses. A better Gtk GlassTimer using that, plus GLX syncing after
PaintCollector.renderAll().

Just for interest's sake I've attached a WIP patch of the last
suggestion. I thought they might provide more insight but I've attached some frame-time plots from the various alternatives anyway. Note that Wayland is on a machine with a frame-rate of 143.88, and the others 59.95.

Regards,
 !Z


[1] https://community.khronos.org/t/understanding-the-opengl-main-loop-swapbuffers/75593/12
[2] https://gitlab.freedesktop.org/xorg/xserver/-/issues/631
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassScene.java
index e623060b30..021535334d 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassScene.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/GlassScene.java
@@ -65,6 +65,7 @@ abstract class GlassScene implements TKScene {
     private volatile boolean entireSceneDirty = true;
 
     private boolean doPresent = true;
+    private boolean doVSync = false;
     private final AtomicBoolean painting = new AtomicBoolean(false);
 
     private final boolean depthBuffer;
@@ -305,6 +306,14 @@ abstract class GlassScene implements TKScene {
         return doPresent;
     }
 
+    public final synchronized void setDoVSync(boolean value) {
+        doVSync = value;
+    }
+
+    public final synchronized boolean getDoVsync() {
+        return doVSync;
+    }
+
     protected Color getClearColor() {
         WindowStage windowStage = stage instanceof WindowStage ? (WindowStage)stage : null;
         if (windowStage != null && windowStage.getPlatformWindow() != null &&
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PaintCollector.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PaintCollector.java
index e6c538bc5d..991e5e828e 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PaintCollector.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PaintCollector.java
@@ -434,10 +434,14 @@ final class PaintCollector implements CompletionListener {
                 if (!needsHint) {
                     needsHint = gs.isSynchronous();
                 }
+            }
+
+            for (final GlassScene gs : dirtyScenes) {
                 // On platforms with a window manager, we always set doPresent = true, because
                 // we always need to rerender the scene  if it's in the dirty list and we do a
                 // swap on a per-window basis
                 gs.setDoPresent(true);
+                gs.setDoVSync(needsHint && dirtyScenes.getLast() == gs);
                 try {
                     gs.repaint();
                 } catch (Throwable t) {
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java
index c2895de7bc..94e1618343 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/PresentingPainter.java
@@ -104,7 +104,7 @@ final class PresentingPainter extends ViewPainter {
 
                 /* present for vsync buffer swap */
                 if (vs.getDoPresent()) {
-                    if (!presentable.present()) {
+                    if (!presentable.present(vs.getDoVsync())) {
                         disposePresentable();
                         sceneState.getScene().entireSceneNeedsRepaint();
                     }
diff --git a/modules/javafx.graphics/src/main/java/com/sun/prism/Presentable.java b/modules/javafx.graphics/src/main/java/com/sun/prism/Presentable.java
index 8fc4c29016..75367a4d74 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/Presentable.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/Presentable.java
@@ -57,6 +57,10 @@ public interface Presentable extends RenderTarget {
      */
     public boolean present();
 
+    public default boolean present(boolean vsync) {
+        return present();
+    }
+
     public float getPixelScaleFactorX();
     public float getPixelScaleFactorY();
 }
diff --git a/modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2SwapChain.java b/modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2SwapChain.java
index b7b074d72b..886d90e0ce 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2SwapChain.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/es2/ES2SwapChain.java
@@ -184,7 +184,17 @@ class ES2SwapChain implements ES2RenderTarget, Presentable, GraphicsResource {
     @Override
     public boolean present() {
         boolean presented = drawable.swapBuffers(context.getGLContext());
-        context.makeCurrent(null);
+        //context.makeCurrent(null); no need to null this here
+        return presented;
+    }
+
+    @Override
+    public boolean present(boolean vsync) {
+        boolean presented = present();
+
+        if (vsync)
+            context.getGLContext().finish();
+
         return presented;
     }
 
@@ -308,6 +318,7 @@ class ES2SwapChain implements ES2RenderTarget, Presentable, GraphicsResource {
         }
 
         if (drawable != null) {
+            context.makeCurrent(null);
             drawable.dispose();
             drawable = null;
         }

Reply via email to