El proyecto se trata acerca de la realización de una animación en assembler ARMv8 usando el simulador QEMU (la idea es que sea "dentro" de una Raspberry Pi). El enunciado del proyecto puede encontrarse aquí y nuestra solución está en este TAR.
La animación realizada es un paisaje elaborado por un fondo con montañas y un descampado compuesto por árboles, yuyos, troncos y una casa estilo campestre. Además, se incluyen efectos de día y noche, y un amanecer.
El video terminado se puede encontrar en el siguiente link.
LARGO_FRAMEBUFFER = 480
ANCHO_FRAMEBUFFER = 640
BITS_PER_PIXEL = 32
AMARILLO = 0xFFF000
AZUL = 0xFF
AZUL_CLARO = 0x3333FF
AZUL_OSCURO = 0x000066
BLANCO = 0xFFFFFF
CELESTE = 0x00FFEF
GRIS = 0x808080
MARRON = 0x663300
NARANJA = 0xFF8000
NEGRO = 0x0
ROJO = 0xFF0000
ROJO_CLARO = 0xFF3333
ROJO_OSCURO = 0xCC0000
ROSA = 0xFF66FF
TURQUESA = 0x33FF99
VERDE = 0xFF00
VERDE_CLARO = 0x66FF66
VERDE_OSCURO = 0x006600
VIOLETA = 0xB300C0
x0
→ Colorx19
→ ANCHO_FRAMEBUFFERx20
→ LARGO_FRAMEBUFFERx21
→ Para que siempre esté dentro del FrameBufferx22
→ Sumo al color en x24 en cada iteraciónx23
→ Aclarar / Oscurecerx24
→ Color a cambiarx25
→ Sumo al color en x0 en cada iteraciónx27
→ Address para return de itera líneax28
→ SP -> Stack Pointerx29
→ FP -> Frame Pointer. Se pone la dirección base del framebufferx30
→ Direcciones para los return de las funciones
Sencillo, usando el stack. Para ello, sean x1,x2,x3 los registros que queremos guardar, debemos poner
// Para guardar los valores
str x1,[sp,-8]!
str x2,[sp,-8]!
str x3,[sp,-8]!
//
// Resto del código ...
//
// Para recuperar los valores
ldr x3,[sp],8
ldr x2,[sp],8
ldr x1,[sp],8
Igual, puede hacerse pesado tener que poner todo esto si son muchos los registros a guardar. Para ello está el ejecutable fast (un .cpp compilado). Si le pasamos los nros de los registros que queremos guardar separados por un espacio y al final la palabra fin, devuelve todas las instrucciones en el orden correcto que se deben colocar. Solo recordar que str va al principio del código mientras que los ldr al último para recuperar los valores. Un ejemplo de llamada puede ser:
┌──(helcsnewsxd㉿helcsnewsxd)-[~/Documentos/FaMAF/Segundo/Primer cuatri/ODC/lab/ODC-2022-LAB/Pruebas]
└─$ ./fast // Ejecutamos fast
1 2 3 4 5 fin // Esto sería lo que se pondría como input, el resto nos lo da el ejecutable
str x1,[sp,-8]!
str x2,[sp,-8]!
str x3,[sp,-8]!
str x4,[sp,-8]!
str x5,[sp,-8]!
ldr x5,[sp],8
ldr x4,[sp],8
ldr x3,[sp],8
ldr x2,[sp],8
ldr x1,[sp],8
- Me da vagancia y me pierdo escribiendo los nombres de los registros, ¿no se pueden crear variables?
No, no se pueden crear variables pero si se pueden usar apodos para los registros. Para ello, se debe tener en cuenta que para "habilitar" un apodo dentro de una función, debe utilizarse la siguiente instrucción:
Estructura ->
apodo .req registro
Ejemplo ->
color .req w10
xpixel .req x9
ypixel .req
Luego, recordar de "deshabilitar" estos apodos antes de terminar la función. Para ello, se hace lo siguiente:
Estructura ->
.unreq apodo
Ejemplo ->
.unreq color
.unreq xpixel
.unreq ypixel
Estas funciones están implementadas en formas_geometricas.s
Una cosa importantísima para destacar en este caso es el uso de algunos registros SAVED. En este caso, vamos a tomar la siguiente convención para todas las funciones de geometría de pintado:
-
x21
→ Si x21 es NO nulo, entonces las coordenadas funcionan con módulo de las longitudes para que siempre se pinte dentro del FrameBuffer. Caso contrario, se pinta normalmente -
x23
→ Si x23 es positivo, se aclara la región. Si x23 es negativo, se oscurece. Caso contrario, si es cero, se pinta normalmente. -
x24
→ x24 es el color a modificar, a cambiar. Si es cero, se cambian todos los colores. Caso contrario, solo los píxeles con color igual a x24. -
x22
→ Lo que se le va sumando al color x24 del mismo modo que se le suma x25 al x0. -
x25
→ Degradado. Es lo que se suma o se resta en las iteraciones para darle un efecto de cambio de color a la figura.
x0
→ color(x9,x10)
→ punto en el plano cartesiano con (0,0) arriba a la izquierda (nos queda mejor para el PixelArt)
Si el punto pertenece al Frame Buffer, se pinta el pixel correspondiente a la dirección de memoria
x12 = x29 + 4 * (x10 * ANCHO_FRAMBUFFER + x9)
Se llama simplemente escribiendo
bl Pinta_punto
Notar que es una función nativa.
(x1,x2) y (x3,x4)
extremos de la línea
Usando el algoritmo de bresenham, esta función devuelve todos los puntos que cumplen pertenecer a este segmento.
Es un poco más rara la llamada debido a que se corta la función a mitad del proceso y luego se retoma. Hay que tener mucho cuidado con el guardado de los registros para no modificar algo que no queremos.
Un ejemplo de llamada es:
// (x1,x2) y (x3,x4) extremos
str x26,[sp,-8]!
str x30,[sp,-8]!
bl Itera_linea
// if x26 == FIN_ITERACION
cmp x26,FIN_ITERACION
b.eq Pinta_linea_fin_iteracion
// else
str x30,[sp,-8]!
// Acá va lo que se hace con el punto que pertenece a esa recta.
// Tener en cuenta que si se modifican registros, hay que guardalos en el stack
ldr x30,[sp],8
ret // para que me itere al siguiente punto
Pinta_linea_fin_iteracion:
ldr x30,[sp],8
ldr x26,[sp],8
ret
Notar que es una función global.
x0
→ color(x1,x2)
y(x3,x4)
→ extremos de la línea
Pinta la línea de extremos (x1,x2) y (x3,x4) del color x0. Se realiza utilizando el Algoritmo de Bresenham para líneas. Para ello, se llama directamente a la función de itera línea.
Se llama simplemente escribiendo
bl Pinta_linea
Notar que es una función global.
x0
→ color(x1,x2)
,(x3,x4)
y(x5,x6)
→ extremosx25
→ lo que se le va sumando al color
Utilizando la función de itera línea, la idea es iterar por todos los puntos del segmento de extremos (x1,x2) y (x3,x4), y dado (x,y) perteneciente a este, se pinta la línea de extremos (x,y) y (x5,x6). Se va sumando x25 en cada pinta_linea al color.
Se llama simplemente poniendo
bl Pinta_triangulo
Notar que es una función global.
x0
→ color(x1,x2) y (x3,x4)
→ extremos opuestosx25
→ lo que se le suma al color
Se itera por todos los puntos del rectángulo y se los pinta. La idea es usar fuertemente que los puntos que se dan son extremos opuestos y que el rectángulo es de lados paralelos a los bordes. Se le va sumando x25 al color en cada iteración del eje y.
Se llama simplemente poniendo
bl Pinta_rectangulo
Notar que es una función global.
x0
→ color(x1,x2)
,(x3,x4)
,(x5,x6)
y(x7,x8)
→ extremos del cuadriláterox25
→ lo que se le suma al color
Pinta el cuadrilátero formado por esos cuatro puntos distintos. Se utiliza la función de Pinta triángulo teniendo en cuenta que hay que pintar tres para asegurarnos cubrir toda la zona. Por ende, también afecta x25 al color.
Se llama simplemente escribiendo:
bl Pinta_cuadrilatero
Notar que es una función global.
x0
→ color(x1,x2)
→ centrox3
→ radio
Utilizando el algoritmo de bresenham para circunferencias, esta función dibuja la circunferencia (sin relleno) de centro (x1,x2) y radio x3.
bl Dibuja_circulo
Es una función global.
x0
→ color(x1,x2)
→ centrox3
→ radiox25
→ lo que se le va sumando al color
La idea es que quede un círculo con "textura" al que le falten algunos píxeles. Para ello, se usa la función Dibuja círculo (que usa bresenham) para hacer todos los círculos de radio entero menor o igual a x3
. Entre el dibujo de cada circunferencia, se suma x25 al color.
bl Pinta_circulo_texturado
Notar que es una función global.
x0
→ color(x1,x2)
→ centrox3
→ radiox25
→ lo que se le va sumando al color
La idea es tener un círculo de color sólido sin "texturas". Es decir que todos los píxeles estén pintados. Para ello, se itera por todos los puntos pertenecientes al cuadrado que tiene el círculo inscripto a él y a los que cumplan que r^2 >= x^2 + y^2
se los pinta. Por cada punto pintado se suma x25 al color.
bl Pinta_circulo
Notar que es una función global.
Estas funciones están implementadas en fondos.s
Genera una nube alrededor de un punto. En el caso de ConjuntoNubes
, se generan desde el punto que se da para la derecha →.
Nube grande ↓
bl Nube1
Nube pequeña ↓
bl Nube2
Conjunto de nubes ↓
bl ConjuntoNubes
Notar que es una función global.
Coloca al medio del framebuffer montañas.
bl Montanas
Notar que es una función global.
Coloca en la mitad superior del framebuffer un fondo de amanecer copado.
- Para el primer llamado cuando el framebuffer está totalmente en negro:
bl Dibuja_fondo_amanecer1
- Para cambiar de noche a día:
bl Dibuja_fondo_amanecer2
Notar que son funciones globales.
Coloca en la mitad superior derecha del framebuffer un sol de amanecer copado.
bl Dibuja_sol_amanecer
Notar que es una función global.
Coloca en la mitad superior del framebuffer un fondo de noche copado, reemplazando el del día pero sin modificar nada más que el fondo.
bl Dibuja_fondo_noche
Notar que es una función global.
Coloca en la mitad superior derecha del framebuffer una luna copada.
bl Dibuja_luna
Notar que es una función global.
Coloca en la mitad inferior del framebuffer el pasto verde sólido y liso.
bl Dibuja_pasto
Notar que es una función global.
Estas funciones están implementadas en vegetacion.s
(x1,x2)
→ Origen del tronco. El pino se genera desde este punto hacia arriba.
A partir de un punto, se genera un pino de tamaño fijo con dos posibles variaciones (grande y mediano).
Pino grande ↓
bl Pino1
Pino mediano ↓
bl Pino2
Pino pequeño ↓
bl Pino3
Existe como utilidad auxiliar la función hojas
la cual genera el árbol mediano sin tronco. Se la puede llamar usando bl Hojas
.
Notar que es una función global.
(x1,x2)
→ Extremo superior izquierdo(x3,x4)
→ Extremo inferior derecho
A partir de dos puntos extremos, se genera un arbusto de tamaño variable. Es importante que se respete que (x1,x2) es el extremo superior izquierdo y (x3,x4) el extremo inferior derecho.
bl Arbusto1
Notar que es una función global.
Estas funciones están implementadas en etapa_1.s
(x1,x2)
→ Extremo izquierdo(x3,x4)
→ Extremo derecho(x5,x6)
→ Altura de la carpa
A partir de 3 puntos, genera una carpa.
Carpa con cola hacia la derecha ↓
bl CarpaDer
Carpa con cola hacia la izquierda ↓
bl CarpaIzq
Notar que es una función global.
(x1,x2)
→ Extremo izquierdo(x3,x4)
→ Extremo derecho- DISCLAIMER: Ambos extremos deben estar sobre el mismo eje Y.
A partir de dos puntos extremos, se genera para arriba una fogata de tamaño variable. Para funcionamiento óptimo, elegir valores para x1 y x3 tal que la diferencia entre los dos sea no menor a ~ 20 píxeles
bl Fogata
Notar que es una función global.