/* * Add controller mappings from the specified file */ void S2D_AddControllerMappingsFromFile(const char *path) { if (!S2D_FileExists(path)) { S2D_Log(S2D_WARN, "Controller mappings file not found: %s", path); return; } int mappings_added = SDL_GameControllerAddMappingsFromFile(path); if (mappings_added == -1) { S2D_Error("SDL_GameControllerAddMappingsFromFile", SDL_GetError()); } else { S2D_Log(S2D_INFO, "Added %i controller mapping(s)", mappings_added); } }
/* * Open controllers and joysticks */ void S2D_OpenControllers() { char guid_str[33]; // Enumerate joysticks for (int device_index = 0; device_index < SDL_NumJoysticks(); ++device_index) { // Check if joystick supports SDL's game controller interface (a mapping is available) if (SDL_IsGameController(device_index)) { SDL_GameController *controller = SDL_GameControllerOpen(device_index); SDL_JoystickID intance_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)); SDL_JoystickGetGUIDString( SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(controller)), guid_str, 33 ); if (intance_id > last_intance_id) { if (controller) { S2D_Log(S2D_INFO, "Controller #%i: %s\n GUID: %s", intance_id, SDL_GameControllerName(controller), guid_str); } else { S2D_Log(S2D_ERROR, "Could not open controller #%i: %s", intance_id, SDL_GetError()); } last_intance_id = intance_id; } // Controller interface not supported, try to open as joystick } else { SDL_Joystick *joy = SDL_JoystickOpen(device_index); SDL_JoystickID intance_id = SDL_JoystickInstanceID(joy); if (!joy) { S2D_Log(S2D_ERROR, "Could not open controller"); } else if(intance_id > last_intance_id) { SDL_JoystickGetGUIDString( SDL_JoystickGetGUID(joy), guid_str, 33 ); S2D_Log(S2D_INFO, "Controller #%i: %s\n GUID: %s\n Axes: %d\n Buttons: %d\n Balls: %d", intance_id, SDL_JoystickName(joy), guid_str, SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumBalls(joy) ); S2D_Log(S2D_WARN, "Controller #%i does not have a mapping available", intance_id); last_intance_id = intance_id; } } } }
/* * Add controller mapping from string */ void S2D_AddControllerMapping(const char *map) { int result = SDL_GameControllerAddMapping(map); char guid[33]; strncpy(guid, map, 32); switch (result) { case 1: S2D_Log(S2D_INFO, "Mapping added for GUID: %s", guid); break; case 0: S2D_Log(S2D_INFO, "Mapping updated for GUID: %s", guid); break; case -1: S2D_Error("SDL_GameControllerAddMapping", SDL_GetError()); break; } }
/* * Print info about the current OpenGL context */ void S2D_GL_PrintContextInfo(S2D_Window *window) { S2D_Log(S2D_INFO, "OpenGL Context\n" " GL_VENDOR: %s\n" " GL_RENDERER: %s\n" " GL_VERSION: %s\n" " GL_SHADING_LANGUAGE_VERSION: %s", window->S2D_GL_VENDOR, window->S2D_GL_RENDERER, window->S2D_GL_VERSION, window->S2D_GL_SHADING_LANGUAGE_VERSION ); }
/* * Create text */ Text *S2D_CreateText(const char *font, const char *msg, int size) { #if GLES S2D_Log("S2D_DrawText not yet implemented!", S2D_WARN); #endif Text *txt; txt = (Text *)malloc(sizeof(Text)); if(!txt) { S2D_Error("S2D_CreateText", "Out of memory!"); return NULL; } // `msg` cannot be an empty string; if so, return if (strlen(msg) == 0) { S2D_Error("S2D_CreateText", "Text message cannot be empty!"); return NULL; } else { txt->msg = msg; } txt->x = 0; txt->y = 0; txt->color.r = 1.0; txt->color.g = 1.0; txt->color.b = 1.0; txt->color.a = 1.0; txt->texture_id = 0; txt->font = TTF_OpenFont(font, size); if (!txt->font) { S2D_Error("TTF_OpenFont", TTF_GetError()); free(txt); return NULL; } // Save the width and height of the text TTF_SizeText(txt->font, txt->msg, &txt->w, &txt->h); SDL_Surface *surface; SDL_Color color = { 255, 255, 255 }; surface = TTF_RenderText_Blended(txt->font, txt->msg, color); S2D_GL_SetUpTexture(&txt->texture_id, GL_RGBA, txt->w, txt->h, surface->pixels, GL_NEAREST); // Free the surface data, no longer needed SDL_FreeSurface(surface); return txt; }
/* * Close the window, clean up SDL */ int S2D_Close(Window *window) { S2D_Log("Closing S2D", S2D_INFO); // SDL IMG_Quit(); Mix_CloseAudio(); Mix_Quit(); TTF_Quit(); SDL_GL_DeleteContext(window->glcontext); SDL_DestroyWindow(window->sdl); SDL_Quit(); return 0; }
/* * Create an image */ Image *S2D_CreateImage(const char *path) { if(!path) return NULL; // TODO: Implement images in GLES #if GLES S2D_Log("S2D_DrawImage not yet implemented!", S2D_INFO); #endif Image *img; SDL_Surface *surface; // Load image from file as SDL_Surface surface = IMG_Load(path); if (!surface) { S2D_Error("IMG_Load", IMG_GetError()); return NULL; } img = (Image *)malloc(sizeof(Image)); if(!img) { S2D_Error("IMG_Load", "Out of memory!"); SDL_FreeSurface(surface); return NULL; } // Initialize values img->x = 0; img->y = 0; img->w = surface->w; img->h = surface->h; img->texture_id = 0; // Detect image mode // TODO: BMP is in BGR...? int format = GL_RGB; if(surface->format->BytesPerPixel == 4) { format = GL_RGBA; } S2D_GL_SetUpTexture(&img->texture_id, format, img->w, img->h, surface->pixels, GL_NEAREST); // Free the surface data, no longer needed SDL_FreeSurface(surface); return img; }
/* * Show the window */ int S2D_Show(Window *window) { // Setting up variables int mouse_x, mouse_y; // Mouse positions const Uint8 *key_state; Uint32 frames = 0; // Total frames since start Uint32 start_ms = SDL_GetTicks(); // Elapsed time since start Uint32 begin_ms = start_ms; // Time at beginning of loop Uint32 end_ms; // Time at end of loop Uint32 elapsed_ms; // Total elapsed time Uint32 loop_ms; // Elapsed time of loop int delay_ms; // Amount of delay to achieve desired frame rate double fps; // The actual frame rate // Enable VSync if (window->vsync) { if (!SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1")) { S2D_Log("VSync cannot be enabled", S2D_WARN); } } // Detect Controllers and Joysticks ////////////////////////////////////////// if (SDL_NumJoysticks() > 0) { sprintf(S2D_msg, "Joysticks detected: %i", SDL_NumJoysticks()); S2D_Log(S2D_msg, S2D_INFO); } // Variables for controllers and joysticks SDL_GameController *controller = NULL; SDL_Joystick *joy = NULL; // Enumerate joysticks for (int i = 0; i < SDL_NumJoysticks(); ++i) { // Check to see if joystick supports SDL's game controller interface if (SDL_IsGameController(i)) { controller = SDL_GameControllerOpen(i); if (controller) { sprintf(S2D_msg, "Found a valid controller, named: %s\n", SDL_GameControllerName(controller)); S2D_Log(S2D_msg, S2D_INFO); break; // Break after first available controller } else { sprintf(S2D_msg, "Could not open game controller %i: %s\n", i, SDL_GetError()); S2D_Log(S2D_msg, S2D_ERROR); } // Controller interface not supported, try to open as joystick } else { sprintf(S2D_msg, "Joystick %i is not supported by the game controller interface", i); S2D_Log(S2D_msg, S2D_WARN); joy = SDL_JoystickOpen(i); // Joystick is valid if (joy) { sprintf(S2D_msg, "Opened Joystick %i\n" "Name: %s\n" "Axes: %d\n" "Buttons: %d\n" "Balls: %d\n", i, SDL_JoystickName(joy), SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumBalls(joy) ); S2D_Log(S2D_msg, S2D_INFO); // Joystick not valid } else { sprintf(S2D_msg, "Could not open Joystick %i", i); S2D_Log(S2D_msg, S2D_ERROR); } break; // Break after first available joystick } } // Main Event Loop /////////////////////////////////////////////////////////// bool quit = false; while (!quit) { // Clear Frame ///////////////////////////////////////////////////////////// S2D_GL_Clear(window->background); // Set FPS ///////////////////////////////////////////////////////////////// frames++; end_ms = SDL_GetTicks(); elapsed_ms = end_ms - start_ms; fps = frames / (elapsed_ms / 1000.0); loop_ms = end_ms - begin_ms; delay_ms = (1000 / window->fps_cap) - loop_ms; if (delay_ms < 0) delay_ms = 0; // Note: `loop_ms + delay_ms` should equal `1000 / fps_cap` SDL_Delay(delay_ms); begin_ms = SDL_GetTicks(); // Handle Input //////////////////////////////////////////////////////////// SDL_Event e; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_KEYDOWN: if (window->on_key) window->on_key(SDL_GetScancodeName(e.key.keysym.scancode)); break; case SDL_MOUSEBUTTONDOWN: if (window->on_mouse) window->on_mouse(e.button.x, e.button.y); break; case SDL_JOYAXISMOTION: if (window->on_controller) window->on_controller(true, e.jaxis.axis, e.jaxis.value, false, 0); break; case SDL_JOYBUTTONDOWN: if (window->on_controller) window->on_controller(false, 0, 0, true, e.jbutton.button); break; case SDL_WINDOWEVENT: switch (e.window.event) { case SDL_WINDOWEVENT_RESIZED: S2D_GL_SetView(e.window.data1, e.window.data2, window->width, window->height); break; } break; case SDL_QUIT: quit = true; break; } } // Detect keys held down int num_keys; key_state = SDL_GetKeyboardState(&num_keys); for (int i = 0; i < num_keys; i++) { if (window->on_key_down) { if (key_state[i] == 1) { window->on_key_down(SDL_GetScancodeName(i)); } } } // Store the mouse position SDL_GetMouseState(&mouse_x, &mouse_y); // Update Window State ///////////////////////////////////////////////////// // Store new values in the window window->mouse.x = mouse_x; window->mouse.y = mouse_y; window->mouse.real_x = mouse_x; window->mouse.real_y = mouse_y; window->frames = frames; window->elapsed_ms = elapsed_ms; window->loop_ms = loop_ms; window->delay_ms = delay_ms; window->fps = fps; // scale the mouse position, if necessary if (window->s_width != window->width) { window->mouse.x = (int)((double)window->mouse.real_x * ((double)window->s_width / (double)window->width) + 0.5); } if (window->s_height != window->height) { window->mouse.y = (int)((double)window->mouse.real_y * ((double)window->s_height / (double)window->height) + 0.5); } // Call update and render callbacks if (window->update) window->update(); if (window->render) window->render(); // Draw Frame ////////////////////////////////////////////////////////////// SDL_GL_SwapWindow(window->sdl); } return 0; }
/* * Logs Simple 2D errors to the console, with caller and message body */ void S2D_Error(const char *caller, const char *msg) { sprintf(S2D_msg, "(%s) %s", caller, msg); S2D_Log(S2D_msg, S2D_ERROR); }
/* * Create a window */ Window* S2D_CreateWindow(const char *title, int width, int height, Update update, Render render, int flags) { // Allocate window and set default values Window *window = (Window*)malloc(sizeof(Window)); window->title = title; window->width = width; window->height = height; window->fps_cap = 60; window->vsync = true; window->update = update; window->render = render; window->on_key = NULL; window->on_key_down = NULL; window->on_mouse = NULL; window->on_controller = NULL; window->background.r = 0.0; window->background.g = 0.0; window->background.b = 0.0; window->background.a = 1.0; // SDL Initialization //////////////////////////////////////////////////////// // Initialize SDL if (SDL_Init(SDL_INIT_EVERYTHING) != 0) S2D_Error("SDL_Init", SDL_GetError()); // Initialize SDL_ttf if (TTF_Init() != 0) { S2D_Error("TTF_Init", TTF_GetError()); free(window); return NULL; } // Initialize SDL_mixer int mix_flags = MIX_INIT_FLAC|MIX_INIT_OGG|MIX_INIT_MP3; int mix_initted = Mix_Init(mix_flags); if ((mix_initted&mix_flags) != mix_flags) { S2D_Error("Mix_Init", Mix_GetError()); } int audio_rate = 44100; Uint16 audio_format = AUDIO_S16SYS; int audio_channels = 2; int audio_buffers = 4096; if (Mix_OpenAudio(audio_rate, MIX_DEFAULT_FORMAT, audio_channels, audio_buffers) != 0) { S2D_Error("Mix_OpenAudio", Mix_GetError()); free(window); return NULL; } // Create SDL window window->sdl = SDL_CreateWindow( window->title, // title SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, // window position window->width, window->height, // window size SDL_WINDOW_OPENGL | flags // flags ); if (!window->sdl) S2D_Error("SDL_CreateWindow", SDL_GetError()); // Window created by SDL might not actually be the requested size. // If not, retrieve and set the actual window size. window->s_width = window->width; window->s_height = window->height; SDL_GetWindowSize(window->sdl, &window->width, &window->height); if ((window->width != window->s_width) || (window->height != window->s_height)) { sprintf(S2D_msg, "Resolution %dx%d unsupported by driver, scaling to %dx%d", window->s_width, window->s_height, window->width, window->height); S2D_Log(S2D_msg, S2D_WARN); } // Init OpenGL / GLES //////////////////////////////////////////////////////// // Specify the OpenGL Context #if !GLES if (FORCE_GL2) { // Use legacy OpenGL 2.1 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); } else { // Request an OpenGL 3.3 forward-compatible core profile SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); } #endif // Create and store the OpenGL context if (FORCE_GL2) { window->glcontext = NULL; } else { // Ask SDL to create an OpenGL context window->glcontext = SDL_GL_CreateContext(window->sdl); } // Check if a valid OpenGL context was created if (window->glcontext) { // Valid context found #if GLES // Initialize OpenGL ES 2.0 gles_init(window->width, window->height, window->s_width, window->s_height); #else // Initialize OpenGL 3.3+ gl3_init(window->width, window->height); #endif } else { // Context could not be created #if GLES S2D_Error("GLES / SDL_GL_CreateContext", SDL_GetError()); #else // Try to fallback using an OpenGL 2.1 context SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); // Try creating the context again window->glcontext = SDL_GL_CreateContext(window->sdl); // Check if this context was created if (window->glcontext) { // Valid context found S2D_GL2 = true; gl2_init(window->width, window->height); } else { // Could not create any OpenGL contexts, hard failure S2D_Error("GL2 / SDL_GL_CreateContext", SDL_GetError()); S2D_Log("An OpenGL context could not be created", S2D_ERROR); free(window); return NULL; } #endif } // Store the context and print it if diagnostics is enabled S2D_GL_StoreContextInfo(window); if (diagnostics) S2D_GL_PrintContextInfo(window); return window; }
/* * Initialize OpenGL */ int S2D_GL_Init(S2D_Window *window) { // Specify OpenGL contexts and set attributes #if GLES SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); #else // Use legacy OpenGL 2.1 if (FORCE_GL2) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); // Request an OpenGL 3.3 forward-compatible core profile } else { SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); } #endif // Create and store the OpenGL context if (FORCE_GL2) { window->glcontext = NULL; } else { // Ask SDL to create an OpenGL context window->glcontext = SDL_GL_CreateContext(window->sdl); } // Check if a valid OpenGL context was created if (window->glcontext) { // Valid context found // Initialize OpenGL ES 2.0 #if GLES S2D_GLES_Init(); S2D_GL_SetViewport(window); // Initialize OpenGL 3.3+ #else // Initialize GLEW on Windows #if WINDOWS GLenum err = glewInit(); if (GLEW_OK != err) S2D_Error("GLEW", glewGetErrorString(err)); #endif S2D_GL3_Init(); S2D_GL_SetViewport(window); #endif // Context could not be created } else { #if GLES S2D_Error("GLES / SDL_GL_CreateContext", SDL_GetError()); #else // Try to fallback using an OpenGL 2.1 context SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); // Try creating the context again window->glcontext = SDL_GL_CreateContext(window->sdl); // Check if this context was created if (window->glcontext) { // Valid context found S2D_GL2 = true; S2D_GL2_Init(); S2D_GL_SetViewport(window); // Could not create any OpenGL contexts, hard failure } else { S2D_Error("GL2 / SDL_GL_CreateContext", SDL_GetError()); S2D_Log(S2D_ERROR, "An OpenGL context could not be created"); return -1; } #endif } // Store the context and print it if diagnostics is enabled S2D_GL_StoreContextInfo(window); if (S2D_diagnostics) S2D_GL_PrintContextInfo(window); return 0; }
/* * Prints current GL error */ void S2D_GL_PrintError(char *error) { S2D_Log(S2D_ERROR, "%s (%d)", error, glGetError()); }