tunnel-runner

Pseudo 3D tunnel effect.
git clone git://git.amin.space/tunnel-runner.git
Log | Files | Refs | README | LICENSE

tunnel_runner.c (21256B)


      1 #include <assert.h>
      2 #include <inttypes.h>
      3 #include "SDL.h"
      4 #include <stdbool.h>
      5 #include <stdio.h>
      6 #include <stdint.h>
      7 #include <math.h>
      8 #include <time.h>
      9 
     10 #define TR_PI32 3.14159265359f
     11 
     12 #define TR_SCREEN_WIDTH 640
     13 #define TR_SCREEN_HEIGHT 480
     14 #define TR_TEX_WIDTH 256
     15 #define TR_TEX_HEIGHT 256
     16 #define TR_BYTES_PER_PIXEL 4
     17 #define TR_MAX_CONTROLLERS 4
     18 #define TR_MOVEMENT_SPEED 5
     19 #define TR_CONTROLLER_STICK_MAX 32770
     20 #define TR_CONTROLLER_STICK_MIN -32770
     21 
     22 // TODO: Should time be stored in a double of seconds?
     23 #define TR_SECOND 1000
     24 #define TR_FPS 60
     25 #define TR_MS_PER_FRAME (TR_SECOND / TR_FPS)
     26 #define TR_UPDATES_PER_SECOND 120
     27 #define TR_MS_PER_UPDATE (TR_SECOND / TR_UPDATES_PER_SECOND)
     28 
     29 #define TR_LOG_ERR(message, ...) fprintf(stderr, (message), ##__VA_ARGS__)
     30 
     31 #ifdef TR_LOGLEVEL_DEBUG
     32 #define TR_LOG_DBG(message, ...) printf((message), ##__VA_ARGS__)
     33 #else
     34 #define TR_LOG_DBG(message, ...)
     35 #endif
     36 
     37 #ifdef TR_LOGLEVEL_DEBUG_FRAME
     38 #define TR_LOG_FRM(message, ...) TR_LOG_DBG((message), ##__VA_ARGS__)
     39 #else
     40 #define TR_LOG_FRM(message, ...)
     41 #endif
     42 
     43 enum Color
     44 {
     45     COLOR_GREEN,
     46     COLOR_RED,
     47     COLOR_BLUE,
     48     COLOR_YELLOW,
     49     COLOR_MAGENTA,
     50     COLOR_CYAN,
     51     COLOR_WHITE
     52 };
     53 
     54 struct SDLOffscreenBuffer
     55 {
     56     // pixels are always 32-bits wide. Memory order: BB GG RR XX.
     57     SDL_Texture *texture;
     58     void *memory;
     59     uint32_t width;
     60     uint32_t height;
     61     int32_t pitch;
     62 };
     63 
     64 struct SDLWindowDimension
     65 {
     66     int32_t width;
     67     int32_t height;
     68 };
     69 
     70 struct TransformData
     71 {
     72     int32_t width;
     73     int32_t height;
     74     int32_t **distance_table;
     75     int32_t **angle_table;
     76     int32_t look_shift_x;
     77     int32_t look_shift_y;
     78 };
     79 
     80 static struct SDLOffscreenBuffer global_back_buffer;
     81 static SDL_GameController *controller_handles[TR_MAX_CONTROLLERS];
     82 static SDL_Haptic *rumble_handles[TR_MAX_CONTROLLERS];
     83 static struct TransformData transform;
     84 
     85 
     86 uint64_t
     87 get_current_time_ms(void)
     88 {
     89     return SDL_GetTicks64();
     90 }
     91 
     92 
     93 void
     94 render_texture(
     95         struct SDLOffscreenBuffer buffer,
     96         uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH],
     97         int32_t x_offset,
     98         int32_t y_offset,
     99         enum Color color_choice)
    100 {
    101     uint8_t *row = (uint8_t *)buffer.memory;
    102 
    103     for (uint32_t y = 0; y < buffer.height; ++y)
    104     {
    105         uint32_t *pixel = (uint32_t *)row;
    106         for (uint32_t x = 0; x < buffer.width; ++x)
    107         {
    108             uint8_t color = texture[
    109                 (uint32_t)(y + y_offset) % TR_TEX_HEIGHT
    110             ]
    111             [
    112                 (uint32_t)(x + x_offset) % TR_TEX_WIDTH
    113             ];
    114             uint32_t red = color << 16;
    115             uint32_t green = color << 8;
    116             uint32_t blue = color;
    117 
    118             switch(color_choice)
    119             {
    120                 case COLOR_GREEN:
    121                 {
    122                     *pixel++ = green;
    123                 } break;
    124                 case COLOR_RED:
    125                 {
    126                     *pixel++ = red;
    127                 } break;
    128                 case COLOR_BLUE:
    129                 {
    130                     *pixel++ = blue;
    131                 } break;
    132                 case COLOR_YELLOW:
    133                 {
    134                     *pixel++ = red | green;
    135                 } break;
    136                 case COLOR_MAGENTA:
    137                 {
    138                     *pixel++ = red | blue;
    139                 } break;
    140                 case COLOR_CYAN:
    141                 {
    142                     *pixel++ = blue | green;
    143                 } break;
    144                 case COLOR_WHITE:
    145                 {
    146                     *pixel++ = red | green | blue;
    147                 } break;
    148                 default:
    149                 {
    150                     TR_LOG_ERR("Invalid color enum value: %d\n", color_choice);
    151                     *pixel++ = red | green | blue;
    152                 } break;
    153             }
    154         }
    155 
    156         row += buffer.pitch;
    157     }
    158 }
    159 
    160 
    161 void
    162 render_tunnel(
    163         struct SDLOffscreenBuffer buffer,
    164         uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH],
    165         int32_t rotation_offset,
    166         int32_t translation_offset,
    167         enum Color color_choice)
    168 {
    169     uint8_t *row = (uint8_t *)buffer.memory;
    170 
    171     for (uint32_t y = 0; y < buffer.height; ++y)
    172     {
    173         uint32_t *pixel = (uint32_t *)row;
    174         for (uint32_t x = 0; x < buffer.width; ++x)
    175         {
    176             uint32_t texel_y = (uint32_t)(transform.distance_table[y + transform.look_shift_y][x + transform.look_shift_x] + translation_offset) % TR_TEX_HEIGHT;
    177             uint32_t texel_x = (uint32_t)(transform.angle_table[y + transform.look_shift_y][x + transform.look_shift_x] + rotation_offset) % TR_TEX_WIDTH;
    178 
    179             assert(texel_x >= 0 && texel_x < TR_TEX_WIDTH);
    180             assert(texel_y >= 0 && texel_y < TR_TEX_HEIGHT);
    181 
    182             uint8_t texel = texture[texel_y][texel_x];
    183 
    184             uint32_t red = texel << 16;
    185             uint32_t green = texel << 8;
    186             uint32_t blue = texel;
    187 
    188             switch(color_choice)
    189             {
    190                 case COLOR_GREEN:
    191                 {
    192                     *pixel++ = green;
    193                 } break;
    194                 case COLOR_RED:
    195                 {
    196                     *pixel++ = red;
    197                 } break;
    198                 case COLOR_BLUE:
    199                 {
    200                     *pixel++ = blue;
    201                 } break;
    202                 case COLOR_YELLOW:
    203                 {
    204                     *pixel++ = red | green;
    205                 } break;
    206                 case COLOR_MAGENTA:
    207                 {
    208                     *pixel++ = red | blue;
    209                 } break;
    210                 case COLOR_CYAN:
    211                 {
    212                     *pixel++ = blue | green;
    213                 } break;
    214                 case COLOR_WHITE:
    215                 {
    216                     *pixel++ = red | green | blue;
    217                 } break;
    218                 default:
    219                 {
    220                     *pixel++ = red | green | blue;
    221                     TR_LOG_ERR("Invalid color enum value: %d\n", color_choice);
    222                 } break;
    223             }
    224         }
    225         row += global_back_buffer.pitch;
    226     }
    227 }
    228 
    229 
    230 struct SDLWindowDimension
    231 sdl_get_window_dimension(SDL_Window *window)
    232 {
    233     int w = 0;
    234     int h = 0;
    235     SDL_GetWindowSize(window, &w, &h);
    236     struct SDLWindowDimension result = { .width = w, .height = h };
    237     return result;
    238 }
    239 
    240 
    241 void
    242 sdl_resize_texture(struct SDLOffscreenBuffer *buffer, SDL_Renderer *renderer, int32_t window_width, int32_t window_height)
    243 {
    244     if (buffer->memory)
    245     {
    246         free(buffer->memory);
    247     }
    248 
    249     if (buffer->texture)
    250     {
    251         SDL_DestroyTexture(buffer->texture);
    252     }
    253 
    254     if (transform.distance_table)
    255     {
    256         for (int32_t y = 0; y < transform.height; ++y)
    257         {
    258             free(transform.distance_table[y]);
    259         }
    260         free(transform.distance_table);
    261     }
    262     if (transform.angle_table)
    263     {
    264         for (int32_t y = 0; y < transform.height; ++y)
    265         {
    266             free(transform.angle_table[y]);
    267         }
    268         free(transform.angle_table);
    269     }
    270 
    271     buffer->texture = SDL_CreateTexture(
    272             renderer,
    273             SDL_PIXELFORMAT_ARGB8888,
    274             SDL_TEXTUREACCESS_STREAMING,
    275             window_width, window_height);
    276 
    277     buffer->width = window_width;
    278     buffer->height = window_height;
    279     buffer->pitch = window_width * TR_BYTES_PER_PIXEL;
    280 
    281     buffer->memory = malloc(window_width * window_height * TR_BYTES_PER_PIXEL);
    282 
    283     transform.width = 2 * window_width;
    284     transform.height = 2 * window_height;
    285     transform.look_shift_x = window_width / 2;
    286     transform.look_shift_y = window_height / 2;
    287     transform.distance_table = malloc(transform.height * sizeof(int32_t *));
    288     transform.angle_table = malloc(transform.height * sizeof(int32_t *));
    289 
    290     for (int32_t y = 0; y < transform.height; ++y)
    291     {
    292         transform.distance_table[y] = malloc(transform.width * sizeof(int32_t));
    293         transform.angle_table[y] = malloc(transform.width * sizeof(int32_t));
    294     }
    295 
    296     // Make distance and angle transformation tables
    297     for (int32_t y = 0; y < transform.height; ++y)
    298     {
    299         for (int32_t x = 0; x < transform.width; ++x)
    300         {
    301             int32_t dist_from_center_x = x - window_width;
    302             int32_t dist_from_center_y = y - window_height;
    303             float dist_from_center = sqrtf((float)(dist_from_center_x * dist_from_center_x + dist_from_center_y * dist_from_center_y));
    304             float angle_from_positive_x_axis = atan2f((float)dist_from_center_y, (float)dist_from_center_x) / TR_PI32;
    305 
    306             float ratio = 32.0f;
    307             transform.distance_table[y][x] = (int32_t)(ratio * TR_TEX_HEIGHT / dist_from_center) % TR_TEX_HEIGHT;
    308             transform.angle_table[y][x] = (int32_t)(0.5f * TR_TEX_WIDTH * angle_from_positive_x_axis);
    309         }
    310     }
    311 }
    312 
    313 
    314 void
    315 sdl_update_window(SDL_Renderer *renderer, struct SDLOffscreenBuffer buffer)
    316 {
    317     if (SDL_UpdateTexture(buffer.texture, 0, buffer.memory, buffer.pitch))
    318     {
    319         TR_LOG_ERR("SDL_UpdateTexture failed: %s\n", SDL_GetError());
    320     }
    321 
    322     SDL_RenderCopy(renderer, buffer.texture, 0, 0);
    323     SDL_RenderPresent(renderer);
    324 }
    325 
    326 
    327 bool
    328 handle_event(SDL_Event *event)
    329 {
    330     bool should_quit = false;
    331 
    332     switch(event->type)
    333     {
    334         case SDL_QUIT:
    335         {
    336             TR_LOG_DBG("SDL_QUIT\n");
    337             should_quit = true;
    338         } break;
    339 
    340         case SDL_WINDOWEVENT:
    341         {
    342             switch(event->window.event)
    343             {
    344                 case SDL_WINDOWEVENT_SIZE_CHANGED:
    345                 {
    346                     SDL_Window *window = SDL_GetWindowFromID(event->window.windowID);
    347                     SDL_Renderer *renderer = SDL_GetRenderer(window);
    348                     TR_LOG_DBG("SDL_WINDOWEVENT_SIZE_CHANGED (%d, %d)\n", event->window.data1, event->window.data2);
    349                     sdl_resize_texture(&global_back_buffer, renderer, event->window.data1, event->window.data2);
    350                 } break;
    351 
    352                 case SDL_WINDOWEVENT_FOCUS_GAINED:
    353                 {
    354                     TR_LOG_DBG("SDL_WINDOWEVENT_FOCUS_GAINED\n");
    355                 } break;
    356 
    357                 case SDL_WINDOWEVENT_EXPOSED:
    358                 {
    359                     SDL_Window *window = SDL_GetWindowFromID(event->window.windowID);
    360                     SDL_Renderer *renderer = SDL_GetRenderer(window);
    361                     sdl_update_window(renderer, global_back_buffer);
    362                 } break;
    363             }
    364         } break;
    365     }
    366     return should_quit ;
    367 }
    368 
    369 
    370 void
    371 sdl_open_game_controllers()
    372 {
    373     int32_t num_joysticks = SDL_NumJoysticks();
    374     for (int32_t controller_index = 0; controller_index < num_joysticks; ++controller_index)
    375     {
    376         if (!SDL_IsGameController(controller_index))
    377         {
    378             continue;
    379         }
    380 
    381         if (controller_index >= TR_MAX_CONTROLLERS)
    382         {
    383             break;
    384         }
    385 
    386         controller_handles[controller_index] = SDL_GameControllerOpen(controller_index);
    387         rumble_handles[controller_index] = SDL_HapticOpen(controller_index);
    388 
    389         if (rumble_handles[controller_index] && SDL_HapticRumbleInit(rumble_handles[controller_index]) != 0)
    390         {
    391             SDL_HapticClose(rumble_handles[controller_index]);
    392             rumble_handles[controller_index] = 0;
    393         }
    394     }
    395 }
    396 
    397 
    398 void
    399 sdl_close_game_controllers()
    400 {
    401     for (int32_t controller_index = 0; controller_index < TR_MAX_CONTROLLERS; ++controller_index)
    402     {
    403         if (controller_handles[controller_index])
    404         {
    405             SDL_GameControllerClose(controller_handles[controller_index]);
    406         }
    407     }
    408 }
    409 
    410 
    411 void
    412 sdl_cleanup(void)
    413 {
    414     TR_LOG_DBG("Cleaning up...\n");
    415     sdl_close_game_controllers();
    416     SDL_Quit();
    417 }
    418 
    419 
    420 int
    421 main(int argc, char *argv[])
    422 {
    423     (void)argc;
    424     (void)argv;
    425     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) != 0)
    426     {
    427         TR_LOG_ERR("SDL_Init failed: %s\n", SDL_GetError());
    428         exit(EXIT_FAILURE);
    429     }
    430 
    431     sdl_open_game_controllers();
    432 
    433     // We register this function only _after_ we init SDL and open the game
    434     // controllers!
    435     atexit(sdl_cleanup);
    436 
    437     SDL_Window *window = SDL_CreateWindow(
    438             "Tunnel Runner",
    439             SDL_WINDOWPOS_UNDEFINED,
    440             SDL_WINDOWPOS_UNDEFINED,
    441             TR_SCREEN_WIDTH,
    442             TR_SCREEN_HEIGHT,
    443             0);
    444 
    445     if (window)
    446     {
    447         SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
    448 
    449         if (renderer)
    450         {
    451             struct SDLWindowDimension dimension = sdl_get_window_dimension(window);
    452             sdl_resize_texture(&global_back_buffer, renderer, dimension.width, dimension.height);
    453 
    454             uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH];
    455 
    456             for (int32_t y = 0; y < TR_TEX_HEIGHT; ++y)
    457             {
    458                 for (int32_t x = 0; x < TR_TEX_WIDTH; ++x)
    459                 {
    460                     // XOR texture:
    461                     texture[y][x] = (x * 256 / TR_TEX_WIDTH) ^ (y * 256 / TR_TEX_HEIGHT);
    462                     // Mosaic texture:
    463                     //texture[y][x] = (x * x * y * y);
    464                 }
    465             }
    466 
    467             bool running = true;
    468             int32_t rotation_offset = 0;
    469             int32_t translation_offset = 0;
    470             enum Color color_choice = COLOR_WHITE;
    471 
    472             uint64_t lag = 0;
    473             uint64_t previous_ms = get_current_time_ms();
    474 
    475             while (running)
    476             {
    477                 uint64_t current_ms = get_current_time_ms();
    478                 uint64_t elapsed_ms = current_ms - previous_ms;
    479                 previous_ms = current_ms;
    480                 lag += elapsed_ms;
    481                 TR_LOG_FRM("Lag: %d\n", lag);
    482 
    483                 TR_LOG_FRM("%" PRIu64 ", %f\n", lag, TR_MS_PER_UPDATE);
    484                 // TODO: I don't think we need determinism
    485                 while (lag >= TR_MS_PER_UPDATE)
    486                 {
    487                     SDL_Event event;
    488 
    489                     while (SDL_PollEvent(&event))
    490                     {
    491                         running = !handle_event(&event);
    492                     }
    493 
    494                     SDL_PumpEvents();
    495 
    496                     dimension = sdl_get_window_dimension(window);
    497 
    498                     const uint8_t *keystate = SDL_GetKeyboardState(0);
    499 
    500                     if (keystate[SDL_SCANCODE_A])
    501                     {
    502                         rotation_offset -= TR_MOVEMENT_SPEED;
    503                     }
    504                     if (keystate[SDL_SCANCODE_D])
    505                     {
    506                         rotation_offset += TR_MOVEMENT_SPEED;
    507                     }
    508                     if (keystate[SDL_SCANCODE_W])
    509                     {
    510                         translation_offset += TR_MOVEMENT_SPEED;
    511                     }
    512                     if (keystate[SDL_SCANCODE_S])
    513                     {
    514                         translation_offset -= TR_MOVEMENT_SPEED;
    515                     }
    516                     if (keystate[SDL_SCANCODE_LEFT])
    517                     {
    518                         rotation_offset --;
    519                     }
    520                     if (keystate[SDL_SCANCODE_RIGHT])
    521                     {
    522                         rotation_offset ++;
    523                     }
    524                     if (keystate[SDL_SCANCODE_UP])
    525                     {
    526                         translation_offset ++;
    527                     }
    528                     if (keystate[SDL_SCANCODE_DOWN])
    529                     {
    530                         translation_offset --;
    531                     }
    532 
    533 
    534                     for (int32_t controller_index = 0; controller_index < TR_MAX_CONTROLLERS; ++controller_index)
    535                     {
    536                         if (SDL_GameControllerGetAttached(controller_handles[controller_index]))
    537                         {
    538                             bool start = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_START);
    539                             bool back = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_BACK);
    540                             bool a_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_A);
    541                             bool b_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_B);
    542                             bool x_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_X);
    543                             bool y_button = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_Y);
    544                             bool left_shoulder = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
    545                             bool right_shoulder = SDL_GameControllerGetButton(controller_handles[controller_index], SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
    546 
    547                             int16_t stick_leftx = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_LEFTX);
    548                             int16_t stick_lefty = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_LEFTY);
    549                             int16_t stick_rightx = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_RIGHTX);
    550                             int16_t stick_righty = SDL_GameControllerGetAxis(controller_handles[controller_index], SDL_CONTROLLER_AXIS_RIGHTY);
    551 
    552                             if (start)
    553                             {
    554                                 SDL_HapticRumblePlay(rumble_handles[controller_index], 0.5f, 2000);
    555                                 color_choice = '\0';
    556                             }
    557                             else
    558                             {
    559                                 SDL_HapticRumbleStop(rumble_handles[controller_index]);
    560                             }
    561 
    562                             if (back)
    563                             {
    564                                 running = false;
    565                             }
    566 
    567                             // NOTE: Buttons select colors.
    568                             if (a_button)
    569                             {
    570                                 color_choice = COLOR_GREEN;
    571                             }
    572                             if (b_button)
    573                             {
    574                                 color_choice = COLOR_RED;
    575                             }
    576                             if (x_button)
    577                             {
    578                                 color_choice = COLOR_BLUE;
    579                             }
    580                             if (y_button)
    581                             {
    582                                 color_choice = COLOR_YELLOW;
    583                             }
    584                             if (left_shoulder)
    585                             {
    586                                 color_choice = COLOR_MAGENTA;
    587                             }
    588                             if (right_shoulder)
    589                             {
    590                                 color_choice = COLOR_CYAN;
    591                             }
    592 
    593                             rotation_offset += stick_leftx / 5000;
    594                             translation_offset -= stick_lefty / 5000;
    595 
    596                             int32_t dampened_x_max = dimension.width / 2;
    597                             int32_t dampened_x_min = -(dimension.width / 2);
    598                             int32_t dampened_y_max = dimension.height / 2;
    599                             int32_t dampened_y_min = -(dimension.height / 2);
    600 
    601                             int32_t dampened_x = (stick_rightx - TR_CONTROLLER_STICK_MIN) * (dampened_x_max - dampened_x_min) / (TR_CONTROLLER_STICK_MAX - TR_CONTROLLER_STICK_MIN) + dampened_x_min;
    602                             int32_t dampened_y = (stick_righty - TR_CONTROLLER_STICK_MIN) * (dampened_y_max - dampened_y_min) / (TR_CONTROLLER_STICK_MAX - TR_CONTROLLER_STICK_MIN) + dampened_y_min;
    603 
    604                             transform.look_shift_x = dimension.width / 2 + dampened_x;
    605                             transform.look_shift_y = dimension.height / 2 + dampened_y;
    606                             TR_LOG_FRM("dimension.width / 2: %d\t damp_x: %d\t raw_x: %d\n", dimension.width / 2, dampened_x, stick_rightx);
    607                             TR_LOG_FRM("dimension.height / 2: %d\t damp_y: %d\t raw_y: %d\n", dimension.height / 2, dampened_y, stick_righty);
    608                         }
    609                     }
    610                     TR_LOG_FRM("%d, %d\n", translation_offset, rotation_offset);
    611 
    612                     TR_LOG_FRM("\t%" PRIu64 ", %f\n", lag, TR_MS_PER_UPDATE);
    613                     //render_tunnel(global_back_buffer, texture, rotation_offset, translation_offset, color_choice);
    614                     lag -= TR_MS_PER_UPDATE;
    615                 }
    616                 render_tunnel(global_back_buffer, texture, rotation_offset, translation_offset, color_choice);
    617                 //render_texture(global_back_buffer, texture, rotation_offset, translation_offset, color_choice);
    618                 sdl_update_window(renderer, global_back_buffer);
    619                 if (elapsed_ms <= TR_MS_PER_FRAME)
    620                 {
    621                     SDL_Delay((TR_MS_PER_FRAME - elapsed_ms));
    622                 }
    623             }
    624         }
    625         else
    626         {
    627             TR_LOG_ERR("SDL_CreateRenderer failed: %s\n", SDL_GetError());
    628             exit(EXIT_FAILURE);
    629         }
    630     }
    631     else
    632     {
    633         TR_LOG_ERR("SDL_CreateWindow failed: %s\n", SDL_GetError());
    634         exit(EXIT_FAILURE);
    635     }
    636 
    637     return 0;
    638 }