Архитектура и программирование компьютера Vectrex

 — А видеовыход у него есть?
 — И как ты себе это представляешь?
(из разговора о Vectrex)

b102f47bda3f45d2b092656405537b49.jpgVectrex выпускался GCE в 1982 — 1983 гг. и представляет собой игровой компьютер (приставку) ключевая особенность которой, векторный дисплей, делает его одним из самых необычных и интересных 8-разрядных компьютеров. С некоторой натяжкой можно сказать, что он является упрощённой версией векторных игровых автоматов Cinematronics, технически более совершенных.

В качестве процессора в Vectrex используется Motorola 6809 — он похож на MOS 6502/6510, но добавлены 16-битные регистры, дополнительные режимы адресации, умножение.
Тактовая частота — 1.5MHz.

Поскольку компьютер был выпущен как игровая приставка и игры для него продавались на картриджах, программа размещается в ПЗУ картриджа (32 кб), а ОЗУ — совсем крохотное (1 кб — две штуки 2114) и предназначено больше для данных.
Также есть встроенное ПЗУ с BIOS’ом (8 кб — одна 2363), который включает набор подпрограмм для рисования векторов и вывода текста, несколько примитивных мелодий и даже одну игру — Minestorm (многим известную как Asteroids).

Звук реализован на чипе AY8912 (также используется в MSX2 и поздних ZX Spectrum) однако, кроме этого существует штатная возможность проигрывания 8-битного звука через ЦАП (практическое применение этого способа, впрочем, ограничено).

Vectrex выполнен в виде моноблока (включающего ЭЛТ экран), но клавиатура не предусмотрена в принципе. Управление осуществляется двумя джойстиками (в т.ч. аналоговыми). Кроме того, может быть подключено световое перо и очки 3D Imager.

С 1982 г. по нынешнее время для Vectrex написали примерно полторы сотни игр, несколько серьёзных программ (типа редакторов графики, музыки, анимации), а также около десятка демо и интро. Интересно, что более половины игр (и все демо) выпущены после 1995 года, т.е. через десятилетие после прекращения производства и поддержки Vectrex. Возрождение платформы связано, в первую очередь, с появлением хороших эмуляторов, которые сделали разработку доступной любому желающему. Сами компьютеры пока также вполне доступны на eBay.

ВЕКТОРНЫЙ ДИСПЛЕЙ

78a55f7c667144399ffe53f42ecb4b19.jpgЗа исключением части отвечающей за вывод изображения, архитектура Vectrex довольно обычна для компьютеров того периода. Основной интерес представляет именно подсистема формирующая векторное изображение, ей и будет посвящена большая часть статьи.

Векторные дисплеи, сейчас практически не встречающиеся и мало кому известные, были распространены в 1970-е.
Основное отличие от всем известных растровых заключается в отсутствии автоматической развёртки. Луч не бегает по строчкам сам. Его перемещение управляется программой — код, который вы напишите, определяет направление, скорость, длительность и яркость, с которой будет перемещаться луч. Как следствие — у векторного дисплея нет пикселов, а, следовательно, и нет понятия «разрешение» (по крайней мере, в привычном понимании).
Фактически, такой дисплей представляет собой осциллограф, к горизонтальному (X), вертикальному (Y) и каналу яркости (Z) которого подключены цифро-аналоговые преобразователи.

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

После завершения перемещения люминофор будет какое-то время светиться и линия будет видна. Однако, это послесвечение длится недолго.
По этой причине всё, что требуется, включая выполнение кода, задержки вызванные обращением к периферии и саму отрисовку линий, нужно выполнить за ограниченное время.
Если линий будет много, изображение начнёт мерцать. Это делает невозможным создание закрашенных фигур (разве что совсем крохотных), а сколько-нибудь серьёзные вещи придётся писать на ассемблере т.к., даже если представить себе идеально оптимизирующий компилятор, рассчитать все временные задержки при перемещении луча, которые будут происходить в выдаваемом им коде, проблематично — одна лишняя загрузка в регистр в неудачном месте может совершенно исказить изображение.

Если для традиционных 8-битных платформ (типа Commodore VIC-20, C64 или ZX Spectrum) вполне можно программировать, ни разу не посмотрев на схему компьютера, в случае с Vectrex понимание того, как работает часть формирующая изображение — необходимо.
Конечно, можно пользоваться для отрисовки линий, простых фигур и вывода надписей готовыми подпрограммами BIOS, однако это не позволяет в полной мере использовать возможности устройства — рано или поздно всё равно придётся работать с железом напрямую.
По этой причине, знакомство с разработкой будет тесно переплетено с описанием устройства компьютера.

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

В данном контексте нас интересуют следующие компоненты Vectrex’a (в скобках приведены номера на блок схеме):

MOS 6522 (IC207) — универсальный адаптер интерфейсов (VIA — Versatile Interface Adapter) — этот чип отображается на область памяти $D000…$D00F. Соответственно, запись по этим адресам значений (например, командами процессора STA $D00x) приводит к изменению его регистров.
6522 содержит, в частности, порты ввода-вывода, два таймера, сдвиговый регистр (shift register).

Подробности лучше посмотреть в документации на чип (см. ссылку на архив в конце статьи), но суть состоит в том что, записывая командами микропроцессора значения в упомянутые выше ячейки памяти, мы устанавливаем торчащие из чипа ножки в 0 или 1 (а также наоборот — можем проверить, поданы ли на них 0 или 1).

MC 1408 (IC301) — 8-разрядный цифро-аналоговый преобразователь (DAC, ЦАП). Преобразует код, поступающий на него с чипа 6522 в соответствующий уровень напряжения. С точки зрения программирования диапазон напряжений соответствует цифрам -128… +127 (а не 0…255!)

LF347 (IC303) — схемы выборки-хранения s&h (store & hold) на операционных усилителях. Сохраняют на выходе напряжение (в т.ч. после того, как оно будет снято с входа). Их две — по каналам Y и Z (для X не нужна, пояснение ниже).

CD 4052 (IC302) — аналоговый мультиплексор с цифровым управлением (mux). Взависимости от кода на его цифровых входах (которые подключены всё к тому же 6522) пропускает входное напряжение с ЦАП-а на один из нескольких своих выходов.

4066 (IC305) — аналоговые ключи. Управляемые (тем же 6522) выключатели, позволяющие пропускать или не пропускать через себя напряжение.

LF247 (IC303) — интеграторы на операционных усилителях (их два — по X и по Y, соответственно). Преобразуют входной прямоугольный сигнал, амплитуда которого задана кодом на ЦАП-е, в изменяющееся напряжение, заставляющее луч плавно перемещаться из одной точки в другую, оставляя на экране светящийся след.

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

29e06a8bab0e4c4badfe58fb3480a96b.png

(для более подробного рисунка см. блок-схему и схему)

Рисование осуществляется примерно следующим образом:

Загружая в определённые регистры 6522 значения, мы можем устанавливать на выходе ЦАП нужное напряжение. Но ЦАП всего один, а нам нужно выставить три напряжения — X (направление по горизонтали -128…+127), Y (направление по вертикали -128…+127) и Z (яркость 0…$7F).
Для этого после установки каждого напряжения нужно переключить мультиплексор, чтобы напряжение было передано на нужный выход. С каналами Y и Z в этом отношении всё просто, а вот канал X идёт (явно для упрощения схемы) в обход мультиплексора.
Т.е., устанавливая Y или Z мы всегда одновременно устанавливаем и X!

Поэтому поступаем так:

1. Записываем в ЦАП яркость, переключаем мультиплексор на вывод в канал Z. Напряжение сохраняется на схеме sample & hold (s&h) канала Z.

2. Записываем в ЦАП Y, переключаем мультиплексор на вывод в канал Y. Напряжение сохраняется на схеме s&h канала Y.

3. Выключаем мультиплексор и записываем в ЦАП X (s&h тут не нужна, так как напряжение сохраняется на самом ЦАП)

Канал Z нам больше не интересен (яркость постоянна), а вот с X и Y разбираемся дальше.

Итак, напряжения по X и Y с выходов схем s&h у нас поданы на аналоговые ключи. Через 6522 (выход PB7) мы подаём на эти ключи сигнал RAMP. Ключи одновременно открываются и оба напряжения попадают на соответствующие интеграторы — по X и по Y.

На выходе интеграторов, соответственно, получаем изменяющиеся напряжения. Они меняются либо от предыдущего значения, оставшегося на конденсаторе интегратора (помните, мы куда-то там до этого поставили луч?), либо от нуля (если ранее интеграторы сбросили в ноль, подав на них через тот же 6522 сигнал ZERO — конденсатор разрядится).

188cf3f20d494083876d51ba9d7f80eb.png

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

Возникает вопрос — в какой момент отключать напряжение? В принципе, это ваше дело. Вы можете просто посчитать, какой длины нужен вектор и вбить задержку в нужное число тактов подходящими командами.
Однако, на практике обычно применяется другой способ — задействуется таймер 1 в 6522. В таймер заносится некое значение, не слишком удачно названное «scale» (масштаб) и начинается обратный отсчёт. Когда значение достигнет нуля, сигналом RAMP интегрирование будет автоматически остановлено. Т.е., достаточно выставить и запустить таймер, луч остановится сам. Однако, тут есть проблема — луч остановится, но как об этом узнать, чтобы начать рисовать следующий? Для этого придётся в цикле проверять один из регистров 6522, где после завершения счёта установится флажок. По сути, получается ожидание впустую, поэтому это время в цикле иногда используют для выполнения каких-нибудь полезных вычислений.

Помимо сплошной линии есть достаточно кривая возможность рисовать пунктирную. Для этой цели используется сдвиговый регистр (shift register) в 6522. Заносим туда необходимый паттерн (к примеру, $AA = %01010101) и говорим 6522, что сдвиг должен происходить автоматически. При сдвиге каждый бит выползает на сигнал BLANK и, таким образом, луч сам включается на единицах и выключается на нулях. Проблема в том, что после 8 сдвигов в регистре остаются одни нули и весь наш замечательный пунктир обрывается. Чтобы этого не происходило, необходимо снова и снова заносить туда значение pattern. Делается это в вышеупомянутом цикле ожидания окончания интеграции. В таких условиях получить именно тот пунктир, какой хочется — весьма непросто.
Впрочем, именно этот регистр используется BIOS’ом для функции вывода текста (т.е., фактически, стандартные символы — растровые, просто рисуются прерывистыми горизонтальными векторами).

Из всего вышеописанного следует три важных момента:

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

2.Длина и направление вектора зависит от сочетания scale и напряжений по X и Y. В некотором смысле, scale задаёт длину, а X и Y направление (но при этом также влияя на длину). Можно сказать, что на рисунке с графиком scale задаёт время от A до B (или от B до C), а значение X (или Y) подаваемое на ЦАП — наклон отрезков на нижней части графика (говоря иначе — скорость изменения напряжения).

3.Поскольку scale — время перемещения луча, оно должно быть по возможности минимальным. Чем оно меньше, тем больше векторов можно успеть нарисовать, пока не начнётся мерцание.

Для немерцающего изображения в 50 кадров в секунду (50 гц для любого Vectrex) необходимо со всеми рисованиями и вычислениями уложиться в 30000 тактов.

Если рисовать горизонтальные линии во всю ширину экрана в максимальном масштабе ($FF) и перед началом рисования каждой линии устанавливать луч в центр, а затем в точку начала линии, то в упомянутые 30000 тактов уложится примерно 60–70 линий, что весьма немного. Ясно, что при уменьшении масштаба (и, соответственно, уменьшении их длины) максимальное число линий будет расти.

РИСОВАНИЕ ПРЯМОЙ ЛИНИИ

Теперь разберём, как всё вышеописанное реализуется в коде (константы VIA_* из vectrex.i от исходников BIOS).

Практически любая программа для Vectrex представляет собой бесконечный цикл. Первым делом в нём всегда вызывается подпрограмма BIOS Wait_Recal, а затем уже всё остальное — необходимые вычисления, отрисовка всех векторов для данного «кадра», проигрывание музыки, опрос джойстиков и пр.
В отличие от традиционных растровых дисплеев, где луч обегает все строчки одинаковое для каждого кадра время, здесь всё иначе — ведь в одном кадре может выводится 10 линий, а в следующем только 5.
Для обеспечения равного времени выполнения всех итераций цикла («кадров») используется следующий метод:

Таймер 2 VIA 6522 программируется в режим one shot и в него заносится значение Vec_Rfrsh = 30000 ($7530). С момента занесения идёт обратный отсчёт. В начале Wait_Recal ожидаем, когда отсчёт закончится. Как только он закончился, осуществляем рекалибровку схем (а в таймер опять заносим то же значение). Смысл этого ожидания в том, чтобы итерация цикла гарантированно занимала не менее 30000 тактов. Если рисование и вычисления заняли меньше, время всё равно будет «дополнено» до 30000.
А вот если они заняли больше, это приведёт к негативным эффектам — во-первых, начнут гаснуть векторы нарисованные первыми, во-вторых, из-за поздней рекалибровки, могут возникнуть различные искажения.

Почему именно 30000? Значение выбрано из расчёта 50 Гц (т.е. на один «кадр» отводится 1/50 секунды), исходя из частоты процессора 1.5 MHz. Именно 50 Гц, по-видимому, выбрано чисто по традиции.

Wait_Recal состоит из нескольких последовательных процедур:

— Увеличивается счётчик Vec_Loop_Count (это просто переменная word)
 — Ожидается когда закончит считать таймер 2 (и если закончил, запускается заново)
 — Устанавливается максимальный масштаб (scale) = $FF
 — Выключение обнуления интеграторов, выключение луча, луч перемещается в позицию x = $7F, y = $7F (левый нижний угол) из предыдущей своей точки (которая может быть какой угодно)
 — Включается луч, очищается сдвиговый регистр (т.е. пустой паттерн для линий), включение «обнуления» интегратора (луч оказывается в центре)
 — Выключение обнуления интеграторов, выключение луча, луч перемещается в позицию x = $80, y = $80 (правый верхний угол)
 — Ещё раз включение обнуления интеграторов (луч снова оказывается в центре)
 — Устанавливается ноль относительно нуля ЦАПа (см. ниже примечание от svo)

Каким образом помогает калибровке перемещение луча в углы экрана — неясно.

С практической точки зрения важно, что в результате этих действий, после вызова Wait_Recal луч у нас выключен и принудительно находится в центре (обнуление интеграторов включено — они не могут работать, даже если начать интегрирование), scale установлен в $FF.

Итак, исходник:

loop:
	jsr     Wait_Recal              ; ожидание таймера 2 и рекалибровка CRT

; Включаем луч и выключаем "обнуление" интеграторов (иначе луч не будет перемещаться)
	lda	#$CE 	                ; BLANK low, ZERO high		
	sta	

Обратите внимание, что паттерн записывается в регистр дважды — перед началом интегрирования и потом в каждой итерации цикла. Второе необходимо потому, что при сдвиге (автоматическом) регистр становится пустым и если значение не обновлять — линия станет невидимой. При обновлении же очень важно правильное соответствие сдвига и тактов, которые занимают команды в цикле. Если туда вставить даже один nop, видимый паттерн линии изменится.

Если рисуется несколько линий (к примеру, квадрат) то, конечно, «clr В подпрограмме BIOS Draw_VL (вызывающей Drawline_d) этот момент обыгрывается довольно хитро: есть переменная Vec_Misc_Count, в которую заносится число линий, которые планируется нарисовать. И когда дорисована последняя, там не просто очищается паттерн, но ещё выполняется сброс интеграторов (т.е. частично выполняется код из Wait_Recal).

Если нужно быстро вернуть луч в центр экрана до завершения цикла и очередного вызова Wait_Recal, можно пользоваться «обнулением» интеграторов, не забывая его отключать:


; обнуляем интеграторы
	        lda     #$CC
                sta     

Тоже самое делает подпрограмма BIOS Reset0Ref (плюс, там ещё очищается shift register), которая вызывается из Wait_Recal.
Существенно, что необходимо выполнять И обнуление интеграторов И запись нулей в ЦАП. Для эмулятора это без разницы, а вот на реальном Vectrex разница очень даже будет.

ЗАМЕЧАНИЯ:

Описанный вариант отрисовки линии намеренно избыточен. К примеру, масштаб и яркость совершенно необязательно устанавливать каждый раз, сплошная линия не потребует лишнего цикла с обновлением shift register. В ряде случаев куда оптимальнее (но менее наглядно) загружать сразу A и B инструкцией LDD, использовать clr, inc/dec и т.д. В реальной ситуации это делать придётся, т.к. экономит такты.2c0e3674d9594d04bfb92a1384129aac.jpg

По-поводу «бессмысленных» инструкций задержки: после записи в порты 6522, в коде BIOS’а иногда выжидают несколько тактов. Иначе следующая запись может не пройти. Когда это нужно и когда нет — вопрос достаточно туманный. Судя по экспериментам (как моим, так и других людей) — не нужно вообще. Возможно, это требовалось для ранних экземпляров Vectrex.

Нужно понимать, что если хочется рисовать длинные непрерывные вектора (к примеру, во всю ширину экрана), то это возможно лишь при максимальном масштабе (scale). Однако, если он будет максимальным, это автоматически означает снижение «разрешения» в том смысле, что шаг в единицу по вертикали будет означать расстояние примерно в ширину яркой линии (т.е. между рядом параллельными линиями будет промежуток).
Если же делать длинные линии из нескольких коротких, места стыков будут заметны.
На практике имеет смысл постоянно переключать масштаб — в одном (большом) рисуется линия, в другом (маленьком) луч перемещается к началу рисования следующей.

Если нарисовать длинную (от -128 до 127) горизонтальную линию (в масштабе $FF) так, чтобы её левый конец был у правого края экрана, а правый конец далеко за экраном, то не получится передвинуть её влево так, чтобы правый конец оказался за левым краем экрана.
На первый взгляд здесь нет проблемы, т.к. перемещения луча всегда относительны. Однако, на настоящем Vectrex линия влево за край не уйдёт (причём, если уменьшать начальную координату линии плавно, то будет видно, как её движение влево будет замедляться и прекратится, не дойдя до нужной позиции около четверти экрана. Это связано с ограничениями на максимальную амплитуду напряжений на выходах интеграторов (эмулятор, к слову, этого не понимает).

Яркость вектора определяется не только значением Z, но также и временем, которое луч находится в данном конкретном месте. Соответственно, яркость будет выше если а) луч перемещается медленно б) луч перемещается по одной и той же траектории многократно.

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

Прямая линия с интегрированием вручную

В предыдущем разделе для начала перемещения луча мы запускали таймер, что автоматически запускало интегрирование. И затем ожидали окончания, проверяя в цикле, не закончил ли таймер считать.
Существует другой способ рисования линии, при котором таймер не используется: интегрирование запускаем вручную (установкой RAMP на ножке PB7 6522), а затем любым способом ждём нужное нам время, пока луч ползёт в заданном (X и Y) направлении. Взависимости от задачи, для ожидания могут быть использованы простые nop, либо цикл (в котором, в частности, можно обновлять значение shift register, если требуется штрих-пунктирная линия).
В конце рисования интегрирование прекращается (также вручную).

Код выглядит так:




loop:
                jsr     Wait_Recal

                lda     #$CE            ; (11001110) /Blank low, /ZERO high
                sta     

Поскольку таймер здесь не используется, задание масштаба (scale) не имеет смысла и ни на что не влияет. Направление и длина линии полностью определяются значениями X, Y и задержкой между началом и окончанием интегрирования.

В BIOS подобный подход (без таймера) используется при выводе символов (подпрограмма Print_Str).

Рисование кривой

С рисованием прямых векторов, если разобраться, всё обстоит достаточно просто. Но что делать, если требуется нарисовать кривую? Специальных аппаратных возможностей для этого Vectrex не имеет.
Более того, вы даже не можете произвольно перемещать луч — необходимо переключать каналы мультиплексора, причём один из каналов (X) вообще идёт мимо него. 6499caf2bce145c6a19ae172f47169cd.jpg

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

Для этого, как и в предыдущем случае, от запуска интегрирования по таймеру и проверки его окончания придётся отказаться.

Запуск производится вручную (сигналом RAMP) и, после того как луч начал движение, мы в нужные моменты начинаем писать значения в каналы X и Y, в результате чего луч вынужден менять направление. Помимо, опять же, необходимости чётко вычислять необходимые для записи моменты, серьёзным ограничением является необходимость переключения мультиплексора между каналами. На практике это приводит к тому, что с каналом X всё хорошо, т.к. он идёт в обход мультиплексора (поэтому отклонения луча по горизонтали получаются чистые и аккуратные). А вот с каналом Y всё плохо.
Чтобы в него записать, необходимо включить мультиплексор (активировать выход S/H 6522 ведущий на DIS MUX мультиплексора), записать значение и снова выключить мультиплексор что, вероятно, приведёт к остановке интегрирования.

Рассмотрим простой пример, в котором рисуется кривая с отклонением только по горизонтали (X):


loop:
                jsr     Wait_Recal        ; калибровка

                lda     #$7f
                sta     

Фактически, приведенный пример аналогичен примеру из предыдущего раздела (прямая линия с интегрированием вручную), просто здесь в процессе рисования мы еще и в DAC значение X пишем.6cbfadff2bdd47d798b63643e496eecf.jpg

Что касается использования shift register, здесь есть сложности. Пунктирную линию сделать можно, но точность пунктира будет условной, т.к. проблематично изменять и X и значение shift register в точности тогда, когда это нужно.

Если через равные промежутки времени писать в X одинаковые значения (например 10,10,10), изменение будет линейным — т.е. получится наклонная прямая.

От задержек между записью значений зависит гладкость кривой и её длина. Если задержки большие, получится просто ломаная линия. Поэтому в конкретных ситуациях будет иметь значение любая лишняя инструкция, а циклы, вполне вероятно, придётся разворачивать. Кроме того, получившаяся кривая может зависеть от конкретного экземпляра вектрекса (из-за отличия параметров аналоговых компонентов/цепей), хотя добиться примерной схожести на разных экземплярах — возможно (правда неясно, где взять столько Vectrex’ов для тестирования).

Описанная технология использовалась на практике, хотя и редко. Наиболее известный пример — дорога в игре Pole Position. Другой — моя intro «Electric Force» для CC'2015.

Можно ли менять яркость в процессе рисования линии (прямой или кривой)? В практических целях — вряд ли. Яркость меняется через тот же ЦАП — так же, как и с каналом Y, понадобится переключение мультиплексора (с соответствующими побочными эффектами). Кроме того, на это будет уходить время, так что длина сегмента линии одинаковой яркости будет слишком большой.

Растр из векторов

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

1. За доступные 30000 тактов (чтобы изображение было стабильным) слишком много линий развёртки не нарисуешь.

Фактически, при максимальной длине линии их число (назовём это вертикальным разрешением) будет измеряться десятками. Причём, если scale будет максимальный (т.е., если мы хотим растр шириной в экран), то линий успеет нарисоваться меньше. Более того — нужно учитывать, что после рисования каждой линии мы должны как-то перейти к началу следующей. Даже если мы рисуем во время хода луча обратно (создавая себе проблемы при адресации точек), при максимальном scale не получится нарисовать несколько горизонтальных линий вплотную — между ними будут значительные промежутки. Чтобы этого избежать, можно уменьшать scale перед перемещением луча по вертикали и возвращать обратно на максимум перед рисованием линии. Это переключание также займёт время.
Можно отказаться от растра на всю ширину экрана и изначально задать scale поменьше. Тогда, во-первых, решается проблема с промежутками по вертикали, во-вторых уменьшается время рисования линий.

2. Если не рекалибровать схему после каждой линии, всё это дело съезжает в сторону.b3ba6cec03f54205997dc5cf5f12f440.jpg

Фатальность проблемы зависит от выбранного scale (т.е., чем дольше рисуется луч, тем сильнее уплывают параметры интеграторов) и от конкретного экземпляра Vectrex. На моём экземпляре при растре во всю ширину экрана уже вторая линия начинается на миллиметр левее, так что о стабильном растре говорить не приходится. Уменьшение scale несколько снижает остроту проблемы, но не снимает её.
Решением является рекалибровка (обнуление интеграторов и ЦАПа) после каждой линии. Это даёт стабильный растр но, разумеется, ценой лишних тактов.
На эмуляторе (ParaJVE) уплывание параметров аналоговых элементов схемы отсутствует, поэтому ориентироваться на него в этом плане нельзя.

3. Включать и выключать луч в процессе его движения нужно очень быстро и точно. Приемлимая скорость возможна лишь при использовании shift register.
Причём, если при рисовании пунктирной линии равномерность пунктира мало кого волнует, то при выводе изображения регулярная потеря точек или лишние промежутки, особенно в совокупности с другими проблемами — недопустимы.
Подход, с таймером тут не срабатывает, поэтому нужно использовать описанный ранее запуск интегрирования вручную после чего, через точно рассчитанные промежутки времени (так, чтобы каждые 8 «пикселов» правильно стыковались друг с другом) shift register обновляется нужными значениями. Соответственно, окончание рисования линии определяется также вручную — программным счётчиком.
Именно так работает подпрограмма BIOS Print_Str, при помощи которой обычно выводятся на экран текстовые строки. На практике, однако, есть масса ньюансов — достаточно посмотреть в код Print_Str. Кстати говоря, в ней для экономии тактов после каждой строки луч не устанавливается в ноль. Из-за чего почти любая строка (длиннее нескольких символов, взависимости от масштаба) выводится искажённой, как минимум на некоторых экземплярах Vectrex.

4.Как уже было замечено ранее, переключать яркость во время рисования линии проблематично. Однако ничто (кроме необходимости уложиться в 30000 тактов) не мешает рисовать 2–3 растра разной яркости, накладывая один на другой.

ЗВУК

Несмотря на почтенный возраст, со звуком в Vectrex де

© Habrahabr.ru