Разработка 16-битной игры - особенности, инструменты, код с пояснениями

+141
в блоге IT Pony!
Картинка для привлечения внимания

По заявке пользователя Niko_de_Andjelo
А можно для нубов написать пост с историей разработки?
Ну, какая архитектура, показать, какой кусок кода что делает и т.д. Можно не очень подробно, просто, приобщить народ к Asm?


публикую этот пост. Основные темы:
  • На чем и как вообще можно делать 16-битную игру в 2020-м году
  • Ограничения 16-битного режима
  • Какие инструменты были использованы
  • Реализация графической библиотеки
  • Основные процедуры
  • Примеры кода

Пост в основном предназначен для разработчиков, хотя и люди, интересующиеся 16-битными играми, смогут найти полезные факты.



На чем и как вообще можно делать 16-битную игру в 2020-м году

В целом, так же и на том же, на чем и 25 лет назад. Родные компиляторы для MS-DOS, генерирующие настоящий 16-битный код и ассемблер для x86. В качестве машины для разработки, можно использовать как машину с DOS/FreeDOS, так и эмуляторы вроде DOSBox. Однако, большая часть перечисленных инструментов не являлись FreeWare, и их использования даже в личных некоммерческих целях — по-прежнему является пиратством. Кое-что всё-таки удалось найти, с возможностью легального использования.
Ниже приведены список инструментов, с указанием возможностей и лицензии.

TurboPascal 5.5 — недавно был отдан компанией-правообладателем в свободное пользование (но только для личных и некоммерческих целей), это хороший компилятор с IDE, поддерживает линковку объектных файлов (но не встроенный ассемблер) и элементы ООП. Для целей любительской разработки 16-битной игры — подходит.
Ссылка для статью: edn.embarcadero.com/article/20803

TurboC 2.01 — аналогично был отдан компанией-правообладателем в свободное пользование (но только для личных и некоммерческих целей), это хороший компилятор с IDE, поддерживает линковку объектных файлов (но не встроенный ассемблер) и элементы ООП. Для целей любительской разработки 16-битной игры — подходит.
Ссылка для статью: edn.embarcadero.com/article/20841

OpenWatcom — форк компилятора Watcom. IDE нет, но компилятор один из самых лучших, поддерживает как подключения объектных файлов, так и встроенный ассемблер, все возможности С и С++ (стандарт С99). Лицензия допускает некоммерческое использование частными лицами. Для целей любительской разработки 16-битной игры — подходит, но без IDE, работать чуть менее удобно.
Сайт: www.openwatcom.org/, а также есть неофициальные форки на open-watcom.github.io/

NASM — современный компилятор ассемблера, имеет среди прочих версий — 16-битную. Его можно применять для сборки ассемблерного кода совместно с теми компиляторами, которые сами не имеют встроенного ассемблера. Лицензия — BSD, так что можно спокойно использовать.
Сайт: nasm.us/

Наконец, последний в списке, в связи со спорностью ситуации — это QuickBasic 4.5. Из всех приведенных выше инструментов, он имеет наилучшую IDE со встроенным отладчиком и редактором, эффективную реализацию языка Бейсик со структурами, процедурами и динамическими массивами, отличная поддержка графики, возможность подключать объектные файлы (правда, собственным способом).
Засада в том, что у меня нет строгой уверенности насчет правомерности его использования. На сайте Microsoft информация о нем отсутствует. В общении с техподдержкой мне несколько раз отвечали, что для личного некоммерческого применения он допустим, но позже я нарыл, что он доступен для подписчиков MSDN, а она стоит сотни долларов.
Если у кого-то есть опция корпоративной поддержки Microsoft и он сможет задать этот вопрос в службу — я буду благодарен.
Информация: ru.wikipedia.org/wiki/QuickBASIC

Также стоит упомянуть наличие кросс-компиляции у Free Pascal Compiler 3.04 — он действительно генерирует 16-битный код из-под 32-битной системы, имея при том всю мощь современного ObjectPascal. Почитать можно тут wiki.lazarus.freepascal.org/DOS
Я сам еще не использовал плотно, потому рекомендовать или отвергать не стану, но выглядит очень удобно. Хотя размер выходного файла получается очень жирным для 16-битной системы, это небольшой минус.

Ограничения 16-битного режима

Что есть 16-битный режим? Это когда процессор работает в реальном режиме, адресует только 1Мб памяти, работает с сегментной моделью памяти (адрес памяти — это пара «сегмент: смещение»), и доступны только регистры размера «слово» (16бит).
Подробнее здесь
ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D1%80%D0%B5%D0%B6%D0%B8%D0%BC

Сие означает, что нам нужно уместить код, графику и все элементы игры в 640Кб доступной прикладной программе памяти (реально нужно рассчитывать на 550-600 Кб, иначе могут начаться проблемы).

С точки зрения графики, мы не можем использовать в этом режиме возможности видеокарты для аппаратного ускорения, высокого разрешения и т.д. Всё, что нам доступно — это видеорежимы BIOS, лучшим из которых для игр является 13h. Это размер экрана 320x200 при палитре на 256 цветов (палитра RGB, по 6 бит на каждый компонент цвета).

Какие инструменты были использованы

Для реализации быстрых операций с памятью (спрайты и буфера) — ассемблер NASM
Для всего остального (загрузчики спрайтов, игровая логика, вывод графики) — компилятор QuickBasic 4.5

Реализация графической библиотеки

Мы хотим сделать библиотеку, которая позволит:
  1. Загружать спрайты из файлов в память
  2. Выводить их на экран с прозрачностью, поворотом и т.д.
  3. Делать это всё очень быстро
В QuickBasic есть замечательная функция — PUT, которая позволяет вывести на экран содержимое буфера заданного размера в заданном месте. Формат буфера очень простой — первые два байта это ширина в битах, далее два байта это высота в пикселях, и далее идут байты с данными. Загрузив в буфер спрайты — можно выводить графику на экран гораздо быстрее, чем по пикселям.
Зачем вообще нам применять ассемблер в таком случае? Только ради производительности. Частота таймера DOS — 18.25Гц, обновлять графику желательно с такой же частотой. А операции с массивами в QuickBasic (как и почти во всех языках высокого уровня) — очень медленные.

Пример: мы хотим заполнить буфер 10x10 зеленым цветом (код палитры 10).
Код на Бейсике:
for i=0 to 99
buf(i)=10
next i

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

mov cx,100 ; указываем, что цикл на 100 раз
mov al,10 ; значение цвета
cld ; говорим, что при записи байта, двигаем указатель вперед
cicle1:
  stosb  ; пишем байт по адресу и двигает указатель вперед
loop cicle1 ; идем на следующий цикл, пока cx>0


Такая конструкция куда выгодней — для каждой записи, выполняются всего две команды — загрузка байта в память и проверка условия цикла. Можно записать короче:

mov cx,100 ; указываем, что цикл на 100 раз
mov al,10 ; значение цвета
cld ; говорим, что при записи байта, двигаем указатель вперед
rep stosb  ; пишем байт по адресу и двигает указатель вперед, пока cx>0


если мы уверены, что размер буфера кратен двум байтам, то можно еще более ускорить, записывая по 2 байта (слово).
mov cx,50 ; указываем, что цикл на 50 раз
mov al,10 ; значение цвета
mov ah,10 ; значение цвета
cld ; говорим, что при записи слова, двигаем указатель вперед
rep stosw  ; пишем слово по адресу и двигает указатель вперед, пока cx>0


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

Чтобы выводить буфер на экран, есть хороший подход — надо выделить буфер, копировать спрайты из буферов спрайтов в этот буфер, а потом одной командой вывести этот буфер на экран. По сути, это кустарный вариант реализации приёма «Двойная буферизация».
ru.wikipedia.org/wiki/%D0%94%D0%B2%D0%BE%D0%B9%D0%BD%D0%B0%D1%8F_%D0%B1%D1%83%D1%84%D0%B5%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F

Как же этого достичь, чтобы скорость была приемлемой? Аналогично — написать процедуру на ассемблере, которая будет копировать один буфер в другой (команда movsb копирует байт памяти из источника в приемник). Поскольку буфер назначения больше, чем буфер для спрайта, то нужно использовать построчное копирование (копируем байты в количестве ширины спрайта), а потом двигать указатель в буфере на ширину буфера минус ширина спрайта. Так мы быстро скопируем спрайт в буфер, сохранив геометрию. Остальные решения работают точно так же — например, при выводе спрайта с прозрачностью копируем байты, но проверяя при этом неравенство цвета условному цвету «прозрачный» (я использую цвет 255 для прозрачного).

Основные процедуры

В библиотеке реализованы основные процедуры:
  • Подготовка буфера графики
  • Копирование буфера графики
  • Чтение файла формата «Индексированный PCX» в буфер спрайтов
  • Чтение файла формата «Индексированный PCX» на экран
  • Вывод спрайта в буфер в позицию
  • Вывод спрайта в буфер в позицию с прозрачностью (поскольку прозрачности нет как таковой, то просто считаем, что цвет с кодом 255 будет прозрачным).
  • Вывод фрагмент буфера в позицию (позволяет реализовать анимацию, то же для разворота и прозрачности)
  • Вывод линии в буфер
  • Заливка области в буфере
  • Установка палитры, полученной из файлов графики
  • Загрузка пиксельного шрифта
  • Вывод текста пиксельным шрифтом

Все эти процедуры имеют ассемблерную основу и декларативную обертку для Бейсика. Поскольку передать напрямую массив в ассемблерный код нельзя, мы передаем в процедуры сегменты и смещение буфера. Подробнее — в следующем разделе.

Примеры кода

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

Сначала ассемблерный код. В данную процедуру передаются процедуры через стек.

SECTION .TEXT

BUFFER_WIDTH equ 10  ; сдвиг в стеке переменной ширины
BUFFER_HEIGHT equ 8  ; сдвиг в стеке переменной высоты
BUFFER_COLOR equ 6  ; сдвиг в стеке переменной цвета

global setUpBufferAsm  ; объявление процедуры как глобальной
setUpBufferAsm:
	; сохранение регистров в стек. В bp сохраняем смещение стека.
	push	bp
	mov 	bp,sp
	push 	di
	push 	es
	push 	si
	push 	ds
	push	ax
	push	bx
	push	cx
	push	dx

	mov	bx,[bp+14]  ; считываем из стека смещение и сегмент переданного массива
	mov 	di,[bx]
	mov 	bx,[bp+12]
	mov     es,[bx]

	cld ; установка движения вперед при записи в массив

	mov	ax,[bp+BUFFER_WIDTH]  ; считываем из стека ширину спрайта
	shl	ax, 3 ; умножаем на три, чтобы перевести в биты
	stosw	; пишем ax в массив
	mov	ax,[bp+BUFFER_HEIGHT] ; считываем из стека высоту спрайта
	stosw  ; пишем ax в массив

	mov 	bx,[bp+BUFFER_WIDTH] ; вычисляем размер спрайта в словах - это высота*ширина/2
	mul	bx
	mov	bx,2
	div	bx
	mov 	cx,ax ;  и пишем в счетчик
		
	mov	ax,[bp+BUFFER_COLOR] ; наконец, помещаем в ax два байта и оба равны цвету
	mov	ah,al
	
	rep	stosw  ; пишем цвет в массив буфера по 2 байта за раз.

        ; восстанавливаем регистры
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	pop	ds
	pop	si
	pop 	es
	pop	di
	mov 	sp,bp
	pop	bp

    retf 10 ; идем обратно в программу - число равно общей длине параметров в байтах, место, где в стеке адрес возврата.



Остальное достаточно просто. В NASM делаем объектный файл, в QuickBasic собираем из него модуль qlb для среды разработки и модуль lib для сборки exe-файла, и определяем процедуру на языке Бейсик:

DECLARE SUB setUpBufferAsm (BufAddr AS INTEGER, BufSeg AS INTEGER, BYVAL w AS INTEGER, BYVAL h AS INTEGER, BYVAL c AS INTEGER)

где первые две переменные — для передачи адреса, а указание BYVAL означает, что надо загнать в стек сами значения w,h,c вместо их адресов. Теперь можно создать программу, которая зальет буфер зеленым цветом и выведет его в заданной позиции.

DEFINT A-Z
'$DYNAMIC
'$INCLUDE: 'gratools.bi'

CLS
SCREEN 13

DIM sprbuf(1024) AS INTEGER
CALL setUpBufferAsm(VARPTR(sprbuf(0)), VARSEG(sprbuf(0)), 40, 20, 10)
PUT (200, 100), sprbuf(0), PSET
END


Результат запуска

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

64 комментария

Задаём вопросы.
NTFS
0
С точки зрения графики, мы не можем использовать в этом режиме возможности видеокарты для аппаратного ускорения, высокого разрешения и т.д. Всё, что нам доступно — это видеорежимы BIOS, лучшим из которых для игр является 13h. Это размер экрана 320x200 при палитре на 256 цветов (палитра RGB, по 6 бит на каждый компонент цвета).


Через VESA и переключение банков можно вплоть до 1280х1024х24бита/цвет
KerHarrad Изменён автором
0
Через VESA

Это уже не совсем функции BIOS, задействована видеокарта. У меня на 486-ом, к примеру, 13h поддерживается, а вот включить VESA уже невозможно, тот же WarCraft 2 не запускается.
NTFS Изменён автором
0
Это от типа карты зависит, если это не SVGA — то там VESA-режимов и не будет, максимум, что можно получить — это 400x512x256 (или 800х512х16), и то с тем ещё шаманством с регистрами карты (и это при условии, что развёртка монитора будет нормально работать на 512 строках, если нет — то лимит снижается до 480).
makise_homura
0
Через VESA и переключение банков можно вплоть до 1280х1024х24бита/цвет
VESA — это уже SVGA, а не VGA. Тут, емнип, суть была в том, чтобы написать именно используя только документированные возможности обычной VGA.
makise_homura
0
Про ограничения памяти в 1Мб.
В ДОСе вроде были модели памяти EMS и XMS (кажется). Просто помню, во времена 98-й, изучая Pascal, я с какой-то из них немного возился. Там конечно заморочки с картированием сегментов куда-то в область между 640к и 1М, но использование этих моделей может дать ещё несколько Мб под спрайты. Не рассматривал такой вариант? На перспективу, скажем.
MexoOne Изменён автором
0
Особого смысла не вижу. У меня была цель сделать игру на той технологии, которая мне была доступна в конце 90-х (единственно, что позволил себе «лишнего» — это ассемблерные операции).
Так-то, я планирую делать игры на современных системах — OpenGL, SFML, Qt и подобное.
Если и бахну еще разок что-то под 16 бит, то опробую уже Turbo Pascal 5.5 или кросс-компиляцию для FreePascal.
NTFS
+2
Забабахай ASCII-артом в консоли следующую))
KerHarrad
+3
«Таких извращений Иван не встречал»©
NTFS
+2
Кидать ASCII-арт JPEG-картинкой? Да как так можно вообще
              ,)))))))),,,
            ,(((((((((((((((,
            )\`\)))))))))))))),
     *--===///`_    ```(((((((((
           \\\ b\  \    ``)))))))
            ))\    |     ((((((((               ,,,,
           (   \   |`.    ))))))))       ____ ,)))))),
                \, /  )  ((((((((-.___.-"    `"(((((((
                 `"  /    )))))))               \`)))))
                    /    ((((``                  \(((((
              _____|      `))         /          |)))))
             /     \                 |          / (((((
            /  --.__)      /        _\         /   )))))
           /  /    /     /'`"~----~`  `.       \   ((((
          /  /    /    /`               `-._    `-. `)))
         /_ (    /    /`                    `-._   \ ((
        /__|`   /   /`                        `\`-. \ `)
               /  /`                            `\ \ \ 
              /  /                                \ \ \
     jgs     /_ (                                 /_()_(
            /__|`                                /__/__|
andreymal
+4
зайти в телеграм плиз
Sasha-Flyer
0
Кидать ASCII-арт JPEG-картинкой? Да как так можно вообще
Ваще издевательство над святым!
makise_homura
+1
В ДОСе вроде были модели памяти EMS и XMS (кажется).
Модели памяти — это то, как распределяются секции программы в нижней памяти (один сегмент на всё (tiny), сегмент на код и сегмент на стек+BSS+heap (small), то же самое, но много сегментов на BSS/heap/стеки (compact), то же самое, что small, но много сегментов на код (medium), и, наконец, много сегментов на всё (large/huge)). А EMS/XMS — это протоколы доступа к памяти выше 1 МБ. EMS — это отображение разных блоков такой памяти в окно в нижней памяти, а XMS — это расширение адресного пространства и доступно только на процессорах с более чем 20-разрядной шиной адреса и защищённым режимом (строго говоря, 386). 8086 может использовать только один мегабайт памяти (а 80286 — до 16 мегабайт в режиме EMS).
makise_homura
0
Только один вопрос: Нахрена?!
Strannick_Moundest
0
Потому-что может)
KerHarrad
+1
Я думаю, что оратор выше спрашивает про целесообразность поста на понилюбском ресурсе. Тут согласен, тема находится одной ногой в оффтопе, а другой топчется на границе.
Но меня попросили это сделать в теме про понную игру, так что я просто выполняю пожелания пользователей.
NTFS
+3
Вопрос наверно больше о том, нахрена делать игры, которые даже не идут под современными системами без костылей, с помощью архаичных инструментов.
Нет, ну может конечно чисто по кайфу, хотя смахивает на мазохизм.
empalu
+2
чисто по кайфу

Это.
Ну вроде как идти под веслами по Большой Воде две морские мили на полутора узлах, хотя на катере можно за 4 минуты, без воды в морду.
Катер всегда удобней, но весла никуда не делись.

архаичных инструментов.

Free Pascal Compiler самый что ни на есть современный, а поддержку 16 бит RTL — оставили как раз для психов, на всякий случай.
NTFS Изменён автором
+3
Мне больше напоминает строительство первобытного шалаша каменным топором. Хотя раньше правда так делали, но сейчас хотя бы возьмут железный топор.
А почему тебе нужно было именно 16 бит, вот я чего не могу понять? Некоторые делают игры, стилизованные под старые, но которые хотя бы под винду запустятся без досбокса
empalu
+2
А почему тебе нужно было именно 16 бит, вот я чего не могу понять?

Я вроде бы раскрывал эту тему, но могу кратко повторить тезисно:
1) В конце 90-х я хотел написать игру, знал тогда только QuickBasic и немного TurboPascal с Delphi под win 3.11
2) Идея провалилась по причинам, раскрытым в этом посте — ну нельзя эффективно работать с памятью на 30 МГц без знания основ Ассемблера.
3) В 2000-м я поступил в ВУЗ, пошел работать, женился, завел детей, открыл бизнес, залез в долги, довел бизнес до ума, дети подросли, долги уменьшились до разумных пределов, дела немного наладились.
4) В 2020-м я доделал то, что не смог в 90-х.
5) Счастливый конец.
NTFS Изменён автором
+4
У меня было то же самое. Тоже примерно в это же время пытался делать игры на QuickBasic и TurboPascal, и даже парочка несложных игр была сделана. Я тоже кстати поступил в 2000-м.
Но сейчас я рад, что если я захочу сделать игру, то мне уже не надо будет трахаться с байтами, ассемблером и прочей хренотой, поэтому и в голову бы не пришло возвращаться к софту тех времён. Да и забыл я уже давно, как там это работает.
Но то я, а то ты, у всех разные потребности
empalu
+1
трахаться с байтами

«Сначала было шампанское и девки, мы выпили всё, что горело и перетрахали всех курей, но тут приехал поручик с ведром керосина и...»
Вот я как тот поручик, у меня есть шампанское, и делать крупный проект на ассемблере — это явно за пределами разумного, но иногда хочется именно ведра керосина, особенно если в молодые годы его так и не выпил.

PS: А так-то, что ты, что я — мы оба сделали достойные проекты на относительно современных системах, за что нам взаимное увожение.
NTFS Изменён автором
+2
то мне уже не надо будет трахаться с байтами, ассемблером и прочей хренотой
Но кому-то такое в самый раз =)
Вообще многое, что одни сочтут за тяжёлый неблагодарный труд, для других — классное развлечение. Я бы сам, было б у меня время, с радостью бы вспомнил те деньки с синим экранчиком кубейсика или третьих борландов.
makise_homura
+3
было б у меня время

Недельный карантин к вашим услугам.
NTFS
+1
Мне за него надо посмотреть Steins;Gate0 и Madoka Gaiden, сделать кучу работы по фанфикам — дооформить комменты к фанфикам («Сказание о королеве», фанфик про Фудзивару-но Моко пока без названия, «Жёлто-рыжая осень», «Странная», «Если друг оказался вдруг»), дописать ещё несколько фанфиков (тот фанфик про Моко, упомянутое «Сказание», ещё один безымянный пока фанфик про Кози Глоу, зарисовки про Сакую Идзаёй и Сатори Комейдзи), а также наконец-то вернуться к дописыванию «Vergessene Zeiten», сделать два видосика про Норико Амевару (восьмой эпизод White Flower of Summer и пародию на «Akari is Here»), и не говоря уже о том, чтобы прочитать то, что успело за время моего отсутствия появиться на табуне…
Короче, покой мне только снится =(
makise_homura
0
Хотя раньше правда так делали, но сейчас хотя бы возьмут железный топор.
Тру-реконы, я думаю, возьмут именно каменный (хотя я не видел реконов по палеолиту).
makise_homura
0
UPD: лол, теперь видел — несколькими комментами ниже. =)
makise_homura
0
Free Pascal Compiler самый что ни на есть современный, а поддержку 16 бит RTL — оставили как раз для психов, на всякий случай.

Грех не упомянуть
Strannick_Moundest
0
Я ещё понимаю, если делать игру на таких известных движках, как Unity, Unreal Engine, который всегда считался самым доступным игровым движком с широчайшими возможностями, или использовать движки от небольших игр, типа PopCap Games Framework, на котором написано несколько сотен игр в дистрибьюции Alawar. Но писать игру под DOS, в наше-то время? Даже известный программист Михаил Флёнов писал игры под DirectX и OpenGL на С++, о чем не мало рассказывал в своих книгах и блоге, хотя тоже такой себе вариант, но всяко лучше чем лезть в пространство ядра, где любая малейшая ошибка заканчивается BSOD-ом.
Strannick_Moundest
0
YouTube. Канал Primitive Technology. Чувак делает каменные топоры. Использует камни и костёр. Долго, муторно, опасно. А на современном заводе на современных станках из современных материалов ежедневно делают не сравнимо более качественные топоры. И тем не менее у того чувака миллионы просмотров.
MexoOne
+1
Важно понимать, что это именно самоцель, процесс ради процесса, а не «можно сделать каменный топор, а ваш стальной — фигня».
Насчет миллионов, не уверен, всё-таки это довольно на любителя дело даже просто смотреть. Гляну при случае, спасибо.
NTFS
+2
Вот даже и не сомневался, что такой чувак есть, и у него обязательно есть канал на ютубе. И скорее всего он это делает в основном ради ютуба, потому что на камеру многие готовы и не такое делать
empalu
0
Ну, насколько я понял из его постов, ему нравится процесс, и он изучает историю. В основном конечно технологии древнего мира и античности.
MexoOne
0
Нет, ему то может и нравится, но и канал на ютубе тоже не последняя причина, иначе мы бы про него и не знали
empalu
0
Ох блин, а вот и упомянутый мной выше реконструктор палеолита (может и нет, но примерно этого я ожидал).
Хм, а не у него ли я смотрел для одного из моих фанфиков, как делать японский бамбуковый лук?..
makise_homura Изменён автором
0
лезть в пространство ядра, где любая малейшая ошибка заканчивается BSOD-ом.
Когда-то люди жили так, что у тебя весь юзерспейс работал в одном адресном пространстве с ядром, и это было не просто нормально, а единственно возможно. Эх, привыкли все к защите памяти и виртуальным адресам, а ведь не так давно 386-го, который положил этому всему начало, не было! =) Вангую, лет через 20 народ не будет представлять себе, как можно жить на bare metal, только виртуалки!
А, ну и никаких BSOD-ов не было, система просто тихо висла — а ты просто жал ресет, ждал, пока она перезагрузится, после чего брал отладчик и смотрел, где ты ошибся портом или адресом.
makise_homura
+1
Я думаю, что оратор выше спрашивает про целесообразность поста на понилюбском ресурсе.
Мне кажется, что это вполне нормально (игра-то как раз про поней, точнее одну поню, есть же в конце концов ОКИ-релейтед посты), а тут просто был аргумент троллейбуса.
makise_homura
0
Аргумент троллейбуса легко парируется аргументом «захотелось». =)
makise_homura
0
Да уж, ностальгией повеяло. Помню, как я, ещё в школе, вооружившись свежей на тот момент книжкой «турбо паскаль» Немнюгина(рекомендую, кстати) читал про все эти видеорежимы и сделал свой первый и последний псевдо3d видеошутер, в стиле Дума, на честной трассировке лучей, и как удивлялся выпуклости стенок. И как же я теперь рад, что теперь не надо всем этим заниматься, вы не представляете.
narf
0
256 цветов (палитра RGB, по 6 бит на каждый компонент цвета)

Как-то странно, 256 цветов — это же всего 8 бит. Сходится, если только у тебя 6 уровней каждого компонента цвета.

Относительно двойного буфера: разве тогда еще не применяли подмену буфера целиком, вместо копирования? Или это только со всяких директхов появилось?
StaSyaN Изменён автором
0
Как-то странно, 256 цветов — это же всего 8 бит. Сходится, если только у тебя 6 уровней каждого компонента цвета.

Там индексированный цвет. То есть, всего может быть 64*64*64 цветов — но одновременно на экране активно не более 256 цветов, заданных текущей палитрой.

разве тогда еще не применяли подмену буфера целиком, вместо копирования?

Были экранные страницы, очень удобный инструмент, но как раз в режиме 13h всего одна экранная страница, потому пришлось делать буферизацию руками.
NTFS Изменён автором
+2
Там индексированный цвет.

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

Может быть был резон перекатиться на меньшее число цветов, но с несколькими экранными страницами?
StaSyaN
0
А меньше там идет только 16 цветов в палитре (по 4 бита на цвет).
Нет смысла, очень мало цветов будет, это уже бабушка-EGA выходит.

Вот относительно подробная таблица.
biosprog.narod.ru/real/ints/int10.htm
NTFS Изменён автором
+1
Мне кажется, если заморочиться, в 16 индексированных цветов влезть можно

Это полностью автоматом, хотел руками сделать, но 2.8 гимп крашнулся, когда хотел размывание небу сделать.
StaSyaN
+1
Некоторая резкость спрайтов видна, да.
Можно конечно, но реально, несложно сделать буфер на 64Кб и самому.
У меня под буфер 64Кб, под спрайты 120 и еще 80 на код+статические данные. В лимит 500 Кб спокойно влезаю.
NTFS
+1
Там, как я понял, библиотека завязана под «1 пиксель = 1 байт».
А так-то тогда это вообще можно было бы на EGA сделать (и тогда бы я смог запустить это на своём 286-ом, а не доставать из шкафа лежаший где-то под грудой завалов 486-ой =)
makise_homura
0
выделить буфер, копировать спрайты из буферов спрайтов в этот буфер, а потом одной командой вывести этот буфер на экран
Тут есть терминологическая путаница. Часто это называют как раз-таки одинарной буферизацией, а полноценной двойной — это когда одно копирование, а буфера переключаются (с использованием только вызовов BIOS это невозможно, но можно вручную ввести VGA в такую вариацию режима 320x200x256, где будет не одна страница видеопамяти, а четыре).
наличие кросс-компиляции у Free Pascal Compiler 3.04 — он действительно генерирует 16-битный код из-под 32-битной системы, имея при том всю мощь современного ObjectPascal
Надо же, похоже, это единственный из современных компиляторов, который умеет работать в 16-битном ABI i8086.
например, при выводе спрайта с прозрачностью копируем байты, но проверяя при этом неравенство цвета условному цвету «прозрачный»
Мне кажется, это не совсем эффективно: здесь нужно будет делать сравнение каждого байта. При больших спрайтах это не очень удачно, кмк; мне в своё время понравилось, как это было сделано в DOOM — там каждый столбец спрайта (именно столбец, а не строка, потому что критической по быстродействию вещью был горизонтальный «рейтрейсинг» (точнее, не совсем рейтрейсинг, но тут это не сильно важно)) представлял собой последовательность примерно такого типа:
uint8_t    nblocks;
struct     data[nblocks]
{
    uint8_t n_transparent_pixels;
    uint8_t n_opaque_pixels;
    uint8_t pixels[n_opaque_pixels];
}
Выводилось это как-то так (пишу чисто из головы, суть в алгоритме, а не его аутентичности):
xor ax, ax
xor cx, cx
mov di, output_buf
mov si, line_data

mov cl, [si]
inc si
loop:
mov al, [si]
add di, ax
inc si
push cx
mov cl, [si]
inc si
rep movsb
pop cx
loop loop
makise_homura
+1
Код с выводом через кодирование длинных серий переписал на Си, а то адские письмена выше смогут прочесть не только лишь все.
Код упрощенный, не проверяем выходы за границы, переполнение dst и т.д.

unsigned int dst = 0 ;
for (unsigned int i=0; i<nblocks; i++) {
   dst+=data[i].n_transparent_pixels ;
   memcpy(output_buf[dst],data[i].pixels[0],data[i].n_opaque_pixels*sizeof(uint8_t)) ;
   dst+=data[i].n_opaque_pixels ;
}


Вообще, пробовал и такой способ представления данных, но выигрыша не заметил существенного.
У меня основная операция по времени — как раз копирование фоновой части буфера 320x200

mov ax,320
mov cx,200
mul cx
mov cx,ax
rep movsb


и даже мухлеж со словом вместо байта особо не спасает

mov ax,320
mov cx,200
mul cx
mov cx,ax
shr cx,1
rep movsw


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

Часто это называют как раз-таки одинарной буферизацией

Тут такое дело, что если аппаратно двойная буферизация (как в OpenGL) не поддерживается, то её приходится сооружать самому, делая второй буфер в обычной памяти и переключаясь между ними не командой железа, а копированием.
Но суть та же — используем ДВА буфера, один на экране статичен, другой в памяти рисуется.
NTFS Изменён автором
+1
Код с выводом через кодирование длинных серий переписал на Си
Ну, там ещё с ассемблером плюс в том, что видно, как это всё уложить в минимум операций. Но да, для тех, кто не знает ассемблер, си хотя бы даст понять, в чём там дело.
Вообще, пробовал и такой способ представления данных, но выигрыша не заметил существенного.
Это да, на спрайтах размера в десятки пикселей это, наверное, будет не слишком заметно.
У меня основная операция по времени — как раз копирование фоновой части буфера 320x200
Возможно, тогда правда стоит посмотреть в сторону VGA Y-mode и page flipping-а? Конечно, и так без него всё работает, но ради опыта (научиться работать с планарной моделью видеопамяти) — думаю, тебе было бы интересно =)
Заодно можно было бы проверить, работает ли это в DOSBox =)
Но суть та же — используем ДВА буфера, один на экране статичен, другой в памяти рисуется.
Вот видимо, терминологическая путаница возникает как раз по вопросу, что понимать под буфером: вообще всё, содержащее данные, или блок, в который можно писать при рендеринге кадра.
Это ещё не самая дичь: в электронике, например, иногда мультиплексором называют обычный мультиплексор из пачки AND-ов, объединённых OR-ом, а селектором — его же, но с дешифратором на адресных входах. А иногда наоборот, селектор называют мультиплексором, а селектором не называют вообще ничего, лол. Дешифратором называют часто то, что преобразует представление вида 1010 в 0000010000000000, но есть и дешифраторы команд и дешифраторы виртуальных адресов (которые на самом деле LUT, с микрокодом и TLB соответственно), то есть фактически ни капельки не дешифраторы… И так далее.
makise_homura Изменён автором
0
, тебе было бы интересно =)

Так много дел, ага.
По времени, уже надо делать обновление «Героев Эквестрии», новые кампании, герои, сюжетка и т.д.
NTFS
0
Надо же, похоже, это единственный из современных компиляторов, который умеет работать в 16-битном ABI i8086.

Про OpenWatcom уже писал, у него даже фанатская версия 2.0 недавно вышла.
Сейчас пробую Digital Mars, там для запуска нужен win32, но 16-битный crt0 и либы для четырех моделей памяти лежат рядом.

Кое-что есть, но это реально как бриться каменным топором.
NTFS
+1
Сейчас пробую Digital Mars

Попробовал, и всё работает как положено, даже встроенный нормальный ассемблер в стиле asm { } есть.
Еще один инструмент в копилку.
Брать можно отсюда www.digitalmars.com/download/freecompiler.html
NTFS
+1
Йей, круто!
Тоже запомню на всякий случай.
makise_homura
0
У него тот же косяк, что и у FPC — конский размер бинарника создаваемого. 16Кб на пустой проект с printf
NTFS
0
Дык полная реализация принтф с поддержкой всех форматов и есть конская, как ни крути…
KerHarrad
0
Сделать свой printf на int 21h? Для игр он и не нужен такой мощный, достаточно аналога бейсиковского PRINT с выводами переменных в списке, чисто на отладочную печать.
NTFS Изменён автором
0
Ага, вполне себе вариант.
KerHarrad
0
В любом случае, если решусь делать следующий проект на 16 бит, то сначала погоню на Паскале. Я плохо знаю С, а учитывая его особенности, скорее всего выстрелю себе в ногу.
NTFS Изменён автором
0
ага… сишные 16-бит указатели с их far, near… тут ноги вместе с яйцами отстрелить можно)
KerHarrad Изменён автором
0
С ними как раз никаких проблем, в Паскале тоже используются. Без дальних указателей с кучей работать нельзя, а без кучи игру не написать. Бейсик просто скрывает эту механику, но она лезет наружу при работе с ассемблером.
Меня в целом С как-то смущает, вроде один и тот же код нормально работает в Паскале, но через раз падает в Си.
NTFS
+1
Паскаль как я помню, он же вроде указатели проверяет на корректность сам по умолчанию, если опцией компилера не отрубить. Поэтому шибко ереси не наделаешь. А в сишке — абсолютно прямой доступ по указателю куда угодно.
KerHarrad
0
А что именно за код? Если это что-то мелкое, то может, я смогу ткнуть в ошибку.
makise_homura
0
Конкретных примеров уже не упомню, на С пишу редко.
Но обычно бывает так — вот пишу какую-то лабу, страничка, две, три. А потом начинает сыпаться. Через раз. Или через два. И пока не выкинешь и не перепишешь заново — толку нет.
Потом делаю на Паскале или C#, алгоритм аналогичной сложности — и всё ОК с первого раза, максимум, выходные данные нужно исправить, если именно реализация неверная.
NTFS
0
puts() должен занимать сильно меньше, если вопрос в выводе чего-то типа «hello world».
А, ну и да, если это просто прилинкованная реализация printf(), то это означает, что размер бинарника почти не будет расти, если ты используешь этот printf() не один раз, а сотню в разных местах.
А, ну и всегда есть всякие UPX (правда, это снизит размер бинарника на диске, а в памяти он будет таким же немаленьким).
makise_homura Изменён автором
0
puts()

Верно, я и забыл про эту функцию.
NTFS
+1
На всякий случай, дополнительно прорекламирую компилятор от Digital Mars — да, он не такой профессиональный, как OpenWatcom, но он создает довольно чистые obj-файлы, без тонн ссылок на собственные библиотеки. И готовые obj прекрасно линкуются как с QuickBasic, так и с TurboPascal.
OpenWatcom и Turbo C по умолчанию тянут дополнительные зависимости.

Если буду обновлять игру, то часть движка, которая была на QuickBasic — перенесу на DigitalMarsCompiler, а на Бейсике оставлю только игровую логику.
NTFS
+1
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.
Скрыто Показать