Оптимизация алгоритма бит-манипуляции в 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).