Tras los análisis que hice del Game Over en el post anterior y tras las conversaciones con Raúl Ortega sobre estas cosillas me propuse añadir la opción del doble buffer en la librería cpcrslib. Una vez implementadas las funciones correspondientes se podría decir que programar un juego usando este método es más sencillo que usando el mapa de tiles pero tiene una serie de desventajas. A saber:
· Ocupa bastante más memoria
· Es más lento o se nota más si hay pocos sprites.
Y lo explico, la memoria que se necesita es mayor puesto que hay que definir dos búferes del mismo tamaño. Uno en donde estará dibujada la pantalla actual sin sprites y el segundo que tendría la pantalla actual más los sprites. Ésta segunda pantalla sería la que se transfiere al área visible una vez que se han dibujado todos los sprites.
La lentitud es notoria principalmente por la forma de transferir el segundo búfer al área visible. En este caso, se transfiere todo el buffer mediante un porrón de LDI y se nota. Con el mapa de tiles solo se transfieren a la pantalla los tiles que deben ser recompuestos y si hay pocos sprites (y pequeños) y por lo tanto hay pocos tiles, el proceso es rápido.
En general, la velocidad es menor pero hay aspectos particulares en los que se gana en rendimiento.
En la configuración mapa de tiles hay que buscar los tiles que hay que restaurar (para borrar el rastro de los sprites en la posición previa si se mueven) y los que hay que transferir al área visible (los que pisa el sprite en su nueva posición más aquellos en los que estaba antes). La búsqueda de los tiles en los que está un sprite es un proceso pesado que lleva su tiempo, su ahorro es apreciado. Así también ahorramos unos preciosos bytes de memoria que son los que se utilizan para almacenar esos tiles “pisados”.
Yendo un poco de la teoría a la práctica, a continuación se muestran las operaciones a realizar para mostrar los sprites en los dos métodos:
Mapa de tiles:
1. Limpiar la lista de tiles. Se inicializa la lista de los tiles que se han de restaurar y mostrar.
2. Buscar los tiles que pisa un sprite tanto en su posición actual como en la anterior. Este proceso busca los tiles y actualiza la lista de tiles “tocados”.
3. Restaurar pantalla. Se leen los tiles en los que estaba el sprite y se restauran los tiles en el búfer para restaurar el mapa de la pantalla.
4. Dibujar el sprite en el búfer.
5. Transferir los tiles “tocados” a la pantalla visible. Se lee la lista de tiles y se copian los trozos correspondientes del búfer a la pantalla. Estos trozos limpiarán y dibujarán el sprite en su posición nueva en el área visible.
Nota: En el búfer está la pantalla más los sprites.
Doble búfer:
1. Restaurar el espacio que pisaba el sprite en el búfer 2 con el trozo “limpio” del búfer 1.
2. Dibujar el sprite en el búfer 2.
3. Transferir el búfer 2 al área visible.
Nota: En el Búfer 1 está la pantalla dibujada sin sprites, en el búfer 2 está la misma pantalla más los sprites.
Como se ve en la comparación de los dos métodos, el segundo es más sencillo pero como ya he comentado, si hay pocos tiles que restaurar, es bastante más lento que el paso 5.
Si tuviera tiempo podría hacer un cálculo matemático para comparar el rendimiento de los dos métodos y ver cuándo empieza a ser mejor usar el doble búfer en función del número de sprites y su tamaño, del tamaño de la pantalla de juego, etc. Pero como no lo dispongo de ese tiempo lo que tengo en mente es hacer unos ejemplos usando los dos métodos a ver cómo se comportan en cada caso.
Otra cosa… para hacer scroll no me he metido a usar doble búfer porque creo que ahí sí que no iba a compensar tener que hacer el scroll en dos búferes en lugar de uno. Aún así, usando la librería con soporte de scroll para pantallas estáticas el doble búfer funcionaría perfectamente ya que se comparten rutinas. En el caso de scroll, la transferencia del búfer 2 al área visible es la misma que la de transferir el búfer del mapa de tiles al área visible (no se transfieren tiles sueltos).
Ya postearé los resultados.
Este año en RetroMadrid se presentó el pack #1 de juegos ESP Soft el cual fue un éxito, se vendieron las 15 que sacamos. Bueno, mejor dicho, que se encargó el amigo David (6128) de sacar ya que él promovió toda la historia, desde convencernos para sacar el pack hasta [...]
Tenía curiosidad por ver cómo estaba hecho el juego Sol Negro de Opera Soft, principalmente en cómo gestiona la pantalla así que me puse a mirar un poco las tripas del mismo con el WinApe que para esto da muchas facilidades. ¿Por qué de mi curiosidad? Pues porque me llamó [...]
Tras un arreón estos últimos días, ya tengo portada la librería para SDCC. Ahora llega la fase de ponerla bonita y testearla profundamente. Afortunadamente no me estoy encontrando muchos errores y, en el proceso de portado, alguna mínima modificación/optimización sí que se ha ganado con lo que alguna mejora habrá. [...]
“La lentitud es notoria principalmente por la forma de transferir el segundo búfer al área visible. En este caso, se transfiere todo el buffer mediante un porrón de LDI y se nota.”
Considero que no es el mejor modo de implementar un doble buffer en Amstrad CPC, donde tenemos la facilidad de con un par de outs cambiar el puntero del inicio de la memoria de video sin ser necesario andar volcando nada.
El volcado usando LDI es un remanente Spectrumil que ayuda bien poco en el caso del Amstrad CPC (por eso las conversiones al Amstrad partiendo del Spectrum eran tan lentas). En el caso del Spectrum, el tamaño del volcado no llega a los 7 Kbytes, mientras que en el Amstrad CPC, en el peor de los casos (usando una resolución equivalente a la spectrumil) es de 12 Kbytes (demasiado lento).
En CPC, con un par de outs y restaurando simplemente la parte del escenario que ha cambiado antes de cambiar la dirección de la memoria de vídeo, te aseguras el doble buffer más rápido del globo…
Es mi opinión…
Salu2.
Y otro problema que se me olvidaba comentar del copiado con LDI… Haciendo el volcado con LDI, ademas de ser sumamente lento, eso añade el problema de que el volcado completo no entra dentro de un único frame, lo cual puede provocar parpadeos molestos (motivo por el cual se usa “doble buffer”, para evitar parpadeos).
Un par de outs y restaurar las partes sucias (método rápido) consigue que el cambio entre pantallas esté dentro del tiempo de pintado de un frame, lo cual es mucho más rápido y te da la posibilidad de sincronizarlo con el VSYNC para evitar parpadeos.
Salu2.
Como muestra, tienes el vídeo del Uwol que subimos. Usamos doble buffer y como se aprecia en el video, la cosa funciona bien suave, y tenemos cantidades enormes de sprites moviéndose por pantalla (más de 12) de un tamaño considerable, 24×24 (más que la media usada, que suele ser 16×16).
Además, la pantalla es de más de 16 Kbytes, por lo que para evitarlo, usamos un split para dividir la pantalla en dos zonas y no pasar de 16 Kbytes.
Aún teniendo que lidiar con todo, split incluido, y controlar cuando un sprite cae en ambas zonas de pantalla, el resultado es muy veloz (se aprecia en el vídeo), y lo mejor de todo es que hemos tenido que “enlentecerlo”, porque iba demasiado rápido. Incluso en el vídeo, si se compara con el resto de versiones, se aprecia que la versión Amstrad CPC es más rápida que el resto.
Resumiendo:
Doble Buffer Spectrumil usando LDI y volcados brutales = Lento
Doble Buffer Cepeceril, usando el CRTC = Cachondamente rápido.
Salu2.
Tienes toda la razón y soy plenamente consciente de que el doble búfer por Hardware es mil veces más rápido. Por ahora mis premisas son dos: usable en 64 kb y dejando libre el máximo de memoria disponible para el programa. Desgraciadamente un doble búfer chupa mucha memoria
De todas formas no lo tengo descartado del todo y cuando acabe con las opciones por software pasaré al hardware lo cual, sin entrar en scrolls, es bastante más sencillo de programar. Un par de OUTs
“Por ahora mis premisas son dos: usable en 64 kb y dejando libre el máximo de memoria disponible para el programa. Desgraciadamente un doble búfer chupa mucha memoria”
No entiendo a qué te refieres. Los requerimientos de memoria son idénticos con ambos sistemas, con la excepción de que el método “hardware” es más rápido y te evita andar copiando 16 Kbytes de VRAM cada vez.
Siendo esto así, no entiendo las premisas que expones para que decidas implementar ese “doble buffer” por software en lugar de la versión rápida. Si a eso añadimos que en pantallas más grandes, el doble buffer con copiado que comentas no te asegura la eliminación de parpadeos, y menos suavidad.
En cualquier caso, cada uno es Rey en su casa e imagino que tus motivos tendrás para hacerlo en modo “spectrumil”, y es respetable.
Lo único, si al final lo implementas como has comentado en el artículo, no uses LDI para hacer la copia. Usa el STACK para acelerar algo más ese proceso y ganar algo de tiempo en el copiado.
Saludos y enhorabuena.
DaD.
Nein, son parecidos, no iguales. No se me va a ocurrir usar la pantalla completa y transferirla usando doble búfer por software porque para eso lo hago por hardware

Además, que no tengo nada en contra del doble búfer hardware… las premisas que pongo son porque lo quiero implementar en la librería cpcrslib y hacer algo que pueda resultar estándar y accesible a todo el mundo… todo el mundo está limitado a 2 ó 3 personas que la usan pero bueno, como ejercicio didáctico para mí
Si me pongo a trastear y tener que hacer rutinas específicas para todo entonces no me vale como estandarización… sí, ya se que podría perder el tiempo en hacer cosas realmente interesantes y productivas. Hacer un juego optimizado en todos los aspectos, rutinas específicas para dibujar cada sprite, doble búfer hardware para 128 kbs…
Pero llega un momento en que no tengo ni tiempo ni ganas de meterme en historias largas y lo que hago es hacer rutinillas que no me llevan mucho tiempo y las “arrejunto” en una librería para C y si le valen a alguien, estupendo y si no… pues ahí se quedan para mí, para hacer algo algún día
Ah, y lo de pasar la pantalla usando la pila en lugar de LDI ya lo tengo también pero me choca con las interrupciones y la música en paralelo así que lo descarté.
Por cierto, ya que vosotros lo tenéis mascadote ya lo redefinir las dimensiones de la pantalla… ¿sabes si es posible definir una pantalla de 8kbs todos seguidos? Me refiero a tener una pantalla más pequeña que quede definida, por ejemplo desde &E000 a &FFFF. Por lo que puede ver aún usando una pantalla de 8kbs, éstos se quedarían entremezclado entre &C000 y &FFFF.
Salu2
No voy a insistir más en el tema del doble buffer. Simplemente, indicas lo de hacerlo “estándar” (por eso lo haces por software). Hacerlo con el CRTC es igual de estándar para el CPC. Todos los CPCs tienen CRTC y todos se van a comportar exactamente igual. Los que usan la librería ni se enteran del modo que tu usas para hacer el doble buffer. No entiendo el concepto de “estándar” que quieres dar.
Usar el CRTC es usar las características del CPC (algo estándar) para sacar beneficio de ellas. No usarlo y hacerlo “copiando” al estilo Spectrum es obviar al propio CPC.
Pero como ya dije antes, tus motivos tendrás y no soy yo nadie para decidir lo que es correcto o no.
En cuanto a tu pregunta sobre la “linealidad”, es sencillo:
* Primero modificas el registro 4 del CRTC con valor 76 y el registro 9 con valor 3. Esto configura los caracteres del CRTC con 4 scanlines por carácter (en lugar de 8) y duplica la altura de la pantalla a 76 caracteres. Si no duplicas la altura, al reducir la altura de los caracteres a la mitad, se te duplicará la pantalla.
* Después configuramos la pantalla a 256×128 píxeles (8192 bytes). Moficias registros 1 y 6 con valor 32. Al haber configurado la altura de los caracteres a 4 scanlines, la altura ha de valer 32 (el doble) y no 16 (como podrías pensar en un primer momento).
* Con los registros 2 y 7, centras la pantalla en la posición deseada.
Y listos… Así tienes una pantalla de 256×128 (Mode 1) o 128×128 (Mode 0) con la memoria “lineal” como pides, desde &C000 hasta &DF00, Teniendo desde &E000 hasta &FFFF libre para lo que se te antoje.
Salu2.
Gracias, muy amable. Me lo apunto.
“…desde &C000 hasta &DF00…”
Desde &C000 hasta &DFFF, perdón… Lapsus numeril.
Salu2.
Probado… y encantado con los resultados… ahora sí me puedo plantear el doble búfer hardware en 64Kbs dejando mucho hueco para código… otra cosa es cómo meterlo en C, pero bueno, todo se andará.
Partía de una premisa falsa debida a mi ignorancia: Pensé que no se podía lograr definir esto de la memoria lineal y que siempre te obligaba a tener más o menos mezclados bytes de video y de código, pero no. Se puede separar
Muy amable.
Nada hombre, a mandar
En cuanto lo tengas listo dame el toque XD
¡Dalo por hecho!
Buscando documentación sobre el CRTC del CPC he encontrado esta web:
http://www.grimware.org/doku.php/documentations/devices/crtc
A falta de profundizarla y estudiarla, tiene muy buena pinta y tiene una imagen explicativa de los registros que creo que es muy suculenta. Ahí queda.
Talué.