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

1.4. FAQ Привязки

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

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

Чтобы вызвать нативную функцию, LWJGL требует как Java, так и нативный код. Давайте используем функцию 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 позволяло разделить нативные и не нативные методы.

В дополнение к нативному методу, так же включены несколько обычных 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.

С нативной стороны все очень просто. Это реализация 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 осуществляет здесь очень важный выбор о реализации; нативный код ничего не делает, кроме параметров заливки для соответствующих типов и вызывает нативную функцию. Все сложности и сопоставление с удобными для пользователя параметрами для корректных нативных данных, обрабатываются в Java-коде. Все указатели передаются как примитивные long-и, примитивные значения передаются без изменений и это все. Параметры JNIEnv и jclass никогда не затрагиваются, и никакого взаимодействия с JVM нет; никакие jobject параметры не передаются, и нет вызовов C-к-Java. Нативные методы также public, что позволяет пользователям создавать свои удобные для них методы поверх исходных, и они могут быть спокойны, что никаких проблем не будет происходит при вызове функции JNI.

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

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

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

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

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

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

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

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

  • Без знаковые целые типы сопоставляются в Java соответствующему типу со знаком. Может показаться что это звучит как с ошибкой, но в большинстве случаев это не так. Они могут быть легко преобразованы в, и из целых чисел с высокой точностью, используя при этом простые бинарные операции. Основные арифметические операции, кроме деления, работают одинаково как для целых чисел со знаком, так и без знака. Java 8 также предоставляет полезные утилиты для их обработки.
  • Непрозрачные указатели сопоставляются с Java через long.
Непрозрачные указатели являются указателями на данные, которые имеют неопределенную структуру и обычно соответствуют внутреннему состоянию, управляемому сторонним 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 теперь неявный, замененный value.remaining() / 4. На 4 потому, что каждый count представляет собой вектор из 4-х значений.

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

Как обрабатываются параметры вывода(output)?

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

Классическим примером является 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. Такие методы можно безопасно использовать из нескольких контекстов одновременно.

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

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

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) // одно значение!

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

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

GLint glGetAttribLocation(GLuint program, const GLchar *name) // A) нуль-терминированный ввод
GLubyte *glGetString(GLenum name) // B) нуль-терминированный вывод
void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) // C) вывод w/ точной длинны

и 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) внутри для автоматического выделения достаточного количества память для возврата лога текста!

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

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

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

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

// Сигнатура функции:
typedef void (APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint id,
 GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
// Регистрация обратного вызова:
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 вызовет утечку памяти, потому что ничего не возвращается, что разработчик может использовать для освобождения генерируемой нативной функции обратного вызова. Существует два подхода к решению этой проблемы:

// Способ 1. Сохраните эту ссылку в переменной Java.
GLDebugMessageCallback cb = GLDebugMessageCallback.create((source, type, id, severity, length, message, userParam) -> {
    // печатать сообщение
});
glDebugMessageCallback(cb, NULL);
// потом...
cb.free();

// Способ 2: используйте glGetPointer для получения обратного вызова
glDebugMessageCallback((source, type, id, severity, length, message, userParam) -> {
    // печатать сообщение
}, NULL);
// потом...
GLDebugMessageCallback.create(glGetPointer(GL_DEBUG_CALLBACK_FUNCTION)).free();

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

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

Да, каждый тип структуры сопоставляется с классом. Класс содержит методы создания/распределения и геттеры/сеттеры для элементов структуры. Компоновка структуры (смещения поля, sizeof, alignof) доступеа как static final int поле.

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

Каждый класс структуры также имеет внутренний класс Buffer, который представляет массив этих структур. Класс Buffer имеет API, который совместим с буферами java.nio, а также геттеры/сеттеры, которые могут использоваться для доступа к массиву структур с приспособленец (flyweight pattern).

Пример:

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()
Существуют также «небезопасные» статические геттеры и сеттеры, которые могут использоваться для доступа к ячейкам памяти, которые не обёрнуты в класс структуры или структурный буфер.

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

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

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

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

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