1

не буду приводить расчёты (потому что глупец). но кажется (я практически уверен что это факт) единственный способ это раскрытие циклов - абстрактно говоря. потому что мы тупо расписываем множество ветвлений, предполагая что можно заранее посчитать ветку поведения (скажем). у меня просто вопрос, так все делают: раскрывают на 16кб варианты отрисовки 8x8 спрайта со всеми смещениями. но если грубо посчитать в уме, не получается чтобы даже 8*8 вариантов помещалось в 16кб. а ведь ещё есть специфика экрана, с его тремя областями. вопрос в том: как это обычно делают?

2 (изменено: lenin1st, 15.09.2022 08:46:50)

1323

3

лобовое решение: вывод на экран по линиям.
обходное решение - вывод "змейкой"

Ненависть- это подарок
защеканец gpv хорошо сосёт.

4 (изменено: lenin1st, 15.09.2022 15:00:36)

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

5

если выводить по линиям, то выходит побайтное копирование данных на экран, например (HL)->(DE). Нарисовав линию спрайта, нужно перейти вниз, по экрану, например

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

Казалось бы, удобнее  запомнить DE и сделать так

push de
;копирование линии
pop de
call nbde;переход на линию экрана

Трюк в том, что первая линия копируется, без сохранения регистра DE, а следующая линия копируется обратно
т.е. в первой линии будет
ld a,(hl)
ld (de),a
inc hl
inc e

а во второй

ld a,(hl)
ld (de),a
inc hl
dec e

Понятно, что нужно подготовить спрайт, чтобы шел другой порядок байтов.

Ненависть- это подарок
защеканец gpv хорошо сосёт.

6 (изменено: lenin1st, 15.09.2022 17:02:14)

щас пока не понял (уже накидался). но самое быстрое, как я понял, это заполнять экран через стек - это не наш путь, просто хотелось упомянуть. прочитаю завтра - осмыслю.

7

стек можно использовать, НО:
-число размер спрайта четное
-положение на экране одно и тоже
-нужно уместиться в 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

Ненависть- это подарок
защеканец gpv хорошо сосёт.

8 (изменено: lenin1st, 13.10.2022 15:43:14)

я немного ушёл головой и телом в плотнейшую лень. но держал в голове эту тему. вчера-позавчера начал ковырять. вот кстати даже ни разу не писал спрайты (не в частностях каких-то там), интересно поковыряться.

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

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

ну а что поделать, вкину что высрал и пусть будет уже.

9

СПРАЙТ КСОРАМИ

оговорюсь об ограничениях (помимо того что это тупой ксор): немодифицирующийся код (код не меняется во время исполнения или непосредственно до); не выравненный по прерыванию и с правильным стеком - код всюду готов к прерыванию; код 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;
  }

первое что нужно раскрывать, это построчную отрисовку. потому что по горизонтали это тупо инкримент и потому что опционального сдвига нет (а мне почему-то кажется что это горячее место). получается 8 блоков отрисовки линий с разным сдвигом. ориентировка на форматы спрайтов 8x8, 16x16, 24x24, покрывает практически все случаи (из адекватных), так что я нацелюсь на них. но начнум с 16x16 и дальше будет видно (примерно умножив на 3) до какой глубины это можно довести.

отрисовка 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

отрисовка 16x16 спрайта, верхний уровень:

  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;
  }

(без пролога 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
(тапка теста + частичный код (без библиотеки и сборщика))

10

lenin1st сказал:

в 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~ ну да, я же что-то такое и писал уже. а почему-то в этот раз мне в голову залетела только таблица адресов.

то, что есть. Для процедуры были другие изябретения.

Ненависть- это подарок
защеканец gpv хорошо сосёт.

11 (изменено: lenin1st, 13.10.2022 15:51:33)

СПРАЙТ КСОРАМИ

дальше нужно раскрыть цикл 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;
  }

  сравнение c лобовой реализацией (прерываний на спрайт - отрисовка и очистка):
  26/176 = 0.1477~
  377/176 = 2.142~
  1-4075/70908/(26/176) = 0.61~ процент времени в вышестоящем коде.
  https://disk.yandex.com/d/3PmmuihT1kJRkg
  (тапка теста + частичный код (без библиотеки и сборщика))

12 (изменено: lenin1st, 13.10.2022 16:03:18)

короче буду исправляться. переделаю, посчитаю. доделаю верхний уровень на асме. и придумаю как обрабатывать частичную отрисовку (20е слово в теме) на краях. вроде понятно более-менее.

13

чото как-то некошерно рисуется

Таки написал бестолковую тулзу конверсии спрайта в "змейку". Пример там же.
snakes.zip

Ненависть- это подарок
защеканец gpv хорошо сосёт.

14

извиняюсь что не отписываюсь по теме. с широкой на широкой: я бухаю и сплю, и мне похуй - почти всё время, и курю ганжубас (я же русский). иногда я просыпаюсь по среди ночи от того что блюю под себя, и тогда я думаю не о самоидентификации, по каким-то там признакам, а о физической смерти, и потом плачу.