Ещё раз про тайлы
Всем привет, недавно у меня возникла необходимость ускорить рендер тайлов для нашего мультиколорного долгостроя Dice Legends. В результате экспериментов удалось увеличить игровое поле с 26х15 до 30х18. Как водится по результатам приятного времяпрепровождения я решил сделать небольшую игру, чтобы обкатать всякие штуки с картой и прочим. Ведь игра это не только про то как быстро что-то нарисовать.

Ничего нового я не изобрёл, но после выхода игры уже несколько человек попросили меня рассказать как там всё устроено. Постараюсь максимально кратко )
Тайлы выводятся согласно «рендер листу», выглядит он просто как список адресов процедур, примерно так:
Тайлы представляют собой данные + код вывода. Их можно генерировать прямо на z80, но лучше сделать конвертор и запаковать. После нескольких подходов я пришел к следующему варианту:
Для каждой трети экрана нужно ещё будет предустановить параметры рендера, например для второй трети экрана будет так:
В принципе это можно сделать отдельной процедурой и вшить в рендер лист.
Запуск рендера я делаю забавным сочетание команд, лет 10 назад я бы никогда такое не написал :)
Соответственно ret в конце процедуры каждого тайла сразу переходит на отрисовку следующего тайла, если вдруг кто запутался то достаточно вспомнить как работает ret — берёт адрес со стека и переходит по нему, но стек у нас указывает на рендер лист, так и летаем по тайлам. Power of RET \0/
Как видно из кода тайлика у нас свободна пара de, можно положить в неё два самых часто встречающихся байта и выжать ещё немного скорости. На моём наборе тайлов это оказались 0 и 255. Можно ещё пустые тайлы рисовать чисто атрибутом. Будет местами сложнее, например с анимацией тайликов, но всё в принципе решаемо.
Самое интересное это генерация рендер листа из карты уровня. Быстрее всего по скорости это принять концепцию «карта и есть рендер лист», да немного неудобно, придётся патчить/восстанавливать каждую строчку процедурой выхода из рендера. Я делал нечто подобное в Gluf где карта это огромный кусок LD PUSH, немного неприятно, но терпимо.
Во втором случае можно хранить карту также как набор адресов, но копировать в рендер лист нужный кусок. Выглядеть это может по разному, я тестил развёрнутый pop hl: ld (addr),hl получилось вполне быстро и ненужно ничего патчить.
И наконец третий вариант это хранить карту как обычно, один байт на тайлик. Тут конечно упираемся в ограничение 256 тайлов на карту, но зато карта уровня занимает меньше места. Именно это вариант я выбрал для ZakMcDruken и тут немного посложнее чем в предыдущих двух.
Для начала нам нужна табличка с адресами тайлов. В табличку на 256 байт влезает только 128 адресов поэтому нам нужно две для младшего и старшего адреса тайла:
Патчинг рендер листа будет зависеть от того как выглядит ваш рендер лист. У меня он разбит по строкам и в начале каждой строки ещё вызывается процедура которая включает/выключает бипер чтобы генерировать звуки для игры паралельнно рендеру.
Я использую стек чтобы патчить и у меня это выглядит примерно так:
Патчить надо с конца, т.к. всё делается через push. Прикол в том чтобы патчить сразу по два тайла, тогда мы можем прыгать по лутам младших и старших байтов адресов быстро, зная что перед вторым тайликом у нас указатель стоит на старшем байте. Это всё ещё можно развернуть из цикла, если памяти достаточно.
Надеюсь кому-нибудь это всё поможет!
PS: Ну и забавное, когда всё уже было отлажено я вспомнил что после yrgb 2019 в баре мы разговорились с Лёшей Борисовым по поводу как всё устроено у него в Automated Cave Explorer и кажется он про что-то такое говорил. Наверное третий раз в жизни я залез в дебагер и увидел там волшебные строчки, байт в байт совпадающие с тем то что написал я только я взял bc и там de )

На тот момент он работал в Gaijin Entertainment и из его интерьвю мы можем узнать что «ради интереса в этой игре сделал всё сам. С оптимизацией кода мне помогал Dark (Виталий Видмиров) из бывшей группы X-Trade, он направлял в нужную сторону мои размышления о том, как оптимально выводить изображение.»
Всё уже былов Симпсонахх в 90-е
PS-2: Если вдруг эту статью будут читать не спектрумисты, а наши друзья/фанаты советстких пк на 8080 то кажется этот метод не сильно подойдёт. Я сделал быстрый экспиремент под Вектор и там как и ожидалось быстрее делать по-классике через стек. Прикол в том что там 16-битные/2байтные команды выгоднее использовать т.к. как правило там каждая команда тормозится процом )
Кстати, кому-нибудь интересны заметки на тему «Со Спектрума на Вектор/Апогей/ПК8000 без регистрации и СМС», Это оказывается довольно весело )
See Ya \0/

Ничего нового я не изобрёл, но после выхода игры уже несколько человек попросили меня рассказать как там всё устроено. Постараюсь максимально кратко )
Тайлы выводятся согласно «рендер листу», выглядит он просто как список адресов процедур, примерно так:
renderList:
dw drawTile17 : dw drawTile33 : : dw drawTile63 ...... dw renderEnd
Тайлы представляют собой данные + код вывода. Их можно генерировать прямо на z80, но лучше сделать конвертор и запаковать. После нескольких подходов я пришел к следующему варианту:
drawTileNN:
ld (hl),01111110b ;байт пикселей
inc h
ld (hl),10000001b ;байт пикселей
inc h
ld (hl),10000001b ;байт пикселей
inc h
ld (hl),10000001b ;байт пикселей
inc h
ld (hl),10000001b ;байт пикселей
inc h
ld (hl),10000001b ;байт пикселей
inc h
ld (hl),10000001b ;байт пикселей
inc h
ld (hl),01111110b ;байт пикселей
;атрибуты
ld h,b ;переходим в атрибуты
ld (hl),1*8+0 ;байт атрибутов
;restore h
ld h,c ;обратно в пиксели
inc l ;к следующему знакоместу
ret ;рисуем следующий тайл
Для каждой трети экрана нужно ещё будет предустановить параметры рендера, например для второй трети экрана будет так:
ld b, high (22528+256*1) : ld hl,16384+2048*1 : ld c,h
В принципе это можно сделать отдельной процедурой и вшить в рендер лист.
Запуск рендера я делаю забавным сочетание команд, лет 10 назад я бы никогда такое не написал :)
ld sp,renderList
ret
Соответственно ret в конце процедуры каждого тайла сразу переходит на отрисовку следующего тайла, если вдруг кто запутался то достаточно вспомнить как работает ret — берёт адрес со стека и переходит по нему, но стек у нас указывает на рендер лист, так и летаем по тайлам. Power of RET \0/
Как видно из кода тайлика у нас свободна пара de, можно положить в неё два самых часто встречающихся байта и выжать ещё немного скорости. На моём наборе тайлов это оказались 0 и 255. Можно ещё пустые тайлы рисовать чисто атрибутом. Будет местами сложнее, например с анимацией тайликов, но всё в принципе решаемо.
Самое интересное это генерация рендер листа из карты уровня. Быстрее всего по скорости это принять концепцию «карта и есть рендер лист», да немного неудобно, придётся патчить/восстанавливать каждую строчку процедурой выхода из рендера. Я делал нечто подобное в Gluf где карта это огромный кусок LD PUSH, немного неприятно, но терпимо.
Во втором случае можно хранить карту также как набор адресов, но копировать в рендер лист нужный кусок. Выглядеть это может по разному, я тестил развёрнутый pop hl: ld (addr),hl получилось вполне быстро и ненужно ничего патчить.
И наконец третий вариант это хранить карту как обычно, один байт на тайлик. Тут конечно упираемся в ограничение 256 тайлов на карту, но зато карта уровня занимает меньше места. Именно это вариант я выбрал для ZakMcDruken и тут немного посложнее чем в предыдущих двух.
Для начала нам нужна табличка с адресами тайлов. В табличку на 256 байт влезает только 128 адресов поэтому нам нужно две для младшего и старшего адреса тайла:
align 256
tilesMapperFull:
N=0
dup 256
db low (drawTile+N*29)
N=N+1
edup
N=0
dup 256
db high (drawTile+N*29)
N=N+1
edup
Патчинг рендер листа будет зависеть от того как выглядит ваш рендер лист. У меня он разбит по строкам и в начале каждой строки ещё вызывается процедура которая включает/выключает бипер чтобы генерировать звуки для игры паралельнно рендеру.
Я использую стек чтобы патчить и у меня это выглядит примерно так:
ld de,gameMap
ld h,high tilesMapperFull
ld iyl,8
exx : ld hl,row0End : ld bc,66 : ld sp,hl : exx
rep0_0:
;==============================================
dup 16
; 7 4
ld a,(de) : ld l,a
; 7 4 7
ld c,(hl) : inc h : ld b,(hl)
; 11
push bc
; 4
dec e
; 7 4
ld a,(de) : ld l,a
; 7 4 7
ld b,(hl) : dec h : ld c,(hl)
; 11
push bc
; 4
dec e
edup
org $-1
;========================================================
ex de,hl : ld bc,64+31: add hl,bc: ex de,hl
exx : add hl,bc : ld sp,hl : exx
dec iyl : jp nz,rep0_0
Патчить надо с конца, т.к. всё делается через push. Прикол в том чтобы патчить сразу по два тайла, тогда мы можем прыгать по лутам младших и старших байтов адресов быстро, зная что перед вторым тайликом у нас указатель стоит на старшем байте. Это всё ещё можно развернуть из цикла, если памяти достаточно.
Надеюсь кому-нибудь это всё поможет!
PS: Ну и забавное, когда всё уже было отлажено я вспомнил что после yrgb 2019 в баре мы разговорились с Лёшей Борисовым по поводу как всё устроено у него в Automated Cave Explorer и кажется он про что-то такое говорил. Наверное третий раз в жизни я залез в дебагер и увидел там волшебные строчки, байт в байт совпадающие с тем то что написал я только я взял bc и там de )

На тот момент он работал в Gaijin Entertainment и из его интерьвю мы можем узнать что «ради интереса в этой игре сделал всё сам. С оптимизацией кода мне помогал Dark (Виталий Видмиров) из бывшей группы X-Trade, он направлял в нужную сторону мои размышления о том, как оптимально выводить изображение.»
Всё уже было
PS-2: Если вдруг эту статью будут читать не спектрумисты, а наши друзья/фанаты советстких пк на 8080 то кажется этот метод не сильно подойдёт. Я сделал быстрый экспиремент под Вектор и там как и ожидалось быстрее делать по-классике через стек. Прикол в том что там 16-битные/2байтные команды выгоднее использовать т.к. как правило там каждая команда тормозится процом )
Кстати, кому-нибудь интересны заметки на тему «Со Спектрума на Вектор/Апогей/ПК8000 без регистрации и СМС», Это оказывается довольно весело )
See Ya \0/
0 комментариев