/////////////////////////////////
INDICE ///////////////////////
DS CODE 0: "Los primeros pasos"
http://www.elotrolado.net/showthread.php?s=&threadid=560011
DS CODE 1: "Interrumpiendo"
http://www.elotrolado.net/showthread.php?s=&postid=1704954093
////////////////////////////////////////////////////////////////
Bueno pues aquí comienza lo que espero que sea una serie de tutoriales sobre programar en la Nintendo DS y más concretamente con libnds. Aqui está el zip con una plantilla para el proyecto
http://www.elotrolado.net/attachment.php?s=&postid=1704667288
1. REQUISITOS BÁSICOS
Lo primero que mencionaré de forma resumida es lo que necesitáis tener para poder empezar a programar con libnds:
-Devkitpro (
http://www.devkitpro.org/)
Contiene todos los elementos para que podáis empezar: compilador, librería libnds y editor. Además lo configura todo automáticamente para los so windows.
Para instalarlo en linux seguid este excelente tutorial
http://www.elotrolado.net/showthread.php?s=&threadid=543702
-Emuladores
Actualmente con
Dualis nos apañaremos para todo. Evidentemente en hardware real sabe mejor, pero cuando lleves sacando y metiendo la tarjeta SD/CF más de 50 veces, agradecerás que vaya en emulador
http://dualis.1emulation.com/
-IDE (opcional)
Si bien el editor incluido en devkitpro es perfectamente válido y funcional, recomiendo la instalación de
Visual Studio y que sigáis éste otro estupendo tutorial
http://www.elotrolado.net/showthread.php?s=&threadid=459646
-Conocimientos de C++
Aquí no aprenderéis a programar. Asi que si queréis seguir estos tutoriales empezad primero con algo como
http://c.conclase.net/curso/index.php
Esos son todos los elementos necesarios para trabajar. Ahora el primer paso que seguiremos es el de crear un proyecto. Para que ésto sea más fácil vamos a utilizar las plantillas que nos proporciona devkitpro. Así que lo primero que haremos es ir a la carpeta
devkitPro\examples\nds\templates\ y compiaremos la carpeta combined que podremos pegar donde queramos (
no puede haber en la ruta carpetas con espacios ) y renombrarla con el nombre de vuestro proyecto.
Ahora entraremos en ésta carpeta y haremos un rápido repaso por sus elementos
-Makefile
Éste archivo dice como deben compilarse las cosas. Hay un general en la carpeta raiz y otros más en cada carpeta correspondiente a los procesadores. Son éstos 2 los que tendremos que modificar en el futuro para añadir nueva funcionalidad.
-Arm9 y arm7
En estas carpetas tenemos la chicha de nuestro proyecto. Por defecto pondremos el código de cada procesador en sus respectivas carpetas source. Además encontramos ya en ellas un par de archivos. En éste tutorial se usarán
arm7.cpp y arm9.cpp (ambos para c++) pudiendo borrar los otros. De momento no se va a explicar nada sobre éstos archivos.
-Crear archivos .bat
Para mayor comodidad de compilar, limpiar y recompilar se recomienda crear archivos .bat que faciliten éstas acciones. En la carpeta raiz del proyecto (donde está el makefile general) crearemos 3 archivos con extensión .bat con los nombres dados y pondremos los siguientes comandos según corresponda
*Compilar : make
*Limpiar: make clean
*Recompilar: make clean
make
El resultado de compilar serán los archivos .nds y .ds.gba. Sin embargo en ocasiones necesitaremos crear nosotros de forma manual un .ds.gba a partir de un .nds (si queremos usar gbfs por ejemplo) y para poder crearlos necesitaremos el archivo ndsmall.bin (que no encuentro de donde bajarlo). Añadiremos a los comandos compilar y recompilar o podremos lanzar desde línea de comandos la siguiente línea
cat ndsmall.bin nombre.nds > nombre.ds.gba
IMPORTANTE . Donde pone nombre, es el nombre de la carpeta del proyecto. Ésto es asi porque el archivo .nds que se genera tiene el mismo nombre que el de la carpeta. Se puede hacer también con reglas en el makefile, pero así es suficiente.
2. VISIÓN GENERAL DE LIBNDS
Bien, ya tenemos todo listo. Así que llega el momento de tratar con el elemento principal para nuestros desarrollos : libnds.
¿Qué es libnds?
Libnds es una librería que contiene una definición sobre los registros, de modo que no tengamos que estar recordando tropecientas direcciones de memoria ni que bits de cada registros hacen tal o cual cosa. Además se ofrecen ciertas funciones que facilitan un poco las cosas, siendo el 3D la parte más beneficiada en todo esto ya que cuenta con una serie de funciones para su manejo que lo hacen practicamente igual a usar Opengl. Así que si sabéis Opengl, tenéis el 50% hecho para programar 3D.
Entonces dicho ésto, ¿por qué "acojona" tanto usar libnds? Pues precisamente porque son definiciones sobre registros, tienes que saber todos los registros y cuá es su función para poder sacarle partido. Y ésta es la gran diferencia respecto a PAlib. Libnds te sirve todo el hardware de un modo más a meno que meras direcciones y registros, pero debes conocerlo para poder usarlo. En cambio PAlib lo oculta totalmente a cambio de una mayor facilidad de uso, ocasionando una programación en muchas ocasiones más rígida y menos libre que no te permite "domar" el hardware como se te antoje. Pero para "domarlo" es para lo que estamos aquí. Asi que ¡al ruedo!
3. VISIÓN GENERAL DEL HARDWARE
En éste capítulo se pretende dar sólo una aproximación de los elementos más importante que tendremos que tener en cuenta a la hora de programar respecto al hardware. Estos conocimientos tendrán que estar siempre presentes para poder hacer nuestros proyectos sin mayores contratiempos y que no nos pase que nos quedamos con cara de alelados al ver que tal cosa no se puede o no era así.
-RAM
Éste evidentemente es el primer elemento que tendremos que tener en cuenta. Tenemos
4 MBs de memoria disponibles para nosotros. ¿Y que significa ésto? Pues bien para entenderlo, vamos a ver "qué se pone en memoria".
Lo primero que resulta evidente que está en memoria es el código compilado, más concretamente el del arm9 (el arm7 se coloca en una memoria diferente) y por tanto sabemos que eso siempre está ocupado. Es por ello que siempre que podamos, evitaremos compilar los gráficos, música, etc junto con el código, pues estaremos ocupando ésta memoria de continuo con estos datos aunque no se estén utilizando en ese momento. Aqui entran en juego los sistemas de ficheros.
Luego tenemos las variables que estemos usando. En un principio es raro que tengamos problemas de memoria, pero en un hardware limitado como la DS no es cuestión de desaprovecharlo asi que es bueno que no utilicemos tipos de datos mayores de lo que necesitemos. Respecto a las variables es especialmente importante recordar que las varaibles estáticas y globales siempre están en memoria.
-VRAM
Ésta es la memoria más importante de todas, la
memoria de video . Aquí situaremos nuestras
paletas, sprites, texturas, mapas, etc , es decir todo lo que sean los recursos gráficos. En el futuro me referiré a ellos como recursos. De momento sólo decir que está dividida en diferentes bancos y que no todos ellos puede usarse para todos los propósitos ni tienen el mismo tamaño. Se tratará en profundidad.
-Otras memorias
Debido a su infrecuente uso no se mencionarán en los tutoriales. Aun así es bueno mencionar la existencia de una memoria de 32 kbytes dividida en 2 bloques iguales y asignable a cualquiera de los 2 procesadores (
SIWRAM ) y una memoria
caché usable sólo por el ARM9. Queda a cuenta del lector el como usarlas.
-Modos de video
La consola cuenta como es evidente con 2 pantallas físicas: una táctil y una "normal".Sin embargo internamente no se trabaja refiriéndose a pantalla de arriba y pantalla de abajo, si no
pantalla principal y pantalla secundaria , permitiéndose además decidir cual de las pantallas físicas corresponde a cada una de esas pantallas lógicas. Por defecto y sin que hagamos nada la táctil es la principal y la otra la secundaria. Sin embargo hasta que no indiquemos el modo de trabajo de cada pantalla, no se visualizará nada en ella.
Cada modo tiene un número y tipo de fondos que se pueden usar principalmente, además de poderse usar 3D en la pantalla principal usando uno de los fondos (el fondo 0). Mucho más sobre ésto proximamente.
-2D
Una vez más mencionaremos por encima lo principal para tratarlo con detenimiento en el futuro. Lo más importante es saber que que
cada pantalla cuenta con sus sprites y sus fondos (que como se ha comentado antes dependen del modo de video elegido). Además hay que
reservar la memoria (VRAM)
para cada tipo de recurso (sprite, fondo, paletas extendidas) y de cada pantalla que queramos usar mediante los bancos de memoria. Vemos como todo va encajando poco a poco. Escoger modo, reservar banco de memoria para tal recurso de tal pantalla y cargar en esa memoria. Si esto está chupado.
Respecto a los
sprites lo más importante es mencionar que sólo hay
128 "huecos" para poner información sobre ellos para cada pantalla. Dicho así suena raro asi que explicaremos un poco más. Si bien los gráficos en si (lo que es la imagen) se ponen en la dirección de memoria de video (VRAM) destinada a sprites, la información de como es ese sprite (posición, dirección de comienzo en memoria del gráfico, tamaño, etc) que necesita el hardware para dibujarlo, se pone en otro lugar y está limitada a 128 huecos. Ésto quiere decir que tu en memoria de video puedes tener tantos gráficos como quepan (según tamaño y profundidad de color) pero sin embargo no se pueden mostrar más de 128 a la vez en cada pantalla porque no hay más que ese número de huecos para la información. No debiera ser un gran limitación pero aun asi hay métodos para aumentar éste número remplazando la información de los sprites a medida que se pintan. Esto se pone interesante
Sobre los fondos, al igual que los sprites cada pantalla tiene los suyos (recordando una vez más que hayamos escogido el modo de video y hayamos reservado memoria para ello). Existen
3 tipos de fondos : de
texto ,
rotacionales y los últimos que llamaré
extendidos . Los 2 primeros funcionan por tiles (grupos de pixels) y el otro es como un buffer donde directamente pintamos pixel a pixel. Además la pantalla principal en uno de los modos de video puede funcionar como un buffer (realmente inutil teniendo el tipo de fondo extendido) o como un bitmap de gran tamaño.De momento nada más.
Por último las paletas.
Cada pantalla lleva de regalo una paleta para sprites y una para todos los fondos . Cada una de estas paletas puede
organizarse de 2 formas diferentes: o bien como
una paleta de 256 colores o bien como 16 paletas de 16 colores cada una. También existe la posibilidad de reservar ciertos bancos de memoria para albergar más paletas de colores (
paletas extendidas ). Paletas, muchas paletas. En un principio para los sprites usaremos siempre paletas (aunque existe un limitado modo para trabajar con sprites a 16 bits de color) y en los fondos se aprenderá a trabajar con paletas y sin paletas en el caso de los fondos extendidos (que permite ambas cosas).
-3D
Bueno llegamos al punto fuerte de libnds, el 3D. Como he comentado antes utiliza una serie de funciones que ofrecen un
nomenclatura y funcionalidad practicamente idéntica a Opengl . Si bien es cierto que no todo va tan cristalino como quisiéramos ni tampoco es oro todo lo que reluce. En cuanto a limitaciones contamos con el tan odiado
límite de 2048 triángulos en pantalla (ampliable mediante registros de captura) y el sólo poder usarlo en una pantalla (usable en las 2 también mediante el registro de captura). Las texturas como los demás recursos, requiere previamente de la pertinente reserva de bancos de memoria. Los formatos de las texturas son de diversos sabores y colores y se verán en un futuro quien sabe cuan lejano.
4. ESQUELETO BÁSICO Y PRIMER PROGRAMA
Primer programa ¿Ya? Si, porque no. Veamos cual es el esqueleto mínimo y luego pongamos nuestro típico "hello" para probar que tenemos todo bien instalado. En la mayoría de los casos sólo modificaremos el código del arm9 (como en este caso)
#include "nds.h"
int main(void) {
while(1) {
}
return 0;
}
Ésta es la estructura absolutamente mínima (tan mínima que no hace nada) de un proyecto con libnds. En el vemos el bucle principal y lo realmente imporante, la inclusión de nds.h, es decir, de libnds.
Ahora vamos a hacer nuestro primer programa "útil". Comentaré de momento la funcionalidad de cada cosa sólo al lado de la línea.
#include "nds.h"
#include <nds/arm9/console.h> //necesario para poder llamar a consoleInitDefault
#include <stdio.h>
int main(void) {
powerON(POWER_ALL); //inicializa los cores 2D y 3D. Necesario en M3 (esto es por lo que algunos homebrew no arrancan)
videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE); //selecciona el modo de video 0 para la pantalla principal (sin activar 3D) y activa el fondo 0
vramSetBankA(VRAM_A_MAIN_BG); //reserva el banco A para fondos de la pantalla principal
BG0_CR = BG_MAP_BASE(31); //configura el registro del fondo 0 para que busque el mapa de tiles en el bloque 31. De momento os lo creéis
BG_PALETTE[255] = RGB15(31,31,31); //ponemos el último color de la paleta de fondos de la pantalla principal a blanco
consoleInitDefault((u16*)SCREEN_BASE_BLOCK(31), (u16*)CHAR_BASE_BLOCK(0), 16); //función básica proporcionada por libnds para poner texto. Sencilla pero con ciertas limitaciones
iprintf("\n\n\tHello EOL!\n"); //nuestro esperado hello
while(1) {
}
return 0;
}
Bueno si esto os funciona ¡Enhorabuena! Ya tenéis libnds funcionando y listo para que lo odi...disfrutéis y saquéis partido
5. ANEXO. TIPO DE DATOS Éste primer anexo lo voy a dedicar a nuestro gran olvidado. Los tipos de datos. En libnds existen una serie de tipos de datos que deberemos conocer para desenvolvernos con soltura. En concreto hay 2 formas de llamarlos. Yo concretamente utilizaré la que utiliza (v) u-s bits
-unsigned char ->u8;
-unsigned short int ->u16;
-unsigned int ->u32;
-unsigned long long int ->u64;
-signed char ->s8;
-signed short int ->s16;
-signed int ->s32;
-signed long long int ->s64;
-volatile u8 ->vu8;
-volatile u16 ->vu16;
-volatile u32 ->vu32;
-volatile u64 ->vu64;
-volatile s8 ->vs8;
-volatile s16 ->vs16;
-volatile s32 ->vs32;
-volatile s64 ->vs64; Como podemos ver v es de volatile, u de unsigned, s de signed y los bits se refieren a cuantos bits ocupa el tipo de dato. Es muy interesante cononcer cuanto ocupan.
¿Y ya está? ¿No falta "algo"? ..... ¿Y float? Bien, aquí todos a la vez. La DS NO TIENE FPU. Es decir, no existe como tal el tipo float. Si bien tu puedes declararlo y operar con el, usarlos te va a suponer una importante penalización de rendimiento. ¿Y qué uso?
Os presento a.... f32. f32 es el equivalente a float pero con coma fija y tiene la notación 1.19.12. No me entero de nada estás pensando. Bien, como no existen los tipos de coma flotante, los coma fija o fixed point in english utilizan tipos enteros. En este caso usamos un int (de ahi los 32 bits) y usamos el primer bit para signo, los 19 siguientes para la parte entera y los 12 últimos para los decimales. Menudo pitote estás pensando. Este tutorial no pretende enseñar los entresijos del uso de los tipos fixed point (queda como trabajo del lector) pero se darán una serie de descripciones que facilitarán su uso.
Bien seguimos entonces con f32. Como hemos visto internamente tiene una disposición de los bits un tanto especial y por tanto si lo inicializamos a 5 directament, no estaríamos guardando 5, si no 0 coma algo (recordemos que los primeros bits son para decimales). Para ello vamos a ver unos útiles definiciones de libnds
#define inttof32(n)-> pasa de int a f32
#define f32toint(n)-> pasa de f32 a int
#define floattof32(n)-> pasa de float a f32
#define f32tofloat(n)-> pasa de f32 a float Asi mismo en cuanto a operaciones no se puede hacer todo tal cual. La suma y la resta si podremos hacerlas como siempre, pero para la división, multiplicación y raiz cuadrada haremos uso de estas funciones
f32 divf32(f32 num, f32 den)->division
f32 mulf32(f32 a, f32 b)->multiplicación
f32 sqrtf32(f32 a)->raiz cuadrada Pues con eso ya tenemos todo lo necesario para ver un ejemplo
f32 numero=0; //el 0 se puede asignar directamente
f32 numero2=inttof32(2); //pasamos un tipo entero
f32 numero3=floattof32(2.5); //y ahora un tipo decimal
int total;
numero= mulf32(numero2,numero3); //resultado 5
numero= numero+numero; //resultado 10
total= f32toint(numero); //guardamos el 10 como entero
Insisto en que se puede usar el tipo float sin que de error de compilación pero el primero paso para aumentar el rendimiento de tu proyecto es no operar con ellos.
Espero que os haya gustado el primero capítulo. Podéis comentar que os parece, si es fácil, díficil, poco completo, que no entiendes, lo que sea. Los siguientes subirán exponencialmente en dificultad y en contenido "palpable".
//////////////////////////////////////////////////////////////////
En las siguientes versiones pondré al final de este modo un zip con los ejemplos de lo hablado en el tutorial. En este caso están las
plantillas vacías (en próximos tutoriales se complementarán) y los ejemplos de
hello y número con
coma fija
http://www.elotrolado.net/attachment.php?s=&postid=1704667288