assembly - Optimización de un algoritmo de manipulación de bits en GameBoy Z80
bit-manipulation (2)
El más pequeño que se me ocurre en este momento es de 57 bytes:
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
La corrección del comentario de Ped7g solo cuesta 4 bytes para un total de 61 bytes:
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
Este no es un problema de tarea, es para un juego que estoy desarrollando.
Tengo dos colores RGB de 16 bits y me gustaría variar sus seis canales de acuerdo con otras seis cantidades de cuatro bits. El algoritmo es simple pero tedioso; Estoy buscando una manera de optimizarlo haciendo un trabajo más útil a la vez.
La descripción de alto nivel:
-
hl
apunta a los cuatro bytes de color.[hl] = %gggrrrrr
,[hl+1] = %0bbbbbgg
,[hl+2] = %GGGRRRRR
, y[hl+3] = %0BBBBBGG
. (Son dos colores,rgb
yRGB
). -
bc
apunta a los tres bytes delta.[bc] = %hhhhaaaa
,[bc+1] = %ddddssss
, y[bc+2] = %ppppqqqq
. (Son seis valores delta,h
,a
,d
,s
,p
yq
) - Entonces, hay seis valores de canal de color de 5 bits y seis valores de delta de 4 bits. Quiero emparejar cada canal de color C con un valor delta D , y variar C como tal: C '' = C + ( D &% 11) - (( D &% 1100) >> 2), pero manteniendo C dentro de sus 5 -bit límites [0, 31]. En realidad, no me importa cómo están emparejados: cualquier combinación conveniente de uno a uno está bien. Y si C + (( D &% 1100) >> 2) - ( D &% 11) permite un algoritmo más elegante de alguna manera, estaría bien con eso.
Si aislo un canal de color C en el registro d
un valor delta D en el registro e
, entonces esta rutina hará la variación para ese par:
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
Hasta ahora tengo una rutina genérica que aplica cualquier DV a cualquier canal de color; luego tres rutinas que aíslan los canales rojo, verde o azul y les aplica un DV dado; y finalmente una rutina principal que selecciona los seis DV y llama a la rutina de modificación de canal apropiada con ellos. Esto es "suficientemente bueno", pero estoy seguro de que hay margen de mejora. La velocidad de ejecución no parece ser un problema, pero me gustaría reducir el tamaño del código (y, por supuesto, eliminar las instrucciones redundantes también mejorará un poco la velocidad). ¿Hay algún truco de manipulación de bits de asm que ayudaría?
Aquí está el código completo:
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
Hmm ... deberías darnos más información acerca de dónde provienen esos datos, si puedes preprocesarlos aún más, porque ese +(d&3)-(d>>2)
parece desafortunado y trataría de evitar eso, si es posible . En realidad, todo el material RGB 5: 5: 5 es probablemente un poco exagerado, pero si sabes que funcionará para ti, adelante (estoy hablando de mi experiencia con ZX Spectrum, donde 3,5MHz fue apenas suficiente para manipular píxeles B & W de 1 bit).
Pero por el momento, lo que ya tienes puede simplificarse un poco al eliminar dos instrucciones:
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)
...
Y si no le falta memoria, puede crear una tabla de búsqueda 256B para fijar valores, por lo que, por ejemplo, debería mantener h
o b
el byte de dirección alta de la tabla, y el resultado en a se cargaría luego en l
o c
y sujetado por ld a,(hl/bc)
. Que es 4 + 7 t en lugar de jr/cp/ret/...
En realidad, solo necesitaría algunos valores de esos 256, de -3 a 34 (0..34 y 253..255) si no los calculé mal (0 + 0 - 3 es mínimo, y 31 + 3 - 0 es el resultado máximo). De modo que aún puede usar bytes en las direcciones "dentro de la página" 35..252 para otros datos o códigos.
Trataré de analizarlo en su totalidad más adelante, para evitar algunos de los elementos genéricos por componente si es posible, pero me temo que un mejor formato de datos de entrada probablemente te daría un impulso mayor, o para conocer tu objetivo general. y todas las restricciones (como si el bit superior en RGB es siempre 0 y debe ser 0, o puede ser aleatorio como resultado, y es 0 como entrada, etc ... cada detalle a menudo puede conducir a otra instrucción eliminada, que a menudo 4-11 t vale en Z80).