Разработчикам движка

Стиль оформления кода

В движке применяется структурное программирование. Код организуется в модули. Подходы ООП не используются, классы не определяются, наследование не осуществляется и т.п.

Используется K&R стиль, за исключением того, что открывающая скобка для составного оператора ставится на той же строке, например:

function foo_bar() {
    // ...
}

if (a > b) {
    // ...
}

Для выравнивания используются 4 пробела (табуляция запрещена).

Примеры

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

var foo_bar = 123;  // correct
var fooBar = 123;   // wrong

Все глобальные переменные начинаются со знака подчеркивания:

var _foo_bar = null;

Константы пишутся прописными буквами и никогда не начинаются со знака подчеркивания:

var FOO_BAR = 100;

Для внешних API названия методов и свойств задаются через точку. Поля, требующие защиту от обфускации, помещаются в специальный тэг @cc_externs:

exports.FOO_BAR = 123;

exports.foo_bar = function() {

}

/**
 * Set properties.
 * @method module:properties.set_props
 * @param {Object} foo Foo object
 * @cc_externs props_1 props_2
 * @cc_externs props_3 props_4
 */
exports.set_props = function(foo) {

    var bar_1 = foo.props_1;
    var bar_2 = foo.props_2;
    var bar_3 = foo.props_3;
    var bar_4 = foo.props_4;

    ...
}

Комментарии только на английском языке. Стиль комментирования - JSDoc.

Сборка движка

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

Для компиляции движка и входящих в SDK приложений достаточно выполнить команду из корневой директории SDK:

make compile

Полная сборка, включающая конвертацию ресурсов (текстур, звуков и видео), компиляцию и подготовку документации вызывается командой:

make build

Сборка архивов с дистрибутивами:

make dist

Все вышеперечисленные операции могут быть выполнены одной командой:

make all

Сборка аддона

Бинарные сборки аддона Blend4Web подготовлены для следующих платформ: Linux x32/64, macOS x64, Windows x32/64. В то же время пользователи имеют возможность произвести сборку самостоятельно.

Для этого необходимо наличие Python 3.x (желательно, чтобы версия была эквивалентна используемой в Blender) и компилятора языка C (в Linux достаточно установить пакеты python3-dev и build-essential).

Пути относительно корневой директории SDK:
  • скрипт сборки: csrc/b4w_bin/build.py
  • аддон Blend4Web: blender_scripts/addons/blend4web/

Запуск сборки осуществляется следующим образом:

python3 ./csrc/b4w_bin/build.py

Результатом сборки будет бинарный файл с именем:

b4w_bin_[ПЛАТФОРМА]_[АРХИТЕКТУРА].[СТАНДАРТНОЕ_РАСШИРЕНИЕ],

размещенный в каталоге с аддоном. Пример: b4w_bin_Linux_64.so. После этого аддон станет готовым к использованию на данной платформе.

Зависимости

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

Название Ubuntu 16.04 package Назначение
Bash в составе по умолчанию интерпретатор скриптов
Python 3 в составе по умолчанию интерпретатор скриптов
NodeJS nodejs компиляция шейдеров
Java default-jre компиляция и обфускация модулей движка
ImageMagick imagemagick конвертация текстур
NVIDIA Texture Tools libnvtt-bin конвертация текстур
Libav libav-tools конвертация медиаресурсов
NVIDIA Cg Toolkit nvidia-cg-toolkit отладка шейдеров
OptiPNG optipng оптимизация PNG-файлов
Emscripten из исходных текстов EMSDK сборка Uranium
Gnuplot gnuplot отладка
Graphviz graphviz отладка
xsel xsel отладка
Sphinx python3-sphinx сборка документации
sphinx-intl устанавливается через PIP v3 (pip3 install sphinx-intl) сборка документации (перевод)
TeX Live texlive texlive-latex-extra texlive-lang-cyrillic texlive-lang-chinese texlive-xetex сборка документации (PDF-версия)
JSDoc 3 устанавливается через NPM (npm install -g jsdoc) сборка документации (документация на API)
PEG.js из исходных текстов PEG.js сборка препроцессора шейдеров

Названия функций и переменных

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

init_
создание абстрактного объекта
create_
создание конкретного объекта
update_
обновить состояние имеющегося объекта
attach_/detach_
добавить/удалить временное свойство к объекту
append_/remove_
добавить/удалить временное свойство к уже существующим подобного рода
insert_/pop_
добавить/удалить элемент массива (доступ по индексу места)
switch_
изменить значение бинарной величины/флага
apply_/clear_
операция с флагом, бинарной величиной или произвольным параметром
set_/get_
установить/получить значение свойства/переменной
_tmp
глобальная переменная - кеш в виде простого объекта (массив, вектор)
_cache
глобальная переменная - кеш в виде сложного объекта

Отладка

Отладка движка производится с помощью методов модуля debug.js.

Структура текущего рендер-графа может быть сохранена в формате DOT с помощью вызова b4w.debug.scenegraph_to_dot(), например, в консоли браузера. После вызова данного метода содержимое консоли сохранить в файл с расширением .gv. Чтобы получить граф в графическом виде, необходим набор утилит graphviz. Преобразование в формат SVG выполняется с помощью вызова:

> dot -Tsvg graph.gv -o graph.svg

где graph.gv имя файла с сохранённым графом.

Компиляция шейдеров

Используемые в движке шейдеры подвергаются обработке компилятором. Kомпилятор выполняет 3 основных процедуры:

  • валидацию кода шейдеров,
  • обфускацию кода шейдеров,
  • оптимизацию кода шейдеров.

Для запуска компиляции требуется выполнить одну из команд в корневой директории SDK:

> make compile_shaders
> make verify_shaders
  • make compile_shaders - проверка, обфускация, оптимизация и экспорт скомпилированных шейдеров,
  • make verify_shaders - проверка, обфускация и оптимизация.

В процессе компиляции сначала осуществляется синтаксический анализ (парсинг) текста шейдера. Соответствующий парсер создается автоматически на основе грамматики с помощью генератора PEG.js. Далее по данным парсинга производится валидация, обфускация и оптимизация шейдеров, после чего шейдеры экспортируются в виде абстрактного синтаксического дерева (Abstract Syntax Tree, AST) для непосредственной загрузки движком.

Расположение основных файлов в репозитории:

  • исходная грамматика - glsl_utils/pegjs/glsl_parser.pegjs
  • скрипт генерации парсера - glsl_utils/pegjs/gen_nodejs.sh
  • парсер - glsl_utils/compiler/glsl_parser.js

Валидация

Компилятор шейдеров выполняет следующие процедуры, связанные с проверкой кода:

  • вывод сообщений о неиспользуемых переменных и функциях (dead code),
  • проверка синтаксиса шейдеров,
  • проверка шейдеров на соответствие import/export-механизма,
  • удаление лишних пробелов, переводов строк и повторяющихся символов «;».

Обфускация

Обфускация служит для сокращения объема и затруднения понимания GLSL-кода. На данный момент в нем реализована следующая процедура:

  • замена пользовательских идентификаторов более короткими односимвольными, двухсимвольными и т.д. именами (с поддержкой import/export-механизма).

Оптимизация

Оптимизация заключается в выполнении следующих процедур:

  • удаление фигурных скобок, которые не несут функциональной нагрузки, но порождают новые области видимости (данный функционал полезен при обработке директив node/lamp),
  • внутрифункциональная оптимизация, связанная с использованием малого числа буферных локальных переменных взамен локальных переменных, заданных программистом.

Примером удаления бесполезных фигурных скобок может служить замена кода

void function(){
    int a;
    {
        a = 1;
    }
}

следующим кодом

void function(){
    int a;
    a = 1;
}

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

int function(){
    int a = 1;
    int b = a + 3;
    return b;
}

будет заменен на

int function(){
    int _int_tmp0 = 1;
    _int_tmp0 = _int_tmp0 + 3;
    return _int_tmp0;
}

Примечание

Не производится оптимизация локальных переменных структур и переменных массивов.

Директивы import/export

В целях упорядочивания, структурирования и повышения удобочитаемости кода шейдеров в include-файлах используются директивы import и export. Они указываются в начале файла и должны выглядеть примерно следующим образом:

#import u_frame_factor u_quatsb u_quatsa u_transb u_transa a_influence
#import qrot

#export skin

Директива #import определяет набор идентификаторов, которые объявлены вне этого include-файла, но доступны для использования в нем. Имеется ограничение: такие идентификаторы должны быть обязательно объявлены где-либо выше места подключения include-файла.

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

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

Идентификаторами могут быть как имена переменных, так и имена функций. По умолчанию при отсутствии директив import/export считается, что include-файл не использует внешние объявления и не предоставляет пользование внутренними.

Рекомендации и ограничения

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

  1. Обязательное использование специальной директивы #var для описания констант, определяемых движком в момент запуска. Например:
#var AU_QUALIFIER uniform
AU_QUALIFIER float a;

Синтаксис здесь схож с директивой #define. Смысл директивы #var в том, чтобы определяемое ею значение позволило распарсить исходный шейдер. Что это будет конкретно (например, „uniform“ или „attribute“ в примере выше), не важно, т.к. на этом этапе оно все равно неизвестно. Однако, желательно указывать более-менее подходящее описание, а не что-то совершенно произвольное.

Примечание

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

  1. Использование при необходимости директив import/export.
  2. Не следует перегружать встроенные функции, только пользовательские.
  3. Не следует объявлять переменные с именем одной из встроенных функций, либо main (даже если это не приводит к ошибке).
  4. Нельзя использовать директивы #var и #define для замены отдельных символов в таких операторах, как: «++», «–», «*=», «/=», «+=», «-=», «==», «<=», «>=», «!=», «&&», «||», «^^».

Например:

#var EQUAL =
...
a *EQUAL b;
...
  1. Использование директивы #include, не должно приводить к неоднозначности при обфускации содержимого include-файла. Это может произойти в том случае, когда один и тот же файл включается в несколько разных шейдеров, и в каком-то из них могут повлиять определенные выше директивы, вроде #var или #define. Также не стоит использовать в include-файле необъявленные функции и переменные.
  2. Использование вложенных include’ов или множественного включения одного и того же include’a в один и тот же шейдер не поддерживается.
  3. К неработоспособности шейдера может привести нетривиальное использование препроцессинга, например, создающее невалидный GLSL-код:
#if TYPE
void function1() {
#else
void function1(int i) {
#endif
    ...
}
  1. Не следует объявлять переменные с именами вида node_[NODE_NAME]_var_[IN_OUT_NODE], где NODE_NAME — название некоторой ноды, IN_OUT_NODE — название одного из входов или выходов ноды.
  2. Не разрешается множественное использование одной и той же директивы #nodes_main, #nodes_global или #lamps_main в одном шейдере.
  3. Директивы #nodes_main, #nodes_global и #lamps_main рекомендуется использовать в том же файле, в котором содержится описание шейдерных нод, например, в одном и том же include-файле - это необходимо для корректной валидации шейдеров.

Расширения WebGL

Работа обфускатора может зависеть от используемых WebGL-расширений, если они каким-либо образом влияют на шейдерный язык. На данный момент поддерживаются следующие расширения:

  • OES_standard_derivatives

Ошибки компилятора

В случае ошибки компилятор выведет соответствующее сообщение в консоли.

Перечень возможных ошибок:

Сообщение об ошибке Причина
Error! Ambiguous obfuscation in include file „FILE_NAME“. Ошибка! Неоднозначная обфускация include-файла FILE_NAME.
Error! Extension NAME is unsupported in obfuscator. File: „FILE_NAME“. Ошибка! WebGL-расширение с именем NAME, использованное в файле FILE_NAME, не поддерживается обфускатором.
Error! Include „FILE_NAME“ not found. Ошибка! При подключении не найден include-файл FILE_NAME.
Error! Undeclared TYPE: „NAME“. File: „FILE_NAME“. Ошибка в файле FILE_NAME. Необъявленный идентификатор типа TYPE (переменная, функция, структура, …) с именем NAME.
Error! Undeclared TYPE: „NAME“. Importing data missed. File: „FILE_NAME“. Ошибка! Необъявленный идентификатор типа TYPE (переменная, функция, структура, … ) с именем NAME. Отсутствует объявление идентификатора, требуемого в include-файле FILE_NAME согласно директиве #import.
Error! Undeclared TYPE: „NAME“. Possibly exporting needed in include file „INCLUDE_NAME“. File: „FILE_NAME“. Ошибка в файле FILE_NAME. Необъявленный идентификатор типа TYPE (переменная, функция, структура, …) с именем NAME. Возможно требуется разрешить его экспорт в include-файле INCLUDE_NAME.
Error! Undeclared TYPE: „NAME“. Possibly importing needed. File: „FILE_NAME“. Ошибка! Необъявленный идентификатор типа TYPE (переменная, функция, структура, … ) с именем NAME. Возможно требуется указать его как импортируемый в include-файле FILE_NAME.
Error! Unused export token „NAME“ in include file „FILE_NAME“. Ошибка! В include-файле FILE_NAME разрешен для экспорта необъявленный идентификатор с именем NAME.

Error! Using reserved word in TYPE „NAME“. File: „FILE_NAME“. Ошибка в файле FILE_NAME. Использование зарезервированного слова при объявлении идентификатора типа TYPE (переменная, функция, структура, …) с именем NAME.
Error! „all“ extension cannot have BEHAVIOR_TYPE behavior. File: „FILE_NAME“. Ошибка! Директива #extension, указанная для всех (all) WebGL-расширений в файле FILE_NAME, не поддерживает поведение BEHAVIOR_TYPE.
Syntax Error. ERROR_MESSAGE. File: FILE_NAME, line: LINE_NUMBER, column: COL_NUMBER. Ошибка синтаксиса в строке LINE_NUMBER, столбце COL_NUMBER при парсинге шейдера FILE_NAME. Исходное описание ошибки приведено в ERROR_MESSAGE. В сообщении прилагается листинг кода в окрестности соответствующей строки (следует учитывать особенность pegjs-парсеров, указывающих чуть далее места, вызвавшего ошибку).
Warning! Function „NAME“ is declared in [include ]file FILE_NAME, but never used. В файле FILE_NAME объявлена функция NAME, которая нигде не используется.
Warning! Include file „FILE_NAME“ not used in any shader, would be omitted! Include-файл FILE_NAME не используется ни в одном из шейдеров, поэтому будет исключен из закомпиленной версии.
Warning! Unused import token „NAME“ in include file „FILE_NAME“. Идентификатор с именем NAME импортируется в include-файле FILE_NAME, но нигде не используется.
Warning! Variable „NAME“ is declared in include file FILE_NAME, but never used. В файле FILE_NAME объявлена переменная NAME, которая нигде не используется.

Обновление переводов аддона

При необходимости обновить все существующие .po файлы, запустите скрипт translator.py из директории SDK/scripts без параметра:

> python3 translator.py

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

> python3 translator.py ru_RU

Для просмотра списка поддерживаемых языков вызовите скрипт следующим образом:

> python3 translator.py help

При вызове скрипта в любом случае будет обновлен файл empty.po.

После обновления .po файлы могут быть отредактированы/переведены.