ЦАП и немного фантазии

ЦАП и немного фантазии

Сокращение DAC произведено от Digital to Analog Converter. По-русски это означает цифро-аналоговый преобразователь (ЦАП). Так называется часть видеоадаптера, преобразующая цифровые сигналы к аналоговому виду. Впервые DAC появились в VGA-адаптерах фирмы IBM. Эта новинка сопровождала выпуск машин серии PS/2 с процессорами i286. Интересно, что в PS/2 VGA-адаптеры встраивались в материнскую плату компьютера. Практика изготовления VGA-адаптеров в виде плат расширения - следствие производства клонов VGA.

VGA впервые ввел в обиход графический режим с разрешением 320х200 точек и 256 цветами, одновременно отображаемыми на экране. Палитра, из которой они выбирались, составляла 262,144 цвета. Этот режим и поныне остается стандартом. Когда за дело берутся независимые производители, они, предлагая дополнительные, то есть выходящие за прежние рамки возможности, обязательно соблюдают некоторый фактический стандарт.

Сейчас на смену режиму 320х200х256, кажется, приходит 640х480х256 с палитрой 16,777,216 цветов. Конечно, режим 640х480х256, как и все нововведения, уже успел подвергнуться стандартизации, скажем, со стороны VESA. Однако VESA описывает интерфейс к расширенным режимам лишь на макроуровне, примерно так, как это сделано в BIOS. Описание не касается архитектуры адаптера.

Программист, работающий с любым VGA-адаптером на уровне регистров, может быть уверен лишь в его стандартных режимах. Дополнительные режимы зачастую оказываются гораздо менее "сговорчивыми", поскольку при их реализации производители вышли за рамки IBM VGA, причем каждый по-своему.

Поэтому те, кто "выжимает" из адаптера все, на что тот способен, прибегают к непосредственному управлению им на уровне портов и регистров и вынужденно ограничиваются стандартными VGA-режимами - для гарантии переносимости своих программ. Так вот, IBM VGA играет роль стандарта де-факто.

До VGA компьютерные мониторы были цифровыми. Одновременно с VGA появились мониторы аналоговые, состоящие в близком родстве с телевизионными. Цифровой способ кодирования цвета не мог обеспечить цветопередачу 0.26-миллионной палитры. Данная перемена и вызвала к жизни DAC. VGA-мониторы управляются тремя цветовыми сигналами (RGB) и сигналами вертикальной и горизонтальной развертки.

Программируя, мы привыкли, что цвета обозначаются номерами. Иногда даже забываешь, что цвет с номером 0 необязательно должен быть черным - просто такова настройка DAC по умолчанию. Перенастраивая DAC, то есть программируя цвета, можно добиться интересных и неожиданных эффектов. Многие из них было бы весьма затруднительно, а то и невозможно получить иным способом. Я считаю, что непосредственно работать с регистрами DAC и проще, и эффективнее, чем прибегать к услугам BIOS для программирования палитры.

Давайте для разминки сделаем черный цвет (0) белым, а белый (7) - черным.

C:\MYDEBUG
-A
0D84:0100 MOV DX,03C8
0D84:0103 XOR AL,AL
0D84:0105 OUT DX,AL
0D84:0106 MOV DX,03C9
0D84:0109 MOV AL,FF
0D84:010B CLI
0D84:010C OUT DX,AL
0D84:010D JMP 010F
0D84:010F OUT DX,AL
0D84:0110 JMP 0112
0D84:0112 OUT DX,AL
0D84:0113 STI
0D84:0114 MOV DX,03C8
0D84:0117 MOV AL,07
0D84:0119 OUT DX,AL
0D84:011A MOV DX,03C9
0D84:011D XOR AL,AL
0D84:011F CLI
0D84:0120 OUT DX,AL
0D84:0121 JMP 0123
0D84:0123 OUT DX,AL
0D84:0124 JMP 0126
0D84:0126 OUT DX,AL
0D84:0127 STI
0D84:0128 INT 20
0D84:012A
-RCX
CX: 0000 2A
-N TSTDAC.COM
-W
-Q
C:\MYTSTDAC
Правда здорово? Кстати, вы знаете, какой разрядности ваш DAC? Дело в том, что первые DAC были 18-разрядными, по 6 битов на каждый из основных цветов. Потому их палитра и равнялась 256К. Это у 24-разрядных DAC, имеющих по 8 битов на красный, зеленый и синий цвет, палитра 16.7 миллиона.

Выяснить, какой разрядности DAC, несложно: измените приведенную программу так, чтобы 7-й цвет получал значения R, G, B, равные 3F (6 битов). Для этого в строке по адресу 0D84:011D вместо XOR AL,AL напишите MOV AL,4F. В случае 8-битового DAC'а 0-й цвет со значениями R, G, B FF (8 битов) должен быть ярче 7-го цвета со значениями R, G, B 3F. Хоть и с трудом, но при нормально отрегулированном мониторе можно увидеть разницу между ярко-белым фоном и чуть менее ярким текстом. Я на своей машине ее увидел. Если DAC 6-битовый, то 2 старших бита из FF будут проигнорированы, цвет фона и текста совпадет - экран станет однородно белым.

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

Для чего же может понадобиться смена палитры? Во-первых, именно так достигается плавное появление/исчезновение картинки на экране - эффект, почитаемый изготовителями игрушек. Во-вторых, если вы захотите вывести на экран картинку из графического файла, то волей-неволей должны будете настроить цвета правильным образом. В конце концов, дайте волю фантазии, развив известные идеи, или придумайте что-нибудь свое.

Одно из интереснейших и очень эффектных применений палитры заключается в оживлении, то есть анимации статичной картинки. Элементарная иллюстрация идеи такова: представьте, что на экране есть надпись длиною в N символов. Каждый символ имеет цвет с номером от 0 до N, соответствующим его месту в строке. Все цвета настроены одинаково, скажем, по схеме 7-0-0 (умеренно красный). Организуйте цикл по К от 1 до N, в котором К-й цвет устанавливается равным FF-FF-FF (ярко-белый), а цвет К-1 получает значение 7-0-0. В результате прогона вы увидите, как по красной строке пробежит белый блик, остановившись на последнем символе.

Можно поступить иначе: нарисовать ряд концентрических колец по-разному настроенными цветами с последовательными номерами, а потом начать "вращать" палитру. "Вращать" означает настроить К-й цвет как цвет К-1; К-1 - как К-2; К-2 - как К-3; ... К-й - как 1-й и повторять описанный цикл постоянно. Будет казаться, что кольца, в зависимости от направления вращения, надвигаются или удаляются. Выглядит это как "полет внутри трубы" - хорошо известный в компьютерной графике прием. Если перед вращением цвета были настроены, чтобы образовывать плавный цветовой переход, то зритель увидит не движение отдельных колец, а мягко движущиеся переливы.

Кстати, при вращении палитры во избежание возникновения "снега" рекомендуется писать в регистры DAC во время обратного хода развертки, то есть междукадрового промежутка времени.

Хоть я и не собирался сначала так поступать, нo все же напишу низкоуровневую вставку и приведу пример на QBASIC. Вот код вставки: 0D84:0100 DB 0 0 0 0 0D84:0104 PUSH BX 0D84:0105 CALL 0108 0D84:0108 POP BX 0D84:0109 PUSH AX 0D84:010A PUSH DX 0D84:010B MOV DX,03DA 0D84:010E IN AL,DX 0D84:010F TEST AL,08 0D84:0111 JZ 010E 0D84:0113 MOV DX,03C8 0D84:0116 MOV AL,[BX-08] 0D84:0119 CLI 0D84:011A OUT DX,AL 0D84:011B MOV DX,03C9 0D84:011E MOV AL,[BX-07] 0D84:0121 OUT DX,AL 0D84:0122 JMP 0124 0D84:0124 MOV AL,[BX-06] 0D84:0127 OUT DX,AL 0D84:0128 JMP 012A 0D84:012A MOV AL,[BX-05] 0D84:012D OUT DX,AL 0D84:012E STI 0D84:012F POP DX 0D84:0130 POP AX 0D84:0131 POP BX 0D84:0132 RETF Пожалуй, здесь уместны несколько замечаний по поводу приведенного кода. Во-первых, для примера на QBASIC вовсе не обязательно сохранять и восстанавливать значения регистров AX, BX и СХ, но лучше писать коды вставок универсальными, подходящими для применения в среде любого языка программирования и даже в теле процедур обработки прерываний. Во-вторых, вставка содержит цикл, гарантирующий, что запись регистров DAC будет произведена во время обратного вертикального хода луча. В-третьих, назначение первых четырех байтов кода следующее: нулевой - номер цвета, первый - красная составляющая (R), второй - зеленая составляющая (G), третий - синяя составляющая (B).

В приведенном ниже примере на QBASIC появляется и исчезает приветственная надпись, после чего программа завершается. Вот ее текст: SCREEN 13 WAIT &H3DA, 8 OUT &H3C8, 7 OUT &H3C9, 0 OUT &H3C9, 0 OUT &H3C9, 0 REM ** 7-й цвет стал черным COLOR 7 PRINT "Привет от VGA DAC!" REM ** Надпись не видна FOR mycolor% = 0 TO 63 WAIT &H3DA, 8 OUT &H3C8, 7 OUT &H3C9, mycolor% OUT &H3C9, mycolor% OUT &H3C9, mycolor% NEXT mycolor% REM ** Надпись появилась SLEEP 3 FOR mycolor% = 63 TO 0 STEP -1 WAIT &H3DA, 8 OUT &H3C8, 7 OUT &H3C9, mycolor% OUT &H3C9, mycolor% OUT &H3C9, mycolor% NEXT mycolor% REM ** Надпись исчезла У этой программки, где запись в порты DAC производится стандартными средствами QBASIC, есть свои закавыки. Например, значения R, G, B имеют по 64 градации, составляя стандартную VGA-палитру из 262,144 цветов.

Заметьте - несмотря на то, что в моей машине в действительности не 6-, а 8-разрядный DAC и проба на ассемблере прошла успешно. Поставив в циклах граничное значение 255 вместо 63 добиваешься только того, что последовательность 0 - 63 повторяется 4 раза.

По-видимому, QBASIC контролирует видеоадаптер в большей степени, чем можно было ожидать.

Есть и другое затруднение неожиданного свойства. Пример прекрасно работает в текстовом видеорежиме, установленном по умолчанию. Проверьте этот факт, закомментировав операторы SCREEN 13 и COLOR 7. Когда пытаешься перевести программу в режим 320х200х256 при помощи SCREEN 13, она отказывается функционировать, пока не поставишь еще и COLOR 7. Впрочем, справиться с примером удалось не более чем за 20 минут.

А вот пример, где был бы использован код приведенной выше вставки, у меня не заработал, хотя отладка заняла пять или шесть часов.

То, что управление действительно передавалось коду вставки и возвращалось обратно QBASIC, факт неоспоримый. Возникшее подозрение, что код вставки неработоспособен сам по себе, было исключено его прогоном в отладчике. Таким же прогоном было отвергнуто предположение, будто не работает использованный способ построения позиционно-независимого кода, ранее не подводивший меня никогда.

Остаются, правда, сомнения по поводу правильности адресации к данным вставки в среде самого QBASIC, явно отличной от среды, создаваемой отладчиком, но можно проверить и это. Например, встроить в программу такой фрагмент кода: 1CE8:0100 DB 00 00 00 00 1CE8:0104 PUSH BX 1CE8:0105 CALL 0108 1CE8:0108 POP BX 1CE8:0109 PUSH AX 1CE8:010A PUSH DX 1CE8:010B MOV AL,01 1CE8:010D MOV [BX-08],AL 1CE8:0110 INC AL 1CE8:0112 MOV [BX-07],AL 1CE8:0115 INC AL 1CE8:0117 MOV [BX-06],AL 1CE8:011A INC AL 1CE8:011C MOV [BX-05],AL 1CE8:011F POP DX 1CE8:0120 POP AX 1CE8:0121 POP BX 1CE8:0122 RETF Как видите, в нем AL принимает значения 1, 2, 3, 4, которые заносятся в область данных. Программа на QBASIC обращается в коду, он инициализирует байты области данных, значения которых оттуда извлекаются и распечатываются. Чтение кода вставки и операторы DATA опущены.

DEF SEG = VARSEG(a%(0)) CALL ABSOLUTE(VARPTR(a%(0)) + &H4) k% = PEEK(VARPTR(a%(0))) l% = PEEK(VARPTR(a%(0)) + 1) m% = PEEK(VARPTR(a%(0)) + 2) n% = PEEK(VARPTR(a%(0)) + 3) DEF SEG PRINT k%, l%, m%, n% Нетрудно видеть, что выполняющиеся операции прямо противоположны тем, которые производятся в программе. В самом деле, там значения заносятся в область данных, а затем используются кодом вставки. В проверочном примере - наоборот: область данных заполняется кодом вставки, а используется программой.

Пропустив контрольный пример, я с искренним удивлением обнаружил: цифр 1, 2, 3, 4 на экранe действительно нет. Только тогда наконец-то понял, что ошибка должна скрываться в коде вставки.

Согласившись списать свою неудачу на таинственные свойства QBASIC и происки Microsoft, я повел себя как юный школяр. Дело в том, что в коде вставки осталось не определенным значение сегмента данных! Проще всего поправить ошибку, указав префикс сегмента перед командами пересылок.

Ошибка была исправлена, и все получилось так, как должно было быть с самого начала. Произошедшая история наглядно показывает суть одного из неписанных законов программирования, гласящего, что если программа не работает, в 99% случаев вина лежит на разработчике, совершившем ошибку. Правда, как и все другие неприятные истины, этот закон чаще всего хранится на задворках сознания.

Наконец предлагаю вашему вниманию окончательный текст программы управления DAC при помощи вставки кода. Обратите внимание, что она стала работать быстрее, чем предыдущая, использовавшая стандартные средства QBASIC.

DIM a%(27) DEF SEG = VARSEG(a%(0)) FOR i% = 0 TO 54 READ d% POKE VARPTR(a%(0)) + i%, d% NEXT i% DEF SEG SCREEN 13 DEF SEG = VARSEG(a%(0)) POKE VARPTR(a%(0)), 7 POKE VARPTR(a%(0)) + 1, 0 POKE VARPTR(a%(0)) + 2, 0 POKE VARPTR(a%(0)) + 3, 0 CALL ABSOLUTE(VARPTR(a%(0)) + 4) DEF SEG SLEEP 1 COLOR 7 PRINT "Привет от VGA DAC!" DEF SEG = VARSEG(a%(0)) FOR clr% = 0 TO 63 POKE VARPTR(a%(0)) + 1, clr% POKE VARPTR(a%(0)) + 2, clr% POKE VARPTR(a%(0)) + 3, clr% CALL ABSOLUTE(VARPTR(a%(0)) + 4) NEXT clr% DEF SEG DEF SEG = VARSEG(a%(0)) FOR clr% = 63 TO 0 STEP -1 POKE VARPTR(a%(0)) + 1, clr% POKE VARPTR(a%(0)) + 2, clr% POKE VARPTR(a%(0)) + 3, clr% CALL ABSOLUTE(VARPTR(a%(0)) + 4) NEXT clr% DEF SEG SCREEN 0 REM ************************** DATA &H00, &H00, &H00, &H00 DATA &H53, &HE8, &H00, &H00 DATA &H5B, &H50, &H52, &HBA DATA &HDA, &H03, &HEC, &HA8 DATA &H08, &H74, &HFB, &HBA DATA &HC8, &H03, &H2E, &H8A DATA &H47, &HF8, &HFA, &HEE DATA &HBA, &HC9, &H03, &H2E DATA &H8A, &H47, &HF9, &HEE DATA &HEB, &H00, &H2E, &H8A DATA &H47, &HFA, &HEE, &HEB DATA &H00, &H2E, &H8A, &H47 DATA &HFB, &HEE, &HFB, &H5A DATA &H58, &H5B, &HCB Надеюсь, что игра с палитрой VGA доставит вам удовольствие.

Евгений Щербатюк


Компьютерная газета. Статья была опубликована в номере 13 за 1997 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета