Después del artículo sobre Portabilidad, me apetecía retomar los programas y las técnicas de 500 FPS en un PC y 100 FPS en un PC (16 bits), para mostrarlo como sería el mismo proceso usando los servicios que ofrece la BIOS para video. Probablemente el método que usaríamos para implementarlo en una primera fase de aprendizaje.

La BIOS, Basic Input-Output System, hace de mediador entre el hardware, y el programador, ofreciendo servicios de bajo nivel que nos permiten acceder al mismo. A diferencia del acceso directo al hardware, el objetivo de BIOS es garantizar la compatibilidad. La idea inicial era que no importaba como estuviera diseñado un PC en concreto, siempre y cuando expusiera una interfaz común. Sin embargo, pronto se vería que las estructuras internas, tenían que ser idénticas también por lo que la BIOS perdió parte de su sentido inicial, quedando relegada a ofrecer un cierto grado de homogeneidad.

En efecto la implementación de los servicios de BIOS, no suele poner énfasis en el rendimiento, algo que vemos claramente con los resultados:
Acceso al hardware: 5.952 KPíxeles/segundo (93 FPS).
BIOS: 18 KPixeles/segundo (0,28 FPS).

Si habéis llegado hasta aquí, estaréis bastante sorprendidos con el resultado. Hablamos de una diferencia del orden de 250 veces más lento, así que vamos a matizarlo. Fijándonos en el bucle principal, nos damos cuenta que usando la BIOS, la única opción de volcar imágenes a pantalla es dibujando píxeles individualmente. Esto quiere decir que el proceso es mucho más complicado que mover 64.000 bytes de un sitio a otro como hacíamos en al artículo anterior, sino que requiere dibujar 64.000 píxeles cada vez, calculando para ello su posición en pantalla. Esto aspecto, explica en gran medida la diferencia de rendimiento.

Pero aún hay más. La función de Write Pixel, igual que el resto de funciones de la BIOS, soportan cualquier modo gráfico disponible. Es decir, el proceso para dibujar a otra resolución, o con otro número de colores, vendría a ser ensencialmente lo mismo, cosa que accediendo directamente al hardware, requeriría cambios mayores. Esta capacidad, implica que el sistema deba hacer comprobaciones adicionales antes de dibujar un píxel, lo que explica el resto de la diferencia en cuanto a rendimiento.

Precisamente esta flexibilidad que vemos, es la razón de que determinadas operaciones se realizaran también invocando a la BIOS, incluso en la anterior entrega. Por ejemplo asignar el modo gráfico, o comprobar el estado del teclado, podría haberse realizado también atacando al hardware directamente, no obstante habría sido más laborioso, y las ventajas en cuando a desempeño, habrían sido despreciables.

Otra característica de la BIOS, es la cantidad de servicios que nos ofrece, así por ejemplo usamos Set block of DAC registers, que nos permite asignar la paleta completa con una sola operación, y en este caso particular, con un rendimiento bastante aceptable. Es decir nos ha facilitado el trabajo.

Os dejo sin más con el código en cuestión:

#include <stdio.h>
#include <time.h>
#include <conio.h>
#ifdef __WATCOMC__
	#include <i86.h>
#else
	#include <dos.h>
#endif
 
 
#if (defined(__FLAT__))
	#define int86	int386
	#define int86x	int386x
	#define far
#else
#endif
 
 
static unsigned char far gacPerin[]={...};
 
 
int main (void)
{
	register unsigned int iX, iY, iPos, iCont;
	union REGS udtRegs;
	struct SREGS udtSegRegs;
	clock_t clkStart, clkEnd;
 
 
	/* Modo 13 */
	udtRegs.w.ax=0x13;
	int86(0x10, &udtRegs, &udtRegs);
 
	/* El DAC espera los valores en el rango 0..63, 
		 por lo que debemos dividir cada uno por 4 (0..255). */
	for (iX=sizeof(gacPerin)-768; iX<sizeof(gacPerin); iX++)
	{
		gacPerin[iX]=gacPerin[iX]>>2;
	}
 
	/* Asignar toda la paleta */
	udtRegs.w.ax=0x1012;
	udtRegs.w.bx=0x00;
	udtRegs.w.cx=256;
	udtRegs.w.dx=FP_OFF(gacPerin+64000);
	udtSegRegs.es=FP_SEG(gacPerin);
	int86x(0x10, &udtRegs, &udtRegs, &udtSegRegs);
 
	iCont=0;
	clkStart=clock();
	do
	{
		iPos=0;
		/* Volcar al framebuffer 320x200 pixeles */
		for (iY=0; iY<200; iY++)
		{
			for (iX=0; iX<320; iX++)
			{
				udtRegs.h.ah=0x0C;
				udtRegs.h.bh=0;
				udtRegs.h.al=gacPerin[iPos];
				udtRegs.w.cx=iX;
				udtRegs.w.dx=iY;
				int86(0x10, &udtRegs, &udtRegs);
				iPos++;
			}
		}
		iCont++;
		/* Comprobar pulsación de tecla */
		udtRegs.w.ax=0x0100;
		int86(0x16, &udtRegs, &udtRegs);
	}
	while (udtRegs.h.al==0);
	clkEnd=clock();
 
	/* Leer tecla del buffer */
	udtRegs.h.ah=0x00;
	int86(0x16, &udtRegs, &udtRegs);
 
	/* Modo texto */
	udtRegs.w.ax=0x3;
	int86(0x10, &udtRegs, &udtRegs);
 
	/* Mostrar en pantalla número de cuadros, y FPS */
	cprintf("Frames: %d; KPixels/seg: %d\n", iCont, ((long) iCont*CLOCKS_PER_SEC*64)/(clkEnd-clkStart));
 
	return(0);
}

Aquí (62 Kb. en formato ZIP), puedes descargar el ejecutable y su código fuente completo.