-
Bug
-
Resolution: Unresolved
-
P3
-
8, 9, 10, 11, 12
ADDITIONAL SYSTEM INFORMATION :
The "OK" sanction is a nominal animation, the NOK is the abnormal behavior
OK : CentOs6.8 gdm 2.30.4 Xorg 1.17.4 jre 1.7
NOK : CentOs6.8 gdm 2.30.4 Xorg 1.17.4 jre 1.8-172
OK : CentOs7.2 gdm 3.14.2 Xorg 1.17.2 jre 1.7-80
OK : CentOs7.2 gdm 3.14.2 Xorg 1.17.2 jre 1.7-80
NOK : CentOs7.2 gdm 3.26.2 Xorg 1.19.5 jre 1.8.0_171-b10
NOK : CentOs7.2 gdm 3.26.2 Xorg 1.19.5 jre 1.8.0_171-b10
NOK : CentOs7.2 gdm 3.14.2 Xorg 1.17.2 jre 1.8.0_65-b17
NOK : CentOs7.4 gdm 3.22.3 Xorg ?.??.? jre 1.8-131
NOK : CentOs7.4 gdm 3.22.3 Xorg ?.??.? jre 1.8-172
OK : CentOs7.5 gdm 3.26.2 Xorg 1.19.5 jre 1.7-67
NOK : CentOs7.5 gdm 3.26.2 Xorg 1.19.5 jre 1.8-161
NOK : CentOs7.5 gdm 3.26.2 Xorg 1.19.5 jre 1.8-161
NOK : CentOs7.5 kde 4.11.19 Xorg 1.19.5 jre 1.8-161
A DESCRIPTION OF THE PROBLEM :
we detected a performance regression for all UI processing on CentOS for Java 1.8 and later releases (test performed on 1.10 also) which was not present on previous releases.
We established a basic test application which only perform a repaint animation in a JPanel. On 1.8 and later, the animation is slow (from 15Hz normally to 1hz on 1.8). We performed several test on several versions.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
A simple translation animation as performed in the following code sample :
/** Custom painting codes on this JPanel */
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
setBackground(Color.WHITE);
Graphics2D g2d = (Graphics2D) g;
transform.setToIdentity();
// The origin is initially set at the top-left corner of the image.
// Move the center of the image to (x, y).
transform.translate(x - imgWidth / 2, y - imgHeight / 2);
// Rotate about the center of the image
transform.rotate(Math.toRadians(direction),
imgWidth / 2, imgHeight / 2);
// Apply the transform to the image and draw
g2d.drawImage(imgFrames[currentFrame], transform, null);
}
triggered by a independent Thread
Thread animationThread = new Thread() {
@Override
public void run() {
long time = System.currentTimeMillis();
while (true) {
update(); // update the position and image
repaint(); // Refresh the display
try {
Thread.sleep(1000 / frameRate); // delay and yield to other threads
} catch (InterruptedException ex) {
}
time = System.currentTimeMillis();
}
}
};
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
a fluid animation close to the selected rate (behavior in 1.7)
ACTUAL -
The animation is slow (slightly over 1Hz) and seems to be faster when the mouse is moving over the JPanel
---------- BEGIN SOURCE ----------
//Requires additionnal pictures for this test case
package test;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/** Animating image frames. Each frame has its own file */
@SuppressWarnings("serial")
public class AnimatedFramesDemo extends JPanel {
// Named-constants
static final int CANVAS_WIDTH = 640;
static final int CANVAS_HEIGHT = 480;
public static final String TITLE = "Animated Frame Demo";
private String[] imgFilenames = {
"images/pacman_1.png", "images/pacman_2.png", "images/pacman_3.png" };
private Image[] imgFrames; // array of Images to be animated
private int currentFrame = 0; // current frame number
private static int frameRate = 20; // frame rate in frames per second
private static JLabel rateLbl = new JLabel(Integer.toString(frameRate) + " fps");
private int imgWidth, imgHeight; // width and height of the image
private double x = 100.0, y = 80.0; // (x,y) of the center of image
private double speed = 8; // displacement in pixels per move
private double direction = 0; // in degrees
private double rotationSpeed = 1; // in degrees per move
// Used to carry out the affine transform on images
private AffineTransform transform = new AffineTransform();
/** Constructor to set up the GUI components */
public AnimatedFramesDemo() {
// Setup animation
loadImages(imgFilenames);
Thread animationThread = new Thread() {
@Override
public void run() {
long time = System.currentTimeMillis();
while (true) {
update(); // update the position and image
repaint(); // Refresh the display
try {
Thread.sleep(1000 / frameRate); // delay and yield to other threads
} catch (InterruptedException ex) {
}
time = System.currentTimeMillis();
}
}
};
animationThread.start(); // start the thread to run animation
// Setup GUI
setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
}
/** Helper method to load all image frames, with the same height and width */
private void loadImages(String[] imgFileNames) {
int numFrames = imgFileNames.length;
imgFrames = new Image[numFrames]; // allocate the array
URL imgUrl = null;
for (int i = 0; i < numFrames; ++i) {
imgUrl = getClass().getClassLoader().getResource(imgFileNames[i]);
if (imgUrl == null) {
System.err.println("Couldn't find file: " + imgFileNames[i]);
} else {
try {
imgFrames[i] = ImageIO.read(imgUrl); // load image via URL
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
imgWidth = imgFrames[0].getWidth(null);
imgHeight = imgFrames[0].getHeight(null);
}
/** Update the position based on speed and direction of the sprite */
public void update() {
x += speed * Math.cos(Math.toRadians(direction)); // x-position
if (x >= CANVAS_WIDTH) {
x -= CANVAS_WIDTH;
} else if (x < 0) {
x += CANVAS_WIDTH;
}
y += speed * Math.sin(Math.toRadians(direction)); // y-position
if (y >= CANVAS_HEIGHT) {
y -= CANVAS_HEIGHT;
} else if (y < 0) {
y += CANVAS_HEIGHT;
}
direction += rotationSpeed; // update direction based on rotational speed
if (direction >= 360) {
direction -= 360;
} else if (direction < 0) {
direction += 360;
}
++currentFrame; // display next frame
if (currentFrame >= imgFrames.length) {
currentFrame = 0;
}
}
/** Custom painting codes on this JPanel */
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
setBackground(Color.WHITE);
Graphics2D g2d = (Graphics2D) g;
transform.setToIdentity();
// The origin is initially set at the top-left corner of the image.
// Move the center of the image to (x, y).
transform.translate(x - imgWidth / 2, y - imgHeight / 2);
// Rotate about the center of the image
transform.rotate(Math.toRadians(direction),
imgWidth / 2, imgHeight / 2);
// Apply the transform to the image and draw
g2d.drawImage(imgFrames[currentFrame], transform, null);
}
/** The Entry main method */
public static void main(String[] args) {
// Run the GUI codes on the Event-Dispatching thread for thread safety
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame(TITLE);
frame.setLayout(new BorderLayout());
frame.add(new AnimatedFramesDemo(), BorderLayout.CENTER);
JPanel rate = new JPanel();
rate.setLayout(new BorderLayout());
final JSlider progress = new JSlider(5, 50, frameRate);
progress.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
frameRate = progress.getValue();
rateLbl.setText(Integer.toString(frameRate) + " fps");
}
});
progress.setValue(frameRate);
rate.add(progress, BorderLayout.CENTER);
rate.add(rateLbl, BorderLayout.EAST);
frame.add(rate, BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null); // center the application window
frame.setVisible(true);
}
});
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
it seems that the usage of calling the method "Toolkit.getDefaultToolkit().sync();" after the drawing method (end of paintComponent method), the animation is fluid again even in Java 1.8
The "OK" sanction is a nominal animation, the NOK is the abnormal behavior
OK : CentOs6.8 gdm 2.30.4 Xorg 1.17.4 jre 1.7
NOK : CentOs6.8 gdm 2.30.4 Xorg 1.17.4 jre 1.8-172
OK : CentOs7.2 gdm 3.14.2 Xorg 1.17.2 jre 1.7-80
OK : CentOs7.2 gdm 3.14.2 Xorg 1.17.2 jre 1.7-80
NOK : CentOs7.2 gdm 3.26.2 Xorg 1.19.5 jre 1.8.0_171-b10
NOK : CentOs7.2 gdm 3.26.2 Xorg 1.19.5 jre 1.8.0_171-b10
NOK : CentOs7.2 gdm 3.14.2 Xorg 1.17.2 jre 1.8.0_65-b17
NOK : CentOs7.4 gdm 3.22.3 Xorg ?.??.? jre 1.8-131
NOK : CentOs7.4 gdm 3.22.3 Xorg ?.??.? jre 1.8-172
OK : CentOs7.5 gdm 3.26.2 Xorg 1.19.5 jre 1.7-67
NOK : CentOs7.5 gdm 3.26.2 Xorg 1.19.5 jre 1.8-161
NOK : CentOs7.5 gdm 3.26.2 Xorg 1.19.5 jre 1.8-161
NOK : CentOs7.5 kde 4.11.19 Xorg 1.19.5 jre 1.8-161
A DESCRIPTION OF THE PROBLEM :
we detected a performance regression for all UI processing on CentOS for Java 1.8 and later releases (test performed on 1.10 also) which was not present on previous releases.
We established a basic test application which only perform a repaint animation in a JPanel. On 1.8 and later, the animation is slow (from 15Hz normally to 1hz on 1.8). We performed several test on several versions.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
A simple translation animation as performed in the following code sample :
/** Custom painting codes on this JPanel */
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
setBackground(Color.WHITE);
Graphics2D g2d = (Graphics2D) g;
transform.setToIdentity();
// The origin is initially set at the top-left corner of the image.
// Move the center of the image to (x, y).
transform.translate(x - imgWidth / 2, y - imgHeight / 2);
// Rotate about the center of the image
transform.rotate(Math.toRadians(direction),
imgWidth / 2, imgHeight / 2);
// Apply the transform to the image and draw
g2d.drawImage(imgFrames[currentFrame], transform, null);
}
triggered by a independent Thread
Thread animationThread = new Thread() {
@Override
public void run() {
long time = System.currentTimeMillis();
while (true) {
update(); // update the position and image
repaint(); // Refresh the display
try {
Thread.sleep(1000 / frameRate); // delay and yield to other threads
} catch (InterruptedException ex) {
}
time = System.currentTimeMillis();
}
}
};
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
a fluid animation close to the selected rate (behavior in 1.7)
ACTUAL -
The animation is slow (slightly over 1Hz) and seems to be faster when the mouse is moving over the JPanel
---------- BEGIN SOURCE ----------
//Requires additionnal pictures for this test case
package test;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/** Animating image frames. Each frame has its own file */
@SuppressWarnings("serial")
public class AnimatedFramesDemo extends JPanel {
// Named-constants
static final int CANVAS_WIDTH = 640;
static final int CANVAS_HEIGHT = 480;
public static final String TITLE = "Animated Frame Demo";
private String[] imgFilenames = {
"images/pacman_1.png", "images/pacman_2.png", "images/pacman_3.png" };
private Image[] imgFrames; // array of Images to be animated
private int currentFrame = 0; // current frame number
private static int frameRate = 20; // frame rate in frames per second
private static JLabel rateLbl = new JLabel(Integer.toString(frameRate) + " fps");
private int imgWidth, imgHeight; // width and height of the image
private double x = 100.0, y = 80.0; // (x,y) of the center of image
private double speed = 8; // displacement in pixels per move
private double direction = 0; // in degrees
private double rotationSpeed = 1; // in degrees per move
// Used to carry out the affine transform on images
private AffineTransform transform = new AffineTransform();
/** Constructor to set up the GUI components */
public AnimatedFramesDemo() {
// Setup animation
loadImages(imgFilenames);
Thread animationThread = new Thread() {
@Override
public void run() {
long time = System.currentTimeMillis();
while (true) {
update(); // update the position and image
repaint(); // Refresh the display
try {
Thread.sleep(1000 / frameRate); // delay and yield to other threads
} catch (InterruptedException ex) {
}
time = System.currentTimeMillis();
}
}
};
animationThread.start(); // start the thread to run animation
// Setup GUI
setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
}
/** Helper method to load all image frames, with the same height and width */
private void loadImages(String[] imgFileNames) {
int numFrames = imgFileNames.length;
imgFrames = new Image[numFrames]; // allocate the array
URL imgUrl = null;
for (int i = 0; i < numFrames; ++i) {
imgUrl = getClass().getClassLoader().getResource(imgFileNames[i]);
if (imgUrl == null) {
System.err.println("Couldn't find file: " + imgFileNames[i]);
} else {
try {
imgFrames[i] = ImageIO.read(imgUrl); // load image via URL
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
imgWidth = imgFrames[0].getWidth(null);
imgHeight = imgFrames[0].getHeight(null);
}
/** Update the position based on speed and direction of the sprite */
public void update() {
x += speed * Math.cos(Math.toRadians(direction)); // x-position
if (x >= CANVAS_WIDTH) {
x -= CANVAS_WIDTH;
} else if (x < 0) {
x += CANVAS_WIDTH;
}
y += speed * Math.sin(Math.toRadians(direction)); // y-position
if (y >= CANVAS_HEIGHT) {
y -= CANVAS_HEIGHT;
} else if (y < 0) {
y += CANVAS_HEIGHT;
}
direction += rotationSpeed; // update direction based on rotational speed
if (direction >= 360) {
direction -= 360;
} else if (direction < 0) {
direction += 360;
}
++currentFrame; // display next frame
if (currentFrame >= imgFrames.length) {
currentFrame = 0;
}
}
/** Custom painting codes on this JPanel */
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
setBackground(Color.WHITE);
Graphics2D g2d = (Graphics2D) g;
transform.setToIdentity();
// The origin is initially set at the top-left corner of the image.
// Move the center of the image to (x, y).
transform.translate(x - imgWidth / 2, y - imgHeight / 2);
// Rotate about the center of the image
transform.rotate(Math.toRadians(direction),
imgWidth / 2, imgHeight / 2);
// Apply the transform to the image and draw
g2d.drawImage(imgFrames[currentFrame], transform, null);
}
/** The Entry main method */
public static void main(String[] args) {
// Run the GUI codes on the Event-Dispatching thread for thread safety
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame(TITLE);
frame.setLayout(new BorderLayout());
frame.add(new AnimatedFramesDemo(), BorderLayout.CENTER);
JPanel rate = new JPanel();
rate.setLayout(new BorderLayout());
final JSlider progress = new JSlider(5, 50, frameRate);
progress.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
frameRate = progress.getValue();
rateLbl.setText(Integer.toString(frameRate) + " fps");
}
});
progress.setValue(frameRate);
rate.add(progress, BorderLayout.CENTER);
rate.add(rateLbl, BorderLayout.EAST);
frame.add(rate, BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null); // center the application window
frame.setVisible(true);
}
});
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
it seems that the usage of calling the method "Toolkit.getDefaultToolkit().sync();" after the drawing method (end of paintComponent method), the animation is fluid again even in Java 1.8