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

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

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

Используется 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 addon: 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

сборка документации (PDF-версия)

JSDoc 3

устанавливается через NPM (npm install -g jsdoc)

сборка документации (документация на API)

PEG.js

из исходных текстов PEG.js

сборка препроцессора шейдеров

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

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

init_

создание абстрактного объекта

create_

создание конкретного объекта

update_

обновить состояние имеющегося объекта

attach_/detach_

добавить/удалить временное свойство к объекту

append_/remove_

добавить/удалить временное свойство к уже существующим подобного рода

insert_/pop_

добавить/удалить элемент массива (доступ по индексу места)

switch_
switch flag’s binary value
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 файлы могут быть отредактированы/переведены.