On 4/10/25 01:20, Thiago Milczarek Sayão wrote:
Just out of curiosity, I compiled the EGL version. Here:
https://github.com/tsayao/jfx/releases/tag/test-egl
It does seem to have a difference in frame rate.
That's a lot better, it's basically the same as if I remove the
makeCurrent() from ES2Pipeline.present and I presume how it behaves on
other systems. I still think it's throttling in the wrong place and the
GlassTimer is terrible, but at least it's throttling. I'll probably
try 25 with the latest mesa and if i still see it file a bug with them.
Over the last few days I did a lot of analysis with a more complex
application - one that calculates an image sequence via OpenCL and
displays it in JavaFX. The OpenCL takes from about 3 to 15ms to
complete depending on the scene. I've attached some inter-pulse timing
plots which are a bit more interesting than the previous ones.
There's JavaFX 25, my patches (Z), and the with your EGL build (I also
tested with the makeCurrent() change since it's a single window, and it
matches the EGL one).
From left to right, top to bottom:
1. Transition with no frame-rate, asynchronous rendering that drops
work if a new request comes in while it's busy.
2. Transition with 60f/s desired frame rate, asynchronous rendering as 1.
3. Transition with no frame-rate, synchronous rendering on the
animation thread.
4. Transition with 60/s desired frame rate, synchronous rendering as 3.
Setting a desired frame rate on the Transition results in considerable
microstutter even with the egl version. Not setting a desired frame
rate - visually it's ok but there is still more frame pacing variation
than calling glFinish() after all scenes have been drawn and with a more
accurate timer.
I want to try to do some latency testing if I can work out a reasonable
way to do it.
For what it's worth i've also attached current patch-in-progress that
includes a more accurate if rather simple timer.
Regards,
!Z
diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java
index f2a33641fd..b7bebbaa98 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java
@@ -397,11 +397,11 @@ final class GtkApplication extends Application implements
@Override
protected native int staticTimer_getMaxPeriod();
- @Override protected double staticScreen_getVideoRefreshPeriod() {
- return 0.0; // indicate millisecond resolution
- }
+ @Override
+ protected native double staticScreen_getVideoRefreshPeriod();
- @Override native protected Screen[] staticScreen_getScreens();
+ @Override
+ native protected Screen[] staticScreen_getScreens();
@Override
protected FileChooserResult staticCommonDialogs_showFileChooser(
diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkTimer.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkTimer.java
index be63a67333..74103b6a4f 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkTimer.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkTimer.java
@@ -32,9 +32,8 @@ final class GtkTimer extends Timer{
super(runnable);
}
- @Override protected long _start(Runnable runnable) {
- throw new RuntimeException("vsync timer not supported");
- }
+ @Override
+ protected native long _start(Runnable runnable);
@Override
protected native long _start(Runnable runnable, int period);
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;
}
diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp
index a9e84cd155..4486adc024 100644
--- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp
+++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassApplication.cpp
@@ -362,6 +362,24 @@ JNIEXPORT jint JNICALL Java_com_sun_glass_ui_gtk_GtkApplication_staticTimer_1get
return 10000; // There are no restrictions on period in g_threads
}
+/*
+ * Class: com_sun_glass_ui_gtk_GtkApplication
+ * Method: staticScreen_getVideoRefreshPeriod
+ * Signature: ()D
+ */
+JNIEXPORT jdouble JNICALL Java_com_sun_glass_ui_gtk_GtkApplication_staticScreen_1getVideoRefreshPeriod
+ (JNIEnv * env, jobject obj)
+{
+ (void)env;
+ (void)obj;
+
+ GdkMonitor *m = gdk_display_get_primary_monitor(gdk_display_get_default());
+
+ int rr = gdk_monitor_get_refresh_rate(m);
+
+ return 1.0E6 / rr;
+}
+
/*
* Class: com_sun_glass_ui_gtk_GtkApplication
* Method: staticView_getMultiClickTime
diff --git a/modules/javafx.graphics/src/main/native-glass/gtk/GlassTimer.cpp b/modules/javafx.graphics/src/main/native-glass/gtk/GlassTimer.cpp
index ff31b55b0d..1f5d219bb5 100644
--- a/modules/javafx.graphics/src/main/native-glass/gtk/GlassTimer.cpp
+++ b/modules/javafx.graphics/src/main/native-glass/gtk/GlassTimer.cpp
@@ -28,27 +28,112 @@
#include <glib.h>
#include <gdk/gdk.h>
#include <stdlib.h>
-
-static gboolean call_runnable_in_timer
- (gpointer);
+#include <time.h>
extern "C" {
-/*
- * Class: com_sun_glass_ui_gtk_GtkTimer
- * Method: _start
- * Signature: (Ljava/lang/Runnable;I)J
- */
-JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_gtk_GtkTimer__1start
- (JNIEnv * env, jobject obj, jobject runnable, jint period)
+typedef struct TimerContext {
+ jobject runnable;
+ struct timespec start;
+ uint64_t frame;
+ int refresh_rate;
+ // 0 = gdk_thread_timeout
+ // 1 = gdk_thread_timeout stop
+ // 2 = vsync thread
+ // 3 = vsync thread stop
+ // 4 = vsync thread failed
+ volatile int flag;
+} TimerContext;
+
+static void *_GlassTimerTask(void *data)
{
- (void)obj;
+ TimerContext* context = (TimerContext*) data;
+
+ JNIEnv *env;
+ jint err = javaVM->AttachCurrentThreadAsDaemon((void **)&env, NULL);
+
+ printf("Start glass timer thread: rate=%6.3f\n", context->refresh_rate * 1.0E-3); fflush(stdout);
+
+ if (err == 0)
+ {
+ int64_t frame = 1;
+
+ while (context->flag == 2) {
+ struct timespec time;
+
+ clock_gettime(CLOCK_REALTIME, &time);
+
+ int64_t current = ((time.tv_sec - context->start.tv_sec) * 1000000 + time.tv_nsec / 1000) - context->start.tv_nsec / 1000;
+ int64_t target = ((frame * 1000000000 / context->refresh_rate));
+ int delay;
+
+ if (target < current) {
+ frame += (current - target) * context->refresh_rate / 1000000000 + 1;
+ delay = 0;
+ } else {
+ delay = target - current;
+ frame++;
+ }
+
+ usleep(delay);
+
+ if (context->flag == 2)
+ {
+ env->CallVoidMethod(context->runnable, jRunnableRun, NULL);
+ LOG_EXCEPTION(env);
+ }
+
+ //printf(" %8ld: %ld %ld %9.6f\n", frame, current, target, (target-current) * 1.0E-6);
+ }
+
+ env->DeleteGlobalRef(context->runnable);
+ context->runnable = NULL;
+ free(context);
+ } else {
+ ERROR0("GlassTimer: Unable to attach JNIEnv\n");
+
+ // FIXME: races
+ context->flag = 4;
+ env->DeleteGlobalRef(context->runnable);
+ context->runnable = NULL;
+ }
+
+ printf("Exit glass timer thread\n");
+
+ return NULL;
+}
+
+static jlong start_timer_task(JNIEnv *env, jobject runnable, int rate) {
+ TimerContext* context = (TimerContext*) malloc(sizeof(TimerContext));
- RunnableContext* context = (RunnableContext*) malloc(sizeof(RunnableContext));
if (context != NULL) {
- context->runnable = env->NewGlobalRef(runnable);
- context->flag = 0;
- gdk_threads_add_timeout_full(G_PRIORITY_HIGH_IDLE, period, call_runnable_in_timer, context, NULL);
+ pthread_t thread;
+ pthread_attr_t attr;
+
+ clock_gettime(CLOCK_REALTIME, &context->start);
+
+ // detach thread, so its resources can be reclaimed as soon as it's finished
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
+
+ int err = pthread_create(&thread, &attr, _GlassTimerTask, context);
+ if (err == 0)
+ {
+ pthread_setname_np(thread, "Glass Timer Thread");
+
+ context->refresh_rate = rate;
+ context->runnable = env->NewGlobalRef(runnable);
+ context->flag = 2;
+ }
+ else
+ {
+ free(context);
+ context = NULL;
+ }
+
+ pthread_attr_destroy(&attr);
+
return PTR_TO_JLONG(context);
} else {
// we throw RuntimeException on Java side when we can't
@@ -59,45 +144,57 @@ JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_gtk_GtkTimer__1start
/*
* Class: com_sun_glass_ui_gtk_GtkTimer
- * Method: _stop
- * Signature: (J)V
+ * Method: _start
+ * Signature: (Ljava/lang/Runnable;)J
*/
-JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkTimer__1stop
- (JNIEnv * env, jobject obj, jlong ptr)
+JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_gtk_GtkTimer__1start__Ljava_lang_Runnable_2
+ (JNIEnv *env, jobject obj, jobject runnable)
{
(void)obj;
- RunnableContext* context = (RunnableContext*) JLONG_TO_PTR(ptr);
- context->flag = 1;
- env->DeleteGlobalRef(context->runnable);
- context->runnable = NULL;
-}
+ GdkMonitor *m = gdk_display_get_primary_monitor(gdk_display_get_default());
+ int rr = gdk_monitor_get_refresh_rate(m);
-} // extern "C"
+ printf("start 'vsync' timer: %d\n", rr); fflush(stdout);
+ return start_timer_task(env, runnable, rr);
+}
-static gboolean call_runnable_in_timer
- (gpointer data)
+/*
+ * Class: com_sun_glass_ui_gtk_GtkTimer
+ * Method: _start
+ * Signature: (Ljava/lang/Runnable;I)J
+ */
+JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_gtk_GtkTimer__1start__Ljava_lang_Runnable_2I
+ (JNIEnv * env, jobject obj, jobject runnable, jint period)
{
- RunnableContext* context = (RunnableContext*) data;
- if (context->flag) {
- free(context);
- return FALSE;
- }
- else if (context->runnable) {
- JNIEnv *env;
- int envStatus = javaVM->GetEnv((void **)&env, JNI_VERSION_1_6);
- if (envStatus == JNI_EDETACHED) {
- javaVM->AttachCurrentThread((void **)&env, NULL);
- }
+ (void)obj;
- env->CallVoidMethod(context->runnable, jRunnableRun, NULL);
- LOG_EXCEPTION(env);
+ printf("start gdk timer: %d\n", period); fflush(stdout);
- if (envStatus == JNI_EDETACHED) {
- javaVM->DetachCurrentThread();
- }
+ return start_timer_task(env, runnable, 1000000 / period);
+}
+
+/*
+ * Class: com_sun_glass_ui_gtk_GtkTimer
+ * Method: _stop
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkTimer__1stop
+ (JNIEnv * env, jobject obj, jlong ptr)
+{
+ (void)obj;
+
+ TimerContext* context = (TimerContext*) JLONG_TO_PTR(ptr);
+ if (context->flag == 0) {
+ context->flag = 1;
+ env->DeleteGlobalRef(context->runnable);
+ context->runnable = NULL;
+ } else if (context->flag == 2) {
+ context->flag = 3;
+ } else if (context->flag == 4) {
+ free(context);
}
- return TRUE;
}
+} // extern "C"