Вот решил поведать о библиотеке, которую написал недавно. Возможно кому-то и пригодится.
Это 2D framework на языке Rust для рендеринга непосредственно в Linux Frame Buffer /dev/fb0.


Задача была — на Raspberry Pi выводить на экран / телевизор простые 2D сцены. Raspberry Pi работает под управление собранного при помощи YoctoProject custom headless Linux. Window Managers отсутствуют, так же, как и OpenGL. Остается только Frame Buffer.


В случае с Frame Buffer обрабатывать приходится каждый пиксель. Так как я для Raspberry Pi пишу в основном на GoLang, то решил написать библиотеку на Go. Очень быстро понял что Go не подходит мне по производительности. Массивные операции с памятью он не смог выполнить за разумное время.


Тогда я обратил внимание на Rust, который выполнял похожие тесты намного быстрее.


Итак, библиотека разработана на Rust. Так как, это мой первый проект на Rust, сильно не пинайте, но комментарии приветствуется.


Как в любой 2D библиотеке, здесь есть три главные сущности: сцены, ноды и спрайты.
Сцена — это контейнер для визуальных объектов. Нод — это виртуальный объект, наделенный определенными свойствами, такими как местоположение, размер и т.д. Ноды могут быть вложенными.
Каждый нод содержит визуальный объект, так называемый спрайт. Спрайты бывают нескольких видов. Сейчас поддерживаются спрайты типа RectSprite, TextureSprite, TextSprite.


RectSprite — прямоугольник определенного цвета. RectSprite без цвета обычно используется для группировки других дочерних нодов. RectSprite соответствует XML тегу <box>


TextureSprite — объект для рендеринга картинки. На данный момент поддерживается только PNG (RGBA). TextureSprite соответствует XML тегу <image>


ТехтSprite — объект для рендеринга одной строки текста выбранным шрифтом и размером. Есть автоматическая поддержка RTL. ТехтSprite соответствует XML тегу <text>


В дополнении к атрибутам местоположения и размера каждый нод имеет атрибуты gravity и anchor. Это помогает точно расположить объект внутри другого объекта. Все атрибуты связанные с размером и местоположением задаются в процентах относительно родительского нода. Такой подход позволяет достичь наилучшего масштабирования на экранах разного размера и пропорций.


Создать сцену возможно, как программным способом, так и создать описывающий сцену xml файл.


  1. Программный способ создания и запуска сцены.


    let mut fb = fb2d::screen_writer_for_framebuffer("/dev/fb0")?;
    fb2d::set_graphics_mode();
    
    let mut scene = fb2d::scene::Scene::new();
    
    let background_sprite = RectSprite::new();
    let background_node = Node::new_rect_node(FLOAT_RECT_FULL, background_sprite);
    
    let sprite1 = RectSprite::new();
    let mut node1 = Node::new_rect_node(
        FloatRect {
            pos: FLOAT_POS_ZERO,
            size: FLOAT_SIZE_HALF,
        },
        sprite1,
    );
    node1.anchor_point = ANCHOR_POINT_TOP_LEFT;
    
    let sprite2 = TextureSprite::new_for_texture("image.png");
    let mut node2 = Node::new_texture_node(
        FloatRect {
            pos: FLOAT_POS_ZERO,
            size: FloatSize {
                width: 0.7,
                height: 0.7,
            },
        },
        sprite2,
    );
    node2.anchor_point = ANCHOR_POINT_CENTER;
    
    let mut sprite3 = TextSprite::new();
    sprite3.text = String::from("Hello, World !!!");
    sprite3.gravity = GRAVITY_CENTER;
    sprite3.height = 0.2;
    let node3 = Node::new_text_node(
        FloatRect {
            pos: FLOAT_POS_ZERO,
            size: FLOAT_SIZE_FULL,
        },
        sprite3,
    );
    
    scene.add_node(node2, node1.key);
    scene.add_node(node1, background_node.key);
    scene.add_node(node3, background_node.key);
    scene.set_root_node(background_node);
    
    scene.writer = Some(Box::new(fb));
    
    scene.run();

  2. Создание сцены при помощи XML файла.

<scene color="#ffa500">
    <box pos="0" size="95% 95%" anchor-point="0.5 0" color="#F0C0C0C0">
        <text pos="0 -40%" size="100% 10%" anchor-point="0.5 0" height="100%" text="???? Hello Привет" font="Times New Roman.ttf" color="red"/>
    </box>
    <box pos="0%" size="25% 25%" anchor-point="0 1" color="olive" alpha="0.5">
        <image pos="0" size="100% 100%" anchor-point="0 1" image="image1.png" />
    </box>
</scene>

Сцена создается указанием на каталог или zip файл, содержащий scene.xml. Зависимые файлы картинок и шрифтов должны находится в этом каталоге или zip файле.


   match fb2d::scene::Scene::new_from_bundle("assets/scene1") {
        Ok(mut scene) => {
            let mut fb = fb2d::screen_writer_for_framebuffer("/dev/fb0")?;
            fb2d::set_graphics_mode();

            scene.writer = Some(Box::new(fb));

            scene.run();
        }
        Err(e) => eprintln!("Error: {:?}", e),
    }

Пример получаемого изображения.
Существует возможность рендерить фрейм в PNG файл для отладки.



GitHub репозиторий.


Любая помощь (Contribution) приветствуется. Есть много работы по оптимизации рендеринга, и добавлению новых возможностей, таких как анимация.

Комментарии (8)


  1. mersinvald
    30.08.2018 08:47
    +2

    Докинь картиночек результата в статью, интересно посмотреть как оно выглядит :)


    1. mark2b Автор
      30.08.2018 09:39

      Спасибо, добавил.
      Существует возможность рендерить фрейм в PNG файл для отладки.


  1. multiadmin
    30.08.2018 09:17

    А с помощью какого IDE создавалась библиотека? Судя по «fb-2d.iml» в .gitignore, что-то от JetBrains?

    Как обстоят сейчас дела с отладкой кода на Rust?


    1. mark2b Автор
      30.08.2018 09:20

      Да, я использую плагин Rust для IntelliJ IDEA. Очень удобно. Отладчика пока нет, насколько я знаю.


      1. ozkriff
        30.08.2018 09:46

        Отладка в IntelliJ-Rust доступна при использовании CLion — первый пост, второй пост (гифка). В vscode отладка через LLDB расширение доступна.


        В обоих случаях "есть нюансы", но в целом работает.


  1. ozkriff
    30.08.2018 11:14

    Советую настроить CI с автопрогоном свежего rustfmt и clippy (пример конфига) — сильно облегчает жизнь, особенно если кто-то PRы будет засылать.


  1. zelyony
    30.08.2018 15:55

    http://pixman.org/ с ассемблерными оптимизациями под ARMv6/NEON… даже если не скомпиляется, можно частично выдрать полезный код


    Вроде как, Малинка поддерживает OpenGL
    из вики > At least the VideoCoreIV-AG100-R found e.g. in the Raspberry Pi 1, 2 and 3, is documented to fully support OpenGL ES 2.0 and OpenVG 1.1.
    https://eltechs.com/how-to-enable-opengl-on-raspberry-pi/


    1. mark2b Автор
      30.08.2018 16:24

      Разумеется Raspberry Pi поддерживает OpenGL. Но задача была, как не перегружать малинку тяжелыми процессами типa X-Server или разными Window Managers, так и обойтись практически без зависимостей.
      В Rust существует 2D библиотека поверх OpenGL или других backends. Piston
      http://www.piston.rs