FUNDAMENTOS

 

    El mundillo de las demos (a partir de ahora scene) es bastante amplio.. Por esta razón , estos 'funamentos' varian en cada efecto. Estaras bastante de acuerdo conmigo que no tiene nada que ver un efecto relacionado con la rotación 3D con otro que nos dibuje un plasma en la pantalla. Sin emabargo en casi todas las demos se utilizan una serie de bloques que nos seran muy útiles, no solo en la programación de demos sino tambien en la de videojuegos..
    Se recomienda entender bien estas rutinas y procedimientos, ya que serán los mas utilizados , asi como serviran de base para explicaciones futuras:
 
Ensamblador: el lenguaje por excelencia de las demos
Modos Gráficos
Dibujando nuestro primer pixel
La paleta gráfica
Pantallas virtuales
Retrazado vertical
Truco de la artimetica Fija
Texto en modo gráfico
Formato gráfico lineal
 
Donwload de las rutinas y ejemplos

   Ensamblador: el lenguaje por excelencia de las demos

    A pesar de que en esta Web se toca a fondo el lenguaje Pascal, existe un lenguaje por excelencia en el mundillo de las demos: el ensamblador. Las principales caracteristicas de dicho lenguaje son:

    - es un lenguaje de bajo nivel, lo cual significa quue las instrucciones en ensamblador se ejecutan muchísimo más rápido que  sus correspondientes en otros lenguajes de alto nivel como el Pascal o el C
    - el código generado es muy compacto, lo que hace que el tamaño del programa final sea muy pequeño
    -al generar un código muy compacto la velocidad del programa es increiblemente rápida, cosa fundamental en las demos, donde lo que queremos es "exprimir" las posibilidades de la CPU
    - tenemos total acceso a todos los recursos de la CPU, algo impensable en algunos lenguajes de programación

    Pero claro, esto seria demasiado bonito si no hubiese un pega :(  La pega está en la sintaxis y reglas de propio ensamblador. Por poner un ejemplo: En los lenguajes de programación existen una seria de palabras llamadas PALABRAS RESERVADAS. Dichas palabras reservadas realizan una determinada tarea (escribir un texto en pantalla, introducir un nombre, abrir un fichero...). Estas palabras reservadas tienen un significado "mas o menos" orientativo, que nos da una idea aproximada de lo que hace, asi por ejemplo la funcion WRITE ("hola mundo") nos imprimira en pantalla el texto "hola mundo". Veamos el progama en Pascal y el mismo programa en ensamblador para que veas realmente donde esta la pega :) (creo que te daras cuenta tu solo :)



Programa en Pascal que muestra "hola mundo" en la pantalla del ordenador:

PROGRAM hola;
Begin
  WRITE ("hola mundo");
End.

Programa en Ensamblador que muestra "hola mundo" en la pantalla del ordenador:

CR        equ 13                ;retorno de carro
LF         equ 10                ;salto de linea
;segmento de datos
DATOS        SEGMENT
TEXTO        DB  "hola mundo",CR,LF
                    DB   "$"
DATOS       ENDS
;segmento de pila
PILA            SEGMENT STACK
                    DB 128 DUP ('PILA')
PILA            ENDS
;segmento de codigo
CODIGO    SEGMENT
EJEMPLO  PROC FAR
    ASSUME CS:CODIGO,DS:DATOS,SS:PILA
    PUSH    DS
    SUB      AX,AX
    PUSH   AX
    MOV    AX,DATOS
    MOV    DS,AX
    LEA      DX,TEXTO
    CALL   ESCRIBIR
    RET
EJEMPLO ENDP
ESCRIBIR PROC
    PUSH AX
    MOV AH,9
    INT 21h
    POP AX
    RET
ESCRIBIR ENDP
CODIGO ENDS
END EJEMPLO



Bien, has notado la diferencia? :)
En el programa en Pascal hay 4 lineas mientras que en el programa en ensamblador hay unas 30, y todo para hacer lo mismo, escribir "hola mundo" !!! Imaginate como tendría que ser un programa en ensamblador que hiciese lo mismo que uno en Pascal si el de Pascal ocupa 30 lineas!!
Sin embargo hay una cosa muy clara. Una persona que sepa programar en Ensamblador le cosotara 1 semana aprender a programar en cualquier otro lenguaje, sin embargo una persona que solo sepa programar en Pascal, C, Basic, etc le costara "bastante" más de 1 semana  aprender a programar en ensamblador. Sin embargo con esto no quiero desanimar a nadie, ya que solo usaremos el Ensamblador cuando nos sea necesariamente útil, de que vale escribir "hola mundo" en ensamblador si no no nos interasa la velocidad?
De todas formas aqui no se explicará en profundidad la sintaxis y funcionamiento del Ensamblador, ya que es un tema bastante amplio para tocar en toda profundidad, aunque te recomiento estos dos tutoriales:

     ASMES.ZIP (tutor de ensamblador en Español) [22 Kb]
 ASMTUT.ZIP (kizas el mejor tutor de Ensamblador en Ingles) [16 Kb]
 

    Modos Gráficos

    Si pretendemos hacer una demo deberemos conocer mas o menos bien el funcionamiento interno del ordenador. Esto no es absolutamente imprescindible, pero si muy recomendable. Lógicamente si pretendemos hacer una demo deberemos pasar a un modo grafico de video, aunque kizas hayas visto demos en modo texto. Actualmente es posible, gracias a las tarjetas aceleradoras conseguir resuluciones de hasta 1600x1200 pixels con millones de colores, cosa impensable a principio de los noventa, donde lo mas normalito era tener una ISA de 1 Mega de memoria RAM.
 
    La gran mayoria de efectos que veras en esta Web estan en el mismo modo grafico: el modo 13h, aunque existen muchisimos mas (como los modos VESA, que permiten altas resoluciones, o los modos X (no, no pienses mal :) que permiten hacer scrolles por hardware, etc)
    Como veras, el modo 13h es posiblemente el mas fácil de programar, ya que su estructura es LINEAL, y no como los demas modos donde tenemos distintos bancos (modos Vesa) y distintas páginas (modos X)
    La caracteristica principal de los modos de video son:
                        -resolución horizontal :.......320 pixels en modo 13h
                        -resolución vertical :...........200 pixels en modo 13h
                    y  -numero de colores simultaneos : 256 colores en modo 13h
 
    Como habras podido notar, este modo gráfico es el que en muchos juegos esta como "baja resolución". Si ya se, ahora diras que en baja resolución los juegos se ven muy mal, pero...los gráficos los vas a hacer tú, y a menos que seas un artista, cuanto mas grande sea la resolución mejor se veran los fallos :) No en serio, "baja resolución" no implica necesariamente peor calidad, mas bien y pensando positivamente implica mas facilidad de manejo :)

    Bueno, estarás diciendo que vale ya de tanta teoria y vamos a lo que nos interesa!! Bueno, no seas tan impaciente, que ahora voy!! :)
    Para comutar al modo 13h deberemos hechar mano de los servicios "gratuitos" :) que pone a nuestra disposición las INTERRUPCIONES de nuestro ordenador. Interrupciones? Sip, son como una especie de "funciones" especificas que la CPU pone a nuestra disposición. Pueden ser debidas al Hardware (Interrupciones BIOS) o debidas al software del sistema operativo instalado. Dichas "funciones" nos posibilitan el acceso directo y reprogramacion de todos los componentes del ordenador, pudiendo llegar a reprogramar el Timer, la PIC, los puertos de Comunicaciones, el CD-rom, el sistema de video,...
Por esta razón ya dije antes que era importante saber el funcionamiento intermo de los componentes de un ordenador, aunque kizas muchos de estos componentes no los usaremos nunca.
    Bueno, pues que ya voy!. Para conmutar a modo 13h utilizaremos lo que se conoce como la interrupción de Video que pone a nuestra disposicion la BIOS. Dicha interrupción es la 10h. La función para cambiar el modo de video es la 0 y para activar el modo 13h....pues será el valor 13h :).
es decir:

      INT 10h
         AH = 0h     (vamos a cambiar de modo de video)
         AL = 13h   (modo de video 13h, 320x200 pixels x 256colores)
         AL = 3h     (modo texto 80x25 columnas x 16 colores)
y su traducción al Pascal/ensamblador:

PROCEDURE SetVga;assembler;
Asm
    mov ah,0h
    mov al,13h
    int 10h
End.

y para volver al modo texto:

PROCEDURE SetTexto;assembler;
Asm
    mov ah,0h
    mov al,3h
    int 10h
End.

(Te recomiendo que te bajes los tutores de ensamblador si no has entendido muy bien como inicializar el modo grafico o como se usan los registros en Asm)

    Bien, con esto ya tendriamos comnutado a un modo gráfico, ahora solo nos queda 'dibujar algo :)


    Dibujando nuestro primer pixel

    Una vez que ya estamos en modo gráfico estamos en disposición de dibujar cualquier cosa. Lo mas simple es dibujar un punto en la pantalla. A este punto se le conoce como pixel, y es la unidad minima que podemos dibujar en la pantalla.
    Para poder dibujar un pixel (y en extensión cualquier otra cosa, ya que no dejarán de ser una serie de pixels) deberemos conocer lo que se denomina offset.
    Por poner un ejemplo muy simple. Ya hemos dicho que el modo 13h es lineal. Esto implica que si tenemos un punto en la coordenada 160,100 su offset seria: 32160
    Vale, y de donde sale este numero? Pues de la siguiente fórmula:

offset = (320 * y) + x
    Esto implica que para poner un pixel es necesario conocer su offset a partir de sus coordenadas, usando la formula.
Como recordamos, en modo 13h tenemos 320 x 200 pixels, que van desde la coordenada 0-319 x 0-199 si calculamos el offset del ultimo punto (319,199) (esquiza inferior derecha) nos dara:
offset = (320 * 199) + 319 = 64.000
    Esto nos da el tamaño maximo en pixels de una pantalla en modo 13h, aunque lo podiamos haber visto más fácil:
320 pixels de ancho x 200 pixels de alto = 64.000 pixels !!

Si ya has mirado los tutores en ensamblador sabras que el offset nunca esta 'solo', siempre tenemos la pareja segmento:offset
Por lo cual tenemos que conocer el segmento donde tenemos que dibujar nuestro pixel. El segmento es una dirección de memoria. Resulta que la dirección A000h es la que esta asociada con la memoria de video. Por lo tanto para dibujar un pixel tendremos que tener una estructura parecida a esta:

[A000:offset] = color
(en ensamblador, si un valor va entre corchetes [ ] indica que en aquella posición:desplazamiento se guarde un valor, en este caso color)
Pues bien, su traducción al Pascal seria:

PROCEDURE PonPixel (X,Y:integer;Color:byte);
Begin
    Mem[$A000:(320*y+x)]:=color
End;

aunque claro, siempre tendremos un equivalente en ensamblador que va un 'poquito' más rápido :)

PROCEDURE PonPixel   (X,Y:Integer;Color:byte);assembler;
Asm
      mov  ax,$A000
      mov  es,ax
      mov  al,Color
      mov  di,Y
      mov  bx,X
      mov  dx,di
      xchg dh,dl
      shl  di,6
      add  di,dx
      add  di,bx
      mov  es:[di],al
End;

Si quisiesemos dibujar un pixel en las coordenadas (259, 140) con el color 1 hariamos en ambos casos:

PonPixel (259,140,1);

Bien, bien. ahora ya sabemos como dibujar un pixel, pero y si lo queremos dibujar rojo, cual es el color rojo, el 1, el 2, el 46?


    La paleta gráfica

    Los pixels que vemos en nuestro ordenador, al igual que los que vemos en nuestra tele de casa, no son mas que electrones impactando sobre una capa de fosforo, la cual reacciona de forma diferente segun la trayectoria de impacto (menuda explicación !! :)
    Pues bien, lo que nos insteresa a nosotros es saber que un pixel no esta formado por un unico color, si no por la mezcla de tres colores primarios: ROJO, VERDE y AZUL. (en inglés R G B  , Red, Green & Blue)
  En modo13h estas componentes tienen 64 valores posibles:.desde el 0 al 63. Haciendo un poco de cálculo vemos que tenemos 64 x 64 x 64 =262.144 posibles combinaciones. Pues bien, este es el máximo numero de colores disponibles en modo 13h, de los cuales podemos visualizar simultaneamente 256.
    La estructura de la paleta se suele guardar en una matriz de 768 elementos:
     256 colores dibididos en:
            256 porcentajes de Rojo
            256 porcentajes de Verde
            256 porcentajes de Azul
haciendo un total de 256+256+256 = 768 bytes.
    De una forma más 'informatica':
            COLOR[0]= [rojo_0,verde_0,azul_0]
            COLOR[1]= [rojo_1,verde_1,azul_1]
             ......
            COLOR[255]= [rojo_255,verde_255,azul_255]

Para cambiar las componentes Roja, verde y azul de un detreminado color deberemos usar los puertos 3C8h y 3C9h.
En el puerto 3C8h deberemos indicar que color vamos a modificar (0-255). A continuación debermos indicar en porcentaje de rojo, verde y azul (EN ESE ORDEN: ROJO, VERDE y AZUL)

Presentamos la forma de realizar este proceso en lenguaje Pascal:

PROCEDURE PutColor(Color,R,G,B:Byte);
Begin
     Port[$3C8]:=Color;
     Port[$3C9]:=R;
     Port[$3C9]:=G;
     Port[$3C9]:=B;
End;

Normanlmente no necesitaremos que este proceso sea muy rápido, de todas formas aqui esta el equivalente en Ensamblador:

PROCEDURE PutColor (Color,R,G,B:Byte);assembler;
Asm
    mov  dx,03C8h
    mov  al,Color
    cli
    out  dx,al
    inc  dx
    mov  al,R
    out  dx,al
    mov  al,G
    out  dx,al
    mov  al,B
    out  dx,al
    sti
End;

Si quieres el color negro las tres componentes deben ser 0 : (Color,0,0,0)
Si quieres el color blanco las tres componentes deben ser 63: (Color,63,63,63)

Un ejemplo, supongamos que el color 239 quieres que sea azul intenso, en ambos casos harias:

PutColor (239,0,0,63);
Componente roja: 0
Componente verde:0
Componente Azul: 63  (el maximo posible)

Si quieres que sea amarillo fuerte: (amarillo: verde + azul )

PutColor (239,0,63,63);
Componente roja:0
Componente verde: 63 (el maximo posible)
Componente Azul: 63 (el maximo posilbe)

    Como ves, las combinaciones posibles son enormes, en concreto 262.144 combinaciones posibles

La paleta suele guardarse en una estructura especial:

TYPE
    Color= record
            Rojo:byte;
            Verde:byte;
            Azul:byte;
           end;
    TipoPaleta=array[0..255] of Color;
VAR
   Paleta:TipoPaleta;

De esta forma, por ejemplo, para modificar la componente verde del color 239 hariamos:

Paleta[239].verde:=63;


    Pantallas virtuales

    Hasta ahora hemos visto que podiamos dibujar un pixel en el modo de video. Sin embargo a la hora de realizar un efecto o juego deberemos tener en cuenta que no nos llegara la memoria de video para poder realizar nuestros cálculos. Tendremos que usar lo que se conoce como pantalla virtual.
    La pantalla virtual no es mas que otro segmento de la memoria, colocado en una dirección que nos ha dado un puntero. En dichas pantallas podremos modificar las imagenes, ya que si modificamos las imagenes el propio segmento de video se verá un parpadeo muy molesto.
    Suponer este ejemplo: tengo una pelota en la posicion (1,100). La quiero mover a la posicion (320,100) en incrementos de X=10. Si realizo esto directamente en la pantalla de video se produce un parpadeo y la animación se verá muy mal.
Lo que hago es lo siguiente:

copiado / borrado de la pantalla virtual. Figura 1
 
    1º me defino una pantalla virtual y copio el estado inicial de la bola en ella
    2º copio la posicion inicial desde la pantalla virtual a video, con lo cual veo la bola
    3º borro la pantalla virtual
    4º realizo los calculos sobre la posición en la pantalla virtual
    5º copio el contenido de la pantalla virutal a la pantalla de video, con lo cual veo la pelota en la nueva posición
    6º borro la pantalla virtual
    y vuelvo al paso 4º   y asi sucesivamente hasta llegar a la posicion 320.

Como ves lo que se esta haciendo es copiar-borrar, copiar-borrar.
Rutina de copiado.
Lo que realmente hace la rutina de copiado es MOVER X cantidad de bytes del segmento origen (pantalla virtual) al segmento destino (normalmente segmento de video).
Tanto en el caso de copiado como en el de borrado si que es necesario utilizar ensamblador, ya que estos bloques se van a ejecutar muchas veces por ciclo, con lo cual deben estar optimizadas al maximo. La rutina que aqui presentamos utiliza registros de 32 bits (va realmente rápido :)
 
   -rutina de copiado en ensamblador:

PROCEDURE Copy64K  (SegOrg,SegDes:Word);assembler;
Asm
    push ds
    mov  ax,SegOrg
    mov  ds,ax
    mov  ax,SegDes
    mov  es,ax
    xor  di,di
    xor  si,si
    mov  cx,16000
    db   66h;rep movsw
    pop  ds
End;

Rutina de borrado.
La rutina de borrado lo que hace es rellenar un segmento de la memoria con un byte especificado (del 0 - 255), si rellenamos con el byte 0 y este representa el color negro 'borraremos' la pantalla con el color 0, si borramos con el byte 25 y este representa el color naranja, 'borraremos' la pantalla con el color naranja. Esta rutina tambien debe estar optimizada al máximo ya que se realiza muchas veces por ciclo. Tambien se usan registros de 32 bits:

  -rutina de borrado en ensamblador:

PROCEDURE Fill64k (Where:word;Col : Byte); assembler;
asm
    push    es
    mov     cx, 16000;
    mov     es,[where]
    xor     di,di
    mov     al,[col]
    mov     ah,al
    mov     dx, ax
    db      $66, $C1, $E0, $10   {shl eax, 16}
    mov     ax, dx
    db      $F3, $66, $AB          {rep stosd}
    pop     es
End;

Bien, estas 2 rutinas utilizan un segmento 'virtual'. Para poder usarlo deberemos obtenerlo, para ello usaremos una variable tipo puntero, que nos devolverá la dirección de memoria. Al principio parece un poco complicado , pero ya veras que se muy sencillo.

1º  definimos una variable Ppantalla como un puntero:
2º  definimos otra variable Psegmento como un word, que nos servira para saber el segmento de dicho puntero:
3º  para poder utilizar dicho puntero deberemos reservar una 'zona' en la memoria
4º  una vez acabado el programa es necesario liberar esa 'zona' de la memoria.
Las definiciones serian:

VAR
   Ppantalla:pointer;
   Psegmento:word;
Begin
   GetMem (Ppantalla,64000)
   Psegmento:=SEG(Ppantalla^);

vale, ahora supon, que quiero 'borrar' la pantalla virutal con el color 0, utilizando la rutina de borrado:

   Fill64k (Psegmento,0);
Ahora imagina que quieres copiar lo que hay en la pantalla virtual y verlo por pantalla, utilizando la rutina de copiado:
Copy64k (Psegmento,$A000);
o bien
Copy64k (Psegmento, SegA000);
(el compilador de Pascal pone a nuestra disposición una variable interna llamada SegA000 que ya 'apunta' al segmento $A000, que como recordaras es el segmento de video)
Antes de acabar el programa deberemos 'liberar' la memoria ocuapa por el puntero, esto lo haremos con la función FreeMem:
    FreeMem (Ppantalla,64000);

    Notad una  cosa, hemos reservado 64.000 bytes. Esto implica un segmento entero  Por restricciones del compilador y del modo de trabajo del Ms-2 solo podemos reservar 64.000 bytes por segmento. Esto puede dar un quebradero de cabeza a mas de alguno, pero con un poco de pericia y algun que otro truco veremos que podemos acceder a toda la memoria del ordenador.
    Por cierto, la memoria que se reserva es memoria convencional, por lo que NO podemos reservar 'muchos' punteros ya que si lo hacemos asi nos quedaremos rapidamente sin memoria. Si haceis unas pocas cuentas vereis que como mucho se pueden reservar 10 punteros, suponiendo que tengamos 640 Kbytes de memoria convencional. En la practica se suelen reservar 2 punteros y en raras ocasiones mas de 2.


    Retrazado vertical

    El retrazado vertical es una caracteristica del monitor, no de la CPU. Los puntos en el monitor no se dibujan instantaneamte, sino que se van dibujando empezando por la esquina superior izquierda y acaba en la esquina inferior derecha, tal como se indica en la FIG 2.
    Una vez que se ha dibujado el ultimo punto el haz de electrones vuelve a la esquina superior izquierda. FIG 3.

trayectoria de los electrones. FIG 2Retrazado vertical. FIG 3
 
   Pues bien, ahora llega lo importante: tal y como hemos visto cuando copiabamos una pantalla virtual no nos fijamos 'cuando' la copiabamos. Esto presenta un pequeño incomveniente, ya que si copiamos la pantalla antes de que los electrones lleguen al final de la misma se produce un parpadeo. Lo que tenemos que hacer para evitar esto es copiar la pantalla justo cuando se produzca el retrazado vertical (FIG 3). Para saber cuando se produce el retrazado vertical volveremos a ayudarnos del ensamblador cogiendo la información del puerto 3DAh:

    -rutina para esperar el retrazado vertical en ensamblador:

PROCEDURE VerticalRetrace;assembler;
Asm
    mov  dx,3DAh
@@1:
    in   al,dx
    test al,8
    jnz  @@1
@@2:
    in   al,dx
    test al,8
    jz   @@2
End;

    Por lo tanto en el bucle principal de nuestro programa deberiamos tener la siguiente estructura antes de copiar una pantalla virtual:

....
VerticalRetrace;
Copy64k (Psegmento,SegA000);
....
    Activar la espera del retrazado vertical y estando en modo 13h fuerza al ordenador a no sobrepasar una frecuencia de refresco de 60-70 Hz. Esto es util para controlar la velocidad de un programa en un ordenador bastante rapido, ya que por muy rapido que la CPU sea sabremos que las imagenes no van a 'refrescarse' mas de 60-70 veces por ciclo.


    Truco de la artimetica Fija

    Normalmente cuando necesitamos cálculos para nuestras demos o videojuegos casi siempre utilizamos numeros enteros. En raras ocasiones utilizamos numeros reales, es decir, con decimales.
    En una gran parte de los cálculos siempre entervienen las formulas trigonometricas del SENO y del COSENO.
    El Pascal, al igual que otros lenguajes de programación, ponen a nuestra disposición funciones que calculan el SENO y el COSENO. Pero dichas funciones son extremadamente lentas, ya que usan numeros reales.
    Alguno de vosotros puede pensar que podemos REDONDEAR el valor de el SENO o del COSENO para poder convertirlo a un numero sin decimales, a un entero, pero esto no es posible ya que estas funciones trigonometricas solo tienen valores posibles entre el -1 y el 1:

-1 < SIN (a) o COS (a) < 1
Pues bien, como nos las apañamos para calcular algo que haga servir estas funciones trigonometricas?
Veremos que el resultado final no es matemáticamente identico, pero si de una suficiente precision como para darlo por bueno.
Para realizar este calculo se usan los OPERADORES DE DESPLAZAMIENTO: SHL y SHR
Si aun no te has bajado los tutores de Ensamblador ahora es un buen momento. Lo que hacen estas instrucciones es ROTAR los bits de un byte (complicado? :) a la izquierda o a la derecha unas determinadas posiciones.
supongamos que tenemos el numero 24:
    24 en binario es : 11000
ahora desplazamos 1 bit a la izquierda: 11000 SHL 1 = 110000 = 48 en decimal
ahora desplazamos 2 bits a la izquierda 11000 SHL 2 = 1100000 = 96 en decimal
Como vemos, desplazar  1 bit a la izquierda equivale a multiplicar por 2 y desplazar 2 bits a la izquierda equivale a multiplicar por 4, con lo que tenemos:
X SHL 1 = X*2
X SHL 2 = X*4
X SHL 3 = X*8
X SHL 4 = X*16
X SHL 5 = X*32
X SHL 6 = X*64
X SHL 7 = X*128
y
X SHL 8 = X*256
del mismo modo si en lugar de desplazar a la izquierda lo hacemos hacia la derecha tendrememos
24 en binario es : 11000
ahora desplazamos 1 bit a la derecha: 11000 SHR 1 = 1100 = 12 en decimal
ahora desplazamos 2 bits a la derecha: 11000 SHR 2 = 110 = 6 en decimal
En esta caso, desplazar 1 bit a la derecha equivale a dividir entre 2 y desplazar 2 bits a la derecha equivale a dividir entre 4, con lo que tenemos:
X SHR 1 = X/2
X SHR 2 = X/4
X SHR 3 = X/8
X SHR 4 = X/16
X SHR 5 = X/32
X SHR 6 = X/64
X SHR 7 = X/128
y
X SHR 8 = X/256

    Bien, y todo esto para que, te estarás preguntado?. Ahora lo veras:
Supuner que tenemos que hacer esta operación: 100 * SIN (45)
Si lo hacemos con una calculadora veremos que da lo siguiente:
    SIN (45) = 0'70710678;  0'70710678 * 100 = 70'710678
Ahora emplearemos lo que hemos aprendido sobre las rotaciones de bits
    SIN (45) = 0'70710678; 0'70710678 * 256 = 181'01934
    181 * 100 = 18100
    18100 / 256 = 70'703125
o bien
    SIN (45) = 0'70710678; 0'70710678  SHL 8 = 181'01934
    181 * 100 = 18100
    18100 SHR 8 = 70'703125

Es decir:
    Con calculadora: 70'710678
    Con punto fijo:    70'703125
Como vemos las diferencias empiezan en las centenas, con lo que es totalmente valida.
En este caso el resultado de 100 * SIN (45) seria 71.
(redondeo de 70'703125 por exceso)
Como veis, este metodo es muchisimo mas rápido y el resultado es casi identico.
    Bueno, alguno se estara preguntando porque no usar otro factor de multiplicacion en lugar de  X SHL 8 o de division en lugar de X SHR 8?. La respuesta es muy simple. Las instrucciones SHL y SHR se realizan en 1 solo ciclo de reloj de la CPU debido a que son multiplicaciones y divisiones de factor 2 ( 2, 4, 8, 16, 32, 64, 128 y 256). Podriamos haber hecho el calculo otro numero, por ejemplo 317:
    SIN (45) = 0'70710678; 0'70710678 * 317 = 224'15285
    224 * 100 = 22400
    22400 / 317 = 70'662461
Como vemos tambien da  71 (redondeo por exceso). Pero en este caso multiplicar y dividir entre 317 en mas lento que hacerlo entre 256, ya que 317 no es multiplo divisor de 2, con lo cual la operacion se hace demasiado lenta.
    Bueno, creo que queda bastante explicado es numero 'misterioso' que es el 256 que suele salir tantas veces en los listados y no sabemos realmente lo que hace.


    Texto en modo gráfico
 
    Muchas veces se nos presenta que tenemos que representar un numero en pantalla y en formato grafico. El problema que tenemos es que el lenguaje Pascal tiene una funcion llamada OutText que hace esto pero tenemos que usar la libreria Graph del Turbo Pascal. Esta libreria es bastante lenta (pero bastante bastante :).
    Por esta razon tendremos que construirnos una rutina capaz de mostrar un numero en modo grafico, para poder representar, por ejemplo, el numero de vidas que tenemos, cuantos puntos llevamos, etc
    Para hacer esto, lo primero que tenemos que saber es donde estan guardadas las fuentes en la ROM. Esto lo averiguaremos con un puntenro, ya que llamando a la funcion 11 y subfuncion 30 de la interrupcion de video ( la 10h) nos devolvera el segmento:offset donde se halla dicho juego de caracteres:

    -rutina para hallar el juego de caracteres:

VAR
  fofs,fseg:word;
PROCEDURE GetFont; assembler;
ASM
    mov ax,1130h
    mov bh,6
    int 10h
    mov fseg,es
    mov fofs,bp
End;

    Bien, con esta rutina ya sabemos la direccion exacta del juego de caracteres: fseg:fofs
Bien, tenemos que implementar una rutina que nos 'escriba' en modo grafico:

    -rutina que escribe un texto en modo grafico:

PROCEDURE WriteTXT(segm,x,y:word; txt:string;color:byte);
VAR i,j,k:byte;
Begin
  for i:=1 to length(txt) do for j:=0 to 15 do for k:=0 to 7 do
    if ((mem[fseg:fofs+ord(txt[i])*16+j] shl k) and 128) <> 0 then begin
      mem[segm:(y+j)*320+(i*8)+x+k]:=color;
    end;
End;

Bien, y ahora si queremos escribir un numero deberemos convertirlo a formato String (cadena) ya que como se ve en el procedimiento WriteTXT la variable TXT es del tipo string.
Para ello utilizaremos la funcion STR del propio pascal:

    -rutina para convertir  una cadena String a un numero de 5 digitos

Function Num2String (numero:Longint):String;
VAR cadena:String[5];
Begin
  STR (numero,cadena);
  Num2String : = cadena;
End;

Bien, ahora supongamos que queremos escribir : PUNTOS : 3421 en las coordenadas (1,1) y con el color 23:

GetFont;
WriteTXT(SegA000,1,1,"PUNTOS : "+Num2String (3421),23);

    Como veis he escrito diriectamente en la pantalla de video (SegA000), pero lo podria haber hecho sobre cualquier otro segmento virtual y luego copiarlo todo a video.
    La rutina GetFont carga el juego de caracteres normalizado. Sin embargo podemos modificar dicho juego de caracteres y hacer que la fuente de la letra sea distinta. Lo unico que debemos hacer es cargar en memoria el nuevo juego de caracteres y hacer que fseg:fofs apunten a este nuevo juego en lugar de al normalizado.
    Por otro lado el procedimiento WriteTXT escribe un texto con de forma normal. Podemos modificar esta rutina para que el texto sea multicolor, de mayor tamaño, italico, negrita, inverso, etc.
Todas estas modificaciones las encontraras en el area de Downloads :)
 


    Formato gráfico lineal

    El formato gráfico lineal, tambien conocido como formato crudo es el mas sencillo de manejar. Existen muchos formatos graficos: PCX, GIF, JPG, PNG, BMP, TIFF, TGA,.... La gran mayoria de estos formatos graficos poseen algoritmos para que los datos almacenados en ellos ocupen menos espacio, de esta forma se consigue que el tamaño del fichero sea mas pequeño.El ejemplo mas claro es en los archivos JPG donde la compresion puede llegar a ser de 3:1.
    Sin embargo, cuanto mas comprimida esta una imagen mas proceso de calculo es necesario para descomprimirla.
En cambio el formato lineal no posee ningun tipo de compresion. Los datos del fichero son exactamente los que se visualizaran  en pantalla. El  problema de estos ficheros es su gran tamaño, ya que es posible que la misma imagen en otro formato ocupe la mitad de espacio en el disco duro. Pero por contrapartida, al no poseer ningun tipo de compresión su manejo es cosa de niños :)
Al tratarse de un fichero lineal y al estar en modo 13h, la longitud de este sera = 320 x 200 = 64.000 bytes
Basicamente hay 2 tipos de ficheros en formato lineal:
    1º El primero tiene una longitud de 64.768 bytes. En este fichero puede haber 2 variantes:
        1.1: que los primeros 768 bytes sean de la paleta y el resto el dibujo
        1.2: que los ultimos 768 bytes sean de la paleta y los primeros 64.000 el dibujo
    2º Tener un fichero nombre.xxx de 64.000 bytes y tener otro fichero paleta.xxx de 768 bytes
En ambos casos se trabaja de la misma forma. Yo personalmente me gusta mas el modelo 2º, ya que de esta forma, y si dos pantallas tienen la misma paleta, solo es necesario tener 1 fichero de paleta para las 2 (3, 4, o mas) pantallas.

En primer lugar y como casi siempre nos definiremos una variable puntero donde almacenar los datos leidos del fichero, tambien nos tendremos que definir la variable para el segmento del puntero, asi como la variable para el manejo de la paleta:
TYPE
   Color = record
            Rojo:byte;
            Verde:byte;
            Azul:byte;
           end;
    TipoPaleta=array[0..255] of Color;

VAR
  Ppantalla:pointer;
  Psegmento:word;
  PALETA:TipoPaleta;

A continuacion deberemos tener los procedimientos que nos cargan los datos del fichero:

PROCEDURE CargaImagen(nombre:STRING);
var F:file;
BEGIN
    assign (F,nombre);
    reset (f,1);
    blockread(f,Ppantalla^,64000);
    close (f);
END;

Como veis la carga es sencillisima.Si queremos cargar la pantalla IMAGEN.SCR llamariamos a este procedimiento:

CargaImagen('IMAGEN.SCR');
Como ves, lo que se hace es leer un bloque (BLOCKREAD) de 64.000 bytes y almacenarlo en el puntero Ppantalla.
Nota: Ppantalla^ (con el simbolo ^) indica la porcion de memoria apuntada por Ppantalla
De la misma forma que cargamos los datos de la imagen cargaremos los datos de la paleta:

PROCEDURE CargaPaleta(nombre:STRING);
var F:file;
BEGIN
    assign (f,nombre);
    reset (f,1);
    blockread (f,PALETA,768);
    close (f);
END;

Como veis, el procedimiento es casi identico, solo cambia la variable contenedora de los datos (en este caso PALETA) y la cantidad de bytes a leer (768 en este caso)

Bien, una vez cargada la paleta deberemos activarla, esto lo conseguiremos utilizando los procedimientos PutColor vistos anteriormente, para los 256 colores disponibles.

Bien y ya para acabar deberemos copiar el contenido del puntero a video usando el procedimiento Copy64k.
El programa principal quedaria algo asi:
  SetVga;
  Getmem (Ppantalla,64000);
  Psegmento:=seg(Ppantalla^);
  CargaImagen ('pk.scr');
  CargaPaleta ('pk.pal');
  EstablecerPaleta(PALETA);
  Copy64k (Psegmento,segA000);
  repeat until keypressed;
  Freemem (Ppantalla,64000);
  SetTexto;

(recordar siempre que es una buena medida liberar el espacio reservado por GetMEM)


    Download de rutinas y ejemplos
 
 
 

ARCHIVO
Tamaño (en Bytes) 
Descripción
Fontedit.zip
19.787
Programa que permite editar y/o modificar un juego de caracteres para poder usarlos posteriormente.
Fonts.zip
53.770
35 ficheros en formato lineal de fuentes para poder usarlas con los ejemplos.
Lineal.zip
13.975
Ejemplo que muestra la carga de una pantalla en formato lineal. Ademas se incluye un efecto de Fading, para mostrar lo que se puede hacer con la paleta
Paleta.zip
9.003
Programa que nos muestra las 262.144 combinaciones posibles de colores de la paleta variando sus 3 componentes basicas
Pcx2lin.zip
6.099
Programa que nos permite convertir un fichero PCX de 320x200 pixels a 256 colores a un formato lineal con paleta independiente.
Texto0.zip
5.327
Rutinas para la visualizacion de caracteres y numero  en modo grafico, tal y como se ha explicado en el apartado de texto en modo grafico
Texto1.zip
27.406
Rutinas que permiten cargar un determinado tipo de fuente (ya sea generada por el programa FontEdit o en formato lineal) en modo grafico. Ademas se incluyen efectos como letras multicolores, centradas, negritas, italicas, de mayor tamaño...
Texto2.zip
5.721
Rutinas que permiten cambiar el tipo de letra en modo texto. Recomendable para cambiar el aspecto del texto bajo MS-2.Permite el uso de fuentes en formato lineal o generadas por FontEdit
Vga0.zip
1.052
Unidad 'basica' creada con todos los procedimientos de estos fundamentos.