diff --git a/src/Configuration.cpp b/src/Configuration.cpp index b091bce..c6d3e49 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -33,8 +33,7 @@ void Configuration::set_defaults() }; sys_config["input"] = { {"suppress-any-key-on-mods", true}, - {"system-any-key-ignore-commands", - {"fullscreen", "screenshot", "toggle-framerate", "record", "quit"}}, + {"system-any-key-ignore-commands", {"fullscreen", "screenshot", "toggle-framerate", "record", "quit"}}, {"any-key-ignore-commands", {}}, {"default-unsuppress-delay", 700}, {"ignore-repeat-keypress", true} diff --git a/src/Display.cpp b/src/Display.cpp index c6b25eb..2217489 100644 --- a/src/Display.cpp +++ b/src/Display.cpp @@ -1,11 +1,13 @@ #include "Display.hpp" #include "Game.hpp" +/* Create a Display instance and subscribe to commands */ Display::Display(Node* parent) : Node(parent) { get_delegate().subscribe(&Display::respond, this); } - + +/* Return the (x, y) size in pixels of the window as an integer vector */ glm::ivec2 Display::get_window_size() const { glm::ivec2 size; @@ -13,11 +15,13 @@ glm::ivec2 Display::get_window_size() const return size; } +/* Return the window dimensions as a Box object */ Box Display::get_window_box() const { return Box(glm::vec2(0, 0), get_window_size()); } +/* Get the pixel format of display at specified index (defaults to index 0) */ Uint32 Display::get_pixel_format(int display_index) const { SDL_DisplayMode display_mode; @@ -32,30 +36,32 @@ Uint32 Display::get_pixel_format(int display_index) const } } +/* Fill the supplied, pre-allocated buffer with 32-bit pixels (8 bits per component) from the GL + * read buffer if in GL context or from the SDL renderer if in SDL context */ void Display::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y) const { if (get_root()->is_gl_context) { -// GLenum format; -// #if SDL_BYTEORDER == SDL_BIG_ENDIAN -// format = GL_BGRA; -// #else -// format = GL_RGBA; -// #endif -// glReadBuffer(GL_FRONT); -// glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels); -// // SDL_Log("(%i, %i, %i) (%i, %i, %i, %i)", -// // glCheckFramebufferStatus(GL_FRAMEBUFFER), -// // glCheckFramebufferStatus(GL_READ_FRAMEBUFFER), -// // glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER), -// // pixels[0], pixels[1], pixels[2], pixels[3]); + GLenum format; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + format = GL_BGRA; +#else + format = GL_RGBA; +#endif + glReadBuffer(GL_FRONT); + glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels); + // SDL_Log("(%i, %i, %i) (%i, %i, %i, %i)", + // glCheckFramebufferStatus(GL_FRAMEBUFFER), + // glCheckFramebufferStatus(GL_READ_FRAMEBUFFER), + // glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER), + // pixels[0], pixels[1], pixels[2], pixels[3]); } else { SDL_Renderer* renderer = const_cast(get_renderer()); SDL_SetRenderTarget(renderer, nullptr); SDL_RenderPresent(renderer); - SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_RGBA32, pixels, bpp / 8 * w); + SDL_RenderReadPixels(renderer, nullptr, SDL_PIXELFORMAT_RGBA32, pixels, bpp / 8 * w); } } @@ -64,8 +70,7 @@ SDL_Surface* Display::get_screen_surface() const glm::ivec2 size = get_window_size(); unsigned char* pixels = new unsigned char[bpp / 8 * size.x * size.y]; get_screen_pixels(pixels, size.x, size.y); - SDL_Surface* surface = get_screen_surface_from_pixels( - pixels, get_root()->is_gl_context); + SDL_Surface* surface = get_screen_surface_from_pixels(pixels, get_root()->is_gl_context); delete[] pixels; return surface; } @@ -101,6 +106,7 @@ SDL_Surface* Display::get_screen_surface_from_pixels(unsigned char* pixels, bool return surface; } +/* Handle fullscreen request */ void Display::respond(SDL_Event& event) { if (get_delegate().compare(event, "fullscreen")) @@ -109,17 +115,19 @@ void Display::respond(SDL_Event& event) } } +/* Use SDL window flags to determine if fullscreen is on or off and use SDL fullscreen + * function to toggle the fullscreen state */ void Display::toggle_fullscreen() const { SDL_Window* window = const_cast(get_window()); if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) { - SDL_Log("fullscreen requested"); + log("fullscreen requested"); SDL_SetWindowFullscreen(window, 0); } else { - SDL_Log("exit fullscreen requested"); + log("exit fullscreen requested"); SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); } } diff --git a/src/Game.cpp b/src/Game.cpp index 6a4a94f..0ef5a60 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -629,6 +629,8 @@ Audio& Game::get_audio() return audio; } +/* Applies delta timing to a vector: returns the passed vector as weighted by the amount of time passed since the + * last frame update, allowing for vector values to change the same amount over time independent of the frame rate */ glm::vec2 Game::weight(glm::vec2 motion) { return {weight(motion.x), weight(motion.y)}; @@ -669,10 +671,10 @@ void Game::frame(float ticks) // std::cout << ", last_frame_length: " << last_frame_length << " [rendering frame]"; if (last_frame_length < 1000) { - if (!is_gl_context) - { + // if (!is_gl_context) + // { recorder.update(); - } + // } delegate.dispatch(); audio.update(); input.unsuppress_animation.update(); @@ -719,16 +721,23 @@ void Game::flag_to_end() done = true; } -void Game::set_framerate(int f) +/* 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 (f < 1) + if (framerate < 1) { - f = 1; + framerate = 1; } - framerate = f; 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) diff --git a/src/Game.hpp b/src/Game.hpp index 0ab1f88..1a28967 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -50,10 +50,14 @@ class Game : public Node private: + int ticks; + float frame_length = 1000.0 / 60.0; + static void sdl_log_override(void*, int, SDL_LogPriority, const char*); public: - + + /* Prevent an instance of this class from being copied or moved */ Game(const Game&) = delete; Game& operator=(const Game&) = delete; Game(Game&&) = delete; @@ -63,9 +67,8 @@ public: SDL_Window* window; SDL_Renderer* renderer = nullptr; SDL_GLContext glcontext = nullptr; - int frame_count_this_second = 0, framerate, ticks, last_frame_length; - float frame_length = 1000.0 / 60.0, frame_time_overflow = 0, last_frame_timestamp, - last_frame_count_timestamp, emscripten_previous_time; + int frame_count_this_second = 0, last_frame_length; + float frame_time_overflow = 0, last_frame_timestamp, last_frame_count_timestamp; bool done = false, show_framerate = true, is_gl_context = true; Configuration configuration = Configuration(this); Delegate delegate = Delegate(this); @@ -106,11 +109,14 @@ public: void flag_to_end(); virtual void update() {}; void set_framerate(int); + float get_frame_length() const; void handle_quit_event(SDL_Event&); void quit(); virtual std::string get_class_name() const { return "Game"; } ~Game(); + /* Applies delta timing to a value: returns the value as weighted by the amount of time passed since the + * last frame update, allowing for values to change the same amount over time independent of the frame rate */ template float weight(T amount) { diff --git a/src/Recorder.cpp b/src/Recorder.cpp index a1c57d2..acbeea7 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -3,23 +3,28 @@ #include "extension.hpp" #include "Recorder.hpp" +/* Create a Recorder instance. Subscribe to command input and set audio callback. + * Only will be active if enabled by the configuration, in which case frames will + * automatically begin to be stashed */ Recorder::Recorder(Node* parent) : Node(parent) { get_delegate().subscribe(&Recorder::respond, this); animation.play(); - Mix_SetPostMix(&process_audio, this); + Mix_SetPostMix(Recorder::process_audio, this); if (!get_configuration()["recording"]["enabled"]) { deactivate(); } } -float Recorder::get_frame_length() +/* Returns length of a recorded video frame in seconds. Defaults to the frame length of the game if this hasn't + * been configured by the user. */ +float Recorder::frame_length() { - return get_configuration()["recording"].value( - "video-frame-length", get_root()->frame_length); + return get_configuration()["recording"].value("video-frame-length", get_root()->get_frame_length()); } +/* Handle commands for screenshot, record video and save video */ void Recorder::respond(SDL_Event& event) { if (get_delegate().compare(event, "screenshot")) @@ -51,40 +56,48 @@ void Recorder::respond(SDL_Event& event) } } +/* Save the current screen pixels as a PNG in the path specified by the configuration. Parent + * directories in the path will be created if necessary. The file name will be auto generated + * based on the configuration. An index will be included in the file name based on previous + * screenshots found in the output directory. */ void Recorder::capture_screen() { nlohmann::json config = get_configuration(); SDL_Surface* surface = get_display().get_screen_surface(); fs::path directory = config["recording"]["screenshot-directory"]; fs::create_directories(directory); - std::string prefix = config["recording"]["screenshot-prefix"]. - get(); - std::string extension = config["recording"]["screenshot-extension"]. - get(); + std::string prefix = config["recording"]["screenshot-prefix"].get(); + std::string extension = config["recording"]["screenshot-extension"].get(); int zfill = config["recording"]["screenshot-zfill"]; fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension); IMG_SavePNG(surface, path.c_str()); SDL_FreeSurface(surface); - SDL_Log("Saved screenshot to %s", path.c_str()); + std::ostringstream message; + message << "saved screenshot to " << path; + log(message.str()); } +/* Writes a video of what was just displayed on the screen up until the function was called. The length + * of the video is determined by the stash length written in the configuration. This is accomplished by + * writing the contents of the most recent Stash object. */ void Recorder::grab_stash() { if (!is_recording and !writing_recording) { int length = get_configuration()["recording"]["max-stash-length"]; - SDL_Log("Stashing most recent %i seconds of video...", length / 1000); + std::ostringstream message; + message << "stashing most recent " << length / 1000.0f << " seconds of video"; + log(message.str()); most_recent_stash = current_stash; current_stash = Stash(); writing_recording = true; - std::function f = - std::bind(&Recorder::write_most_recent_frames, this); + std::function f = std::bind(&Recorder::write_most_recent_frames, this); std::thread writing(f); writing.detach(); } else { - SDL_Log("Recording in progress, cannot grab most recent frames"); + log("recording in progress, cannot grab most recent frames"); } } @@ -95,19 +108,16 @@ void Recorder::write_most_recent_frames() open_audio_file(); while (!most_recent_stash.audio_buffers.empty()) { - write_audio(most_recent_stash.audio_buffers.front(), - most_recent_stash.audio_buffer_lengths.front()); - most_recent_stash.audio_buffers.erase( - most_recent_stash.audio_buffers.begin()); - most_recent_stash.audio_buffer_lengths.erase( - most_recent_stash.audio_buffer_lengths.begin()); + write_audio(most_recent_stash.audio_buffers.front(), most_recent_stash.audio_buffer_lengths.front()); + most_recent_stash.audio_buffers.erase(most_recent_stash.audio_buffers.begin()); + most_recent_stash.audio_buffer_lengths.erase(most_recent_stash.audio_buffer_lengths.begin()); } audio_file.close(); if (get_configuration()["recording"]["write-mp4"]) { write_mp4(); } - SDL_Log("Wrote video frames to %s", current_video_directory.c_str()); + SDL_Log("wrote video frames to %s", current_video_directory.c_str()); writing_recording = false; } @@ -115,7 +125,7 @@ void Recorder::start_recording() { if (!writing_recording) { - SDL_Log("Starting recording..."); + log("starting recording"); is_recording = true; video_stashes.push_back(Stash()); make_directory(); @@ -123,7 +133,7 @@ void Recorder::start_recording() } else { - SDL_Log("Writing in progress, cannot start recording"); + log("writing in progress, cannot start recording"); } } @@ -142,7 +152,7 @@ void Recorder::add_frame() unsigned char* pixels = new unsigned char[bytes]; get_display().get_screen_pixels(pixels, size.x, size.y); int max_length = get_configuration()["recording"]["max-stash-length"]; - float length = get_frame_length() * current_stash.pixel_buffers.size(); + float length = frame_length() * current_stash.pixel_buffers.size(); if (length > max_length) { delete[] current_stash.pixel_buffers.front(); @@ -157,10 +167,9 @@ void Recorder::add_frame() memcpy(vid_pixels, pixels, bytes); video_stashes.back().pixel_buffers.push_back(vid_pixels); video_stashes.back().flipped.push_back(get_root()->is_gl_context); - if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length) + if (video_stashes.back().pixel_buffers.size() * frame_length() > max_length) { - std::function f = - std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1); + std::function f = std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1); std::thread writing(f, &video_stashes.back()); writing.detach(); // int frame_offset = video_stashes.back().frame_offset; @@ -182,9 +191,8 @@ void Recorder::add_frame() // write_stash_frames(video_stashes.back().pixel_buffers, // video_stashes.back().flipped, // video_stashes.back().frame_offset); - video_stashes.push_back( - Stash(video_stashes.back().frame_offset + - video_stashes.back().pixel_buffers.size())); + video_stashes.push_back(Stash(video_stashes.back().frame_offset + + video_stashes.back().pixel_buffers.size())); } } } @@ -264,7 +272,7 @@ void Recorder::write_stash_frames(Stash* stash) GifWriteFrame(&gif_writer, (const uint8_t*) converted->pixels, frame->w, frame->h, gif_frame_length / 10); } - elapsed += get_frame_length(); + elapsed += frame_length(); delete[] stash->pixel_buffers.front(); stash->pixel_buffers.erase(stash->pixel_buffers.begin()); stash->flipped.erase(stash->flipped.begin()); @@ -336,7 +344,7 @@ void Recorder::write_mp4() std::string pixel_format = get_configuration()["recording"]["mp4-pixel-format"].get(); fs::path images_match = current_video_directory / "%05d.png"; mp4_command << "ffmpeg -f s16le -ac 2 -ar 22050 -i " << current_audio_path.string() << - " -f image2 -framerate " << (1000 / get_frame_length()) << + " -f image2 -framerate " << (1000 / frame_length()) << " -i " << images_match.string() << " -s " << size.x << "x" << size.y << " -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " << current_video_directory.string() << ".mp4"; @@ -352,22 +360,21 @@ void Recorder::write_audio(Uint8* stream, int len) void Recorder::update() { - if (is_recording and get_memory_size() > - get_configuration()["recording"]["max-video-memory"]) + if (is_recording and get_memory_size() > get_configuration()["recording"]["max-video-memory"]) { end_recording(); } - animation.set_frame_length(get_frame_length()); + animation.set_frame_length(frame_length()); animation.update(); } -void process_audio(void* context, Uint8* stream, int len) +void Recorder::process_audio(void* context, Uint8* stream, int len) { Recorder* recorder = static_cast(context); if (recorder->is_active()) { int max_length = recorder->get_configuration()["recording"]["max-stash-length"]; - float length = recorder->get_frame_length() * recorder->current_stash.pixel_buffers.size(); + float length = recorder->frame_length() * recorder->current_stash.pixel_buffers.size(); if (length > max_length) { delete[] recorder->current_stash.audio_buffers.front(); diff --git a/src/Recorder.hpp b/src/Recorder.hpp index 8fb3f6c..c13153c 100644 --- a/src/Recorder.hpp +++ b/src/Recorder.hpp @@ -32,9 +32,11 @@ struct Stash Stash(int frame_offset = 0) : frame_offset(frame_offset) {} }; -struct Recorder : Node +class Recorder : public Node { +private: + Stash current_stash = Stash(); Stash most_recent_stash; std::list in_game_stashes; @@ -44,8 +46,12 @@ struct Recorder : Node bool is_recording = false, writing_recording = false, writing_most_recent = false; std::ofstream audio_file; + float frame_length(); + static void process_audio(void*, Uint8*, int); + +public: + Recorder(Node*); - float get_frame_length(); void respond(SDL_Event&); void capture_screen(); void grab_stash(); @@ -66,6 +72,4 @@ struct Recorder : Node }; -void process_audio(void*, Uint8*, int); - #endif diff --git a/src/extension.cpp b/src/extension.cpp index a9745cd..d93f7cc 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -6,11 +6,32 @@ void sfw::set_magnitude(glm::vec2& vector, float magnitude) vector = glm::normalize(vector) * magnitude; } +/* Return coordinates of a point x, y at specified angle on a circle described by center and radius */ glm::vec2 sfw::get_point_on_circle(const glm::vec2& center, float radius, float angle) { return {center.x + std::sin(angle) * radius, center.y - std::cos(angle) * radius}; } +/* Return a point x, y at specified angle on a circle at the origin with specified radius (default 1) */ +glm::vec2 sfw::get_point_on_circle(float angle, float radius) +{ + return get_point_on_circle({0, 0}, radius, angle); +} + +/* Return a vector of count number of points evenly spaced around a circle starting at the angle offset + * (defaults to 0) */ +std::vector sfw::get_points_on_circle(int count, float radius, const glm::vec2& center, float offset) +{ + std::vector points; + points.reserve(count); + float step = glm::two_pi() / count; + for (int ii = 0; ii < count; ii++) + { + points.push_back(get_point_on_circle(center, radius, ii * step + offset)); + } + return points; +} + Box sfw::get_texture_box(SDL_Texture* texture) { int width, height; @@ -637,6 +658,12 @@ std::ostream& operator<<(std::ostream& out, const glm::vec2& vector) return out; } +std::ostream& operator<<(std::ostream& out, const glm::vec3& vector) +{ + out << "{" << vector.x << ", " << vector.y << ", " << vector.z << "}"; + return out; +} + std::ostream& operator<<(std::ostream& out, const SDL_Color& color) { out << "{" << static_cast(color.r) << ", " << static_cast(color.g) << ", " << diff --git a/src/extension.hpp b/src/extension.hpp index f2c349c..1636f03 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -37,6 +37,8 @@ namespace sfw void set_magnitude(glm::vec2&, float); glm::vec2 get_point_on_circle(const glm::vec2&, float, float); + glm::vec2 get_point_on_circle(float, float = 1.0f); + std::vector get_points_on_circle(int, float = 1.0f, const glm::vec2& = {0, 0}, float = 0.0f); Box get_texture_box(SDL_Texture*); glm::vec2 fit_and_preserve_aspect(const glm::vec2&, const glm::vec2&); std::vector> get_blinds_boxes(glm::vec2, float = 0.05f, int = 4); @@ -187,6 +189,7 @@ std::ostream& operator<<(std::ostream& out, const std::vector& members) } std::ostream& operator<<(std::ostream&, const glm::vec2&); +std::ostream& operator<<(std::ostream&, const glm::vec3&); std::ostream& operator<<(std::ostream&, const SDL_Color&); #endif