Докуметация Cтарт Статьи Форум Лента Вход
Не официальное русскоязычное сообщество
Главная
    Статьи
        Переводы с других сайтов
            LWJGL
                2.6.6 LWJGL3 миграция

2.6.6 LWJGL3 миграция

Опубликованно: 08.05.2018, 18:25
Последняя редакция, Andry: 17.05.2018 13:27

LWJGL3 Руководство по миграции. Данное руководство предназначено для того что бы показать процесс переноса приложения с LWJGL2 на LWJGL3. Для этого я написал простую игру Pong в LWJGL2 (2.9.3), и это руководство будет описывать, как я перенес его на LWJGL3 (3.0.0b Build # 64 — последняя стабильная на момент написания статьи). Руководство будет разделено на commit-ы, по одному commit-у для каждой функции, которую я переношу на LWJGL3, и вы можете точно увидеть, что я изменил, просмотрев разные commit-ы в репозиторий Github: Миграция Pong в Github Репозитории. Репозиторий содержит оригинал игры в папке «PongLWJGL2», а в папке «PongLWJGL3» содержится обновленная версия.

Прежде чем вы прочитаете это руководство, если вы хотите использовать улучшения стабильности (не функции) LWJGL3, но не хотите переносить свою игру, то у вас есть альтернатива. Каппа написал слой совместимости называемый LWJGLX, смотрите это сообщение на форуме. ##Commit 1 — Переключение Этот commit просто переключает natives и библиотеки на использование LWJGL3, ничего более. Как и следовало ожидать, это не сработает. ##Commit 2 — Синтаксис команд OpenGL. Одно из наиболее незначительных изменений, внесенных в LWJGL3, является изменение синтаксиса OpenGL на актуальный синтаксис, которые вы можете найти в C API. Как это бывает, единственное изменение, которое требуется сделать в примере программы, заключается в изменении всех вызовов:

glUniformMatrix4(int location, boolean transpose, FloatBuffer values);

на:

glUniformMatrix4fv(int location, boolean transpose, FloatBuffer values);

Логика синтаксиса LWJGL2: подразумевается, что это float матрица, поскольку вы проходите FloatBuffer. LWJGL3 возвращается к исходному API, чтобы быть более совместимым с другими источниками информации, особенно официальной ссылкой.

Как правило, вы будете добавлять «f» и «i’s» и «fv» и.т.п. для вызова команд. Не должно вызывать слишком много проблем и, вероятно, будет достаточно очевидно, что заменить.

Следующий commit будет охватывать самое большое изменение из всех — переход от собственной оконной системы LWJGL2 к GLFW, используемой LWJGL3. ##Commit 3 — Создание Контекста Window/OpenGL. Я думаю, что есть три важных момента, которые нужно знать, когда дело доходит до создания окна и контекста OpenGL в LWJGL3 (то есть GLFW, использует библиотеку окон LWJGL).

  1. GLFW может управлять несколькими окнами и, подобно тому, как OpenGL управляет своими объектами, на них ссылается обработчик, который передается функциям, которые управляют этим окном. LWJGL3 привязки используют «long» переменные для хранения этого обработчика.
  2. Перед созданием окна вы указываете все атрибуты, которые вы хотели бы иметь с помощью подсказок окна. Все передаётся с помощью одной функции: glfwWindowHint()
  3. Поскольку вы можете иметь дело с несколькими окнами, каждое из которых потенциально имеет свой собственный контекст OpenGL, то после его создания вы должны указать LWJGL3, какой контекст вы используете.

Прежде чем мы что-либо сделаем в GLFW, мы должны инициализировать GLFW с помощью glfwInit(), у этого есть сопутствующая функция для завершения GLFW, реализующая его ресурсы glfwTerminate(). В Pong они добавляются к началу и концу функций init() и deinit() соответственно.

Затем неплохо было бы заставить GLFW печатать свои ошибки на консоли, что он не делает по умолчанию, но это просто, и LWJGL предоставляет функцию утилиту для создания соответствующего обратного вызова (подробнее об обратных вызовах позже) в org.lwjgl.glfw.GLFWErrorCallback классе.

glfwSetErrorCallback(errorCallback = GLFWErrorCallback.createPrint(System.err));

Это будет работать отлично, но errorCallback ДОЛЖЕН будет быть полем, а НЕ локальной переменной (запомните дополнительно о обратных вызовах позже).

Теперь, чтобы задать свойства окна. Шаг за шагом в Pong: заголовок передается как параметр функции glfwCreateWindow(), свойство resizeable устанавливается с флагом GLFW_RESIZABLE, запрошенная версия OpenGL устанавливается с помощью флагов GLFW_CONTEXT_VERSION_MAJOR и GLFW_CONTEXT_VERSION_MINOR, запрошенный профиль устанавливается с помощью Флаг GLFW_OPENGL_PROFILE и с полноэкранным режимом мы пока не будем иметь дело.

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

В старом LWJGL2

Display.setTitle("Pong - LWJGL2");
Display.setResizable(true);
Display.create(
    new PixelFormat(), 
    new ContextAttribs(3, 3).withProfileCore(true)
);

В новом LWJGL3

glfwWindowHint(GLFW_RESIZABLE, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Pong - LWJGL3", 0, 0);

«0» в вызове glfwCreateWindow() относятся к полноэкранному и совместно используемому контекстам OpenGL, если вам все равно, их можно игнорировать, но они будут объяснены позже. Дополнительную информацию об этих функциях смотрите в официальной документации: glfwWindowHint(), glfwCreateWindow(). Вы также заметите, что LWJGLException теперь никогда не используется в коде (и фактически удален в LWJGL3), поэтому все случаи применения в Pong можно удалить.

Однако это ещё не все, так как мы все еще проверяем, было создано окно успешно, сообщите LWJGL, чтобы использовать контекст OpenGL этого окна и сделать окно видимым (поскольку окно GLFW скрыто по умолчанию).

Теперь glfwCreateWindow() вернет ‘0’, предоставляя значение NULL, если оно по какой-либо причине не сработает. Таким образом, в Pong добавлена простая проверка, и ваш обратный вызов может поймать причину ошибки, если вы этого хотите:

if(window == 0) {
    throw new RuntimeException("Failed to create window");
}

Теперь мы должны установить контекст этого окна как текущий контекст в этом потоке, вызвав glfwMakeContextCurrent() с обработчиком нашего окна в качестве параметра и, наконец, вызвать GL.createCapabilities(), чтобы LWJGL использовал контекст текущего (нашего окна). Подробнее о контекстах здесь (OpenGL Контексты), и вот документация по glfwMakeContextCurrent().

glfwMakeContextCurrent(window);
GL.createCapabilities();

И, наконец, сделайте окно видимым с помощью glfwShowWindow(). Документация здесь: glfwShowWindow().

glfwShowWindow(window);

После этого у вас будет работающее и видимое окно, которое можно визуализировать, поэтому давайте обновим использование этого окна. Большинство функций, кроме код обработки ввода и изменения размера, являются прямой аналогией, поэтому это не займет много времени.

Прежде всего уничтожить окно, как только вы с ним закончите. Эквивалентом Display.destroy() является glfwDestroyWindow(), документация: glfwDestroyWindow().

glfwDestroyWindow(window);

Теперь Display.isCloseRequested() становится glfwWindowShouldClose(), но это API-интерфейс C, поэтому он не будет возвращать логическое значение, а будет возвращать целое число, представляющее логическое значение, посмотрите здесь: glfwWindowShouldClose.

while(glfwWindowShouldClose(window) == GL_FALSE && 
		remainOpen) {
	...
}

И, наконец, Display.update() не имеет прямого аналога в основном потому, что он выполняет две функции: свопирует буферы и опросы устройств ввода. В GLFW вы должны использовать две функции: glfwPollEvents() и glfwSwapBuffers(), дополнительную информацию смотрите здесь glfwPollEvents() и здесь glfwSwapBuffers().

glfwPollEvents();
glfwSwapBuffers(window);

На этом этапе, если вы закомментируете все содержимое update(), setDisplayMode() и получите что currentTimeMillis() вернёт 0, Pong запустится и отобразит растягиваемое закрываемое окно нужного размера. К несчастью, оно будет просто пустым, но не потому, что рендеринг не работает, а потому что, так как мы не имеем дело с событиями изменения размера, проекционная матрица неправильна.

Это то, что мы будем делать дальше. ##Commit 4 — Framebuffer Resize Handling(Обработка Изменения Размера Кадрового буфера) Прежде чем продолжить, я хочу объяснить важное различие между фреймбуфером(кадровым буфером) и окном. Фреймбуфер — это «холст» OpenGL — массив пикселей, который может быть отображен. Окно — это то, что содержит фреймбуфер, и его можно перемещать и изменять размер и.т.п. Как правило, изменение размера окна приведет к изменению размера фреймбуфера, но это два разных события, и GLFW позволит вам обрабатывать их независимо. Здесь мы сосредоточимся только на событиях изменения размера фреймбуфера. Подробнее о фреймбуферах читайте здесь.

Чтобы обрабатывать события изменения размера фреймбуфер, а также большинства других событий, GLFW использует обратные вызовы. Фактически мы уже использовали обратный вызов для обработки ошибок GLFW. Теперь обратные вызовы — это, по сути, другое название для слушателей, которые мы используем в Swing, JavaFX и во многом другом. LWJGL3 предоставляет абстрактные классы для переопределения, для всех вариантов обратных вызовов GLFW и даже интерфейсы для использования в качестве функциональных интерфейсов, если вы используете лямбды Java8, в этом случае org.lwjgl.glfw.GLFWFramebufferSizeCallback и функциональный интерфейс: org.lwjgl.glfw.GLFWFramebufferSizeCallback.SAM.

Есть одна критически важная вещь связанная с обратными вызовами GLFW. ВСЕГДА ПОДДЕРЖИВАЙТЕ В ПОРЯДКЕ ССЫЛКИ ОБРАТНОГО ВЫЗОВА. Иначе обратные вызовы будут накапливаться в виде мусора, что приведет к довольно сложной ошибке отладки.

Теперь задайте функции изменения размера фреймбуфера для обратного вызова — это glfwSetFramebufferSizeCallback() подробнее здесь: glfwSetFramebufferSizeCallback().

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

if(Display.wasResized() || goneFullscreen) {
    //Сбросить флаг goneFullscreen.
    goneFullscreen = false;
    onResize(Display.getWidth(), Display.getHeight());
}

В LWJGL3 мы можем избавиться от флага goneFullscreen, и все получается намного проще. Мы имеем это в коде инициализации:

glfwSetFramebufferSizeCallback(window, (framebufferSizeCallback = new GLFWFramebufferSizeCallback() {
    @Override
    public void invoke(long window, int width, int height) {
        onResize(width, height);
    }
}));
onResize(WINDOW_WIDTH, WINDOW_HEIGHT);

Мы вызываем onResize() после настройки обратного вызова фреймбуфера для инициализации проекционной матрицы до её начального значения.

Теперь Pong запустится, он создаст рабочее окно и отобразит в нем все элементы Pong. Но пока что он ещё не будет реагировать на ввод, или обновление любого из его элементов. Это будет в следующем commit, но прежде чем мы сможем обрабатывать ввод, нам нужно обновить наш код настройки времени. ##Commit 5 — Получение Времени. LWJGL2 давал нам две функции для получения точного времени Sys.getTime() и Sys.getTimerResolution(). Последняя требовалась, потому что первая, в зависимости от платформы могла работать в других единицах, чтобы вы получали лучшее разрешение и точность. Дело в том, что эта функциональность была написана в то время, когда эквивалентов JRE просто не было, что теперь уже не так, поэтому их можно заменить на функции, найденные в java.lang.System.

Однако GLFW также предоставляет альтернативу, которую мы будем использовать здесь: glfwGetTime() возвращает время в секундах, подробности здесь. Для использования этих значений в Pong мы должны преобразовать их в double значения для хранения текущего времени с точностью, которую может обеспечить этот метод, currentTimeMillis поэтому будет:

public static double currentTimeMillis() {
    return GLFW.glfwGetTime() * 1000;
}

##Commit 6 — Ввод с Клавиатуры. В Pong и, в общем случае, мы получаем ввод с клавиатуры двумя способами. Во-первых, мы запрашиваем клавиатуру из её текущего состояния, выясняя, находится ли какая-то конкретная клавиша нажатой или нет в это время, во-вторых, мы действуем на события клавиатуры, о которых сообщает наша система ввода.

Первый метод является самым простым, и именно это мы рассмотрим в первую очередь. Итак, в LWJGL2 у нас была Keyboard.isKeyDown (). В GLFW мы имеем glfwGetKey (), который вместо логического возвращает GLFW_PRESS или GLFW_RELEASE. Так в Понге:

updatePaddle(
    paddle1, delta, 
    glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS, 
    glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS
);
updatePaddle(
    paddle2, delta, 
    glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS,
    glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS
);

Теперь для обработки событий клавиатуры, и здесь GLFW использует обратные вызовы. На этот раз функция glfwSetKeyCallback (), абстрактный класс LWJGL — org.lwjgl.glfw.GLFWKeyCallback, а функциональный интерфейс — org.lwjgl.glfw.GLFWKeyCallback.SAM. Как всегда с обратными вызовами, вы должны ПОМНИТЬ, ЧТОБЫ СОХРАНЯТЬ СИЛЬНУЮ СПРАВКУ К ВЫЗОВУ. Поэтому в Понге мы должны добавить это к нашей функции init ().

glfwSetKeyCallback(window, (keyCallback = new GLFWKeyCallback() {

    @Override
    public void invoke(long window, int key, int scancode, int action, int mods) {
        if(key == GLFW_KEY_SPACE && action == GLFW_RELEASE) {
            onPlayPauseToggle();
        } else if(key == GLFW_KEY_F5 && action == GLFW_RELEASE) {
            setDisplayMode(true);
        } else if(key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
            //Request close.
            remainOpen = false;
        }
    }

}));

Полное описание параметров метода invoke () callback можно найти в связанном описании, но важно отметить mods, бит-поле, описывающее ключи модификатора, включенные во время этого события. Вы найдете это очень полезным, помните об этом. Также стоит отметить, что callback может получать события GLFW_REPEAT, а также события GLFW_PRESS и GLFW_RELEASE.

Если вы раскомментируете код обновления мяча в update (), на данный момент у вас будет полностью работающая игра в Понге, хотя и отсутствуют некоторые из особенностей оригинала. Следующее commit мы заменим код ввода мыши. ## Commit 7 — Ввод мыши Перед тем, как мы начнем, есть одна важная разница с записью с GLFW по сравнению с LWJGL2. В LWLJGL2, когда он подошел к позиции курсора, нижний левый угол был выбран как начало большинства приложений OpenGL. GLFW берет начало координат в верхнем правом углу, как и в большинстве графических интерфейсов. В Понге это потребует небольшой модификации.

Как и в случае с клавиатурой, мы должны обрабатывать наши запросы мыши и события мыши, а API следует точно так же, как и ввод с клавиатуры. Однако самый простой способ получить позицию курсора в GLFW — использовать обратный вызов позиции курсора, glfwSetCursorPosCallback (), org.lwjgl.glfw.GLFWCursorPosCallback. Как всегда помните, что ХРАНИТЕ СИЛЬНУЮ СПРАВОЧНУЮ ЗАДЕРЖКУ.

В этом фрагменте cursorPos — это просто поле, в котором хранятся текущие позиции x и y текущего курсора. Мы вычитаем заданную позицию y из высоты фреймбуфера, чтобы учитывать GLFW, беря верхний левый угол в качестве источника, когда мы ожидаем, что он будет внизу слева.

glfwSetCursorPosCallback(window, (cursorPosCallback = new GLFWCursorPosCallback() {

    @Override
    public void invoke(long window, double xpos, double ypos) {
        cursorPos.x = xpos;
        cursorPos.y = framebuffer.height - ypos;
    }

}));

Следует отметить, что GLFW использует удвоение, поскольку поддерживает платформу подпиксельной точностью. Это означает, однако, что некоторые из кода обновления должны быть обновлены для использования удвоений. Теперь на фактический код обновления.

updateNewBall(Mouse.getX(), Mouse.getY());

будет выглядеть так:

updateNewBall(cursorPos.x, cursorPos.y);

И цикл событий мыши в update () становится:

glfwSetMouseButtonCallback(window, (mouseButtonCallback = new GLFWMouseButtonCallback() {

    @Override
    public void invoke(long window, int button, int action, int mods) {
        if(button == 0) {
            if(action == GLFW_PRESS && addBall == null) {
                onNewBall(cursorPos.x, cursorPos.y);
            } else if(action == GLFW_RELEASE && addBall != null) {
                onNewBallRelease(cursorPos.x, cursorPos.y);
            }
        }
    }

}));

И с этим изменением у нас есть полностью функционирующая игра Понга. Единственное, чего у нас не было, это полноэкранная возможность, которая будет нашей следующей фиксацией. ## Commit 8 — Fullscreen Первое, что нужно сказать, это то, что при переходе на GLFW мы не сможем реализовать полноэкранное переключение с тех пор, поскольку с момента написания этого GLFW не поддерживает полноэкранное переключение. Это можно сделать, но это связано с созданием нового окна и простым переключением между ними.

Способ, которым мы указываем полноэкранный режим, — передать не-нулевой (не нулевой) монитор в glfwCreateWindow (). Этот аргумент говорит GLFW, который контролирует размещение полноэкранного приложения. Когда вы передаете ненулевой монитор, ширина и высота становятся запрошенным разрешением, которое GLFW будет делать, это лучше всего выполнить. Если мы хотим разрешение рабочего стола (что обычно является хорошей идеей для плавного запуска и производительности).

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

Когда у нас есть монитор, нам нужно разрешение рабочего стола, которое мы можем получить, запросив текущее разрешение монитора. GLFW хранит эту информацию в структуре видеорежима, GLFWVidMode может быть восстановлен с помощью glfwGetVideoMode.

monitor = glfwGetPrimaryMonitor();
GLFWVidMode vidMode = glfwGetVideoMode(monitor);
width = vidMode.width();
height = vidMode.height();

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

window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Pong - LWJGL3", 0, 0);

становится

int windowWidth = WINDOW_WIDTH;
int windowHeight = WINDOW_HEIGHT;
long monitor = 0;
if(START_FULLSCREEN) {
    monitor = glfwGetPrimaryMonitor();
    Retrieve the desktop resolution
    GLFWVidMode vidMode = glfwGetVideoMode(monitor);
    windowWidth = vidMode.width();
    windowHeight = vidMode.height();
}
window = glfwCreateWindow(windowWidth, windowHeight, "Pong - LWJGL3", monitor, 0);

Это создаст полноэкранное окно с разрешением рабочего стола или обычное окно размера WINDOW_WIDTH x WINDOW_HEIGHT на основе значения START_FULSCREEN.

Но в Понге есть еще одна вещь — когда мы инициализируем нашу матрицу проекций с первым вызовом

onResize(WINDOW_WIDTH, WINDOW_HEIGHT

мы не принимаем во внимание, что фреймбуфер не будет такого размера, если мы находимся в полноэкранном режиме. Мы должны получить размер фреймбуфера, используя glfwGetFramebufferSize () (документация). Связывание LWJGL3 этого немного отличается, поскольку мы не имеем указателей. Мы используем java.nio.Buffers, в данном случае IntBuffers, чтобы дать функции где-то положить возвращаемые значения, а затем извлечь значения из буферов. Следовательно:

IntBuffer framebufferWidth = BufferUtils.createIntBuffer(1), 
                framebufferHeight = BufferUtils.createIntBuffer(1);
glfwGetFramebufferSize(window, framebufferWidth, framebufferHeight);
onResize(framebufferWidth.get(), framebufferHeight.get());

Понг теперь будет корректно отображаться и будет полностью функционировать.


Переведено для jmonkeyengine.ru, оригинал
Автор перевода: Andry

Добавить комментарий

jMonkeyEngine.ru © 2017. Все права сохранены.