теория спрайтовго движка
теория спрайтовго движка
не буду приводить расчёты (потому что глупец). но кажется (я практически уверен что это факт) единственный способ это раскрытие циклов - абстрактно говоря. потому что мы тупо расписываем множество ветвлений, предполагая что можно заранее посчитать ветку поведения (скажем). у меня просто вопрос, так все делают: раскрывают на 16кб варианты отрисовки 8x8 спрайта со всеми смещениями. но если грубо посчитать в уме, не получается чтобы даже 8*8 вариантов помещалось в 16кб. а ведь ещё есть специфика экрана, с его тремя областями. вопрос в том: как это обычно делают?
[img=1323]https://www.shemale-porn-galls.com/galls/baileyjay/sex_hat_pic/3.jpg[/img]
Последний раз редактировалось lenin1st 15 сен 2022, 08:46, всего редактировалось 1 раз.
да это понятно всё. хотя что значит змейкой я не понял. я про то что есть алгоритм вывода в любую точку экрана, так. а если раскрыть циклы и знать первую точку (соответственно), можно очень навыигрывать сократив циклы. естественно все так делают, просто интересно насколько жёстко и что именно раскрывают. потому что так-то 64 варианта только начала спрайта есть, а ещё специфика экрана.
Последний раз редактировалось lenin1st 15 сен 2022, 15:00, всего редактировалось 1 раз.
если выводить по линиям, то выходит побайтное копирование данных на экран, например (HL)->(DE). Нарисовав линию спрайта, нужно перейти вниз, по экрану, например
Казалось бы, удобнее запомнить DE и сделать так
Трюк в том, что первая линия копируется, без сохранения регистра DE, а следующая линия копируется обратно
т.е. в первой линии будет
ld a,(hl)
ld (de),a
inc hl
inc e
а во второй
ld a,(hl)
ld (de),a
inc hl
dec e
Понятно, что нужно подготовить спрайт, чтобы шел другой порядок байтов.
Код: Выделить всё
nbde INC D:LD A,D:AND 7:RET NZ
LD A,E:ADD A,#20:LD E,A:RET C
LD A,D:SUB 8:LD D,A:RET
Код: Выделить всё
push de
;копирование линии
pop de
call nbde;переход на линию экрана
т.е. в первой линии будет
ld a,(hl)
ld (de),a
inc hl
inc e
а во второй
ld a,(hl)
ld (de),a
inc hl
dec e
Понятно, что нужно подготовить спрайт, чтобы шел другой порядок байтов.
Ненависть- это подарок
щас пока не понял (уже накидался). но самое быстрое, как я понял, это заполнять экран через стек - это не наш путь, просто хотелось упомянуть. прочитаю завтра - осмыслю.
Последний раз редактировалось lenin1st 15 сен 2022, 17:02, всего редактировалось 1 раз.
стек можно использовать, НО:
-число размер спрайта четное
-положение на экране одно и тоже
-нужно уместиться в 1 фрейм между прерываниями, иначе данные засрутся.
пример:
можно по-другому, но с ограничениями.Например
pop bc
ld (hl),c
inc l
ld (hl),b
inc l
-число размер спрайта четное
-положение на экране одно и тоже
-нужно уместиться в 1 фрейм между прерываниями, иначе данные засрутся.
пример:
Код: Выделить всё
ld (backsp+1),sp;сохранить стек
ld sp, sprite ; стек указывает на данные спрайта
;развернутый цикл
pop hl
ld ($4000),hl
pop hl
ld ($4002),hl
... продолжить копирование одной линии
;вторая линия
pop hl
ld ($4100),hl
pop hl
ld ($4102),hl
backsp: ld sp,0;восстановить значение стека
можно по-другому, но с ограничениями.Например
pop bc
ld (hl),c
inc l
ld (hl),b
inc l
Ненависть- это подарок
я немного ушёл головой и телом в плотнейшую лень. но держал в голове эту тему. вчера-позавчера начал ковырять. вот кстати даже ни разу не писал спрайты (не в частностях каких-то там), интересно поковыряться.
Шыншыл
в nbde наверно не ret c и sub 8, а ret nc и add 8. и типа в среднем тогда (168*(4+4+7+11) + 21*(4+4+7+11+4+7+4+11) + 3*(4+4+7+11+4+7+4+11+4+7+4+10)) / 192 = 29.64~ ну да, я же что-то такое и писал уже. а почему-то в этот раз мне в голову залетела только таблица адресов.
ну а стек это понятно, чтобы экран двигать. то наверно не об этом.
ну а что поделать, вкину что высрал и пусть будет уже.
Шыншыл
в nbde наверно не ret c и sub 8, а ret nc и add 8. и типа в среднем тогда (168*(4+4+7+11) + 21*(4+4+7+11+4+7+4+11) + 3*(4+4+7+11+4+7+4+11+4+7+4+10)) / 192 = 29.64~ ну да, я же что-то такое и писал уже. а почему-то в этот раз мне в голову залетела только таблица адресов.
ну а стек это понятно, чтобы экран двигать. то наверно не об этом.
ну а что поделать, вкину что высрал и пусть будет уже.
Последний раз редактировалось lenin1st 13 окт 2022, 15:43, всего редактировалось 1 раз.
СПРАЙТ КСОРАМИ
оговорюсь об ограничениях (помимо того что это тупой ксор): немодифицирующийся код (код не меняется во время исполнения или непосредственно до); не выравненный по прерыванию и с правильным стеком - код всюду готов к прерыванию; код reentrant (при сохранении контекста на прерывании, можно вызвать этот же код пока он в процессе); формат хранения спрайта по линиям; отрисовка прямо на экран без теневого; без обработки ситуаций половинчатой отрисовки на краях экрана. по сути, всё для упрощения.
лобовая реализацию:
первое что нужно раскрывать, это построчную отрисовку. потому что по горизонтали это тупо инкримент и потому что опционального сдвига нет (а мне почему-то кажется что это горячее место). получается 8 блоков отрисовки линий с разным сдвигом. ориентировка на форматы спрайтов 8x8, 16x16, 24x24, покрывает практически все случаи (из адекватных), так что я нацелюсь на них. но начнум с 16x16 и дальше будет видно (примерно умножив на 3) до какой глубины это можно довести.
отрисовка 16x16 спрайта, линии:
отрисовка 16x16 спрайта, верхний уровень:
(без пролога pop/push но с эпилогом ret, потому что пролог временный)
70+149+185+201+217+201+185+149 = 1357 t-states
11+28+38+42+46+42+38+28 = 273 bytes
сравнение с лобовой реализацией (прерываний на спрайт - отрисовка и очистка):
94/176 = 0.535~
377/176 = 2.142~
(1-2714/70908/(94/176) = 0.92~ процент времени в вышестоящем коде (прологе, в
C и библиотечном коде на прерываниях).
https://disk.yandex.com/d/p9yWzCoo2UNZtA
(тапка теста + частичный код (без библиотеки и сборщика))
оговорюсь об ограничениях (помимо того что это тупой ксор): немодифицирующийся код (код не меняется во время исполнения или непосредственно до); не выравненный по прерыванию и с правильным стеком - код всюду готов к прерыванию; код reentrant (при сохранении контекста на прерывании, можно вызвать этот же код пока он в процессе); формат хранения спрайта по линиям; отрисовка прямо на экран без теневого; без обработки ситуаций половинчатой отрисовки на краях экрана. по сути, всё для упрощения.
лобовая реализацию:
Код: Выделить всё
#define SCREENPTR(HIGHBYTE, X, Y) \
((char*)(((HIGHBYTE) | ((Y) & 0xC0) >> 3 | (Y) & 0x07) << 8 \
| (((Y) & 0x38) << 2 | (X) >> 3)))
#define SPRITE(XBYTE, Y) (gl_ptr+(Y)*2+(XBYTE))
void draw_sprite(int vpos_x, int vpos_y, int vsizex, int vsizey) {
int x, y; char *ptr;
for (y = 0; y != vsizey; y++) {
for (x = 0; x != vsizex + (vpos_x&7?8:0); x += 8) {
ptr = SCREENPTR(0x40, vpos_x+x, vpos_y+y);
if (x < vsizex) {
*ptr ^= *SPRITE(x/8, y) >> (vpos_x&7);
}
if (x >= 8 && (vpos_x&7)) {
*ptr ^= *SPRITE((x-8)/8, y) << (8-(vpos_x&7));
}
}
}
return;
}
отрисовка 16x16 спрайта, линии:
Код: Выделить всё
.globl _draw_sprite16_line1
_draw_sprite16_line1:
pop bc
pop hl
pop de
push de
push hl
push bc
;; 0 head
ld a,(de)
srl a ;; rrca; rrca; and a,#0x3f ...
ld b,(hl)
xor a,b
ld (hl), a
;; 1 body
ld a,(de)
rrca ;; rrca; rrca; a,#0xc0 ...
and a,#0x80 ;;
ld b,a
inc l
inc de
ld a,(de)
srl a ;; rrca; rrca; and a,#0x3f ...
or a,b
ld b,(hl)
xor a,b
ld (hl), a
;; 2 tail
ld a,(de)
rrca
and a,#0x80 ;; rrca; rrca; and a,#0xc0 ...
inc l
ld b,(hl)
xor a,b
ld (hl), a
ret
Код: Выделить всё
void draw_sprite2(int vpos_x, int vpos_y, int vsizex, int vsizey) {
int y; char *ptr;
if (vsizex != 16) {
draw_sprite(vpos_x, vpos_y, vsizex, vsizey);
}
for (y = 0; y != vsizey; y++) {
ptr = SCREENPTR(0x40, vpos_x, vpos_y+y);
switch (vpos_x & 7) {
case 0: draw_sprite16_line0(ptr, SPRITE(0, y)); break;
case 1: draw_sprite16_line1(ptr, SPRITE(0, y)); break;
case 2: draw_sprite16_line2(ptr, SPRITE(0, y)); break;
case 3: draw_sprite16_line3(ptr, SPRITE(0, y)); break;
case 4: draw_sprite16_line4(ptr, SPRITE(0, y)); break;
case 5: draw_sprite16_line5(ptr, SPRITE(0, y)); break;
case 6: draw_sprite16_line6(ptr, SPRITE(0, y)); break;
case 7: draw_sprite16_line7(ptr, SPRITE(0, y)); break;
}
}
return;
}
70+149+185+201+217+201+185+149 = 1357 t-states
11+28+38+42+46+42+38+28 = 273 bytes
сравнение с лобовой реализацией (прерываний на спрайт - отрисовка и очистка):
94/176 = 0.535~
377/176 = 2.142~
(1-2714/70908/(94/176) = 0.92~ процент времени в вышестоящем коде (прологе, в
C и библиотечном коде на прерываниях).
https://disk.yandex.com/d/p9yWzCoo2UNZtA
(тапка теста + частичный код (без библиотеки и сборщика))
то, что есть. Для процедуры были другие изябретения.lenin1st 88509 писал(а): в nbde наверно не ret c и sub 8, а ret nc и add 8. и типа в среднем тогда (168*(4+4+7+11) + 21*(4+4+7+11+4+7+4+11) + 3*(4+4+7+11+4+7+4+11+4+7+4+10)) / 192 = 29.64~ ну да, я же что-то такое и писал уже. а почему-то в этот раз мне в голову залетела только таблица адресов.
Ненависть- это подарок
СПРАЙТ КСОРАМИ
дальше нужно раскрыть цикл for (y = 0; y != vsizey; y++), что даст 8 блоков по 16 вызовов конкретного сдвига линии (call draw_sprite16_line<сдвиг>).
как считать адрес на экране по Y (накидал что смог придумать):
0) всегда считать заново
1) пересчитывать инкрементально (в смысле не с нуля)
2) заранее определить специфику адреса и чётко располагать нужные операции
3) определить таблицу адресов каждого Y на экране: 192*2 = 384.
x) может что-то смеженное, где как-то замостить вызовы в цикле, может до кратности 8 линий, потом считать адрес заново...
2) кажется совсем нереальном, потому что специфика экрана слишком сложная
0) на вскидку, если требуется случайный адрес получить, даже быстрее его посчитать. но последовательно, исходя из того что у меня получилось, лучше таблица.
1) если бы было последовательное заполнение по знакоместам, там что-то можно было бы придумать. x + 32 давал бы переполнение через 7 в старшем байте... (вот тут я похоже и погнал, не знаю что меня дёрнуло)
3) беру
раскрыл я одну функцию и понял, что это уже никуда не годится. нет смысла
вызывать отрисовку линий не в цикле, ведь (в моей реализации, конечно).
средняя сложность отрисовка линии спрайта 16x16 в t-states
l = (70+149+185+201+217+201+185+149) / 8
общий размер отрисовки линий спрайта 16x16
b = 11+28+38+42+46+42+38+28
вызов отрисовки линии в цикле по Y:
142 + 10 + (91+l) * 16 = 4322 t-states (в среднем на спрайт)
(20 + 1 + 19) * 8 + b = 593 bytes (всего)
раскрытие по Y с вызовом отрисовки линии:
135 + (77+l)*16-6 = 4075
(18 + 15*16 - 1) * 8 + b = 2329
тело отрисовки линии в цикле по Y:
142 + 10 + (91-17-10+l) * 16 = 3890
(20 + 1 + 19-3) * 8 + b - 1*8 = 561
раскрытие по Y с встраиванием тела отрисовки линии
...
ну тут понятно, что даже второй вариант уже избыточен. в третьем убирается вызов (call/ret), со встраиванием тела отрисовки линии, и накладные на loop уже компенсируются.
(для наглядности вариант "вызов отрисовки линии в цикле по Y"):
сравнение c лобовой реализацией (прерываний на спрайт - отрисовка и очистка):
26/176 = 0.1477~
377/176 = 2.142~
1-4075/70908/(26/176) = 0.61~ процент времени в вышестоящем коде.
https://disk.yandex.com/d/3PmmuihT1kJRkg
(тапка теста + частичный код (без библиотеки и сборщика))
дальше нужно раскрыть цикл for (y = 0; y != vsizey; y++), что даст 8 блоков по 16 вызовов конкретного сдвига линии (call draw_sprite16_line<сдвиг>).
как считать адрес на экране по Y (накидал что смог придумать):
0) всегда считать заново
1) пересчитывать инкрементально (в смысле не с нуля)
2) заранее определить специфику адреса и чётко располагать нужные операции
3) определить таблицу адресов каждого Y на экране: 192*2 = 384.
x) может что-то смеженное, где как-то замостить вызовы в цикле, может до кратности 8 линий, потом считать адрес заново...
2) кажется совсем нереальном, потому что специфика экрана слишком сложная
0) на вскидку, если требуется случайный адрес получить, даже быстрее его посчитать. но последовательно, исходя из того что у меня получилось, лучше таблица.
1) если бы было последовательное заполнение по знакоместам, там что-то можно было бы придумать. x + 32 давал бы переполнение через 7 в старшем байте... (вот тут я похоже и погнал, не знаю что меня дёрнуло)
3) беру
раскрыл я одну функцию и понял, что это уже никуда не годится. нет смысла
вызывать отрисовку линий не в цикле, ведь (в моей реализации, конечно).
средняя сложность отрисовка линии спрайта 16x16 в t-states
l = (70+149+185+201+217+201+185+149) / 8
общий размер отрисовки линий спрайта 16x16
b = 11+28+38+42+46+42+38+28
вызов отрисовки линии в цикле по Y:
142 + 10 + (91+l) * 16 = 4322 t-states (в среднем на спрайт)
(20 + 1 + 19) * 8 + b = 593 bytes (всего)
раскрытие по Y с вызовом отрисовки линии:
135 + (77+l)*16-6 = 4075
(18 + 15*16 - 1) * 8 + b = 2329
тело отрисовки линии в цикле по Y:
142 + 10 + (91-17-10+l) * 16 = 3890
(20 + 1 + 19-3) * 8 + b - 1*8 = 561
раскрытие по Y с встраиванием тела отрисовки линии
...
ну тут понятно, что даже второй вариант уже избыточен. в третьем убирается вызов (call/ret), со встраиванием тела отрисовки линии, и накладные на loop уже компенсируются.
(для наглядности вариант "вызов отрисовки линии в цикле по Y"):
Код: Выделить всё
.globl _draw_sprite16_lines0
_draw_sprite16_lines0:
exx
pop hl
pop de
pop bc
push bc ; pspr
push de ; x, y
push hl ; addr
push bc
ld b,#0x00
ld c,e
ld hl,#_gl_scryaddr
add hl,bc
add hl,bc
exx
pop bc
ld d,#0x10
;; hl` = pscryaddr
;; bc` = tmp
;; de` = x, y
;; hl = *pscryaddr + x
;; bc = pspr
;; de = counter, tmp
l_16_0: ;; l_16_1 ...
exx
ld a,d
or a,(hl)
inc hl
ex af,af
ld a,(hl)
inc hl
exx
ld h,a
ex af,af
ld l,a
call _draw_sprite16_line0 ;; _draw_sprite16_line1 ...
inc bc
dec d
jp nz,l_16_0 ;; jp nz,l_16_1 ...
ret
Код: Выделить всё
void draw_sprite2(int vpos_x, int vpos_y, int vsizex, int vsizey) {
if (vsizex != 16 && vsizey != 16) {
draw_sprite(vpos_x, vpos_y, vsizex, vsizey);
}
switch (vpos_x & 7) {
case 0: draw_sprite16_lines0(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
case 1: draw_sprite16_lines1(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
case 2: draw_sprite16_lines2(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
case 3: draw_sprite16_lines3(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
case 4: draw_sprite16_lines4(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
case 5: draw_sprite16_lines5(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
case 6: draw_sprite16_lines6(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
case 7: draw_sprite16_lines7(vpos_x>>3<<8 | vpos_y, SPRITE(0, 0)); break;
}
return;
}
26/176 = 0.1477~
377/176 = 2.142~
1-4075/70908/(26/176) = 0.61~ процент времени в вышестоящем коде.
https://disk.yandex.com/d/3PmmuihT1kJRkg
(тапка теста + частичный код (без библиотеки и сборщика))
Последний раз редактировалось lenin1st 13 окт 2022, 15:51, всего редактировалось 1 раз.
короче буду исправляться. переделаю, посчитаю. доделаю верхний уровень на асме. и придумаю как обрабатывать частичную отрисовку (20е слово в теме) на краях. вроде понятно более-менее.
Последний раз редактировалось lenin1st 13 окт 2022, 16:03, всего редактировалось 1 раз.
извиняюсь что не отписываюсь по теме. с широкой на широкой: я бухаю и сплю, и мне похуй - почти всё время, и курю ганжубас (я же русский). иногда я просыпаюсь по среди ночи от того что блюю под себя, и тогда я думаю не о самоидентификации, по каким-то там признакам, а о физической смерти, и потом плачу.