Разработчикам движка¶
Содержание
Стиль оформления кода¶
В движке применяется структурное программирование. Код организуется в модули. Подходы ООП не используются, классы не определяются, наследование не осуществляется и т.п.
Используется 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 | сборка 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 | сборка препроцессора шейдеров |
Названия функций и переменных¶
Рекомендуется при создании новых функций и переменных использовать следующие префиксы и суффиксы.
- 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-файлов, а также особенностями реализации компилятора гарантировать работоспособность полученного на выходе кода можно только при соблюдении ряда правил или ограничений на текст исходных шейдеров:
Обязательное использование специальной директивы
#var
для описания констант, определяемых движком в момент запуска. Например:
#var AU_QUALIFIER uniform
AU_QUALIFIER float a;
Синтаксис здесь схож с директивой #define. Смысл директивы #var в том, чтобы определяемое ею значение позволило распарсить исходный шейдер. Что это будет конкретно (например, ‘uniform’ или ‘attribute’ в примере выше), не важно, т.к. на этом этапе оно все равно неизвестно. Однако, желательно указывать более-менее подходящее описание, а не что-то совершенно произвольное.
Примечание
Для констант, используемых не в коде шейдера, а в выражениях препроцессинга, директива #var
не обязательна.
Использование при необходимости директив import/export.
Не следует перегружать встроенные функции, только пользовательские.
Не следует объявлять переменные с именем одной из встроенных функций, либо main (даже если это не приводит к ошибке).
Нельзя использовать директивы #var и #define для замены отдельных символов в таких операторах, как: “++”, “–”, “*=”, “/=”, “+=”, “-=”, “==”, “<=”, “>=”, ”!=”, “&&”, “||”, “^^”.
Например:
#var EQUAL =
...
a *EQUAL b;
...
Использование директивы #include, не должно приводить к неоднозначности при обфускации содержимого include-файла. Это может произойти в том случае, когда один и тот же файл включается в несколько разных шейдеров, и в каком-то из них могут повлиять определенные выше директивы, вроде #var или #define. Также не стоит использовать в include-файле необъявленные функции и переменные.
Использование вложенных include’ов или множественного включения одного и того же include’a в один и тот же шейдер не поддерживается.
К неработоспособности шейдера может привести нетривиальное использование препроцессинга, например, создающее невалидный GLSL-код:
#if TYPE
void function1() {
#else
void function1(int i) {
#endif
...
}
Не следует объявлять переменные с именами вида
node_[NODE_NAME]_var_[IN_OUT_NODE]
, гдеNODE_NAME
— название некоторой ноды,IN_OUT_NODE
— название одного из входов или выходов ноды.Не разрешается множественное использование одной и той же директивы
#nodes_main
,#nodes_global
или#lamps_main
в одном шейдере.Директивы
#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 согласно директиве |
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’. | Ошибка! Директива |
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 файлы могут быть отредактированы/переведены.