Ещё раз про тайлы

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



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

Тайлы выводятся согласно «рендер листу», выглядит он просто как список адресов процедур, примерно так:

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, он направлял в нужную сторону мои размышления о том, как оптимально выводить изображение.»

Всё уже было в Симпсонахх в 90-е

PS-2: Если вдруг эту статью будут читать не спектрумисты, а наши друзья/фанаты советстких пк на 8080 то кажется этот метод не сильно подойдёт. Я сделал быстрый экспиремент под Вектор и там как и ожидалось быстрее делать по-классике через стек. Прикол в том что там 16-битные/2байтные команды выгоднее использовать т.к. как правило там каждая команда тормозится процом )

Кстати, кому-нибудь интересны заметки на тему «Со Спектрума на Вектор/Апогей/ПК8000 без регистрации и СМС», Это оказывается довольно весело )

See Ya \0/

0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.