ohsp

Prototype for a game with dual thruster controls.
git clone git://git.amin.space/ohsp.git
Log | Files | Refs | LICENSE

platform_sdl.c (15254B)


      1 #include "platform_sdl.h"
      2 
      3 #ifdef __linux__
      4 #include "util_linux.h"
      5 #elif __WIN32
      6 #endif
      7 
      8 #include <stdbool.h>
      9 #include <stdio.h>
     10 #include <time.h>
     11 
     12 #ifndef MAP_ANONYMOUS
     13 #define MAP_ANONYMOUS MAP_ANON
     14 #endif
     15 
     16 extern bool PAUSED;
     17 
     18 static struct SDLOffscreenBuffer global_back_buffer = {0};
     19 static struct SDLInputRecord input_record = {0};
     20 static bool TRACE = false;
     21 
     22 
     23 struct SDLWindowDimension sdl_get_window_dimension(SDL_Window *window)
     24 {
     25     struct SDLWindowDimension result = {0};
     26     SDL_GetWindowSize(window, &result.width, &result.height);
     27     return(result);
     28 }
     29 
     30 
     31 void sdl_resize_texture(struct SDLOffscreenBuffer *buffer, SDL_Renderer *renderer, int width, int height)
     32 {
     33     if (buffer->memory)
     34     {
     35         free(buffer->memory);
     36     }
     37 
     38     if (buffer->texture)
     39     {
     40         SDL_DestroyTexture(buffer->texture);
     41     }
     42 
     43     buffer->texture = SDL_CreateTexture(
     44             renderer,
     45             SDL_PIXELFORMAT_ARGB8888,
     46             SDL_TEXTUREACCESS_STREAMING,
     47             width, height);
     48 
     49     buffer->width = width;
     50     buffer->height = height;
     51     buffer->pitch = width * BYTES_PER_PIXEL;
     52 
     53     buffer->memory = malloc(width * height * BYTES_PER_PIXEL);
     54 }
     55 
     56 
     57 void sdl_update_window(SDL_Renderer *renderer, struct SDLOffscreenBuffer *buffer)
     58 {
     59     if (SDL_UpdateTexture(buffer->texture, 0, buffer->memory, buffer->pitch))
     60     {
     61         // TODO(amin): Handle this error
     62     }
     63 
     64     SDL_RenderCopy(renderer, buffer->texture, 0, 0);
     65     SDL_RenderPresent(renderer);
     66 }
     67 
     68 
     69 void clear_screen(struct SDLOffscreenBuffer *buffer, uint32_t pixel_value)
     70 {
     71     // NOTE(amin): Memset is faster than nested for loops, but can only set
     72     // pixels to single byte values
     73     memset(buffer->memory, pixel_value, buffer->height * buffer->width * BYTES_PER_PIXEL);
     74 }
     75 
     76 
     77 // TODO: make the deadzone circular
     78 float sdl_process_controller_axis_value(int16_t value, int16_t deadzone_threshold)
     79 {
     80     float result = 0.0f;
     81     if (value < -deadzone_threshold)
     82     {
     83         result = (float)(value + deadzone_threshold) / (32768.0f - deadzone_threshold);
     84     }
     85     else if (value > deadzone_threshold)
     86     {
     87         result = (float)(value - deadzone_threshold) / (32767.0f - deadzone_threshold);
     88     }
     89     return result;
     90 }
     91 
     92 
     93 bool sdl_handle_event(SDL_Event *event, struct GameControllerInput *controller_input)
     94 {
     95     if (!event || !controller_input)
     96     {
     97         return true;
     98     }
     99 
    100     bool should_quit = false;
    101 
    102     switch(event->type)
    103     {
    104         case SDL_QUIT:
    105         {
    106             printf("SDL_QUIT\n");
    107             should_quit = true;
    108         } break;
    109 
    110         case SDL_KEYDOWN:
    111         case SDL_KEYUP:
    112         {
    113             SDL_Keycode key_code = event->key.keysym.sym;
    114             bool is_down = (event->key.state == SDL_PRESSED);
    115             if (is_down)
    116             {
    117                 if (key_code == SDLK_p)
    118                 {
    119                     PAUSED = !PAUSED;
    120                 }
    121                 else if (key_code == SDLK_t)
    122                 {
    123                     TRACE = !TRACE;
    124                 }
    125                 else if (key_code == SDLK_r)
    126                 {
    127                     if (input_record.state == INACTIVE)
    128                     {
    129                         printf("input_record: RECORDING\n");
    130                         input_record.initial_state_recorded = false;
    131                         input_record.record_head = 0;
    132                         input_record.state = RECORDING;
    133                     }
    134                     else if (input_record.state == RECORDING)
    135                     {
    136                         printf("input_record: INACTIVE\n");
    137                         input_record.state = INACTIVE;
    138                     }
    139                     else if (input_record.state == PLAYING_BACK)
    140                     {
    141                         printf("Can't record while playing back\n");
    142                     }
    143                     else
    144                     {
    145                         printf("ERROR: input_record invalid state\n");
    146                     }
    147                 }
    148                 else if (key_code == SDLK_l)
    149                 {
    150                     if (input_record.state == INACTIVE)
    151                     {
    152                         printf("input_record: PLAYING_BACK\n");
    153                         input_record.playback_head = 0;
    154                         input_record.state = PLAYING_BACK;
    155                     }
    156                     else if (input_record.state == PLAYING_BACK)
    157                     {
    158                         printf("input_record: INACTIVE\n");
    159                         input_record.state = INACTIVE;
    160                     }
    161                     else if (input_record.state == RECORDING)
    162                     {
    163                         printf("Can't play back while recording\n");
    164                     }
    165                     else
    166                     {
    167                         printf("ERROR: input_record invalid state\n");
    168                     }
    169                 }
    170             }
    171         } break;
    172 
    173         case SDL_CONTROLLERAXISMOTION:
    174         {
    175             // down and right are positive
    176             float normalized_axis_value = sdl_process_controller_axis_value(event->caxis.value, DEADZONE_THRESHOLD);
    177 
    178             //printf("SDL_CONTROLLERAXISMOTION (%d, %s, %d, %f)\n",
    179             //        event->caxis.which,
    180             //        SDL_GameControllerGetStringForAxis(event->caxis.axis),
    181             //        event->caxis.value,
    182             //        normalized_axis_value);
    183 
    184             switch(event->caxis.axis)
    185             {
    186                 case SDL_CONTROLLER_AXIS_LEFTX:
    187                 {
    188                     controller_input->left_stick_x = normalized_axis_value;
    189                 } break;
    190                 case SDL_CONTROLLER_AXIS_LEFTY:
    191                 {
    192                     controller_input->left_stick_y = normalized_axis_value;
    193                 } break;
    194                 case SDL_CONTROLLER_AXIS_RIGHTX:
    195                 {
    196                     controller_input->right_stick_x = normalized_axis_value;
    197                 } break;
    198                 case SDL_CONTROLLER_AXIS_RIGHTY:
    199                 {
    200                     controller_input->right_stick_y = normalized_axis_value;
    201                 } break;
    202                 default:
    203                 {
    204                     printf("Unhandled axis input\n");
    205                 }
    206             }
    207         } break;
    208 
    209         case SDL_WINDOWEVENT:
    210         {
    211             switch(event->window.event)
    212             {
    213                 case SDL_WINDOWEVENT_SIZE_CHANGED:
    214                 {
    215                     SDL_Window *window = SDL_GetWindowFromID(event->window.windowID);
    216                     SDL_Renderer *renderer = SDL_GetRenderer(window);
    217                     printf("SDL_WINDOWEVENT_SIZE_CHANGED (%d, %d)\n", event->window.data1, event->window.data2);
    218                     sdl_resize_texture(&global_back_buffer, renderer, event->window.data1, event->window.data2);
    219                 } break;
    220 
    221                 case SDL_WINDOWEVENT_FOCUS_GAINED:
    222                 {
    223                     printf("SDL_WINDOWEVENT_FOCUS_GAINED\n");
    224                 } break;
    225 
    226                 case SDL_WINDOWEVENT_EXPOSED:
    227                 {
    228                     SDL_Window *window = SDL_GetWindowFromID(event->window.windowID);
    229                     SDL_Renderer *renderer = SDL_GetRenderer(window);
    230                     sdl_update_window(renderer, &global_back_buffer);
    231                 } break;
    232             }
    233         } break;
    234     }
    235     return(should_quit);
    236 }
    237 
    238 
    239 int main(int argc, char *argv[])
    240 {
    241     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER))
    242     {
    243         // TODO(amin): log SDL_Init error
    244     }
    245 
    246     SDL_Window *window = SDL_CreateWindow(
    247             TITLE,
    248             SDL_WINDOWPOS_UNDEFINED,
    249             SDL_WINDOWPOS_UNDEFINED,
    250             SCREEN_WIDTH,
    251             SCREEN_HEIGHT,
    252             SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
    253 
    254 #ifdef FULLSCREEN
    255     SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
    256 #endif
    257 
    258     if (window)
    259     {
    260         SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
    261 
    262         if (renderer)
    263         {
    264             struct SDLWindowDimension dimension = sdl_get_window_dimension(window);
    265             sdl_resize_texture(&global_back_buffer, renderer, dimension.width, dimension.height);
    266 
    267             bool running = true;
    268 
    269 #ifdef USE_TEST_SEED
    270             srand((uint32_t)0);
    271 #else
    272             srand((uint32_t)time(NULL));
    273 #endif
    274 
    275             int num_joysticks = SDL_NumJoysticks();
    276             SDL_GameController *controller_handles[MAX_CONTROLLERS];
    277             for (int controller_index = 0; controller_index < num_joysticks; ++controller_index)
    278             {
    279                 if (!SDL_IsGameController(controller_index))
    280                 {
    281                     continue;
    282                 }
    283 
    284                 if (controller_index >= MAX_CONTROLLERS)
    285                 {
    286                     break;
    287                 }
    288 
    289                 controller_handles[controller_index] = SDL_GameControllerOpen(controller_index);
    290             }
    291 
    292             uint64_t lag = 0;
    293             uint64_t previous_ms = (SDL_GetPerformanceCounter() * SECOND) / SDL_GetPerformanceFrequency();
    294 
    295             struct GameState game_state = {0};
    296             game_init(&game_state, dimension.width, dimension.height);
    297 
    298             struct GameControllerInput controller_input = {0};
    299 
    300             struct SDLGameCode game_code = load_game_code(GAME_LIB_PATH);
    301 
    302             while (running)
    303             {
    304                 uint64_t current_ms = (SDL_GetPerformanceCounter() * SECOND) / SDL_GetPerformanceFrequency();
    305                 uint64_t elapsed_ms = current_ms - previous_ms;
    306                 previous_ms = current_ms;
    307                 lag += elapsed_ms;
    308                 //printf("%" PRIu64 ", %" PRIu64 ", %f\n", elapsed_ms, lag, MS_PER_UPDATE);
    309 
    310                 time_t last_write_time = file_get_modified_time(GAME_LIB_PATH);
    311                 bool game_code_has_changed = last_write_time && (last_write_time != game_code.last_write_time);
    312                 if (game_code_has_changed)
    313                 {
    314                     unload_game_code(&game_code);
    315                     game_code = load_game_code(GAME_LIB_PATH);
    316                     if (!game_code.is_valid)
    317                     {
    318                         // TODO: fall back to backup?
    319                     }
    320                 }
    321 
    322                 SDL_Event event;
    323                 while (SDL_PollEvent(&event))
    324                 {
    325                     running = !sdl_handle_event(&event, &controller_input);
    326                 }
    327                 SDL_PumpEvents();
    328 
    329                 switch (input_record.state)
    330                 {
    331                     case INACTIVE: {} break;
    332 
    333                     case RECORDING:
    334                     {
    335                         // TODO: sdl_input_record(&controller_input);
    336                         if (input_record.record_head < INPUT_RECORD_SIZE)
    337                         {
    338                             if (input_record.record_head == 0)
    339                             {
    340                                 input_record.initial_state = game_state;
    341                                 input_record.initial_state_recorded = true;
    342                             }
    343                             input_record.input_buffer[input_record.record_head] = controller_input;
    344                             ++input_record.record_head;
    345                         }
    346                         else
    347                         {
    348                             printf("Input record buffer full\n");
    349                             input_record.state = INACTIVE;
    350                         }
    351                     } break;
    352 
    353                     case PLAYING_BACK:
    354                     {
    355                         if (input_record.playback_head < input_record.record_head)
    356                         {
    357                             if (input_record.playback_head == 0 && input_record.initial_state_recorded)
    358                             {
    359                                 game_state = input_record.initial_state;
    360                             }
    361 
    362                             // TODO: sdl_input_playback(&controller_input);
    363                             controller_input = input_record.input_buffer[input_record.playback_head];
    364                             ++input_record.playback_head;
    365                         }
    366                         else
    367                         {
    368                             printf("Playback restarting\n");
    369                             input_record.playback_head = 0;
    370                         }
    371                     } break;
    372 
    373                     default:
    374                     {
    375                         printf("Unhandled SDLInputRecordState\n");
    376                     } break;
    377                 }
    378 
    379                 dimension = sdl_get_window_dimension(window);
    380 
    381                 struct OffscreenBuffer buffer =
    382                 {
    383                     // WARNING: these pointers are aliased until the end of the
    384                     // loop
    385                     .memory = global_back_buffer.memory,
    386                     .width = global_back_buffer.width,
    387                     .height = global_back_buffer.height,
    388                     .pitch = global_back_buffer.pitch,
    389                 };
    390 
    391                 if (PAUSED)
    392                 {
    393                     lag = 0;
    394                 }
    395                 else if (input_record.state == RECORDING || input_record.state == PLAYING_BACK)
    396                 {
    397                     // TODO: Make up your mind about what kind of time step and game loop this game needs.
    398 
    399                     // When recording or playing back, we do exactly one update
    400                     // per frame. This is so that recorded input played back
    401                     // over the initial state always results in the same
    402                     // sequence of states.
    403                     game_code.game_update(&game_state, controller_input, buffer.width, buffer.height);
    404                     lag -= MS_PER_UPDATE;
    405                 }
    406                 else
    407                 {
    408                     while (lag >= MS_PER_UPDATE)
    409                     {
    410                         game_code.game_update(&game_state, controller_input, buffer.width, buffer.height);
    411                         //printf("\t%" PRIu64 ", %f\n", lag, MS_PER_UPDATE);
    412                         lag -= MS_PER_UPDATE;
    413                     }
    414                 }
    415 
    416                 if (!TRACE)
    417                 {
    418                     clear_screen(&global_back_buffer, COLOR_BACKGROUND);
    419                 }
    420                 game_code.game_render(&buffer, lag/SECOND, &game_state);
    421                 sdl_update_window(renderer, &global_back_buffer);
    422                 if (elapsed_ms <= MS_PER_FRAME)
    423                 {
    424                     SDL_Delay(MS_PER_FRAME - elapsed_ms);
    425                 }
    426             }
    427 
    428             game_cleanup(&game_state);
    429 
    430             for (int controller_index = 0; controller_index < num_joysticks; ++controller_index)
    431             {
    432                 if (!SDL_IsGameController(controller_index))
    433                 {
    434                     continue;
    435                 }
    436 
    437                 if (controller_index >= MAX_CONTROLLERS)
    438                 {
    439                     break;
    440                 }
    441 
    442                 SDL_GameControllerClose(controller_handles[controller_index]);
    443             }
    444         }
    445         else
    446         {
    447             // TODO(amin): log SDL_Renderer error
    448         }
    449     }
    450     else
    451     {
    452         // TODO(amin): log SDL_Window error
    453     }
    454 
    455     SDL_Quit();
    456     return(0);
    457 }