Simple DirectMedia Layer
- ¿Qué es?
- SDL es una API de desarrollo multimedia y multiplataforma libre
- Usada para juegos
- Usada para SDKs de juegos
- Usada para emuladores
- Usada para demos
- Usada para applicaciones multimedia
- ¿Qué puede hacer?
- Video
- Eventos
- Audio
- CD-ROM de audio
- Hilos
- Temporizadores
- Independencia del peso del sistema
Video - Establecer un modo de vídeo de cualquier profundidad, con conversión opcional, si es que el modo de vídeo no está soportado por el hardware.
- Escribir directamente a un búfer de marco gráfico.
- Crear superficies con atributos de color clave o fundido alpha.
- Los volcados de superficie son convertidos automáticamente al formato destino usando volcadores especializados, y están acelerados por hardware, cuando esto es posible. Volcados optimizados con MMX están disponibles para la arquitrectura x86.
- Los volcados y rellenos son acelerados por hardware en el caso de que este lo soporte.
Pista:
Puedes establecer la barra de título de tu aplicación (si la tiene) y su icono usando las funciones SDL_WM_SetCaption() y SDL_WM_SetIcon() respectivamente.Eventos - Los eventos se facilitan para:
- Cambiar la visibilidad de la aplicación
- Entrada del teclado
- Entrada del ratón
- Salida solicitada por el usuario
- Cada evento puede hablitarse o deshabilitarse con SDL_EventState().
- Los eventos se pasan a una función de filtro especificada por el usuario antes de ser mandados a la cola interna de eventos.
- Cola de eventos segura con hilos.
Pista:
Usa SDL_PeepEvents() para buscar un evento de un tipo determinado en la cola de eventos.Audio - Establecer la reproducción de sonido de 8 bits y 16 bits, mono o estéreo, con conversión opcional si el formato no está soportado por el hardware.
- El sonido se ejecuta independientemente en un hilo separado, rellenándose mediante una función de retrollamada.
- Diseñado para usar mezcladores software personalizados, pero el archivo de ejemplos contiene una librería completa de salida de sonido/música.
Pista:
Usa las funciones SDL_LockAudio() y SDL_UnlockAudio() para sincronizar el acceso a datos compartidos por la retrollamada de sonido y el resto del programa.CD-ROM audio - API completa de control de sonido de CD
Pista:
Si le pasas un manejador de CD-ROM NULL a las funciones de CD-ROM de la API, actuarán en el último CD-ROM que se abrió.Hilos - API de creación de hilos simple
- Semáforos binarios simples para sincronización
Pista:
No utilices funciones de la librería de C como E/S y manejo de memoria desde hilos si puedes evitarlo - estas bloquean recursos utilizados por otros hilos.Temporizadores - Obtener el número de milisegundos transcurridos
- Esperar un número especificado de milisegundos
- Establecer un único temporizador con una resolución de 10ms
Pista:
Puedes reemplazar fácilmente la funcion de Win32 GetTickCount() con SDL_GetTicks()Independencia del peso del sistema - Detectar el peso del sistema del sistema actual
- Rutinas para el intercambio rápido de valores de datos
- Lectura y escritura de datos de un peso especificado
- ¿Sobre qué plataformas se ejecuta?
- Linux
- Win32
- BeOS
- Portes no oficiales, portes en desarrollo
Linux
| Pista: Puedes obtener todas las porciones ocultas del interfaz del driver de SDL a través de la función SDL_GetWMInfo(). Esta te permite hacer cosas como eliminar las decoraciones de ventanas iconificar progamadamente la ventana. |
Win32
| Pista: Debes de realizar llamadas a las funciones de eventos de SDL periódicamente desde el hilo principal para bombear la cola de mensajes de Windows y mantener la respuesta de tu aplicación. |
BeOS
| Pista: Linux y BeOS soportan el indicador SDL_INIT_EVENTTHREAD el cual, cuando es pasado a SDL_Init(), hace que la cola de eventos se ejecute asíncronamente en otro hilo. Esto es útil para los cursores de color que responden incluso cuando la aplicación está ocupada. |
Portes no oficiales, portes en desarrollo
|
- Inicializando la librería
Usa SDL_Init() para cargar dinámicamente e inicializar la librería. Esta función toma un conjunto de indicadores correspondientes a las porciones que deseas activar: | Pista: SDL carga dinámicamente la librería SDL desde la localización estándar de las librerías del sistema. Usa la función SDL_SetLibraryPath() para usar una localización alternativa para las librerías dinámicas distribuidas con tu aplicación. |
Ejemplo: #include <stdlib.h> #include "SDL.h" main(int argc, char *argv[]) { if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 ) { fprintf(stderr, "No se puede iniciar SDL: %s\n", SDL_GetError()); exit(1); } atexit(SDL_Quit); ... } |
- Video
- Escogiendo y estableciendo módos de video (la manera fácil)
- Pintando píxels en la pantalla
- Cargando y mostrando imágenes
| Pista #1: Puedes encontrar los modos de vídeo más rápidos soportados por el hardware con la función SDL_GetVideoInfo(). Pista #2: Puedes obtener una lista de resoluciones de vídeo soportadas para una determinada profundidad de color usando la función SDL_ListModes(). |
Ejemplo:{ SDL_Surface *screen; screen = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE); if ( screen == NULL ) { fprintf(stderr, "No se puede establecer el modo \ de video 640x480: %s\n", SDL_GetError()); exit(1); } } |
| Pista: Si sabes que vas a realizar mucho dibujo, es mejor cerrar la pantalla (si es necesario) una vez antes de pintar, dibujar mientras mantienes una lista de areas que necesitan ser actualizadas, y abrir la pantalla de nuevo antes de actualizar el dispositivo de visualización. |
Ejemplo:
Dibujando un píxel en la pantalla de un formato arbitrariovoid DrawPixel(SDL_Surface *screen, Uint8 R, Uint8 G, Uint8 B) { Uint32 color = SDL_MapRGB(screen->format, R, G, B); if ( SDL_MUSTLOCK(screen) ) { if ( SDL_LockSurface(screen) < 0 ) { return; } } switch (screen->format->BytesPerPixel) { case 1: { /* Asumimos 8-bpp */ Uint8 *bufp; bufp = (Uint8 *)screen->pixels + y*screen->pitch + x; *bufp = color; } break; case 2: { /* Probablemente 15-bpp o 16-bpp */ Uint16 *bufp; bufp = (Uint16 *)screen->pixels + \ y*screen->pitch/2 + x; *bufp = color; } break; case 3: { /* Modo lento 24-bpp, normalmente no usado */ Uint8 *bufp; bufp = (Uint8 *)screen->pixels + y*screen->pitch + x; *(bufp+screen->format->Rshift/8) = R; *(bufp+screen->format->Gshift/8) = G; *(bufp+screen->format->Bshift/8) = B; } break; case 4: { /* Probablemente 32-bpp */ Uint32 *bufp; bufp = (Uint32 *)screen->pixels + \ y*screen->pitch/4 + x; *bufp = color; } break; } if ( SDL_MUSTLOCK(screen) ) { SDL_UnlockSurface(screen); } SDL_UpdateRect(screen, x, y, 1, 1); } |
| Pista #1: Si estás cargando una imagen que se va a mostrar varias veces, puedes acelerar la velocidad de volcado convirtiéndola al formato de la pantalla. La función SDL_DisplayFormat() realiza esta conversión por tí. Pista #2: Muchas imágenes de sprite tienen un fondo transparente. Puedes habilitar volcados transparentes (volcados con color clave) mediante la función SDL_SetColorKey(). |
Ejemplo: void ShowBMP(char *file, SDL_Surface *screen, int x, int y) { SDL_Surface *image; SDL_Rect dest; /* Cargamos el archivo BMP en la superficie */ image = SDL_LoadBMP(file); if ( image == NULL ) { fprintf(stderr, "No pude cargar %s: %s\n", file, SDL_GetError()); return; } /* Volcamos en la superficie de pantalla. Las superficies no deberían estar bloqueadas en este punto. */ dest.x = x; dest.y = y; dest.w = image->w; dest.h = image->h; SDL_BlitSurface(image, NULL, screen, &dest); /* Actualizamos la porcion de pantalla que ha cambiado */ SDL_UpdateRects(screen, 1, &dest); SDL_FreeSurface(image); } |
- Eventos
- Esperando eventos
- Consultando eventos
- Consultando el estado de eventos
Esperando eventos usando la función SDL_WaitEvent(). | Pista: SDL tiene soporte de teclados internacionales, traducción de eventos de teclado y ubicación de los equivalentes UNICODE en event.key.keysym.unicode. Ya que esto conlleva cierta sobrecarga de proceso, debe habilitarse usando la función SDL_EnableUNICODE(). |
Ejemplo: { SDL_Event event; SDL_WaitEvent(&event); switch (event.type) { case SDL_KEYDOWN: printf("¡Se pulsó la tecla %s!\n", SDL_GetKeyName(event.key.keysym.sym)); break; case SDL_QUIT: exit(0); } } |
Consultar eventos usando la función SDL_PollEvent(). | Pista: Puedes mirar los eventos de la cola de eventos sin necesidad de eliminarlos pasando la acción SDL_PEEKEVENT a la función SDL_PeepEvents(). |
Ejemplo: { SDL_Event event; while ( SDL_PollEvent(&event) ) { switch (event.type) { case SDL_MOUSEMOTION: printf("El ratón se movió \ desde %d,%d hasta (%d,%d)\n", \ event.motion.xrel, event.motion.yrel, \ event.motion.x, event.motion.y); break; case SDL_MOUSEBUTTONDOWN: printf("Botón de ratón %d pulsado \ en (%d,%d)\n", event.button.button, event.button.x, event.button.y); break; case SDL_QUIT: exit(0); } } } |
Además de manejar los eventos directamente, cada tipo de evento tiene una función que te permite consultar el estado de evento de la aplicación. Si utilizas esto exclusivamente, deberías ignorar todos los eventos de la función SDL_EventState(), y llamar a SDL_PumpEvents() periódicamente para actualizar el estado de evento de la aplicación. | Pista: Puedes ocultar o mostrar el cursor del ratón del sistema usando la función SDL_ShowCursor(). |
Ejemplo: { SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); } void CheckMouseHover(void) { int mouse_x, mouse_y; SDL_PumpEvents(); SDL_GetMouseState(&mouse_x, &mouse_y); if ( (mouse_x < 32) && (mouse_y < 32) ) { printf("¡Ratón en la esquina superior \ izquierda!\n"); } } |
- Sonido
- Abriendo el dispositivo de audio
- Cargando y reproduciendo sonidos
Necesitas tener una función de retrollamada que mezcle tus datos de sonido y lo coloque en el flujo de sonido. Tras esto, elije el formato de sonido que desees y la velocidad, y abre el dispositivo de sonido. | Pista: Si tu aplicación puede manejar diferentes formatos de audio, pasa un segundo puntero SDL_AudioSpec a la función SDL_OpenAudio() para obtener el formato real de sonido del hardware. Si dejas el segundo puntero como NULL, los datos de sonido serán convertidos al formato de sonido del hardware en tiempo de ejecución. |
Ejemplo: #include "SDL.h" #include "SDL_audio.h" { extern void mixaudio(void *unused, Uint8 *stream, int len); SDL_AudioSpec fmt; /* Establece sonido estéreo de 16 bits a 22Khz */ fmt.freq = 22050; fmt.format = AUDIO_S16; fmt.channels = 2; fmt.samples = 512; /* Un buen valor para juegos */ fmt.callback = mixaudio; fmt.userdata = NULL; /* ¡Abre el dispositivo de sonido y comienza a reproducir sonidos! */ if ( SDL_OpenAudio(&fmt, NULL) < 0 ) { fprintf(stderr, "¡No puedo abrir el sonido!: %s\n", SDL_GetError()); exit(1); } SDL_PauseAudio(0); ... SDL_CloseAudio(); } |
SDL facilita para tu conveniencia, una rútina única para cargar sonido, SDL_LoadWAV(). Tra cargar tus sonidos, deberías convertirlos al formato de audio de salida usando SDL_ConvertAudio(), y así hacerlo disponible a tu función de mezcla. | Pista: Las facilidades de sonido de SDL están diseñadas para un mezclador de sonido de software de bajo nivel. Una completa implementación de un mezclador de ejmplo está disponible bajo la licencia LGPL, y puede encontrarse en la sección de demos del archivo SDL. |
Ejemplo: #define NUM_SOUNDS 2 struct sample { Uint8 *data; Uint32 dpos; Uint32 dlen; } sounds[NUM_SOUNDS]; void mixaudio(void *unused, Uint8 *stream, int len) { int i; Uint32 amount; for ( i=0; i<NUM_SOUNDS; ++i ) { amount = (sounds[i].dlen-sounds[i].dpos); if ( amount > len ) { amount = len; } SDL_MixAudio(stream, &sounds[i].data[sounds[i].dpos], amount, SDL_MIX_MAXVOLUME); sounds[i].dpos += amount; } } void PlaySound(char *file) { int index; SDL_AudioSpec wave; Uint8 *data; Uint32 dlen; SDL_AudioCVT cvt; /* Busca una ranura de sonido vacía(o finalizada) */ for ( index=0; index<NUM_SOUNDS; ++index ) { if ( sounds[index].dpos == sounds[index].dlen ) { break; } } if ( index == NUM_SOUNDS ) return; /* Carga el archivo de sonido y lo convierte a estéreo 16 bits a 22kHz */ if ( SDL_LoadWAV(file, &wave, &data, &dlen) == NULL ) { fprintf(stderr, "No pude cargar %s: %s\n", file, SDL_GetError()); return; } SDL_BuildAudioCVT(&cvt, wave.format, wave.channels, wave.freq, AUDIO_S16, 2, 22050); cvt.buf = malloc(dlen*cvt.len_mult); memcpy(cvt.buf, data, dlen); cvt.len = dlen; SDL_ConvertAudio(&cvt); SDL_FreeWAV(data); /* Pone los datos de audio en la ranura (comienza la reproducción inmediatemente) */ if ( sounds[index].data ) { free(sounds[index].data); } SDL_LockAudio(); sounds[index].data = cvt.buf; sounds[index].dlen = cvt.len_cvt; sounds[index].dpos = 0; SDL_UnlockAudio(); } |
- CD-ROM de audio
- Abriendo una unidad de CD-ROM para su uso
- Reproduciendo el CD-ROM
Ejemplo: { SDL_CD *cdrom; if ( SDL_CDNumDrives() > 0 ) { cdrom = SDL_CDOpen(0); if ( cdrom == NULL ) { fprintf(stderr, "No puedo abrir el CD-ROM \ por defecto: %s\n" SDL_GetError()); return; } ... SDL_CDClose(cdrom); } } |
Las unidades de CD-ROM especifican el tiempo bien en formato MSF (mins/segs/marcos), bien directamente en marcos. Un marco es una unidad estándar de tiempo en el CD, que se corresponde con 1/75 segundos. SDL usa marcos en lugar del formato MSF cuando especifica longitudes de pisat y desplazamientos, pero puedes convertir entre ambos formatos usando las macros FRAMES_TO_MSF() y MSF_TO_FRAMES(). | Pista: Puedes determinar qué pistas son de audio y cuales son de datos mirando en cdrom->tracks[track].type, y comparando su valor con SDL_AUDIO_TRACK y SDL_DATA_TRACK. |
Ejemplo: void PlayTrack(SDL_CD *cdrom, int track) { if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) { SDL_CDPlayTracks(cdrom, track, 0, track+1, 0); } while ( SDL_CDStatus(cdrom) == CD_PLAYING ) { SDL_Delay(1000); } } |
- Hilos
- Crear un hilo simple
- Sincronizando el acceso a un recurso
La creación de un hilo es realizada pasando una función a SDL_CreateThread(). Cuando la función retorna, si lo hace con éxito, tu función estará ahora ejcutándose concurrentemente con el resto de tu aplización, en su propio contexto (pila, registros, etc) y será capaz de acceder a la memoria y manejadores de archivo usados por el resto de la aplicación. | Pista: El segundo argumento de SDL_CreateThread() se pasa como parámetro a la función del hilo. Puedes utilizar esto para pasarle valores en la pila, o simplemente un puntero a datos para que los use el hilo. |
Ejemplo: #include "SDL_thread.h" int global_data = 0; int thread_func(void *unused) { int last_value = 0; while ( global_data != -1 ) { if ( global_data != last_value ) { printf("Valores de datos cambiados a\n", global_data); last_value = global_data; } SDL_Delay(100); } printf("Saliendo del hilo\n"); return(0); } { SDL_Thread *thread; int i; thread = SDL_CreateThread(thread_func, NULL); if ( thread == NULL ) { fprintf(stderr, "No se puede crear el hilo: %s\n", SDL_GetError()); return; } for ( i=0; i<5; ++i ) { printf("Cambiando el valor a %d\n", i); global_data = i; SDL_Delay(1000); } printf("Indicando al hilo que finalice\n"); global_data = -1; SDL_WaitThread(thread, NULL); } |
Puedes prevenir que un recurso sea accedido por más de un hilo creando un mutex y encerrando el acceso a el recurso llamadas de cierre (SDL_mutexP()) y apertura (SDL_mutexV()). | Pista: Todos los datos que pueden ser accedidos por más de un hilo deberían protegerse con un mutex. |
Ejemplo: #include "SDL_thread.h" #include "SDL_mutex.h" int potty = 0; int gotta_go; int thread_func(void *data) { SDL_mutex *lock = (SDL_mutex *)data; int times_went; times_went = 0; while ( gotta_go ) { SDL_mutexP(lock); /* Cerramos el potty */ ++potty; printf("Hilo %d usando el potty\n", SDL_ThreadID()); if ( potty > 1 ) { printf("¡Oh oh, alguien más está usando el potty!\n"); } --potty; SDL_mutexV(lock); ++times_went; } printf("Yep\n"); return(times_went); } { const int progeny = 5; SDL_Thread *kids[progeny]; SDL_mutex *lock; int i, lots; /* Creamos un cierre de sincronización */ lock = SDL_CreateMutex(); gotta_go = 1; for ( i=0; i<progeny; ++i ) { kids[i] = SDL_CreateThread(thread_func, lock); } SDL_Delay(5*1000); SDL_mutexP(lock); printf("¿Acabó todo el mundo?\n"); gotta_go = 0; SDL_mutexV(lock); for ( i=0; i<progeny; ++i ) { SDL_WaitThread(kids[i], &lots); printf("El hilo %d uso el potty %d veces\n", i+1, lots); } SDL_DestroyMutex(lock); } |
- Temporizadores
- Obtener el tiempo actual, en milisegundos
- Esperar un determinado número de milisegundo
SDL_GetTicks() te dice cuantos milisegundos han pasado desde un punto arbitrario del pasado. | Pista: En general, cuando se implementa un juego, es mejor mover objetos en el juego basándose en el tiempo en lugar de la velocidad de marcos. Esto produce jugabilidad tanto en máquinas rápidas como en lentas. |
Ejemplo: #define TICK_INTERVAL 30 Uint32 TimeLeft(void) { static Uint32 next_time = 0; Uint32 now; now = SDL_GetTicks(); if ( next_time <= now ) { next_time = now+TICK_INTERVAL; return(0); } return(next_time-now); } |
SDL_Delay() te permite esperar un número determinado de milisegundos. | Pista: La mayor parte de los sistemas operativos tiene una porción de tiempo del planificador de 10 ms. Puedes utilizar SDL_Delay(1) como una manera de liberar la CPU para la porción de tiempo actual, permitiendo la ejecución de otros hilos. Esto es importante si tienes un hilo ejecutando un bucle muy estrecho pero deseas que los demás hilos (como el del sonido) sigan ejecutándose. |
Ejemplo: {
while ( game_running )
{
UpdateGameState();
SDL_Delay(TimeLeft());
}
}
|
- Independencia del peso del sistema
El preprocesador de C define el símbolo SDL_BYTEORDER como SDL_LIL_ENDIAN o SDL_BIG_ENDIAN, dependiendo del orden de los bytes del sistema actual. | Pista: Los sistemas x86 son de tipo byte menos significativo primero(little endian) y los sistemas PPC son de tipo byte más significativo primero(big endian). |
Ejemplo: #include "SDL_endian.h" #if SDL_BYTEORDER == SDL_LIL_ENDIAN #define SWAP16(X) (X) #define SWAP32(X) (X) #else#define SWAP16(X) SDL_Swap16(X) #define SWAP32(X) SDL_Swap32(X) #endif |
SDL facilita un conjunto de macros en SDL_endian.h, SDL_Swap16() y SDL_Swap32(), los cuales intercambian el peso de los datos por tí. Esisten asimismo macros que intercambian datos de un peso en particular al peso del sistema local. | Pista: Si simplemente necesitas conocer el orden de los byes, pero no usar todas las funciones de intercambio, incluye SDL_byteorder.h en lugar SDL_endian.h |
Ejemplo: #include "SDL_endian.h" void ReadScanline16(FILE *file, Uint16 *scanline, int length) { fread(scanline, length, sizeof(Uint16), file); if ( SDL_BYTEORDER == SDL_BIG_ENDIAN ) { int i; for ( i=length-1; i >= 0; --i ) scanline[i] = SDL_SwapLE16(scanline[i]); } } |