I’m getting a random jitter when moving an image in a loop. I’m I the only one experiencing this?
I tried various attempts to fix this problem like changing my game loop to tick based instead of time based loop, Changing JFrame to AWT Frame, putting the game loop in EDT, use JPanel instead of canvas, using two threads to separate update() from draw(), use other jvm like openj9. All these attempts didn’t solve the issue.
After all of those attempts, I found a solution by using the full screen exclusive mode. But, my game needs to be switched in windowed mode.
I found a solution for windowed mode by manually activating the openGL pipeline.
System.setProperty("sun.java2d.opengl", "True");
Seems like everything is fine, when I remove the game loop and just put the update() and draw() method in an infinite loop, the image moves smoothly and the fps stays at 60 fps without a game loop. How’s that happened? When I don’t activate openGL or don’t use full screen exclusive mode, the fps is so fast. Same goes if I activate the d3d pipeline. Does this happen to all machines or it depends on the machine? If this happens in all machine then this is a great solution to my problem, but I think it varies. I hope somebody can answer this question because I couldn’t find any article regarding to this. I tried reading some article on oracle and still couldn’t find the answer.
https://docs.oracle.com/javase/8/docs/technotes/guides/2d/flags.html
https://docs.oracle.com/javase/8/docs/technotes/guides/2d/new_features.html
https://www.oracle.com/technetwork/java/perf-graphics-135933.html
When I put back the game loop like this:
while(true)
{
currentTime = System.nanoTime();
totalTime += currentTime - initialTime;
t2 += currentTime - initialTime;
initialTime = currentTime;
if( totalTime > NANOS_PER_SECOND ) {
totalTime -= NANOS_PER_SECOND;
st.fps = frames;
frames = 0;
}
if(t2 > FRAMES_PER_NANO)
{
st.update_Game();
st.drawGraphics();
frames += 1;
t2 -= FRAMES_PER_NANO;
}
//st.update_Game();
//st.drawGraphics();
Thread.yield();
}
I put the Update_game() and drawGraphics() in if statement, and the image starts to randomly jitter again even I manually activate the openGL pipeline.
When I do my game loop like this with openGL pipeline activated
while(true)
{
st.update_Game();
st.drawGraphics();
}
The image moves smoothly.
This kind of code arrangement in game loop also has no jitter as long as the openGL pipeline is activated.
while(true)
{
currentTime = System.nanoTime();
totalTime += currentTime - initialTime;
t2 += currentTime - initialTime;
initialTime = currentTime;
if( totalTime > NANOS_PER_SECOND ) {
totalTime -= NANOS_PER_SECOND;
st.fps = frames;
frames = 0;
}
st.update_Game();
st.drawGraphics();
frames += 1;
Thread.yield();
}
As you can see I didn’t put the update_Game(), drawGraphics() and frames += 1; in an if statement this time. In my machine this loop frame count is 60 as long as the openGL pipeline is activated or full screen exclusive mode is activated. Otherwise, It will go up to a thousand frame counts.
Is there something wrong with my loop? Or the main loop is being block by other third party thread like gc or EDT? Or it’s just java is so bad doing active rendering when not accelerated?
Here’s my test code in case you want to try if random jitter happens in your machine.
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GraphicsDevice;
import java.awt.GraphicsConfiguration;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.awt.image.BufferStrategy;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.imageio.ImageIO;
import java.io.File;
import java.util.Random;
public class ScreenTest extends JFrame
{
private static ScreenTest st;
private Graphics2D g2dbf;
//
private Graphics g;
private VolatileImage bgImage;
private Canvas frameCanvas;
//JFrame size
private int frameWidth,frameHeight;
//Identity transform
private AffineTransform identityTrans;
//Object transform
private AffineTransform objTrans;
//
private BufferStrategy bufferS;
//platform's graphics attributes...
GraphicsEnvironment ge;
GraphicsDevice gd;
GraphicsConfiguration gc;
public double posX, posY;
private double velX, velY;
public double recentPosY;
int fps;
private BufferedImage myImg;
Random rand = new Random();
ScreenTest()
{
//JFrame properties
setTitle("Screen Test");
setIgnoreRepaint(true);
setResizable(false);
setUndecorated(true);
// Get platform's graphics attributes...
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gd = ge.getDefaultScreenDevice();
gc = gd.getDefaultConfiguration();
//Set transform to identity transform
identityTrans = new AffineTransform();
//object transform
objTrans = new AffineTransform();
//Exclusive Full Screen Mode
/*
gd.setFullScreenWindow( this );
if( gd.isDisplayChangeSupported() ) {
DisplayMode dm = gd.getDisplayMode();
frameWidth = dm.getWidth();
frameHeight = dm.getHeight();
gd.setDisplayMode( new DisplayMode( frameWidth, frameHeight, dm.getBitDepth(), dm.getRefreshRate() ));
}
*/
//Simulated full screen mode
//Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
//frameWidth = (int)screenSize.getWidth();
//frameHeight = (int)screenSize.getHeight();
//Windowed Mode
frameWidth = 640;
frameHeight = 480;
try { myImg = ImageIO.read(new File("img.png")); }
catch(IOException e){ System.err.println(e); }
posX = frameWidth/2;
posY = frameHeight/2;
velX = 0;
velY = -6;
recentPosY = posY;
setSize(frameWidth,frameHeight);
setVisible(true);
createBufferStrategy( 2 );
bufferS = getBufferStrategy();
//back buffer image
bgImage = gc.createCompatibleVolatileImage(frameWidth,frameHeight);
}
void update_Game()
{
updateImg();
}
void drawGraphics()
{
try
{
String str1 = String.format("Velocity X: %.2f",velX);
String str2 = String.format("Velocity Y: %.2f",velY);
g2dbf = bgImage.createGraphics();
g2dbf.setTransform(identityTrans);
//set background
g2dbf.setColor(Color.BLACK);
g2dbf.fillRect(0,0,frameWidth,frameHeight);
drawImg();
//print some status information
g2dbf.setColor(Color.WHITE);
g2dbf.drawString("Image: " + Math.round(posX) + "," +
Math.round(posY) , 5, 40);
g2dbf.drawString(str1, 5, 60);
g2dbf.drawString(str2, 5, 80);
g2dbf.drawString( String.format( "FPS: %s", fps ), 5, 140 );
g = bufferS.getDrawGraphics();
g.drawImage(bgImage,0,0,null);
if( !bufferS.contentsLost() ) bufferS.show();
Toolkit.getDefaultToolkit().sync();
}
finally
{
if(g != null) g.dispose();
if(g2dbf != null) g2dbf.dispose();
}
}
synchronized void drawImg()
{
g2dbf.setTransform(identityTrans);
objTrans.setToIdentity();
objTrans.translate(posX, posY);
g2dbf.drawImage(myImg,objTrans,null);
}
synchronized void updateImg()
{
posX += velX;
posY += velY;
if (posX < -70) posX = frameWidth + 10;
else if (posX > frameWidth + 10) posX = -10;
if (posY < -10) posY = frameHeight + 10;
else if (posY > frameHeight + 10) posY = -10;
}
public static void main(String[]args) throws InterruptedException,InvocationTargetException
{
System.setProperty("sun.java2d.opengl", "True");
//System.setProperty("sun.java2d.d3d", "True");
SwingUtilities.invokeAndWait(new Runnable(){
@Override
public void run()
{
st = new ScreenTest();
}
});
int frames = 0;
long totalTime = 0,t2 = 0;
long initialTime = System.nanoTime();
long currentTime = 0;
final short FRAMES_PER_SECOND = 60;
final int NANOS_PER_SECOND = 1_000_000_000;
final float FRAMES_PER_NANO = NANOS_PER_SECOND / FRAMES_PER_SECOND;
int count = 0;
while(true)
{
currentTime = System.nanoTime();
totalTime += currentTime - initialTime;
t2 += currentTime - initialTime;
initialTime = currentTime;
if( totalTime > NANOS_PER_SECOND ) {
totalTime -= NANOS_PER_SECOND;
st.fps = frames;
frames = 0;
}
if(t2 > FRAMES_PER_NANO)
{
st.update_Game();
st.drawGraphics();
frames += 1;
t2 -= FRAMES_PER_NANO;
}
//st.update_Game();
//st.drawGraphics();
Thread.yield();
}
}
}