Вступление
Хабр, привет!
Я - Леонид Забурунов, инженер-программист компании БФГ - стартапа по разработке системы виртуализации - в отделе разработки систем виртуализации графических ресурсов.
В данном цикле статей мы разбираем самую объёмную часть протокола SPICE - доставку изображения удалённого рабочего стола. Напомню, что в прошлый раз мы бегло изучили под микроскопом графическую архитектуру протокола - и увидели, что происходит между моментами генерации графической команды в гостевой ОС и отображения нового состояния дисплея на клиентском устройстве.
Сегодня же микроскоп нависнет над гостевой ОС Windows. Графика в ОС Windows делится на "до" и "после". Поворотная точка - выпуск Windows Vista. Поэтому я нахожу уместным посмотреть на обе графических модели и на то, как одна сменяла другую. И также я немного коснусь графической модели Linux, потому что X11 и Wayland оказываются к Windows гораздо ближе, чем может показаться.
Запасайтесь любопытством - и мы начинаем!
Оглавление
Windows в эпоху 2D. Windows XP, GDI и зенит славы QXL-адаптера

ДИСКЛЕЙМЕР! В данном параграфе я буду осторожно говорить об эпохе, которую как пользователь застал в школе и даже в детском саду. Изучать устройство графической подсистемы времён Windows XP я стал недавно из общего любопытства, присущего
реверс-инженеру. Я ни в коем разе не претендую на абсолютную истину и буду очень рад, если старая школа подтянется в комментарии и дополнит мой рассказ своими знаниями, соображениями и воспоминаниями.
Немного личной истории
Мой папа до сих пор работает на Windows XP. Он - энтузиаст рок- и метал-музыки. На протяжении 20 лет его рабочее место состоит из конкретного набора программ для работы со звуком и конкретных обоев рабочего стола. Это как гараж в других семьях. Лучший способ повысить градус в беседе с моим отцом - это предложить обновить операционную систему. Ну а как вы хотели: представьте, что будет, если вы предложите своему снести гараж и поставить вместо него вагончик для иностранных специалистов?
Графическая модель XPDM
Современными глазами компьютерный мир 20-25 летней давности кажется очень наивным. И Windows XP, как важная его часть, тоже. Про изоляцию и безопасность тогда думали сильно меньше нынешнего, чего уж там говорить про виртуализацию (видео)памяти.
XPDM (Windows XP Driver Model) - это название архитектуры графической подсистемы Windows вплоть до Windows XP. Иногда называют XDDM (Windows 2000 Display Driver Model).
Про диапазон версий Windows
Если с до всё +/- понятно, то вот с какой версии ОС такая архитектура пошла - сказать сложнее. Windows в те годы развивался бешеными темпами - и параллельными ветками: на первой ветке Windows 95/98/ME, на второй - Windows NT 3.1/3.5/4.0 и Windows 2000. Мы тут скорее говорим про вторую ветку, модель драйверов во многом унаследована оттуда - как и графическая подсистема.
XPDM строился в эпоху, когда 3D всё ещё оставался экзотикой. К моменту выхода Windows XP компания NVIDIA только-только начала греметь по всему миру со своими GeForce, как и компания ATI со своими Radeon, ну а Intel...Они были слишком заняты процессорами Itanium, чтобы обращать внимание на графику - до закрепившейся практики использования интегрированных видеоядер в Intel Core i3/5/7 ещё ой как далеко (хотя уже были попытки разместить ядро на матплате и даже модели с видеоядрами).
Посмотрев на всё это хозяйство, Microsoft отталкивалась от предположения, что у пользователя из того, что способно работать с графикой, будут только CPU и монитор. А производители GPU пусть по своему усмотрению добавляют акселерацию на те функции, на которые посчитают нужным (об этом чуть ниже).
Характерной особенностью XPDM было то, что каждая программа пишет напрямую в кадровый буфер. Это могло привести - и регулярно приводило - к интересным визуальным артефактам, например:

Выглядит безобидно и даже забавно, я много такого помню. Но если есть возможность сломать другое приложение, то можно сломать и добрую половину системы, не правда ли? Действительно, возможность сломать систему есть - и в грамотных руках она перестанет работать вообще ;) Поскольку дополнительная изоляция слоёв отсутствовала, обвал видеодрайвера каким-либо приложением вполне себе реален - а значит, реально и бсоднуться по пустяку. GPU по сути - глобальный shared resource, оперирующий в режиме ядра.
Этот вопрос выходит за рамки графики, тут стоило бы поговорить про безопасность ОС как таковую. Но это не наша тема.
Видеодрайвер в XPDM
Как в эту эпоху выглядел видеодрайвер?
Во-первых, интересная особенность драйверов для XPDM: можно реализовать произвольное подмножество доступных функций. Всё остальное Windows заменит на software fallback по принципу "лишь бы работало". Умеешь аппаратно ускорять функцию? Молодец, выстави capability и дай указатель на функцию! Не умеешь? Ну, ничего страшного, я как-нибудь сам...
Эта особенность значит гораздо больше, чем можно подумать изначально. В те годы был зоопарк самых разных технологий, это своего рода переходный период во время только формировавшегося тренда на перенос графической нагрузки из общих CPU-ядер в отдельный, мощный сопроцессор. Невозможность дальше совмещать устаревшую архитектуру с возможностями новой графической модели привела к нарушению обратной совместимости, которую Microsoft (как правило) поддерживают до победного. Это стало одной из причин долгой поддержки Windows XP.
Во-вторых, драйвер поделен на 2 части: miniport driver - для ранней инициализации устройства, управления питанием и прерываниями; display driver - для реализации непосредственно функционала отрисовки. (Конкретно в этом разделении, кстати, до сих пор мало что изменилось.)
В-третьих, драйвер выглядит как набор команд: напиши текст, залей область цветом, скопируй прямоугольную область в другие координаты. Логика 2D-рисования времён безальтернативного использования CPU здесь полностью сохранена, просто открыли дорогу к оптимизациям конкретных функций. Этим всем активно пользовались Remote Desktop приложения, в том числе и SPICE.
QXL-адаптер в мире XPDM

Как мы увидели в предыдущей части, QXL-драйвер для XPDM является самым богатым. И вот реализацию каких функций он объявляет (комментарии сгенерированы):
static DRVFN drv_calls[] = { // Вызывается при выгрузке драйвера — освобождает глобальные ресурсы {INDEX_DrvDisableDriver, (PFN)DrvDisableDriver}, // Точка входа для нестандартных команд (escape-коды) от приложений через ExtEscape() {INDEX_DrvEscape, (PFN)DrvEscape}, // Инициализирует физическое устройство (PDEV, physical device): аллоцирует структуры, // сообщает GDI capabilities драйвера через DEVINFO и GDIINFO {INDEX_DrvEnablePDEV, (PFN)DrvEnablePDEV}, // Уничтожает PDEV: освобождает всё что было выделено в DrvEnablePDEV {INDEX_DrvDisablePDEV, (PFN)DrvDisablePDEV}, // Вызывается после DrvEnablePDEV — передаёт драйверу handle HDEV, // который GDI присвоил устройству (для GDI-коллбэков) {INDEX_DrvCompletePDEV, (PFN)DrvCompletePDEV}, // Создаёт primary surface (framebuffer), которую GDI будет использовать // как цель рендеринга; здесь драйвер сообщает адрес и формат VRAM {INDEX_DrvEnableSurface, (PFN)DrvEnableSurface}, // Уничтожает primary surface, созданную в DrvEnableSurface {INDEX_DrvDisableSurface, (PFN)DrvDisableSurface}, // Переключает устройство между активным режимом и disabled-состоянием // (например, при смене разрешения или переходе в полноэкранный режим) {INDEX_DrvAssertMode, (PFN)DrvAssertMode}, // Возвращает список поддерживаемых видеорежимов (разрешения, глубина цвета, // частота обновления) — используется при настройке дисплея {INDEX_DrvGetModes, (PFN)DrvGetModes}, // Синхронизирует GDI с GPU: GDI вызывает это перед прямым доступом // к поверхности, чтобы убедиться что GPU закончил все pending операции {INDEX_DrvSynchronize, (PFN)DrvSynchronize}, // Копирует прямоугольный регион между двумя поверхностями одного формата // (оптимизированный путь без ROP — только простое копирование) {INDEX_DrvCopyBits, (PFN)DrvCopyBits}, // Основная операция блочной передачи: копирование с применением ROP // (растровых операций), clipping, паттернов и brush'ей {INDEX_DrvBitBlt, (PFN)DrvBitBlt}, // Аппаратный вывод текста: принимает глифы и их позиции, // рисует с применением foreground/background цветов {INDEX_DrvTextOut, (PFN)DrvTextOut}, // Рисует контур пути (path) заданным пером — линии, кривые Безье, // ломаные; без заливки {INDEX_DrvStrokePath, (PFN)DrvStrokePath}, // Конвертирует логический brush в аппаратно-специфичный формат // для последующего использования в BitBlt/FillPath операциях {INDEX_DrvRealizeBrush, (PFN)DrvRealizeBrush}, // Задаёт форму и hotspot аппаратного курсора мыши; // если драйвер не справляется — возвращает FALSE, GDI рисует курсор сам {INDEX_DrvSetPointerShape, (PFN)DrvSetPointerShape}, // Перемещает аппаратный курсор в новые координаты без перерисовки экрана {INDEX_DrvMovePointer, (PFN)DrvMovePointer}, // BitBlt с масштабированием: копирует и растягивает/сжимает // прямоугольную область с применением ROP {INDEX_DrvStretchBlt, (PFN)DrvStretchBlt}, // То же что DrvStretchBlt, но с явно заданной ROP-операцией // (StretchBlt с произвольной растровой операцией, не только SRCCOPY) {INDEX_DrvStretchBltROP, (PFN)DrvStretchBltROP}, // Копирует регион с поддержкой color key: пиксели заданного цвета // считаются прозрачными и не переносятся {INDEX_DrvTransparentBlt, (PFN)DrvTransparentBlt}, // Копирует регион с попиксельным альфа-смешением (alpha blending); // используется для полупрозрачных окон и эффектов {INDEX_DrvAlphaBlend, (PFN)DrvAlphaBlend}, // Создаёт device-specific bitmap в VRAM — для offscreen рендеринга // на стороне GPU без участия системной памяти {INDEX_DrvCreateDeviceBitmap, (PFN)DrvCreateDeviceBitmap}, // Освобождает device bitmap, созданную через DrvCreateDeviceBitmap {INDEX_DrvDeleteDeviceBitmap, (PFN)DrvDeleteDeviceBitmap}, };
И швец, и жнец, и на дуде игрец. Этот драйвер реализует собой едва ли не полноценный графический 2D-движок. Да, вот так выглядели драйвера во времена, когда трава была зеленее!
Множество низкоуровневых команд открывает простор для самых разных оптимизаций. Например, отправить по сети текст гораздо быстрее, чем картинку, содержащую этот текст (при условии, что текст помещается полностью и что мы не говорим про аномально малые размеры шрифтов). Накопительный эффект от всех таких ухищрений по экономии битрейта на графических примитивах получался огромным.
Windows в эпоху аппаратного ускорения. Windows Vista, WDDM, DXGI и стадия принятия для QXL-адаптера

Графическая модель WDDM
Всему этому раю для системных программистов суждено было закончиться. Сообщество пришло в шок, когда увидело ЭТО... Конечно же, я говорю про выпуск Wayland Windows Vista. Ну и DirectX 10. Сосредоточьтесь на своих чувствах, я произнесу два главных слова: Windows Aero. Ну как? Да, я тоже скучаю...
Золотая эпоха Windows XP закончилась. Дыры в безопаности заделали изоляцией системных служб в Session 0, проблемы с анархией в вопросах доступа к видеопамяти решились исправить с помощью изоляции графического буфера через Desktop Window Manager (DWM). Короче, ламповую тусовку всех со всеми разогнали, а на смену представили мир розовых пони многослойных системных абстракций.
Переход на Windows Vista означал, в частности, полную замену графической модели. Для драйверов предложили новую архитектуру WDDM (Windows Display Driver Model) - она существует до сих пор и, кажется, будет с нами ещё долго. Для пользовательских приложений оставили выбор - можно пользоваться старым API в виде функций GDI, но рекомендуется переходить на стильное-модное-молодёжное API на базе Direct2D.
Концепции частичной реализации функций больше не осталось. На смену в DX10+ пришли Feature Levels: Microsoft выкатывает спецификацию DirectX определённой версии, а производитель обязан поддержать сразу всё.
Вместе с выпуском DX10 также произошёл переход на базовый компонент системы DXGI - DirectX Graphics Infrastructure. Это своего рода прослойка между, с одной стороны, различными версиями DirectX и, с другой, видеодрайверами с железом (GPU + мониторы). Сюда вынесены все функции, которые не свойственны меняться от инкремента версии DX: управление swap chain и flip model (переключение и двойная буферизация для корректного отображения кадров в окнах или фулскрине), опрос доступных устройств (EnumAdapters, EnumOutputs), ну и так далее. Приложения разных поколений на DX10, 11 и 12 пользуются одними и тем же вызовами для работы системой (при том, что DX11 и DX12 построены вообще на разных философиях).
И ещё один интересный момент. DXGI среди прочего известен своим API захвата экрана, доступным начиная с Windows 8 - Desktop Duplication API. Это API для захвата кадров в виде D3D-текстур (по указателю) напрямую из видеобуфера. На этом API работает всё современное ПО для работы с изображением экрана - от Bandicam до облачного гейминга. Ладно, об этом в следующей статье ;)
Desktop Window Manager

Вернёмся к DWM. Он отвечает за компоновку кадра перед выдачей на экран. Добавился слой абстрации видеопамяти: приложение теперь рисует себя не в видеопамять напрямую, а только в выделенный промежуточный кадровый буфер (по сути каждое приложение в принудительном порядке обзавелось собственным offscreen surface). Произвольные артефакты с наложением окон больше невозможны, поскольку DWM компонует кадровые буферы в финальную кадровую текстуру самостоятельно на основе Z order.
С архитектурой исполнения теперь ситуация такая. Если вы хотите теперь написать текст, или сделать alpha blend, или скопировать регион кадра в другое место, то вместо обращения к команде видеодрайвера:
1. Идёт обращение к DWM
2. DWM формирует цепочку DirectX-команд (command buffer)
3. DWM отправляет команды в очередь на исполнение
4. DWM ожидает готовый результат и передаёт для вывода на экран
Обратите внимание на пункты 3 и 4. Да, как ни странно, они выделены в разные этапы и отвечают за совсем разные части графического конвейера. Теперь в Windows графический драйвер имеет две зоны ответственности: исполнение задач и презентация на экран. Если драйвер умеет всё сразу, то это обычный драйвер в нашем привычном обывательском понимании (WDDM Full Graphics Driver). Если он умеет только вывод на экран, то это WDDM Display Only Driver: D3D и D2D эмулируются на CPU с помощью специального WARP-адаптера, а драйвер работает только с готовым кадровым буфером. Если он умеет только рендеринг, то это WDDM Render Only Driver (это полезно, например, для headless вычислительных рабочих станций).
Переход на WDDM нельзя назвать бесшовным.
Во-первых, вплоть до Windows 8 старые драйвера работали в режиме совместимости несмотря на диаметрально противоположные архитектуры (чуть дальше объясню) - а между релизами Vista и 8 прошло, на минуточку, 5 лет! Видеодрайвера простыми в разработке назвать язык не повернётся, поэтому неудивительно, что разработчикам дали время на адаптацию к новой модели.
Во-вторых, display-only и render-only варианты драйверов появились только в Windows 8. В случае с Vista и 7 для реализации Remote Desktop вариантов кроме использования устаревших драйверов я не увидел.
В-третьих, поначалу возможность аппаратного ускорения GDI-функций просто отобрали - и вернули только в Windows 7. Современные модные приложения, работающие через Direct3D (например, те, что написаны на WPF), чувствуют себя отлично, тогда как всё остальное вынуждено упираться в возможности CPU. Это к разговору о том, почему Vista запомнилась многим людям в очень негативном свете.
Отказаться от DWM нельзя. Ну хорошо, есть один способ - когда вы запускаете D3D-приложение, можно попросить систему войти в режим exclusive fullscreen, чтобы избежать накладных расходов на композицию и выводить изображение на экран в единоличном режиме. Этим пользуются игры.
По итогу сложно найти хоть что-то, что осталось от старой модели. Разве что название операционной системы, для которой это всё запускается...
QXL-адаптер в мире WDDM

И снова вернёмся к SPICE. В предложенных обстоятельствах драйвер для QXL-адаптера не в состоянии предложить ничего интересеного. Протокол SPICE упростился до ещё одной реализации remote framebuffer.
Уберём служебный код по управлению устройством, настройку разрешения, соглашения для драйверов Windows - и получаем в сухом остатке один коллбэк на функцию драйвера DxgkDdiPresentDisplayOnly:
NTSTATUS QxlDevice::ExecutePresentDisplayOnly( ... _In_ ULONG NumMoves, _In_ D3DKMT_MOVE_RECT* Moves, _In_ ULONG NumDirtyRects, _In_ RECT* DirtyRect, ... ) { ... // Области скролла копируем через QXL_COPY_BITS for (UINT i = 0; i < NumMoves; i++) { pDrawables[nIndex] = Drawable(QXL_COPY_BITS, &Moves[i].DestRect, NULL, 0); if (pDrawables[nIndex]) { nIndex++; // Заполнение информации } } // Области обновления копируем через QXL_DRAW_COPY for (UINT i = 0; i < NumDirtyRects; i++) { pDrawables[nIndex] = Drawable(QXL_DRAW_COPY, &DirtyRect[i], NULL, 0); if (pDrawables[nIndex]) { nIndex++; // Заполнение информации } } ... }
Вот и вся любовь.
"Вопрос ИИ-эксперту" (вместо подытога)
Напоследок по теме Windows - сравнение от кремниевого:
Кремниевый, привет! Я тут готовлю статью для Хабра про графическую подсистему Windows, помоги мне сравнительной таблицей философии и архитектуры XPDM и WDDM. Я её вставлю как подытог.
Режим работы драйвера | Полностью kernel mode | User mode DLL + KM miniport |
Центральный арбитр | Нет, драйвер управляет сам | dxgkrnl.sys |
Планировщик GPU | Отсутствует | VidSch (кооп. → preemptive в WDDM 2.0) |
Виртуализация VRAM | Нет, плоское адресное пространство | Есть, VidMM + paging в системную RAM |
Отказоустойчивость | Падение драйвера → BSOD | TDR: сброс GPU без перезагрузки |
Компоновка окон | Нет DWM, прямой framebuffer | DWM компонует всё в одну текстуру |
Захват экрана |
| Через DWM output, несколько API |
Захват DirectX-окон | Ненадёжно (back buffer issue) | Полноценно через Desktop Duplication |
Метод захвата экрана | GDI | GDI BitBlt / Desktop Duplication / WGC |
Производительность захвата | Медленно, CPU read bottleneck | GPU-to-GPU без копий на CPU (Duplication API) |
Protected content | Не блокируется | Чёрный прямоугольник в захвате |
Захват отдельного окна | Только через HWND + BitBlt | WGC с полной поддержкой offscreen-части |
Поддержка ОС | Windows XP / 2003 | Vista и все последующие версии |
(вставил как есть, без исправлений, будьте внимательны)
А что там в Linux?
Мы разобрали, как менялась графическая модель Windows. Настал отличный момент, чтобы пару слов сказать про Linux. Не потому, что это наша основная тема, а потому, что эволюция X11 и, в конце концов, миграция в сторону Wayland повторяет тот же путь, что и от XPDM к WDDM.
Параллели с Windows
В двух словах: заменяете термин XPDM на X11, а WDDM на Wayland - получите, распишитесь. Шутка. Или нет? Конечно, специалисты найдут много отличий, но поскольку для нас это чистый оффтоп - пойдёт. Ну ведь правда же: X11 - это сложная система из графического сервера и клиента с безграничными возможностями для пользователя, в то время как Wayland - это all-in-one графический сервер с композитором, изолирующий всю подкапотную часть от пользователя и не дающий никаких прямых доступов к кадровому буферу, текстурам и прочим составляющим рабочего стола (его текущего состояния).
Давайте на трёх занятных примерах.
Первое. Сегодня мы знаем X11-сервер как userspace-процесс, а GDI - как часть ядра Windows. Можно осторожно сказать, что это противостояние микроядерной и монолитной архитектур соответственно. Но недавно я наткнулся на интересную подробность: до выпуска Windows NT 4.0 (хронологически это между Windows 95 и Windows 98, но NT 4.0 это скорее корпоративный продукт) GDI сидел в системе как отдельный графический сервер в user mode! Вот официальный документ о перемещении GDI в ядро. Конечно, есть и различия, например: GDI не задуман как сетевой протокол, в то время как для X11 by design можно запустить клиент и сервер на двух разных хостах. Но тем не менее, на стартовых позициях это две очень схожих архитектуры.
Второе. BitBlt - краеугольный камень GDI. С ним мы уже знакомы. Посмотрим на полную сигнатуру из официальной документации:
BOOL BitBlt( [in] HDC hdc, // A handle to the destination device context. [in] int x, // The x-coordinate, in logical units, of the upper-left corner of the destination rectangle. [in] int y, // The y-coordinate, in logical units, of the upper-left corner of the destination rectangle. [in] int cx, // The width, in logical units, of the source and destination rectangles. [in] int cy, // The height, in logical units, of the source and the destination rectangles. [in] HDC hdcSrc, // A handle to the source device context. [in] int x1, // The x-coordinate, in logical units, of the upper-left corner of the source rectangle. [in] int y1, // The y-coordinate, in logical units, of the upper-left corner of the source rectangle. [in] DWORD rop /* A raster-operation code. * These codes define how the color data * for the source rectangle is to be combined with the color data * for the destination rectangle to achieve the final color. */ );
А что в X11? А в X11 есть XCopyArea. Вот его полная сигнатура, тоже из официальной документации:
int XCopyArea( Display *display, // Specifies the connection to the X server. Drawable src, // Specify the source rectangle to be combined. Drawable dest, // Specify the destination rectangle to be combined. GC gc, // Specifies the Graphics Context int src_x, int src_y, /* Specify the x and y coordinates, * which are relative to the origin of the source rectangle * and specify its upper-left corner. */ unsigned int width, unsigned int height, /* Specify the width and height, * which are the dimensions of both * the source and destination rectangles. */ int dest_x, int dest_y /* Specify the x and y coordinates, * which are relative to the origin of the destination rectangle * and specify its upper-left corner. */ );
За исключением поля rop - идентично.
Третье. В Windows есть такая штука как WGL - это мостик между оконной системой и OpenGL-контекстами устройств. Ну а в X11 есть GLX, реализующий то же самое для X-сессии. А знаете, насколько они друг от друга отличаются? Полюбуйтесь:

Про Wayland ничего конкретного указывать не буду. Ну хорошо, там вместо Direct3D идёт привязка к OpenGL+EGL. EGL - тот же GLX, только в профиль. Мобильный профиль, потому что есть возможность переключиться на OpenGL ES. Думаю, вы всё уже поняли.
Да и так, в общем-то, много где. И не только в графических подсистемах. Windows и Linux декларируют диаметрально противоположные философии, но разрабатывают-то их примерно одни и те же инженеры - продукты той же среды. И работать нужно с одним и тем же железом. Вот и получается, что архитектурные решения очень часто схожи - где они рождались независимо, где DOS стырит что-то из мира Unix, а где наоборот. Жизнь такая, пространство оптимальных решений конечно и строго ограничено.
QXL-адаптер в мире Linux
Как мы знаем, QXL-адаптер работает не только в Windows, но и в Linux. Если в Windows для нас важна только версия ОС, то в Linux у нас ситуация совершенно другая: версия операционной системы - ровно как и ядра - нас не интересует вообще, и даже тип дистрибутива в определённой степени не имеет значения. QXL-адаптеру все равно, стоит у вас Ubuntu 22 или 24. QXL-адаптеру все равно, заменили ли вы в Ubuntu GNOME на KDE. QXL-адаптеру будет все равно, даже если у вас появились причины вместо Ubuntu поставить Astra Linux. Для QXL-адаптера важен только тип графической сессии: X11 или Wayland.
Про дистрибутивы
Релиз дистрибутива, конечно, имеет значение. Но только в контексте типа сессии. Держатели дистрибутивов Linux долгое время откладывали переход на Wayland, не в последнюю очередь из-за проблем с драйверами (привет, NVIDIA). Дальше всех в вопросах Wayland продвинулись, кажется, GNOME: актуальные дистрибутивы зачастую сохраняют возможность переключения между X11 и Wayland (так, например, поступают Ubuntu 22 и Fedora 41), тогда как новейшие дистрибутивы решаются на полный отказ от X11.
(Напомню часть раздела про драйвера из предыдущей части). Совместимость с X11 обеспечивается драйвером, на данный момент поддерживаемым сообществом Freedesktop. Прибегну к уже знакомому средству для быстрой демонстрации используемых команд:
# Ищем использование графических команд QXL в драйвере для X11-сессий zaburunovleonid@fedora:~/develop$ grep -R "QXL_DRAW_" --include="*.c*" ./xf86-video-qxl/ ./xf86-video-qxl/src/qxl_mem.c: else if (is_drawable && drawable->type == QXL_DRAW_COPY) ./xf86-video-qxl/src/qxl_mem.c: else if (is_drawable && drawable->type == QXL_DRAW_COMPOSITE) ./xf86-video-qxl/src/qxl_surface.c: drawable_bo = make_drawable (qxl, surf, QXL_DRAW_FILL, rect); ./xf86-video-qxl/src/qxl_surface.c: drawable_bo = make_drawable (qxl, surface, QXL_DRAW_COPY, &rect); ./xf86-video-qxl/src/qxl_surface.c: drawable_bo = make_drawable (qxl, qxl->primary, QXL_DRAW_COPY, &rect); ./xf86-video-qxl/src/qxl_surface.c: drawable_bo = make_drawable (qxl, dest, QXL_DRAW_COPY, &qrect); ./xf86-video-qxl/src/qxl_surface.c: drawable_bo = make_drawable (qxl, dest, QXL_DRAW_COMPOSITE, &rect); ./xf86-video-qxl/src/qxl_surface.c: drawable_bo = make_drawable (qxl, dest, QXL_DRAW_COPY, &rect); # И QXL_COPY_BITS zaburunovleonid@fedora:~/develop$ grep -R "QXL_COPY_BITS" --include="*.c" ./xf86-video-qxl/ ./xf86-video-qxl/src/qxl_surface.c: drawable_bo = make_drawable (qxl, dest, QXL_COPY_BITS, &qrect);
Что мы видим? Если не считать QXL_DRAW_COMPOSITE, то драйвер поддерживает минимальный набор команд - копирование области кадрового буфера (`QXL_DRAW_COPY`) и заливка области одним цветом (`QXL_DRAW_FILL`). Не густо. На серьёзные оптимизации пропускной способности рассчитывать не приходится.
Теперь посмотрим на Wayland. Архитектурная разница с X11 у него примерно такая же, как и у WDDM с XPDM - тут мы уже разобрались. Совместимость с сессиями Wayland обеспечивается...ну, скажем так, QXL-адаптер здесь не при чём.
Wayland-драйвер для QXL отсутствует в принципе. Разбор архитектуры графического сервера Wayland в статью не поместится, поэтому я всего лишь кратко отмечу, что Wayland имеет более прямую архитектуру: каждый клиент рисует в свой изолированный буфер, а compositor забирает его напрямую, минуя промежуточный X-сервер. И для плавной работы требуется аппаратно ускоренный OpenGL/EGL. Когда создаётся контекст отрисовки OpenGL, поддержки аппаратного ускорения со стороны QXL не окажется, гостевая ОС разведёт руками и запустит эмуляцию через llvmpipe. Вот и всё, никакой магии, снова программный рендеринг.
Проблемы SPICE внутри Wayland общепризнаны (см, например, здесь). И поэтому для локального развёртывания сегодня рекомендуют вместо QXL использовать проброс хостовой GPU через virtio-gpu и VirGL (см. также информацию для OpenGL и Vulkan). И вообще вместо SPICE рекомендуют использовать альтернативы, например, VNC.
Вывод и заключение
Ура, мы снова закончили! Тема Windows на порядок объёмнее темы про SPICE, в связи с чем усилий по увеличению плотности информации было ещё больше. Надеюсь, что ясность изложения не пострадала.
Ключевое, что я бы мог отметить из всего сегодняшнего пациента - всё, что касается низкоуровневой оптимизации работы рабочего стола, легло на плечи производителя графического драйвера. И поделом: конторы гигантские, справятся как-нибудь :)
Если без шуток, то это - часть естественного процесса развития, когда мы переходим к работе с более высокоуровневыми блоками. Мы сегодня наблюдаем это во всех отраслях: новые языки программирования, новые инструменты визуализации, вайб-кодинг и прочая-прочая.
Трагедия SPICE и QXL - не в том, что разработчики были какие-то не такие, а в том, что парадигма построения графического стека ОС изменилась не от хорошей жизни. Операционная система является фундаментом для пользовательской работы, а потому построение множества абстракций для обеспечения более стабильной и комфортной работы неизбежно. Да, есть у этого всего и обратная сторона - например, переход Microsoft к веб-разработке даже для системных приложений, вследствие чего какой-нибудь калькулятор весит и жрёт на порядок больше прежнего при неизменном функционале. Прямо как мы с вами после приобщения к качалкам.
Как бы то ни было, теперь мы наконец-то готовы сузить фокус исследования. В следующий раз я расскажу конкретно про возможности захвата экрана в Windows и про то, как вообще подходят к захвату и сжатию видео при работе со стримингом - будь то:
Внешняя карта захвата
Организация видеонаблюдения
Облачный гейминг
Плагин для стримеров на Twitch/YouTube
Инструмент видеомонтажа
Удалённый доступ
Система виртуализации
Да, общих моментов там много.
Всё вышеперечисленное даст нам возможность сформировать архитектуру стримингового агента и стать полностью готовыми к его реализации. Сердечно приглашаю аудиторию в комментарии: готов ответить на вопросы по теме и жажду критических замечаний.
До встречи в будущих статьях, счастливо!
