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

1.4. FAQ Привязки

Опубликованно: 31.07.2018, 14:02
Последняя редакция, Andry: 09.08.2018 6:36

Каковы различные компоненты родных(native) привязок?

Чтобы вызвать родную(native) функцию, LWJGL требует как Java, так и родной(native) код. Давайте используем функцию glShaderSource, которая была введена в версии 2.0 спецификации OpenGL:

void glShaderSource(GLuint shader, GLsizei count, const GLchar **strings, const GLint *length)

Это void функция, которая принимает целое число без знака, целое число со знаком размер, указатель на массив строк и указатель на массив целых чисел со знаком. В LWJGL это преобразование происходит следующим образом:

public static native void nglShaderSource(int shader, int count, long strings, long length)

Обратите внимание на префикс ‘n’ и сопоставления типов: Как со знаком, так и беззнаковые целочисленные параметры были сопоставлены с Java int и оба указателя, сопоставлены с Java long. Мы обсудим эти сопоставления позже.

Префикс ‘n’ не является абсолютно необходимым, но добавляется для того что бы автозавершение кода в среде IDE позволяло разделить родные(native) и не-родные(native) методы.

В дополнение к родному(native) методу, так же включены несколько обычных Java-методов:

public static void glShaderSource(int shader, PointerBuffer strings, IntBuffer length)
public static void glShaderSource(int shader, CharSequence... strings)
public static void glShaderSource(int shader, CharSequence string)

Это дружественные Java методы, которые разработчики обычно используют. Первый специализирует типы указателей и сбрасывает аргумент count, второй заменяет все с помощью параметра CharSequence vararg, а третий с одним CharSequence.

С родной(native) стороны все очень просто. Это определение C указателя функции:

typedef void (APIENTRY *glShaderSourcePROC) (jint, jint, const intptr_t, const intptr_t);

и реализация функции JNI:

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_GL20_nglShaderSource(
    JNIEnv *__env, jclass clazz, // unsed
    jint shader, jint count, jlong stringsAddress, jlong lengthAddress
) {
	glShaderSourcePROC glShaderSource = /* получить указатель функции */;
	const intptr_t strings = (const intptr_t)stringsAddress;
	const intptr_t length = (const intptr_t)lengthAddress;
	UNUSED_PARAM(clazz)
	glShaderSource(shader, count, strings, length);
}
Используемые C типы не соответствуют исходной функции, но они бинарно совместимы.

LWJGL принимает здесь очень важное решение; собственный код ничего не делает, кроме параметров заливки для соответствующих типов и вызывает функцию native. Вся сложность и сопоставление с удобными для пользователя параметрами для правильных исходных данных обрабатываются в Java-коде. Все указатели передаются как примитивные longs, примитивные значения передаются без изменений и все. Параметры JNIEnv и jclass никогда не затрагиваются, и никакого взаимодействия с JVM нет; никакие параметры задания не передаются, и нет вызовов C-to-Java. Собственные методы также общедоступны, что позволяет пользователям создавать свои собственные методы удобства поверх исходных, и они могут быть уверены, что ничего необычного не происходит при вызове функции JNI.

Двигаясь вперед, нынешний проект облегчит LWJGL принятие Project Panama, который будет доступен на Java 10.

Вышеупомянутое относится ко всем привязкам LWJGL. Большинство пользователей никогда не будут иметь дело с родными методами напрямую, и они не должны, если нет веской причины; использование их по своей сути является небезопасным. Более опытные разработчики могут делать интересные вещи с помощью арифметики raw pointer и утилиты org.lwjgl.system.MemoryUtil.

Что такое параметр __functionAddress, который требуется многим нативным методам?

Существует две проблемы с динамически связанными библиотеками: a) указатели на функции должны быть динамически обнаружены во время выполнения и b) некоторые библиотеки могут использовать разные указатели на функции в разных контекстах. С другой стороны, код JNI является статическим, что означает, что единственный способ вызвать такие функции — передать адреса функций до собственного кода.

Префикс ‘__’ позволяет сделать более очевидным, что это синтетический параметр.

Если собственные методы не используются напрямую, пользователям редко приходится явно передавать адрес функции. LWJGL использует специальные механизмы для этого автоматически. Например, методы OpenGL используют поточно-локальное хранилище для текущего контекста, а методы Vulkan используют параметр диспетчерского дескриптора для получения правильного указателя функции.

Как отображаются примитивные значения?

Отображение обычно является простым, за исключением неподписанных типов и непрозрачных указателей:

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

Непрозрачные указатели являются указателями на данные, которые имеют неопределенную структуру и обычно соответствуют внутреннему состоянию, управляемому сторонним API. Значения таких указателей просто передаются и выходят из API и никогда не используются для прямого доступа к памяти.

Давайте посмотрим на некоторые примеры. Следующие объявления функций:

void glDepthMask(GLboolean mask)
void glAlphaFunc(GLenum func, GLfloat ref)
void glClear(GLbitfield mask)
void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type)
void glVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3)
void glVertexAttrib4Nub(GLuint dinex, GLubyte x, GLubyte y, GLubyte z, GLubyte w)
void glfwSwapBuffers(GLFWwindow* window)

отображаются на следующие Java-методы:

public static void glDepthMask(boolean flag)
public static void glAlphaFunc(int func, float ref)
public static void glClear(int mask)
public static void glCopyPixels(int x, int y, int width, int height, int type)
public static void glVertexAttrib4s(int index, short v0, short v1, short v2, short v3)
public static void glVertexAttrib4Nub(int index, byte x, byte y, byte z, byte w)
public static void glfwSwapBuffers(long window)

Как отображаются указатели на примитивные значения?

Учитывая объявление родной функции, например:

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

Непрозрачные указатели являются указателями на данные, которые имеют неопределенную структуру и обычно соответствуют внутреннему состоянию, управляемому сторонним API. Значения таких указателей просто передаются и выходят из API и никогда не используются для прямого доступа к памяти.

Давайте посмотрим на некоторые примеры. Следующие объявления функций:

void glDepthMask(GLboolean mask)
void glAlphaFunc(GLenum func, GLfloat ref)
void glClear(GLbitfield mask)
void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type)
void glVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3)
void glVertexAttrib4Nub(GLuint dinex, GLubyte x, GLubyte y, GLubyte z, GLubyte w)
void glfwSwapBuffers(GLFWwindow* window)

отображаются на следующие Java-методы:

public static void glDepthMask(boolean flag)
public static void glAlphaFunc(int func, float ref)
public static void glClear(int mask)
public static void glCopyPixels(int x, int y, int width, int height, int type)
public static void glVertexAttrib4s(int index, short v0, short v1, short v2, short v3)
public static void glVertexAttrib4Nub(int index, byte x, byte y, byte z, byte w)
public static void glfwSwapBuffers(long window)

Как отображаются указатели на примитивные значения?

Учитывая объявление родной функции, например:

void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);

у нас есть следующие методы Java:

public static native void nglUniform4fv(int location, int count, long value) // A
public static void glUniform4fv(int location, FloatBuffer value) // B

В этом случае у нас есть указатель float и явный параметр count, который определяет, сколько векторов vec4 следует читать из этого указателя.

В GLSL vec4 представляет собой вектор из 4 значений float. Поэтому указатель должен указывать на массив count * 4 * 4 байта.

В методе A, который является исходным методом JNI, параметр count и адрес указателя являются явными.

В методе B произошли две трансформации; a) параметр value теперь является FloatBuffer, соответствующим типу родного указателя, и b) параметр count теперь неявный, замененный значением .remaining () / 4. 4 есть потому, что каждый счетчик представляет собой вектор из 4 значений.

Текущий Buffer.position () влияет на адрес указателя, который будет передан в нативную функцию. Если FloatBuffer обертывает указатель с адресом x, а текущая позиция равна 2, будет использоваться адрес x + 8.

Текущий Buffer.limit () определяет, сколько значений будет считано или записано в буфер. В сочетании с примечанием о Buffer.position () выше это соответствует тому, как буферы java.nio обычно используются в JDK. Единственное различие заключается в том, что LWJGL никогда не изменяет текущую позицию или ограничение буфера. Это уменьшает необходимость перетаскивания () или перемотки назад () буферов.

Как обрабатываются выходные параметры?

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

Классическим примером является glGetIntegerv, очень полезная функция OpenGL:

void glGetIntegerv(GLenum pname, GLint *data)

LWJGL включает следующие методы для этой функции:

public static native void nglGetIntegerv(int pname, long params) // A
public static void glGetIntegerv(int pname, IntBuffer params) // B
public static int glGetInteger(int pname) // C

Методы A и B аналогичны описанным выше, за исключением того, что нет явного параметра count. Если бы был параметр count, то такое же преобразование было бы применено в случае B (params.remaining () будет использоваться неявно). Для этой функции пользователь должен убедиться, что для возвращаемых данных достаточно места на основе запрошенного параметра.

Это, по общему признанию, плохой дизайн, но это очень старая функция. Современные функции OpenGL выполняют гораздо более эффективную работу с проверкой, и большинство из них требуют явных размеров.

Метод C является интересным. Его можно использовать, когда запрос возвращает одно значение. Без него пользователь должен будет выделить один буфер значений, вызвать метод, а затем прочитать значение из буфера, сложную процедуру. Вместо этого LWJGL сбрасывает параметр params и изменяет тип возвращаемого метода из void в int. Это делает его более естественным и удобным в использовании.

LWJGL использует org.lwjgl.system.MemoryStack для реализации таких методов, как C. Такие методы можно безопасно использовать из нескольких контекстов одновременно.

Это круто. Можно ли это сделать для однозначных входных параметров?

Конечно, если это полезно для определенной функции. Например:

void glDeleteTextures(GLsizei n, const GLuint *textures)

может использоваться с любым из них:

public static native void nglDeleteTextures(int n, long textures)
public static void glDeleteTextures(IntBuffer textures)
public static void glDeleteTextures(int texture) // single value!

Есть ли удобный способ использования String или CharSequence?

Да, LWJGL упрощает обработку текстовых данных. Соглашением для текста является то, что входные параметры отображаются на CharSequence, а выходные (возвращенные) значения сопоставляются с String. LWJGL корректно обрабатывает кодировки символов, поддерживает кодирование / декодирование ASCII, UTF-8 и UTF-16 и будет использовать правильный вариант в зависимости от используемой функции. Наконец, он может так же легко обрабатывать строки и строки с нулевым завершением с явной длиной. Некоторые примеры:

GLint glGetAttribLocation(GLuint program, const GLchar *name) // A) null-terminated input
GLubyte *glGetString(GLenum name) // B) null-terminated output
void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) // C) output w/ explicit length

и методы Java:

// A)
public static native int nglGetAttribLocation(int program, long name)
public static int glGetAttribLocation(int program, ByteBuffer name)
public static int glGetAttribLocation(int program, CharSequence name)
// B)
public static native long nglGetString(int name)
public static String glGetString(int name)
// C)
public static native void nglGetProgramInfoLog(int program, int maxLength, long length, long infoLog)
public static void glGetProgramInfoLog(int program, IntBuffer length, ByteBuffer infoLog)
public static String glGetProgramInfoLog(int program, int maxLength)
public static String glGetProgramInfoLog(int program)

Бонусная функция: последний glGetProgramInfoLog будет использовать glGetProgrami (program, GL_INFO_LOG_LENGTH) для автоматического выделения достаточного количества хранилища для возвращаемого текста журнала!

Как можно настроить обратные вызовы из собственного кода?

Каждый тип обратного вызова сопоставляется с парой интерфейса / абстрактного класса. Подобно CharSequence и String, когда тип обратного вызова является входным параметром, он отображается на интерфейс и когда выход (возвращаемое значение) относится к абстрактному классу. Это важно по двум причинам:

  • Конверсии Lambda SAM применимы только к интерфейсам. Все интерфейсы обратного вызова являются функциональными интерфейсами по определению.
  • Встроенные функции обратного вызова генерируются во время выполнения. Это означает, что есть собственные ресурсы, которые должны быть сохранены как состояние и должны быть явно освобождены, если их больше не использовать. Только абстрактный класс может иметь состояние и метод free ().

Примером функции обратного вызова, которая может быть зарегистрирована в контексте OpenGL, является:

// Function signature:
typedef void (APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
// Callback registration:
void glDebugMessageCallback(GLDEBUGPROC callback, const void *userParam)

Они сопоставляются с:

public static native void nglDebugMessageCallback(long callback, long userParam)
public static void glDebugMessageCallback(GLDebugMessageCallbackI callback, long userParam)

и интерфейс org.lwjgl.opengl.GLDebugMessageCallbackI, который определяет один метод:

void invoke(int source, int type, int id, int severity, int length, long message, long userParam);

Обратите внимание, что glDebugMessageCallback принимает GLDebugMessageCallbackI. Нативная функция обратного вызова генерируется при вызове glDebugMessageCallback и указатель на эту функцию передается в LWJGL. Это означает, что простое перенос лямбды в glDebugMessageCallback вызовет утечку памяти, потому что ничего не возвращается, что разработчик может использовать для освобождения генерируемой функции обратного вызова. Существует два подхода к решению этой проблемы:

// Method 1: store this reference in a Java variable.
GLDebugMessageCallback cb = GLDebugMessageCallback.create((source, type, id, severity, length, message, userParam) -> {
    // print message
});
glDebugMessageCallback(cb, NULL);
// later...
cb.free();

// Method 2: use glGetPointer to retrieve the callback
glDebugMessageCallback((source, type, id, severity, length, message, userParam) -> {
    // print message
}, NULL);
// later...
GLDebugMessageCallback.create(glGetPointer(GL_DEBUG_CALLBACK_FUNCTION)).free();

Второй способ более удобен, но не все API-интерфейсы обеспечивают способ получения указателя функции обратного вызова, который был ранее установлен. В таких случаях пользователь LWJGL отвечает за сохранение ссылки обратного вызова в Java-коде.

Существует ли специальное сопоставление для собственных структур?

Да, каждый тип структуры сопоставляется с классом. Класс содержит методы создания / распределения и getters / setters для членов структуры. Макет структуры (смещения поля, sizeof, alignof) доступен как статические конечные поля int.

LWJGL поддерживает структуры, союзы и любую комбинацию вложенных друг в друга. Соотношения элементов и характеристики выравнивания / размера вычисляются во время выполнения, для обработки разностей размеров указателя и динамического заполнения.

Каждый класс структуры также имеет внутренний буферный класс, который представляет массив этих структур. Класс Buffer имеет API, который совместим с буферами java.nio, а также getters / seters, которые могут использоваться для доступа к массиву структур с помощью мухи.

Пример:

typedef struct GLFWimage
{
    int width;
    int height;
    unsigned char* pixels;
} GLFWimage;
GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* images);

Вышеуказанные значения отображаются на:

public class GLFWImage extends Struct implements NativeResource

public static long nglfwCreateCursor(long image, int xhot, int yhot)
public static long glfwCreateCursor(GLFWImage image, int xhot, int yhot) // A

public static void nglfwSetWindowIcon(long window, int count, long images)
public static void glfwSetWindowIcon(long window, GLFWImage.Buffer images) // B

GLFWImage имеет следующие статические поля:

GLFWImage.SIZEOF // sizeof(GLFWimage)
GLFWImage.ALIGNOF // alignof(GLFWimage)
GLFWImage.WIDTH // offsetof(GLFWimage, width)
GLFWImage.HEIGHT // offsetof(GLFWimage, height)
GLFWImage.PIXELS // offsetof(GLFWimage, pixels)

и следующие геттеры и сеттеры:

img.width()
img.height()
img.pixels()

Существуют также «небезопасные» статические геттеры и сеттеры, которые могут использоваться для доступа к ячейкам памяти, которые не завернуты в класс struct или struct buffer.

В приведенных выше методах A просто. Результат image.address () передается в собственный код. В B-изображениях представлен массив структур GLFWimage. Обратите внимание, что он был сопоставлен с GLFWImage.Buffer, и параметр count был удален, как и в других методах, которые принимают буферы java.nio с автоматическим размером.

Структуры памяти и структурные буферы могут быть любой памятью без кучи. MemoryStack, MemoryUtil и BufferUtil могут использоваться для распределения экземпляров структуры с соответствующими характеристиками удобства использования / производительности.


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

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

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