Оптимизация алгоритма бит-манипуляции в GameBoy Z80

Это не домашняя проблема, это для игры, которую я разрабатываю.

У меня есть два 16-битных цвета RGB, и я хотел бы изменить их шесть каналов в соответствии с шестью другими четырьмя битами. Алгоритм прост, но утомителен; Я ищу способ оптимизировать его, сделав более полезную работу сразу.

Обзор высокого уровня:

  • hl указывает на четыре цветовых байта. [hl] = %gggrrrrr, [hl+1] = %0bbbbbgg, [hl+2] = %GGGRRRRR и [hl+3] = %0BBBBBGG. (Это два цвета, rgb и rgb.)
  • bc указывает на три байта дельта. [bc] = %hhhhaaaa, [bc+1] = %ddddssss и [bc+2] = %ppppqqqq. (Это шесть значений дельта, h, a, d, s, p и q.)
  • Таким образом, существует шесть значений 5-битного цветового канала и шесть 4-битных значений дельта. Я хочу связать каждый цветовой канал C с дельта-значением D и изменить C следующим образом: C '= C + ( D и% 11) - (( D и% 1100) → 2), но сохраняя C в пределах своих 5-битных границ [0, 31]. Мне все равно, как они спарены: любое удобное соединение "один-к-одному" прекрасно. И если C + (( D и% 1100) → 2) - ( D и% 11) позволяет более элегантный алгоритм, Я был бы в порядке с этим.

Если я изолирую цветной канал C в регистре d и значение дельта D в регистре e, то эта процедура будет делать изменение для этой пары

VaryColorChannelByDV:
; d = color, e = DV
; a <- d + (e & %11) - (e >> 2), clamped to [0, 31]
    ld a, e
    and %11   ; a <- (e & %11)
    add d   ; a <- d + (e & %11)
    srl e
    srl e   ; e <- e >> 2
    sub e   ; a <- d + (e & %11) - (e >> 2)
    jr c, .zero   ; a < 0, clamp to 0
    cp 32
    ret c   ; 0 <= a < 32
    ld a, 31   ; a >= 32, clamp to 31
    ret
.zero
    xor a
    ret

До сих пор у меня есть общая подпрограмма, которая применяет любой DV к любому цветному каналу; затем три процедуры, которые изолируют красные, зеленые или синие каналы и применяют к ним данный DV; и, наконец, основная процедура, которая выбирает шесть DV и вызывает с ними соответствующую процедуру изменения канала. Это "достаточно хорошо", но я уверен, что есть место для улучшения. Скорость выполнения не кажется проблемой, но я бы хотел уменьшить размер кода (и, конечно, удаление избыточных инструкций также немного улучшит скорость). Есть ли какие-либо хитроумные манипуляции с использованием asm, которые помогут?

Здесь полный код:

GetColorChannelVariedByDV:
; d = color, e = DV
; a <- d + (e & %11) - (e & %1100 >> 2), clamped to [0, 31]
    ld a, e
    and %11
    add d
    srl e
    srl e
    sub e
    jr c, .zero
    cp 32
    ret c
    ld a, 31
    ret
.zero
    xor a
    ret

VaryRedByDV:
;;; e = DV
;;; [hl+0] = gggr:rrrr
;;; [hl+1] = 0bbb:bbgg
; store red in d
    ld a, [hl]
    and %00011111
    ld d, a
; vary d according to e
    call GetColorChannelVariedByDV
; store a back in red
    ld d, a
    ld a, [hl]
    and %11100000
    or d
    ld [hl], a
    ret

VaryGreenByDV:
;;; e = DV
;;; [hl+0] = gggr:rrrr
;;; [hl+1] = 0bbb:bbgg
; store green in d
    ld a, [hli]
    and %11100000
    srl a
    swap a
    ld d, a ; d = 00000ggg
    ld a, [hld]
    and %00000011
    swap a
    srl a
    or d
    ld d, a
; vary d according to e
    call GetColorChannelVariedByDV
; store a back in green
    sla a
    swap a
    ld d, a
    and %11100000
    ld e, a
    ld a, d
    and %00000011
    ld d, a
    ld a, [hl]
    and %00011111
    or e
    ld [hli], a
    ld a, [hl]
    and %11111100
    or d
    ld [hld], a
    ret

VaryBlueByDV:
;;; e = DV
;;; [hl+0] = gggr:rrrr
;;; [hl+1] = 0bbb:bbgg
; store blue in d
    inc hl
    ld a, [hl]
    and %01111100
    srl a
    srl a
    ld d, a
; vary d according to e
    call GetColorChannelVariedByDV
; store a back in blue
    ld d, a
    sla d
    sla d
    ld a, [hl]
    and %10000011
    or d
    ld [hl], a
    dec hl
    ret

VaryColorsByDVs::
; hl = colors
; [hl+0] = gggr:rrrr
; [hl+1] = 0bbb:bbgg
; [hl+2] = GGGR:RRRR
; [hl+3] = 0BBB:BBGG
; bc = DVs
; [bc+0] = hhhh:aaaa
; [bc+1] = dddd:ssss
; [bc+2] = pppp:qqqq

;;; LiteRed ~ hDV, aka, rrrrr ~ hhhh
; store hDV in e
    ld a, [bc]
    swap a
    and %1111
    ld e, a
; vary LiteRed by e
    call VaryRedByDV

;;; LiteGrn ~ aDV, aka, ggggg ~ aaaa
; store aDV in e
    ld a, [bc]
    and %1111
    ld e, a
; vary LiteGrn by e
    call VaryGreenByDV

;;; move from h/a DV to d/s DV
    inc bc

;;; LiteBlu ~ dDV, aka, bbbbb ~ dddd
; store dDV in e
    ld a, [bc]
    swap a
    and %1111
    ld e, a
; vary LiteBlu by e
    call VaryBlueByDV

;;; Move from Lite color to Dark color
    inc hl
    inc hl

;;; DarkRed ~ sDV, aka, RRRRR ~ ssss
; store sDV in e
    ld a, [bc]
    and %1111
    ld e, a
; vary DarkRed by e
    call VaryRedByDV

;;; move from d/s DV to p/q DV
    inc bc

;;; DarkGrn ~ pDV, aka, GGGGG ~ pppp
; store pDV in e
    ld a, [bc]
    swap a
    and %1111
    ld e, a
; vary DarkGrn by e
    call VaryGreenByDV

;;; DarkBlu ~ qDV, aka, BBBBB ~ qqqq
; store qDV in e
    ld a, [bc]
    and %1111
    ld e, a
; vary DarkBlu by e
    call VaryBlueByDV

    ret

Ответ 1

Самое маленькое, что я могу придумать сейчас: 57 байт:

VaryColorsByDVs::
; hl = colors
; [hl+0] = gggr:rrrr
; [hl+1] = 0bbb:bbgg
; [hl+2] = GGGR:RRRR
; [hl+3] = 0BBB:BBGG
; bc = DVs
; [bc+0] = hhhh:aaaa
; [bc+1] = dddd:ssss
; [bc+2] = pppp:qqqq
    ld a, 2 ; -floor($100/3)*6 mod $100
.next:
    sla [hl]
    inc hl
    rl [hl]
.loop:
    push af
    rrca
    ld a, [bc]
    jr nc, .skip
    swap a
    inc bc
.skip:
    rlca
    ld d, a
    and %00011000
    ld e, a
    ld a, d
    rlca
    rlca
    and %00011000
    add a, [hl]
    jr nc, .noOverflow
    or %11111000
.noOverflow:
    sub e
    jr nc, .noUnderflow
    and %00000111
.noUnderflow:
    dec hl
    ld de, 5
.rotate:
    add a, a
    rl [hl]
    adc a, d
    dec e
    jr nz, .rotate
    inc hl
    ld [hl], a
    pop af
    add a, 85 ; floor($100/3)
    jr nc, .loop
    ret z
    inc hl
    jr .next

Исправление комментариев Ped7g стоит всего 4 байта в общей сложности 61 байт:

VaryColorsByDVs::
; hl = colors
; [hl+0] = gggr:rrrr
; [hl+1] = 0bbb:bbgg
; [hl+2] = GGGR:RRRR
; [hl+3] = 0BBB:BBGG
; bc = DVs
; [bc+0] = hhhh:aaaa
; [bc+1] = dddd:ssss
; [bc+2] = pppp:qqqq
    ld a, 2 ; -floor($100/3)*6 mod $100
.next:
    sla [hl]
    inc hl
    rl [hl]
.loop:
    push af
    rrca
    ld a, [bc]
    jr nc, .skip
    swap a
    inc bc
.skip:
    ld d, a
    and %00001100
    ld e, a
    ld a, d
    rlca
    rlca
    and %00001100
    sub e
    add a, a
    jr nc, .positive
.negative:
    add a, [hl]
    jr c, .continue
    and %00000111
    db $38 ; jr c,
.positive:
    add a, [hl]
    jr nc, .continue
    or %11111000
.continue:
    dec hl
    ld de, 5
.rotate:
    add a, a
    rl [hl]
    adc a, d
    dec e
    jr nz, .rotate
    inc hl
    ld [hl], a
    pop af
    add a, 85 ; floor($100/3)
    jr nc, .loop
    ret z
    inc hl
    jr .next

Ответ 2

Хм... вы должны предоставить нам больше информации о том, откуда эти данные, если вы можете их предварительно обработать, потому что этот +(d&3)-(d>>2) выглядит неудачным, и я постараюсь избежать этого, если это возможно. На самом деле весь материал RGB 5: 5: 5, вероятно, немного над головой Z80, но если вы знаете, что это сработает для вас, продолжайте (я говорю из своего опыта ZX Spectrum, где 3,5 МГц было едва достаточно манипулировать 1 бит B & W пикселей).

Но на данный момент, что вы уже получили, можно немного упростить, удалив две инструкции ld:

VaryColorChannelByDV:
    ...
    add d
;    ld d, a   ; d <- d + (e & %11)
    srl e
    srl e
;    ld a, d   ;### A didn't change, still contains C + DV&3
    sub e   ; a <- d + (e & %11) - (e & %1100 >> 2)
    ...

И если у вас мало памяти, вы можете создать 256B look-up-table для фиксации значений, так что, например, вы бы сохранили в h или b старший адресный байт таблицы, а результат в затем загружается в l или c и зажимается ld a,(hl/bc). Который составляет 4 + 7 т вместо тех jr/cp/ret/.... Вам действительно понадобятся только некоторые значения из 256, от -3 до 34 (0..34 и 253..255), если бы я не просчитал его (0 + 0 - 3 минимально, а 31 + 3 - 0 максимальный результат). Таким образом, вы можете использовать байты по адресам "внутри страницы" 35..252 для других данных или кода.

Я попытаюсь взглянуть на него как целое позже, чтобы избежать некоторых из общих компонентов для каждого компонента, если это возможно, но я боюсь, что лучший формат входных данных, вероятно, даст вам больший импульс или будет знать ваша общая цель и все ограничения (например, если верхний бит в RGB всегда равен 0 и должен быть 0 или может быть случайным в качестве результата и равен 0 в качестве ввода и т.д.... каждая деталь может часто приводить к другой удаленной инструкции, который часто составляет 4-11 т на Z80).