Быстрая графика для DOS (из бабушкиного сундука)

Дата публикации 17 сен 2002

Быстрая графика для DOS (из бабушкиного сундука) — Архив WASM.RU

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

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

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

Эта статья предлагает начальные сведения и некоторые элементарные примеры регистрового программирования видеоадаптеров, совместимых со стандартом VGA, позволяющие в предельно компактном коде добиться быстрой, несложной и достаточно эстетичной графики, позволяющей, например, создать оконный интерфейс с меню, иконками, разнообразными шрифтами и т.д. Приведенные примеры тестировались на компьютере AT 286 (не удивляйтесь откуда такое ископаемое животное - дело было еще в 94 году :smile3:) c видеоадаптером VGA 256. На современных адаптерах эти примеры до сих пор работают, хотя, конечно и не раскрывают всех их возможностей.

Так как в короткой статье невозможно охватить все, остановимся на текстовом режиме 03h (80*25, 16 цветов) (наиболее употребительном) и графическом режиме 12h (640*480, 16 цветов).

Установка видеорежима.

Тема эта кажется настолько банальной, что я позволю себе просто ограничиться примером (мало ли кто не знает :smile3:):

; На входе: ax - номер видеорежима
SetVideoMode PROC
mov ax, ;
int 10h
SetVideoMode ENDP

Кроме обращения к функции int 10h fh 0h можно переключать видеорежим и с помощью регистрового программирования и прямых обращений к памяти. Однако это достаточно трудоемкая и громоздкая операция, поэтому любопытствующих могу только отослать к документации.

Текстовый режим. Доступ к памяти

В текстовом режиме 03h видеоадаптер для вывода символов использует буфер в памяти, располагающийся по постоянному адресу 0B8000h (SEG: 0B800h, OFT: 0h). Каждая логическая ячейка этого буфера состоит из двух байт:
0 - ASCII-код символа
1 - Атрибут цветности (см. Цвет)

Ячейки располагаются непосредственно друг за другом построчно и постранично.
(Видеоадаптеры с размером памяти >=256К поддерживают 8 экранных страниц из которых в некоторый момент времени может быть отображена только одна, см. ниже)
Это означает, что символ с экранными координатами 1*1 страницы 0 будет иметь адрес 0B8000h, символ с координатами 80*25 - 0B8000h + 80 * 25 * 2 - 2, символ с координатами 0*0 страницы 1 - 0B8000h + 80 * 25 * 2.
Отсюда выводится несколько простых и удобных функций записи символов на любую экранную страницу и чтения символов с любой экранной страницы независимо от позиции курсора и активной страницы:

Вычисление физического смещения символа по заданным координатам

; На входе: al - Горизонтальная координата X (отсчет от 1)
; ah - Вертикальная координата Y (отсчет от 1)
; cl - Номер страницы (отсчет от 0)
; На выходе: di - смещение символа по заданным координатам

CharAddr PROC
; Вычисляем смещение для позиции символа
mov CH, AH
mul CH ; Перемножаем координаты X, Y
shl AX, 1 ; Умножаем результат на 2
sub AX, 2 ; Устанавливаем смещение на начало ячейки
; Вычисляем смещение страницы
mov DI, AX ; Сохраняем смещение символа в DI
push DX ; Сохраняем содержимое DX
mov AX, 4000 ; Помещаем в AX размер страницы с байтах
xor CH, CH
mul CX ; Вычисляем смещение страницы
pop DX ; Восстанавливаем содержимое DX
; Вычисляем полное смещение
add DI, AX
ret
CharAddr ENDP

* Функция не проверяет переполнение AX при выполнении mul CX, полагая, что смещение позиции символа для диапазона страниц 0..7 не может превышать размера WORD

Запись символа по заданным координатам

; На входе: al - Горизонтальная координата (отсчет от 1)
; ah - Вертикальная координата (отсчет от 1)
; dl - ASCII-код символа
; dh - Атрибут цветности
; cl - Номер страницы (отсчет от 0)

WriteChar PROC
push ES
push DI
; Помещаем в ES сегмент видеобуфера
push 0B800h
pop ES
; Вычисляем полное смещение символа
call CharAddr
; Выводим символ на экранную страницу
mov ES:[DI], DX
pop DI
pop ES
ret
WriteChar ENDP

Чтение символа по заданным координатам

; На входе: al - Горизонтальная координата X (отсчет от 1)
; ah - Вертикальная координата Y (отсчет от 1)
; cl - Номер страницы (отсчет от 0)
; На выходе: al - ASCII-код символа
; ah - Атрибут цветности

ReadChar PROC
push ES
push DI
; Помещаем в ES сегмент видеобуфера
push 0B800h
pop ES
; Вычисляем полное смещение символа
Call CharAddr
; Читаем символ с экранную страницу
mov AX, ES:[DI]
pop DI
pop ES
ret
ReadChar ENDP

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

Сохранение заданной области экрана (предполагается, что активная страница = 0; область сохраняется на странице 1).

; На входе: al - Горизонтальная координата X (отсчет от 1)
; ah - Вертикальная координата Y (отсчет от 1)
; dl - Ширина области в символах
; dh - Высота области в экранных строках

SaveRect PROC
push DS
push ES
push DI
push SI
; Помещаем в ES и DS сегмент видеобуфера
push 0B800h
pop ES
push ES
pop DS
; Вычисляем полное смещение начального символа области
xor CX, CX
Call CharAddr
; Инициализация смещений и счетчика строк
mov SI, DI
add DI, 4000 ; DI адресует символ с теми же экранными координатами, только на странице 1
push SI ; Сохраняем смещение для упрощения вычислений
mov CX, DX
shr CX, 8 ; Помещаем в CX высоту сохраняемой области
; Сохраняем область
cld
@Again: push CX
mov CX, DX
xor CH, CH
rep movsw
mov SI, [SP+4] ; Восстанавливаем начальное смещение
add SI, 160 ; Увеличиваем начальные смещения на ширину строки в байтах
mov DI, SI
add DI, 4160
mov [SP+4], SI ; Записываем начальное смещение следующей строки
pop CX ; Восстанавливаем счетчик строк
dec CX
cmp CX, 0
jnz @Again
pop SI
pop SI
pop DI
pop ES
pop DS
ret
SaveRect ENDP

В немного измененном виде приведенная выше функция может служить прототипом для операций "заливки" области экрана определенным цветом и символом, плавной прокрутки изображения и т.д.

Вывод строк текста при прямом обращении к видеопамяти представляется несколько более сложным, чем с использованием функций DOS, однако он тоже имеет ряд преимуществ:
- Независимость вывода от текущей позиции курсора.
- Возможность выводить строки с разными атрибутами цвета в пределах строки в один прием.

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

int 10h, fh 05h.
На входе:
ah = 05h
al = PageNumber (отсчет от нуля)

Работа с наборами символов и творчески переосмысленная псевдографика.

Вид символов из набора ASCII 256 может быть легко переопределен программой. Это полезно как при написании утилит, например русификаторов, так и при решении прикладных задач - при создании оконного интерфейса программы в текстовом режиме для рисования различных иконок, галочек, чекбоксиков. При более творческом подходе возможно с помощью нестандартной, определенной программистом псевдографики и рисование достаточно сложных картинок максимальным размером 16*16 уникальных символов (128*256 пикселей). Такие картинки могут служить, например, в качестве логотипа программы при ее запуске или, особенно в сочетании с переключением активных видеостраниц, могут быть использованы при написании вирусов-пугалок :smile3:. Единственное досадное ограничение - возможность в пределах одного блока изображения размером 8*16 пикселей пользоваться только двумя цветами.

Установка определяемого программой внешнего вида символов сводится к вызову функции int 10h fh 11h, с предварительной подготовкой где-либо в памяти битовой карты новых символов. Битовая карта символа представляет собой последовательность из 16 байт (при высоте символа VGA 16 пикселей и ширине в 8 пикселей).

Установка определенного программой символа

; На входе: ax - количество загружаемых символов
; dx - индекс символа
; cx - смещение битовой карты загружаемого символа (предполагается, что битовая карта располагается в ds)

SetCharBits PROC
push ES
push BP
push DS
pop DS
xor BL, BL ; Предполагается, что символ загружается в таблицу символов 0
mov BH, 16 ; Высота символа VGA 16 пикселей
mov BP, CX
mov CX, AX
mov AX, 1100h
int 10h
pop BP
pop ES
ret
SetCharBits ENDP

В графических режимах программа должна самостоятельно выводить битовые карты символов на экран (см. ниже). Для этого она может подготовить в памяти собственный набор битовых карт для всех необходимых ей символов и по мере надобности копировать из него битовые карты в видеобуфер. Однако, из соображений компактности кода или недопустимости дополнительных файлов-шрифтов (например, при написании вирусов) часто желательно воспользоваться уже готовыми и используемыми в текстовых режимах наборами символов.
Для получения адреса дополнительной таблицы битовых карт символов 128..255 достаточно получить значение вектора прерывания 1fh (0:007C), содержащего указатель на дополнительную таблицу символов. Основная таблица символов самым простым способом может быть прочитана напрямую из видеопамяти по недокументированному адресу F000:FA6E. Битовые карты символов располагаются в таблицах непосредственно одна за другой. Единственное, в чем следует убедиться программе при обращении к стандартным таблицам - выяснить высоту символов.

Цвет

Байт атрибута цветности, следующий в видеопамяти в текстовом режиме за байтом ASCII-кода символа имеет следующие битовые поля:
0..2 - Цвет символа
3 - Бит яркости символа
5..6 - Цвет фона
7 - Бит мерцания / яркости фона (в зависимости от установки адаптера).

Бит 7 может быть интерпретирован не только как бит мерцания (по умолчанию), но и как бит яркости, тем самым можно получить для фона в текстовом режиме все 16 цветов.

Так как большинство операций по изменению регистров палитры и ЦАП VGA должны производиться только в момент обратного хода луча по кадру, наиболее простые операции, не критичные ко времени выполнения, стоит производить через функции BIOS.

Одной из таких интересных функций является int 10h fh 10h
Не буду описывать все подфункции функции 10h - их можно найти в любой документации по сервису BIOS, проиллюстрирую лишь наиболее полезные, на мой взгляд, примерами:

Установка значения бита 7 байта атрибута символа для текстовых режимов.

; На входе: bl - интерпретация бита 7: 0 = Яркость, 1 = Мерцание

SetBlink PROC
mov AX,1003h
int 10h
ret
SetBlink ENDP

Переопределение значения фактического цвета в ЦАП с заданным индексом в палитре

; На входе: ax - индекс регистра ЦАП [0..255]. Для 16-ти цветных режимах при использовании палитры 0 - [0..15]
; dh - яркость красного
; ch - яркость зеленого
; cl - яркость синего


Значения яркости для совместимости с VGA должны быть 6-битными значениями. Вероятно (но не тестировалось), что в современных адаптерах поддерживается и 8 бит на значение яркости по каждому из каналов R, G и B.

SetColorBIOS PROC
push BX
mov BX,AX ; Регистр палитры должен находиться в BX
mov AX,1010h
int 10h
pop BX
ret
SetColorBIOS ENDP

* По некоторым сведениям функция int 10h, fh 10h разрушает содержимое регистров si, di, bp, sp, bx, ax. Так что при ее использовании следует соблюдать осторожность и в случае ошибок сохранять и затем восстанавливать упомянутые регистры.

Приведенный выше пример может быть реализован также и средствами регистрового программирования, путем чтения/записи регистров ЦАП 3C7h - 3C9h. Однако, эти операции рекомендуется производить в момент обратного ходы луча по кадру во избежании мерцания. Также, циклы операций чтения/записи регистра PEL-данных рекомендуется разносить по времени минимум на 240 наносекунд.

Регистры Режим записи PEL-Адреса (3C8h) и Режим Чтения PEL-Адреса (3C7h, запись) позволяют перевести регистр PEL-Данных (3C9h) в режимы записи/чтения соответственно. Одновременно устанавливается индекс регистра цвета ЦАП, для которого производятся операции чтения/записи. После установки регистра 3C8h или 3C7h программа должна произвести три последовательных операции записи/чтения в/из порта 3C9h - цикл записи/чтения. Прерывания в момент цикла должны быть запрещены. После выполнения цикла записи/чтения индекс текущего цвета автоматически увеличивается на 1. Эта особенность позволяет при чтении/записи диапазона цветов устанавливать режим и индекс цвета только один раз.
Регистр Состояние ЦАП позволяет определить, находится ли регистр PEL-данных в режиме чтения или записи. Регистр Состояние ЦАП доступен через порт 3C7h только на чтение (регистр Режим Чтения PEL-Адреса доступен только на запись). Если 2 младших бита регистра содержат 11 - ЦАП находится в режиме записи, если 00 - в режиме чтения.

Изменение цвета регистра ЦАП по индексу AL

; На входе: al - индекс регистра ЦАП [0..255]. Для 16-ти цветных режимах при использовании палитры 0 - [0..15]
; ah - яркость красного
; ch - яркость зеленого
; cl - яркость синего
Значения яркости для совместимости с VGA должны быть 6-битными значениями.

SetColorReg PROC
mov DX, 3C8h
out DX, AL
inc DX
cli
mov AL, AH
out DX, AL
mov AL, CH
out DX, AL
mov AL, CL
out DX, AL
sti
SetColorReg ENDP

Организация памяти в графическом режиме 12h

Видеопамять в этих режимах организована образом, отличным от текстового режима.
Во-первых, начало видеобуфера находится по адресу 0A0000h.
Во-вторых, память организована в виде битовых матриц. В режиме 12h этих матриц четыре. Каждому пикселю экрана соответствует один бит каждой из битовых матриц. Таким образом каждой адресуемой ячейке видеопамяти - байту - соответствует восемь пикселей. Значение цвета пикселя получается как результат объединения значений соответствующего ему бита всех битовых матриц.
К сожалению, программа не может адресовать отдельные битовые матрицы непосредственно в памяти. Запись в видеобуфер - для корректного задания цвета как отдельных пикселей в байте, так и всей строки из 8 пикселей байта - должна сочетаться с регистровым программированием. Некоторые наиболее простые и полезные приемы рассмотрены ниже.


Вычисление адреса Пикселя по экранным координатам.

Нетрудно догадаться, что адрес отдельного пикселя состоит из двух значений: смещения байта относительно начала видеобуфера, в котором находится пиксель и индекса бита внутри этого байта. Для практического применения индекс бита должен быть представлен в виде операнда к командам SHL, SHR или маски к логическим командам and, or, xor, not. Так как это задача чисто математическая и не имеет отношения к тематике статьи позволю себе оставить ее решение на совести программиста, который возьмется за программирования регистровой графики :smile3:.

Рисование с помощью регистров Графического Контроллера

Запись видеопамяти с использованием регистров Графического Контроллера позволяет выполнять большинство операций рисования и вывода символов на экран.

Регистры Графического Контроллера доступны только косвенно через порты 3CEh, 3CFh (Другие регистра Графического Контроллера 3CAh и 3CCh не представляют интереса для прикладного программиста) . Для выбора требуемого регистра следует запись его индекс в регистр Адрес Графики 1 и 2, порт 3CEh. Для быстроты установки требуемого регистра Графического контроллера может быть использован словный вариант команды OUT - out DX, AX, при этом DX должен содержать 3CEh, AL - индекс требуемого регистра, а AH - значение, записываемое в регистр.

Установка цвета отдельных пикселей.

Пример основан на комбинированном использовании регистров Режим (индекс 5) и Битовая Маска (индекс 8). Выбирается режим записи 2 регистра Режим. В этом режиме записанный в видеопамять байт будет интерпретироваться как значение цвета пикселей, разрешенных в регистре Битовая Маска. Пиксели, запрещенные в регистре Битовая Маска не изменяются. Чтобы разрешить пиксель для изменения достаточно установить соответствующий ему бит регистра Битовой Маски в 1.

; На входе: ah - битовая маска устанавливаемых пикселей
; al - значение цвета
; * Для простоты записывается первая 8-ми пиксельная ячейка видеопамяти.

SetPixels PROC
push ES
push DI
push 0A000h
pop ES
xor DI, DI
mov DX, 3CEh
push AX
mov AX, 0205h
out DX, AX ; Выбор регистра Режим и одновременная установка режима 2
mov AX, [SP]
mov AL, 8
out DX, AX ; Выбор регистра Битовая Маска и одновременная запись маски разрешенных пикселей.
pop AX
mov AH, ES:[DI] ; *
mov ES:[DI], AL
pop DI
pop ES
ret
SetPixels ENDP

* Команда mov AH, ES:[DI] служит для сохранения текущего цвета запрещенных, неизменяемых пикселей и связана с функционированием видеоадаптера: Значение цвета всех пикселей ячейки в распределении по битовым матрицам не может быть считано из памяти непосредственно. При чтении центральным процессором видеопамяти 4 байта из всех матриц автоматически помещаются процессором адаптера в так называемые регистры-защелки. При записи видеопамяти данные, передаваемые центральным процессором комбинируются со значениями этих регистров-защелок по правилам, определяемым установками регистров Графического Контроллера. таким образом, если перед записью по некоторому адресу видеопамяти не выполнить чтение по этому же адресу, то неизменяемые пиксели примут цвет пикселей последней считанной кем-либо 8-ми пиксельной ячейки.

Приведенный выше пример удобен для выполнения одноцветной заливки областей экрана, рисования линий, вывода символов с прозрачным фоном.

Другой регистр Графического Контроллера - регистр Циклический Сдвиг Данных (индекс 3) позволяется, например выводить символы в курсивном начертании, а также использовать при выводе логические операции над текущими экранными цветами и записываемым цветом. Значения битов при установке этого регистра следующие:
0..2 - Количество позиций циклического сдвига вправо
3..4 - Логические операции
00b - Запись данных без изменения
01b - And
10b - Or
11b - Xor
5..7 - Не используются

Прокрутка областей экрана, копирование областей экрана.

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

; На входе: di - смещение видеопамяти, по которому производится копирование
; si - смещение видеопамяти, по которому находится копируемая ячейка
; * Пример копирует только одну 8-ми пиксельную ячейку. Нетрудно модиицировать его для копирования/прокрутки любых областей экрана.

CopyPixels PROC
push ES
push 0A000h
pop ES
mov DX, 3CEh
mov AX, 0105h
out DX, AX ; Выбор регистра Режим и одновременная установка режима записи 1
mov AH, ES:[DI] ; Чтение ячейки в регистры-защелки
mov ES:[SI], AH ; Запись регистров-защелок в память
pop ES
ret
CopyPixels ENDP

Следует отметить, что пример эффективен только в том случае, если прокручиваемая/копируемая область по ширине и начальная координата области по горизонтали кратны 8-ми пикселям.

Чтение отдельных пикселей с экрана.

Прием основан на использовании регистров Выбор Схемы Чтения (индекс 4) и Режим (индекс 5). В зависимости от установки бита 3 регистра Режим можно выбрать 2 режима чтения. В режиме чтения 0 чтение из байта памяти возвращает байт, записанный в битовой матрице, заданной в регистре Выбор Схемы Чтения. Таким образом, чтобы считать 8-ми пиксельную ячейку экрана потребуется 4 операции чтения и, соответственно, 4 байта в области данных для сохранения прочитанной ячейки. Режим 1 более сложен для программирования и на мой взгляд менее функционален для решения простых прикладных задач, поэтому я опускаю его подробное рассмотрение.

Следующий пример демонстрирует чтение 8-ми пиксельной ячейки с координатами 0, 0 в область данных со смещением DI

; На входе: DI - смещение области данных для сохранения значений.
; SI - смещение ячейки в видеопамяти

ReadPixels PROC
push ES
push 0A000h
pop ES
mov DX, 3CEh
mov AX, 0005h
out DX, AX ; Выбор регистра Режим и одновременная установка режима чтения 0
xor AH, AH
@Again: mov AL, 04h
out DX, AX ; Выбор регистра Выбор Схемы Чтения и установка битовой матрицы AH
mov AL, ES:[SI]
mov DS:[DI], AL
inc DI
inc AH
cmp AH, 4
jnz @Again
pop ES
ret
ReadPixels ENDP

Следует заметить, что восстановление сохраненной таким образом области экрана - восстановление с использованием регистров Графического Контроллера - вызовет определенные трудности. Может потребоваться не 4 операции записи, а значительно больше - в зависимости от "разноцветья" пикселей в сохраненных ячейках. Однако, здесь нам на помощь приходит другой прием - рисование с помощью регистров Указателя Последовательности.

Рисование с помощью регистров Указателя Последовательности

Регистры Указателя Последовательности также доступны только косвенно через порт 3C5h. Индекс требуемого регистра следует записать в порт 3C4h - в регистр Адреса Указателя Последовательности.

Установка цвета ячейки пикселей.

Прием основан на использовании регистра Маскирования Растра (индекс 2), позволяющего разрешить/запретить для записи отдельные битовые матрицы.

Следующий пример демонстрирует вывод 8-ми пиксельной ячейки на экран. Предполагается, что в области данных по смещению SI находятся 4 байта ячейки, эквивалентные полученным в результате Чтения ячейки экрана в предыдущем примере. Следует отметить, что формат регистра AH, идентифицирующего текущую битовую матрицу отличается от предыдущего примера. Матрица в регистре Выбор Схемы Чтения задается обычным числовым индексом (в битах 0..2) матрицы. В регистре Маскирования Растра матрица задается с помощью битовой маски:
Бит 0 - Матрица 0
1 - Матрица 1
2 - Матрица 2
3 - Матрица 3
Остальные биты регистра не используются.
Установка бита в 1 разрешает запись в Матрицу, сброс бита - запрещает.

; На входе: SI - Смещение области данных, по которому располагаются
; подготовленные 4 байта ячейки.
; DI - Смещение в видеопамяти, по которому записывается ячейка

WritePixels PROC
push ES
push 0A000h
pop ES
mov DX, 3C4h
mov AH, 01h
@Again: mov AL, 02h
out DX, AX ; Выбор регистра Маскирования Растра и установка битовой матрицы AH
mov AL, DS:[SI]
mov ES:[DI], AL
inc SI
shl AH, 1
cmp AH, 10h
jnz @Again
mov AX,0F02h
out DX,AX ; *
pop ES
ret
WritePixels ENDP

* Запись в регистр Маскирования Растра, осуществляемая командами mov AX, 0F02h; out DX, AX разрешает запись во все битовые матрица. Необходимость выдачи этих команд обусловлена тем, что установки регистра Маскирования Растра действуют для всех режимов записи (в том числе для записи через регистры Графического Контроллера, рассмотренной выше).

Регистр Маскирования Растра кроме того очень удобен для выполнения операций только над определенными цветами определенных пикселей. Режим записи 0 регистра Режим Графического Контроллера позволяет комбинировать установки регистра Маскирования Растра (разрешающего матрицы) и регистра Битовой Маски (разрешающего пиксели)
Следующий фрагмент устанавливает яркость каждого второго пикселя ячейки не изменяя цвет (при стандартной настройке палитры):

mov DX 3C4h
mov AX, 0802h ; Разрешаем битовую матрицу 3
out DX, AX
mov DX, 3CEh
mov AH, 01010101b ; Маскируем пиксели
mov AL, 8
out DX, AX
mov AX, 0005h ; Устанавливаем режим записи 0
out DX, AX
mov AL, 0FFh
mov AH, ES:[DI]
mov ES:[DI], AL ; Изменяем цвет

Графический режим 13h

Графический режим 13h (320*200 пикселей, 256 цветов) представляется значительно более простым для программирования. Видеобуфер в этом режиме располагается также по адресу 0A0000h, однако каждому экранному пикселю соответствует один байт видеобуфера. Иными словами, запись в видеопамять байта означает установку цвета (в диапазоне 0..255) экранного пикселя. Так как байты-пиксели располагаются в памяти последовательно, непосредственно один за другим, вычисление адреса пикселя в памяти по заданным координатам полностью эквивалентно вычислению адреса символа в текстовом режиме.


Конечно, это описание возможностей регистрового программирования видеоадаптеров далеко не полно. Целью данной статьи было дать возможность читателю быстро и легко оснастить программу наиболее существенными в большинстве практических приложений графическими функциями, а также показать на примерах, что регистровое программирование далеко не так сложно, как может показаться неискушенному кодеру на первый взгляд. А при более глубоком ознакомлении с документацией по этому вопросу можно творить с обычным видеоадаптером почти фантастические вещи :smile3:

© gadyuka (Микаэлла Клюева)

12 6.288
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532

Комментарии


      1. Andrei 15 апр 2018
        Итоговая функция для Turbo C ...

        void PutChar ()
        {
        int sm=((((y-1)*40)+x)*2)-2;
        asm {
        mov ax,1
        int 10h
        //
        // mov al,40
        // mov ah,25
        mov dl,78h
        mov dh,44
        mov cl,1
        // mov ch,ah
        // mul ch
        // shl ax,1
        // shl ax,1
        // sub ax,2
        // push di
        push di
        mov di,sm
        push dx
        mov ax,2048
        xor ch,ch
        mul cx
        pop dx
        add di,ax
        // ret
        push es
        // push di
        mov ax,0B800h
        mov es,ax
        // pop es
        // jmp CharAddr
        // mov di,2100;
        mov es:[di],dx
        pop di
        pop es
        // ret
        }
        return ;
        // CharAddr:
        // asm {
        // }
        }
      2. Andrei 15 апр 2018
        Итоговая формула :
        int sm=((((y-1)*40)+x)*2)-2;
        Где 40, - количество столбцов экрана
      3. Andrei 15 апр 2018
        Вот так работает
        Формула вычисления смещения ...
        int sm=((((y-1)*40)+x)*2)-2;

        Вот отладочный код

        #include <stdio.h>
        #include <conio.h>
        int x=20;
        int y=10;
        void PutChar ()
        {
        int sm=((((y-1)*40)+x)*2)-2;
        asm {
        mov ax,1
        int 10h
        mov al,40
        mov ah,25
        mov dl,78h
        mov dh,44
        mov cl,0
        mov ch,ah
        mul ch
        shl ax,1
        // shl ax,1
        sub ax,2
        // push di
        mov di,sm
        push dx
        mov ax,2048
        xor ch,ch
        mul cx
        pop dx
        add di,ax
        // ret
        push es
        // push di
        mov ax,0B800h
        mov es,ax
        // pop es
        // jmp CharAddr
        // mov di,2100;
        mov es:[di],dx
        // pop di
        pop es
        // ret
        }
      4. Andrei 15 апр 2018
        sm=((y-1)*40+x)*2-2 для режима 1h (40x25)
        sm=((y-1)*80+x)*2-2 для режима 3h (80x25)

        Только вот непонятно, что размер страницы ....у меня получилось что размер страницы 0
        равен 2048
      5. Andrei 15 апр 2018
        Вроде как смещение будет так
        sm=((y-1)*40+x)*2 для режима 1h (40x25)
        sm=((y-1)*80+x)*2 для режима 3h (80x25)
        Но я пока не пробовал, возможно еще нужно подправить
      6. Andrei 14 апр 2018
        В режиме 1h , 40 на 25, (мне именно этот режим нужен) Правильно выставляется позиция символа
        при (x,y) =1,1 и правильно в принципе работает при X=40, при любом у. А в остальных случаях бредятина, ...
      7. Andrei 14 апр 2018
        Немного прояснилось, и почти заработало, и на любую страницу пишет, и переключается нормально,
        неправильно вычисляется позиция символа на странице (
        Пока ищу закономерности, в координату 1,1 ставиться нормально, а вот дальше белиберда, по вертикали вообще не реагирует, да и по горизонтали как то странно, ...
      8. Andrei 14 апр 2018
        Не получается, хотя должно работать это точно ...
        Вот так я делал, компилируется, либо зависает, либо выводит символ на страницу 0, а я в cl 1 указывал.
        Что то рядом но никак, подскажите как правильно оформить все это дело в виде С функции ...
        У меня Turbo C ver. 3.2 Dos.
        void PutChar()
        {
        asm{} // Установка видео режима, очистка экрана
        asm{} // Заносим исходные данные в регистры al,ah,dl,dh,cl из переменных
        asm{} //вывод символа по заданным координатам
        Return (0); // Выход из С функции
        CharAddr: // Метка подпрограммы asm
        asm{} //вычисление физического адреса
        }
      9. yashechka 13 апр 2018
        Наверное ваши коммы никто не видит, я их случайно увидел.
      10. Andrei 13 апр 2018
        Если я сделаю так ...
        mov ax,0b800h
        push ax
        pop es

        Так будет правильно ?
      11. Andrei 13 апр 2018
        Если я сделаю так ...
        mov ax,0b800h
        push ax
        pop es

        Так будет правильно ?
      12. Andrei 13 апр 2018
        Все перепробовал, никак не пойму в чем, дело, могу криншот выслать, помогите разобраться в чем дело ...
      13. Andrei 13 апр 2018
        Я правильно понимаю что push 0b800h; ложит значение буфера в стек, а pop es; извлекает его в регистр es?? У меня Turbo C 3.2. dos, не хочет компилировать push 0b800h пишет instruction not enabled. Подскажите пожалуйста в чем дело, redd@samtel.ru ...
        Очень интересует запись в неактивные страницы в текстовом режиме ...