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

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
V
V2EX
C
Check Point Blog
GbyAI
GbyAI
D
Docker
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
博客园 - Franky
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Microsoft Security Blog
Microsoft Security Blog
P
Privacy & Cybersecurity Law Blog
WordPress大学
WordPress大学
The Cloudflare Blog
S
SegmentFault 最新的问题
Latest news
Latest news
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
I
InfoQ
博客园 - 【当耐特】
NISL@THU
NISL@THU
A
About on SuperTechFans
T
Tailwind CSS Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Scott Helme
Scott Helme
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
V
Vulnerabilities – Threatpost
Security Archives - TechRepublic
Security Archives - TechRepublic
A
Arctic Wolf
Hacker News: Ask HN
Hacker News: Ask HN
N
News and Events Feed by Topic
IT之家
IT之家
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
大猫的无限游戏
大猫的无限游戏
S
Security Affairs
The Register - Security
The Register - Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 热门话题
T
Tor Project blog

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 миллионов точек без потерь
Remoded: разбираем демо-эффекты (часть 1)
BiTL · 2026-05-04 · via Все публикации подряд на Хабре

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

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

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

Туториал

Приветствую всех ценителей больших пикселей и выпуклых мониторов. Мы продолжаем разбираться с технологиями демосцены 90-х. В этой статье: недокументированные режимы VGA, аппаратный скролл, fake mode, удивительный алгоритм закраски, и многое другое, о чем бесполезно спрашивать у ChatGPT.

Что ж... С чего начать? Давайте я введу вас в курс дела, вдруг вы не читали моих предыдущих статей, или вообще открыли эту страницу случайно? Есть такая штука, называется демосцена. "Ага, я видел одну такую демосцену!" - воскликнет кто-то из читателей. Но нет, демосцена - это субкультура (и явление), а то, что вы могли видеть, это "демо" (demo), или "интро" (intro), в зависимости от размера (все, что 64кб и меньше обычно называют "интро"). Но обобщенное "демки" тоже сойдёт. И демками все не ограничивается. Конечно же, трекерная музыка, чиптюны, пиксель-арт, ascii-арт и все такое - это тоже часть мира demoscene. Хотя и не обязательно. Например, тусовка почитателей стиля 8-bit music (chiptune) давно выделилась в самостоятельное явление и ее участники могут и не знать ничего про демосцену.
Демки делают для разных платформ, и современных, и старых, и очень старых, и даже для калькуляторов, осцилографов и кассовых аппаратов. А потом собираются на демопати и устраивают конкурсы. Демопати проходят по всему миру, но больше всего их в Европе. В России тоже есть (вот буквально на днях в Вологде прошел очередной "Мультиматограф")

Если вы ничего не поняли из этого объяснения, то возможно вам стоит пробежаться по статье в Вики,  или в архивном Лурке (где даже больше информации, как не странно).Если лень, то читайте так, ничего не понимая, статья с картинками!

Если вы ничего не поняли из этого объяснения, то возможно вам стоит пробежаться по статье в Вики, или в архивном Лурке (где даже больше информации, как не странно).
Если лень, то читайте так, ничего не понимая, статья с картинками!

Я увлёкся демосценой еще в школе (а это было в середине 90-х). И вот, на дворе 2026-й, у меня давно уже выросла борода и свитер, а я все еще делаю демки. Причем начав с кодинга под DOS, добравшись до Windows и OpenGL, я проделал круг и вернулся к DOS и 486-му, с которого начинал. Но это нормально. Тут у нас все такие. Просто кто-то начинал с Амиги, Спектрума и сейчас делает демки для этих платформ. Кто-то и не уходил с них. Но и современные платформы тоже никто не отменял, конечно.

Так вот. В прошлом году я (bitl/7dump) и мой коллега по цеху Manwe/SandS выкатили DOS-демку под названием Demoded, и базанули на крупной финской пати Assembly, выиграв 1-е место в номинации Oldskool demo (да, оно так и пишется). Статья с разбором той демки тоже имеется.

И вот, не прошло и года, и мы сделали еще одну демку, выставили ее на демопати Revision 2026, и, что вы думаете?.. Не выиграли. Ну, не все коту масленица. На самом деле я и не питал иллюзий. Нет, правда. Дело в том, что это изначально-то и демкой быть не планировало, и в конкурсах участвовать тоже не собиралось. План был совсем другой. Я хотел опубликовать финальную версию и исходный код к Demoded, и мне нужно было небольшое интро, буквально одно-экранное (с одним эффектом и текстом), в качестве "КДПВ". То есть релиз, которым я бы привлек внимание к выходу "сорцев" и "финал вершон" (по просьбам телезрителей). Вот это я и стал делать. Но до причесывания исходников для их релиза руки все не доходили. Зато "КДПВ-интра" всё прирастала новыми эффектами, без особого сценария и плана. Ну, вот так и родился этот кадавр, который из-за названия (Remoded) многие приняли за продолжение первой демо, хотя идея была в другом и продолжением оно, конечно же, не являлось, имело совсем другой стиль и динамику. Отчего, я полагаю, некоторые были разочарованы. Так что мы действительно не питали больших надежд, особенно учитывая, что Revision - это не какой-то местечковый квартирник, а крупнейшее международное пати (из тех, что считаются чисто сценерскими, без коммерции, геймеров и конкурсов аниме-косплея). Но шанса поучаствовать в следующий раз может и не быть (особенно учитывая нашу новую реальность, которая все больше начинает походить на заглючившую симуляцию), так что Manwe спросил - успею ли я доделать демку к Revision? Я спросил: думаешь, имеет смысл? Он говорит: ага. Ну, на том и порешили.

Revision 2026, Саарбрюккен, Германия

Revision 2026, Саарбрюккен, Германия

Ну и, в общем, наша Remoded заняла 3-е место (всего в номинации Oldskool demo участвовало 7 работ, что непривычно мало, в 2025-м было 17). На самом деле я зря прибедняюсь, это мы еще хорошо отделались, учитывая, что все остальные демо были под 8-битные платформы. Нас немного попинали на дискорде (мол, PC, да еще и 486-й, какой же это олдскул?), но демку в целом приняли хорошо, что уж тут жаловаться. А первое место выиграло демо от группы Otomata Labs, внимание: под Atari 2600 VCS. Саму эту демку можно посмотреть тут: https://www.pouet.net/prod.php?which=105918. Такое сложно переолдскулить, согласитесь.

Немного технических данных

Как и в предыдущий раз, демо я делал под 486DX2/66МГц, с VGA-совместимым видеоадаптером и MS-DOS 5.0. Потому что он у меня есть. Я на него и ориентируюсь. Впрочем, на этот раз демо не требует сопроцессора, поэтому 486SX тоже подойдет. Желательна VLB-видеокарта, но ISA тоже ок.

Если предыдущая демка (Demoded) использовала только слегка подтюненный режим 13h с классической линейной организацией памяти и разрешением 320x200, и практически никаких хардварных трюков не эксплуатировала, то в этот раз я решил немного упороться и использовать mode-x со всеми вытекающими. Что за mode-x? Давайте вкратце. Это так называемые "недокументированные режимы" чипа VGA. Хотя где-то пишут, что документированные, но та документация спрятана в секретных подвалах IBM. Я не проверял.
Но BIOS про эти режимы не знает, и включить их можно только самостоятельно пощелкав регистрами видеокарты на низком уровне.

Так или иначе, считается, что первым такой режим описал Майкл Абраш в своих журнальных статьях в 1991 году (позже в своей книге ). По сути это такой гибрид EGA и VGA режимов, обычный для VGA цветовой формат пикселей: 8-битный индекс указывающий на цвет в 256-цветной палитре (оттенки в которой определяются 18-ю битами). Но с планарной (плоскостной) организацией видеопамяти. В отличие от EGA, по планам разбиваются не биты цвета, а пиксели целиком... Ох, боюсь я вас запутаю так (и сам запутаюсь).
Давайте по простому: если в стандартном режиме 13h вы записываете байт в смещение 0 (сегмента A000h), то это пиксель с координатами x:0,y:0. Так? Если в смещение 1, то это пиксель x:1, y:0, смещение 2 - x2:x0. И так далее... Все линейно.
В случае с планарным mode-x: смещение 0 это пиксель x:0:y0, смещение 1 - это x:4,y:0, смещение 2 - x:8,y:0, ит.д. Чтобы, например, нарисовать пиксель x:1:y:0 - надо изменить битовую маску в секвенсоре VGA (sequencer map mask), через порт видеокарты, тем самым включить запись в план "1" (счет планов начинается с нуля) И записать байт опять таки в смещение 0. Планов всего четыре. Так что строку экрана можно представить себе в виде чередующихся планов:
0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3...
В какой сейчас план/слой рисовать - выбираем сами с помощью 4-битной маски. Можно во все сразу - записью одного байта в видеопамять, если маска = 15. (1111b), тогда закрасятся сразу 4 соседних пикслея (одним цветом). Попробую нарисовать:

Цвета здесь не имеют отношения к цвету пикселей. Каждая клеточка - это пиксель (байт).

Цвета здесь не имеют отношения к цвету пикселей. Каждая клеточка - это пиксель (байт).

Получается в каждом плане хранится каждый 4-й пиксель. Таким образом, чтобы нарисовать одну точку с произвольными координатами нам нужно:
1. Определить какому плану соответствует X координата: plan = X and 3
2. Установить маску секвенсора на запись в этот план: mask = 1 << plan
3. Вычислить смещение (offset): X / 4 + Y * (screen_width / 4)
4. Записать байт (номер цвета) по адресу [0xA000 : offset]

Почти то же самое, что и для обычного VGA-режима, но с установкой маски. Правда эта операция, пожалуй, съедает больше процессорного времени, чем все остальные. Одна только запись в порт видеокарты займет (для 80486) не менее 16 тактов. Поэтому на практике мы не можем щелкать этой переключалкой ради каждого пикселя, приходится придумывать такие схемы отрисовки, которые требуют минимального количества переключений маски планов. В каких-то случаях это удобно, но в основном - вынос мозга.
Добавим к трудностям еще и тот факт, что видеокарты обычно работают медленнее в этом режиме. Например Cirrus Logic, который сейчас воткнут в мою "четверку", в mode-x запись в видеопамять почти в 2 раза медленнее, чем при стандартном 13h режиме.

Так нафига попу гармонь? В чем профит от этого мудренного режима? Основной выигрыш заключается в том, что в планарном режиме мы можем использовать 256 кб памяти видеокарты (VRAM). Тогда как в стандартном 256-цветном режиме нам доступно только 64 кб (один сегмент). А в modex у нас: 4 плана * 64кб. А значит, мы можем: иметь аппаратный бэкбуфер (2 или более логических видеостраниц), и мгновенно выбирать - какая из них отображается на экране. Можем подкрутить тайминги развертки и сделать кастомное разрешение, например 320х240, или 320х400, или 360х480 и т.д. В обычном случае нам бы просто не хватило размера сегмента для доступа ко всем пикселям, ведь даже 320х240 = 76800 байт. Также мы можем установить логический размер видеостраницы больше, чем установленное разрешение, что удобно для реализации аппаратного скролла. Да, он вполне себе реализуем на любой VGA-совместимой видеокарте. Есть и другие фичи. О них мы поговорим по ходу разбора эффектов.

К вышесказанному хочу добавить, что на самом деле для 90-х годов это хоть и было экзотической технологией, но она вполне себе использовалась в геймдеве. Например в платформере Jazz Jackrabbit, многочисленных Пинболах, портах с приставок, вроде Earthworm Jim, сотнях т.н. инди-игр, и даже в оригинальном Doom.

Но давайте, что ли, посмотрим демку

FAKE MODE

Пять частей демки из семи используют так называемый "fake mode", это имитация high color в 256-цветном режиме посредством использования пикселей в роли RGB-сабпикселей. Как вы понимаете, любой монитор так и работает, просто его сабпиксели крошечные, а в нашей "эмуляции" остается довольствоваться тем, чем VGA послал. Вот здесь нам приходится кстати кастомный режим с 400 или 480 строками, вместо стандартных 200. Наше демо предлагает на выбор 4 варианта, на всякий случай (480-строчные режимы имеют частоту кадровой развертки - 60 гц, что более совместимо с современными мониторами, проекторами и ютубом).
Почему просто не использовать VESA-режимы с честными 16 или 24-битными цветами? Ну, по той же причине, почему мы используем 486-й, например... For fun! В первой половине 90-х 256 цветов все еще было стандартом, и хотя VESA 1.2 появится уже в 1991, но толком не успел войти в обиход, да и работа с high/true color все еще тяжелая задача для "четверочки". Поэтому и геймдев и демосцена не спешили расставаться с VGA. Некоторые игроделы робко пробовали использовать SVGA (640x480 c 256 цветами), но в таких играх, где не требовалось быстрой динамичной полноэкранной графики. Ну, и если всякого рода трюки с имитацией большого, чем 256 количества цветов для игр не особо подходили, то демосценерам сам Боженька велел изголяться над железом и глазами зрителя.
Позже, когда демки все же стали писать под VESA 2.0 и использовать 15/16/24-битные режимы, то "fake mode" иной раз делали опциональным, как запасной вариант, на случай, если на вашем железе новомодный режим не взлетит (что было нередкостью).

Unreal / Future Crew (1992)                    Transgression 2 / mfx (1996)                 Bill G Force / Complex (1995)

Unreal / Future Crew (1992) Transgression 2 / mfx (1996) Bill G Force / Complex (1995)

Самый банальный "fake mode" - это рисовать RGB-компоненты построчно. То есть каждый логический пиксель - это три реальных соседних пикселя в колонке: первый - красный канал, второй - зеленый, третий - синий (или в любом другом порядке, не суть) - образуют один логический пиксель. Палитру VGA задаем соответственно: цвета 0-63 - градиент красного, 64-127 зеленого, 128-191 синего. И вперед!
Картинка получается полосатой и темной, но зато вы получаете удобное представление цвета и как бы "262144 colors" одновременно. Впрочем, в случае с демосценой полосатость можно считать не минусом, а стилизацией. И знаете, наш зрительный центр мозга настолько нетребователен, что это вполне прокатывает. Вы видите не отдельные красно-зелено-синие линии, а вполне себе красочное изображение. Тем более если смотреть на мониторе 14'' (прищюрившись и отойдя на 3 метра).

Я же решил использовать в Remoded немного более сложный порядок пикселей:

Четные логические пиксели имеют форму Г, нечетные _| и они стыкуются как пазлы. Получается, что два логических пикселя занимают 3 реальных по горизонтали, и 2 по вертикали. При реальном разрешении 360х480 логическое разрешение получается 240х240. При этом, учитывая что это планарный режим, то работать мы должны с 4 планами, на которые раскидан наш кадр по горизонтали. Ну а как это выглядит - вы видели (если посмотрели демку).

1. Размытие (Blur)

Вступительная часть представляет собой разноцветные кружочки, летающие по незамысловатой траектории, которые оставляют за собой плавно размывающийся радужный шлейф. В определенные моменты прорисовываются логотипы названий групп 7dump и SandS. Вроде и пустячок, но чтобы заставить это работать на 486-м с приемлимым FPS мне все же пришлось бросить пить основательно поломать голову. Во-первых блюр. Даже самый обычный (без всяких fakemode'ов и modex'ов) блюр (усреднение по 4 соседним пикселям) довольно тяжелая задача для такого железа. В одной из прошлых статей мы реализовывали самый банальный вариант (для VGA-режима 13h и палитрой, представленной монохромным градиентом от 0 до 63), но тогда речь шла об оптимизации размера. Поэтому там мы побайтово проходили весь экранный буфер:

for y:=0 to 199 do {паскале-подобный псевдо-код для простоты понимания}
       for x:=0 to 319 do 
           buf[x,y] := ( buf[x+1, y]
                        +buf[x-1, y]
                        +buf[x, y+1]
                        +buf[x, y-1]) / 4;

Также в статье про "ротозумер" мы выяснили: чтобы добиться нормальной скорости мы должны оперировать не байтами, а "словами" (word), а еще лучше двойными словами (dword). То есть в идеале нам нужно и читать и записывать сразу по 4 байта. К счастью в случае с подобным алгоритмом блюра это делается довольно просто. Приведу код на Ассемблере:

mov es, buf
xor di, di
mov cx, 320*200/4
@L:
   mov eax,[di-320]
   add eax,[di+320]
   add eax,[di-1]
   add eax,[di+1]
   and eax, 0xfcfcfcfc
   shr eax, 2
   mov es:[di], eax
   add di, 4
dec cx
jnz @L

Здесь никаких новостей нет, не я это придумал и думаю нет смысла это разбирать. Чтож, это дает нам blur приличной скорости. Но ведь у нас не просто линейный монохромный битмап, а долбанный тетрис из RGB-пикселей, вдобавок планарный режим, как вы наверное помните.

Определимся с задачей. Нам нужно блюрить растр в буфере со сложной структурой. Прямо в видеопамяти мы этого делать не можем, поскольку чтение из VRAM очень медленное (на самом деле из видеопамяти вообще лучше никогда ничего не читать, кроме особых случаев). Поэтому нам нужен буфер в обычной памяти (RAM). При этом, нам надо копировать этот буфер в видеопамять, и делать это как можно быстрее (значит читать RAM надо линейно, чтобы эффективно работал кэш процессора). И при этом нам нужна такая структура буфера, чтобы его можно было проблюрить dword'ами (обрабатывая по 4 байта за раз).

В буфере мы будем хранить значения интенсивности цвета: 0..63, а не реальные индексы цветов, так как это вызвало бы сложности с блюром. В индексы мы их превратим позже, на стадии копирования в видеопамять. И всё же, как все это провернуть?
Интуитивно я чувствовал, что если данные в буфере представить в соответствии с планарной организацией видеопамяти, разбив на 4 группы, то и сложение RGB-компонентов соседних пикселей посредством 32-битной операции должно получиться. К тому же тогда мы сможем максимально быстро копировать буфер в видеопамять, dword'ами, а не побайтово, целиком план за планом, переключив маску всего 4 раза.
Будь я математиком, я бы наверное доказал это математически, но так как я не он, то я принялся рисовать клеточки карандашом на бумаге, пытаясь понять, как это может работать. Когда кончились карандаши и бумага, я нарисовал таблицу в Экселе. Страшную и запутанную. Но для вас я сделал красивую инфографику:

Представление логической строки изображения с разбивкой на планы

Представление логической строки изображения с разбивкой на планы

Сложим планы стопочкой и попробуем разглядеть в этом какой-то смысл:

Бинго! Видите, что получается? Догадка оказалась верна: данная структура позволяет нам оперировать 4-байтовыми блоками, RGB-компоненты и номера пикселей стыкуются и мы можем складывать соседние по горизонтали логические пиксели одной 32-битной операцией, хоть и в несколько мудренном порядке. Ну а по вертикали все просто, с ней проблемы не стояло.

Для любопытных, код, который у меня получился:

Скрытый текст
procedure BlurHorizontal(sorc,dst:word);
var 
   m, y1, y2 :word;
begin

y1:=0;
y2:=90*4;

for m:=1 to 90 do begin
asm
push ds
mov es, dst

mov si, y1
mov di, y2

mov ds, sorc
mov cx, 22

@n: 
mov eax, ds:[si]   
add eax, ds:[si+90*3]    
and eax, 0xFEFEFEFE 
shr eax, 1
mov es:[es:di+90*1], eax

mov eax, ds:[di];   
add eax, ds:[di+90*3]    
and eax, 0xFEFEFEFE
shr eax, 1
mov es:[es:si+90*2], eax

mov eax, ds:[si+90*1]
add eax, ds:[si+1]
and eax, 0xFEFEFEFE
shr eax, 1
mov es:[es:di+90*2], eax

mov eax, ds:[di+90*1]
add eax, ds:[di+1]
and eax, 0xFEFEFEFE
shr eax, 1
mov es:[es:si+90*3], eax

mov eax, ds:[si+90*2]
add eax, ds:[si+91]
and eax, 0xFEFEFEFE
shr eax, 1
mov es:[es:di+90*3], eax

mov eax, ds:[di+90*2]
add eax, ds:[di+91]
and eax, 0xFEFEFEFE
shr eax, 1
mov es:[es:si+1], eax

mov eax, ds:[si+90*3]
add eax, ds:[si+90*2+1]
and eax, 0xFEFEFEFE
shr eax, 1
mov es:[es:di+1], eax

mov eax, ds:[di+90*3]
add eax, ds:[di+90*2+1]
and eax, 0xFEFEFEFE
shr eax, 1
mov es:[es:si+91], eax

add di, 4
add si, 4
dec cx
jnz @n

pop ds
end;

y1:=y1+90*8;
y2:=y2+90*8;

end;

Что дальше? Дальше нам нужно копировать буфер в видеопамять. У нас уже подходящая структура для планарного режима, так что, устанавливаем маску для конкретного плана и загоняем каждую четвертую строку (у нас планы в буфере расположены как слоеный пирог) буфера в сегмент 0xA000. Но погодите! В буфере у нас интенсивность (числа в диапазоне 0..63). При том, что VGA-палитру мы установили в виде трех градиентов:

R- 0..63,  G - 64..127, B - 128..191,  192-255 свободны

R- 0..63, G - 64..127, B - 128..191, 192-255 свободны

Сейчас, если просто скопировать буфер в VRAM, то мы получим только оттенки красного. Нам нужно правильно "раскрасить" наши "сабпиксели", добавив к тем, что согласно построению зеленые число 64 (ну или сделать AND 64), а к тем что синие - 128. Так что нам придется не просто копировать данные (вроде rep movsd), а модифицировать. К счастью, это мы тоже можем делать 32-битной операцией (например: ADD EAX, 0x00804000), сразу для 4 байтов).
Тут нас ждет еще небольшой квест. Дело в том, что паттерн RGB - это три значения, а копируем мы dword'ами, где четыре байта. Получается RGBR, GBRG, BRGB, дальше повторяется. Ну и построчно тоже начало паттерна "плывёт". Значит, развернем код так, чтобы за один цикл выполнялось три 32-битных чтения/сложения/записи (по 12 "сабпикселей" за цикл).
Как обычно, для любопытствующих код под катом:

Скрытый текст
procedure DrawScreen(plan, position, sorc,dest:word);
var
rgb:array[0..2] of longint;
y,b,m90,y90, y90_4 :word;
rgb_0,rgb_1,rgb_2  :longint;

begin
rgb[0]:=$00804000;   {rgbr}
rgb[1]:=$40008040;   {gbrg}
rgb[2]:=$80400080;   {brgb}

Portw[SC_INDEX]:=SC_MAP_MASK + (1 shl plan) shl 8;

m90:=plan*90;
y90:=position;
y90_4:=m90;
    for y:=0 to 181 do begin

        b:= (plan + (y  and 1) shl 1);
        if b>2 then b:=b-3; {mod 3}

        rgb_0:=rgb[b]; inc(b); if b>2 then b:=0;
        rgb_1:=rgb[b]; inc(b); if b>2 then b:=0;
        rgb_2:=rgb[b];

        asm
        push ds
        push bp
        mov es, dest
        mov ds, sorc

        mov ebx, word ptr rgb_0
        mov ecx, word ptr rgb_1
        mov edx, word ptr rgb_2

        mov di, y90
        mov si, y90_4
        
        mov bp, 90/12
        @n:       
            mov eax, ds:[si]
            add eax, ebx
            mov es:[di], eax
            
            mov eax, ds:[si+4]
            add eax, ecx
            mov es:[di+4], eax
            
            mov eax, ds:[si+8]
            add eax, edx
            mov es:[di+8], eax

            add si, 12
            add di, 12
        dec bp
        jnz @n 

        mov eax, ds:[si] {дорисовываем последние 6 пикселей строки}
        add eax, ebx
        mov es:[di], eax
        mov ax, ds:[si+4]
        add ax, cx
        mov es:[di+4], ax

        pop bp
        pop ds
        end;

        y90  :=y90+90;
        y90_4:=y90_4+(90*4);
    end;
end;

Ну, и в конце концов, нам нужно что-то рисовать в буфер. Учитывая сложную нелинейную структуру это тоже не совсем тривиальная задача. Но решаемая. Очевидно, что можно заранее подготовить спрайты, чтобы они были той же структуры, и тогда остается их только линейно записывать в буфер. Рисовать что-то рассчитанное "на лету" сложнее, так как расчет позиций байтов в буфере требует 5-6 инструкций. Поэтому в демке я расчитал таблицу для логической строки, и при рисовании точки получаю из нее сразу готовое смещение в буфере. Так рисуются разноцветные кружочки.

И несмотря на все ухищрения, 486-й PC еще довольно слаб, чтобы этот эффект работал со скоростью 60/70 кадров/сек. Так что в ход пошли все грязные трюки, которые я обычно использую в таких случаях. Да, сразу понятно, что эффект не полноэкранный - активный фрейм занимает чуть больше трети (182 строки). Но и это не всё. Блюр поделен на две рутины - размытие по горизонтали и размытие по вертикали. Одна выполняется только на четных кадрах, другая на нечетных. На глаз это не заметно, конечно. Просто размытие растягивается по времени, но нас это устраивает. И это еще не все. Следующее кидалово заключается в том, что за один рендер-цикл на экране обновляется только половина столбцов. То есть в видеопамять за один проход записывается только два плана из четырёх (по четным кадрам план 0 и 2, по нечетным 1 и 3). Сам рендер-цикл не ограничен в скорости, ожидания конца вертикальной развертки нет. Так что когда производительности компьютера достаточно, то за один рефреш монитора у вас обновится весь экран целиком, а если комп слегка не вывозит, то это не слишком заметно, в этой "мозаичной размазне" вы все равно не заметите что какие-то пиксели не обновились.
Ограничен частотой развертки только блур буфера, то есть он не может выполниться большее количество раз, чем текущая частота вертикальной развертки.

Вот теперь я похоже все секреты растрепал. Но это всего лишь вступительная часть на 30 секунд.

Собако-медвед

Следом идет короткая заставочка с картинкой, которая также отрисовывается в "fake mode". Тут на самом деле не о чем рассказывать. Эмуляцию high color подобным паттерном мы уже разобрали в предыдущей главе. А интересных "колдунств" тут не применялось. Могу лишь рассказать историю этой картинки. На самом деле ей 20 с лишним лет и это плод коллективного творчества на одном из сценерских ресурсов (scene.org.ru). Если память мне не изменяет, начальный эскиз зверушки набросал Lynx из группы Crolyx. Я тогда тоже что-то дорисовал к ней... язык, нос... В общем потом это баловство ушло в небытье, вместе с сайтом. Но у меня картинка сохранилась. И вот, наши дни. Я готовлю демку Demoded (прошлогоднюю) для релиза на Assembly, до дедлайна буквально неделя, а мне нужна картинка для одного из эффектов. Любая забавная картинка. Иной юный читатель скажет: дедуля, есть же нейросети, нарисует шо хош! Но за нейронку на демосцене могут и вилкой в глаз. Нравы здесь суровые, внучок. В общем, порылся в архивах, нашел этот скетч и дорисовал, добавив фон, лапу со стаканом (отсылка к одному топику на pouet.net, но не суть...). Но для "Demoded" она не подошла, меня не вставило ее сочетание с тем видео-эффектом, так что для той демки я сделал другую картинку. А эту отложил на будущее. Ну, вот она и пригодилась в этот раз. К сожалению я не смог найти контакты Lynx'a, чтобы спросить, хочет ли он, чтобы его указали в кредитсах. Ну мало ли?.. Надеюсь он не обидится.

Собако-зумер (rotozoomer)

Вслед за обложкой идет ротозумер с той же картинкой. Он также выполнен в технике "fake mode" с тем же паттерном пикселей. Ротозумерам я посвятил целую отдельную статью, поэтому я рад, что здесь могу сэкономить время и не рассказывать о принципе и приемах оптимизации этого эффекта. Но совсем ничего не рассказывать не получится, так как все же этот ротозумер не совсем обычный, а как я уже сказал - fake-модный, и опять же, в планарном режиме. Я почти уверен, что именно такой вы не найдете ни в одной DOS-демке (и они, те парни, должны дать мне Meteoriks покая не начал вводить тарифы). В отличие от части с блуром здесь никакого буфера не используется, рутина рисует все сразу в видеопамять.
Как рендерить ротозумер для modex-режима, в котором планарная организация видеопамяти делит наш кадр на группы столбцов по модулю "4"? Я решил отрисовывать кадр не строками, а столбцами. Чтобы и интерполяция UV-координат была линейной, как обычно, а запись в видеопамять сочеталась с концепцией планов.

Взглянем еще раз на строение RGB-паттерна, на этот раз обратите внимание на образующиеся столбцы:

Мы имеем три вида столбцов - RB, GR, BG. Столбцы RB и BG содержат пары байт одного логического пикселя, это можно использовать: конвертируем картинку в два битмапа, в одном пары Red, Blue, в другом Blue,Green. Теперь мы можем по крайней мере в двух случаях из трех, при отрисовке колонки, читать из текстуры "словами" (word). Какая-никакая, но оптимизация.
В цикле отрисовки определяем - какой вид столбца из трёх нужно рисовать в данный момент, и соответственно - из какой из двух текстур читать (либо из обоих по байту).
Ну как-то так... Пара сотен строк на ассемблере.

Если сравнивать эту реализацию ротозумера с той, что описана в отдельной статье, то здесь я также добавил коррекцию с поправкой на Pixel Aspect Ratio. И 32-битную точность (fixed point 16:16), что делает вращение растра гладким, даже при очень сильном масштабировании.

Аппаратный скроллинг (hardware scrolling)

Но не ротозумером единым... Настало время поговорить об аппаратных трюках. В данной сцене вверху экрана у нас имеется текстовый скроллер. И вот он как раз реализован через аппаратную "прокрутку". Зачем? Ну зачем нужна бегущая строка текста, надеюсь, понятно. Дань традициям. До 90-х годов-то и демка не была демкой, если в ней параллельно основному эффекту не было текст-скроллера. А лучше два или три :) Зачем "аппаратный"? Чтобы минимально нагружать процессор. Мы не перерисовываем буквы каждый кадр, чтобы сместить их на экране. Мы вообще ничего не перерисовываем в этом блоке. Каждый кадр мы лишь добавляем по одной колонке буквы (глифа). Шрифт у нас высотой 32 пикселя, соответственно - в видеопамять мы записываем лишь 32 байта на кадр. А чтобы текст "ехал" мы меняем значение в регистре CRT-контроллера видеокарты определяющем стартовый адрес (Start Address register) видеопамяти для первого отображаемого на экране пикселя. Правда в нашем случае изображение будет смещаться на 4 пикселя минимум. А вот чтобы скролл был плавным у нас есть еще один регистр, в контроллере атрибутов (Horizontal Pixel Panning Register), который управляет смещением изображения по горизонтали с точностью в один пиксель (но максимум на 7 пикселей), независимо от адреса видеопамяти. Там, конечно же, все

через жопу

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

Вдумчивый читатель скажет: погоди, но ведь у тебя скроллится только верхний блок с текстом, а ротозумер стоит наместе, как и нижняя часть с шестеренками. А вот для этого у нас есть еще один "хак" - сплитинг экрана. Регистр CRT-контроллера Line Compare позволяет указать номер строки развертки дойдя до которой, видеокарта сбросит счетчик (current scan line address) и продолжит отрисовку экрана с начала видеопамяти.
Таким образом, вопреки тому, что вы видите на экране, на самом деле ротозумер расположен в видеопамяти выше блока "бегущей строки". Для лучшего понимания пожалуй нарисую схему:

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

В начале 90-х аппаратный скролл, сплитинг и прочие EGA/VGA-фокусы были описаны тем же Майклом Абрашем (великим и ужасным) и тайной не являлись. Но в PC-геймдеве аппаратный скролл использовали лишь панки, серьезные студии почему-то сторонились этих техник. Одним из первых эту фичу использовал Джон Кармак в платформере Commander Keen (он EGA-шный, но на EGA это тоже работает). Также аппаратную прокрутку можно увидеть в многочисленных пинболах, в Jazz Jackrabbit 1, The Castle (и это понятно: и многие пинболы и "зелёного зайца", и "The Castle" делали демосценеры), в некоторых портах игр с Амиги и сотнях любительских игр.

            Pinball Illusions (1992)                      Epic Pinball (1993)                   Jazz Jackrabbit: Holliday Hare (1995)                     Теперь вы знаете, почему информационная панель у них всегда внизу

Pinball Illusions (1992) Epic Pinball (1993) Jazz Jackrabbit: Holliday Hare (1995) Теперь вы знаете, почему информационная панель у них всегда внизу

Палитровая анимация (color cycling)

И еще одна "дедовская" технология, которая применяется в этой же части демки. Палитровая анимация (есть еще термин Color cycling) применялась практически на всех платформах, включая игровые приставки, когда в ходу были видео-режимы с индексированными цветами. На эту тему на хабре есть хорошая статья, советую. Суть ее в том, что мы можем изменить оттенок цвета в палитре, и все пиксели на экране, которые соответствуют данному цвету, автоматически изменят свой цвет. Выходит очень экономично, ведь процессор не перерисовывает все эти точки, а просто отправляет по три байта на цвет в порт видеокарты, дальше видяшка сама разруливает ситуацию. Так можно делать плавные затемнения (фэйды) всего экрана, или наоборот вспышки. А можно делать всякие циклические эффекты. Помните экран загрузки Windows 95/98? Вот бегущий градиент снизу как раз сделан через изменение палитры (ну, на что хватило фантазии).

Ну, а в Remoded так сделаны шестерёнки и волны на буквах. Просто посмотрите на следующие иллюстрации.

Так растр выглядит со статичной системной палитрой:

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

Ну и на буковках:

Скрытый текст

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

Ну и вишенка на торте. И текст-скроллер, и палитровая анимация подвешены на прерывание, синхронизированное с вертикальной разверткой, поэтому независимо от того с каким FPS работает ротозумер, скроллер и шестеренки всегда движутся идеально гладко, без пропуска кадров. Ведь никому не нужен тормозящий скроллер, согласитесь?

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

Если вы заметили технические ошибки и просто глупости - напишите пожалуйста комментарий. Если не заметили - все равно что-нибудь напишите, чтоб я знал, что кто-то это вообще читал :) Так же можете предложить в комментах какую-нибудь демку/эффект из эпохи DOS'a, которую стоило бы разобрать и исследовать.

Полезные ссылки

Демосцена в России:
https://retroscene.org/
https://www.demoscene.ru/
https://chaosconstructions.ru/ - фестиваль Chaos Constructions (Питер)
http://www.dihalt.org.ru/ - демопати DiHalt (Н.Новгород)
https://multimatograf.ru/ - фестиваль «Мультиматограф» (Вологда)