BT — тулчейн баннерной разработки

Ранее я обещал рассказать о своем новом проекте, не связанном с D — выполняю обещание. Волею судеб несколько лет назад я стал профессиональным баннермейкером. Это разновидность фронтенд-разработки на стыке с анимационным дизайном — чаще всего я делаю HTML-баннеры и виджеты с разнообразной анимацией, эффектами и интерактивными механиками. Поскольку почти все рекламные сети имеют ограничение для баннеров по весу (и довольно серьезное — обычно 150 кб), эта профессия еще и пересекается в какой-то степени с демосценой, где на первом месте — искусство минимизировать информацию и генерировать ее процедурно.

Баннеры — это обычные HTML-странички, которые показывают пользователю рекламный сюжет и содержат ссылку на сайт рекламодателя. Они могут быть изготовлены при помощи самых разных инструментов, в том числе визуальных (Adobe Animate, Google Web Designer), но самое гибкое решение — писать непосредственно на HTML и JavaScript, рисуя графику либо обычными элементами DOM, либо через canvas. Благодаря отказу от Animate, вы не привязаны к его JS-библиотеке, которая сама по себе сжирает много веса, если ее нужно приложить к баннеру локально. Однако для отрисовки мало-мальски сложной анимации одним CSS вы не обойдетесь, и вам нужны такие библиотеки, как GSAP, Anime.js и др. — благо, весят они совсем немного. Основная сложность — уместить все ресурсы баннера в те самые 150 кб, что порой представляет нетривиальную задачу.

Для решения этой и многих других задач, которые возникают при разработке HTML-баннеров, я написал на Node.js комплект инструментов BT (Banner Toolchain). Его идея уходит корнями во внутренний инструментарий компании SmartHead, которым я пользовался три года и решил переделать полностью с нуля, уже в качестве независимого проекта с более эффективной реализацией большинства фич.

BT — это локальный сервер, предназначенный для разработки одностраничных сайтов с упором на минимизацию веса всех ресурсов. Например, в нем есть встроенный оптимизатор изображений, генератор CSS-анимации, функция записи баннера в GIF и видео, а также сборщик для подготовки баннера под все популярные рекламные площадки и упаковки в архив.

(далее…)

OpenSource и выгорание. Что будет, если все махнут рукой?

В последнее время часто говорят о выгорании мейнтейнеров открытых проектов. Люди теряют интерес к обновлению и поддержке своих старых проектов, в результате чего многие (если не большинство) из библиотек в публичных репозиториях превращаются в abandonware — необновляемое заброшенное ПО. Это становится серьезной проблемой для проектов, зависящих от подобных библиотек, так как в заброшенном коде могут быть уязвимости. Например, при работе с NPM частенько можно видеть критические уязвимости в некоторых пакетах, причем они висят годами, так как никто не может создать новый релиз, кроме мейнтейнера, даже если ошибку легко исправить. Решения звучат разные: ввести некую форму демократии в управление проектами, создать автоматизированную систему финансирования для мотивации мейнтейнеров и пр. На мой взгляд, тут имеет место быть классический человеческий фактор, и потому проблему нельзя исправить чисто техническими и формальными методами — нужна смена парадигмы в головах, оздоровление мышления. Проблема не в технологии, а в психологии.

Потеря интереса к проекту, над которым вы работаете длительное время без материальной или моральной компенсации, — ситуация вполне ожидаемая. Далеко не всегда OpenSource-проект превращается в дело жизни, когда автор сам активно использует свой код на работе, в бизнесе или хобби — и, следовательно, имеет личную заинтересованность в его поддержке. Некоторые библиотеки и программы, которые выкладываются на GitHub, пишутся для решения разовой задачи, после чего автор забрасывает разработку как бесперспективную и неинтересную. Но чаще всего факторы, по моим наблюдениям, следующие:

  • Переход в другую профессиональную сферу, на другую платформу, другой стек технологий. Поддерживать проект на языке, которым вы уже активно не пользуетесь и который вам не нужен в резюме, становится довольно напряжно и выглядит как путь в никуда;
  • Смена приоритетов в личной жизни. Не все могут позволить себе выкраивать львиную долю свободного времени на OpenSource, жертвуя отношениями, семьей, друзьями, другими увлечениями помимо программирования;
  • Сообщество вокруг проекта становится токсичным. Неадекватные требования пользователей, наплыв «доброхотов» с советами, как надо все переделать, всевозможные тролли, хамы, а также люди, пытающиеся применить ваш проект не по назначению и регулярно изливающие на вас фрустрацию по поводу своих неудач;
  • Автор в принципе не видит смысла продолжать работу над проектом, так как это по совокупности причин не делает его счастливее. Играет роль социальная атмосфера и функция, которую человек выполняет в обществе. Для счастья человеку нужен смысл — и смысл жизни в целом, и смысл любой конкретной деятельности. Потеря смысла — катастрофа.

Выгорание — это реакция психики на стресс от ответственности, на эмоциональное перенапряжение. Следовательно, чтобы решить эту проблему, и разработчики, и пользователи должны пересмотреть свое отношение друг к другу. Стоит признать: пользователи слишком многого хотят бесплатно, а разработчики берут на себя слишком много бесплатной ответственности. Прогресс дается человечеству отнюдь не даром, как всем, наверное, кажется — происходит эксплуатация энтузиазма. Я предлагаю следующие тезисы, напоминающие о простых истинах и базовых фактах, касающихся социальных аспектов OpenSource — скопируйте их себе, распечатайте, повесьте на стену, делайте что хотите, главное — держите в голове:

  1. Вы вправе самостоятельно распоряжаться своей жизнью, временем и усилиями
    Цель не оправдывает средства, когда речь идет о жизни и здоровье. Вы можете строить планы и формулировать цели, но вы не обязаны жертвовать чем-либо — и никто не вправе требовать от вас жертв. Вы не обязаны делать то, что вам неинтересно или трудно.
  2. Ваш вклад в свободное ПО не определяет вас как личность
    Управление свободными проектами — лишь один из множества видов полезной деятельности, которые делают человека ценным для общества. Не надо думать, что если вы устали, утратили мотивацию писать и сопровождать свободный код, вы стали хуже как человек. Нет ничего плохого в том, чтобы переключаться, уделять больше внимания своему здоровью и благополучию, учиться чему-то новому, пробовать новые для себя занятия и виды творчества. Вы не раб сообщества СПО и не прикованы к своим репозиториям цепями.
  3. Свободное ПО — это подарок от разработчиков, а не их долг перед кем-то
    Скачав из Интернета архив со свободным кодом, вы получаете в свое распоряжение только этот код и ничего более! Вы не получаете право на время и усилия автора этого кода. Вы входите с автором в отношения лицензиара и лицензиата, которые прописаны в лицензии (прошу прощения за тавтологию). А в любой свободной лицензии заглавными буквами написано — никаких гарантий.
  4. Написав и выложив код, вы не становитесь автоматически ответственным за судьбу этого кода
    Ответственность возникает только по закону и добровольно заключенному договору. Вы не обязаны исправлять баги и вообще хоть как-то реагировать на багрепорты. Вы не обязаны что-то обновлять, «своевременно» выпускать релизы, поддерживать «в актуальном состоянии» пакеты в реестрах. Нигде такая ответственность не прописана, ни по какому закону она не возникает — просто нет ее от слова совсем.
  5. Все эти репозитории, VCS, менеджеры пакетов — один большой общественный эксперимент
    Никто не может гарантировать, что экосистема, основанная на чистом энтузиазме, будет стабильно работать фоном. Тот факт, что кто-то построил на основе СПО свой бизнес, начал зависеть от мейнтейнеров, которые чинят баги, реагируют на просьбы и выпускают обновления — это исключительно его проблема. Верить, что автор библиотеки никогда ее не забросит — убийственно наивное допущение. Нужно перестраховываться от этого риска, а не взваливать моральную ответственность на автора.

И напоследок, на правах имхо. Студенты-миллениалы, с небывалым интересом подхватившие идеи Ричарда Столлмана и Эрика Реймонда для того, чтобы изменить мир, сделали слишком много и получили взамен слишком мало — а то и в чем-то потеряли. Они положили молодость на то, чтобы приложения могли создаваться, как конструктор, из готовых блоков, доступных бесплатно — и это породило многомиллиардную индустрию, изменившую всю нашу жизнь. Но что в итоге? Да, мы теперь можем заказать еду в кафе при помощи мобильного приложения. Да, у нас есть соцсети и мессенджеры, электронный банкинг и кассы самообслуживания, голосовые помощники и системы умного дома. Но разбогатели на этом единицы, все миллиарды ушли в карманы Джеффов Безосов и Марков Цукербергов, а остальные — в том числе и разработчики свободного кода — вынуждены работать «на дядю» как и раньше, десятилетиями выплачивать ипотеку и лишь мечтать об образе жизни, который мог себе позволить рядовой обыватель XX века безо всяких компьютеров и приложений. А государство, тем временем, благодаря свободным технологиям получило небывалую власть: оно следит за людьми через камеры с распознаванием лиц, требует QR-код при входе в магазин во время пандемии, ловит инакомыслящих при помощи геолокации, соцсетей и все тех же «умных» систем обработки данных, созданных на основе бескорыстного труда идеалистов — на фундаменте для глобального цифрового рая строится глобальный цифровой концлагерь. Совсем не такой новый мир мы себе представляли, когда создавали что-то новое!

Вечно так продолжаться, конечно, не может. Если мир ответил на ваш благородный порыв столь цинично, то ответом должен быть цинизм в квадрате. Если общество не захочет меняться, если не будет оздоровления, то никто не будет больше ничего создавать. Вчерашние энтузиасты, которым никто даже не сказал «спасибо» (а лишь ущемили в правах), плюнут на все и уйдут в себя. Они будут стоять и смотреть, как рушится построенное ими здание.

Передаем данные из GIMP в Blender

Не так давно столкнулся с интересной задачей при создании 2D-анимации в Blender: мне нужно было сделать плоскую сетку по форме объекта из PNG-изображения с прозрачным фоном. На обычную плоскость ее натянуть нельзя, так как предполагалось, что объект будет деформироваться при помощи скелета и shape keys. И таких сеток нужно было создать довольно много. Создавать их вручную, расставляя вершины по контуру картинки, как-то очень уж трудоемко — захотелось этот процесс как-то оптимизировать. И тут я вспомнил, что GIMP умеет преобразовывать маски в кривые, которые затем можно сохранить как SVG и импортировать в Blender. Осталось лишь заскриптовать эту последовательность действий!

Я решил, что переносить SVG вручную из одной программы в другую я тоже не хочу — пусть будет условно одна-единственная кнопка, по нажатию на которую слой из GIMP переносится в текущий открытый проект Blender. Подобное взаимодействие двух приложений можно реализовать при помощи технологий RPC (remote procedure call) — в частности XML-RPC, который позволяет через HTTP на клиенте вызвать серверную функцию, передав ей параметры, и затем получить результат. Преимущество XML-RPC в том, что он полностью скрывает транспортный механизм такого вызова — в скриптовых языках он выглядит просто как обычный вызов функции. Сервером я решил сделать плагин для Blender, клиентом — плагин для GIMP. Оба плагина я написал на Python, где протокол XML-RPC реализован в стандартной библиотеке. В GIMP и Blender используются разные версии Python, поэтому код работы с XML-RPC немного отличается.

Серверная часть выглядит достаточно тривиально: нужна лишь функция, которая принимает на вход строку, содержащую SVG — эта функция регистрируется как серверная функция в объекте SimpleXMLRPCServer:

import os
import bpy

import threading
import tempfile
from xmlrpc.server import SimpleXMLRPCServer

HOST = "127.0.0.1"
PORT = 8000

def svg_to_curve(svg:str):
    tmp = tempfile.NamedTemporaryFile(delete=False, mode="w")
    tmp.write(svg)
    tmp.close()
    bpy.ops.import_curve.svg(filepath=tmp.name, filter_glob='*')
    os.unlink(tmp.name)
    return {}

def launch_server():
    server = SimpleXMLRPCServer((HOST, PORT))
    server.register_function(svg_to_curve)
    server.serve_forever()

(для краткости я опустил служебный код для регистрации плагина)

Проблема возникает лишь в момент импорта SVG — Blender умеет импортировать только по файловому имени, поэтому пришлось сохранить строку во временный файл. Выглядит не очень элегантно, но работает.

На стороне GIMP делается следующее: текущему слою создается маска из альфа-канала, из маски создается выделение (gimp_image_select_item), из выделения, в свою очередь — кривая (plug_in_sel2path). Кривая экспортируется в SVG (gimp_vectors_export_to_string), а затем мы просто вызываем удаленную функцию svg_to_curve, после чего удаляем все служебные объекты.

import xmlrpclib
from gimpfu import *

def export_svg(svg):
    proxy = xmlrpclib.ServerProxy("http://localhost:8000/")
    try:
        proxy.svg_to_curve(svg)
    except xmlrpclib.Fault as err:
        pdb.gimp_message(err.faultString)

def layer_to_blender_curve(image, layer):
    if not pdb.gimp_item_is_group(layer):
        mask = layer.mask
        if not mask:
            mask = layer.create_mask(ADD_ALPHA_TRANSFER_MASK)
            layer.add_mask(mask)
        pdb.gimp_image_select_item(image, CHANNEL_OP_REPLACE, mask)
        path = pdb.plug_in_sel2path(image, None)
        pdb.gimp_selection_none(image)
        
        vector_name = pdb.gimp_path_list(image)[1][0]
        vec = pdb.gimp_image_get_vectors_by_name(image, vector_name)
        vec.name = "mask_path"
        
        svg = pdb.gimp_vectors_export_to_string(image, path)
        export_svg(svg)
        
        pdb.gimp_image_remove_vectors(image, vec)
        pdb.gimp_layer_remove_mask(layer, 0)

Ошибки, которые могли возникнуть в процессе передачи данных, удобно выводить в лог функцией gimp_message.

Исходники плагинов вы можете найти в репозитории https://github.com/gecko0307/image2curve.

Недостатком данного решения является то, что на стороне Blender будет постоянно работать HTTP-сервер на localhost:8000, так что вы в это время не сможете привязать к этому порту ничего другого. В Python есть способы получить случайный незанятый номер порта, чтобы не конфликтовать с другими серверами, однако в этом случае придется как-то передать порт в GIMP, что, как мне кажется, несколько усложняет весь процесс и добавляет лишнюю точку отказа.

Ссылки

У меня много разных сайтов, ресурсов и аккаунтов в соцсетях, но я нечасто их рекламирую — вероятно, не все мои проекты известны читателям этого блога. В этом посте привожу исчерпывающий список ссылок на все, что актуально на сегодняшний день.

Общее
https://timurgafarov.ru — портфолио с проектами и примерами дизайнерских работ (в будущем планирую сделать на этом домене сайт моего бизнеса);

Геймдев и разработка
https://github.com/gecko0307 — профиль на GitHub;
https://www.patreon.com/gecko0307 — страница на Patreon;
https://gamedev.timurgafarov.ru — блог о разработке игр на языке D;
https://timurgafarov.medium.com — блог о языке D на Medium;
https://www.youtube.com/channel/UCVaRZr3TpAWVP_kaqBfmoew — YouTube-канал;
https://xtreme3d.ru — портал, посвященный Xtreme3D, 3D-движку для Game Maker и Game Maker: Studio, а также, в целом, созданию 3D-игр на GM;
https://psxdev.xtreme3d.ru — страничка о разработке под первую PlayStation;
https://fps.xtreme3d.ru — журнал о цифровом творчестве;

Моделирование
https://www.artstation.com/gecko0307 — портфолио на ArtStation;
https://dobrofile.ru/gecko0307 — 3D-модели в продаже;
Также у меня до недавнего времени был профиль с моделями на CGTrader, но меня лишили статуса продавца в связи со всем известными событиями;

Искусство
https://art.timurgafarov.ru — основное художественное портфолио и краткая творческая биография;
https://www.deviantart.com/timurgafarov — профиль на DeviantArt;
https://www.behance.net/timurgafarov — профиль на Behance;
https://500px.com/p/gecko0307 — фототворчество;

Соцсети
https://twitter.com/gecko0307 — Twitter;
https://www.linkedin.com/in/timurgafarov — профиль на LinkedIn;
https://vk.com/tgafarov — страница ВКонтакте;

Разное
https://kadath.fandom.com — википроект по вселенной Страны Снов Лавкрафта;
https://timurgafarov.ru/pixeltavern — фанатский микроблог, посвященный игре Margonem.

Deferred Texturing для ландшафта

Еще одной новинкой в Dagon 0.14 станет довольно интересная и нетипичная техника, которую я еще не встречал в готовых реализациях — Deferred Texturing (отложенное текстурирование) для ландшафта.

Когда у меня в движке появилась поддержка ландшафтов, я не стал особо мудрить и сделал для них простейший шейдер, к которому подключаются до четырех текстур, причем для них пришлось сделать специальные свойства у материала, что выглядело не очень эстетично с точки зрения дизайна API. Также этот шейдер не поддерживал текстуры шероховатости и металличности, что делало его неполноценным по сравнению с остальными компонентами рендера. Техника Deferred Texturing, описанная Натаном Ридом, практически идеально вписалась в мой пайплайн и полностью решает эти две старые проблемы.

Основная идея заключается в том, чтобы вынести процесс наложения каждой текстуры в отдельный проход. Чтобы не рендерить сетку ландшафта по нескольку раз, мы запекаем геометрические данные в G-буфер, как в обычном отложенном рендере — отличие в том, что нам при этом нужно сохранять только интерполированные вершинные нормали и текстурные координаты ландшафта.

Полученные буферы вместе с PBR-текстурами (карта нормалей, base color, roughness, metallic) скармливаем текстурирующему проходу, который рисует экранный квад и записывает значения уже в обычный G-буфер. Для вычисления финальных нормалей используются значения из буфера нормалей ландшафта (плюс глубина, которую я сохраняю как альфа-канал буфера нормалей — она нужна для реконструирования eyespace-позиции пикселя). PBR-текстуры сэмплируются UV-координатами из буфера. Чтобы экранный квад не перезаписывал значения, уже сохраненные в G-буфере, нужно отсекать пиксель по маске, которую я храню в Z-канале буфера текстурных координат — 0 означает, что в данной точке нет ландшафта, 1 — есть.

Текстурирующий проход срабатывает несколько раз подряд — по количеству слоев текстур, которые я храню в специальном многослойном материале, каждый слой которого сам является отдельным материалом. Проход рисует в режиме альфа-смешивания, альфа-канал для прохода хранится в слое как отдельная текстура. Что самое интересное, прозрачность можно анимировать/клиппить и таким образом создать эффект плавного перехода одной текстуры в другую — например, постепенного покрытия ландшафта снегом. Возможности открываются самые широкие!

Новая статья на Medium

Я написал статью «Almighty Alias» на Medium о моем любимом ключевом слове alias, основное назначение которого — определение псевдонимов к типам и объектам. Но на самом деле alias способен на большее, особенно в связке с шаблонами.

ImGui

В следующей версии Dagon появится экспериментальная интеграция популярного UI-тулкита ImGui на основе биндинга bindbc-imgui — очень интересный инструмент, я только начал его изучать, но уже многое нравится. Удивил большой выбор готовых виджетов.

Nuklear при этом никуда из движка не денется, оба тулкита будут сосуществовать в виде двух отдельных расширений.

Новости по проектам

Долго не писал в блог — не потому, что не о чем, а просто в последнее время психическое состояние не располагало к публичности. Ушел в себя, в рефлексию и размышления. Не могу отрицать, что энтузиазм по хобби-проектам у меня уже не тот, что раньше. Стало сложно сохранять прежний настрой, и вообще очень многие вещи в жизни отошли на второй план. Но я не бросаю D, не бросаю работу над движком — просто в данный момент пишу код небольшими итерациями, насколько хватает кратковременных состояний потока.

Из нового: практически доделал рефакторинг системы загрузки текстур в Dagon (ветка texture). Текстуры из DDS теперь загружаются напрямую, без создания промежуточных объектов SuperImage. Также 2D-текстуры и кубические карты объединены в один класс Texture, и работать с ними стало проще — например, загрузка карты окружения на стороне пользователя теперь выглядит одинаково как для кубической карты из DDS, так и для равнопромежуточной карты HDR. А еще появилась поддержка формата сжатия ASTC.

Обновил и выложил на GitHub свой старый программный растеризатор MiniGL. Попутно внес несколько улучшений — например, теперь конвейер поддерживает «шейдеры»: можно задать функции D, которые выполняются при обработке вершины и пикселя. Я не знаю, кому и для чего эта штука может пригодиться в 2022 году, но писать ее было весело, и код получился довольно наглядный и компактный — меньше 1000 строк, так что это как минимум хороший пример использования dlib.

И, наконец, около месяца у меня ушло на доработку одного интересного инструмента, не связанного с D, о котором я напишу подробнее в одном из следующих постов.

Texture Tools Exporter

Отличный бесплатный конвертер текстур от NVIDIA, о котором я узнал почему-то только недавно. Поддерживает системы сжатия BC1 (S3TC/DXT1), BC2 (S3TC/DXT3), BC3 (S3TC/DXT5), BC4/5 (RGTC), BC6/7 (BPTC), ASTC, а также несжатые 8-битные целочисленные форматы и 16- и 32-битные с плавающей запятой. Можно экспортировать как 2D текстуры, так и кубические карты. Список поддерживаемых контейнеров также внушителен: DDS, KTX2, OpenEXR, HDR и все обычные — PNG, JPEG, BMP, TGA, PPM и др.

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

Обращение к пользователям dlib, Dagon и других моих публичных проектов в связи с происходящими событиями

Я, Тимур Гафаров, создатель и мэйнтейнер dlib, Dagon, bindbc-wgpu, bindbc-newton, bindbc-soloud и др., в настоящее время живу в России без возможности в обозримом будущем покинуть эту страну.

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

Тем не менее, реальность такова, что разработчики из России в любой момент могут оказаться отрезанными от мирового сообщества СПО. Это серьезно повлияет на проекты, в которых они принимают участие. Я искренне надеюсь, что этого не произойдет. В противном случае я, к моему огромному сожалению, не смогу управлять разработкой dlib, Dagon и других моих пакетов в реестре Dub. В отсутствие сопровождения эти пакеты будут постепенно терять актуальность. Также у меня пока нет уверенности, что я смогу получать финансирование через Patreon и PayPal.

До ухудшения ситуации, пока у меня есть доступ к GitHub и реестру пакетов, я продолжаю сопровождение dlib и Dagon, в настоящее время уделяя этому минимум своего свободного времени. Я на неопределенный срок приостанавливаю разработку dlib 2.0 и новых версий Dagon из-за негативного психологического фона и неясной картины будущего. В случае катастрофического развития событий я не обещаю, что смогу посвящать проектам даже минимальные время и силы.

В связи с этим, я с сегодняшнего дня не рекомендую использовать dlib, Dagon и другие мои пакеты в качестве критических зависимостей в важных программных продуктах. Кто хочет взять активную разработку на себя, создать форк — буду только рад. Пожалуйста, не делайте pull request’ы с большим количеством коммитов, существенными изменениями и новыми фичами, которые мне придется читать и проверять — у меня для этого может физически не оказаться возможности. Если у меня сохранится доступ к GitHub, я, скорее всего, увижу ваш форк и сам сделаю слияние, как только смогу.

P.S. Я в срочном порядке выпустил Dagon 0.13.0.

P.P.S. Облачная папка с примерами Dagon (Sponsor Folder), которая была ранее доступна только для подписчиков на Patreon, теперь открыта для всех.