Ejemplos con aPLib para C64

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Ejemplos con aPLib para C64

Mensajepor BlackHole » 20 Oct 2017 17:10

Hola a todos.

El pasado martes fue mi cumpleaños y quería obsequiaros con un pequeño trabajo que he estado haciendo durante los últimos días. Se trata de un ejemplo real de una implementación en 6502 de una rutina de descompresión de aPLib optimizada para velocidad. Está basada en un código anterior para el micro 65C02 para Apple IIc, que como algunos sabréis, posee más instrucciones y modos de direccionamiento que permiten ahorrar bytes, así que la conversión ha sido complicada: como tiene muchos bucles desenrollados y el código de 6502 ya de por sí ocupa mucho más que el de Z80, al final la rutina ha quedado en 327 bytes, pero es un 60% más rápida que Exomizer y los datos comprimidos apenas suelen ser 2% más largos.

Sé que por aquí paran un par de gurús del 6502 (Silicebit, Chema) del mundo Oric y quería que echasen un vistazo por si podían optimizarla... como dije más arriba, está basada en un código anterior de 65C02 que es más corto. Ese es un procesador que posee direccionamiento indirecto puro, no como el 6502 que únicamente trae indirecto indexado (que además gasta un ciclo de reloj más) y eso te obliga a inicializar el registro Y a cero y mantenerlo así todo el rato, con el coste en bytes que supone. Otras características del 65C02 que no puedo usar, son el mover a pila el registro X y el registro Y directamente sin pasar por el acumulador.

Esta rutina es la versión 15 y por más vueltas que le doy no consigo optimizarla más. Cualquier sugerencia será bienvenida. Es complicado, ya que el procesador no tiene ningún registro de 16 bits ni tampoco instrucciones para cálculos aritméticos de 16 bits, con lo que las operaciones a 8 bits se llevan gran parte del código, y por consiguiente del tiempo de proceso. El que el reloj del 6510 en el Commodore 64 PAL sea de 985 kHz (ni siquiera 1 MHz), ya termina de matar al micro.

Código: Seleccionar todo

; aPLib 6502 depacker for C64 by Francisco Crespo (Black Hole)
; Based on an earlier 65C02 depacker for Apple IIc by Peter Ferrie

; Depacker variables
.DEFINE TMPY    =       $A7
.DEFINE tmp     =       $A8 ; A8/A9
.DEFINE newsrc  =       $AA ; AA/AB
.DEFINE ecx     =       $F7 ; F7/F8
.DEFINE last    =       $F9 ; F9/FA
.DEFINE src     =       $FB ; FB/FC
.DEFINE dst     =       $FD ; FD/FE

.MACRO  GETBIT_M
        txa
        asl
        bne     ++
        lda     (src),Y
        inc     src
        bne     +
        inc     src+1
+       rol     a
++      tax
.ENDM

.MACRO GETSRC_M
        LDA     (src),Y
        INC     src
        BNE     +
        INC     src+1
+
.ENDM

.MACRO PUTDST_M
        STA     (dst),Y
        INC     dst
        BNE     +
        INC     dst+1
+
.ENDM

depack: LDX     #$80
        LDY     #$00
        STY     ecx+1           ; Y=0

literal:
        GETSRC_M
        PUTDST_M
        ;ldy    #2
        LDA     #$02
        STA     TMPY

nexttag:
        GETBIT_M
        bcc     literal
        GETBIT_M
        bcc     codepair
        GETBIT_M
        bcs     onebyte
        GETSRC_M
        lsr
        beq     donedepacking
        STY     ecx             ; Y=0
        rol     ecx
        sta     last
        STY     last+1          ; Y=0
        JMP     domatch_with_2inc

donedepacking:
        RTS

onebyte
        ;ldy    #1
        ;sty    ecx
        ;iny
        LDA     #1
        STA     ecx
        LDA     #2
        STA     TMPY
        lda     #$10

getmorebits
        pha
        GETBIT_M
        pla
        rol
        bcc     getmorebits
        STY     tmp+1           ; Y=0
        bne     domatch
        PUTDST_M
        JMP     nexttag

codepair
        jsr     getgamma
        LDA     ecx
        SEC
        SBC     TMPY
        STA     TMPY
        ora     ecx+1
        bne     normalcodepair
        jsr     getgamma
        JMP     domatch_lastpos

normalcodepair
        GETSRC_M
        sta     last
        jsr     getgamma
        ;dey
        ;sty    last+1
        dec     TMPY
        LDA     TMPY
        STA     last+1
        ;cpy    #$7d
        CMP     #$7D
        bcs     domatch_with_2inc
        ;cpy    #5
        CMP     #$05
        bcs     domatch_with_inc
        lda     last
        bmi     domatch_lastpos
        ;tya
        LDA     TMPY
        bne     domatch_lastpos

domatch_with_2inc
        inc     ecx
        bne     domatch_with_inc
        inc     ecx+1

domatch_with_inc
        inc     ecx
        bne     domatch_lastpos
        inc     ecx+1

domatch_lastpos
        ;ldy    #1
        LDA     #1
        STA     TMPY
        lda     last+1
        sta     tmp+1
        lda     last

domatch:
        sta     tmp
        lda     dst
        sec
        sbc     tmp
        sta     newsrc
        lda     dst+1
        sbc     tmp+1
        sta     newsrc+1

        lda     ecx
        beq     dloop
-       LDA     (newsrc),Y
        INC     newsrc
        BNE     +
        INC     newsrc+1
+       PUTDST_M
        dec     ecx
        bne     -
        lda     ecx+1
        beq     +++

dloop:  LDA     (newsrc),Y
        INC     newsrc
        BNE     ++
        INC     newsrc+1
++      PUTDST_M
        dec     ecx
        bne     dloop
        dec     ecx+1
        bne     dloop
+++     JMP     nexttag

getgamma:
        LDA     #$01
        STA     ecx
        STY     ecx+1           ; Y=0

        txa                     ; GETBIT_1
gloop:  asl     a
        bne     ++
        lda     (src),Y
        inc     src
        bne     +
        inc     src+1
+       rol     a

++      rol     ecx
        rol     ecx+1

        asl     a               ; GETBIT_2
        bne     ++
        lda     (src),Y
        inc     src
        bne     +
        inc     src+1
+       rol     a
++      bcs     gloop
        tax                     ; stillbitsleft

        rts

end_depack:

Me gustaría dedicar este hilo a mis futuros "apaños" con el Commodore 64. No prentendo que mis trabajos sean equivalentes a los de grupos como Remember o Nostalgia, puesto que ni tengo intención de complicarlos con trainers ni cargadores de puntuaciones, ni pegar una intro con un logotipo delante de cada juego.

Solo voy a convertir algunos juegos en cinta que me gustaron de joven, ya estoy muy mayor para buscar otro reconocimiento. :mrgreen:

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Ejemplos con aPLib para C64: Hunter's Moon

Mensajepor BlackHole » 20 Oct 2017 18:01

Mi primer ejemplo es el Hunter's Moon publicado por Thalamus en 1987. Esta versión de fichero único, incluye tanto el juego como la música y pantalla de carga de la cinta; además he arreglado varias cosas: el juego está correctamente "descongelado" pues utilizaron el Expert Cartridge 2.9 para crear el volcado del máster de la cinta, y también he corregido un pequeño "glitch" gráfico que aparecía al principio antes del logo del juego. Si bien la descompresión típica con Exomizer ronda los 10 segundos, al utilizar aPLib en esta producción consigo que el juego se descomprima en apenas 5 segundos, todo un logro para la exigua velocidad de proceso del C64 a 1 MHz.

Imagen
Hunters Moon [BH][2017].d64.gz
C64 Hunter's Moon - Thalamus 1987
(34.9 KiB) Descargado 20 veces

Avatar de Usuario
minter
Mensajes: 1607
Registrado: 22 Jul 2014 18:51
Agradecido : 866 veces
Agradecimiento recibido: 383 veces

Re: Ejemplos con aPLib para C64

Mensajepor minter » 20 Oct 2017 18:01

Y como te lo enganche quien yo me se... te mete el código en un Z81 -507
Por cierto!
Felicidades y que sean muchos mas!!! -drinks

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Re: Ejemplos con aPLib para C64

Mensajepor BlackHole » 20 Oct 2017 18:26

minter: Hahaha, no va a hacer falta puesto que para el procesador Z80 ese trabajo hace tiempo que se ha hecho. El sevillano Metalbrain hace tiempo que liberó el código de versiones optimizadas en tamaño (156 bytes) y en velocidad (197 bytes para una parcialmente desenrollada y otra de 227 bytes completamente desenrollada) disponibles en el foro de Amstrad.ES. Yo uso otra desde hace 10 años, basada en el código de un holandés, y también parcialmente desenrollada, que me ocupa 222 bytes... pero no he tenido tiempo de comparar los ciclos totales con las de Metalbrain.

Avatar de Usuario
Silicebit
Mensajes: 1345
Registrado: 16 May 2011 21:13
Ubicación: La buhardilla del silicio.
Agradecido : 35 veces
Agradecimiento recibido: 93 veces
Contactar:

Re: Ejemplos con aPLib para C64

Mensajepor Silicebit » 20 Oct 2017 20:01

¡Felicidades BlackHole!

Yo estoy ahora liado con las controladoras y otros hardwares, pero siempre hay un poco de tiempo (sobre todo en el curro -507 ) para intentar optimizar un poco de código. Veré lo que puedo hacer, y si a Chema le apetece, quizás entre los dos podamos arañarle unos ciclos a esa rutina.
El 6809 es el Rolls-Royce de los 8bits, el 6502 es el Mercedes, y el Z80 el SEAT 850. Sorry, but... I think different. :-P -0r1c -m3s3x -t4nd1 -cbmja YouTube

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Ejemplos con aPLib para C64: Rambo

Mensajepor BlackHole » 30 Oct 2017 21:42

Como segundo ejemplo del uso de aPLib, os traigo hoy Rambo publicado por Ocean en 1986. Esta versión incluye la música de carga y una pantalla alternativa diseñada por STE'86. Ha sido un poco agobiante encontrar los huecos para colocar cosas, porque el juego ocupa 63 KB entre $0334 y $FF00. En este caso incorporo una nueva rutina de descompresión de aPLib realizada por el malagueño Antonio Villena, basada en un código anterior del sueco Sven Dahl, que gana un 33% de velocidad sobre la mía anterior usada en el Hunter's Moon más arriba y que en este juego en particular es un 140% más rápida que Exomizer.

Imagen
Rambo [BH][2017].d64.gz
C64 Rambo - Ocean 1986
(39.25 KiB) Descargado 23 veces

Avatar de Usuario
Chema
Mensajes: 1745
Registrado: 21 Jun 2012 20:13
Ubicación: Gijón
Agradecido : 688 veces
Agradecimiento recibido: 267 veces
Contactar:

Re: Ejemplos con aPLib para C64

Mensajepor Chema » 30 Oct 2017 22:51

Hola Black Hole! Le echaré un vistazo, pero eso de optimizar ciclos de reloj no es lo mío.

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Re: Ejemplos con aPLib para C64

Mensajepor BlackHole » 31 Oct 2017 14:48

Hola Chema,

Antes de que te vuelvas loco probando cosas, a no ser que hayas usado un emulador del C64 para extraer el binario del disco del Rambo que subí ayer, mejor subo el nuevo código fuente que estoy usando en este momento. Aunque en realidad, no es exactamente éste pues la subrutina "getgamma" he decidido no desenrollarla completamente. Sabemos que el par JSR/RTS gasta 12 ciclos, pero que la rutina pase de 394 a ocupar sólo 314 bytes es muy interesante: como las llamadas a "getgamma" no son numerosas porcentualmente hablando, la ganancia de velocidad sigue siendo muy alta. Sé que la versión larga está ahí si la necesito, pero estamos hablando de una diferencia de 1 décima de segundo.

Código: Seleccionar todo

; Rutina de decompresión de aPLib para 6502 por Antonio Villena Godoy
; Basada en un código anterior por Sven-Åke Dahl (https://github.com/svendahl/cap)

            apd_src       = $f4
            apd_bitbuffer = $f6
            apd_lwm       = $f7
            apd_length    = $f8
            apd_offset    = $fa
            apd_source    = $fc
            apd_dest      = $fe

daplib:     lda     #$80
            sta     apd_bitbuffer
            ldy     #0
            sty     apd_length+1
read_lit:   #getbyte
write_lit:  sta     (apd_dest), y
            inc     apd_dest
            bne     clear_lwm
            inc     apd_dest+1
clear_lwm:  sty     apd_lwm
read_flag:  #mgetbitd
            bcc     read_lit
            #mgetbitd
            bcc     large_match
            #mgetbitd
            bcc     short_match
            lda     #%11100000
mini_match: #mgetbit
            rol
            bcs     mini_match
            beq     write_lit
            sbc     apd_dest
            eor     #%11111111
            sta     apd_source
            tya
            sbc     apd_dest+1
            eor     #%11111111
            sta     apd_source+1
            lda     (apd_source), y
            bne     write_lit
apd_end:    rts
short_match:#getbyte
            lsr
            beq     apd_end
            sta     apd_offset
            sty     apd_offset+1
            lda     #1
            rol
            sta     apd_length
            jmp     set_lwm
large_match:#getgamma
            cmp     #2
            bne     offset_mat
            cpy     apd_lwm
            bne     offset_mat
            #getgamma
            bcc     set_lwm
offset_mat: lsr     apd_lwm
            sbc     #2
            sta     apd_offset+1
            #getbyte
            sta     apd_offset
            #getgamma
            lda     apd_offset+1
            bne     al_chkh
            bit     apd_offset
            bmi     set_lwm
            lda     #2
            bcc     al_add
al_chkh:    cmp     #>1280
            bcc     set_lwm
            cmp     #>32000
            lda     #1
al_add:     adc     apd_length
            sta     apd_length
            bcc     set_lwm
            inc     apd_length+1
set_lwm:    lda     #1
            sta     apd_lwm
            sec
            lda     apd_dest
            sbc     apd_offset
            sta     apd_source
            lda     apd_dest+1
            sbc     apd_offset+1
            sta     apd_source+1
            lda     apd_length+1
            beq     cm_skip
cm_loop1:   lda     (apd_source), y
            sta     (apd_dest), y
            iny
            bne     cm_loop1
            inc     apd_source+1
            inc     apd_dest+1
            dec     apd_length+1
            bne     cm_loop1
cm_skip:    ldx     apd_length
            beq     cm_end
cm_loop2:   lda     (apd_source), y
            sta     (apd_dest), y
            iny
            dex
            bne     cm_loop2
            clc
            tya
            ldy     #0
            adc     apd_dest
            sta     apd_dest
            bcc     cm_end
            inc     apd_dest+1
cm_end:     jmp     read_flag

getgamma:   .macro
            sty     apd_length+1
            lda     #1
getgamma1:  #mgetbit
            rol
            rol     apd_length+1
            #mgetbit
            bcs     getgamma1
            sta     apd_length
            .endm

getbyte     .macro
            lda     (apd_src), Y
            inc     apd_src
            bne     getbyte1
            inc     apd_src+1
getbyte1:   .endm

mgetbit     .macro
            asl     apd_bitbuffer
            bne     m_getbit1
            tax
            #getbyte
            rol
            sta     apd_bitbuffer
            txa
m_getbit1:  .endm

mgetbitd    .macro
            asl     apd_bitbuffer
            bne     m_getbit2
            #getbyte
            rol
            sta     apd_bitbuffer
m_getbit2:  .endm

Avatar de Usuario
Chema
Mensajes: 1745
Registrado: 21 Jun 2012 20:13
Ubicación: Gijón
Agradecido : 688 veces
Agradecimiento recibido: 267 veces
Contactar:

Re: Ejemplos con aPLib para C64

Mensajepor Chema » 31 Oct 2017 15:24

Buf... ese código hay que estudiarlo, pero dudo mucho que sin algo raro raro sacado de la manga en plan trucazo, vaya a haber nada que implique una ganancia de velocidad aceptable.

Veo que hay dos bucles principales que leen datos y los copian en otro sitio. No entiendo muy bien cómo funcionan. Lo que yo hago siempre que puedo es cargarme el direccionamiento indirecto con Y y usar código que se automodifique. Si el bucle se repite muchas veces, la ganancia es impresionante.

Es algo como esto. Supongamos que este código funcionase... (lo he tirado en un momento, así que fijo que no, pero para ilustrar valdrá)

Código: Seleccionar todo


loop1
    ldy #0
loop
    lda (src),y
    sta (dst),y
    iny
    bne loop

    .... Incremento la parte alta de src y dst y miro si hay más que copiar
    bne loop1


Pues es más eficiente hacer:

Código: Seleccionar todo

loop1
    ldy #0
loop
misrc
    lda $0000,y
midst
    sta $0000,y   
    iny
    bne loop

    .... Incremento misrc+2 y midst+2 y miro si hay más que copiar
    bne loop1


Fijo que captas la idea...

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Re: Ejemplos con aPLib para C64

Mensajepor BlackHole » 31 Oct 2017 15:49

Sí, sé que el direccionamiento absoluto comparado con el indirecto indexado ahorra ciclos, pero el tema es que los punteros se usan bastantes veces en posiciones de memoria diferentes (aunque el sueco tiene una versión absoluta en su GIT que no he probado a ensamblar, pero tiene pinta de ocupar demasiado). Además la rutina dejaría de ser reentrante y/o el tema de reinicializar los punteros con otros valores al final nos gastan tantos bytes como ahorramos.

Avatar de Usuario
antoniovillena
Mensajes: 135
Registrado: 18 Ago 2012 13:06
Agradecimiento recibido: 2 veces

Re: Ejemplos con aPLib para C64

Mensajepor antoniovillena » 01 Nov 2017 09:47

Esa alternativa la he probado, pero es más lenta. El problema es que el direccionamiento absoluto indexado lda n,y sólo es un ciclo más rápido que el indirecto ld (n),y. Es un ciclo por iteración, pero las iteraciones mayoritarias de este bucles son de 2 y 3 (los patrones que más se repiten) y el código para preparar todo esto es mucho mayor que esos 2/3 ciclos. También está lo que dice BlackHole. Estos punteros se usan en diferentes sitios, con lo que sólo es aplicable en uno, haciendo mucho más lentos los otros (al ser absulote en lugar de zeropage).

Avatar de Usuario
Chema
Mensajes: 1745
Registrado: 21 Jun 2012 20:13
Ubicación: Gijón
Agradecido : 688 veces
Agradecimiento recibido: 267 veces
Contactar:

Re: Ejemplos con aPLib para C64

Mensajepor Chema » 01 Nov 2017 12:46

Ok. Ya dije que iba a ser de poca ayuda porque se veía que ya habíais optimizado bastante.

Además pensé que esos bucles ejecutaban muchas veces. Ese truco solo vale en esos casos si no se pierde más de lo que se gana como decís.

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Re: Ejemplos con aPLib para C64

Mensajepor BlackHole » 01 Ene 2018 18:01

El primer día de 2018, os presento mi versión de Yie Ar Kung Fu publicado por Imagine en 1986. Esta versión incluye la melodía "Ocean Reloaded" compuesta por Laxity/Maniacs of Noise en 2006 y una nueva pantalla de carga y de juego, diseñada por JonEgg en Noviembre 2017. Desde que pulsamos espacio o disparo hasta que empieza el juego, solo tarda 3.5 segundos, no está nada mal.

Imagen
Yie Ar Kung Fu [BH][2018].d64.gz
C64 Yie Ar Kung Fu - Imagine 1986
(39.35 KiB) Descargado 14 veces

Avatar de Usuario
SrHead
Mensajes: 28
Registrado: 10 Nov 2016 16:19
Agradecido : 23 veces

Re: Ejemplos con aPLib para C64

Mensajepor SrHead » 02 Ene 2018 16:49

Me parece super interesante el tema, pero no me estoy enterando de na. He entendido medianamente el ejemplo de los bucles de Chema, pero tengo ni idea de para que sirve o se utiliza la aPlib. ¿Podríais dar algunas nociones básicas para torpes? -grin
Gracias y un saludo

BlackHole
Mensajes: 790
Registrado: 03 Ago 2011 23:07
Ubicación: Aluche, Madrid
Agradecido : 4 veces
Agradecimiento recibido: 65 veces

Re: Ejemplos con aPLib para C64

Mensajepor BlackHole » 03 Ene 2018 00:45

SrHead: No hay problema. aPLib es una conocida biblioteca de compresión de datos, cuyo rendimiento es cercano al del ZIP, programada por el sueco Jørgen Ibsen, y gracias a que existen descompresores para diferentes procesadores, ha permitido su uso en diversas máquinas populares de 8, 16 y 32 bits. El compresor es código propietario, y aunque no es libre, el programador lo ha dejado disponible para diversos compiladores con detalladas instrucciones de uso, tanto para Windows como para Unix. Los descompresores ocupan muy poca memoria y no necesitan memoria adicional para descomprimir; eso ha propiciado su uso en equipos clásicos Z80 como Spectrum, Amstrad y MSX, equipos 68000 como Amiga, equipos i386 como IBM PC y compatibles, y ahora en máquinas 6502 como Commodore 64, Apple II o Atari 8b que es de lo que trata este hilo.

Para Commodore 64 tradicionalmente hubo siempre compresores de datos desde los años 80, y era común en las producciones de la época que las copias pirata de los juegos se distribuyesen comprimidas. Los compresores normalmente eran lentos y poco versátiles, hasta que se empezó a utilizar el PC para comprimir los datos en décimas de segundo. Para Commodore 64, existe en la actualidad un programa de PC muy extendido llamado "Exomizer" que comprime un poco más, pero su descompresor nativo es el doble de lento que aPLib. Aquí estamos discutiendo mis pruebas con esta biblioteca, y las geniales optimizaciones que el malagueño Antonio Villena ha incorporado para hacerla aún un 33% más rápida.

Avatar de Usuario
SrHead
Mensajes: 28
Registrado: 10 Nov 2016 16:19
Agradecido : 23 veces

Re: Ejemplos con aPLib para C64

Mensajepor SrHead » 07 Ene 2018 17:10

Muchas gracias por la explicación BlackHole, ahora lo voy entendiendo. Saludos -thumbup


Volver a “Software C64”

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado