SDL 1.2 for 3DS Porting

2017-02-12

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:

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;
}