Basic Programs

Hello world

This is a simple Hello World program for the PSP.

Click on view source below to see the code and how to build it.

View source

main.c

#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>

// PSP_MODULE_INFO is required
PSP_MODULE_INFO("Hello World", 0, 1, 0);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);

int exit_callback(int arg1, int arg2, void *common) {
    sceKernelExitGame();
    return 0;
}

int callback_thread(SceSize args, void *argp) {
    int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
    sceKernelRegisterExitCallback(cbid);
    sceKernelSleepThreadCB();
    return 0;
}

int setup_callbacks(void) {
    int thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0);
    if(thid >= 0)
        sceKernelStartThread(thid, 0, 0);
    return thid;
}

int main(void)  {
    // Use above functions to make exiting possible
    setup_callbacks();
    
    // Print Hello World! on a debug screen on a loop
    pspDebugScreenInit();
    while(1) {
        pspDebugScreenSetXY(0, 0);
        pspDebugScreenPrintf("Hello World!");
        sceDisplayWaitVblankStart();
    }

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(hello)

add_executable(${PROJECT_NAME} main.c)

target_link_libraries(${PROJECT_NAME} PRIVATE
    pspdebug
    pspdisplay
    pspge
)

# Create an EBOOT.PBP file
create_pbp_file(
    TARGET ${PROJECT_NAME}
    ICON_PATH NULL
    BACKGROUND_PATH NULL
    PREVIEW_PATH NULL
    TITLE ${PROJECT_NAME}
    VERSION 01.00
)

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and the PSP can run it.

Drawing shapes

This is a simple square drawn on the PSP. It uses the native libgu library.

Click on view source below to see the code and how to build it.

View source

main.c

#include <pspkernel.h>
#include <pspgu.h>
#include <pspdisplay.h>

PSP_MODULE_INFO("shape", 0, 1, 0);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_VFPU | THREAD_ATTR_USER);

#define BUFFER_WIDTH 512
#define BUFFER_HEIGHT 272
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT BUFFER_HEIGHT

char list[0x20000] __attribute__((aligned(64)));
int running;

int exit_callback(int arg1, int arg2, void *common) {
    running = 0;
    return 0;
}

int callback_thread(SceSize args, void *argp) {
    int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
    sceKernelRegisterExitCallback(cbid);
    sceKernelSleepThreadCB();
    return 0;
}

int setup_callbacks(void) {
    int thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0);
    if(thid >= 0)
        sceKernelStartThread(thid, 0, 0);
    return thid;
}

void initGu(){
    sceGuInit();

    //Set up buffers
    sceGuStart(GU_DIRECT, list);
    sceGuDrawBuffer(GU_PSM_8888,(void*)0,BUFFER_WIDTH);
    sceGuDispBuffer(SCREEN_WIDTH,SCREEN_HEIGHT,(void*)0x88000,BUFFER_WIDTH);
    sceGuDepthBuffer((void*)0x110000,BUFFER_WIDTH);

    //Set up viewport
    sceGuOffset(2048 - (SCREEN_WIDTH / 2), 2048 - (SCREEN_HEIGHT / 2));
    sceGuViewport(2048, 2048, SCREEN_WIDTH, SCREEN_HEIGHT);
    sceGuEnable(GU_SCISSOR_TEST);
    sceGuScissor(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    //Set some stuff
    sceGuDepthRange(65535, 0); //Use the full buffer for depth testing - buffer is reversed order

    sceGuDepthFunc(GU_GEQUAL); //Depth buffer is reversed, so GEQUAL instead of LEQUAL
    sceGuEnable(GU_DEPTH_TEST); //Enable depth testing

    sceGuFinish();
    sceGuDisplay(GU_TRUE);
}

void endGu(){
    sceGuDisplay(GU_FALSE);
    sceGuTerm();
}

void startFrame(){
    sceGuStart(GU_DIRECT, list);
    sceGuClearColor(0xFFFFFFFF); // White background
    sceGuClear(GU_COLOR_BUFFER_BIT);
}

void endFrame(){
    sceGuFinish();
    sceGuSync(0, 0);
    sceDisplayWaitVblankStart();
    sceGuSwapBuffers();
}

typedef struct {
    unsigned short u, v;
    short x, y, z;
} Vertex;

void drawRect(float x, float y, float w, float h) {

    Vertex* vertices = (Vertex*)sceGuGetMemory(2 * sizeof(Vertex));

    vertices[0].x = x;
    vertices[0].y = y;

    vertices[1].x = x + w;
    vertices[1].y = y + h;

    sceGuColor(0xFF0000FF); // Red, colors are ABGR
    sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, 0, vertices);
}


int main() {
    // Make exiting with the home button possible
    setup_callbacks();

    // Setup the library used for rendering
    initGu();

    running = 1;
    while(running){
        startFrame();

        drawRect(216, 96, 34, 64);

        endFrame();
    }

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(shape)

add_executable(${PROJECT_NAME} main.c)

target_link_libraries(${PROJECT_NAME} PRIVATE
    pspgu
    pspge
    pspdisplay
)

# Create an EBOOT.PBP file
create_pbp_file(
    TARGET ${PROJECT_NAME}
    ICON_PATH NULL
    BACKGROUND_PATH NULL
    PREVIEW_PATH NULL
    TITLE ${PROJECT_NAME}
    VERSION 01.00
)

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and the PSP can run it.

More libgu examples can be found here.

Drawing Sprites

This is a sprite drawn on the PSP. It uses the native libgu library to draw and the stb_image library to load the image. stb_image supports a lot of image formats. For the Playstation Portable it is important that the width of images used is a power of 2. In this case 16, but 32, 64, 128 and 256 will also work. This limitation does not exist for the example with SDL2 below.

Click on view source below to see the code and how to build it.

View source

main.c

#include <pspkernel.h>
#include <pspctrl.h>
#include <pspdisplay.h>
#include <pspgu.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

PSP_MODULE_INFO("texture", 0, 1, 0);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_VFPU | THREAD_ATTR_USER);

#define BUFFER_WIDTH 512
#define BUFFER_HEIGHT 272
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT BUFFER_HEIGHT

typedef struct
{
    float u, v;
    uint32_t colour;
    float x, y, z;
} TextureVertex;

typedef struct
{
    int width, height;
    uint32_t * data;
} Texture;

char list[0x20000] __attribute__((aligned(64)));

void * fbp0;
void * fbp1;
Texture texture;
int running;

int exit_callback(int arg1, int arg2, void *common) {
    running = 0;
    return 0;
}

int callback_thread(SceSize args, void *argp) {
    int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
    sceKernelRegisterExitCallback(cbid);
    sceKernelSleepThreadCB();
    return 0;
}

int setup_callbacks(void) {
    int thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0);
    if(thid >= 0)
        sceKernelStartThread(thid, 0, 0);
    return thid;
}

void initGu(){
    sceGuInit();

    fbp0 = guGetStaticVramBuffer(BUFFER_WIDTH, BUFFER_HEIGHT, GU_PSM_8888);
    fbp1 = guGetStaticVramBuffer(BUFFER_WIDTH, BUFFER_HEIGHT, GU_PSM_8888);

    //Set up buffers
    sceGuStart(GU_DIRECT, list);
    sceGuDrawBuffer(GU_PSM_8888, fbp0, BUFFER_WIDTH);
    sceGuDispBuffer(SCREEN_WIDTH,SCREEN_HEIGHT,fbp1, BUFFER_WIDTH);
    
    // We do not care about the depth buffer in this example
    sceGuDepthBuffer(fbp0, 0); // Set depth buffer to a length of 0
    sceGuDisable(GU_DEPTH_TEST); // Disable depth testing

    //Set up viewport
    sceGuOffset(2048 - (SCREEN_WIDTH / 2), 2048 - (SCREEN_HEIGHT / 2));
    sceGuViewport(2048, 2048, SCREEN_WIDTH, SCREEN_HEIGHT);
    sceGuEnable(GU_SCISSOR_TEST);
    sceGuScissor(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

    // Start a new frame and enable the display
    sceGuFinish();
    sceGuDisplay(GU_TRUE);
}

void endGu(){
    sceGuDisplay(GU_FALSE);
    sceGuTerm();
}

void startFrame(){
    sceGuStart(GU_DIRECT, list);
    sceGuClearColor(0xFFFFFFFF); // White background
    sceGuClear(GU_COLOR_BUFFER_BIT);
}

void endFrame(){
    sceGuFinish();
    sceGuSync(0, 0);
    sceDisplayWaitVblankStart();
    sceGuSwapBuffers();
}

void drawTexture(float x, float y, float w, float h) {
    static TextureVertex vertices[2];

    vertices[0].u = 0.0f;
    vertices[0].v = 0.0f;
    vertices[0].colour = 0xFFFFFFFF;
    vertices[0].x = x;
    vertices[0].y = y;
    vertices[0].z = 0.0f;

    vertices[1].u = w;
    vertices[1].v = h;
    vertices[1].colour = 0xFFFFFFFF;
    vertices[1].x = x + w;
    vertices[1].y = y + h;
    vertices[1].z = 0.0f;

    sceGuTexMode(GU_PSM_8888, 0, 0, GU_FALSE);
    sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
    sceGuTexImage(0, texture.width, texture.height, texture.width, texture.data);

    sceGuEnable(GU_TEXTURE_2D); 
    sceGuDrawArray(GU_SPRITES, GU_COLOR_8888 | GU_TEXTURE_32BITF | GU_VERTEX_32BITF | GU_TRANSFORM_2D, 2, 0, vertices);
    sceGuDisable(GU_TEXTURE_2D);
}


int main() {
    initGu();

    texture.data = (uint32_t *) stbi_load("grass.png", &texture.width, &texture.height, NULL, 4);

    running = 1;
    while(running){
        startFrame();

        drawTexture(SCREEN_WIDTH / 2 - 8, SCREEN_HEIGHT / 2 - 8, 16, 16);

        endFrame();
    }

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(texture)

add_executable(${PROJECT_NAME} main.c)

target_link_libraries(${PROJECT_NAME} PRIVATE
    pspgu
    pspge
    pspdisplay
)

# Create an EBOOT.PBP file
create_pbp_file(
    TARGET ${PROJECT_NAME}
    ICON_PATH NULL
    BACKGROUND_PATH NULL
    PREVIEW_PATH NULL
    TITLE ${PROJECT_NAME}
    VERSION 01.00
)

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and add the grass image file, download it from here, to be able to run it on the PSP.

More libgu examples can be found here.

Input

This is a simple program to use the PSP’s input functions.

Click on view source below to see the code and how to build it.

View source

main.c

#include <pspkernel.h>
#include <pspdebug.h>
#include <pspctrl.h>
#include <stdlib.h>
#include <string.h>

PSP_MODULE_INFO("Controller", 0, 1, 1);

PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);

#define printf pspDebugScreenPrintf

int done = 0;

int exit_callback(int arg1, int arg2, void *common)
{
    done = 1;
    return 0;
}

int callback_thread(SceSize args, void *argp)
{
    int cbid = sceKernelCreateCallback("Exit Callback",
        exit_callback, NULL);
    sceKernelRegisterExitCallback(cbid);
    sceKernelSleepThreadCB();
    return 0;
}

int setup_callbacks(void)
{
    int thid = sceKernelCreateThread("update_thread",
        callback_thread, 0x11, 0xFA0, 0, 0);

    if(thid >= 0)
        sceKernelStartThread(thid, 0, 0);
    return thid;
}

int main(void)
{
    SceCtrlData pad;

    pspDebugScreenInit();
    setup_callbacks();

    sceCtrlSetSamplingCycle(0);
    sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);

    while (!done)
    {
        pspDebugScreenSetXY(0, 2);
        sceCtrlReadBufferPositive(&pad, 1);

        printf("Analog X = %3d, ", pad.Lx);
        printf("Analog Y = %3d \n", pad.Ly);

        if (pad.Buttons != 0)
        {
            if (pad.Buttons & PSP_CTRL_SQUARE)
            {
                printf("Square pressed! \n");
            }
            if (pad.Buttons & PSP_CTRL_TRIANGLE)
            {
                printf("Triangle pressed! \n");
            }
            if (pad.Buttons & PSP_CTRL_CIRCLE)
            {
                printf("Circle pressed! \n");
            }
            if (pad.Buttons & PSP_CTRL_CROSS)
            {
                printf("Cross pressed! \n");
            }

            if (pad.Buttons & PSP_CTRL_UP)
            {
                printf("Up direction pad pressed! \n");
            }
            if (pad.Buttons & PSP_CTRL_DOWN)
            {
                printf("Down direction pad pressed! \n");
            }
            if (pad.Buttons & PSP_CTRL_LEFT)
            {
                printf("Left direction pad pressed! \n");
            }
            if (pad.Buttons & PSP_CTRL_RIGHT)
            {
                printf("Right direction pad pressed! \n");
            }
        }
    }

    sceKernelExitGame();
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(controls)

add_executable(${PROJECT_NAME} main.c)

target_link_libraries(${PROJECT_NAME} PRIVATE
    pspdebug
    pspdisplay
    pspge
    pspctrl
)

# Create an EBOOT.PBP file
create_pbp_file(
    TARGET ${PROJECT_NAME}
    ICON_PATH NULL
    BACKGROUND_PATH NULL
    PREVIEW_PATH NULL
    TITLE ${PROJECT_NAME}
    VERSION 01.00
)

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and the PSP can run it.

Audio

This is a simple program to use the audio of the PSP with minimal effort. It uses native audio library.

Click on view source below to see the code and how to build it.

View source

main.c

#include <pspkernel.h>
#include <pspdebug.h>
#include <pspaudiolib.h>
#include <pspaudio.h>
#include <pspdisplay.h>
#include <pspctrl.h>

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>

PSP_MODULE_INFO("audio", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);

#define printf	pspDebugScreenPrintf

/* Exit callback */
int exitCallback(int arg1, int arg2, void *common) {
    sceKernelExitGame();
    return 0;
}

/* Callback thread */
int callbackThread(SceSize args, void *argp) {
    int cbid;

    cbid = sceKernelCreateCallback("Exit Callback", (void*) exitCallback, NULL);
    sceKernelRegisterExitCallback(cbid);
    sceKernelSleepThreadCB();

    return 0;
}

/* Sets up the callback thread and returns its thread id */
int setupCallbacks(void) {
    int thid = 0;

    thid = sceKernelCreateThread("update_thread", callbackThread, 0x11, 0xFA0, 0, 0);
    if (thid >= 0) {
        sceKernelStartThread(thid, 0, 0);
    }
    return thid;
}

/* Main code */

const float PI = 3.1415926535897932f;
const int sampleRate = 44100;
float frequency = 440.0f;
float currentTime = 0;
int function = 0;

typedef struct {
        short l, r;
} sample_t;

float currentFunction(const float time) {
    double x;
    float t = modf((time / (2 * PI)), &x);

    switch(function) {
        case 0: // SINE
            return sinf(time);
        case 1: // SQUARE
            if (t < 0.5f) {
                return -0.2f;
            } else {
                return 0.2f;
            }
        case 2: // TRIANGLE
            if (t < 0.5f) {
                return (t * 2.0f) - 0.5f;
            } else {
                return 0.5f - (t - 0.5f) * 2.0f;
            }
        default:
             return 0.0f;
    }
}

/* This function gets called by pspaudiolib every time the
   audio buffer needs to be filled. The sample format is
   16-bit, stereo. */
void audioCallback(void* buf, unsigned int length, void *userdata) {
    const float sampleLength = 1.0f / sampleRate;
    const float scaleFactor = SHRT_MAX - 1.0f;
    static float freq0 = 440.0f;
    sample_t* ubuf = (sample_t*) buf;
    int i;
    
    if (frequency != freq0) {
            currentTime *= (freq0 / frequency);
    }
    for (i = 0; i < length; i++) {
        short s = (short) (scaleFactor * currentFunction(2.0f * PI * frequency * currentTime));
        ubuf[i].l = s;
        ubuf[i].r = s;
        currentTime += sampleLength;
    }
    if (currentTime * frequency > 1.0f) {
        double d;
        currentTime = modf(currentTime * frequency, &d) / frequency;
    }

    freq0 = frequency;
}

/* Read the analog stick and adjust the frequency */
void controlFrequency(void) {
    static int oldButtons = 0;
    const int zones[6] = {30, 70, 100, 112, 125, 130};
    const float response[6] = {0.0f, 0.1f, 0.5f, 1.0f, 4.0f, 8.0f};
    const float minFreq = 32.0f;
    const float maxFreq = 7040.0f;
    SceCtrlData pad;
    float direction;
    int changedButtons;
    int i, v;

    sceCtrlReadBufferPositive(&pad, 1);

    v = pad.Ly - 128;
    if (v < 0) {
           direction = 1.0f;
        v = -v;
    } else {
        direction = -1.0f;
    }

    for (i = 0; i < 6; i++) {
        if (v < zones[i]) {
            frequency += (response[i] * direction);
            break;
        }
    }

    if (frequency < minFreq) {
        frequency = minFreq;
    } else if (frequency > maxFreq) {
        frequency = maxFreq;
    }

    changedButtons = pad.Buttons & (~oldButtons);
    if (changedButtons & PSP_CTRL_CROSS) {
        function++;
        if (function > 2) {
            function = 0;
        }
    }

    oldButtons = pad.Buttons;
}

int main(void) {
    pspDebugScreenInit();
    setupCallbacks();

    pspAudioInit();
    pspAudioSetChannelCallback(0, audioCallback, NULL);

    sceCtrlSetSamplingCycle(0);
    sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);

    printf("Press up and down to select frequency\nPress X to change function\n");
    
    while(1) {
        sceDisplayWaitVblankStart();
        pspDebugScreenSetXY(0,2);
        printf("freq = %.2f \n", frequency);

        switch(function) {
            case 0:
                printf("SINE WAVE. \n");
                break;
            case 1:
                  printf("SQUARE WAVE. \n");
                break;
            case 2:
                  printf("TRIANGLE WAVE. \n");
                break;
        }

        controlFrequency();
    }

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(audio)

add_executable(${PROJECT_NAME} main.c)

target_link_libraries(${PROJECT_NAME} PRIVATE
    pspdebug
    pspdisplay
    pspge
    pspctrl
    pspaudio
    pspaudiolib
    psputility
)

# Create an EBOOT.PBP file
create_pbp_file(
    TARGET ${PROJECT_NAME}
    ICON_PATH NULL
    BACKGROUND_PATH NULL
    PREVIEW_PATH NULL
    TITLE ${PROJECT_NAME}
    VERSION 01.00
)

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and the PSP can run it.

More audiolib examples can be found here.

Using SDL2

SDL2 is a library which handles system specific things like input, audio and window management for you. It can also be used to render shapes and images, just like the native libgu. This can be slower, but will result in code that is easier to read and can run on multiple platforms.

Despite this example adding an option to close by pressing the start button, the code is much shorter. It can even be build for Linux without any further modifications.

Click on view source below for the to see the code and how to build it.

View source

main.c

#include <SDL.h>

int main(int argc, char *argv[])
{
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);

    SDL_Window * window = SDL_CreateWindow(
        "window",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        480,
        272,
        0
    );

    SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    SDL_Rect square = {216, 96, 34, 64}; 

    int running = 1;
    SDL_Event event;
    while (running) { 
        // Process input
        if (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_QUIT:
                    // End the loop if the programs is being closed
                    running = 0;
                    break;
                case SDL_CONTROLLERDEVICEADDED:
                    // Connect a controller when it is connected
                    SDL_GameControllerOpen(event.cdevice.which);
                    break;
                case SDL_CONTROLLERBUTTONDOWN:
                    if(event.cbutton.button == SDL_CONTROLLER_BUTTON_START) {
                        // Close the program if start is pressed
                        running = 0;
                    }
                    break;
            }
        }

        // Clear the screen
        SDL_RenderClear(renderer);

        // Draw a red square
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        SDL_RenderFillRect(renderer, &square);

        // Draw everything on a white background
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        SDL_RenderPresent(renderer);
    }
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(sdl2)

add_executable(${PROJECT_NAME} main.c)

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)

target_include_directories(${PROJECT_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})

target_link_libraries(${PROJECT_NAME} PRIVATE
    ${SDL2_LIBRARIES}
)

if(PSP)
    create_pbp_file(
        TARGET ${PROJECT_NAME}
        ICON_PATH NULL
        BACKGROUND_PATH NULL
        PREVIEW_PATH NULL
        TITLE ${PROJECT_NAME}
        VERSION 01.00
    )
endif()

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP` file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and the PSP can run it.

If you have sdl2 dev package and a compiler installed this code will also build on Linux for Linux by running:

mkdir build && cd build
cmake ..
make

More documentation on SDL can be found here.

Using SDL2 Image

SDL2 image is a library which adds support for multiple image formats to SDL2. This example results in the same image as the sprite example using libgu above. Here the limitation of the image width being a power 2 does not apply.

Despite this example adding an option to close by pressing the start button, the code is much shorter. It can even be build for Linux without any further modifications.

Click on view source below to see the code and how to build it.

View source

main.c

#include <SDL.h>
#include <SDL_image.h>

int main(int argc, char *argv[])
{
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);

    // Enable png support for SDL2_image
    IMG_Init(IMG_INIT_PNG);

    SDL_Window * window = SDL_CreateWindow(
        "window",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        480,
        272,
        0
    );

    SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    // Load the texture
    SDL_Surface * pixels = IMG_Load("grass.png");
    SDL_Texture * sprite = SDL_CreateTextureFromSurface(renderer, pixels);
    SDL_FreeSurface(pixels);

    // Store the dimensions of the texture
    SDL_Rect sprite_rect;
    SDL_QueryTexture(sprite, NULL, NULL, &sprite_rect.w, &sprite_rect.h);

    // Set the position to draw to in the middle of the screen
    sprite_rect.x = 480/2 - sprite_rect.w/2;
    sprite_rect.y = 272/2 - sprite_rect.h/2;

    int running = 1;
    SDL_Event event;
    while (running) { 
        // Process input
        if (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_QUIT:
                    // End the loop if the programs is being closed
                    running = 0;
                    break;
                case SDL_CONTROLLERDEVICEADDED:
                    // Connect a controller when it is connected
                    SDL_GameControllerOpen(event.cdevice.which);
                    break;
                case SDL_CONTROLLERBUTTONDOWN:
                    if(event.cbutton.button == SDL_CONTROLLER_BUTTON_START) {
                        // Close the program if start is pressed
                        running = 0;
                    }
                    break;
            }
        }

        // Clear the screen
        SDL_RenderClear(renderer);

        // Draw the 'grass' sprite
        SDL_RenderCopy(renderer, sprite, NULL, &sprite_rect);

        // Draw everything on a white background
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        SDL_RenderPresent(renderer);
    }
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(sdl2-image)

add_executable(${PROJECT_NAME} main.c)

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)
pkg_search_module(SDL2_IMAGE REQUIRED SDL2_image)

target_include_directories(${PROJECT_NAME} PRIVATE
    ${SDL2_INCLUDE_DIRS}
    ${SDL2_IMAGE_INCLUDE_DIRS}
)

target_link_libraries(${PROJECT_NAME} PRIVATE
    ${SDL2_LIBRARIES}
    ${SDL2_IMAGE_LIBRARIES}
)

if(PSP)
    # Create an EBOOT.PBP file
    create_pbp_file(
        TARGET ${PROJECT_NAME}
        ICON_PATH NULL
        BACKGROUND_PATH NULL
        PREVIEW_PATH NULL
        TITLE ${PROJECT_NAME}
        VERSION 01.00
    )
endif()

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and add the grass image file, download it from here, to be able to run it on the PSP.

If you have sdl2 sdl2-image dev packages and a compiler installed this code will also build on Linux for Linux by running:

mkdir build && cd build
cmake ..
make

Documentation for SDL2_image can be found here.

Using SDL2 mixer

This is a simple program to use the SDL2_mixer library. It handle audio playback in multimedia applications and games. It supports various audio formats(MP3/OGG).

Click on view source below to see the code and how to build it.

View source

main.c

#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>

// Define MIN macro
#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))

// Define screen dimensions
#define SCREEN_WIDTH    480
#define SCREEN_HEIGHT   272

// audio file path
#define MUSIC_PATH "ms0:/MUSIC/test.ogg" // ogg/mp3 file format

int main(int argc, char **argv) {
    (void)argc;
    (void)argv;

    // Initialize sdl
    SDL_Init(SDL_INIT_VIDEO |
        SDL_INIT_AUDIO |
        SDL_INIT_GAMECONTROLLER
    );

    // Initialize sdl2_mixer
    Mix_OpenAudio(44100, 
        MIX_DEFAULT_FORMAT, 
        MIX_DEFAULT_CHANNELS, 
        2048
    );

    // create window
    SDL_Window *win = SDL_CreateWindow(
        "psp_win",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        SCREEN_WIDTH,
        SCREEN_HEIGHT,
        0
    );

    // Create Renderer
    SDL_Renderer *renderer = SDL_CreateRenderer(
        win, -1, 0
    );

    // Load ogg file
    Mix_Music *ogg_file = NULL;
    ogg_file = Mix_LoadMUS(MUSIC_PATH);
    if (!ogg_file) {
        return 0;
    }

    SDL_Rect rect;

    // Square dimensions: Half of the min(SCREEN_WIDTH, SCREEN_HEIGHT)
    rect.w = MIN(SCREEN_WIDTH, SCREEN_HEIGHT) / 2;
    rect.h = MIN(SCREEN_WIDTH, SCREEN_HEIGHT) / 2;

    // Square position: In the middle of the screen
    rect.x = SCREEN_WIDTH / 2 - rect.w / 2;
    rect.y = SCREEN_HEIGHT / 2 - rect.h / 2;


    // Declare rects of pause symbol
    SDL_Rect pause_rect1, pause_rect2;

    pause_rect1.h = rect.h / 2;
    pause_rect1.w = 40;
    pause_rect1.x = rect.x + (rect.w - pause_rect1.w * 3) / 2;
    pause_rect1.y = rect.y + rect.h / 4;
    pause_rect2 = pause_rect1;
    pause_rect2.x += pause_rect1.w * 2;
    
    // play the music 8 times
    if (Mix_PlayMusic(ogg_file, 8) == -1) {
        return 0;
    }

    int running = 1;
    SDL_Event e;
    while (running) {
        if(SDL_PollEvent(&e)) {
            switch(e.type) {
                case SDL_QUIT:
                    running = 0;
                break;
                case SDL_CONTROLLERDEVICEADDED:
                    SDL_GameControllerOpen(e.cdevice.which);
                break;
                case SDL_CONTROLLERBUTTONDOWN:
                    // pause using cross button
                    if (e.cbutton.button == SDL_CONTROLLER_BUTTON_A) {
                        Mix_PauseMusic();
                    // resume using circle button
                    } else if (e.cbutton.button == SDL_CONTROLLER_BUTTON_B) {
                        Mix_ResumeMusic();
                    }	
                    // press start button to exit
                    if (e.cbutton.button == SDL_CONTROLLER_BUTTON_START) {
                        running = 0;
                    }
            break;		
            }
        }

        // Initialize renderer color black for the background
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);

        // Clear screen
        SDL_RenderClear(renderer);

        // Set renderer color green to draw the square
        SDL_SetRenderDrawColor(renderer, 0, 0xFF, 0, 0xFF);

        // Draw filled square
        SDL_RenderFillRect(renderer, &rect);

        // Check pause status
        if(Mix_PausedMusic()) {
            // Set renderer color black to draw the pause symbol
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);

            // Draw pause symbol
            SDL_RenderFillRect(renderer, &pause_rect1);
             SDL_RenderFillRect(renderer, &pause_rect2);
        }

        // Update screen
        SDL_RenderPresent(renderer);
    }

    Mix_FreeMusic(ogg_file);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(win);
    Mix_CloseAudio();
    SDL_Quit();

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(sdl2_mixer)

add_executable(${PROJECT_NAME} main.c)

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)
pkg_search_module(SDL2_MIXER REQUIRED SDL2_mixer)

target_include_directories(${PROJECT_NAME} PRIVATE
    ${SDL2_INCLUDE_DIRS}
    ${SDL2_MIXER_INCLUDE_DIRS}
)

target_link_libraries(${PROJECT_NAME} PRIVATE
    ${SDL2_LIBRARIES}
    ${SDL2_MIXER_LIBRARIES}
)

if(PSP)
    create_pbp_file(
        TARGET ${PROJECT_NAME}
        ICON_PATH NULL
        BACKGROUND_PATH NULL
        PREVIEW_PATH NULL
        TITLE ${PROJECT_NAME}
        VERSION 01.00
    )
endif()

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and you need an audio file to test the program, download it from here. Put it in a directory in ms0:/MUSIC/ and then rename the audio file same as name on your MUSIC_PATH macro in your C code and the PSP can run it.

Documentation for SDL2_mixer can be found here.

Using SDL2 ttf

This is a simple program to use the SDL2_ttf library. It provides functionality for rendering TrueType fonts for your PSP.

Click on view source below to see the code and how to build it.

View source

main.c

#include <stdio.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

// Define screen dimensions
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 272

int main(int argc, char **argv)
{
    (void)argc;
    (void)argv;

    // Initialize SDL2
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        printf("SDL2 could not be initialized!\n"
               "SDL2 Error: %s\n",
               SDL_GetError());
        return 0;
    }

    // Initialize SDL2_ttf
    if (TTF_Init() < 0)
    {
        printf("SDL2_ttf could not be initialized!\n"
               "SDL2_ttf Error: %s\n",
               SDL_GetError());
        return 0;
    }

    SDL_Window *win = SDL_CreateWindow(
        "window",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        SCREEN_WIDTH,
        SCREEN_HEIGHT,
        0);

    if (!win)
    {
        printf("Window could not be created!\n"
               "SDL_Error: %s\n",
               SDL_GetError());
    }

    SDL_Renderer *renderer = SDL_CreateRenderer(win, -1, 0);
    TTF_Font *font = TTF_OpenFont("Pacifico.ttf", 40);

    // Set the text and background color
    SDL_Color text_color = {0xff, 0xff, 0xff, 0xff};
    SDL_Color bg_color = {0x00, 0x00, 0x00, 0xff};

    SDL_Rect text_rect;
    SDL_Surface *surface = TTF_RenderText(font, "Hello World!", text_color, bg_color);
    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);

    // Get text dimensions
    text_rect.w = surface->w;
    text_rect.h = surface->h;

    SDL_FreeSurface(surface);

    text_rect.x = (SCREEN_WIDTH - text_rect.w) / 2;
    text_rect.y = text_rect.h + 30;

    int running = 1;
    SDL_Event e;
    while (running)
    {
        if (SDL_PollEvent(&e))
        {
            switch (e.type)
            {
            case SDL_QUIT:
                running = 0;
                break;
            case SDL_CONTROLLERDEVICEADDED:
                SDL_GameControllerOpen(e.cdevice.which);
                break;
            case SDL_CONTROLLERBUTTONDOWN:
                if (e.cbutton.button == SDL_CONTROLLER_BUTTON_START)
                {
                    running = 0;
                }
                break;
            }
        }

        SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff);
        SDL_RenderClear(renderer);
        SDL_SetRenderDrawColor(renderer, 0xff, 0x00, 0x00, 0xff);
        SDL_RenderCopy(renderer, texture, NULL, &text_rect);
        SDL_RenderPresent(renderer);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(win);
    TTF_Quit();
    SDL_Quit();

    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(sdl2_ttf)

add_executable(${PROJECT_NAME} main.c)

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)
pkg_search_module(SDL2_TTF REQUIRED SDL2_ttf)

target_include_directories(${PROJECT_NAME} PRIVATE
    ${SDL2_INCLUDE_DIRS}
    ${SDL2_TTF_INCLUDE_DIRS}
)

target_link_libraries(${PROJECT_NAME} PRIVATE
    ${SDL2_LIBRARIES}
    ${SDL2_TTF_LIBRARIES}
)

if(PSP)
    create_pbp_file(
        TARGET ${PROJECT_NAME}
        ICON_PATH NULL
        BACKGROUND_PATH NULL
        PREVIEW_PATH NULL
        TITLE ${PROJECT_NAME}
        VERSION 01.00
    )
endif()

Building can be done with:

mkdir build && cd build
psp-cmake ..
make

This will result in an EBOOT.PBP file in the build directory. Put it in a directory in ms0:/PSP/GAME/ and you need a font file to test the program, download it from here. Put it in a directory same as EBOOT.PBP and the PSP can run it.

Documentation for SDL2_ttf can be found here.

More Examples

More examples on how to use specific functions offered in the PSPDEV toolchain can be found here. Additional documentation on these functions can be found here.

Debugging

When making changes to the example programs listed above, you might run into errors or even crashes. To learn how to figure out what went wrong, check out the debugging page.