Подпрограммы для построения строк
Универсальная подпрограмма пересылки
Давайте вернемся к постановке вопроса. Задана строка, содержащая N точек, код каждой из них занимает м байтов. Эту строку надо скопировать из одного места памяти в другое. Мы специально употребили выражение "место памяти", поскольку пересылка из оперативной в видеопамять является частным случаем.
При такой формулировке задачи напрашивается очевидный способ ее решения. Надо вычислить количество байтов в строке (L = N * м) и переслать L байтов из одного места памяти в другое. Обратите внимание, в результате умножения мы избавились от размера кода точки и при составлении подпрограммы учитывается только количество пересылаемых байтов.
Частные случаи решения такой задачи обсуждались уже несколько раз. Наиболее подробно был рассмотрен один из них при описании способов ускорения рисования линии в режимах PPG (примеры 3.8-3.10). Остается собрать указанные примеры в одну подпрограмму, включив в нее вычисление количества байтов в строке. Это и сделано в примере 7.12.
Пример 7.12. Универсальный (цикл пересылки) способ построения строк.
drawl ine: push dx сохранение содержимого регистра dx xchg ax, ex обмен содержимого регистров (ах = N) mul bytppnt dxrax = L =-N * М xchg CX j 3X обмен содержимого регистров (сх = L) pop dx восстановление содержимого регистра dx drawalt: push dx сохранение содержимого регистра dx mov dx, di копирование адреса в регистр dx add dx, ex dx = исходный адрес + L jc @F › прямая расположена в двух окнах xor dx, dx остаток в dx равен нулю @@: sub ex, dx количество байтов в текущем окне call moveto строим первую часть строки mov ex, dx сх = оставшееся количество байтов pop dx восстановление содержимого регистра dx or di, di адрес в пределах текущего окна? jne d exit › да, строка построена полностью call NxtWin установка следующего окна moveto: shr ex, 01 преобразуем байты в слова jnc @F › четное число байтов movs byte ptr [di], fs:[si]; пересылка одного байта @@: shr ex, 01 преобразуем слова в двойные слова jnc @F › четное число слов movs word ptr [di], fs:[si]; пересылка одного слова @@: je d exit; › пересылать больше нечего rep movs dword ptr [di], fs:[si]; основной цикл пересылки d exit: ret; возврат из подпрограммы
Выполнение примера 7.12 начинается с вычисления количества байтов в строке. При умножении используются регистры dx и ах, поэтому содержимое dx сохраняется в стеке, а содержимое ах – за счет двухкратного использования команды xchg. Произведение находится в регистрах dx:ax. Мы будем считать, что оно меньше чем 65 536, т. е .dx содержит 0. Команда обмена xchg помешает результат в сх, одновременно восстанавливая исходное состояние ах, а из стека выталкивается исходное содержимое регистра dx.
Если количество байтов в строке известно заранее, то заново вычислять его не имеет смысла. Оно указывается в регистре сх, а для вызова подпрограммы используется вторая точка входа, имеющая имя drawait.
Команда с меткой drawait сохраняет в стеке содержимое регистра dx, затем в него копируется адрес видеопамяти, который увеличивается на размер строки в байтах. Если при сложении произойдет переполнение (установка С-разряда), то команда jc @F исключает очистку dx. В противном случае строка помещается в текущем окне и регистр dx очищается. Затем вычисляется количество выводимых точек, и подпрограмма moveto строит первую часть строки.
После построения первой части строки в регистр сх копируется содержимое dx (остаток строки). Регистр dx освободился и надо восстановить его исходное состояние. Для выбора дальнейших действий проверяется текущий адрес в регистре di, если он отличен от нуля, то построение строки завершено и выполняется команда ret. В противном случае устанавливается следующее окно видеопамяти, и подпрограмма moveto строит остаток линии. После ее выполнения завершится работа основной подпрограммы, т. к. в верхушке стека находится адрес возврата на вызывающий модуль.
В подпрограмме moveto команда rep movs dword ptr [di], f s: [si] является основной, она пересылает группу 32-разрядных слов. Однако количество байтов в строке не обязательно кратно четырем. Поэтому нужна предварительная проверка и пересылка от одного до трех "лишних" байтов, так чтобы остаток был кратен четырем.
Команда, имеющая метку moveto, сдвигает содержимое регистра сх на разряд вправо. Если оно было нечетным, то пересылается первый байт строки, в противном случае jnc @F исключает эту пересылку.
Затем содержимое сх еще раз сдвигается на разряд вправо. Если оно было нечетным, то пересылается слово (два байта строки), в противном случае jnc @F исключает эту пересылку.
В результате выполнения двух сдвигов и пересылки "лишних" байтов в строке остается целое число 32-разрядных слов, количество которых находится в регистре ex. Если оно равно нулю, то произойдет переход на команду ret, в противном случае выполняется микропрограммный цикл копирования 32-разрядных слов. После этого выполняется команда ret.
Вторая точка входа drawait введена для тех случаев, когда известен размер строки в байтах. Приведем несколько примеров таких случаев. При работе в режимах PPG количество байтов совпадает с количеством точек. Если размер строки равен ширине экрана, то ее размер в байтах указывает переменная bperiine и вычислять его нет смысла. При построении рисунка, содержащего большое количество строк, целесообразно один раз вычислить размер строки в байтах, а не повторять одни и те же вычисления при построении каждой строки. Наконец, возможны также случаи, когда количество байтов вычисляется нестандартным способом, например, зависит от определенных условий.
Обсуждение результатов
Описанная подпрограмма не только не зависит от размера кода точек, но и затрачивает минимально возможное время на построение строки, что особенно важно при работе в режимах direct color. Поэтому мы советуем использовать именно ее в тех случаях, когда возможна простая пересылка строк графических объектов.
Подпрограмма может записывать в видеопамять не более чем 65 536 байтов. Это ограничение связано с тем, что источник или преемник расположен в оперативной памяти. Мы исходили из предположения, что он полностью помещается в одном сегменте, и поэтому исключили контроль адресов. Если же это условие нарушено, то в подпрограмму придется добавить контроль адресов и переключение сегментов оперативной памяти. Способ переключения сегментов зависит от того, в какой части оперативной памяти они расположены (см. Приложение Б).
Если при работе со строкой выполняется не простое копирование, а более сложные действия, то для сокращения количества проверок адресов видеопамяти можно использовать работу с двумя частями строки. Алгоритм работы с двумя частями приведен в примере 7.6 (подпрограмма Twopart). Для выполнения конкретных действий надо составить подпрограмму baselp, которая в примере 7.6 вызывается для обработки каждой из двух частей строки. Например, такая подпрограмма может считывать код очередной точки, как-то обрабатывать его и возвращать результат на старое место. Вопрос о целесообразности работы с двумя частями строки решается с учетом конкретных особенностей алгоритма работы с графическим объектом.