707 lines
22 KiB
C++
707 lines
22 KiB
C++
#include "Game.hpp"
|
|
|
|
Game::Game()
|
|
{
|
|
/* Set the appropriate priority level for the default log category so either info level messages
|
|
* and higher are enabled or debug level messages are enabled, depending on the global configuration */
|
|
SDL_LogPriority default_log_category_priority;
|
|
if (configuration()["log"]["debug-to-file"] || configuration()["log"]["debug-to-stdout"])
|
|
{
|
|
default_log_category_priority = SDL_LOG_PRIORITY_DEBUG;
|
|
}
|
|
else
|
|
{
|
|
default_log_category_priority = SDL_LOG_PRIORITY_INFO;
|
|
}
|
|
SDL_LogSetPriority(sb::Log::DEFAULT_CATEGORY, default_log_category_priority);
|
|
/* set custom log function that prints to stdout/stderr and to file if enabled */
|
|
SDL_LogSetOutputFunction(&Game::sdl_log_override, this);
|
|
/* pretty print config to debug log */
|
|
std::ostringstream log_message;
|
|
log_message << std::setw(4) << configuration() << std::endl;
|
|
sb::Log::log(log_message, sb::Log::DEBUG);
|
|
/* tell SDL which render driver you will be requesting when calling SDL_CreateRenderer */
|
|
SDL_SetHint(SDL_HINT_RENDER_DRIVER, configuration()["display"]["render driver"].get<std::string>().c_str());
|
|
/* initialize the buffer of frame lengths which will be used to calculate FPS */
|
|
frame_length_history.reserve(5000);
|
|
set_framerate(configuration()["display"]["framerate"]);
|
|
delegate.subscribe(&Game::handle_quit_event, this, SDL_QUIT);
|
|
/* Needed for displaying fullscreen correctly on Linux (?) Also might need SDL_VIDEO_CENTERED (?) */
|
|
std::string fullscreen_env_assigment = "SDL_VIDEO_X11_LEGACY_FULLSCREEN=0";
|
|
putenv(const_cast<char*>(fullscreen_env_assigment.c_str()));
|
|
/* log compiled and linked SDL versions */
|
|
SDL_version version;
|
|
log_message = std::ostringstream();
|
|
SDL_VERSION(&version);
|
|
log_message << "compiled against SDL " << static_cast<int>(version.major) << "." << static_cast<int>(version.minor) <<
|
|
"." << static_cast<int>(version.patch) << std::endl;
|
|
SDL_GetVersion(&version);
|
|
log_message << "linked to SDL " << static_cast<int>(version.major) << "." << static_cast<int>(version.minor) << "." <<
|
|
static_cast<int>(version.patch);
|
|
sb::Log::log(log_message);
|
|
/* allows use of our own main function (?) see SDL_SetMainReady.html */
|
|
SDL_SetMainReady();
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
|
|
{
|
|
sb::Log::sdl_error("SDL could not initialize");
|
|
flag_to_end();
|
|
}
|
|
log_message = std::ostringstream();
|
|
log_message << "GLEW " << glewGetString(GLEW_VERSION);
|
|
sb::Log::log(log_message.str());
|
|
glm::ivec2 window_size = configuration()["display"]["dimensions"].get<glm::ivec2>();
|
|
|
|
/* Set these before creating a window (see SDL_GLattr.html) */
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
|
|
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_SHARE_WITH_CURRENT_CONTEXT, 1);
|
|
|
|
/* Create a window with dimensions set in the config, centered, and flagged to be usable in OpenGL context */
|
|
_window = SDL_CreateWindow(
|
|
configuration()["display"]["title"].get_ref<const std::string&>().c_str(), SDL_WINDOWPOS_CENTERED,
|
|
SDL_WINDOWPOS_CENTERED, window_size.x, window_size.y, SDL_WINDOW_OPENGL);
|
|
if (_window == nullptr)
|
|
{
|
|
sb::Log::sdl_error("Could not create window");
|
|
flag_to_end();
|
|
}
|
|
/* Create an SDL renderer for clearing the screen to black and for logging renderer properties. Destroy renderer when finished.
|
|
* Skip this in emscripten because it causes a mouse event bug. */
|
|
#ifndef __EMSCRIPTEN__
|
|
if ((renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)) == nullptr)
|
|
{
|
|
sb::Log::sdl_error("Could not create renderer");
|
|
flag_to_end();
|
|
}
|
|
else
|
|
{
|
|
int w, h;
|
|
/* log the renderer resolution */
|
|
SDL_GetRendererOutputSize(renderer, &w, &h);
|
|
log_message = std::ostringstream();
|
|
log_message << "renderer output size is " << w << "x" << h;
|
|
sb::Log::log(log_message);
|
|
/* clear screen to black */
|
|
SDL_SetRenderTarget(renderer, nullptr);
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(renderer);
|
|
SDL_RenderPresent(renderer);
|
|
SDL_RenderFlush(renderer);
|
|
SDL_DestroyRenderer(renderer);
|
|
}
|
|
#endif
|
|
SDL_ShowCursor(configuration()["display"]["show-cursor"]);
|
|
if (TTF_Init() < 0)
|
|
{
|
|
sb::Log::sdl_error("Could not initialize SDL ttf");
|
|
flag_to_end();
|
|
}
|
|
else
|
|
{
|
|
SDL_Log("initialized SDL ttf %d.%d.%d", SDL_TTF_MAJOR_VERSION,
|
|
SDL_TTF_MINOR_VERSION, SDL_TTF_PATCHLEVEL);
|
|
}
|
|
if ((bp_mono_font = TTF_OpenFont("BPmono.ttf", 14)) == nullptr)
|
|
{
|
|
sb::Log::log("Could not load BPmono.ttf", sb::Log::ERROR);
|
|
}
|
|
if (Mix_Init(MIX_INIT_OGG) == 0)
|
|
{
|
|
sb::Log::sdl_error("Could not initialize SDL mixer");
|
|
// flag_to_end();
|
|
}
|
|
else
|
|
{
|
|
SDL_Log("initialized SDL mixer %d.%d.%d", SDL_MIXER_MAJOR_VERSION,
|
|
SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL);
|
|
}
|
|
// if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT,
|
|
// MIX_DEFAULT_CHANNELS, 1024) < 0)
|
|
if (Mix_OpenAudio(11025, AUDIO_U8, MIX_DEFAULT_CHANNELS, 2048) < 0)
|
|
{
|
|
sb::Log::sdl_error("Could not set up audio");
|
|
}
|
|
SDL_Log("Using audio driver: %s", SDL_GetCurrentAudioDriver());
|
|
const int audio_device_count = SDL_GetNumAudioDevices(SDL_TRUE);
|
|
for (int ii = 0; ii < audio_device_count; ii++)
|
|
{
|
|
std::ostringstream message;
|
|
message << "Found audio capture device " << ii << ": " << SDL_GetAudioDeviceName(ii, SDL_TRUE);
|
|
sb::Log::log(message);
|
|
}
|
|
audio.load_sfx();
|
|
audio.load_bgm();
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
sb::Log::log("big endian");
|
|
#else
|
|
sb::Log::log("little endian");
|
|
#endif
|
|
last_frame_timestamp = SDL_GetTicks();
|
|
}
|
|
|
|
void Game::load_sdl_context()
|
|
{
|
|
if (glcontext != nullptr)
|
|
{
|
|
SDL_GL_DeleteContext(glcontext);
|
|
glcontext = nullptr;
|
|
}
|
|
SDL_RendererInfo renderer_info;
|
|
int render_driver_count = SDL_GetNumRenderDrivers();
|
|
SDL_Log("Render drivers:");
|
|
for (int ii = 0; ii < render_driver_count; ii++)
|
|
{
|
|
SDL_GetRenderDriverInfo(ii, &renderer_info);
|
|
log_renderer_info(renderer_info);
|
|
}
|
|
if ((renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)) == nullptr)
|
|
{
|
|
sb::Log::sdl_error("Could not create renderer");
|
|
flag_to_end();
|
|
}
|
|
else
|
|
{
|
|
SDL_Log("Current renderer:");
|
|
SDL_GetRendererInfo(renderer, &renderer_info);
|
|
log_renderer_info(renderer_info);
|
|
SDL_Log("Renderer supports the use of render targets? %d", SDL_RenderTargetSupported(renderer));
|
|
}
|
|
is_gl_context = false;
|
|
log_display_mode();
|
|
}
|
|
|
|
void Game::load_gl_context()
|
|
{
|
|
if (renderer != nullptr)
|
|
{
|
|
SDL_DestroyRenderer(renderer);
|
|
renderer = nullptr;
|
|
}
|
|
if ((glcontext = SDL_GL_CreateContext(window())) == nullptr)
|
|
{
|
|
sb::Log::sdl_error("Could not get GL context");
|
|
flag_to_end();
|
|
}
|
|
/* try enabling vsync */
|
|
if (SDL_GL_SetSwapInterval(1) == 0)
|
|
{
|
|
sb::Log::log("enabled vsync");
|
|
}
|
|
else
|
|
{
|
|
sb::Log::log("vsync not supported");
|
|
}
|
|
GLenum error = glewInit();
|
|
std::ostringstream message;
|
|
if (error != GLEW_OK)
|
|
{
|
|
message << "GLEW could not initialize " << glewGetErrorString(error);
|
|
sb::Log::log(message, sb::Log::ERROR);
|
|
}
|
|
message << "OpenGL " << glGetString(GL_VERSION) << ", renderer " << glGetString(GL_RENDERER) << ", shading language " <<
|
|
glGetString(GL_SHADING_LANGUAGE_VERSION);
|
|
sb::Log::log(message);
|
|
is_gl_context = true;
|
|
log_display_mode();
|
|
}
|
|
|
|
/* Overrides SDL's default log function to log a message to stdout/stderr and, if log is enabled in the
|
|
* global configuration, to a file. Debug level statements may be suppressed, printed to stdout, or printed to
|
|
* both stdout and file, depending on the global configuration. */
|
|
void Game::sdl_log_override(void* userdata, int category, SDL_LogPriority priority, const char* message)
|
|
{
|
|
Game* game = static_cast<Game*>(userdata);
|
|
std::ostream& out = (priority > SDL_LOG_PRIORITY_WARN) ? std::cerr : std::cout;
|
|
/* print to stdout/stderr if priority is higher than debug or debug statements are enabled */
|
|
if (priority > SDL_LOG_PRIORITY_DEBUG || game->configuration()["log"]["debug-to-stdout"])
|
|
{
|
|
out << message << std::endl;
|
|
}
|
|
/* handle writing to log file */
|
|
if (game->configuration()["log"]["enabled"])
|
|
{
|
|
fs::path path = game->configuration()["log"]["output-directory"];
|
|
if (!fs::exists(path))
|
|
{
|
|
fs::create_directories(path);
|
|
}
|
|
/* prepend a timestamp to the message */
|
|
std::time_t now = std::time(nullptr);
|
|
std::stringstream stamped_message;
|
|
stamped_message << std::put_time(std::localtime(&now), "%F %T ") << message;
|
|
/* if debug is enabled, append message to debug log file */
|
|
if (game->configuration()["log"]["debug-to-file"])
|
|
{
|
|
fs::path debug_path = path / game->configuration()["log"]["debug-file-name"];
|
|
std::ofstream debug_stream(debug_path, std::ios_base::app);
|
|
debug_stream << stamped_message.str() << std::endl;
|
|
}
|
|
/* only append messages to the info log that are higher than debug priority */
|
|
if (priority > SDL_LOG_PRIORITY_DEBUG)
|
|
{
|
|
fs::path info_path = path / game->configuration()["log"]["info-file-name"];
|
|
std::ofstream info_stream(info_path, std::ios_base::app);
|
|
info_stream << stamped_message.str() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Game::print_frame_length_history()
|
|
{
|
|
for (float& frame_length : frame_length_history)
|
|
{
|
|
std::cout << frame_length << ", ";
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
/* Create, compile and return the ID of a GL shader from the GLSL code at path */
|
|
GLuint Game::load_shader(const fs::path& path, GLenum type) const
|
|
{
|
|
GLuint shader = glCreateShader(type);
|
|
std::fstream file = std::fstream(path);
|
|
std::ostringstream message;
|
|
std::string contents = sb::file_to_string(path);
|
|
glShaderSource(shader, 1, reinterpret_cast<const GLchar**>(&contents), 0);
|
|
glCompileShader(shader);
|
|
GLint is_compiled;
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled);
|
|
if (is_compiled == GL_TRUE)
|
|
{
|
|
message << "compiled shader at " << path;
|
|
sb::Log::log(message);
|
|
return shader;
|
|
}
|
|
else
|
|
{
|
|
/* log error by allocating a string to copy the GL error message buffer into */
|
|
std::string error_info;
|
|
GLint max_length;
|
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);
|
|
error_info.resize(max_length, 0);
|
|
glGetShaderInfoLog(shader, error_info.size(), nullptr, error_info.data());
|
|
message << "failed to compile " << path << ": " << error_info;
|
|
sb::Log::log(message, sb::Log::Level::ERROR);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool Game::link_shader(GLuint program) const
|
|
{
|
|
glLinkProgram(program);
|
|
int is_linked;
|
|
glGetProgramiv(program, GL_LINK_STATUS, (int *) &is_linked);
|
|
std::ostringstream message;
|
|
if (is_linked == GL_TRUE)
|
|
{
|
|
message << "linked shader program " << program;
|
|
sb::Log::log(message);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
/* log error by allocating a string to copy the GL error message buffer into */
|
|
std::string error_info;
|
|
GLint max_length;
|
|
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length);
|
|
error_info.resize(max_length, 0);
|
|
glGetProgramInfoLog(program, error_info.size(), nullptr, error_info.data());
|
|
message << "failed linking shader program " << program << ": " << error_info;
|
|
sb::Log::log(message, sb::Log::Level::ERROR);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Game::log_renderer_info(SDL_RendererInfo& info)
|
|
{
|
|
std::ostringstream message;
|
|
message << "renderer name: " << info.name << ", flags: " << info.flags << ", texture formats: " <<
|
|
info.num_texture_formats << ", max texture w: " << info.max_texture_width << ", max texture h: " <<
|
|
info.max_texture_height;
|
|
sb::Log::log(message);
|
|
}
|
|
|
|
/* Write resolution, monitor refresh rate, and pixel format to the log. Code taken from SDL_GetCurrentDisplayMode.html
|
|
* on the SDL wiki */
|
|
void Game::log_display_mode()
|
|
{
|
|
SDL_DisplayMode current;
|
|
std::ostringstream message;
|
|
for (int ii = 0; ii < SDL_GetNumVideoDisplays(); ii++)
|
|
{
|
|
int mode = SDL_GetCurrentDisplayMode(ii, ¤t);
|
|
if (mode != 0)
|
|
{
|
|
message << "Could not get display mode for video display #" << ii;
|
|
sb::Log::sdl_error(message.str());
|
|
}
|
|
else
|
|
{
|
|
message << "Display #" << ii << ": display mode is " << current.w << "x" << current.h << "px @ " <<
|
|
current.refresh_rate << "hz " << get_pixel_format_string(current.format);
|
|
sb::Log::log(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Game::log_surface_format(SDL_Surface* surface, std::string preface)
|
|
{
|
|
SDL_PixelFormat* format = surface->format;
|
|
std::string pixel_format = get_pixel_format_string(format->format);
|
|
SDL_Log("%s bpp: %i mask: %i %i %i %i format: %s", preface.c_str(),
|
|
format->BytesPerPixel, format->Rmask, format->Gmask, format->Bmask,
|
|
format->Amask, pixel_format.c_str());
|
|
}
|
|
|
|
std::string Game::get_pixel_format_string(Uint32 format)
|
|
{
|
|
std::string pixel_format;
|
|
if (format == SDL_PIXELFORMAT_UNKNOWN)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_UNKNOWN";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_INDEX1LSB)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_INDEX1LSB";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_INDEX1MSB)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_INDEX1MSB";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_INDEX4LSB)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_INDEX4LSB";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_INDEX4MSB)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_INDEX4MSB";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_INDEX8)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_INDEX8";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGB332)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGB332";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGB444)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGB444";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGB555)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGB555";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGR555)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGR555";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_ARGB4444)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_ARGB4444";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGBA4444)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGBA4444";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_ABGR4444)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_ABGR4444";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGRA4444)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGRA4444";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_ARGB1555)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_ARGB1555";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGBA5551)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGBA5551";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_ABGR1555)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_ABGR1555";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGRA5551)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGRA5551";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGB565)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGB565";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGR565)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGR565";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGB24)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGB24";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGR24)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGR24";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGB888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGB888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGBX8888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGBX8888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGR888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGR888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGRX8888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGRX8888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_ARGB8888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_ARGB8888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_RGBA8888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_RGBA8888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_ABGR8888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_ABGR8888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_BGRA8888)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_BGRA8888";
|
|
}
|
|
else if (format == SDL_PIXELFORMAT_ARGB2101010)
|
|
{
|
|
pixel_format = "SDL_PIXELFORMAT_ARGB2101010";
|
|
}
|
|
return pixel_format;
|
|
}
|
|
|
|
const nlohmann::json& Game::configuration() const
|
|
{
|
|
return _configuration.config;
|
|
}
|
|
|
|
nlohmann::json& Game::configuration()
|
|
{
|
|
return _configuration.config;
|
|
}
|
|
|
|
const SDL_Window* Game::window() const
|
|
{
|
|
return _window;
|
|
}
|
|
|
|
SDL_Window* Game::window()
|
|
{
|
|
return _window;
|
|
}
|
|
|
|
const SDL_Renderer* Game::get_renderer() const
|
|
{
|
|
return renderer;
|
|
}
|
|
|
|
SDL_Renderer* Game::get_renderer()
|
|
{
|
|
return renderer;
|
|
}
|
|
|
|
const Input& Game::get_input() const
|
|
{
|
|
return input;
|
|
}
|
|
|
|
Input& Game::get_input()
|
|
{
|
|
return input;
|
|
}
|
|
|
|
Audio& Game::get_audio()
|
|
{
|
|
return audio;
|
|
}
|
|
|
|
void Game::run()
|
|
{
|
|
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
|
|
suppress_input_temporarily();
|
|
#if defined(__EMSCRIPTEN__)
|
|
SDL_Log("using emscripten main loop");
|
|
emscripten_set_main_loop_arg(&loop, this, -1, true);
|
|
#else
|
|
SDL_Log("using standard main loop");
|
|
while (!done)
|
|
{
|
|
frame(SDL_GetTicks());
|
|
SDL_Delay(8);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Game::frame(float ticks)
|
|
{
|
|
if (ticks - last_frame_timestamp + frame_time_overflow >= frame_length)
|
|
{
|
|
last_frame_length = ticks - last_frame_timestamp;
|
|
if (frame_length_history.size() == 5000)
|
|
{
|
|
frame_length_history.pop_back();
|
|
}
|
|
frame_length_history.insert(frame_length_history.begin(), last_frame_length);
|
|
frame_time_overflow = last_frame_length + frame_time_overflow - frame_length;
|
|
last_frame_timestamp = ticks;
|
|
if (last_frame_length < 1000)
|
|
{
|
|
recorder.update();
|
|
delegate.dispatch();
|
|
audio.update();
|
|
input.unsuppress_animation.update();
|
|
update();
|
|
framerate_indicator.update();
|
|
_configuration.update();
|
|
if (!is_gl_context)
|
|
{
|
|
SDL_SetRenderTarget(renderer, nullptr);
|
|
SDL_RenderPresent(renderer);
|
|
}
|
|
}
|
|
// if (frame_time_overflow > frame_length)
|
|
// {
|
|
// SDL_Log("%i frame(s) dropped", ((int) (frame_time_overflow / frame_length)));
|
|
// frame_time_overflow = 0;
|
|
// }
|
|
frame_count_this_second++;
|
|
if (ticks - last_frame_count_timestamp >= 1000)
|
|
{
|
|
framerate_indicator.refresh();
|
|
last_frame_count_timestamp = ticks;
|
|
frame_count_this_second = 0;
|
|
}
|
|
}
|
|
// std::cout << std::endl;
|
|
}
|
|
|
|
#if defined(__EMSCRIPTEN__)
|
|
|
|
void loop(void* context)
|
|
{
|
|
Game* game = static_cast<Game*>(context);
|
|
game->frame(emscripten_performance_now());
|
|
if (game->done)
|
|
{
|
|
emscripten_cancel_main_loop();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void Game::flag_to_end()
|
|
{
|
|
done = true;
|
|
}
|
|
|
|
/* Set the length of a frame in seconds by passing the framerate, the amount of frames to display
|
|
* per second */
|
|
void Game::set_framerate(int framerate)
|
|
{
|
|
if (framerate < 1)
|
|
{
|
|
framerate = 1;
|
|
}
|
|
frame_length = 1000.0 / framerate;
|
|
}
|
|
|
|
/* Return the length of a frame in seconds */
|
|
float Game::get_frame_length() const
|
|
{
|
|
return frame_length;
|
|
}
|
|
|
|
void Game::handle_quit_event(SDL_Event &event)
|
|
{
|
|
if (event.type == SDL_QUIT)
|
|
{
|
|
flag_to_end();
|
|
}
|
|
}
|
|
|
|
void Game::quit()
|
|
{
|
|
if (glcontext != nullptr)
|
|
{
|
|
SDL_GL_DeleteContext(glcontext);
|
|
}
|
|
if (renderer != nullptr)
|
|
{
|
|
SDL_DestroyRenderer(renderer);
|
|
}
|
|
if (_window != nullptr)
|
|
{
|
|
SDL_DestroyWindow(_window);
|
|
}
|
|
if (TTF_WasInit())
|
|
{
|
|
TTF_CloseFont(bp_mono_font);
|
|
TTF_Quit();
|
|
}
|
|
Mix_CloseAudio();
|
|
Mix_Quit();
|
|
SDL_Quit();
|
|
}
|
|
|
|
Game::~Game()
|
|
{
|
|
get_delegate().unsubscribe(this);
|
|
}
|
|
|
|
FramerateIndicator::FramerateIndicator(Node* parent) : Sprite(parent)
|
|
{
|
|
get_delegate().subscribe(&FramerateIndicator::respond, this);
|
|
hide();
|
|
}
|
|
|
|
void FramerateIndicator::respond(SDL_Event& event)
|
|
{
|
|
if (get_delegate().compare(event, "toggle-framerate"))
|
|
{
|
|
toggle_hidden();
|
|
}
|
|
}
|
|
|
|
SDL_Surface* FramerateIndicator::get_surface()
|
|
{
|
|
std::string padded = sb::pad(get_root()->frame_count_this_second, 2);
|
|
SDL_Surface* shaded = TTF_RenderText_Shaded(
|
|
get_root()->bp_mono_font, padded.c_str(), {0, 0, 0, 255}, {255, 255, 255, 255});
|
|
if (!shaded)
|
|
{
|
|
sb::Log::sdl_error("Could not create text");
|
|
}
|
|
return shaded;
|
|
}
|
|
|
|
void FramerateIndicator::refresh()
|
|
{
|
|
if (!is_hidden() && get_root()->bp_mono_font != nullptr)
|
|
{
|
|
unload();
|
|
SDL_Surface* surface = get_surface();
|
|
SDL_Texture* texture = SDL_CreateTextureFromSurface(get_root()->get_renderer(), surface);
|
|
add_frames(texture);
|
|
SDL_FreeSurface(surface);
|
|
set_ne(get_display().window_box().ne());
|
|
}
|
|
}
|