-
Bug
-
Resolution: Fixed
-
P2
-
8, 9
-
x86
-
os_x
FULL PRODUCT VERSION :
All recent versions of Java 8.
ADDITIONAL OS VERSION INFORMATION :
OSX 10.10.5
Windows 7
EXTRA RELEVANT SYSTEM CONFIGURATION :
The exact system does not seem to be relevant.
A DESCRIPTION OF THE PROBLEM :
The attached demo application was actually designed to show something else
but it also shows a problem with the default scene graph rendering mode
compared to an enforced software rendering.
Please run the attached demo two times.
1. with the option
-Djavafx.animation.fullspeed=true
and
2. with the options
-Djavafx.animation.fullspeed=true
-Dprism.order=sw
You will see an animation of triangles. After an initial warm-up you should see
that the framerate settles at arround 12 FPS in the first case. (The exact
numbers depend on your computer of course.)
In the second case, with enforced software rendering, you should see a much
higher framerate well above 60 FPS.
The result of this test is that the so-called hardware rendering is actually
much slower than the enforced software rendering. This looks like a severe
bug to me because the default hardware rendering should always be faster
than the software rendering. Otherwise you could just selectively switch it off.
This bug only seems to affect scene graph path rendering. Optimized shapes
like Rectangles are not affected. Also this behavior cannot be observed
for Canvas path rendering.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Just lauch the demo program with the runtime parameters as specified above.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The default hardware rendering should be faster than the software rendering.
ACTUAL -
The default hardware rendering is significantly slower than the software rendering.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package bugs.performance.triangles.report2;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public class TrianglePerformanceTest extends Application {
private final static double WIDTH = 800;
private final static double HEIGHT = 800;
private final static double SIZE = 50;
private final static double NOMINAL_SCALE = 0.5;
private final static boolean CONTINUOUS_ROTATION = true;
private final static boolean CONTINUOUS_SCALING = true;
private final static boolean AVERAGE_FRAME_RATE = true;
private final static Color STROKE_COLOR = null;
private final StackPane graphicsPane = new StackPane();
private final Pane drawingPane = new Pane();
private final Group graphics = new Group();
private ToolBar toolBar;
private TextField numElemSelection;
private TextField frameRate;
private ToggleButton triRectButton;
private ToggleButton animButton;
private int numElements = 1000;
private boolean showRectangles = false;
private class MyAnimationTimer extends AnimationTimer {
private final static long SECONDS_TO_NANOS = 1_000_000_000;
private long previousTimeNanos = 0;
private boolean active = false;
private long averageDeltaNanos = 0;
@Override
public void start() {
super.start();
this.active = true;
}
@Override
public void stop() {
super.stop();
this.active = false;
}
@Override
public void handle(long nowNanos) {
if (previousTimeNanos > 0) {
long deltaNanos = nowNanos - previousTimeNanos;
if (deltaNanos > 0) {
if (AVERAGE_FRAME_RATE) {
averageDeltaNanos = (averageDeltaNanos > 0) ? (59 * averageDeltaNanos + deltaNanos) / 60 : deltaNanos;
} else {
averageDeltaNanos = deltaNanos;
}
frameRate.setText(String.format("%d", SECONDS_TO_NANOS/averageDeltaNanos));
}
}
previousTimeNanos = nowNanos;
double seconds = (double)nowNanos / SECONDS_TO_NANOS;
double angle = 10 * seconds;
double scale = NOMINAL_SCALE + 0.1*Math.sin(seconds);
if (CONTINUOUS_ROTATION) {
setRotation(angle);
}
if (CONTINUOUS_SCALING) {
setScale(scale);
}
}
public boolean isActive() {
return active;
}
};
MyAnimationTimer timer = new MyAnimationTimer();
@Override
public void start(Stage stage) throws Exception {
stage.setTitle(getClass().getSimpleName());
BorderPane root = new BorderPane();
drawingPane.getChildren().add(graphics);
graphicsPane.getChildren().add(drawingPane);
toolBar = new ToolBar();
root.setTop(toolBar);
root.setCenter(graphicsPane);
{
// Number of elements:
Label label = new Label("Num. Elem.:");
numElemSelection = new TextField();
numElemSelection.setAlignment(Pos.BASELINE_RIGHT);
numElemSelection.setPrefColumnCount(4);
numElemSelection.setText(String.valueOf(numElements));
numElemSelection.setOnAction(resetConfigActionHandler);
toolBar.getItems().addAll(label, numElemSelection);
}
{
// Frame rate:
Label label = new Label("Frame rate:");
frameRate = new TextField();
frameRate.setAlignment(Pos.BASELINE_RIGHT);
frameRate.setPrefColumnCount(3);
frameRate.setEditable(false);
toolBar.getItems().addAll(label, frameRate);
}
toolBar.getItems().add(new Label(" "));
{
// Triangles/Rectangles toggle button:
triRectButton = new ToggleButton("Triangles/Rectangles");
triRectButton.setSelected(false);
triRectButton.setOnAction(resetConfigActionHandler);
toolBar.getItems().add(triRectButton);
}
{
// Start/Stop animation toggle button:
animButton = new ToggleButton("Start/Stop Animation");
animButton.setSelected(true);
animButton.setOnAction(animationActionHandler);
toolBar.getItems().add(animButton);
}
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.show();
drawingPane.widthProperty().addListener((v,o,n) -> resetConfig());
drawingPane.heightProperty().addListener((v,o,n) -> resetConfig());
resetConfig();
}
private void setScale(double value) {
graphics.setScaleX(value);
graphics.setScaleY(value);
};
private void setRotation(double angle) {
graphics.setRotate(angle);
}
private EventHandler<ActionEvent> resetConfigActionHandler = e -> resetConfig();
private EventHandler<ActionEvent> animationActionHandler = e -> {
boolean showAnimation = animButton.isSelected();
if (showAnimation != timer.isActive()) {
if (showAnimation) {
timer.start();
} else {
timer.stop();
}
}
};
private void resetConfig() {
try {
timer.stop();
numElements = Math.abs(Integer.valueOf(numElemSelection.getText()));
showRectangles = triRectButton.isSelected();
boolean showAnimation = animButton.isSelected();
updateGraphics();
if (showAnimation) {
timer.start();
} else {
timer.stop();
}
} catch (NumberFormatException e) {
System.err.println("Illegal value for number of elements: " + numElemSelection.getText());
}
}
private void updateGraphics() {
graphics.getChildren().clear();
setRotation(0.0);
setScale(NOMINAL_SCALE);
for (int i = 0; i < numElements; i++) {
addShape(graphics, drawingPane.getWidth(), drawingPane.getHeight(), showRectangles);
}
}
private void addShape(Group graphics, double width, double height, boolean showRectangles) {
final double px0 = Math.random() * width;
final double py0 = Math.random() * height;
final double px1 = px0 - SIZE/2 + Math.random() * SIZE;
final double py1 = py0 - SIZE/2 + Math.random() * SIZE;
final double px2 = px0 - SIZE/2 + Math.random() * SIZE;
final double py2 = py0 - SIZE/2 + Math.random() * SIZE;
Shape shape;
if (!showRectangles) {
shape = new Polygon(px0, py0, px1, py1, px2, py2);
} else {
double boundsX = Math.min(Math.min(px0, px1), px2);
double boundsY = Math.min(Math.min(py0, py1), py2);
double boundsWidth = Math.max(Math.max(px0, px1), px2) - boundsX;
double boundsHeight = Math.max(Math.max(py0, py1), py2) - boundsY;
shape = new Rectangle(boundsX, boundsY, boundsWidth, boundsHeight);
}
shape.setFill(randomColor());
shape.setStroke(STROKE_COLOR);
graphics.getChildren().add(shape);
}
private Color randomColor() {
return new Color(Math.random(), Math.random(), Math.random(), 1.0);
}
public static void main(String[] args) {
launch(args);
}
}
---------- END SOURCE ----------
All recent versions of Java 8.
ADDITIONAL OS VERSION INFORMATION :
OSX 10.10.5
Windows 7
EXTRA RELEVANT SYSTEM CONFIGURATION :
The exact system does not seem to be relevant.
A DESCRIPTION OF THE PROBLEM :
The attached demo application was actually designed to show something else
but it also shows a problem with the default scene graph rendering mode
compared to an enforced software rendering.
Please run the attached demo two times.
1. with the option
-Djavafx.animation.fullspeed=true
and
2. with the options
-Djavafx.animation.fullspeed=true
-Dprism.order=sw
You will see an animation of triangles. After an initial warm-up you should see
that the framerate settles at arround 12 FPS in the first case. (The exact
numbers depend on your computer of course.)
In the second case, with enforced software rendering, you should see a much
higher framerate well above 60 FPS.
The result of this test is that the so-called hardware rendering is actually
much slower than the enforced software rendering. This looks like a severe
bug to me because the default hardware rendering should always be faster
than the software rendering. Otherwise you could just selectively switch it off.
This bug only seems to affect scene graph path rendering. Optimized shapes
like Rectangles are not affected. Also this behavior cannot be observed
for Canvas path rendering.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Just lauch the demo program with the runtime parameters as specified above.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The default hardware rendering should be faster than the software rendering.
ACTUAL -
The default hardware rendering is significantly slower than the software rendering.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
package bugs.performance.triangles.report2;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public class TrianglePerformanceTest extends Application {
private final static double WIDTH = 800;
private final static double HEIGHT = 800;
private final static double SIZE = 50;
private final static double NOMINAL_SCALE = 0.5;
private final static boolean CONTINUOUS_ROTATION = true;
private final static boolean CONTINUOUS_SCALING = true;
private final static boolean AVERAGE_FRAME_RATE = true;
private final static Color STROKE_COLOR = null;
private final StackPane graphicsPane = new StackPane();
private final Pane drawingPane = new Pane();
private final Group graphics = new Group();
private ToolBar toolBar;
private TextField numElemSelection;
private TextField frameRate;
private ToggleButton triRectButton;
private ToggleButton animButton;
private int numElements = 1000;
private boolean showRectangles = false;
private class MyAnimationTimer extends AnimationTimer {
private final static long SECONDS_TO_NANOS = 1_000_000_000;
private long previousTimeNanos = 0;
private boolean active = false;
private long averageDeltaNanos = 0;
@Override
public void start() {
super.start();
this.active = true;
}
@Override
public void stop() {
super.stop();
this.active = false;
}
@Override
public void handle(long nowNanos) {
if (previousTimeNanos > 0) {
long deltaNanos = nowNanos - previousTimeNanos;
if (deltaNanos > 0) {
if (AVERAGE_FRAME_RATE) {
averageDeltaNanos = (averageDeltaNanos > 0) ? (59 * averageDeltaNanos + deltaNanos) / 60 : deltaNanos;
} else {
averageDeltaNanos = deltaNanos;
}
frameRate.setText(String.format("%d", SECONDS_TO_NANOS/averageDeltaNanos));
}
}
previousTimeNanos = nowNanos;
double seconds = (double)nowNanos / SECONDS_TO_NANOS;
double angle = 10 * seconds;
double scale = NOMINAL_SCALE + 0.1*Math.sin(seconds);
if (CONTINUOUS_ROTATION) {
setRotation(angle);
}
if (CONTINUOUS_SCALING) {
setScale(scale);
}
}
public boolean isActive() {
return active;
}
};
MyAnimationTimer timer = new MyAnimationTimer();
@Override
public void start(Stage stage) throws Exception {
stage.setTitle(getClass().getSimpleName());
BorderPane root = new BorderPane();
drawingPane.getChildren().add(graphics);
graphicsPane.getChildren().add(drawingPane);
toolBar = new ToolBar();
root.setTop(toolBar);
root.setCenter(graphicsPane);
{
// Number of elements:
Label label = new Label("Num. Elem.:");
numElemSelection = new TextField();
numElemSelection.setAlignment(Pos.BASELINE_RIGHT);
numElemSelection.setPrefColumnCount(4);
numElemSelection.setText(String.valueOf(numElements));
numElemSelection.setOnAction(resetConfigActionHandler);
toolBar.getItems().addAll(label, numElemSelection);
}
{
// Frame rate:
Label label = new Label("Frame rate:");
frameRate = new TextField();
frameRate.setAlignment(Pos.BASELINE_RIGHT);
frameRate.setPrefColumnCount(3);
frameRate.setEditable(false);
toolBar.getItems().addAll(label, frameRate);
}
toolBar.getItems().add(new Label(" "));
{
// Triangles/Rectangles toggle button:
triRectButton = new ToggleButton("Triangles/Rectangles");
triRectButton.setSelected(false);
triRectButton.setOnAction(resetConfigActionHandler);
toolBar.getItems().add(triRectButton);
}
{
// Start/Stop animation toggle button:
animButton = new ToggleButton("Start/Stop Animation");
animButton.setSelected(true);
animButton.setOnAction(animationActionHandler);
toolBar.getItems().add(animButton);
}
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.show();
drawingPane.widthProperty().addListener((v,o,n) -> resetConfig());
drawingPane.heightProperty().addListener((v,o,n) -> resetConfig());
resetConfig();
}
private void setScale(double value) {
graphics.setScaleX(value);
graphics.setScaleY(value);
};
private void setRotation(double angle) {
graphics.setRotate(angle);
}
private EventHandler<ActionEvent> resetConfigActionHandler = e -> resetConfig();
private EventHandler<ActionEvent> animationActionHandler = e -> {
boolean showAnimation = animButton.isSelected();
if (showAnimation != timer.isActive()) {
if (showAnimation) {
timer.start();
} else {
timer.stop();
}
}
};
private void resetConfig() {
try {
timer.stop();
numElements = Math.abs(Integer.valueOf(numElemSelection.getText()));
showRectangles = triRectButton.isSelected();
boolean showAnimation = animButton.isSelected();
updateGraphics();
if (showAnimation) {
timer.start();
} else {
timer.stop();
}
} catch (NumberFormatException e) {
System.err.println("Illegal value for number of elements: " + numElemSelection.getText());
}
}
private void updateGraphics() {
graphics.getChildren().clear();
setRotation(0.0);
setScale(NOMINAL_SCALE);
for (int i = 0; i < numElements; i++) {
addShape(graphics, drawingPane.getWidth(), drawingPane.getHeight(), showRectangles);
}
}
private void addShape(Group graphics, double width, double height, boolean showRectangles) {
final double px0 = Math.random() * width;
final double py0 = Math.random() * height;
final double px1 = px0 - SIZE/2 + Math.random() * SIZE;
final double py1 = py0 - SIZE/2 + Math.random() * SIZE;
final double px2 = px0 - SIZE/2 + Math.random() * SIZE;
final double py2 = py0 - SIZE/2 + Math.random() * SIZE;
Shape shape;
if (!showRectangles) {
shape = new Polygon(px0, py0, px1, py1, px2, py2);
} else {
double boundsX = Math.min(Math.min(px0, px1), px2);
double boundsY = Math.min(Math.min(py0, py1), py2);
double boundsWidth = Math.max(Math.max(px0, px1), px2) - boundsX;
double boundsHeight = Math.max(Math.max(py0, py1), py2) - boundsY;
shape = new Rectangle(boundsX, boundsY, boundsWidth, boundsHeight);
}
shape.setFill(randomColor());
shape.setStroke(STROKE_COLOR);
graphics.getChildren().add(shape);
}
private Color randomColor() {
return new Color(Math.random(), Math.random(), Math.random(), 1.0);
}
public static void main(String[] args) {
launch(args);
}
}
---------- END SOURCE ----------
- relates to
-
JDK-8180086 Changing cache hint can cause effect to darken
-
- Resolved
-