Java2D: взаимодействие между событиями XWindows и частотой кадров

Я испытываю неожиданное взаимодействие между системными событиями и частотой обновления окна в простых приложениях Java2D в Linux/XWindows. Это лучше всего продемонстрировать с помощью небольшого примера ниже.

Эта программа создает небольшое окно, в котором полукруг отображается при разных поворотах. Графика обновляется со скоростью 60 кадров в секунду для создания мерцающего дисплея. Это достигается с помощью BufferStrategy, а именно путем вызова метода show.

Тем не менее, я отмечаю, что когда я (а) перемещаю мышь над окном, чтобы окно принимало события мыши или (б) удерживало клавишу на клавиатуре, чтобы окно получало события клавиатуры, мерцание увеличивается заметно.

Поскольку скорость, с которой вызван BufferStrategy.show(), не влияет на эти события, как видно из распечаток на консоли (они должны постоянно находиться на скорости около 60 кадров в секунду). Тем не менее, более быстрое мерцание указывает на то, что скорость фактического обновления дисплея действительно меняется.

Мне кажется, что фактические, т.е. видимые 60 кадров в секунду, не достигаются, если не генерируются события мыши или клавиатуры.

public class Test {
    // pass the path to 'test.png' as command line parameter
    public static void main(String[] args) throws Exception {
        BufferedImage image = ImageIO.read(new File(args[0]));

        // create window
        JFrame frame = new JFrame();
        Canvas canvas = new Canvas();
        canvas.setPreferredSize(new Dimension(100, 100));
        frame.getContentPane().add(canvas);
        frame.pack();
        frame.setVisible(true);

        int fps = 0;
        long nsPerFrame = 1000000000 / 60; // 60 = target fps
        long showTime = System.nanoTime() + nsPerFrame;
        long printTime = System.currentTimeMillis() + 1000;
        for (int tick = 0; true; tick++) {
            BufferStrategy bs = canvas.getBufferStrategy();
            if (bs == null) {
                canvas.createBufferStrategy(2);
                continue;
            }

            // draw frame
            Graphics g = bs.getDrawGraphics();
            int framex = (tick % 4) * 64;
            g.drawImage(image, 18, 18, 82, 82, framex, 0, framex+64, 64, null);
            g.dispose();
            bs.show();

            // enforce frame rate
            long sleepTime = showTime - System.nanoTime();
            if (sleepTime > 0) {
                long sleepMillis = sleepTime / 1000000;
                int sleepNanos = (int) (sleepTime - (sleepMillis * 1000000));
                try {
                    Thread.sleep(sleepMillis, sleepNanos);
                } catch (InterruptedException ie) {
                    /* ignore */
                }
            }
            showTime += nsPerFrame;

            // print frame rate achieved
            fps++;
            if (System.currentTimeMillis() > printTime) {
                System.out.println("fps: " + fps);
                fps = 0;
                printTime += 1000;
            }
        }
    }
}

Образец изображения для использования с этой программой (путь к которому должен быть передан как аргумент командной строки):

введите описание изображения здесь

Итак, мой вопрос (2 части):

Почему этот эффект происходит? Как я могу достичь фактических 60 кадров в секунду?

(Бонусный вопрос для комментаторов: вы испытываете тот же эффект и в других операционных системах?)

Ответ 1

Q: Почему этот эффект происходит?

Короткий ответ: нужно позвонить canvas.getToolkit().sync() или Toolkit.getDefaultToolkit().sync() после BufferStrategy#show

Длинный ответ. Без явных пикселов Toolkit#sync не выходят на экран до тех пор, пока командный буфер X11 не будет заполнен или другое событие, например движение мыши, не запустит флеш.

Цитата из http://www.java-gaming.org/index.php/topic,15000.

... даже если мы (Java2D) немедленно выдаем наши команды визуализации видео водитель может предпочесть не выполнять их сразу. Классическим примером является X11 - попробуйте синхронизировать цикл Graphics.fillRects - посмотрите, сколько вы можете выпустить через пару секунд. Без toolkit.sync() (который в случае X11-конвейера делает XFlush()) после каждого вызова или в конце цикла то, что вы на самом деле измеряете, - это то, как быстро Java2D может вызовите метод X11 lib XFillRect, который просто расширяет эти вызовы до тех пор, пока не примет команду буфер заполнен, только затем они отправляются на сервер X для выполнения.

Q: как я могу достичь фактических 60 кадров в секунду?

A: очистите командный буфер и включите ускорение OpenGL для Java2D с помощью System.setProperty("sun.java2d.opengl", "true"); в коде или -Dsun.java2d.opengl=true командной строки.

См. также: