PDA

Просмотр полной версии : Выводим спрайт, с проверкой границ экрана



drbars
30.12.2012, 11:59
Всех с наступившим 0x7DD годом! :) Продолжаю эксперименты над выводом спрайта с учётом границ экрана, перетряхнул процедуру и сделал вывод атрибутов.

Сами атрибуты выводятся в основном цикле, с каждой 8ой строкой спрайта. На мой взгляд это намного быстрее, чем выводить строку отдельной итерацией и тем более быстрее вывода атрибутов отдельным циклом, с учётом специфики границ экрана. Если спрайт не заходит за границы экрана, то подготовка регистров для процедуры вывода (включая проверку границ, расчет фазы атрибутов и адреса экрана) занимает около 300 тактов. При нижней границе у нас отсекается высота спрайта (+16 тактов), а вот верхнюю границу можно немного оптимизировать.

Процедура по-прежнему не использует стек :v2_dizzy_roll:



DEVICE ZXSPECTRUM128

MODULE MAIN

SPR_SIZE=#10

ORG #8000

START:
LD SP,#7FFF
LD HL,#BE00
LD A,H
LD I,A
IM 2
LD BC,#00BF
LD (HL),C
INC HL
DJNZ $-2
LD (HL),C
LD H,C
LD L,C
LD (HL),#C9

ST_LOOP:
EI
HALT
LD A,#07
OUT (#FE),A

LD HL,TEST_SPRITE
CURS_Y LD D,0 ; D=Y
CURS_X LD E,0 ; E=X
LD B,64 ; B=HEIGHT
LD C,SPR_SIZE ; C=LEN
CALL SPRITE_OUT

SUB A
OUT (#FE),A
JP ST_LOOP


SPRITE_OUT:
;-----------------------------------------------------
; Проверка левой и правой границ, расчет длинны строки
LD A,E
OR A
JP P,R_EDGE
NEG
LD (L_SKIP1),A
LD (L_SKIP2),A
NEG
ADD A,C
LD E,#00 ; Начало левой границы по X
EXX
LD IY,SP_CL2 ; Указатель на процедуру вывода коррекции слева
LD DE,PHASE2 ; Указатель на параметр фазы
JP LEN_CL
R_EDGE ADD A,C
SUB #20
JR C,NO_COR
LD (R_SKIP1),A
LD (R_SKIP2),A
NEG
ADD A,C
CP C ; Спрайт на всю ширину экрана?
EXX
LD IY,SP_CL3 ; Процедура вывода коррекции справа
LD DE,PHASE3 ; Указатель на параметр фазы
JR NZ,LEN_CL
EXX
NO_COR LD A,C ; Вывод без коррекции
EXX
LD IY,SP_CL1 ; Указатель на процедуру вывода без коррекции
LD DE,PHASE1 ; Указатель на параметр фазы
LEN_CL OR A
RET M ; Выход, если спрайт целиком за левой или правой границей экрана
ADD A,A ; Тут расчитываем длину спрайта для текущего метода вывода
SUB #40
NEG
LD IX,L_OUT1 ; Указатель на процедуру вывода LDI (HL-->DE)
LD C,A ; C'=A
LD B,#00
ADD IX,BC
EXX
;--------------------------------
; Проверка верхней границы экрана
LD A,D
CP #C1 ; Верхняя граница (-1..-63)
JR C,D_EDGE
NEG ; A - кол-во пропускаемых строк сверху
CP B
RET P ; Выход, если спрайт целиком за верхней границей экрана
LD D,A
PUSH DE
RRCA ; Расчитываем сколько строк атрибутов ушло за верхнюю границу
RRCA
RRCA
AND #1F
ADD A,D ; Пропускам D/8 строк
LD E,A
LD D,#00
PUSH BC
LD A,B
EXX
INC A
AND #07
LD (DE),A ; Помещаем код фазы в выбранную процедуру
EXX
LD B,#06
MULCYC SRL C
JR NC,NO_ADD
ADD HL,DE
NO_ADD SLA E
RL D
DJNZ MULCYC
POP AF
POP DE
SUB D
LD B,A
LD D,#40 ; Начало верхней границы на экране
JP (IY)
;-------------------------------
; Проверка нижней границы экрана
D_EDGE LD A,D
SUB #C0 ; Нижняя граница > 192 ?
RET NC ; Выход, если спрайт целиком за нижней границей экрана
ADD A,B
JR NC,CLC_DE ; Не корректируем высоту спрайта
SUB B
NEG
LD B,A
;-----------------------------
; Расчет экранного адреса
; E=X (#00-#1F), D=Y (#00-#BF)
CLC_DE LD A,D
AND #38
RLCA
RLCA
OR E
LD E,A
LD A,D
AND #C0
RRA
SCF
RRA
RRCA
XOR D
AND #F8
XOR D
LD D,A
;---------------------------------------
; Расчет фазы атрибутов для цикла вывода
LD A,B
EXX
INC A
AND #07
LD (DE),A ; Помещаем код фазы в выбранную процедуру
EXX
JP (IY)
;---------------------------------------------------
; Расчет в DE адрес атрибута, вывод строки атрибутов
AT_OUT LD A,D
RRCA
RRCA
RRCA
AND #0B
OR #50
LD D,A
;-----------------------------
; Вывод строки спрайта
L_OUT LD C,D
JP (IX)
L_OUT1 DUP 32
LDI
EDUP
RET
;-----------------------------
; Расчет следующего знакоместа
COR_DE LD A,E
SUB #E0
LD E,A
SBC A,A
AND #F8
ADD A,D
LD D,A
JP (IY)
;-----------------------------------------------------
; Процедура вывода на экран с атрибутами без коррекции
LOOP1 INC D
LD A,D
AND #07
JR Z,COR_DE
SP_CL1 LD A,E
CALL L_OUT
DEC DE ; Коррекция D, если Е > #FF
LD E,A
LD A,B
AND #07
PHASE1 EQU $+1
XOR #00
JR Z,A_OUT1
DJNZ LOOP1
RET
A_OUT1 PUSH DE
CALL AT_OUT
POP DE
DJNZ LOOP1
RET
;----------------------------------------------------------
; Процедура вывода на экран с атрибутами с коррекцией слева
LOOP2 INC D
LD A,D
AND #07
JR Z,COR_DE
SP_CL2 LD A,B
L_SKIP1 EQU $+1
LD BC,#0000 ; Переменная отступа слева
ADD HL,BC
LD B,E
CALL L_OUT
LD E,B ; При сдвиге влево D можно не корректировать
LD B,A
AND #07
PHASE2 EQU $+1
XOR #00
JR Z,A_OUT2
DJNZ LOOP2
RET
A_OUT2 PUSH DE
LD A,B
L_SKIP2 EQU $+1
LD BC,#0000
ADD HL,BC
LD B,A
CALL AT_OUT
POP DE
DJNZ LOOP2
RET
;-----------------------------------------------------------
; Процедура вывода на экран с атрибутами с коррекцией справа
LOOP3 INC D
LD A,D
AND #07
JR Z,COR_DE
SP_CL3 LD A,B
LD B,E
CALL L_OUT
DEC DE ; Коррекция D, если Е > #FF
LD E,B
R_SKIP1 EQU $+1
LD BC,#0000 ; Переменная отступа справа
ADD HL,BC
LD B,A
AND #07
PHASE3 EQU $+1
XOR #00
JR Z,A_OUT3
DJNZ LOOP3
RET
A_OUT3 PUSH DE
CALL AT_OUT
LD A,B
R_SKIP2 EQU $+1
LD BC,#0000
ADD HL,BC
LD B,A
POP DE
DJNZ LOOP3
RET

;----------------
; Тестовый спрайт
ORG #C000,#00
TEST_SPRITE:
DUP 3

DUP 4
DS SPR_SIZE,#55
DS SPR_SIZE,#AA
EDUP
DB #41
DS SPR_SIZE-2,#01
DB #41

DUP 4
DS SPR_SIZE,#F0
EDUP
DUP 4
DS SPR_SIZE,#0F
EDUP
DB #42
DS SPR_SIZE-2,#02
DB #42

DUP 4
DS SPR_SIZE,#55
DS SPR_SIZE,#AA
EDUP
DB #43
DS SPR_SIZE-2,#03
DB #43

DUP 4
DS SPR_SIZE,#F0
EDUP
DUP 4
DS SPR_SIZE,#0F
EDUP
DB #44
DS SPR_SIZE-2,#04
DB #44

DUP 4
DS SPR_SIZE,#55
DS SPR_SIZE,#AA
EDUP
DB #45
DS SPR_SIZE-2,#05
DB #45

DUP 4
DS SPR_SIZE,#F0
EDUP
DUP 4
DS SPR_SIZE,#0F
EDUP
DB #46
DS SPR_SIZE-2,#06
DB #46

DUP 4
DS SPR_SIZE,#55
DS SPR_SIZE,#AA
EDUP
DB #07
DS SPR_SIZE-2,#47
DB #07

DUP 4
DS SPR_SIZE,#F0
EDUP
DUP 4
DS SPR_SIZE,#0F
EDUP
DB #41
DS SPR_SIZE-2,#01
DB #41


EDUP
PAGE #00
SAVESNA "test.sna",START
ENDMODULE



---

Upd. во вложении пример использования этой процедуры для вывода спрайта в игре. :v2_dizzy_keyboard:

Alex Rider
30.12.2012, 13:06
Я тоже после мероприятия, пока не в состоянии глубоко анализировать чужой код :( Сорри.

Destr
30.12.2012, 15:37
Процедуру отсечки нужно гвоздями прибивать к размерности спрайта.
Т.е. сама выводилка уже в себе несёт знание с чем ей работать.
Говоря языком человеков = под каждый конкретный размер спрайта - своя процедура вывода.
Тогда можно заточить выводилку так, что даже байтовая координата будет нести больше инфы, чем казалось могла-бы.
(Ну к примеру не 256 значений в байте можно хранить, а все 258. именно из-за того что либо влево либо вправо спрайт вылазит (и отсекается).)
Сумбурно описал, наверное, но суть думаю ясна.
Сильно не бить, ламер я ещё тот :)
Но работает такой подход!
(в ранних спек-гамах, этого не делали, делали "закрытку" атрибутами слева и справа, отсекайка как-бы и ненужна, но мы-то хотим кодить под фулскрин, так что придётся колдовать :)
Причем по тактам не так уж и много это занимает.

---------- Post added at 14:37 ---------- Previous post was at 14:32 ----------

А, да!
Совсем забыл.
Некий jerri зело крут в таких вещах.
Топикстартеру лучше всего с ним в асе (или ещё как) погутарить.
Он всегда поможет быстрей и толковей чем весь форум вместе взятый.
(форумчане хоть и дружелюбны, но ленивы, каждый надеется что кто-то другой сделает, а жерри - он оперативен, быстр и мудр)

Vitamin
30.12.2012, 16:03
Капля оффтопа. Одно время вынашивал идею хранения спрайтов в больших картах шириной в 256 знакомест, кадрирование и вывод получаются весьма просто. Очевидные проблемы- жуткая фрагментация памяти. "Дырки" можно использовать под системные нужды. В некоем ограниченном виде даже применил.

alone
30.12.2012, 16:43
Надо учесть, что обычно нужны спрайты шириной 1-4 знакоместа с точностью до 1-4 пикселей по горизонтали.

Destr
30.12.2012, 16:48
Надо учесть, что обычно нужны спрайты шириной 1-4 знакоместа с точностью до 1-4 пикселей по горизонтали.
По-ходу тут пытаются (т.drbars) универсальную (или приближённо к идеалу) процедуру замутить, но пока ещё не осознал что спек (как структура) этого не допустит :)

drbars
30.12.2012, 16:52
Говоря языком человеков = под каждый конкретный размер спрайта - своя процедура вывода.

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

Сейчас задача стоит написать небольшую, но эффективную процедуру вывода.


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


Если такты не жмут но все нормально. Немного странно что ldi в call'ах, но не критично. Если все плохо то я бы изначально попробовал под ld RP,data push RP.
Это процедура вывода локаций из моей игры, стек нельзя использовать категорически. Все стековые выводы подвижной графики только в прерываниях. Суть вызова LDI массива через CALL очевидна. Да, это 27 лишних тактов на строку. Далее, эти массивы будут разные: для вывода OR/AND/XOR, отражения и сдвига на полубайт. При инициализации заданному циклу вывода будет указываться заданная процедура вывода строки. Но это позже.

Второе это отрисовка атрибутов, как видно из кода там есть заготовка для расчета адреса атрибута. Как её толком использовать пока не придумал, но это явно эффективнее чем рисовать атрибуты отдельным циклом.

Хранить атрибуты в начале спрайта или в конце тоже не эффективно. Надо ещё и расчитывать адрес атрибутов при выходе за верхнюю или нижнюю границу экрана (ведь в тогда в конце вывода HL указывает не на спрайт, а на массив атрибутов), а это альтернативные регистры для передачи данных на вывод (конструкции типа PUSH HL:EXX:POP HL) и ещё одно умножение как минимум. В общем не вариант.

Destr
30.12.2012, 19:16
Проблема: Как быстро, и главное правильно, расчитать момент включения вывода строки атрибута при выходе за верхнюю или нижнюю границы экрана?

Это и есть самый сволочизм zx-спек-строения.
Грааль нашей 8-бит машинки.
Сорри за оф (если что)

drbars
30.12.2012, 19:30
Это и есть самый сволочизм zx-спек-строения.
Грааль нашей 8-бит машинки.
Сорри за оф (если что)

да ничё, я уже нижнюю границу запилил :v2_dizzy_step: Злое какое-то шампанское было...
Сам отношусь к чужому коду — проще написать свой, чем понимать.
Всё гениальное — просто :) Втянул кодинг, после всяких 1С и Ораклов хорошо мозг проветривает :v2_dizzy_keyboard:

13 лет ничего не писал, а тут раз чё-то сел и понеслось :)

Destr
30.12.2012, 22:24
какое-то шампанское было...
:)
(бъем бокалы, не знаю как там смайл копирастиется)

drbars
30.12.2012, 23:02
В общем сделал расчет атрибутов верх и низ, теперь лево-право думаю... :) Тока с атрибутами, высота спрайта должна быть кратна 8, иначе — видеоэффекты :) Фаза расчитывается исходя из высоты спрайта, а регистр B в цикле DJNZ используется как счетчик, чтобы не заводить лишний. Правда зачем некратная высота нужна с атрибутом? :v2_conf2:

jerri
30.12.2012, 23:55
drbars, давай рассмотрим ситуацию
ты выводишь спрайт с аттрибутами

высота спрайта 64
выводишь с точностью до 4

последнюю строку аттрибутов будешь дублировать? чтобы изображение не плыло или может проще спрайт одним цветом красить?

drbars
31.12.2012, 01:00
jerri, нет, строка пока не дублируется. Завтра доделаю базовую часть, а там дальше думать будем:)

Wlodek
31.12.2012, 21:19
Я делал так: таблица адресов экрана с избыточным количеством значений. Для крайних координат вместо адресов экрана указываются адреса в ПЗУ. В итоге отсечка получается автоматически без специальных мер.

drbars
01.01.2013, 17:59
Я делал так: таблица адресов экрана с избыточным количеством значений. Для крайних координат вместо адресов экрана указываются адреса в ПЗУ. В итоге отсечка получается автоматически без специальных мер.
Процедура не использует таблиц, расчет на лету :) Код доработал, обновил топик.
Проблема в том, что с таблиц очень удобно стеком снимать адрес в регистовую пару. Если же без стека, то нужно считывать в регистр сначала один байт, затем инкремент, второй байт, снова инкремент... а ещё и сам регистр HL затирать нельзя. Так что без стека, гораздо быстрее считать на лету.

Кто-нибудь видит способы оптимизации? Думается мне, можно как-то уменьшать кол-во лишних итераций в цикле умножения при проверке верхней границы.

drbars
02.01.2013, 12:39
Сложность номер 2 — внедрить вывод спрайта с заданным цветом в рамках аглоритма:

Чтобы это работало, на этапе инициализации задать:
A' — цвет спайта
E' — длина спрайта (можно и напрямую передавать, так даже быстрее. Сделал для примера)



AT_OUT LD A,D
RRCA
RRCA
RRCA
AND #0B
OR #50
LD D,A
EX AF,AF'
JP Z,AT_CLR
PUSH BC
EX AF,AF'
EXX
LD A,E
EXX
LD B,A
EX AF,AF'
AT_FILL LD (DE),A
INC E
DJNZ AT_FILL
EX AF,AF'
EXX
LD A,E
EXX
LD E,A
LD D,#00
ADD HL,DE
POP BC
RET
AT_CLR EX AF,AF'
...

jerri
02.01.2013, 12:41
drbars, есть такое предложение по организации графики

есть битмап шириной 2048 точек (256 байт) высотой 64 точки (16384 байт)
если нужен цвет создаем еще массив (256*8) 2048

итого 18432 под графику
если надо больше - можно выделить больше
далее
вывод спрайта

ldi
ldi
ldi
ld a(hl) ld (de),a
inc h inc d
ldd
ldd
ldd
ld a(hl) ld (de),a
inc h
inc d

вариант с маской возможен тоже

далее - урезание поверху - берем координату Y и если она отрицательно и меньше высоты то прибавляем к адресу спрайта и прибавляем к высоте.

подробнее раскладывать?

alone
02.01.2013, 12:45
А сабж для каких игр?

drbars
02.01.2013, 13:58
А сабж для каких игр?
Для отрисовки статических локаций спрайтом произвольного размера.

jerri, ты предлагаешь из bitmap'а выкусывать спрайт, расчитывая отступ слева и справа?

upd. понял inc h =) Метод прикольный, поэксперементирую :)
Единственное, придётся ещё держать таблицу указателей на спрайт в массив и его размеры. Но это в любом случае нужная информация.

---------- Post added at 16:58 ---------- Previous post was at 15:46 ----------

Вот какой из PC форматов может такой bitmap хранить без конверсии? Чтобы накидать в фотошопе спрайтов и не париться с вырезанием кстати :)

upd. .wbm отлично подходит) кстати, этот алгорим решил ещё одну мою системную проблему.
ну, а вывод спарйтов в топике — может кому пригодится :) Он тоже хорош, для своих целей.

jerri
02.01.2013, 18:22
drbars, могу кинуть свою нарезалочку

drbars
02.01.2013, 18:37
drbars, могу кинуть свою нарезалочку
Давай на почту. drbars:v2_dizzy_keyboard:ngs.ru

Я решил не расчитывать на лету границы, а уже готовые параметры в таблице карт хранить. Так быстрее отрисовка и спрайт максимально 8*8 знакомест, с кратностью высоты 2.

newart
02.01.2013, 18:44
drbars, в чем кодишь? Графику кто рисует?

drbars
03.01.2013, 12:22
drbars, в чем кодишь? Графику кто рисует?
100% pure asm :v2_dizzy_coder:

alone
04.01.2013, 01:23
ля отрисовки статических локаций спрайтом произвольного размера.
Весь экран за фрейм не заполнить. А стек занят.

GriV
05.01.2013, 10:28
Я свою капельку добавлю. Автор топика (drbars) выводит спрайты-полотна, а дырявые спрайты - состоящие из знакомсет правильнее, т.к. часто же половина спрайта заполнена нулями и чёрнотой.
Самый быстрый способ вывода "дырявых" знакоместных спрайтов я нашёл для себя такой - это хранить адрес в атрибутной области, куда выводить атрибут, а быструю процедуру пересчёта атрибутов в пикселы я приводил уже в этюдах (http://zx.pk.ru/showpost.php?p=451743&postcount=97). Т.е. получив адрес знакоместа сразу записываем туда атрибут, в 35 тактов переходим в адреса пикселов, и записываем уже само знакоместо.
Проверять на выход за границу экрана тоже просто - если добавить атрибутное смещение по X и Y в атрибутных же терминах, то если адрес получился за границами атрибутной области, то точно выводить не надо. Если 3 старших бита младшего байта поменялись - то аналогично, выводить не надо.

Код такой

ld bc,x+y*32 - координаты куда выводить, x и y могут быть отрицательными

....

pop af - в аккумуляторе - атрибут
pop hl - в hl - атрибутный адрес

ld e,a

ld a,l - запоминаем чтобы использовать старшие 3 бита
add hl,bc

xor l
and %11100000 - сравниваем изменились ли эти биты, т.е. вписались ли по X
jr nz,out_of_screen

ld a,h
cp #58 - проверяем был ли выход за атрибутную область, т.е. вписались ли по Y
jr c,out_of_screen
cp #5b
jr nc,out_of_screen

ld (hl),e - записываем атрибут

; преобразование адреса атрибутов в адрес знакоместа
ld a,h ; 4
; and %00000011 ; 7 0 - так и хочется замаскировать эти биты, но эта операция не нужна,
add a,a ; 4 8 - т.к. старшие биты уйдут по дальнейшей маске %00011111, а
add a,a ; 4 12 - младшие биты забиты нулями по add a,a
add a,a ; 4 16
xor h ; 4 20
and %00011111 ; 7 27 - сохраним нужные биты и обнулим ненужные 0-2
xor h ; 4 31 - вытащили исходный адрес расположения
ld h,a ; 4 35

dup 4 ; 4 раза выводим пару пиксельных линий
pop de
ld (hl),e
inc h
ld (hl),d
inc h
edup

получается очень быстро, заточенность на стек легко убираема.

alone
05.01.2013, 14:19
Чем не устраивает sub #50:rlca:rlca:rlca?

drbars
06.01.2013, 00:21
Я решил для себя ввести некоторые ограничения:

1) Сделать предвычисления и уже их помещать в карту вывода спрайтов. Ведь мне нужна ещё возможность выводить заданную часть спрайта, обрезая его слева или справа не границей экрана. Для этого очень удобен метод 256 знакомест, предложенный выше.

2) На каждую длину каждого метода вывода будет отдельная процедурка.

GriV
06.01.2013, 00:40
Чем не устраивает sub #50:rlca:rlca:rlca?
объясни?

alone
06.01.2013, 01:07
Пересчёт старшего адреса атрибутов в адрес на экране.

drbars
06.01.2013, 12:51
dup 4 ; 4 раза выводим пару пиксельных линий
pop de
ld (hl),e
inc h
ld (hl),d
inc h
edup

Здесь получаются те же 16 тактов на байт, аналогично LDI по скорости.
10+7+4+7+4=32

newart
06.01.2013, 14:00
получается очень быстро
А конкретнее?