I have used the BufferStrategy, but my code runs still way too fast. xrandr reports that TearFree is "on" and the video mode has 60 Hz. But when I measure the time between two frames, I get one millisecond or less instead of about 16ms.
I am using OpenJDK 17.0.4 on Debian 11 using Linux 5.10.0-20-amd64. The hardware is an AMD Ryzen 7 5700G. I have visually tested TearFree with glxgears which reports about 60 frames.
Output of xrandr:
HDMI-A-0 connected 3840x2160+0+0 (normal left inverted right x axis y axis) 698mm x 393mm
EDID:
00ffffffffffff0009d1507945540000
1e1f0103804627782e5995af4f42af26
0f5054a56b80d1c0b300a9c081808100
81c0010101014dd000a0f0703e803020
3500ba892100001a000000ff0058374d
30313638353031390a20000000fd0018
4c1e873c000a202020202020000000fc
0042656e5120455733323730550a0165
02034df1515d5e5f6061101f22212005
14041312030123090707830100006d03
0c002000387820006001020367d85dc4
01788003681a00000101283cfde305e0
01e40f180000e6060501544c2c565e00
a0a0a029502f203500ba892100001abf
650050a0402e6008200808ba89210000
1c0000000000000000000000000000ad
GAMMA_LUT_SIZE: 4096
range: (0, -1)
DEGAMMA_LUT_SIZE: 4096
range: (0, -1)
GAMMA_LUT: 0
range: (0, 65535)
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 1
DEGAMMA_LUT: 0
range: (0, 65535)
TearFree: on
supported: off, on, auto
HDCP Content Type: HDCP Type0
supported: HDCP Type0, HDCP Type1
Content Protection: Undesired
supported: Undesired, Desired, Enabled
vrr_capable: 0
range: (0, 1)
max bpc: 8
range: (8, 16)
underscan vborder: 0
range: (0, 128)
underscan hborder: 0
range: (0, 128)
underscan: off
supported: off, on, auto
scaling mode: None
supported: None, Full, Center, Full aspect
link-status: Good
supported: Good, Bad
CONNECTOR_ID: 80
supported: 80
non-desktop: 0
range: (0, 1)
3840x2160 60.00*+ 60.00 50.00 59.94 30.00 25.00 24.00 29.97 23.98
2560x1600 59.94
2560x1440 59.95
1920x1200 60.00
1920x1080 60.00 60.00 50.00 59.94 30.00 25.00 24.00 29.97 23.98
1600x1200 60.00
1680x1050 59.88
1600x900 60.00
1280x1024 75.02 60.02
1440x900 60.00
1280x800 59.91
1152x864 75.00
1280x720 60.00 50.00 59.94
1024x768 75.03 60.00
832x624 74.55
800x600 75.00 60.32
720x576 50.00
720x480 60.00 59.94
640x480 75.00 60.00 59.94
720x400 70.08
Complete Java example code just drawing a single blue dot:
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Label;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Map;
public class Gfx
{
static void draw ()
{
pixel (width/2, height/2, blue);
}
static boolean done = false;
static final int black = Color.BLACK.getRGB ();
static final int white = Color.WHITE.getRGB ();
static final int red = Color.RED.getRGB ();
static final int green = Color.GREEN.getRGB ();
static final int blue = Color.BLUE.getRGB ();
static final int width = 100;
static final int height = 100;
static final int canvas_width = 1000;
static final int canvas_height = 1000;
static Frame frame = null;
static void frame (String title)
{
frame = new Frame (title);
frame.addWindowListener (new WindowAdapter ()
{
@Override
public void windowClosing (WindowEvent ev) { done = true; }
});
frame.setResizable (false);
frame.setLayout (new BorderLayout ());
frame.setFont (new Font(Font.MONOSPACED, Font.PLAIN, 18));
}
static FileDialog loadfile = null;
static void loadfile ()
{
loadfile = new FileDialog (frame);
}
static BufferedImage image = null;
static void image ()
{
image = new BufferedImage (width, height, BufferedImage.TYPE_INT_ARGB);
draw();
}
static void pixel (int x, int y, int c) { image.setRGB (x, y, c); }
static void pixel (int x, int y) { pixel (x, y, black); }
static Canvas canvas = null;
static void canvas ()
{
canvas = new Canvas ()
{
@Override
public Dimension getPreferredSize () {
return new Dimension (canvas_width, canvas_height); }
@Override
public void paint (Graphics graphics)
{
super.paint (graphics);
graphics.drawImage (image, 0, 0, canvas_width, canvas_height, this);
}
};
frame.add (canvas, BorderLayout.CENTER);
}
static MenuBar menubar = null;
static void menubar ()
{
menubar = new MenuBar();
Menu menu_app = new Menu("App");
menubar.add(menu_app);
MenuItem app_loadfile = new MenuItem("Load File");
app_loadfile.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ev)
{
loadfile.setVisible (true);
System.out.println (loadfile.getDirectory() + loadfile.getFile());
}});
menu_app.add(app_loadfile);
frame.setMenuBar (menubar);
}
static Label statusbar = null;
static void statusbar ()
{
statusbar = new Label("Hallo");
frame.add (statusbar, BorderLayout.SOUTH);
}
static void start ()
{
frame.pack ();
frame.setVisible (true);
canvas.createBufferStrategy(2);
BufferStrategy strategy = canvas.getBufferStrategy();
long t0 = -1;
long t1 = -1;
DecimalFormat df = new DecimalFormat("###");
// Main loop
while (!done) {
// Prepare for rendering the next frame
if (t1 > 0) t0 = t1;
t1 = System.currentTimeMillis();
if (t0 > 0)
System.out.print(" \r" + df.format(t1 - t0));
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics graphics = strategy.getDrawGraphics();
// Render to graphics
// ...
image();
graphics.drawImage (image, 0, 0, canvas_width, canvas_height, canvas);
// Dispose the graphics
graphics.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
// Dispose the window
frame.setVisible(false);
frame.dispose();
}
public static void main (String[] args)
{
frame ("Gfx");
loadfile ();
menubar ();
image ();
canvas ();
statusbar ();
start ();
}
}
I tried also this example, but it does not compile anymore:
VSyncTest.java:7: error: package sun.java2d.pipe.hw is not visible
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
^
(package sun.java2d.pipe.hw is declared in module java.desktop, which does not export it)
1 error