tunnel-runner

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

tunnel_runner.c (21348B)


      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 <time.h>
      8 
      9 #define TR_PI32 3.14159265359f
     10 
     11 #define TR_SCREEN_WIDTH 640
     12 #define TR_SCREEN_HEIGHT 480
     13 #define TR_TEX_WIDTH 256
     14 #define TR_TEX_HEIGHT 256
     15 #define TR_BYTES_PER_PIXEL 4
     16 #define TR_MAX_CONTROLLERS 4
     17 #define TR_MOVEMENT_SPEED 5
     18 #define TR_CONTROLLER_STICK_MAX 32770
     19 #define TR_CONTROLLER_STICK_MIN -32770
     20 
     21 // TODO: Should time be stored in a double of seconds?
     22 #define TR_SECOND 1000
     23 #define TR_FPS 60
     24 #define TR_MS_PER_FRAME (TR_SECOND / TR_FPS)
     25 #define TR_UPDATES_PER_SECOND 120
     26 #define TR_MS_PER_UPDATE (TR_SECOND / TR_UPDATES_PER_SECOND)
     27 
     28 #define TR_LOG_ERR(message, ...) fprintf(stderr, (message), ##__VA_ARGS__)
     29 
     30 #ifdef TR_LOGLEVEL_DEBUG
     31 #define TR_LOG_DBG(message, ...) printf((message), ##__VA_ARGS__)
     32 #else
     33 #define TR_LOG_DBG(message, ...)
     34 #endif
     35 
     36 #ifdef TR_LOGLEVEL_DEBUG_FRAME
     37 #define TR_LOG_FRM(message, ...) TR_LOG_DBG((message), ##__VA_ARGS__)
     38 #else
     39 #define TR_LOG_FRM(message, ...)
     40 #endif
     41 
     42 enum Color
     43 {
     44     COLOR_GREEN,
     45     COLOR_RED,
     46     COLOR_BLUE,
     47     COLOR_YELLOW,
     48     COLOR_MAGENTA,
     49     COLOR_CYAN,
     50     COLOR_WHITE
     51 };
     52 
     53 struct SDLOffscreenBuffer
     54 {
     55     // pixels are always 32-bits wide. Memory order: BB GG RR XX.
     56     SDL_Texture *texture;
     57     void *memory;
     58     uint32_t width;
     59     uint32_t height;
     60     int32_t pitch;
     61 };
     62 
     63 struct SDLWindowDimension
     64 {
     65     int32_t width;
     66     int32_t height;
     67 };
     68 
     69 struct TransformData
     70 {
     71     int32_t width;
     72     int32_t height;
     73     int32_t **distance_table;
     74     int32_t **angle_table;
     75     int32_t look_shift_x;
     76     int32_t look_shift_y;
     77 };
     78 
     79 static struct SDLOffscreenBuffer global_back_buffer;
     80 static SDL_GameController *controller_handles[TR_MAX_CONTROLLERS];
     81 static SDL_Haptic *rumble_handles[TR_MAX_CONTROLLERS];
     82 static struct TransformData transform;
     83 
     84 
     85 uint64_t
     86 get_current_time_ms(void)
     87 {
     88     struct timespec current;
     89     clock_gettime(CLOCK_MONOTONIC, &current);
     90     uint64_t milliseconds = ((current.tv_sec * 1000000000) + current.tv_nsec) / 1000000;
     91     return milliseconds;
     92 }
     93 
     94 
     95 void
     96 render_texture(
     97         struct SDLOffscreenBuffer buffer,
     98         uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH],
     99         int32_t x_offset,
    100         int32_t y_offset,
    101         enum Color color_choice)
    102 {
    103     uint8_t *row = (uint8_t *)buffer.memory;
    104 
    105     for (uint32_t y = 0; y < buffer.height; ++y)
    106     {
    107         uint32_t *pixel = (uint32_t *)row;
    108         for (uint32_t x = 0; x < buffer.width; ++x)
    109         {
    110             uint8_t color = texture[
    111                 (uint32_t)(y + y_offset) % TR_TEX_HEIGHT
    112             ]
    113             [
    114                 (uint32_t)(x + x_offset) % TR_TEX_WIDTH
    115             ];
    116             uint32_t red = color << 16;
    117             uint32_t green = color << 8;
    118             uint32_t blue = color;
    119 
    120             switch(color_choice)
    121             {
    122                 case COLOR_GREEN:
    123                 {
    124                     *pixel++ = green;
    125                 } break;
    126                 case COLOR_RED:
    127                 {
    128                     *pixel++ = red;
    129                 } break;
    130                 case COLOR_BLUE:
    131                 {
    132                     *pixel++ = blue;
    133                 } break;
    134                 case COLOR_YELLOW:
    135                 {
    136                     *pixel++ = red | green;
    137                 } break;
    138                 case COLOR_MAGENTA:
    139                 {
    140                     *pixel++ = red | blue;
    141                 } break;
    142                 case COLOR_CYAN:
    143                 {
    144                     *pixel++ = blue | green;
    145                 } break;
    146                 case COLOR_WHITE:
    147                 {
    148                     *pixel++ = red | green | blue;
    149                 } break;
    150                 default:
    151                 {
    152                     TR_LOG_ERR("Invalid color enum value: %d\n", color_choice);
    153                     *pixel++ = red | green | blue;
    154                 } break;
    155             }
    156         }
    157 
    158         row += buffer.pitch;
    159     }
    160 }
    161 
    162 
    163 void
    164 render_tunnel(
    165         struct SDLOffscreenBuffer buffer,
    166         uint32_t texture[TR_TEX_HEIGHT][TR_TEX_WIDTH],
    167         int32_t rotation_offset,
    168         int32_t translation_offset,
    169         enum Color color_choice)
    170 {
    171     uint8_t *row = (uint8_t *)buffer.memory;
    172 
    173     for (uint32_t y = 0; y < buffer.height; ++y)
    174     {
    175         uint32_t *pixel = (uint32_t *)row;
    176         for (uint32_t x = 0; x < buffer.width; ++x)
    177         {
    178             uint32_t texel_y = (uint32_t)(transform.distance_table[y + transform.look_shift_y][x + transform.look_shift_x] + translation_offset) % TR_TEX_HEIGHT;
    179             uint32_t texel_x = (uint32_t)(transform.angle_table[y + transform.look_shift_y][x + transform.look_shift_x] + rotation_offset) % TR_TEX_WIDTH;
    180 
    181             assert(texel_x >= 0 && texel_x < TR_TEX_WIDTH);
    182             assert(texel_y >= 0 && texel_y < TR_TEX_HEIGHT);
    183 
    184             uint8_t texel = texture[texel_y][texel_x];
    185 
    186             uint32_t red = texel << 16;
    187             uint32_t green = texel << 8;
    188             uint32_t blue = texel;
    189 
    190             switch(color_choice)
    191             {
    192                 case COLOR_GREEN:
    193                 {
    194                     *pixel++ = green;
    195                 } break;
    196                 case COLOR_RED:
    197                 {
    198                     *pixel++ = red;
    199                 } break;
    200                 case COLOR_BLUE:
    201                 {
    202                     *pixel++ = blue;
    203                 } break;
    204                 case COLOR_YELLOW:
    205                 {
    206                     *pixel++ = red | green;
    207                 } break;
    208                 case COLOR_MAGENTA:
    209                 {
    210                     *pixel++ = red | blue;
    211                 } break;
    212                 case COLOR_CYAN:
    213                 {
    214                     *pixel++ = blue | green;
    215                 } break;
    216                 case COLOR_WHITE:
    217                 {
    218                     *pixel++ = red | green | blue;
    219                 } break;
    220                 default:
    221                 {
    222                     *pixel++ = red | green | blue;
    223                     TR_LOG_ERR("Invalid color enum value: %d\n", color_choice);
    224                 } break;
    225             }
    226         }
    227         row += global_back_buffer.pitch;
    228     }
    229 }
    230 
    231 
    232 struct SDLWindowDimension
    233 sdl_get_window_dimension(SDL_Window *window)
    234 {
    235     int w = 0;
    236     int h = 0;
    237     SDL_GetWindowSize(window, &w, &h);
    238     struct SDLWindowDimension result = { .width = w, .height = h };
    239     return result;
    240 }
    241 
    242 
    243 void
    244 sdl_resize_texture(struct SDLOffscreenBuffer *buffer, SDL_Renderer *renderer, int32_t window_width, int32_t window_height)
    245 {
    246     if (buffer->memory)
    247     {
    248         free(buffer->memory);
    249     }
    250 
    251     if (buffer->texture)
    252     {
    253         SDL_DestroyTexture(buffer->texture);
    254     }
    255 
    256     if (transform.distance_table)
    257     {
    258         for (int32_t y = 0; y < transform.height; ++y)
    259         {
    260             free(transform.distance_table[y]);
    261         }
    262         free(transform.distance_table);
    263     }
    264     if (transform.angle_table)
    265     {
    266         for (int32_t y = 0; y < transform.height; ++y)
    267         {
    268             free(transform.angle_table[y]);
    269         }
    270         free(transform.angle_table);
    271     }
    272 
    273     buffer->texture = SDL_CreateTexture(
    274             renderer,
    275             SDL_PIXELFORMAT_ARGB8888,
    276             SDL_TEXTUREACCESS_STREAMING,
    277             window_width, window_height);
    278 
    279     buffer->width = window_width;
    280     buffer->height = window_height;
    281     buffer->pitch = window_width * TR_BYTES_PER_PIXEL;
    282 
    283     buffer->memory = malloc(window_width * window_height * TR_BYTES_PER_PIXEL);
    284 
    285     transform.width = 2 * window_width;
    286     transform.height = 2 * window_height;
    287     transform.look_shift_x = window_width / 2;
    288     transform.look_shift_y = window_height / 2;
    289     transform.distance_table = malloc(transform.height * sizeof(int32_t *));
    290     transform.angle_table = malloc(transform.height * sizeof(int32_t *));
    291 
    292     for (int32_t y = 0; y < transform.height; ++y)
    293     {
    294         transform.distance_table[y] = malloc(transform.width * sizeof(int32_t));
    295         transform.angle_table[y] = malloc(transform.width * sizeof(int32_t));
    296     }
    297 
    298     // Make distance and angle transformation tables
    299     for (int32_t y = 0; y < transform.height; ++y)
    300     {
    301         for (int32_t x = 0; x < transform.width; ++x)
    302         {
    303             int32_t dist_from_center_x = x - window_width;
    304             int32_t dist_from_center_y = y - window_height;
    305             float dist_from_center = sqrtf((float)(dist_from_center_x * dist_from_center_x + dist_from_center_y * dist_from_center_y));
    306             float angle_from_positive_x_axis = atan2f((float)dist_from_center_y, (float)dist_from_center_x) / TR_PI32;
    307 
    308             float ratio = 32.0f;
    309             transform.distance_table[y][x] = (int32_t)(ratio * TR_TEX_HEIGHT / dist_from_center) % TR_TEX_HEIGHT;
    310             transform.angle_table[y][x] = (int32_t)(0.5f * TR_TEX_WIDTH * angle_from_positive_x_axis);
    311         }
    312     }
    313 }
    314 
    315 
    316 void
    317 sdl_update_window(SDL_Renderer *renderer, struct SDLOffscreenBuffer buffer)
    318 {
    319     if (SDL_UpdateTexture(buffer.texture, 0, buffer.memory, buffer.pitch))
    320     {
    321         TR_LOG_ERR("SDL_UpdateTexture failed: %s\n", SDL_GetError());
    322     }
    323 
    324     SDL_RenderCopy(renderer, buffer.texture, 0, 0);
    325     SDL_RenderPresent(renderer);
    326 }
    327 
    328 
    329 bool
    330 handle_event(SDL_Event *event)
    331 {
    332     bool should_quit = false;
    333 
    334     switch(event->type)
    335     {
    336         case SDL_QUIT:
    337         {
    338             TR_LOG_DBG("SDL_QUIT\n");
    339             should_quit = true;
    340         } break;
    341 
    342         case SDL_WINDOWEVENT:
    343         {
    344             switch(event->window.event)
    345             {
    346                 case SDL_WINDOWEVENT_SIZE_CHANGED:
    347                 {
    348                     SDL_Window *window = SDL_GetWindowFromID(event->window.windowID);
    349                     SDL_Renderer *renderer = SDL_GetRenderer(window);
    350                     TR_LOG_DBG("SDL_WINDOWEVENT_SIZE_CHANGED (%d, %d)\n", event->window.data1, event->window.data2);
    351                     sdl_resize_texture(&global_back_buffer, renderer, event->window.data1, event->window.data2);
    352                 } break;
    353 
    354                 case SDL_WINDOWEVENT_FOCUS_GAINED:
    355                 {
    356                     TR_LOG_DBG("SDL_WINDOWEVENT_FOCUS_GAINED\n");
    357                 } break;
    358 
    359                 case SDL_WINDOWEVENT_EXPOSED:
    360                 {
    361                     SDL_Window *window = SDL_GetWindowFromID(event->window.windowID);
    362                     SDL_Renderer *renderer = SDL_GetRenderer(window);
    363                     sdl_update_window(renderer, global_back_buffer);
    364                 } break;
    365             }
    366         } break;
    367     }
    368     return should_quit ;
    369 }
    370 
    371 
    372 void
    373 sdl_open_game_controllers()
    374 {
    375     int32_t num_joysticks = SDL_NumJoysticks();
    376     for (int32_t controller_index = 0; controller_index < num_joysticks; ++controller_index)
    377     {
    378         if (!SDL_IsGameController(controller_index))
    379         {
    380             continue;
    381         }
    382 
    383         if (controller_index >= TR_MAX_CONTROLLERS)
    384         {
    385             break;
    386         }
    387 
    388         controller_handles[controller_index] = SDL_GameControllerOpen(controller_index);
    389         rumble_handles[controller_index] = SDL_HapticOpen(controller_index);
    390 
    391         if (rumble_handles[controller_index] && SDL_HapticRumbleInit(rumble_handles[controller_index]) != 0)
    392         {
    393             SDL_HapticClose(rumble_handles[controller_index]);
    394             rumble_handles[controller_index] = 0;
    395         }
    396     }
    397 }
    398 
    399 
    400 void
    401 sdl_close_game_controllers()
    402 {
    403     for (int32_t controller_index = 0; controller_index < TR_MAX_CONTROLLERS; ++controller_index)
    404     {
    405         if (controller_handles[controller_index])
    406         {
    407             SDL_GameControllerClose(controller_handles[controller_index]);
    408         }
    409     }
    410 }
    411 
    412 
    413 void
    414 sdl_cleanup(void)
    415 {
    416     TR_LOG_DBG("Cleaning up...\n");
    417     sdl_close_game_controllers();
    418     SDL_Quit();
    419 }
    420 
    421 
    422 int
    423 main(void)
    424 {
    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 }