惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Exploit Database - CXSecurity.com
N
News and Events Feed by Topic
Latest news
Latest news
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
IT之家
IT之家
V
V2EX
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
Cisco Talos Blog
Cisco Talos Blog
K
Kaspersky official blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
S
SegmentFault 最新的问题
小众软件
小众软件
A
Arctic Wolf
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
G
GRAHAM CLULEY
罗磊的独立博客
T
Tor Project blog
C
Cisco Blogs
美团技术团队
博客园 - Franky
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
T
Threat Research - Cisco Blogs
Cyberwarzone
Cyberwarzone
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
有赞技术团队
有赞技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Security Latest
Security Latest
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
Spread Privacy
Spread Privacy
J
Java Code Geeks
C
CERT Recently Published Vulnerability Notes
大猫的无限游戏
大猫的无限游戏
S
Securelist
The Cloudflare Blog
博客园 - 叶小钗
D
Darknet – Hacking Tools, Hacker News & Cyber Security
阮一峰的网络日志
阮一峰的网络日志
雷峰网
雷峰网
Project Zero
Project Zero

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Препарируем графическую подсистему Windows. Протокол SPICE в современном графическом стеке, часть 2
ZabLen · 2026-05-14 · via Все публикации подряд на Хабре

Уровень сложностиСложный

Время на прочтение18 мин

Охват и читатели767

Обзор

Вступление

Хабр, привет!

Я - Леонид Забурунов, инженер-программист компании БФГ - стартапа по разработке системы виртуализации - в отделе разработки систем виртуализации графических ресурсов.

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

Сегодня же микроскоп нависнет над гостевой ОС Windows. Графика в ОС Windows делится на "до" и "после". Поворотная точка - выпуск Windows Vista. Поэтому я нахожу уместным посмотреть на обе графических модели и на то, как одна сменяла другую. И также я немного коснусь графической модели Linux, потому что X11 и Wayland оказываются к Windows гораздо ближе, чем может показаться.

Запасайтесь любопытством - и мы начинаем!

Оглавление

Windows в эпоху 2D. Windows XP, GDI и зенит славы QXL-адаптера

Иллюстрация работы драйвера QXL в Windows XP (Gemini)

Иллюстрация работы драйвера QXL в Windows XP (Gemini)

ДИСКЛЕЙМЕР! В данном параграфе я буду осторожно говорить об эпохе, которую как пользователь застал в школе и даже в детском саду. Изучать устройство графической подсистемы времён 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 было то, что каждая программа пишет напрямую в кадровый буфер. Это могло привести - и регулярно приводило - к интересным визуальным артефактам, например:

Пример шлейфа от окна при неправильной обработке событий (из официальной документации Microsoft)

Пример шлейфа от окна при неправильной обработке событий (из официальной документации Microsoft)

Выглядит безобидно и даже забавно, я много такого помню. Но если есть возможность сломать другое приложение, то можно сломать и добрую половину системы, не правда ли? Действительно, возможность сломать систему есть - и в грамотных руках она перестанет работать вообще ;) Поскольку дополнительная изоляция слоёв отсутствовала, обвал видеодрайвера каким-либо приложением вполне себе реален - а значит, реально и бсоднуться по пустяку. 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 в Windows XP (Gemini)

Схема работы драйвера QXL в Windows XP (Gemini)

Как мы увидели в предыдущей части, 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-адаптера

Иллюстрация архитектуры компонентов Windows после перехода на WDDM (Gemini)

Иллюстрация архитектуры компонентов Windows после перехода на WDDM (Gemini)

Графическая модель 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

Принцип работы Windows Desktop Window Manager, встроенный в систему начиная с Windows Vista (Gemini)

Принцип работы Windows Desktop Window Manager, встроенный в систему начиная с Windows Vista (Gemini)

Вернёмся к 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

Иллюстрация работы драйвера QXL в современных Windows (Gemini)

Иллюстрация работы драйвера QXL в современных Windows (Gemini)

И снова вернёмся к 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 компонует всё в одну текстуру

Захват экрана

BitBlt из единого framebuffer'а

Через DWM output, несколько API

Захват DirectX-окон

Ненадёжно (back buffer issue)

Полноценно через Desktop Duplication

Метод захвата экрана

GDI BitBltединственный

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-сессии. А знаете, насколько они друг от друга отличаются? Полюбуйтесь:

Сравнительная таблица функций WGL и GLX (из официальной документации Microsoft)

Сравнительная таблица функций WGL и GLX (из официальной документации Microsoft)

Про 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

  • Инструмент видеомонтажа

  • Удалённый доступ

  • Система виртуализации

Да, общих моментов там много.

Всё вышеперечисленное даст нам возможность сформировать архитектуру стримингового агента и стать полностью готовыми к его реализации. Сердечно приглашаю аудиторию в комментарии: готов ответить на вопросы по теме и жажду критических замечаний.

До встречи в будущих статьях, счастливо!