Графика буферной стратегии Java или целочисленный массив

При разработке 2D-игр на Java большинство учебных пособий создают стратегию буферизации для рендеринга. Это имеет смысл. Однако то, что люди, кажется, искажают, - это метод отрисовки фактической графики в буфер.

В некоторых руководствах создается буферизованное изображение, а затем создается целочисленный массив для представления отдельных цветов пикселей.

private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();

Graphics g = bs.getDrawGraphics();
g.setColor(new Color(0x556B2F));
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);

Однако некоторые другие учебные пособия не создают буферизованное изображение, рисуя пиксели в массиве int, а вместо этого используют компонент Graphics в BufferStrategy для рисования своих изображений непосредственно в буфере.

Graphics g = bs.getDrawGraphics();
g.setColor(new Color(0x556B2F));
g.fillRect(0, 0, getWidth(), getHeight());

g.drawImage(testImage.image, x*128, y*128, 128, 128, null);

Мне просто интересно, зачем создавать весь массив int, а потом его рисовать. Это требует гораздо больше работы по реализации прямоугольников, растяжения, прозрачности и т. д. Графический компонент стратегии буфера уже имеет методы, которые можно легко вызвать. Есть ли огромный прирост производительности при использовании массива int?

Я искал это часами, и все сайты, которые я видел, просто объясняли, что они делают, а не почему они решили сделать это именно так.


person Kabistarz    schedule 22.09.2013    source источник
comment
Я не уверен на 100%, но мне кажется, что некоторые люди считают, что быстрее обновлять массив int/pixel, чем использовать Graphics API. Это может быть правдой, также вероятно, что люди не понимали, как создавать совместимые графические объекты. Использование буферизованной стратегии должно обеспечивать почти прямой доступ к аппаратному уровню (где это возможно), поэтому я действительно не понимаю, почему вам нужно понять, почему вам нужно использовать массив int, это только я, и я я такой ленивый ;)   -  person MadProgrammer    schedule 23.09.2013
comment
Да, я тестировал оба метода. С массивом int я мог изменить около 27648000 пикселей, прежде чем мой fps начал падать ниже 120. С помощью графического объекта я мог отображать прозрачное изображение в увеличенном прямоугольнике в несколько тысяч раз, что было примерно эквивалентно массиву int. В целом использование графического объекта показалось более полезным.   -  person Kabistarz    schedule 24.09.2013
comment
Некоторое время назад я сделал относительно простой пример, используя не что иное, как JPanel с некоторой пользовательской прорисовкой для основного объекта. Затем я получил 4500 таких объектов, движущихся в разных направлениях, включая вращение основного объекта так, чтобы он указывал в направлении своего движения. Не уверен, какая на самом деле была частота кадров, но у меня она обновлялась со скоростью около 25 кадров в секунду, и она работала на удивление хорошо. Я думаю, что в конвейере рендеринга много оптимизаций, с возможностью использовать DirectX или OpenGL, где это возможно, но вам нужно поэкспериментировать;)   -  person MadProgrammer    schedule 24.09.2013
comment
Вы можете обнаружить, что использование простых методов рендеринга, таких как использование статического изображения для неизмененных объектов и наслоение вывода, будет иметь значение, но убедитесь, что вы используете совместимые изображения, чтобы они быстрее отображались на устройстве... для пример   -  person MadProgrammer    schedule 24.09.2013
comment
@Kabistarz есть много статей (в основном устаревших), описывающих различные методы оптимизации. Некоторые из них порекомендуют вам использовать низкоуровневые инструкции JVM для повышения производительности. Вы бы последовали? :) Во всяком случае, я описал некоторые исторические аспекты, чтобы ответить на ваш вопрос. Если вам действительно интересно сделать что-то нетривиальное, попробуйте jogl (или другую альтернативу).   -  person Renat Gilmanov    schedule 05.07.2015
comment
@Kabistarz Насколько мне известно, графический API может использовать преимущества графической карты. Я никогда не кодировал int[][] для этого. Я читал убийственное программирование игр на Java, и мне понравилось   -  person David Pérez Cabrera    schedule 06.07.2015


Ответы (2)


Давайте проясним одну вещь: оба фрагмента кода делают одно и то же — рисуют изображение. Однако фрагменты довольно неполные — второй фрагмент не показывает, что такое «testImage.image» на самом деле или как он создается. Но они оба в конечном итоге вызывают Graphics.drawImage(), и все варианты drawImage() в Graphics или Graphics2D рисуют изображение, простое и понятное. Во втором случае мы просто не знаем, является ли это изображением BufferedImage, VolatileImage или даже изображением Toolkit.

Таким образом, нет никакой разницы в том, что на самом деле проиллюстрировано здесь!

Между этими двумя фрагментами есть только одно различие: первый также получает прямую ссылку на целочисленный массив, который в конечном итоге внутренне поддерживает экземпляр Image. Это дает прямой доступ к данным пикселей вместо того, чтобы проходить через API (буферизованного) изображения с использованием, например, относительно медленных методов getRGB() и setRGB(). Причина, по которой это нельзя сделать конкретным в контексте, заключается в этом вопросе, массив получен, но никогда не используется во фрагменте. Таким образом, чтобы дать следующему объяснению какую-либо причину существования, мы должны сделать предположение, что кто-то хочет напрямую читать или редактировать пиксели изображения, вполне возможно, по причинам оптимизации, учитывая «медлительность» API (буферизованного) изображения для манипулировать данными.

И эти причины оптимизации могут быть преждевременной оптимизацией, которая может иметь для вас неприятные последствия.


Во-первых, этот код работает только потому, что тип изображения — INT_RGB, что дает изображению IntDataBuffer. Если это был образ другого типа, например 3BYTE_BGR, этот код завершится с ошибкой ClassCastException, поскольку резервный буфер данных не будет IntDataBuffer. Это может не быть большой проблемой, когда вы создаете изображения только вручную и применяете определенный тип, но изображения, как правило, загружаются из файлов, созданных внешними инструментами.

Во-вторых, есть еще один большой недостаток прямого доступа к пиксельному буферу: когда вы это сделаете, Java2D откажется от ускорения этого изображения, поскольку он не может знать, когда вы будете вносить в него изменения вне его контроля. Просто для ясности: ускорение — это процесс хранения неизмененного изображения в видеопамяти, а не копирование его из системной памяти каждый раз при отрисовке. Это потенциально огромное улучшение производительности (или потеря, если вы его сломаете) в зависимости от того, с каким количеством изображений вы работаете.

Как создать образ с аппаратным ускорением с помощью Java2D?

(Как показывает этот связанный вопрос: вы должны использовать GraphicsConfiguration.createCompatibleImage() для создания экземпляров BufferedImage).

Итак, по сути: старайтесь использовать API Java2D для всего, не обращайтесь к буферам напрямую. Этот внешний ресурс дает хорошее представление о том, какие функции API должен поддерживать вас в этом, не переходя на более низкий уровень:

http://www.pushing-pixels.org/2008/06/06/efficient-java2d.html

person Gimby    schedule 30.06.2015

Во-первых, много исторических аспектов. Ранний API был очень простым, поэтому единственным способом сделать что-то нетривиальное было реализовать все необходимые примитивы.

Доступ к необработанным данным немного устарел, и мы можем попытаться провести некоторую «археологию», чтобы найти причину, по которой использовался такой подход. Я думаю, что есть две основные причины:

<сильный>1. Эффекты фильтра

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

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

Самый простой способ реализовать такой эффект в Java 1 заключался в использовании массива int и фильтра, определенного как матрица. У Герберта Шильдта, например, было много таких демонстраций:

public class Blur {

    public void convolve() {
        for (int y = 1; y < height - 1; y++) {
            for (int x = 1; x < width - 1; x++) {
                int rs = 0;
                int gs = 0;
                int bs = 0;
                for (int k = -1; k <= 1; k++) {
                    for (int j = -1; j <= 1; j++) {
                        int rgb = imgpixels[(y + k) * width + x + j];
                        int r = (rgb >> 16) & 0xff;
                        int g = (rgb >> 8) & 0xff;
                        int b = rgb & 0xff;
                        rs += r;
                        gs += g;
                        bs += b;
                    }
                }
                rs /= 9;
                gs /= 9;
                bs /= 9;
                newimgpixels[y * width + x] = (0xff000000
                        | rs << 16 | gs << 8 | bs);
            }
        }
    }
} 

Естественно, вы можете реализовать это с помощью getRGB, но доступ к необработанным данным намного эффективнее. Позже Graphics2D предоставил лучший уровень абстракции:

общедоступный интерфейс BufferedImageOp

Этот интерфейс описывает операции с одним вводом/выводом, выполняемые над объектами BufferedImage. Он реализуется AffineTransformOp, ConvolveOp, ColorConvertOp, RescaleOp и LookupOp. Эти объекты можно передать в BufferedImageFilter для работы с BufferedImage в парадигме ImageProducer-ImageFilter-ImageConsumer.

<сильный>2. Двойная буферизация

Другая проблема была связана с мерцанием и очень медленной прорисовкой. Двойная буферизация устраняет уродливое мерцание и внезапно предоставляет простой способ создания эффектов фильтрации, потому что у вас уже есть буфер.

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

Что-то вроде окончательного заключения :)

Я бы сказал, что описанная вами ситуация довольно типична для любой развивающейся технологии. Есть два способа достижения одних и тех же целей:

  • использовать устаревший подход, больше кодировать и т. д.
  • полагаться на новые уровни абстракции, предоставленные методы и т. д.

Также есть несколько полезных расширений, которые еще больше упростят вам жизнь, так что не нужно использовать int[] :)

person Renat Gilmanov    schedule 02.07.2015
comment
Спасибо, не надо этого делать. Это о веселье. Более того, мне кажется, что у меня достаточно очков ;) - person Renat Gilmanov; 07.07.2015