This post is just for archive purpose. Please use nop90's SDL porting if possible
Introduction
As discussed last time, there is no good SDL 1.2 port for 3DS, so I am going to do that myself. GitHub Repo: https://github.com/nbzwt/SDL-1.2-N3DS
There have been multiple attempts of porting SDL to the 3DS. First port was done by xerpi, forked SDL2 repo 3 years ago, added 3DS support there. Based on the commit history it seems like there are lot of works going on there. However based on the code, it seems like it was never finished. Then the second port was done by nop90, with lot of codes reused from xerpi's port, along with his own modifications. It was partially working, but not to a point I could use. I am probably the third one, based off my work on top of nop90's code (though I have rewrote basically everything except the button handling).
Graphics
The first issue to be solved is the graphics driver. I don't need any hardware acceleration, and I am not going to bother doing any accelerated copies. There aren't much issues, configuring the gfx, disabling the double-buffering, and taking nSDL as a reference, I was able to get it working. I have also implemented 8-bit paletted color support. The only minor issue was that 3DS' framebuffer has vertical scanlines instead of horizontal, making copying a bit weird. But given the processor and RAM are pretty fast, that's not a big issue. Good enough for some simple games. (Code: https://github.com/nbzwt/SDL-1.2-N3DS/blob/master/SDL-1.2.15/src/video/n3ds/SDL\_n3dsvideo.c)
So the supported features are:
- Any video size supported, the video would be rendered centered on the screen. The default screen is the top screen. The drawing is unaccelerated and NOT double-buffered, thus make it possible to port simple old programs without concerning the Vblank or flush the buffer, etc.
- 32bpp(RGBA8888), 24bpp(RGB888), 16bpp(RGB565) and 8bpp(Palette) modes are supported.
- It will always render as SWSURFACE. SDL_FULLSCREEN is of no use, no scailing is supported.
- One could use only one screen and another as console output (optional).
Test code:
#include <3ds.h>
#include <stdio.h>
#include "SDL/SDL.h"
int main(int argc, char **argv)
{
SDL\_Init(SDL\_INIT\_VIDEO);
SDL\_Surface *screen;
SDL\_Surface *bitmap;
screen = SDL\_SetVideoMode(320, 240, 16, SDL\_SWSURFACE | SDL\_TOPSCR |
SDL\_CONSOLEBOTTOM);
Result rs = romfsInit();
if (rs)
printf("romfsInit: %08lx\n", rs);
else {
printf("romfs Initialization succeed.\n");
bitmap = SDL\_LoadBMP("romfs:/test.bmp");
if (bitmap == NULL)
printf("Open Bitmap failed!\n");
else
SDL\_BlitSurface(bitmap, NULL, screen, NULL);
}
printf("Should be bilted\n");
SDL\_Flip(screen);
printf("Should be flipped\n");
SDL\_Delay(2000);
SDL\_Quit();
return 0;
}
Results:
Timer
Not finished yet.
Multithreading
xerpi's code has some has some unfinished mutlithreading supporting code, but unfortunately there aren't anything I could use. But thanks to libctru, which has good support for multithreading, it was possible to finish the SDL_systhread.c based on the libctru/include/3ds/svc.h and libctru/include/3ds/thread.h. (Code:https://github.com/nbzwt/SDL-1.2-N3DS/tree/master/SDL-1.2.15/src/thread/n3ds)
However one thing to keep in mind is that the 3DS uses cooperative multithreading. Which means the running thread needs to give other thread time to run, otherwise it would never be switched out. One crude way to do a context switch would be using timer to do a delay. (Code: https://github.com/nbzwt/SDL-1.2-N3DS/blob/master/SDL-1.2.15/src/timer/n3ds/SDL\_systimer.c)
Test code:
#include <3ds.h>
#include <stdio.h>
#include "SDL/SDL.h"
#include "SDL/SDL\_thread.h"
static int testThread(void *ptr)
{
int cnt;
for (cnt = 0; cnt < 10; ++cnt) {
printf("Thread %d counter: %d\n", SDL\_ThreadID(), cnt);
SDL\_Delay(50);
}
return cnt;
}
int main(int argc, char **argv)
{
SDL\_Init(SDL\_INIT\_VIDEO | SDL\_INIT\_AUDIO);
SDL\_Surface *screen;
SDL\_Thread *thread1;
int threadReturnValue;
screen = SDL\_SetVideoMode(400, 240, 32, SDL\_SWSURFACE | SDL\_TOPSCR |
SDL\_CONSOLEBOTTOM); //Init console
thread1 = SDL\_CreateThread(testThread, (void *)NULL);
for (int i = 0; i < 5; i++) {
printf("Main thread\n");
SDL\_Delay(50);
}
if (thread1 == NULL) {
printf("\nSDL\_CreateThread failed: %s\n", SDL\_GetError());
} else {
SDL\_WaitThread(thread1, &threadReturnValue);
printf("\nThread returned value: %d", threadReturnValue);
}
SDL\_Delay(10000);
SDL\_Quit();
return 0;
}
Sound
I didn't intend to support the multithreading, but that's the pre-requisite for the audio support, so I had no choice. Note it is also possible to do interrupt driven audio if mutlithreading is not available.
There are 2 available libraries for audio, and I am using the ndsp library, as that seems to be the recommended one.
Test code:
#include <3ds.h>
#include <stdio.h>
#include "SDL/SDL.h"
#include "SDL/SDL\_thread.h"
static Uint8 *audio\_chunk;
static Uint32 audio\_len;
static Uint8 *audio\_pos;
void fill\_audio(void *udata, Uint8 *stream, int len)
{
/* Only play if we have data left */
if ( audio\_len == 0 )
return;
/* Mix as much data as possible */
len = ( len > audio\_len ? audio\_len : len );
SDL\_MixAudio(stream, audio\_pos, len, SDL\_MIX\_MAXVOLUME);
audio\_pos += len;
audio\_len -= len;
}
long fileLoad(const char * filename, unsigned char * * buffer) {
FILE * pFile;
long lSize;
pFile = fopen(filename, "rb");
if (pFile != NULL) {
fseek(pFile, 0, SEEK\_END);
lSize = ftell(pFile);
rewind(pFile);
*buffer = (unsigned char *)malloc(lSize);
if (buffer != NULL) {
return fread(*buffer, 1, lSize, pFile);
}
fclose(pFile);
}
return 0;
}
int main(int argc, char **argv)
{
SDL\_Init(SDL\_INIT\_VIDEO | SDL\_INIT\_AUDIO);
SDL\_Surface *screen;
SDL\_Surface *bitmap;
SDL\_Thread *thread1, *thread2;
int threadReturnValue;
screen = SDL\_SetVideoMode(400, 240, 32, SDL\_SWSURFACE | SDL\_TOPSCR |
SDL\_CONSOLEBOTTOM);
romfsInit();
audio\_len = fileLoad("romfs:/test.wav", &audio\_chunk);
audio\_pos = audio\_chunk;
printf("Ready to play\n");
SDL\_AudioSpec audioSpec;
audioSpec.freq = 22050;
audioSpec.format = AUDIO\_S16;
audioSpec.channels = 2;
audioSpec.samples = 2048;
audioSpec.callback = fill\_audio;
audioSpec.userdata = NULL;
if (SDL\_OpenAudio(&audioSpec, NULL) < 0) {
printf("Sound initialization failed.\n");
}
SDL\_PauseAudio(0);
while (audio\_len > 0) {
printf("Playing: %lu left \n", audio\_len);
SDL\_Delay(1000);
}
free(audio\_chunk);
SDL\_Delay(10000);
SDL\_Quit();
return 0;
}