JDK-8291958 : vsync happens once for each stage instead of once per pulse
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: openjfx11,openjfx18
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: linux
  • CPU: x86_64
  • Submitted: 2022-08-03
  • Updated: 2023-01-03
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
openjdk 11.0.15
Problem shows up on both JavaFX 11 and JavaFX 18.0.2
Problem shows up on CentOS 7 and RHEL 7. Does not show up on Windows 10.
Currently testing on the following graphics card with the proprietary nvidia drivers, although it shows up with other nvidia cards:
Graphics Vendor: NVIDIA Corporation
       Renderer: Quadro GP100/PCIe/SSE2
        Version: 4.6.0 NVIDIA 515.57

A DESCRIPTION OF THE PROBLEM :
The pulse loop waits for one vsync per a dirty scene. If I have multiple windows open, each with their own scene, and each scene is continuously updating (always dirty), the frame rate is 60 fps / number of windows.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create an application with multiple stages and update the contents of each stage every call to an `AnimationTimer` (make each stage constantly dirty).

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Smooth, 60 frames per second animations. Adding multiple windows with simple contents should not increase the time it takes until the next pulse.
ACTUAL -
Frame rate is 60 frames per second *divided by the number of windows*. Adding multiple windows with simple, but animated contents significantly decreases the frame rate.

---------- BEGIN SOURCE ----------
package multiplescenes;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableLongValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class MultipleDirtyScenes extends Application {
    private static final int SCENE_COUNT = 3;

    @Override
    public void start(Stage ignorePrimaryStage) {
        final FramesPerSecondCounter frameRateCounter = new FramesPerSecondCounter();
        frameRateCounter.start();

        for (int i = 0; i < SCENE_COUNT; i++) {

            final Label fpsLabel = new Label();
            fpsLabel.textProperty().bind(Bindings.format("FPS: %.1f", frameRateCounter.fpsProperty())); // FPS will be ~ 60.0/SCENE_COUNT
            final Label lastFrameTimeNsLabel = new Label();
            lastFrameTimeNsLabel.textProperty().bind(Bindings.format("Last frame time (ns): %d", frameRateCounter.lastFrameTimeNsProperty())); // Makes the scene constantly dirty

            final Scene scene = new Scene(new VBox(fpsLabel, lastFrameTimeNsLabel), 480, 100);

            final Stage stage = new Stage();
            stage.setScene(scene);
            stage.show();
        }
    }

    public static void main(String[] args) {
        launch();
    }

    private static class FramesPerSecondCounter extends AnimationTimer {

        private static final int FRAME_TIME_COUNT = 10;
        private final long[] frameTimesNs = new long[FRAME_TIME_COUNT];
        private int frameTimeIndex = 0;
        private final LongProperty lastFrameTimeNs = new SimpleLongProperty();
        private final DoubleProperty fpsValue = new SimpleDoubleProperty();

        @Override
        public void handle(final long nowNs) {
            lastFrameTimeNs.set(nowNs);

            final long oldFrameTimeNs = frameTimesNs[frameTimeIndex];
            frameTimesNs[frameTimeIndex] = nowNs;
            frameTimeIndex = (frameTimeIndex + 1) % frameTimesNs.length;

            final long elapsedNanos = nowNs - oldFrameTimeNs;
            final long elapsedNanosPerFrame = elapsedNanos / frameTimesNs.length;
            final double fps = 1_000_000_000.0 / elapsedNanosPerFrame;
            fpsValue.set(fps);
        }

        public ObservableDoubleValue fpsProperty() {
            return fpsValue;
        }

        public ObservableLongValue lastFrameTimeNsProperty() {
            return lastFrameTimeNs;
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Use *both* `-Dprism.vsync=false` and environment variable `__GL_SYNC_TO_VBLANK=0` (see https://bugs.openjdk.org/browse/JDK-8168366).

FREQUENCY : always



Comments
Checked with attached testcase in Ubuntu 20.04, <attached short clipping > Moving to JDK project for review cat /etc/os-release NAME="Ubuntu" VERSION="20.04.1 LTS (Focal Fossa)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 20.04.1 LTS" VERSION_ID="20.04" HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" VERSION_CODENAME=focal UBUNTU_CODENAME=focal Prism pipeline init order: sw Using Double Precision Marlin Rasterizer Using dirty region optimizations Not using texture mask for primitives Not forcing power of 2 sizes for textures Using hardware CLAMP_TO_ZERO mode Opting in for HiDPI pixel scaling *** Fallback to Prism SW pipeline Prism pipeline name = com.sun.prism.sw.SWPipeline (X) Got class = class com.sun.prism.sw.SWPipeline Initialized prism pipeline: com.sun.prism.sw.SWPipeline vsync: true vpipe: false
05-08-2022