Tutorial muy básico de C para Master System

Vale, pues te explico como hacerlo con folder2c, la otra la usaré para la nueva lección con las novedades en la librería.

Bueno, el tema este de los bancos pasa por que la master del cartucho solo ve 48kb. Esto es así por que solo direcciona 64k, que se reparten entre 48kb para la ROM, y la ram de 8 kb está dos veces mapeada seguida. Como 48kb dan para poco, se usaron mappers especiales que eran capaces de cambiar al "vuelo" bloques de 16kb que llamamos "bancos".

El espacio de los 0kb a los 16kb es el Bank 0, el de los 16kb a los 32kb el Bank 1, y el de los 32kb a los 48kb, el Bank 2. DevKitSMS permite cambiar al vuelo el Bank2 con unas simples instrucciones. Vamos a ver como hacerlo:

Lo principal, es que hemos de decidir como agrupamos los assets del juego, de manera que cada grupo no pese más de 16kb, y que cada uno de estos grupos esté en su propio directorio que tratamos con folder2c. Imaginemos un juego de matamarcianos, podriamos agrupar sus assets de la siguiente manera:
grupo1: fondos de la fase 1 & 2
grupo2: fondos de la fase 3&4
grupo3: Musica.

Lo que haríamos sería poner todos los fondos de las fases 1 y 2 en un directorio grupo1, las de las fases 3 y 4 en un directorio grupo2, y en un directorio grupo3, las músicas. Cada uno de estos directorios, al pasar la utilidad folder2.c, nos darà unos ficheros grupo1.h, grupo1.c, grupo2.h, grupo2.c, grupo3.h, grupo3.c
A la hora de compilarlos, ponemos lo siguiente:
sdcc -c -mz80   --constseg BANK2 grupo1.c
sdcc -c -mz80   --constseg BANK3 grupo2.c
sdcc -c -mz80   --constseg BANK4 grupo3.c


Las constantes BANKx nos dicen que identificador de banco le vamos a dar (se han de empezar a usar a partir del BANK2, que es el que puede ser cambiado al vuelo).

Luego al linkar, hemos de poner:
sdcc -o output.ihx -mz80 --no-std-crt0  --data-loc 0xC000 -Wl-b_BANK2=0x8000 -Wl-b_BANK3=0x8000 -Wl-b_BANK4=0x8000 crt0_sms.rel main.rel  SMSlib.lib .\PSGlib.rel  .\grupo1.rel .\grupo2.rel .\grupo3.rel


Definiendo la localización de los bancos creados.

Ahora, durante el juego, supongamos que queremos cargar los fondos de la fase1, haríamos:
SMS_mapROMBank(2);
SMS_loadTiles(Fase1Tiles_bin, currentVRAMPosTilemap,
         Fase1Tiles_bin_size); /


En el momento de ejcutar una música haríamos:
SMS_mapROMBank(4);
PSGPlay(level1_psg); /


Y cuando quisieramos cargar los fondos de la fase 3:
SMS_mapROMBank(3);
SMS_loadTiles(Fase3Tiles_bin, currentVRAMPosTilemap,
         Fase3Tiles_bin_size); /


Obviamente, no hace falta llamar siempre a mapRomBank, solo cuando cambiamos de banco, pero depende de como sea nuestra lógica, esto puede ser mas o menos complicado.

@Gammenon Ha sido una explicación bastante rápida, pero a ver si se entiende :-)
kusfo79 escribió:Vale, pues te explico como hacerlo con folder2c, la otra la usaré para la nueva lección con las novedades en la librería.

Bueno, el tema este de los bancos pasa por que la master del cartucho solo ve 48kb. Esto es así por que solo direcciona 64k, que se reparten entre 48kb para la ROM, y la ram de 8 kb está dos veces mapeada seguida. Como 48kb dan para poco, se usaron mappers especiales que eran capaces de cambiar al "vuelo" bloques de 16kb que llamamos "bancos".

El espacio de los 0kb a los 16kb es el Bank 0, el de los 16kb a los 32kb el Bank 1, y el de los 32kb a los 48kb, el Bank 2. DevKitSMS permite cambiar al vuelo el Bank2 con unas simples instrucciones. Vamos a ver como hacerlo:

Lo principal, es que hemos de decidir como agrupamos los assets del juego, de manera que cada grupo no pese más de 16kb, y que cada uno de estos grupos esté en su propio directorio que tratamos con folder2c. Imaginemos un juego de matamarcianos, podriamos agrupar sus assets de la siguiente manera:
grupo1: fondos de la fase 1 & 2
grupo2: fondos de la fase 3&4
grupo3: Musica.

Lo que haríamos sería poner todos los fondos de las fases 1 y 2 en un directorio grupo1, las de las fases 3 y 4 en un directorio grupo2, y en un directorio grupo3, las músicas. Cada uno de estos directorios, al pasar la utilidad folder2.c, nos darà unos ficheros grupo1.h, grupo1.c, grupo2.h, grupo2.c, grupo3.h, grupo3.c
A la hora de compilarlos, ponemos lo siguiente:
sdcc -c -mz80   --constseg BANK2 grupo1.c
sdcc -c -mz80   --constseg BANK3 grupo2.c
sdcc -c -mz80   --constseg BANK4 grupo3.c


Las constantes BANKx nos dicen que identificador de banco le vamos a dar (se han de empezar a usar a partir del BANK2, que es el que puede ser cambiado al vuelo).

Luego al linkar, hemos de poner:
sdcc -o output.ihx -mz80 --no-std-crt0  --data-loc 0xC000 -Wl-b_BANK2=0x8000 -Wl-b_BANK3=0x8000 -Wl-b_BANK4=0x8000 crt0_sms.rel main.rel  SMSlib.lib .\PSGlib.rel  .\grupo1.rel .\grupo2.rel .\grupo3.rel


Definiendo la localización de los bancos creados.

Ahora, durante el juego, supongamos que queremos cargar los fondos de la fase1, haríamos:
SMS_mapROMBank(2);
SMS_loadTiles(Fase1Tiles_bin, currentVRAMPosTilemap,
         Fase1Tiles_bin_size); /


En el momento de ejcutar una música haríamos:
SMS_mapROMBank(4);
PSGPlay(level1_psg); /


Y cuando quisieramos cargar los fondos de la fase 3:
SMS_mapROMBank(3);
SMS_loadTiles(Fase3Tiles_bin, currentVRAMPosTilemap,
         Fase3Tiles_bin_size); /


Obviamente, no hace falta llamar siempre a mapRomBank, solo cuando cambiamos de banco, pero depende de como sea nuestra lógica, esto puede ser mas o menos complicado.

@Gammenon Ha sido una explicación bastante rápida, pero a ver si se entiende :-)


Muchísimas gracias kisfo79! Intentaré ponerlo en práctica tan pronto me sea posible. Por cierto, te suena que el ihx2sms me indique que no encuentra el header de la ROM?
@Gammenon

Si!, esto es para poder ejecutar la rom en maquinas con Bios (La master americana o la europea), necesitas que haya un header de sega allí. Esto se hace poniendo esto al final de tu fichero principal, fuera de bucles y de todo:

SMS_EMBED_SEGA_ROM_HEADER(productCode,revision);
SMS_EMBED_SDSC_HEADER_AUTO_DATE(verMaj,verMin,author,name,descr);

con tu numero de producto (yo le pongo 99999), la revision. El major version, minversion, autor, nombre de juego y descripcion.
kusfo79 escribió:@Gammenon

Si!, esto es para poder ejecutar la rom en maquinas con Bios (La master americana o la europea), necesitas que haya un header de sega allí. Esto se hace poniendo esto al final de tu fichero principal, fuera de bucles y de todo:

SMS_EMBED_SEGA_ROM_HEADER(productCode,revision);
SMS_EMBED_SDSC_HEADER_AUTO_DATE(verMaj,verMin,author,name,descr);

con tu numero de producto (yo le pongo 99999), la revision. El major version, minversion, autor, nombre de juego y descripcion.


Qué raro, antes no tenía eso y el programa no indicaba que faltaba. Pero bueno, no hay problema, añado eso y listos, gracias! [beer]

@kusfo79 Perdona que te cite pero sino el hilo no se muestra como "nuevo" ni nadie lo mira [+risas] La limitación de los 5 días es realmente tocapelotas.

El tema es que sigo avanzando con el juego y tengo un par de dudas:

- Cómo haces para calcular números aleatorios?

- Ayer estuve implementando memset y memcpy a mano, con un for y ya. Tienes las versiones optimizadas de estas rutinas o alguna pequeña librería con estas y otras funciones típicas que puedas compartir?

Gracias ;-)
Buenas!

Pues para los número aleatorios, uso una estrategia bastante tonta. Tengo en ROM una tabla de 256 elementos, llena de números aleatorios (los genero con un script de python). Para escoger un número aleatorio, depende de lo que hago, es por ejemplo, tener una variable de tipo unsigned char que es el indice de número aleatorio, incrementarla a cada frame, y cuando necesito un número aleatorio, coger el elemento del indice actual dentro de la tabla. O también puedes setear este indice inicial cuando por ejemplo el jugador pulsa un boton, y simplemente irlo incrementando cada vez que pides un nuevo valor aleatorio.


Sobre MemSet y Memcopy, hay implementaciones directamente en SDCC, pero no he probado que tal están de optimización. Aquí hay varias optimizaciones del z80:

http://www.smspower.org/Development/Z80ProgrammingTechniques

Por curiosidad, estas usando memoria dinamica? ojo que en estas máquinas con 8kb, todo esto es bastante peligroso
kusfo79 escribió:Buenas!

Pues para los número aleatorios, uso una estrategia bastante tonta. Tengo en ROM una tabla de 256 elementos, llena de números aleatorios (los genero con un script de python). Para escoger un número aleatorio, depende de lo que hago, es por ejemplo, tener una variable de tipo unsigned char que es el indice de número aleatorio, incrementarla a cada frame, y cuando necesito un número aleatorio, coger el elemento del indice actual dentro de la tabla. O también puedes setear este indice inicial cuando por ejemplo el jugador pulsa un boton, y simplemente irlo incrementando cada vez que pides un nuevo valor aleatorio.


Sobre MemSet y Memcopy, hay implementaciones directamente en SDCC, pero no he probado que tal están de optimización. Aquí hay varias optimizaciones del z80:

http://www.smspower.org/Development/Z80ProgrammingTechniques

Por curiosidad, estas usando memoria dinamica? ojo que en estas máquinas con 8kb, todo esto es bastante peligroso


Gracias por la información!

Lo de los números aleatorios que comentas me ha gustado y pensaba hacer algo similar, contando frames y/o el input del jugador, a ver si así conseguía algo de aleatoriedad.

Respecto a la implementación de SDCC de esas funciones le echaré un vistazo. No estoy usando memoria dinámica, simplemente estoy por ejemplo copiando la paleta en un array para modificarla y así hacer un fundido a negro.
kusfo79 escribió:Esto va a ser tu amigo pues:

http://www.smspower.org/Development/ScreenFading


Probaré primero con mi método, a ver si funciona y estoy entendiendo el percal. Lo que no sé es cómo sería añadir ese código a mi proyecto. Están en ensamblador y yo por ahora sólo tiro de C.
Un ejemplo del propio smslib.c

void SMS_isr (void) __interrupt __naked {
  __asm
    push af
    push hl
    in a,(_VDPStatusPort)                   ; also aknowledge interrupt at VDP
    ld (_SMS_VDPFlags),a                    ; write flags to SMS_VDPFlags variable
    rlca
    jr nc,1$
    ld hl,#_VDPBlank                        ; frame interrupt
    ld (hl),#0x01
    ld hl,(_KeysStatus)
    ld (_PreviousKeysStatus),hl
    in a,(_IOPortL)
    cpl
    ld hl,#_KeysStatus
    ld (hl),a
#ifdef TARGET_GG
    in a,(_GGIOPort)
#else
    in a,(_IOPortH)
#endif
    cpl
    inc hl
    ld (hl),a
    jr 2$
1$:                                         ; line interrupt
    push bc
    push de
    push iy
    ld hl,(_SMS_theLineInterruptHandler)
    call ___sdcc_call_hl
    pop iy
    pop de
    pop bc
2$:
    pop hl
    pop af                                  ; Z80 disable the interrupts on ISR,
    ei                                      ; so we should re-enable them explicitly.
    reti                                    ; this is here because function is __naked
  __endasm;
}
kusfo79 escribió:Un ejemplo del propio smslib.c

void SMS_isr (void) __interrupt __naked {
  __asm
    push af
    push hl
    in a,(_VDPStatusPort)                   ; also aknowledge interrupt at VDP
    ld (_SMS_VDPFlags),a                    ; write flags to SMS_VDPFlags variable
    rlca
    jr nc,1$
    ld hl,#_VDPBlank                        ; frame interrupt
    ld (hl),#0x01
    ld hl,(_KeysStatus)
    ld (_PreviousKeysStatus),hl
    in a,(_IOPortL)
    cpl
    ld hl,#_KeysStatus
    ld (hl),a
#ifdef TARGET_GG
    in a,(_GGIOPort)
#else
    in a,(_IOPortH)
#endif
    cpl
    inc hl
    ld (hl),a
    jr 2$
1$:                                         ; line interrupt
    push bc
    push de
    push iy
    ld hl,(_SMS_theLineInterruptHandler)
    call ___sdcc_call_hl
    pop iy
    pop de
    pop bc
2$:
    pop hl
    pop af                                  ; Z80 disable the interrupts on ISR,
    ei                                      ; so we should re-enable them explicitly.
    reti                                    ; this is here because function is __naked
  __endasm;
}



Por ahora parece que el fade out funciona, ahora estoy con el juego propiamente dicho. Te suena este warning, por cierto?

warning 110: conditional flow changed by optimizer: so said EVELYN the modified DOG

Supongo que el compilador cambia el orden de ciertas instrucciones para que sea más eficiente o algo así.
Esto suele ser que el compilador ha detectado que una condición nunca se cumple, y se la ha cepillado (Ojo, que alguna vez esto es simptoma de que hay alguna variable que desborda, o cosas por el estilo).
Buenas

Gracias kusfo79 por tu tutorial y por el hilo, estoy aprendiendo desde 0, algunas cosas me suenan bastante, [carcajad] de Assembler a "C"

Asi también crear mis propios programas y si es posible juegos, esto a largo plazo claro ;)

Saludos! [bye]
@BcnAbel76

A poco que aprendas, seguro que nos harás grandes cosas Abel!! :-D
Vuelvo a la carga con otra duda, @kusfo79. Creo que es bueno que pregunte este tipo de cosas para que así si otros que le den al tema y se encuentren con el mismo problema tengan material para consultar. Al grano:

Recuerdo que dijiste que con el Z80 era mejor no pasar muchos parámetros a las funciones, que era mejor tirar de variables globales. Creo que, aún sin una anidación demasiado alta (3-4 niveles de llamadas creo), ya he llegado al límite de la pila y tengo un bonito stack overflow. De esto:

Imagen

He pasado a esto:

Imagen

Simplemente llamado a otra función que sólo devuelve true. He hecho varias pruebas quitando una cosa, metiendo otra y creo que apunta a eso, a un stack overflow. Tendré que pasar ciertos parámetros a variables globales.

Relacionado con esto me pregunto: cómo se organizaría la memoria en un juego con múltiples tipos de juego? Me explico: si tengo un juego tipo Sonic y todas las fases son del mismo tipo (plataformeo con enemigos, anillos, otros objetos, etc) puedo asignar cierta cantidad de memoria a cada cosa: aquí va la información de los enemigos, que si el tilemap con las colisiones, etc etc. Podríamos simplificarlo con que hay varios structs por ahí representando cada cosa, y estos structs serían variables globales. Nada más arrancar el juego el código que ha generado el compilador asigna direcciones de la RAM a cada struct.

Bien, si yo tengo estas fases de tipo plataformeo, pero luego resulta que quiero meter otros tipos que no tienen nada que ver, como por ejemplo un mata marcianos, un juego tipo puzzle, etc, cómo se organiza la memoria? Si declaro variables globales también ocuparán su porción de memoria y muy fácilmente me puedo pasar de los escasos 8KB que tengo para todo.

Entonces, ¿Qué se suele hacer en estos casos? Usar un union de structs que aglutine todas las variables globales de cada tipo de juego? Así a bote pronto no se me ocurre cómo hacerlo de otra manera.

Gracias de antemano y perdón por el tocho [beer]

Adjuntos

@Gammenon

Perdon por no contestar ayer, pero es que las preguntas que me haces requieren una respuesta no pequeña, jajajaja.

El tema del stack overflow, como comentas, pasa especialmente cuando llamas recursivamente a una función con parámetros. Siendo un columns el juego que estás desarrollando, me puedo imaginar que son llamadas que realizas para ver si tienes 3 piezas adyacentes o algo así, no?

Una primera manera de evitar esto, es de pasar de la recursividad y del backtracking, y realizar esa lógica a base de loops, es menos "limpio", pero irá mejor en una master.

Por otro lado, acerca de lo las variables globales, es bastante putilla, especialmente si quieres que sea flexible. Por ejemplo, una manera es crear una seria de variables a,e,i,o,u como unsigned chars, otras b,c,d,f,g como unsigned ints, etc, e irlas reusando dependiendo de la lógica que estás implementando en cada momento. Luego aparte, suelo tener structs con los datos de "entidades", que tienen x,y, vx, vy, etc y los coloco en un array, algo del tipo:

typedef struct {
   character *characterData;
   unsigned char entityIndex;
   unsigned char px;
   unsigned char py;
   unsigned char direction;
   unsigned char currentAnimation;
   unsigned char currentFrame;
   unsigned char framecnt;
   unsigned int vramposition;
   bool has2ReloadTiles;
   bool animationEnded;
} entity;

entity entities[MAX_NUM_ENTITIES];

Y esto lo voy reusando independientemente del tipo de jugabilidad que hay en cada momento. Pero bueno, el ejemplo que pones tu es especialmente peliagudo, jejej.
kusfo79 escribió:@Gammenon

Perdon por no contestar ayer, pero es que las preguntas que me haces requieren una respuesta no pequeña, jajajaja.

El tema del stack overflow, como comentas, pasa especialmente cuando llamas recursivamente a una función con parámetros. Siendo un columns el juego que estás desarrollando, me puedo imaginar que son llamadas que realizas para ver si tienes 3 piezas adyacentes o algo así, no?

Una primera manera de evitar esto, es de pasar de la recursividad y del backtracking, y realizar esa lógica a base de loops, es menos "limpio", pero irá mejor en una master.

Por otro lado, acerca de lo las variables globales, es bastante putilla, especialmente si quieres que sea flexible. Por ejemplo, una manera es crear una seria de variables a,e,i,o,u como unsigned chars, otras b,c,d,f,g como unsigned ints, etc, e irlas reusando dependiendo de la lógica que estás implementando en cada momento. Luego aparte, suelo tener structs con los datos de "entidades", que tienen x,y, vx, vy, etc y los coloco en un array, algo del tipo:

typedef struct {
   character *characterData;
   unsigned char entityIndex;
   unsigned char px;
   unsigned char py;
   unsigned char direction;
   unsigned char currentAnimation;
   unsigned char currentFrame;
   unsigned char framecnt;
   unsigned int vramposition;
   bool has2ReloadTiles;
   bool animationEnded;
} entity;

entity entities[MAX_NUM_ENTITIES];

Y esto lo voy reusando independientemente del tipo de jugabilidad que hay en cada momento. Pero bueno, el ejemplo que pones tu es especialmente peliagudo, jejej.


No te preocupes kusfo79, sé que responde cuando puedes. Gracias por la respuesta ;-)

No estoy haciendo nada recursivo, simplemente quizás estoy abusando de la pila con variables locales a las funciones, paso por parámetros y demás. He cambiado eso y efectivamente la cosa funciona. Ahora mismo tengo algo así: en el fichero principal tengo el main(). Hay una variable global de un struct que me he creado para hacer máquinas de estado, que básicamente es un conjunto de punteros a función. Entonces main() llama al updateStateMachine() y éste llama a la función que toca en ese momento (updateGame(), updateGameOver(), etc). Esto ya añade un par de entradas en la pila. Luego voy yo y meto un par de chars aquí y allá dentro de una función y cuando llegar el for(unsigned char i = 0; ...) ya me peta. Como has visto en las capturas paso de Master System a Atari 2600 XD

Aún y todo le voy pillando el truco al asunto, recoloco variables y hago las cosas con macros etc y la cosa va avanzando. Al final voy a hacerlo muy simple, modo 1 jugador survival y ya, suficiente Master System para una primera toma de contacto.
@Gammenon

Pues por esto que dices no debería petar la pila...vamos, no me parece que tengas muchos contextos almacenados a la vez en esa situación...
kusfo79 escribió:@Gammenon

Pues por esto que dices no debería petar la pila...vamos, no me parece que tengas muchos contextos almacenados a la vez en esa situación...


A mi también me extraña pero no le veo otra explicación, quizás esté cargando la pila en alguna parte sin darme cuenta.
La de cosas que descubre uno al programar. Mucho ojo con estas cosas (sacado del manual de SDCC):

--fsigned-char By default char is unsigned. To set the signess for characters to signed, use the option --fsignedchar.
If this option is set and no signedness keyword (unsigned/signed) is given, a char will be unsigned.
All other types are unaffected

[flipa]
Gammenon escribió:La de cosas que descubre uno al programar. Mucho ojo con estas cosas (sacado del manual de SDCC):

--fsigned-char By default char is unsigned. To set the signess for characters to signed, use the option --fsignedchar.
If this option is set and no signedness keyword (unsigned/signed) is given, a char will be unsigned.
All other types are unaffected

[flipa]


Pues eso es algo no parece estar tan claro:
https://stackoverflow.com/questions/205 ... by-default

The standard does not specify if plain char is signed or unsigned.

In fact, the standard defines three distinct types: char, signed char, and unsigned char. If you #include <limits.h> and then look at CHAR_MIN, you can find out if plain char is signed or unsigned (if CHAR_MIN is less than 0 or equal to 0), but even then, the three types are distinct as far as the standard is concerned.
@Gammenon @Diskover

Ciertamente, depende de la implementación del compilador C, pero claro, los que estamos acostumbrados a Gcc, pues....

Como regla general, por si acaso, yo ahora trato de especificar siempre signed or unsigned usando compiladores como el SDCC, o el HUC, para ahorrarme pesadillas XD
Buenas! Lo primero, muchas gracias por los tutoriales [beer] .

Después de mucho rondar y gracias a tu tutorial, me he decidido a intentar programar algo para la master.

De momento estoy sólo jugando con el background y tengo un problemilla. No sé si alguien me podrá echar una mano (pero cualquier aporte lo agradeceré infinito).

Estoy intentando capturar cada interrupción del scanline para crear un efecto con el fondo. Pero al ver el resultado, veo que la función se llama cada 2 scanlines, en vez de cada uno.

Al principio del main lo tengo así:

SMS_setLineCounter(0);
SMS_enableLineInterrupt();

Luego, tengo una función que captura la interrupción y mueve el fondo usando SMS_setBGScrollX() . Por cada scanline, el valor que paso a SMS_setBGScrollX() va cambiando. Aquí es donde veo que lo hace cada dos (Probando el juego en el Emulicious es muy evidente).

¿Qué estoy haciendo mal?
@Baw

pues yo lo veo bien, la única explicación que se me ocurre es que por alguna razón esté tardando demasiado la función que llamas en el HBLANK....
si quieres, pasame algo de código y le hecho un ojo....
@kusfo79 gracias por responder :) .

Si, ese el motivo que he pensado, pero me puse a darle vueltas y ni quitando toda la lógica de la función de interrupción consigo que vaya bien (eso sí, ha mejorado respecto a lo que tenía). Te copio-pego algo de código por si te apetece echarle un vistazo:

1- Tengo estas dos funciones en mi código (sacadas de la librería), por si conseguía aumentar algo la velocidad. El método SMS_write_VDPRegister original tiene alguna cosa más.

inline void SS_write_VDPRegister (unsigned char VDPReg, unsigned char value)
{
VDPControlPort=value;
VDPControlPort=VDPReg|0x80;
}

void SS_setLineCounter (unsigned char count)
{
SS_write_VDPRegister(0x0A,count);
}

2- Mi función captura interrupciones. Como ves, le he quitado toda la lógica que tenía dentro por si era un problema de velocidad. Lo único que hace es sumar uno al índice para acceder al siguiente campo del array en cada interrupción y hace el Set_BGScrollX (mando 0x08 como operación y offsetVector[offsCounter++] como valor del scroll).

void lineInterruptHandler(void)
{
SS_write_VDPRegister(0x08,offsetVector[offsCounter++]);
}

3- Mi main. Inicio el tema de las interrupciones. En el bucle principal, después del VBlank, recalculo los valores de offsetVector. Cambio la paleta cada 4 frames, que no debería influir/tener efectos laterales en el tiempo de proceso de pintado de las líneas porque lo hago después del VBlank

void main()
{
init_console();
load_graphics2vram();
SMS_enableLineInterrupt();
SMS_setLineInterruptHandler(&lineInterruptHandler);
SS_setLineCounter (0);

while (1)
{
// Leo input del player.
SMS_waitForVBlank();
SMS_disableLineInterrupt();
// Calculo nuevos valores para el array offsetVector
LoadLevelPalette ();
SMS_enableLineInterrupt();
SS_setLineCounter(0);
}
}

4- Con esto así y con el array de offsetVector relleno con una sucesión numérica que suma uno al anterior (1,2,3,4,5,6,7,8,9,10,11,12...) debería desplazar el scroll cada línea de pixeles un pixel. Pues no [buuuaaaa]

La función que captura la interrupción, ahora mismo, es llamada cada:
2 scanlines, 1 scanline, 2 scanlines, 2 scanlines, 1 scanline, 2 scanlines, 1 scanline.

Por lo que para las 11 primeras scanlines se ha llamado 7 veces y se ha hecho avanzar el scroll 7 pixeles (y deberían de ser 11).

Y eso me rompe totalmente el efecto que estoy intentando darle.

Cualquier ayuda será bienvenida.

EDITO: Añado captura. La verdad es que con este último código el efecto ha mejorado bastante. No es cada scanline, pero ya no es tan horrible como cuando era cada tres (como se puede ver donde he señalado en la imagen de la izquierda). A la izquierda la antigua carretera, a la derecha la nueva (que tiene la perspectiva más forzada).

Imagen
@Baw

Pues yo ha vista de pajaro no veo nada mal....quieres que le pegue un tiento a sverx en smspower? a ver si el ve algo que se me escapa? (Con las llamadas tan austeras que haces, no debería haber problema).
@kusfo79 pues te lo agradecería un montón [beer]

Es mi primer intento de programar un juego para un sistema clásico y me temo que estoy bastante verde. Me gustaría entender bien porque pasa esto para poder avanzar al capítulo de sprites [+risas] .
@Baw

ya ha contestado sverx, aunque creo que se lo ha leido un poco por encima:

http://www.smspower.org/forums/15228-DevkitSMSDevelopYourHomebrewInC?start=400#101448

Básicamente dice que uses sus funciones directamente, y que evites usar tanto el habilitar y deshabilitar las interrupciones:

void lineInterruptHandler(void)
{
    SMS_setBGScrollX(offsetPointer++);
}

void main() {
    SMS_setLineInterruptHandler(&lineInterruptHandler);
    SMS_setLineCounter(0);
    SMS_enableLineInterrupt();
    while (1)
    {
         SMS_waitForVBlank();
         // calculate new values for the offset Array here, reset offsetPointer to &offArray[1]
         do_your_math();
        offsetPointer=&offArray[1];
        //  set uppermost line value
        SMS_setBGScrollX(offArray[0]);
   }
}


Y por otro lado, que actives el deshabilitar la columna de la izquierda, para evitar los artefactos que salen por ese lado:

SMS_VDPturnOnFeature (VDPFEATURE_LEFTCOLBLANK);
Qué nivel @Baw, tiene muy buena pinta aún con los fallos gráficos que comentas :-)
Muchas gracias @Gammenon , tu port si que tiene buena pinta. Si le añadimos el Italia 90, tenemos el megagames I portado a Master [plas] .

Y a ti también @kusfo79 , muchas gracias por las molestias (te lo has currado un montón). He visto el post donde te contestan en SMSPower (lo han actualizado). Con los consejos de sverx he conseguido mejorarlo un poco más. Para que te hagas una idea, si la parte azul son 112 líneas aprox, deberían salir 112 interrupciones del scanline. Cuando era cada dos contaba 56 interrupciones en la parte azul, con los últimos cambios lo subí a 76 y con lo de sverx es capaz de llegar a las 89 líneas contadas de las 112.

Veo en su respuesta que me dice que quizá debería tocar la parte de ensamblador (el cual tengo oxidadisimo y del z80 concretamente no se nada). Quitar los push del principio, etc. Me he metido con el Emulicious a mirar donde está su rutina de interrupción de línea y he visto que hace:

push bc
push de
push iy
ld hl, (_RAM_C020_) 
call _LABEL_143A_   
pop iy
pop de
pop bc
pop hl
pop af
ei
reti

_LABEL_143A_:
jp hl




en _RAM_C020 tengo la posición 372. Con lo que hace un salto a línea 372. En 372 tengo lo que creo que es el cuerpo de mi rutina de interrupción:

      ld hl, (_RAM_C011_)
      ld c, (hl)
      ld iy, _RAM_C011_
      inc (iy+0)
      jr nz, _LABEL_382_
      inc (iy+1)

_LABEL_382_:

       ld l, c
       jp _LABEL_121A_


saltamos a _LABEL_12A. Donde está la función que manda al VDP el cambio de scroll.

_LABEL_121A_:

     di
     ld a, l
     out (Port_VDPAdress), a
     ld a, $88
     out (Port_VDPAdress), a
     ei
     ret


Ahora, si quito los push, pop innecesarios y los saltos, etc, su rutina me queda:

     
    push iy
     ld hl, (_RAM_C011_)
     ld c, (hl)
     ld iy, _RAM_C011_
     inc (iy+0)
     jr nz, _LABEL_382_
     inc (iy+1)
_LABEL_382_:
     out (Port_VDPAdress), c
     ld c, $88
     out (Port_VDPAdress), c
     pop iy
     pop hl
     pop af
     ei
     ret


Me quedaría mirar si con los cambios funciona y estudiar un poco de z80 y ensamblador para ver como se hacen los direccionamientos, etc, por si encuentro la manera de hacerlo ir más rápido.

¿Alguna manera cómoda de incrustar mi código o me va a tocar ir compilando la librería?

Gracias otra vez y siento el coñazo.

EDITO:

Contando ciclos mi método gasta 189 ciclos (Que ya son menos de los 228 que dice sverx que tengo disponibles.

Me quedan 39 libres, teniendo en cuenta lo que gastan los pops, jp, etc veo normal y entiendo que con el método genérico de la librería se me vayan algunas líneas porque no le de tiempo.

EDITO 2:

He decompilado mi .sms con el Emulicious y lo he modificado a pelo. Luego lo he compilado otra vez y lo he probado. Aparte de estar rotísimo (los tiles no salen como deberían), creo que por hacerlo a lo bestia usando direcciones y de todo según salen del Emulicious sin tener en cuenta cuanto borro y cuanto añado, ha mejorado otras 23 líneas contadas en cielo azul. Eso más las 89 anteriores, hacen las 112 líneas. Con lo que si consigo añadir mi rutina en ensamblador y a falta de probarlo, creo que ya lo tengo [360º]

EDITO 3:

Al final me he mirado un poco como iba la memoria y he modificado el .asm , lo he compilado y casi perfecto (ha subido a 114, si, creo que conté mal el total). He añadido la nueva rutina al final del .asm, a partir de la dirección $4000 (el .asm acababa en la $3FFF). Y mirando el código del columns he modificado la parte de arriba para añadirle más tamaño al banco de memoria ROM (porque si no al compilar no entraba el nuevo código).
@Baw

Buena currada tio, perdona, pero he estado unas horas desenchufado, y me he perdido las actualizaciones, jajaja

Sobre lo que comentabas, puedes insertar asm directamente en el código C, no es necesario compilar la libreria aparte, usando los pragmas __asm y __endasm , hay un ejemplo aquí:

https://github.com/sverx/devkitSMS/blob/master/SMSlib/src/SMSlib.c

De todas formas me sigue sorprendiendo que estes gastando realmente todos los ciclos del hblank con eso....
Baw escribió:Muchas gracias @Gammenon , tu port si que tiene buena pinta. Si le añadimos el Italia 90, tenemos el megagames I portado a Master [plas] .



Hostia no había caído en ello XD XD XD
Con lo último creo que ya lo tengo bien. No se me debe de dar bien contar líneas en el emulador porque me va bailando el número, pero yo veo la carretera perfecta (quitando que se ve destrozada porque la curva ha cambiado al tener mayor resolución vertical que antes).

He estado mirando un poquito de z80 y os copio y pego la rutina original (sacando el código ensamblador de mi rom con el emulador) y los cambios que he realizado sobre ella (puede que haya hecho algo que tenga efectos laterales o que rompa algo debido a la inexperiencia).


     push bc
     push de
     push iy
     ld hl, (_RAM_C020_) 
     call _LABEL_143A_   
     pop iy
     pop de
     pop bc
     pop hl
     pop af
     ei
     reti

_LABEL_143A_:
     jp hl

;SALTO A POSICION DE MEMORIA _RAM_C020_

      ld hl, (_RAM_C011_)
      ld c, (hl)
      ld iy, _RAM_C011_
      inc (iy+0)
      jr nz, _LABEL_382_
      inc (iy+1)

_LABEL_382_:

       ld l, c
       jp _LABEL_121A_

_LABEL_121A_:

     di
     ld a, l
     out (Port_VDPAdress), a
     ld a, $88
     out (Port_VDPAdress), a
     ei
     ret



Lo primero he quitado los saltos, he borrado las siguientes instrucciones (ciclos que me ahorro apuntado al lado):

ld hl, (_RAM_C020_)   -> 16 ciclos
call _LABEL_143A_    -> 17 ciclos
jp hl -> 4 ciclos
jp _LABEL_121A_ -> 10 ciclos


(Total: 47 ciclos menos)

Quedando:

     push af     
     push bc
     push de
     push iy
     ld hl, (_RAM_C011_)
     ld c, (hl)
     ld iy, _RAM_C011_
     inc (iy+0)
     jr nz, _LABEL_382_
     inc (iy+1)
_LABEL_382_:
     ld l, c
     di
     ld a, l
     out (Port_VDPAdress), a
     ld a, $88
     out (Port_VDPAdress), a
     pop iy
     pop de
     pop bc
     pop hl
     pop af
     ei
     reti


Siguientes cambios:

Quitar registros l y c, porque creo que todo se puede hacer con el registro a. Eso quita las instrucciones:
ld l, c -> 4 ciclos
ld a, l -> 4 ciclos

Dejar de usar el registro iy y hacerlo todo con el hl. Eso quita:
push iy -> 15 ciclos
pop iy -> 14 ciclos

Y convierte:

ld iy, _RAM_C011_ -> 14 ciclos
inc (iy+0) -> 23 ciclos
jr nz, _LABEL_382_
inc (iy+1) -> 23 ciclos


en

ld hl, _RAM_C011_ -> 10 ciclos
inc (hl) -> 11 ciclos
jr nz, _LABEL_382_
inc (hl)  -> 11 ciclos
inc (hl)  -> 11 ciclos


(Total: 47 ciclos menos de antes y 42-53 ciclos de ahora )

Por último uso los registros alternativos (no sé si esto se puede hacer libremente). Con ello cambio muchos push/pop por instrucciones con menos ciclos:

push af -> 11 ciclos
push bc-> 11 ciclos
push de-> 11 ciclos



por

ex af, af' -> 4 ciclos
exx         -> 4 ciclos 


y

pop de -> 10 ciclos
pop bc-> 10 ciclos
pop hl-> 10 ciclos
pop af-> 10 ciclos


por

ex af, af' -> 4 ciclos
exx         -> 4 ciclos


Si con esto no he roto nada y si no se me ha pasado algo calculando, la rutina se ha reducido en 47 + 42-53 (dependiendo del if) + 57 = 146 ciclos - 157 ciclos

EDITO: Añado la rutina final, que se me ha pasado:

   
        di
   exx
   ex af, af'
   ld hl, (_RAM_C011_)
   ld a, (hl)
   ld hl,_RAM_C011_
   inc (hl)
   jr nz, _LABEL_4001_
   inc (hl)
   inc (hl)
_LABEL_4001_:
   out (Port_VDPAddress), a
   ld a, $88
   out (Port_VDPAddress), a
   exx
   ex af, af'
   ei
   ret


Total: 114 ciclos - 126 ciclos
Por cierto, la carretera es para ver bien el efecto del scroll o realmente quieres hacer un juego de carreras?
La idea era ir haciendo los tutoriales montando algo un poco diferente. Sin muchas pretensiones y de manera relajada, porque estoy hasta arriba de curro y ya habrás visto por mis post que me engancho y me cuesta parar [tomaaa]

Outrun (Arcade-Saturn) y SuperHangOn (Megadrive) son dos juegos que me gustaron mucho y a los que le doy de vez en cuando (la ost del SuperHangOn me parece brutal). Por otro lado, la conversión del Hang on para la master es muy tempranera y muy distinta gráficamente al arcade.

Creo que se puede mejorar bastante el aspecto que tenía ese juego y acercarlo más a la calidad gráfica del GPRider de GameGear o del mismo Outrun de MasterSystem.

Pero vamos, si consigo una moto que se vea bien y la carretera haciendo curvas suavemente con una buena animación de avanzar (tengo que retocar un poco los colores en el grafico original para suavizar el avance de la paleta, uso 4 colores para cada cambio de color con lo que queda relativamente decente) me doy por satisfecho [fumando] .
@Baw

Pues aunque no lo hemos visto en movimiento, tu carretera tiene una pinta genial!

Si te da tiempo a tener algo medio hecho, podrías presentarte a la SMSPower competition que acaba el dia 21!

http://www.smspower.org/forums/16959-Competitions2018DiscussionThread
kusfo79 escribió:@Baw

Pues aunque no lo hemos visto en movimiento, tu carretera tiene una pinta genial!

Si te da tiempo a tener algo medio hecho, podrías presentarte a la SMSPower competition que acaba el dia 21!

http://www.smspower.org/forums/16959-Competitions2018DiscussionThread


No sabía las fechas del concurso de smspwer, a ver si puedeo llegar con el Columns [beer]

@Baw: La banda sonora del Super Hang On es brutal, a mi parecer superior a la del Out Run. En youtube tienes unos buenos remixes
Muchas gracias!

no llego al día 21 [buuuaaaa] , lo tengo que dejar aparcado unos días [+furioso] y me queda bastante trabajo todavía.

Tengo que conseguir crear la curvas de manera suave y con sentido porque ahora puedo interpolar de la recta a la curva pero queda un poco raro (creo que ya tengo todas las variables para hacerlo bien). También tengo que añadir un desplazamiento constante a todas las líneas, pero variable entre frames, para que cuando la carretera se gire la parte más cercana a la cámara no se deforme y se quede centrada.

Por último y con lo que he estado la última vez que me he puesto es con los cambios de paleta tanto entre líneas, como en mitad de la línea. Necesito tiempo para hacer pruebas y para ver si puedo integrarlo. Así puedo ir haciendo cambios de color de algunas cosas sobre el background (que se come 12 colores para las 3 animaciones de la carretera) y dejar la paleta de sprites tranquila para las motos y elementos laterales del escenario. Tengo el estudio hecho sobre los cambios de colores que necesito y cuando los necesito y en la teoría todo es posible [+risas]

Bueno el cambio de color a mitad de línea (que me vendría genial para colorear los marcadores de rojo a la izquierda y de verde a la derecha) lo veo poco posible. He estado haciendo pruebas y de momento no consigo que siempre empiece en el mismo sitio el cambio de color, ni que siempre tarde lo mismo (y no se si el emulador se lo comerá bien en las pruebas o si funcionará diferente en una master real).
Baw escribió:Muchas gracias!

no llego al día 21 [buuuaaaa] , lo tengo que dejar aparcado unos días [+furioso] y me queda bastante trabajo todavía.

Tengo que conseguir crear la curvas de manera suave y con sentido porque ahora puedo interpolar de la recta a la curva pero queda un poco raro (creo que ya tengo todas las variables para hacerlo bien). También tengo que añadir un desplazamiento constante a todas las líneas, pero variable entre frames, para que cuando la carretera se gire la parte más cercana a la cámara no se deforme y se quede centrada.

Por último y con lo que he estado la última vez que me he puesto es con los cambios de paleta tanto entre líneas, como en mitad de la línea. Necesito tiempo para hacer pruebas y para ver si puedo integrarlo. Así puedo ir haciendo cambios de color de algunas cosas sobre el background (que se come 12 colores para las 3 animaciones de la carretera) y dejar la paleta de sprites tranquila para las motos y elementos laterales del escenario. Tengo el estudio hecho sobre los cambios de colores que necesito y cuando los necesito y en la teoría todo es posible [+risas]

Bueno el cambio de color a mitad de línea (que me vendría genial para colorear los marcadores de rojo a la izquierda y de verde a la derecha) lo veo poco posible. He estado haciendo pruebas y de momento no consigo que siempre empiece en el mismo sitio el cambio de color, ni que siempre tarde lo mismo (y no se si el emulador se lo comerá bien en las pruebas o si funcionará diferente en una master real).


Es para el 27, tienes unos días más, ánimo! [beer]

Yo ayer estuve con el Columns y la cosa avanza, aunque me hacía cosas rarísimas. Asignaba un valor a un elemento de un array tal que así:

Byte unaFuncion()
{
  return 5;
}

miArray[0] = unaFuncion();


Y pintando miArray[0] en pantalla me daba un 65 en vez de 5. No sé que puñetas está pasando ahí [mad]
@Gammenon pfff...complicado, quizá con algo más de código (como lo pintas, como esta declarado ese array, etc).

¿Usas base octal para algo? (¿se puede usar?). Lo único que se me ocurre es que 65 decimal es 101 en octal y 5 es 101 en binario [tomaaa] .

EDITO (Otra idea):

65 decimal es 0100 0001 en binario. Si estas trabajando a nivel de bits, con desplazamientos, mascaras,etc y si ese cinco se calcula como la suma de 4 + 1 (mucha casualidad). Puedes llegar a 65 si has desplazado sin querer los bits del número 4 antes de sumar.
Baw escribió:@Gammenon pfff...complicado, quizá con algo más de código (como lo pintas, como esta declarado ese array, etc).

¿Usas base octal para algo? (¿se puede usar?). Lo único que se me ocurre es que 65 decimal es 101 en octal y 5 es 101 en binario [tomaaa] .


Como has definido ese tipo Byte? de que tipo es el array? suena a conversión de datos totalmente, como ha dicho Baw XD
Baw escribió:@Gammenon pfff...complicado, quizá con algo más de código (como lo pintas, como esta declarado ese array, etc).

¿Usas base octal para algo? (¿se puede usar?). Lo único que se me ocurre es que 65 decimal es 101 en octal y 5 es 101 en binario [tomaaa] .

EDITO (Otra idea):

65 decimal es 0100 0001 en binario. Si estas trabajando a nivel de bits, con desplazamientos, mascaras,etc y si ese cinco se calcula como la suma de 4 + 1 (mucha casualidad). Puedes llegar a 65 si has desplazado sin querer los bits del número 4 antes de sumar.


El tema es que si hago

miArray[0] = 5;

La cosa funciona perfectamente }:/
Mira si es un tema del compilador. En el manual de SDCC no veo que ponga nada del tipo de datos Byte.

Una prueba rápida para saber si esto puede estar influyendo, es que la función la declares devolviendo un unsigned char (Por probar).

EDITO:
Mi mensaje 100 (y en sólo 12 años...eso tiene que ser record [360º] )
Es algo así de memoria:

typedef unsigned char Byte;
Byte miArray[3];
Gammenon escribió:Es algo así de memoria:

typedef unsigned char Byte;
Byte miArray[3];

Como estás mirando el valor?
wave escribió:
Gammenon escribió:Es algo así de memoria:

typedef unsigned char Byte;
Byte miArray[3];

Como estás mirando el valor?


Lo imprimo en pantalla con una función que me hice. Estoy seguro que esta función funciona correctamente, de hecho es consistente el valor que muestra y la mierda que sale por pantalla (tiles incorrectos y tal) XD

Por cierto @kusfo79 he probado lo de los bancos y la cosa funciona, de la rom de 32KB he pasado a la de 64KB al usarlos. Muchas gracias de nuevo! [oki]
Gammenon escribió:Es algo así de memoria:

typedef unsigned char Byte;
Byte miArray[3];
Los typedef son una pesadilla, recomiendo no usarlos.

Aparte de eso y desde el desconocimiento, me imagino que en una máquina de 8 bits no es problema y que leerá los words como 2 bytes y no pasa nada, pero lo comento por si acaso porque en micros como el 68k con alineamiento a word, eso de tener un array de bytes de tamaño impar puede petar en cualquier momento.
Manveru Ainu escribió:
Gammenon escribió:Es algo así de memoria:

typedef unsigned char Byte;
Byte miArray[3];
Los typedef son una pesadilla, recomiendo no usarlos.

Aparte de eso y desde el desconocimiento, me imagino que en una máquina de 8 bits no es problema y que leerá los words como 2 bytes y no pasa nada, pero lo comento por si acaso porque en micros como el 68k con alineamiento a word, eso de tener un array de bytes de tamaño impar puede petar en cualquier momento.


Los typedef dan problemas? :O
Lo del alineamiento puede ser, no tengo ni idea del Z80. Lo he arreglado haciendo lo siguiente:

const Byte a = rand() % yaTal;
miArray[0] = a;

Haciéndolo así me funciona perfectamente.
@Gammenon no problemas no dan los typedefs en sí, pero siempre pueden ser poco intuitivos y liosos para casos como el que pones. Yo personalmente sólo los uso para los structs/unions/enums y para funciones, y sólo porque ahorran escribir una barbaridad sin que hayan líos xD.
1022 respuestas