VRDesktopUltra: четыре экрана Mac в VR — и текст без ряби
Самый приятный результат последних экспериментов с VRDesktopUltra — короткая фраза после очередной проверки в гарнитуре: «Ура, рябь пропала!»
За ней — несколько итераций передачи изображения, смена способа отображения виртуальных мониторов и замеры, которые заставили пересмотреть часть первоначальных решений. Сейчас на нашем стенде работают три виртуальных ультрашироких экрана Mac и основной Retina-дисплей — вместе, в одной VR-сцене на Quest Pro.
VRDesktopUltra вырос из WireDeskVR — нашего предыдущего проекта виртуального рабочего пространства на Unreal Engine 5. Мы сохранили задачу — удобные экраны Mac в VR, — но в новой реализации перешли от UE5 к собственной связке Rust и OpenXR.
Рассказываем, к какому устройству системы мы пришли к 21 октября 2026 года, почему выбрали именно его и где пока проходит граница между работающим экспериментом и готовым инструментом для ежедневной работы.
Нам нужен рабочий стол, а не просто большое видео
Показать картинку с компьютера в шлеме — только начало. Если на ней фильм, небольшая потеря детализации может остаться незаметной. Если перед глазами редактор кода, терминал или пиксельный рисунок, требования совсем другие: тонкие линии не должны рябить при движении головы, мелкий текст должен оставаться читаемым, а прокрутка — отзывчивой.
И ещё хочется не одного прибитого к взгляду прямоугольника, а рабочего пространства: поставить экраны вокруг себя, менять расстояние, размер и кривизну, перемещаться между ними.
Отсюда первое важное решение: Mac передаёт содержимое мониторов, а Quest сам строит и показывает VR-сцену. Положение головы, контроллеры и геометрия экранов обрабатываются локально. Для поворота головы не нужно ждать, пока компьютер нарисует и пришлёт новое изображение всего виртуального мира. Последняя полученная картинка остаётся на своём месте, даже если обновление рабочего стола ещё в пути.
Основная логика написана на Rust. На Mac используем ScreenCaptureKit, Metal и аппаратный VideoToolbox; на Quest — OpenXR, OpenGL ES и аппаратное декодирование. Между ними — наш протокол поверх USB-сети NCM. Текущий путь проводной, без переключения на Wi-Fi.
Почему одного способа передачи оказалось мало
Мы проверили два разных подхода к изображению рабочего стола.
Передавать изменения без потерь. Mac сравнивает изображение с подтверждённой версией на GPU, выделяет изменившиеся плитки размером 128 × 128 пикселей и отправляет только их. Если LZ4 уменьшает плитку — используем сжатие, иначе передаём исходные данные. При неизменном изображении новая партия не нужна.
Для текста и небольших правок это полезное свойство. Но полноценная прокрутка меняет пиксели почти по всей площади экрана. «Отправляем только изменения» в таком случае вовсе не означает «отправляем мало». Ранние замеры на трёх одновременно прокручиваемых экранах показали, насколько дорогими становятся упаковка и передача больших партий.
Передавать движение аппаратным видеокодеком. Это сокращает объём данных при больших непрерывных изменениях, но добавляет цену кодирования и декодирования, а также потери качества.
В итоге выбрали адаптивный путь, а не один универсальный режим:
- небольшие изменения передаются плитками без потерь;
- при устойчивом большом движении включается аппаратный HEVC;
- после прекращения движения приходит полное обновление без потерь, возвращающее точную базу для последующих изменений.
В видеорежиме каждый передаваемый кадр независимый: используем IDR, без цепочки предсказательных кадров. Это упрощает восстановление, но оплачивается объёмом данных и работой кодера. Такой компромисс мы измеряем, а не считаем бесплатным.
Важна и согласованность: Quest публикует целиком подготовленное изображение, а не показывает наполовину обновлённый монитор. Подтверждение отправляется после завершения соответствующей GPU-работы. Следующие точные изменения не накладываются поверх приблизительной видеокартинки без предварительного восстановления базы.
Главное визуальное изменение: мониторы — отдельными слоями
Одна из самых заметных проблем была не в кабеле: мелкий текст рябил при движении головы.
Мы изменили способ его показа. Вместо того чтобы всегда рисовать лицевые поверхности мониторов внутри общего изображения 3D-сцены для каждого глаза, передали их отдельными пространственными слоями композитору Quest. Для плоского экрана используется quad, для круговой кривизны — cylinder. Сама сцена остаётся локальной и иммерсивной; экраны не превращаются в панели системного меню.
Это отделяет содержимое монитора от растеризации общего вида сцены. Неподвижное изображение также не копируется заново при каждом повороте головы: обновлять его нужно только после получения новой версии.
На этой реализации прошла реальная проверка трёх изогнутых мониторов в надетой гарнитуре. Именно после неё мы получили подтверждение: рябь исчезла. Зафиксировали этот вариант как визуальную базу для дальнейшей работы.
Это результат конкретной проверки, а не обещание идеальной читаемости любого шрифта под любым углом. Но для проекта он важнее очередного удачного запуска: изменилось само ощущение от изображения.
Теперь в пространстве есть и основной экран Mac
Текущая конфигурация стенда — три виртуальных монитора по 5760 × 1200 и основной Retina-дисплей 4112 × 2658. После восстановления USB-сессии проверены одновременная передача всех четырёх источников и отправка четырёх слоёв мониторов в активной VR-сессии.
Виртуальные экраны работают в режиме native 1×: размер рабочего стола, захвата и передаваемого изображения совпадает. Основной экран захватывается в его реальном backing-разрешении, а не в уменьшенном логическом размере macOS. Физический режим дисплея при этом не меняем.
На Retina встретилась показательная деталь: аппаратный декодер может выделить изображение немного выше полезной области. Для нашего источника видимая высота — 2658 пикселей, а кодированная поверхность имеет высоту 2672. Теперь читаем фактический crop декодера и проверяем его относительно исходного экрана: техническое дополнение не должно растягивать или обрезать рабочий стол.
Мониторы можно перемещать контроллерами, наклонять, менять размер, глубину и кривизну. Отдельно исправили накопление вертикального смещения при смене пространства отслеживания после снятия и надевания гарнитуры. Полную проверку всех сочетаний управления и повторных входов ещё продолжаем.
Убрали лишнее ожидание на приёмнике
После улучшения изображения задержка никуда автоматически не исчезла. Следующий эксперимент показал лишнюю привязку: готовое изображение ожидало очередной итерации XR-цикла, прежде чем стать доступным для показа.
Мы разделили ожидание следующего XR-кадра и обслуживание готовых изображений. Пока runtime ждёт свой момент, основной графический поток может продвигать загрузку и проверять завершение GPU-работы. Проверки целостности и подтверждение применения сохранили — ускоряться за счёт преждевременного «готово» нельзя.
В сопоставимом 18-секундном эксперименте двигалось содержимое одного экрана, два других оставались статичными. Сервер, USB, кодек, разрешение и расположение мониторов между прогонами не менялись; оба замера проходили в активном состоянии гарнитуры.
Измеряли время на стороне приёмника: от передачи кадра в декодирование до публикации готового изображения.
| Метрика | До | После |
|---|---|---|
| Среднее | 29,14 мс | 13,70 мс |
| 95-й процентиль | 29,29 мс | 17,73 мс |
Это не задержка от изменения на Mac до света в линзах. В таблицу не входят захват и кодирование на компьютере, путь до приёмника и последующий вывод дисплеем. Цель сквозных менее 20 мс остаётся открытой. Зато этот A/B показывает конкретное устранённое ожидание без снижения разрешения или смены визуальной базы.
Проверяем не только спокойный рабочий стол
Для следующего этапа сделали полноэкранную Metal-нагрузку на всех подключённых дисплеях: цветная геометрия, мелкие детали, движение, масштабирование, вращение и смена сцен. На изображении есть номер исходного кадра и время — так проще отличать движение сцены от реального поступления новых кадров.
Тестовый шаблон в VR: цветная сетка, мелкие детали и движущиеся элементы; сверху — счётчики исходных кадров и времени. Это стереоснимок из гарнитуры, а не фотография через линзы и не измерение оптической задержки.
30-секундный прогон на Mac дал около 59,8 завершённых GPU-кадров в секунду на каждом из четырёх источников. Это проверка генератора нагрузки, не доказательство такой же частоты видео в Quest: в том прогоне гарнитура спала. Сопоставимая проверка всей цепочки в надетой гарнитуре ещё впереди.
Также установлена экспериментальная сборка с запросом пространственного supersampling для слоёв мониторов и изменением обслуживания приёма под плотным потоком. Эти изменения пока не объявляем новым визуальным или скоростным результатом: сначала нужен живой A/B с принятой базой.
Чего пока не обещаем
Проект ещё не готов назвать себя инструментом для ежедневной работы без оговорок. Впереди — измерение сквозной задержки, длительная нагрузка с контролем памяти и нагрева, проверка переподключений и устойчивой частоты видео на всех экранах.
Отдельный критичный сценарий — попиксельное рисование. Сохранить исходное разрешение недостаточно: текущий HEVC при движении использует сжатие с потерями и цветовую субдискретизацию. Последующее обновление без потерь не гарантирует точность в момент редактирования. Поэтому полную pixel-perfect передачу во время рисования мы ещё не заявляем. И один пиксель исходного экрана не становится автоматически одним физическим пикселем шлема при произвольном размере и положении виртуального монитора.
Но направление уже определилось: локальная VR-сцена, отдельные слои мониторов и адаптивная передача содержимого по USB. У нас есть работающая конфигурация из четырёх экранов, подтверждённое исчезновение ряби на выбранной визуальной базе и измеримое сокращение одного из участков задержки. Теперь задача — сохранить это качество и добиться устойчивой отзывчивости всей системы, а не только отдельных её частей.