Сегодня речь пойдёт о том, как устроен графический UI Фантома.

(Что такое ОС Фантом можно узнать, прочитав вот эти статьи.)

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

Теперь же подошла пора сделать хоть несложные — но приложения, а значит — нужен UI. Да и вообще — система, будем откровенны, выглядела страшновато. А это нынче не в моде.

Что было в наличии на начало проекта UI? В принципе — немало.

Была, собственно, графика — видеодрайвер, оконная подсистема в режиме только отображения, bitmap шрифты, подсистема оконных событий (events), управление фокусом окон и сопутствующие примитивы.

Теперь по шагам и чуть подробнее.

Подсистема видеодрайверов умеет запустить по очереди функцию probe() нескольких драйверов, получить от них заявки на максимальное разрешение и битность цвета, плюс способность работать в режиме 2D акселератора. Система при этом требует минимум 24-битного цвета. На этом уровне мы имеем фреймбуфер (экран в памяти), мышь и примитивы bitblt нескольких типов.

Примитивы bitblt — были реализованы три базовых типа — полное копирование графики (с вырезанием прямоугольников), копирование с учётом бинарной прозрачности (пиксель или полностью прозрачный, или полностью непрозрачный) и z-buffer. То есть способность скопировать на экран только те пиксели, которые имеют z-координату больше, чем z-координата у имеющегося пикселя — для отработки частичного перекрытия окон.

Следующий слой функций — оконная подсистема. Тут есть понятие окна, декорации окна (рамка, title window с кнопками), x/y/z координаты окон и набор функций, которые отвечают за отрисовку окон на экран и управление их перемещением по всем осям.

Документация

Далее следуют события — очередь микрозадач, которые отрабатываются нижнеуровневым драйвером отрисовки и управления состоянием окон.

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

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

Сюда же попадает просто поток событий от мышки (нажали, потащили), клавиатуры (нажали, отпустили) и самой оконной системы (вторичные события — после переноса окна вверх перерисовать область экрана).

Отдельная задача на уровне потока событий — так называемый фокус. Сфокусированное окно получает поток событий от клавиатуры, да и вообще явно выделено на экране как точка адресации активности пользователя. Кроме очевидной задачи выбора окна для направления события эта система также информирует окна о потере фокуса, что иногда важно.

Документация

Следующий уровень — графические примитивы для рисования на окне.

Здесь существуют два основных варианта реализации. Старый, экономный — когда окно не хранит копию того, что в него нарисовано. Если такое окно стёрли, и нужно нарисовать стёртое снова (например, окно вернули на экран из-за края), то окно зовёт функцию из своей программы, и эта функция должна нарисовать всё, что надо. Это типовая и ужасно неудобная по массе причин модель. В Фантоме выбрана вторая — у каждого окна есть битмеп, в котором нарисовано содержание окна на данный момент. Графическая система всегда может обратиться к этой копии и обновить её на экране, не дёргая программу пользователя.

Обратим внимание, что окно, принадлежащее пользовательской программе (а не ядру) в Фантоме, конечно, является персистентным, хранится в персистентной памяти и после перезагрузки ОС сохраняет всё в нём нарисованное. Это, кстати, на удивление полезно и упрощает прикладной код местами до неприличия.

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

На этом богатство графической подсистемы на начало проекта «Новый UI Фантома» и заканчивалось. В принципе, этого джентльменского комплекта хватало на многое, но только в сторону пользователя. Без ввода.

Точнее сказать, была рудиментарная поддержка понятия «кнопка», но только мышкой, только в тулбаре и только чтобы закрыть окно. :)

Задача на развитие стояла так:

  • TrueType. Без этого уже стыдно.
  • Клавиатурные события и управление с клавиатуры. Хотя бы базово.
  • Подумать в сторону локализации раскладок — кириллицу как минимум, но заложить фундамент под смену раскладок.
  • Контролы — кнопки, радиокнопки, поля текста, лейблы, меню и проч.
  • Фокус контрола — выбор точки управления внутри окна.
  • Какой-то экранный компонент для управления окнами на экране. Таск бар?
  • Собственно изображения контролов и вообще какой-то дизайн UI — должно быть не так колхозно, как было.

А было так:

image

По дороге выяснилось, что нужен ещё и альфа-блендинг, то есть частичная прозрачность пикселей при наложении картинок. Ну и стало понятно, что пора трогать за вымя Юникод.

Подход к этому весу делится на три больших части: Дизайн, Трутайп, остальное.

Про дизайн, коротко: в Интернете есть бесплатные дизайны UI без злых требований по использованию. Три дня на поиски и отбор, бесконечное время на художественное выпиливание графических элементов.

Трутайп


Этого я боялся, но, как выяснилось, зря. Есть libfreetype, есть примеры применения, через два дня рендеринг векторных шрифтов вполне работал в тестовом режиме.

Впрочем, тут есть тонкости, и не весь путь пройден. А именно. Работа со шрифтами из ядра — есть. Шрифты при этом загнаны хардкодом в бинарник ядра. Это неизбежно для системного шрифта, но пользовательский код должен иметь свои механизмы загрузки. И хотя какие-то ФС в Фантоме, конечно, есть и будут, эта модель для него неестественна. Нужно уметь хранить шрифты в персистентных объектах и добывать их по сети.

Второе проще — лежбища бесплатных шрифтов есть в изобилии, да и своё организовать недолго.

А вот первое…

Вы, наверное, не знаете, но строковые переменные в Фантоме обладают неожиданным для непривыкших к персистентности программистов свойствами. Ими можно заменять файлы. Поток байт есть поток байт. Мало того, он ещё и по определению memory mapped — это же переменная. То есть, в принципе, то, что мы в обычной ОС храним в файле, в Фантоме можно просто положить в строковую переменную. Я так часто и поступаю — а компилятор языка Фантом даже имеет конструкцию — всосать файл в строковую константу. Так в userland Фантома проникают, например, битмепы. Но это тоже стыдноватый способ, потому что потом в рантайме эту переменную нужно отпарсить, чтобы получить операбельное представление объекта. Впрочем, что касается битмепов, то, к чести Фантомовской концепции, тут всё хорошо. Мы всасываем при компиляции графический файл в строку, при первом запуске Фантома она конвертируется в персистентный же бинарный объект типа битмеп, и он уже используется дальше после любого количества перезагрузок ОС и оригинального источника не требует. Так же надо бы сделать и со шрифтами, но это чуть менее банально. При работе векторный шрифт рендерится в растр, и хранить надо бы именно такие вот отрендеренные растры. Это не фокус и не проблема — они опять же могут быть сложены в Фантомовские объекты типа bitmap, но тут уже нужна какая-никакая инфраструктура — дерево хранения вида шрифт — начертание — размер — глиф (код UTF) — битмап глифа.

Это не то, чтобы сложно, но, видимо, задача следующего этапа. Пока шрифты растеризуются по факту обращения.

Документация

Юникод


Рендеринг шрифтов по определению предполагает работу с Юникодом. Это, конечно, хорошо, потому что надо же было когда-то начинать. По факту достаточно было снабдить рендерер конвертером из UTF-8 в UTF-32 (а это и есть номер глифа в шрифте), скачать шрифты с Кириллицей и эта часть локализации заработала. Более того, если мы хотим другие языки, то необходимо и достаточно заменить шрифт. Впрочем, выбранный базовый шрифт содержит немало — для европы точно хватит. Китаю потребуется замена шрифта, да.

Работа с клавиатурой


Тут вообще ничего не предвещало военных действий, но, паче чаяния, воевать пришлось. Выяснилось, что старый драйвер клавиатуры всё ещё… надеется увидеть железо от IBM PC XT. Да, прошлого века. Дело в том, что контроллер клавиатуры штатно умеет (умел!) конвертировать скан коды современных клавиатур (так называемый второй набор кодов) в тот, древний.

Выяснилось это потому, что из поздних QEMU такую конвертацию, видимо, наконец выкинули. Или случайно сломали. Но факт в том, драйвер работать отказался. С горя я за час с помощью какой-то матери портанул в Фантом драйвер из дружественной uOS. Только чтобы узнать, что у него та же проблема. Первый набор. Пришлось переписать таблицу скан-кодов и парсер. К старому драйверу я возвращаться не стал, и вот почему. Оказалось, что драйвер от uOS имеет более изящный интерфейс в систему. А именно — он возвращает в неё не, как это было принято, пару (код символа, скан-код кнопки), а один 32-битный UTF-32 символ. Оказывается, в UTF есть специальный диапазон кодов, выделенных для локального употребления, и их более чем хватает на все возможные функциональные клавиши. Работать с таким потоком событий в коде UI гораздо проще.

Мало того, на такую модель отлично легла локализация. Достаточно наложить сверху таблицу ASCII->UTF32 для нужного языка (набора символов), и ура — у нас есть кириллица. Ну — почти есть. Теперь бы надо или перекодировать это в UTF-8, или переделать на UTF-32 потроха некоторых частей UI. Этот момент я тоже пока отложил в низкий приоритет.

Контролы



Кнопки, радио, чекбоксы и прочие конкретные элементы UI.

Общая инфраструктура включает в себя:

  • Механизм хранения контролов в привязке к окну
  • Типовые элементы визуализации контрола — рамка, фон, текст, иконка и т.п.
  • Передача контролу событий и типовые схемы реакции (push/toggle)
  • Отслеживание событий мыши и hover state
  • Коллбеки и генерация вторичных событий для информирования об изменении состояния

Фокус контрола


Для того, чтобы контрол (кнопка, например) мог быть использован без мыши, нужны несколько вещей.

  • Возможность его выбрать с клавиатуры
  • Отображение этого выбора
  • Реакция на нужные клавиши
  • Детектирование потери фокуса.

Последнее сложнее всего.

Фактически контрол фокусируется как клавиатурой, так и мышью, причём это одна и та же сущность — если мы выбрали текстовое поле мышкой, то оно будет реагировать и на клавиши. Если после этого нажать TAB, право работы с клавиатурой уйдёт другому.

Отдельная задача состоит в том, что некоторые контролы могут быть собраны в группы и их состояние требуется обновлять связанным образом. Нажатие радиобатторна «отжимает» его соседей по группе.

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

То есть его связи с ядром хорошо бы минимизировать. Каждый пойнтер в неперсистентную память (собственно в ядро) после перезагрузки будет невалиден и его придётся восстановить. Это значит, что такой пойнтер не имеет права хранить информацию о состоянии контрола. Позиция курсора в виде целого числа — да. Пойнтер в буфер в ядре, чьё положение определяется позицией курсора — нет. Ну или да, только целое число всё равно есть и оно главнее. Это на практике не очень обременяет, но помнить надо.

Наконец, таск бар. Это такая штуковина внизу (сбоку, сверху) экрана, куда пользователь тычет, если потерял окно.

В принципе, это бы уже должно быть частью user land, но… ядро уже активно пользуется GUI, так что пока эта часть тоже будет внизу. Надеюсь, временно.

Итого


image

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

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


  1. merhalak
    22.11.2019 18:58

    Немного не понял: сочетания клавиш будут зависеть от активной раскладки клавиатуры или нет? Очень часто раздражает, к примеру, поведение vim: переключил раскладку и в командном режиме не тыкается ничего.


    Если это загасить на начальном этапе проектирования ОС, не придётся потом делать настройку в каждом приложении отдельно.


    1. dzavalishin Автор
      22.11.2019 21:08
      -1

      Вы очень правы, действительно раздражает. Учёл, модификаторы запрещают трансляцию.


      1. merhalak
        22.11.2019 21:51

        Мне кажется, стоит сделать возможность переключения в командный режим, чтобы со стороны приложения можно было бы выбирать, когда интерпретировать, как командный режим, а когда — как текстовый. Тот же vim командный режим имеет без всяких модификаторов.


        1. dzavalishin Автор
          23.11.2019 03:51

          В принципе, нет проблем отправить в приложение два кода — транслированный и чистый.


      1. Sabubu
        23.11.2019 02:21

        Решение не в модификаторах, и не в "режимах" (это боль), а в том, чтобы разделить событие нажатия клавиши и ввода символа. И иметь 2 кода — код клавиши в латинской раскладке для хоткеев (Ctrl + S независимо от раскладки) и Unicode код введенного символа.


        Далее, я рекомендую вам не завязываться на раскладки везде. В Линуксе, в иксах, например, вы должны при имитации ввода (искуственном создании событий нажатий клавиш) указать скан-код клавиши. Это причиняет боль разработчикам серверов вроде VNC, так как с клиента может прийти код отсутствующего в раскладке символа (иероглиф какой-нибудь), и где взять его скан-код на системе, в которой нет японской раскладки? Это приводит к хакам вроде "временно добавить в текущую раскладку японский символ с кодом 255, отправить событие keydown/keyup с кодом 255, убрать символ из раскладки". И вся эта жесть из-за плохого проектирования в иксах, так как это проектировали явно англоязычные разработчики, не понимающие проблем ввода на других языках.


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


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


        • А. побуквенный ввод текста в текстовом редакторе, самое банальное. Нужно получать от системы код Unicode символа, а также коды управляющих клавиш (стрелки, backspace и тд).
        • Б. хоткеи. Нужен не зависящий от раскладки код — скан-код либо латинская буква. Последнее лучше, так как не зависит от модели клавиатуры и аппаратной платформы (у экранных клавиатур нет скан-кодов).
        • В. виртуальная машина или эмулятор компьютера, игры. Требуется имитирование IBM-совместимой клавиатуры и получение "сырых" нажатий с минимумом воздействий или фильтрации. Например, нажатие shift + A просто должно передаваться в программу как нажатие shift + A и все, без учета раскладок, смены регистра букв, состояния капс-лока и тд.
        • Г. VNC-сервер, средства автоматизации — нужно вбрасывать в систему искуственные события нажатия клавиш, пришедших от клиента
        • Д. консолечка — нужно корректно обрабатывать комбинации вроде Ctrl + C или Esc + A.
        • Е. ввод текста на иностранных языках с использованием IME, а также со всяких умных экранных клавиатур (это когда мы печатаем, появляется список вариантов слова и мы его выбираем) — нужно по мере набора передавать слова или их части, которые могут откатываться или коммититься в редактор.

        Исходя из этого, как мне кажется, пригодилась бы такая структура события:


        keydown/keyup (нажатие клавиши):


        • modifiers
        • ibm_scan_code — аппаратный скан-код IBM (для эмуляторов и ВМ)
        • latin_code — код клавиши в латинской раскладке без учета модификаторов, например "S", "1" (для хоткеев)

        input (ввод символа, коммит слова из IME, нажатие управляющей клавиши):


        • modifiers
        • text (введенная буква или текст)
        • keysym (код управляющей клавиши вроде Enter, если нажата она. Для комбинаций вроде Ctrl + C в keysym копируется latin_code)

        typing (ввод пока не набранного до конца слова в IME или вирт. клавиатуре)


        • text

        Вернемся к сценариям:


        Физическая клавиатура генерирует события keydown/up, которые порождают события input. Виртуальная клавиатура, VNC-сервер или скрипты автоматизации генерируют события input, из которых воссоздаются события keydown/up. При этом ввод эмодзи, например, не позволит заполнить поля ibm_scan_code или latin_code в событии keydown, так как для эмодзи нет скан-кодов.


        А. Текстовый редактор использует только событие input, и берет либо поле text, либо код клавиши из keysym.
        Б. Для хоткеев используются поля latin_code и modifiers в событиях keydown/up. Либо keysym + modifiers в input (?)
        В. ВМ и эмуляторы, игры используют событие keydown/up и поле latin_code либо ibm_scan_code
        Г. Описано выше, вбрасываются события input, по которым воссоздаются keydown/keyup
        Д. Для распознавания нажатий вроде Ctrl + C используется событие input, поле keysym + modifiers, с помощью библиотечной функции, которая по событию возвращает подходящий консольный код. Либо используется событие keydown, поля latin_code + modifiers
        Е. Используются события typing и input


        Вот как-то так.


        И, конечно, не стоит изобретать свои коды клавиш, а стоит взять те же иксы или что-то еще готовое.


        1. dzavalishin Автор
          23.11.2019 04:03

          (Искреннее спасибо за подробный разбор. Всё очень по делу.)

          Из VNC не приезжают IBM scan codes, приезжают как раз иксовые коды.

          Сам по себе IBM scan code — штука мерзкая, а ближе к принтскрину и вообще сумасшедшая, не хочется его применять как опорный.

          Может быть, обойтись троицей?

          — Нетранслированный x11 code или ascii char без локализации
          — char транслированный по полной — если нажат ctrl, то 00-1F, если локальный кеймап, то кириллица/японица, что там — в UTF32. Короче, tty char
          — модификаторы

          Кажется, эта схема закрывает всё и хорошо ложится на VNC и X11.


          1. Sabubu
            24.11.2019 03:37

            Скан-коды я оставил исключительно для сценариев вроде DOS эмуляторов или вирт. машин, где нужно передавать внутрь скан-коды IBM-клавиатуры. Но сейчас я подумал, что в принципе DOS эмулятор может использоваться совместно с VNC или экранной клавиатурой, которые не знают ничего о скан-кодах, и может быть, имеет смысл опираться на latin_code + modifiers и из него формировать скан-коды IBM в таких приложениях с помощью готовой библиотечной функции. Плюс, у пользователя может стоять DVORAK-раскладка и в вирт. машину надо передавать переставленные местами скан-коды.


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


            Потому важно, на мой взгляд:


            • разделять события нажатия клавиш и события ввода символов или текста (типичный пример — IME, где текст коммитится только после выбора слова, а не при нажатии каждой клавиши или Compose key, где может быть несколько последовательных нажатий клавиш, которые вставляют один символ в итоге, например Compose + 1 + 2 вставляет ?)
            • всегда передавать как latin_code, который нужен для реализации горячих клавиш и управления в играх. Для сценариев вроде вирт. клавиатур или VNC, latin_code должен воссоздаваться из приходящих кодов символов (например, на основе самых популярных раскладок, хотя еще правильнее было бы, если бы VNC-клиент передавал и latin_code, и юникодный символ. Так как в VNC передаются юникодные коды, и так как VNC-сервер не знает раскладку на клиенте, он не может в 100% ситуаций корректно восстановить latin_code и корректно передавать нажатия управляющих комбинаций при любых комбинациях раскладок на клиенте и сервере. Это проблема в дизайне протокола VNC)
            • в событии ввода текста передавать введенный текст, который может быть как одним символов, так и целым словом при использовании IME (хотя, можно при выборе слова в IME передавать по событию на каждую букву, но передавать слово целиком в одном событии логичнее)

            Единственное, в чем я не уверен — надо ли передавать управляющие комбинации, не генерирующие текст (например, Backspace, Left, Ctrl + C) в событии input или нет (только в keydown/keyup). Если их не передавать все выглядит логичнее, но писать эмулятор консоли будет труднее, так как ему нужен и текст и управляющие клавиши и придется обрабатывать все события и разделять нажатия из них. Передача управляющих клавиш в событии input с пустым текстом решает эту проблему, хоть и делает дизайн событий менее красивым.


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


            Кстати, скажу вам еще одни грабли в иксах. Если вы назначаете какую-то горячую клавишу на комбинацию только из модификаторов (например, Ctrl + SHift для переключения раскладки), то вы не можете вводить другие комбинации с этими модификаторами, например Ctrl + Shift + A, так как переключение раскладки "проглатывает" нажатие клавиши Shift или Ctrl. Другой пример — использование как горячих клавиш одиночного нажатия на Win и Win + буква будет конфликтовать.


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


            1. dzavalishin Автор
              24.11.2019 13:58

              Ну вот то, что я написал выше, кажется, полностью подходит.

              keyCode — или код функциональной клавиши (тот же X11 code), или latin1 char. Используется именно как управляющая клавиша в контексте команды и для восстановления кода в эмуляторах. 1:1 соответствует клавише. Не модифицируется раскладкой.

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

              modifiers — все биты альтов и контролов, keyUp и прочие meta.


    1. kalininmr
      23.11.2019 19:13

      в vi эа ситуация интересно решается: сопоставление символов в разных раскладках.


      1. merhalak
        24.11.2019 02:43

        Это костыль. А еще и собственная переключалка раскладки в vi. Зачем? Просто — зачем? А если у людей три раскладки клавы? Это ведь просто "не проблемы первого мира". Так вопрос — почему в своих системах мы должны это не учитывать?


        Сколько проблем с раскладками не только в консоли, но и в линуксовом гуи?


        1. Sabubu
          24.11.2019 03:15

          К счастью для нас с вами, linux это опенсурс, а значит у нас есть возможность спроектировать и может даже внедрить более удачный стандарт.


          1. merhalak
            24.11.2019 03:16

            Не так: слишком много обратной совместимости с текущими решениями. Надо быть минимум RH/IBM, чтобы продвинуть такое.


  1. AntonSazonov
    22.11.2019 20:41

    1. Зачем прятать ссылки под спойлер?
    2. Подскажите, где можно увидеть реализацию rect_add? Никак не могу найти её в репозитории. Какую-то непривычную структуру он (репозиторий) у вас имеет…


    1. dzavalishin Автор
      22.11.2019 20:59

      1 — да, наверное, действительно незачем
      2 — github.com/dzavalishin/phantomuserland/blob/master/phantom/libwin/rect_cmp.c

      Структура имеет исторические корни. :)


  1. Dreamer_other
    22.11.2019 20:44
    +2

    А почему GUI не реализовали полностью в юзер спэйсе, зачем ядру знать про контролы?


    1. dzavalishin Автор
      22.11.2019 22:12
      -1

      Есть две причины.

      1. Был в Фантоме такой эксперимент, как реализация API KolibriOS. А там в апи входят контролы. Кстати, некоторые приложения Колибри запустить удалось, но подробных спек на апи нет, а разработчики на связь не вышли, так что он пока на паузе.

      2. Быстрее


      1. Siemargl
        22.11.2019 23:03
        +1

        В Колибри очень подробные спецки на АПИ, просто по причине их простоты.

        Заодно отмечу твое/ваше визионерство — 10 лет назад про персистентность ОС даже и не думали, а тут появились SSD/FlashMem/Intel идеи итп, что сделало идею ФантомОС [почти] реализуемой…

        Но не поддерживаю затею с VMкодом в ОС — она себя за это время дискредитировала — слишком большие затраты ресурсов (10х).


        1. dzavalishin Автор
          23.11.2019 05:29

          Не подробные. Когда начинаешь писать вылезает миллион вопросов, и ответов нет. Навскидку первый: как распаковать бинарник приложения. Имеющаяся документация на этот счёт ошибочна.

          VM будет закрыт JIT-ом.


          1. Siemargl
            24.11.2019 00:19

            Навскидку первый — в КолибриАПИ просто нет функции распаковки приложений. В молоко. Впрочем это тут оффтоп.

            Не знаю ни одного удачного JITa, все проигрывают AOT. Кстати, мой комментарий был про применения пары JIT/GC, т.к. интерпретация байт кода почти не требует ресурсов, только медленная.


            1. dzavalishin Автор
              24.11.2019 01:15

              1. Системный вызов номер 7. Запуск приложения. Я не нашёл никого, кто бы чётко понимал, как работает распаковщик. В итоге удалось сделать запуск только распакованных приложений.

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

              Если вдруг Вы в курсе, как оно должно быть устроено, то, может быть, подскажете? Код вот тут:

              github.com/dzavalishin/phantomuserland/blob/master/oldtree/kernel/phantom/elf.c#L131

              2. Сравнение JIT/AOT — тема сложная и небанальная.

              — Есть много критериев. Объём кода. Объём данных. Скорость работы. Объём данных — если на типовом сценарии вылезаем из кеша — может убить скорость.

              — Есть много сценариев. Монотонные операции и работа на сильно разных данных. Во втором случае JIT может давать катастрофический выигрыш за счёт перекомпиляции на ходу с учётом анализа фактического графа исполнения и направлений переходов.

              — Есть разный код. Ява способна свернуть константы на пачку функций внутрь цепочки вызовов и оптимизировать то, что статический линкер никак не может.

              И т.п.

              Ну и тупые тесты показывают, что jvm vs gcc даёт вполне сравнимые результаты. Где-то в пользу си, где-то в пользу Явы.

              www.stefankrause.net/wp/?p=4

              www.codenet.ru/webmast/java/javavscpp.php

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

              Другое дело, что и до джита Фантому ещё надо дожить. А вот, кстати, АОТ сделать проще.


  1. pda0
    22.11.2019 23:44
    +1

    Такую вещь давно хотел спросить. Допустим у меня сложное ресурсоёмкое приложение, вроде современной AAA игры, которой требуется быстро обрабатывать большой массив данных. Причём изначально эти данные закодированы (сжаты) по соображениям экономии места.
    Т.е. во первых мне ни горячо ни холодно от того, что данные как бы уже находятся в памяти и у меня есть на них указатель, потому что во первых это скорее всего медленная память (hdd, flash), а во вторых они там в непригодном для использования виде. А во вторых быстрой (оперативной) памяти всё равно не хватает и необходимо следить за тем что в неё помещается, а от чего можно отказаться. Причём статистические алгоритмы, встроенные в операционную систему явно будут проигрывать движку игры, которых хотя бы знает с чем работает и как данные связаны друг с другом. Ну и в третьих, существование легковесных алгоритмов сжатия вроде lz4, zsdt может сделать нецелесообразным вытеснение данных в медленную память (swap), т.к. их проще и быстрее декодировать и распарсить из оригинального источника.
    В связи с этим хотелось бы понять, какой смысл в персистентности, при учёте, что тут приходится имитировать функции чтения/записи из обычной OS, а так же вручную следить за тем, что находится в быстрой памяти. Или такие системы, как PhantomOS по определению имеют практический смысл только в условиях использования гипотетической так и не выпущенной MRAM, имеющей объём hdd, а ресурс и скорость оперативки?


    1. dzavalishin Автор
      23.11.2019 05:38

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

      Изначально сжатые данные приложением разжимаются при начале работы.

      Имитировать функции чтения из обычной ОС (ФС?) не приходится. Фантом легко хранит содержимое скомпрессированного объекта просто в строке и из неё же можно распарсить финальный объект.

      Вот тут есть пример программы для Фантома: phantomdox.readthedocs.io/en/latest/#example-of-phantom-program

      Обратите внимание на строки

      bmp = new .internal.bitmap();
      bmp.loadFromString(getBackgroundImage());


      По сути они сводятся к

      bmp.loadFromString(import "../resources/backgrounds/weather_window.ppm");


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

      Эта строка будет содержать в данном случае картинку в файловом формате.

      loadFromString парсит и конвертирует в финальный битмап, который и живёт в персистентной переменной.


      1. pda0
        23.11.2019 15:24
        +1

        И нет гарантии, что она подкреплена физической памятью. Ни в какой ОС.
        Функции VirtualLock в Windows и mlock в unix смотрят на это утверждение с удивлением…


        1. dzavalishin Автор
          24.11.2019 01:20
          -1

          Ок, согласен. :) Но, всё же, это не типовой сценарий. И рут нужен. И ограничения есть.

          А технически то запереть странички и в Фантоме можно, тут разницы нет.


          1. pda0
            25.11.2019 03:01

            Так-то да, хотя есть специализированные программы, вроде СУБД, которые нужные привилегии без проблем получают…

            На самом деле просто есть одна вещь, которую давно хотелось иметь в виде прикладного API, но которую никто не делать. Это обработчик для проецирования в память. Т.е. при создании некоторой области памяти, для неё определяется функция-генератор и один или более источников — файловых дескрипторов или указателей (для фантома только последнее). При обращении к указанной области памяти система вызывает обработчик, который генерирует весь объект или его части. Теперь система не сохраняет содержимое объекта в swap, а просто выбрасывает его и воссоздаёт вызовом функции-генератора по необходимости. В более сложном случае может быть даже определена функция, управляющая очерёдностью выкидывания страниц из памяти. Так же, такие объекты, созданные в режиме только чтение и зависящие только от внешних источников могли бы становиться разделяемыми, если определены в разделяемой библиотеке.
            Собственно, практическое использование могло бы быть уже в коде, отвечающим за рендеринг шрифтов. Сейчас вы рендрите шрифты в битмапы, а их храните в памяти для отрисовки. Т.е. количество глифов перемножаем на используемые размеры, перемножаем на цвет символов, перемножаем на количество шрифтов в системе, учитываем, что современный качественный рендеринг шрифтов использует субпиксельное позиционирование, а порой ещё и сглаживание с фоном… Т.е. количество отрисованных глифов быстро становится астрономическим. И всё это хранится в несжатом виде, а потом попадает на диск и лежит там вечно. Ну или придётся городить отдельный уборщик мусора для шрифтов, который будет искать что уже никому не нужно и удалять ссылки, чтобы потом системный сборщик мусора наконец удалил это всё.
            Ну или это помещается в такой вот генерируемый объект и выкидывается как только появляется потребность в памяти, а потому рисуется заново по мере необходимости.
            Или как второй этап делается собственная библиотека, которая на первом этапе читает содержимое ttf, переводит в более удобный формат и имеет более низкий приоритет на удаление, т.к. эти данные более компактны и чаще востребованы. В вот уже генерируемый объект от этого объекта хранит кеш глифов с алгоритмом удаления, выкидывающим из памяти в первую очередь давно не использованное и редко используемое, чтобы старьё и разовые глифы не занимали память…

            Я просто думал, что такое будет естественным для фантома и хотел узнать как его реализовали (на что получился похож API), но увы, до такого похоже ещё очень долго…


            1. dzavalishin Автор
              25.11.2019 14:17

              Мысль довольно очевидная, такой тикет у Фантома даже стоит в планах.

              Тут бы надо разделять две сущности. Генерацию объекта по факту потребности в нём и memory mapping. Первое вполне возможно без второго, вообще говоря. Запрашиваем глиф, если его нет в кеше рендеринга, то рисуем и выдаём. При исчезновении всех ссылающихся на глиф объектов он остаётся только в кеше. Кеш прореживаем по LRU.

              Мне отображение в память больше виделось как инструмент для доступа к сетевым ресурсам.


              1. pda0
                25.11.2019 21:25

                *возможно в фантоме. В обычно OS как раз проецирование файлов в память самое близкое что можно почувствовать. Был файл на диске, а теперь немного магии OS и получилась область памяти.

                А насчёт тикета — хорошо. Придётся подождать пока из этого что-то выйдет…


      1. vagran
        23.11.2019 15:39

        И нет гарантии, что она подкреплена физической памятью. Ни в какой ОС.

        Неправда. Отключите на Линуксе своп и все аллокации на куче будут в RAM.


        1. dzavalishin Автор
          24.11.2019 01:22
          -1

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


  1. Sabubu
    23.11.2019 01:41

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


    Битмапы на каждое окно мне не особо нравятся. Вы оцениваете, какой будет расход памяти на экране 4K с 20 полноэкранными окнами? Я знаю, что это тенденция во всех современных дисплейных серверах, но неужели нельзя хотя бы на скрытые окна не тратить память? Это хорошо работает на смартфонах, где окно одно и занимает весь экран, но на десктопе это ведь неэкономично.


    1. dzavalishin Автор
      23.11.2019 05:41

      Да чёрт с ней, она дешевеет с каждым годом, память-то. Ну и требования персистентности диктуют. Есть гарантия, что рестарт приложения выдаст юзеру предыдущую картинку его окна мгновенно.


    1. dzavalishin Автор
      24.11.2019 03:20

      Насчёт одной очереди — если отрисовка встала, то мы всё равно не увидим эффекта от ввода. Хотя, конечно, сделать две отдельные очереди не так и сложно. Меня эта мысль тоже посещала.


  1. bgnx
    23.11.2019 02:20

    А почему вы завязываетесь на древний способ 2д отрисовки — возней с пикселями, копиями, битмапами, кешированием и софтварной растеризацией? Сейчас практически на каждом устройстве есть поддержка как минимум OpenGL ES а это значит что графическую подсистему можно построить так что заниматься растеризацией и прочей возней с пикселями будет видеокарта а со стороны программы или os достаточно просто на каждый фрейм отрисовки передать на gpu массив объектов данных примитивов (если это прямоугольик то координаты вершин или точка + ширина-высота, если кривая безье то координаты контрольных точек и т.д) — и уже на видеокарте в шейдерах будет происходить отрисовка. Причем примитивы можно бесконечно усложнять поскольку шейдеры можно программировать.
    Что касается взаимодействия отдельных программ-окон то тут есть 2 варианта
    1) Каждое окно будет лично взаимодействовать с видеокартой — вызывать функцию отрисовки (draw-call) но результат будет сохраняться во временный буфер-текстурку а потом os еще раз вызовет отрисовку композируя из нескольких текстурок итоговую картинку. Причем надо заметить что результат отрисовки каждого окна можно хранить на видеокарте а не заниматься копированием между cpu и gpu
    2) Можно попробовать построить взаимодействие так чтобы вызов отрисовки (draw-call) на видеокарте был только один — для этого собираем результат от каждого окна и дополнительно препроцессим смещая координаты — либо на уровне массива примитивов (через постпроцессинг либо через некий общий апи рисования) смещая координаты каждого примитива с соотвествии со смещением каждого окна либо на уровне компиляции кода шейдеров добавить дополнительный код который будет относительно каждого окна добавлять смещение для gl_Position


    1. dzavalishin Автор
      23.11.2019 05:47

      По сути Вы совершенно правы, так и надо. Более того, я проделал серию экспериментов с софтверным GL-ем в качестве базиса для оконной системы.

      Но — на всё рук не хватает, да и акселерированные драйвера современных видеокарт — это ад и израиль, несколько подходов к снаряду вида «что бы такого втянуть целым кусочком» пока кроме глубокой задумчивости результатов не дали. :)

      Так что пока я тактически отступил к 2D.

      Присоединяйтесь, сделайте это, будет действительно здорово. Я уже представляю себе шелл с трёхмеркой и окнами, которые сложены в стопочку в углу экрана. :)


  1. Alex_ME
    24.11.2019 13:32

    Некие мысли по поводу контролов.
    Думаю, можно выделить два подхода к построению UI:


    • контролы рисует система, повинуясь вызовам API (напр. GDI+). За обработку ввода в этом случае также отвечает система.
    • система просто предоставляет буфер, куда приложение рисует своими силами (Qt QML, WPF, UWP итд, а также б-гмерзкие веб-технологии). За обработку ввода внутри окна отвечает приложение.

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


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