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 <pspuser.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.11)
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 <pspuser.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();
}
// Stop rendering
endGu();
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
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 SDL below.
Click on view source below to see the code and how to build it.
View source
main.c
#include <pspuser.h>
#include <pspctrl.h>
#include <pspdisplay.h>
#include <pspgu.h>
#include <memory.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
PSP_MODULE_INFO("texture", 0, 1, 0);
PSP_MAIN_THREAD_ATTR(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;
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();
}
Texture * loadTexture(const char * filename) {
Texture * texture = (Texture *) calloc(1, sizeof(Texture));
texture->data = (uint32_t *) stbi_load(filename, &(texture->width), &(texture->height), NULL, STBI_rgb_alpha);
// Make sure the texture cache is reloaded
sceKernelDcacheWritebackInvalidateAll();
return texture;
}
void drawTexture(Texture * texture, 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() {
// Make exiting with the home button possible
setup_callbacks();
// Create a texture from a file
Texture * texture = loadTexture("grass.png");
// Start rendering
initGu();
running = 1;
while(running){
startFrame();
drawTexture(texture, SCREEN_WIDTH / 2 - texture->width / 2, SCREEN_HEIGHT / 2 - texture->height / 2, texture->width, texture->height);
endFrame();
}
// Stop rendering
endGu();
// Clean up
stbi_image_free(texture->data);
free(texture);
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
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 <pspuser.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.11)
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 <pspuser.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.11)
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 SDL

SDL 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 <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
int main(int argc, char *argv[]) {
// This prevents compiler warnings
// We don't actually need these variables, but they do need to be there so SDL_main works
(void)argc;
(void)argv;
// Initialize sdl
if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Window * window = NULL;
SDL_Renderer * renderer = NULL;
if (!SDL_CreateWindowAndRenderer("window", 480, 272, 0, &window, &renderer)) {
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
SDL_Quit();
return 2;
}
SDL_FRect square = {216, 96, 34, 64};
int running = 1;
SDL_Event event;
while (running) {
// Process input
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
// End the loop if the programs is being closed
running = 0;
break;
case SDL_EVENT_GAMEPAD_ADDED:
// Connect a controller when it is connected
SDL_OpenGamepad(event.cdevice.which);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
if(event.gbutton.button == SDL_GAMEPAD_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.11)
project(sdl)
add_executable(${PROJECT_NAME} main.c)
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
)
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 sdl 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 SDL for Drawing Sprites
![]()
SDL support loading BMP and PNG image files out of the box. 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 <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
int main(int argc, char *argv[]) {
// This prevents compiler warnings
// We don't actually need these variables, but they do need to be there so SDL_main works
(void)argc;
(void)argv;
// Initialize sdl
if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Window * window = NULL;
SDL_Renderer * renderer = NULL;
if (!SDL_CreateWindowAndRenderer("window", 480, 272, 0, &window, &renderer)) {
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
SDL_Quit();
return 2;
}
// Load the texture
SDL_Surface * pixels = SDL_LoadPNG("grass.png");
if (!pixels) {
SDL_Log("Couldn't load grass.png: %s", SDL_GetError());
SDL_Quit();
return 3;
}
SDL_Texture * sprite = SDL_CreateTextureFromSurface(renderer, pixels);
SDL_DestroySurface(pixels);
if (!sprite) {
SDL_Log("Couldn't create texture: %s", SDL_GetError());
SDL_Quit();
return 4;
}
// Store the dimensions of the texture
SDL_FRect sprite_rect;
SDL_GetTextureSize(sprite, &sprite_rect.w, &sprite_rect.h);
// Set the position to draw to in the middle of the screen
sprite_rect.x = 480.0f / 2.0f - sprite_rect.w / 2.0f;
sprite_rect.y = 272.0f / 2.0f - sprite_rect.h / 2.0f;
int running = 1;
SDL_Event event;
while (running) {
// Process input
if (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_QUIT:
// End the loop if the programs is being closed
running = 0;
break;
case SDL_EVENT_GAMEPAD_ADDED:
// Connect a controller when it is connected
SDL_OpenGamepad(event.cdevice.which);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
if(event.gbutton.button == SDL_GAMEPAD_BUTTON_START) {
// Close the program if start is pressed
running = 0;
}
break;
}
}
// Clear the screen
SDL_RenderClear(renderer);
// Draw the 'grass' sprite
SDL_RenderTexture(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.11)
project(sdl-sprite)
add_executable(${PROJECT_NAME} main.c)
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
)
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 the sdl dev package and a compiler installed this code will also build on Linux for Linux by running:
mkdir build && cd build
cmake ..
make
If you want support for more image formats than just PNG and BMP, you can use the SDL image library ins addition to SDL. This adds the IMG_Load function, which can be used in place of SDL_LoadPNG to load any image format. This would require adding an include and linking to SDL image. Documentation for SDL image can be found here.
More documentation on SDL can be found here.
Using SDL mixer

This is a simple program to use the SDL_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 <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_mixer/SDL_mixer.h>
// Define screen dimensions
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 272
int main(int argc, char **argv) {
// This prevents compiler warnings
// We don't actually need these variables, but they do need to be there so SDL_main works
(void)argc;
(void)argv;
// Initialize sdl
if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
return 1;
}
// Initialize SDL mixer
if(!MIX_Init()) {
SDL_Log("Couldn't initialize SDL mixer: %s", SDL_GetError());
SDL_Quit();
return 2;
}
// Initialise audio device
MIX_Mixer * mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
if (!mixer) {
SDL_Log("Couldn't open default audio device: %s", SDL_GetError());
MIX_Quit();
SDL_Quit();
return 3;
}
// Initialize channel to play music on, called a track in SDL mixer
MIX_Track * music_track = MIX_CreateTrack(mixer);
if (!music_track) {
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
MIX_DestroyMixer(mixer);
MIX_Quit();
SDL_Quit();
return 3;
}
SDL_Window * window = NULL;
SDL_Renderer * renderer = NULL;
if (!SDL_CreateWindowAndRenderer("window", 480, 272, 0, &window, &renderer)) {
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
MIX_DestroyMixer(mixer);
MIX_Quit();
SDL_Quit();
return 4;
}
// Load ogg file
MIX_Audio *ogg_file = MIX_LoadAudio(mixer, "test.ogg", false);
if (!ogg_file) {
SDL_Log("Couldn't load audio file: %s", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
MIX_DestroyMixer(mixer);
MIX_Quit();
SDL_Quit();
return 5;
}
SDL_FRect rect;
// Square dimensions: Half of the min(SCREEN_WIDTH, SCREEN_HEIGHT)
rect.w = SDL_min(SCREEN_WIDTH, SCREEN_HEIGHT) / 2.0f;
rect.h = SDL_min(SCREEN_WIDTH, SCREEN_HEIGHT) / 2.0f;
// 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_FRect pause_rect1, pause_rect2;
pause_rect1.h = rect.h / 2.0f;
pause_rect1.w = 40.0f;
pause_rect1.x = rect.x + (rect.w - pause_rect1.w * 3.0f) / 2.0f;
pause_rect1.y = rect.y + rect.h / 4.0f;
pause_rect2 = pause_rect1;
pause_rect2.x += pause_rect1.w * 2.0f;
// play the music 8 times
MIX_SetTrackAudio(music_track, ogg_file);
MIX_SetTrackGain(music_track, 1.0f);
if (!MIX_PlayTrack(music_track, 8)) {
SDL_Log("Couldn't play audio: %s", SDL_GetError());
MIX_DestroyAudio(ogg_file);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
MIX_DestroyMixer(mixer);
MIX_Quit();
SDL_Quit();
return 6;
}
int running = 1;
SDL_Event e;
while (running) {
if(SDL_PollEvent(&e)) {
switch(e.type) {
case SDL_EVENT_QUIT:
running = 0;
break;
case SDL_EVENT_GAMEPAD_ADDED:
SDL_OpenGamepad(e.cdevice.which);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
// pause using cross button
if (e.gbutton.button == SDL_GAMEPAD_BUTTON_SOUTH) {
MIX_PauseTrack(music_track);
// resume using circle button
} else if (e.gbutton.button == SDL_GAMEPAD_BUTTON_EAST) {
MIX_ResumeTrack(music_track);
// press start button to exit
} else if (e.gbutton.button == SDL_GAMEPAD_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_TrackPlaying(music_track)) {
// 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_DestroyAudio(ogg_file);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
MIX_DestroyMixer(mixer);
MIX_Quit();
SDL_Quit();
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(sdl-mixer)
add_executable(${PROJECT_NAME} main.c)
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
find_package(SDL3_mixer REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
SDL3_mixer::SDL3_mixer
)
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 add the test audio file, download it from here, to be able to run it on the PSP.
Documentation for SDL_mixer can be found here.
Using SDL ttf

This is a simple program to use the SDL_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 <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_ttf/SDL_ttf.h>
// Define screen dimensions
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 272
int main(int argc, char **argv) {
// This prevents compiler warnings
// We don't actually need these variables, but they do need to be there so SDL_main works
(void)argc;
(void)argv;
// Initialize sdl
if(!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) {
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
return 1;
}
// Initialize SDL ttf
if (!TTF_Init()) {
SDL_Log("Couldn't initialize SDL ttf: %s", SDL_GetError());
SDL_Quit();
return 2;
}
SDL_Window * window = NULL;
SDL_Renderer * renderer = NULL;
if (!SDL_CreateWindowAndRenderer("window", 480, 272, 0, &window, &renderer)) {
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
SDL_Quit();
return 3;
}
// Load the font file, make sure it is in the directory the eboot file is in
TTF_Font *font = TTF_OpenFont("Pacifico.ttf", 40);
if (!font) {
SDL_Log("Couldn't load font: %s", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 4;
}
// Set the text and background color
SDL_Color text_color = {0x00, 0x00, 0x00, 0xff};
SDL_Color bg_color = {0xff, 0xff, 0xff, 0xff};
// Create a texture using a string and SDL ttf
const char * text = "Hello World!";
SDL_FRect text_rect = {0, 0, 0, 0};
SDL_Surface *surface = TTF_RenderText_Solid(font, text, SDL_strlen(text), text_color);
if (!surface) {
SDL_Log("Couldn't render text: %s", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 5;
}
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
// Get text dimensions
text_rect.w = (float) surface->w;
text_rect.h = (float) surface->h;
SDL_DestroySurface(surface);
text_rect.x = (SCREEN_WIDTH - text_rect.w) / 2.0f;
text_rect.y = text_rect.h + 30.0f;
int running = 1;
SDL_Event e;
while (running) {
if (SDL_PollEvent(&e)) {
switch (e.type) {
case SDL_EVENT_QUIT: // Event that is passed when the user closes the program
running = 0;
break;
case SDL_EVENT_GAMEPAD_ADDED: // Activate any gamepad that is connected
SDL_OpenGamepad(e.cdevice.which);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
if (e.gbutton.button == SDL_GAMEPAD_BUTTON_START) {
running = 0;
}
break;
}
}
// Refresh the screen and redraw the text every frame
SDL_SetRenderDrawColor(renderer, bg_color.r, bg_color.g, bg_color.b, bg_color.a);
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, texture, NULL, &text_rect);
SDL_RenderPresent(renderer);
}
// Clean up
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(sdl-ttf)
add_executable(${PROJECT_NAME} main.c)
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
find_package(SDL3_ttf REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
SDL3_ttf::SDL3_ttf
)
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 SDL_ttf can be found here.
More Examples
More examples on how to use specific functions offered in the PSP SDK 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.