Приветствую всех ценителей больших пикселей и выпуклых мониторов. Мы продолжаем разбираться с технологиями демосцены 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.

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

Истинные ценители могут скачать само демо:
https://www.pouet.net/prod.php?which=105924
https://files.scene.org/view/parties/2026/revision26/oldskool-demo/remoded.zip

На реальном железе проблем, скорее всего, не будет (читайте txt-файл, если что).
Если используете DOSBox, то рекомендую форк DOSBox Staging (cycles лучше установить не менее 40 000).

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)

Самый банальный "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/ - фестиваль «Мультиматограф» (Вологда)

Демосценерские ресурсы (международные):
https://www.pouet.net/ - новые демо-релизы и основная тусовка западной сцены
https://www.demoparty.net/ (календарь мировых демопати)
https://demozoo.org/ - новости (жиденькие) и каталог всех релизов мировой сцены https://www.youtube.com/@psenough/videos - еженедельные обзоры жизни демосцены

Старый добрый DEMO.DESIGN FAQ (материалы из FidoNet и прочий стаф из 90-х):
https://www.enlight.ru/demo/faq/
https://www.enlight.ru/faq3d/content.htm

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


  1. Manwe_SandS
    04.05.2026 09:45

    <3