Все вместе они называются API среднего уровня!

PixiJS выпустил API среднего уровня в версии 5. Он существует между слоем WebGL и высокоуровневым API экранных объектов. Этот API позволяет создавать собственные экранные объекты и работать напрямую с модулем визуализации WebGL.

Он состоит из трех систем, которые я выделил в названии:

  • Геометрия - эта система управляет атрибутами и объектами атрибутов вершин (VAO).
  • Шейдер - эта система управляет униформой и программами WebGL.
  • Состояние - эта система позволяет вам атомарно получать и устанавливать все переменные состояния WebGL - режим наложения, отбраковку, смещение многоугольника, проверку глубины и намотку.

Эта статья предназначена для двух ключевых аудиторий: людей, интересующихся внутренним устройством PixiJS, и людей, заинтересованных в использовании нового API среднего уровня.

Вы читаете главу Внутри PixiJS: WebGL Renderer.

Система геометрии

Что такое геометрия?

Геометрия относится к набору атрибутов и буферам, в которых они хранятся. PixiJS любит делить это на две части:

  • Стиль геометрии: это набор атрибутов и их свойств.
  • Геометрические данные: это набор буферов, которые предоставляют данные для каждого атрибута.

Атрибут Класс

PIXI.Attribute - это класс данных, описывающий атрибут, который не нужно прикреплять к буферу данных.

Класс буфера

PIXI.Buffer оборачивает ArrayBuffer, который содержит значения атрибута. Он также имеет поле index, которое указывает, что этот буфер является индексным буфером.

Buffer имеет _updateId, который увеличивается при изменении данных. Это помогает уменьшить загрузку буфера, как описано в разделе ниже.

GLBuffer и предотвращение загрузки буфера

Система геометрии буферизует данные атрибутов только в двух случаях - при первоначальном рендере и когда он был изменен.

GLBuffer отслеживает загруженные WebGLBuffer, соответствующие Buffer. Это делается с помощью двух полей:

  • updateId: это равно _updateId буфера, когда он был загружен в последний раз. Если теперь оно неравное, это означает, что данные были изменены.
  • byteLength: это размер загруженного буфера. Если текущая длина буфера больше этой, он должен быть снова помещен в буфер с помощью gl.bufferData. В противном случае может произойти перезапись с использованием gl.bufferSubData.

Геометрическая система отслеживает GLBuffer экземпляров, используя Buffer#_glBuffers карту. Он сопоставляет UID контекста с GLBuffer, упаковывающим загруженный WebGLBuffer.

GLBuffer также имеет refCount - количество геометрических фигур, ссылающихся на один и тот же буфер. Помните, что даже если буфер используется в нескольких геометриях, он все равно имеет тот же WebGLBuffer дескриптор.

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

Класс геометрии

PIXI.Geometry управляет набором атрибутов и их буферов. Это не обязательно должно быть сопоставление «один-к-одному», поскольку буфер может содержать несколько атрибутов (как я опишу при чередовании).

Кроме того, Geometry также включает следующие функции:

  • indexBuffer, который сообщает WebGL о порядке рисования вершин.
  • glVertexArrayObjects: Карта контекстных UID для массивов VAO для привязки этой геометрии с помощью одного вызова WebGL 2. Массивы VAO индексируются идентификаторами шейдерных программ, которые ссылаются на эту геометрию.
  • instanced: Если эта геометрия содержит экземпляр атрибута.
  • instanceCount: количество экземпляров для рисования с этой геометрией.
  • refCount: количество ссылок на эту геометрию.

GeometrySystem

Этот класс не является частью API среднего уровня - это фактически система рендеринга, которая управляет привязкой геометрии!

contextChange и совместимость с WebGL 1, 2

GeometrySystem#contextChange вызывается хотя бы один раз и при каждом изменении объекта контекста WebGL. Этот метод выполняет некоторое полифиллинг контекстов WebGL 1, так что их можно рассматривать как контекст WebGL 2, если доступны поддерживаемые расширения.

  • Если OES_vertex_array_object доступен, он будет выполнять полифиллинг методов createVertexArray, bindVertexArray и deleteVertexArray в контексте с помощью методов расширения.
  • Точно так же, если ANGLE_instanced_arrays доступен, он будет выполнять полифилирование методов vertexAttribDivisor, drawElementsInstanced и drawArraysInstanced.

Если VAO не поддерживаются изначально, тогда createVertexArray, bindVertexArray и deleteVertexArray будут полифилированы пустыми функциями, которые ничего не возвращают.

Привязка

Метод bind вызывается методами render экранного объекта для привязки их геометрии. Это внутренне использует VAO для достижения этой цели. Если VAO изначально не поддерживаются, то каждый атрибут и его буфер привязываются вручную.

Работает это так:

  • bind передается geometry и shader для привязки. Он пытается найти VAO в glVertexArrayObjects карте геометрии.
  • Если он не найден, он создает его, используя initGeometryVao. Если VAO не поддерживаются, то этот метод не делает ничего важного.
  • Он связывает VAO с использованием gl.bindVertexArray (если они изначально поддерживаются) или this.activateVao (если они не поддерживаются).
  • Он обновляет грязные буферы через updateBuffers.

Рисунок

Метод draw рисует геометрию с помощью вызова gl.drawArrays или gl.drawElements. Если геометрия является экземпляром, он будет использовать соответствующий вызов. Это следует вызывать только после привязки геометрии.

Чередование

Если геометрия статична, то есть не меняется со временем, ее можно чередовать для дальнейшей работы. При чередовании атрибутов они сохраняются в одном буфере; однако их шаг равен размеру вершины, а не их собственному размеру. Размер вершины - это размер всех чередующихся атрибутов вместе взятых.

«Интересные функции включают возможность автоматического чередования данных в одном буфере для максимальной эффективности рендеринга», - Мэт Гровс

Система шейдеров

Эта система управляет программами шейдеров и их униформой.

Класс шейдера

Класс шейдера содержит программу WebGL и ее униформы. По большей части это поверхностная абстракция над обоими.

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

Программа Класс

Program - это абстракция над WebGLProgram. Это объединение вершинного шейдера и фрагментного шейдера. Кроме того, у него также есть name. Когда он построен, он изменяет источник вершинного шейдера - он проверяет, поставили ли вы #version в первую строку; в противном случае он делает следующее:

  • Добавляет #version за вас.
  • Он добавляет для вас макрос #define SHADER_NAME ${name} (где ${name} заменяется именем шейдера, которое вы указали). Если вы не указали имя, по умолчанию будет использоваться pixi-shader.
  • Добавлен оператор set-precision (высокая точность).

Существует nameCache карта, в которой хранится количество ссылок на конкретное имя. Например, если вы создаете Program с именем «custom-shader», тогда для nameCache['custom-shader'] будет установлено значение 1. При создании другого экземпляра Program с тем же именем для nameCache['custom-shader'] будет установлено значение 2, а ваше имя будет изменено на «custom -шейдер-1 ». Это гарантирует, что у всех шейдеров будет уникальное имя. Кроме того, если вы не укажете имя, имя вашего шейдера будет фактически «pixi-shader-‹ какое-то число ›».

Этот класс также предоставляет несколько удобных карт осмотра. Свойства attributeData и uniformData - это карты имен атрибутов и унифицированных имен в их метаданные. Эти объекты метаданных хранят type, size, name и location.

Например,

const vertexShaderSource = `
  attribute vec2 vertexData;
  attribute vec2 uvs;
  uniform mat3 projectionMatrix;
  /* more */
`;

/* A default fragment shader is used here! Ignore it! */
const program = new PIXI.Program(vertexShaderSource,
                                   undefined,
                                   "demo-shader");
console.log(program.name);// demo-shader
console.log(program.attributedata);
// [
//   {
//     type: 'vec2',
//     name: 'vertexData',
//     size: 2,
//     location: <some value via gl.getAttribLocation()>
//   },
//   ...
// ]
console.log(program.uniformData);
// [
//  {
//    type: 'mat3',
//    name: 'projectionMatrix',
//.   size: 1,
//.   location: <some value via gl.getUniformLocation>
//  }
// ]
const duplicateProgram = new PIXI.Program(undefined, undefined,
                                            "demo-shader");
console.log(program.name);// demo-shader-1

UniformGroup Класс

UniformGroup - особый класс. Он содержит список униформ, который также может включать вложенные группы униформ.

const uniform = new PIXI.UniformGroup([
  projectionMatrix: PIXI.Matrix.IDENTITY,// mat3
  cameraPosition: Float32Array.from([0, 0, 0, 0]), // vec4
  app_wide_uniforms: <some other uniform group>
]);

Разрешая вложенные группы униформ, PixiJS позволяет вам «прикреплять» общие формы ко всем вашим шейдерам наряду с их конкретными. Это позволяет вам изменять единые значения в одном месте, но отражать их во всех шейдерах.

Например, в PixiJS есть translationMatrix униформа, доступная глобально для всех шейдеров. Это делается путем добавления globals uniform-group ко всем формам шейдера. globals содержит translationMatrix, который может быть обновлен там сам.

ShaderSystem

Система шейдеров отвечает за привязку программ шейдеров и их униформы. Привязать программу шейдера просто, как gl.useProgram. Однако PixiJS использует сложный механизм для синхронизации однородных групп.

ShaderSystem использует функцию generateSyncUniform для динамического создания функции, которая загружает однородную группу. Этой функции передаются три аргумента - uniformData, uniformGroups и renderer. Здесь uniformData должен быть кешем загруженных унифицированных значений (как я описываю ниже). uniformGroups - свойство программы uniform-groups. renderer - это средство визуализации WebGL.

Эта динамическая генерация кода экономит время за счет уменьшения количества вызовов функций (до нуля, если не присутствуют вложенные группы). Если eval не разрешен в браузере, то пакет unsafe-eval предоставляет альтернативный механизм для загрузки униформы. Он делает это путем вызова функций, которые загружают единый тип данных для каждой униформы.

Короче говоря, PixiJS экономит время, встраивая вызовы функций для каждой формы. Кроме того, он загружает каждую униформу, только если она изменилась (как и система геометрии).

Это достигается путем кеширования загруженных значений с помощью GLProgram. Класс GLProgram имеет три свойства:

  • program: экземпляр Program.
  • uniformData: Карта единообразных имен для { value: , location: } объектов. Это кеш загруженных унифицированных значений.
  • uniformGroups: это карта UniformGroup объектов с функциями, которые их загружают. Почему не только одна функция, которая выгружает всю форму (корневую)? Это связано с тем, что загрузка является рекурсивной, и функция, созданная для загрузки корневой унифицированной группы, будет вызывать renderer.shader.syncUniformGroup(nestedGroup).

Каждый Program имеет контекстный UID для GLProgram карты, называемой glPrograms. GLProgram извлекается шейдерной системой при вызове syncUniformGroup.

Согласен, эта часть очень раздутая. После прочтения исходный код определенно должен иметь гораздо больше смысла (я надеюсь!).

Государственная система

Наконец-то! На самом деле это самая простая из всех трех систем среднего уровня. Здесь нет кеширования, динамической генерации кода и полифиллинга - просто улучшение производительности!

Государственный класс

State содержит четыре переменных состояния WebGL - режим наложения, смещение многоугольника, отбраковку, проверку глубины и извилистость. Для методов render отображаемого объекта может потребоваться, чтобы контекст WebGL находился в определенном состоянии. Традиционно это достигается путем включения / отключения каждой переменной вручную:

gl.setBlendMode(gl.BLEND_MODE_NORMAL);
gl.enable(gl.BLEND);
gl.disable(gl.CULL_FACE);// what if it was already disabled?

StateSystem

Поскольку вызовы WebGL дороги, система состояний сокращает их, сохраняя переменные состояния WebGL локально. Вы можете установить состояние WebGL с помощью renderer.state.set(stateInstance). При этом будут установлены только те переменные, которые необходимо изменить.

Кроме того, state объединяет значения всех четырех переменных состояния в одно число, которое можно сравнивать. Это дальнейшая оптимизация.

«Все возможные комбинации состояний WebGL битовые упаковываются в уникальное целое число» - Мэт Гровс

Привет, ребята, меня зовут Шукант Пал, и эта статья - моя постоянная попытка задокументировать проекты с открытым исходным кодом, такие как PixiJS. Моя цель - сделать их доступными для новых разработчиков, таких как я, и повысить прозрачность в сообществе разработчиков ПО с открытым исходным кодом.