diff --git a/demo/Demo.cpp b/demo/Demo.cpp index ecae7f1..f334354 100644 --- a/demo/Demo.cpp +++ b/demo/Demo.cpp @@ -1,8 +1,9 @@ /*** reset, pause, auto reset, analog d-pad, gamepad config, any key, confirm exit - game, screen wipes + game, screen wipes, screen offset, screen scale :) SWEATY HANDS :) OILY SNACKS :) AND BAD HYGIENE :) + *surf's up broccoli* ***/ @@ -65,56 +66,6 @@ int link_shader(GLuint program) return 0; } -SDL_Surface* get_screen_surface(SDL_Window *window) -{ - int w, h; - SDL_GetWindowSize(window, &w, &h); - unsigned char* pixels = new unsigned char[24 * w * h]; - GLenum format; -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - format = GL_RGB; -#else - format = GL_BGR; -#endif - glReadPixels(0, 0, w, h, format, GL_UNSIGNED_BYTE, pixels); - SDL_Surface *surface = zoomSurface( - SDL_CreateRGBSurfaceFrom(pixels, w, h, 24, 3 * w, - 0, 0, 0, 0), 1, -1, SMOOTHING_OFF); - delete[] pixels; - return surface; -} - -void capture_screen(SDL_Window *window) -{ - SDL_Surface *surface = get_screen_surface(window); - IMG_SavePNG(surface, "screen.png"); - printf("saved png to screen.png\n"); - SDL_FreeSurface(surface); -} - -void start_recording(bool *is_recording) -{ - *is_recording = true; - printf("start recording\n"); -} - -void end_recording(std::list frames, bool *is_recording) -{ - *is_recording = false; - printf("end recording\n"); - SDL_Surface *frame; - int ii = 0; - while (not frames.empty()) - { - frame = frames.front(); - char path[22]; - sprintf(path, "frames/%03i.png", ii++); - IMG_SavePNG(frame, path); - frames.pop_front(); - SDL_FreeSurface(frame); - } -} - GLuint get_gl_texture_from_surface(SDL_Surface *surface, GLint mipmap_filter) { GLuint id; @@ -162,12 +113,9 @@ struct Demo : Game { SDL_Texture *grass_texture; - int recording_capture_framerate = 100, frame_time_overflow = 0, - capture_time_overflow = 0, frame_count = 0, frame_count_timestamp, - last_capture_timestamp; - std::list frames; - bool is_recording = false, right_active = false, down_active = false, - left_active = false, up_active = false; + int frame_count = 0, frame_count_timestamp; + bool right_active = false, down_active = false, left_active = false, + up_active = false; SDL_Event event; GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program, world_program, fake_texture_id; @@ -341,7 +289,7 @@ struct Demo : Game glBindTexture(GL_TEXTURE_2D, space_texture_id); glUniform1i(sampler_uniform_id, 0); glDepthFunc(GL_LESS); - frame_count_timestamp = last_capture_timestamp = SDL_GetTicks(); + frame_count_timestamp = SDL_GetTicks(); framerate_texture_id = get_gl_texture_from_surface( get_framerate_indicator_surface(frame_count), GL_LINEAR); } @@ -367,18 +315,7 @@ struct Demo : Game // { // if (event.type == SDL_KEYDOWN) // { - // if (event.key.keysym.sym == SDLK_F10) - // { - // if (not is_recording) - // { - // start_recording(&is_recording); - // } - // else - // { - // end_recording(frames, &is_recording); - // } - // } - // else if (event.key.keysym.sym == SDLK_F11) + // if (event.key.keysym.sym == SDLK_F11) // { // if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) // { @@ -441,20 +378,6 @@ struct Demo : Game // } // } // } - if (is_recording and ticks - last_capture_timestamp + capture_time_overflow > - recording_capture_framerate) - { - frames.push_back(get_screen_surface(window)); - printf("added frame at %i\n", ticks); - capture_time_overflow = ticks - last_capture_timestamp + capture_time_overflow - - recording_capture_framerate; - last_capture_timestamp = ticks; - for (int ii = 1; capture_time_overflow > recording_capture_framerate; - ii++, capture_time_overflow -= recording_capture_framerate) - { - fprintf(stderr, "lost %i frame(s) during capture\n", ii); - } - } if (is_gl_context) { // glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); diff --git a/demo/Makefile b/demo/Makefile index f2f8b05..770ec23 100644 --- a/demo/Makefile +++ b/demo/Makefile @@ -36,18 +36,21 @@ $(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h $(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h $(CC_LINUX) $(CFLAGS) $< -o $@ -$(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Location.*pp) -$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp Recorder.*pp) -$(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp) -$(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp) -$(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp Node.*pp) +$(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Location.*pp Node.*pp) +$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp \ + Recorder.*pp Node.*pp) +$(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp Node.*pp) +$(SFW_SRC_DIR)Animation.o: $(addprefix $(SFW_SRC_DIR),Timer.*pp) +$(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp Node.*pp) +$(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp) $(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@ -Demo.o: Demo.cpp Demo.hpp $(addprefix $(SFW_SRC_DIR),Sprite.*pp Node.*pp Game.*pp Location.*pp Input.*pp Recorder.*pp) +Demo.o: Demo.cpp Demo.hpp $(addprefix $(SFW_SRC_DIR),Sprite.*pp Node.*pp Game.*pp Location.*pp Input.*pp \ + Recorder.*pp Timer.*pp Animation.*pp extension.*pp) $(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@ linux: Demo.o $(addprefix $(SFW_SRC_DIR),Sprite.o Node.o Game.o Location.o Configuration.o Input.o Delegate.o \ - Display.o Recorder.o extension.o) \ + Display.o Recorder.o Timer.o Animation.o extension.o) \ $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(CPPC_LINUX) $(LFLAGS) -D__LINUX__ $^ -lGL -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -o demo diff --git a/demo/config.json b/demo/config.json index a0cc862..88fff3f 100644 --- a/demo/config.json +++ b/demo/config.json @@ -5,7 +5,8 @@ }, "path": { - "screenshots": "local/screenshots" + "screenshots": "local/screenshots", + "video": "local/video" }, "gamepad": { diff --git a/src/Animation.cpp b/src/Animation.cpp new file mode 100644 index 0000000..2701edf --- /dev/null +++ b/src/Animation.cpp @@ -0,0 +1,70 @@ +#include "Animation.hpp" + +void Animation::play(int delay, bool play_once) +{ + this->delay = delay; + playing = true; + paused = false; + previous_step_time = timer.elapsed; + overflow = 0; + count = 0; + ending = play_once; + if (delay <= 0) + { + timer.toggle(true); + } +} + +void Animation::play_once(int delay) +{ + play(delay, true); +} + +void Animation::pause() +{ + timer.toggle(false); + paused = true; +} + +void Animation::unpause() +{ + timer.toggle(true); + paused = false; +} + +void Animation::reset() +{ + timer.toggle(false); + playing = false; + timer.reset(); +} + +void Animation::update() +{ + timer.update(); + if (playing and not paused) + { + if (delay > 0) + { + delay -= timer.frame_duration; + if (delay <= 0) + { + timer.toggle(true); + } + } + if (delay <= 0) + { + if (timer.elapsed - previous_step_time + overflow > framerate) + { + overflow = timer.elapsed - previous_step_time + overflow - framerate; + previous_step_time = timer.elapsed; + step(); + count++; + if (ending) + { + reset(); + } + } + } + } +} diff --git a/src/Animation.hpp b/src/Animation.hpp new file mode 100644 index 0000000..5764b5d --- /dev/null +++ b/src/Animation.hpp @@ -0,0 +1,36 @@ +#ifndef Animation_h_ +#define Animation_h_ + +#include +#include +#include + +#include "Timer.hpp" + +typedef std::function callback; + +struct Animation +{ + + bool playing = false, ending = false, paused = false; + int previous_step_time = 0, delay = 0, overflow = 0, count = 0, framerate; + callback step; + Timer timer = Timer(); + + template + Animation(void(T::*f)(), T* o, int framerate = 0) : framerate(framerate) + { + step = std::bind(f, o); + timer.toggle(false); + } + + void play(int = 0, bool = false); + void play_once(int = 0); + void pause(); + void unpause(); + void reset(); + void update(); + +}; + +#endif diff --git a/src/Configuration.cpp b/src/Configuration.cpp index bd4f909..94f3bbe 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -15,20 +15,24 @@ void Configuration::set_defaults() sys_config["keys"] = { {"record", {"CTRL", "SHIFT", "f10"}}, {"screenshot", "f9"}, - {"action", " "}, + {"action", "space"}, {"up", "up"}, {"right", "right"}, {"down", "down"}, - {"left", "left"} + {"left", "left"}, + {"pause", "enter"}, + {"fullscreen", {"ALT", "enter"}} }; sys_config["path"] = { - {"screenshots", "."} + {"screenshots", "."}, + {"video", "."} }; sys_config["display"] = { {"dimensions", {640, 480}}, {"screenshot-prefix", "screenshot-"}, {"screenshot-extension", ".png"}, - {"screenshot-zfill", 5} + {"screenshot-zfill", 5}, + {"recording-framerate", 100} }; } diff --git a/src/Display.cpp b/src/Display.cpp index 6296324..518a541 100644 --- a/src/Display.cpp +++ b/src/Display.cpp @@ -9,3 +9,27 @@ glm::ivec2 Display::get_window_size() SDL_GetWindowSize(get_root()->window, &size.x, &size.y); return size; } + +void Display::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y) +{ + GLenum format; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + format = GL_RGB; +#else + format = GL_BGR; +#endif + glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels); +} + +SDL_Surface* Display::get_screen_surface() +{ + 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 = SDL_CreateRGBSurfaceFrom( + pixels, size.x, size.y, bpp, bpp / 8 * size.x, 0, 0, 0, 0); + SDL_Surface* zoomed_surface = zoomSurface(surface, 1, -1, SMOOTHING_OFF); + SDL_FreeSurface(surface); + delete[] pixels; + return zoomed_surface; +} diff --git a/src/Display.hpp b/src/Display.hpp index 42b1012..a20478f 100644 --- a/src/Display.hpp +++ b/src/Display.hpp @@ -4,6 +4,14 @@ #define GLM_ENABLE_EXPERIMENTAL #include "glm/vec2.hpp" +#define GL_GLEXT_PROTOTYPES +#define GLEW_STATIC +#include "glew/glew.h" + +#include +#include "sdl2-gfx/SDL2_gfxPrimitives.h" +#include "sdl2-gfx/SDL2_rotozoom.h" + #include "SDL.h" #include "Node.hpp" @@ -11,8 +19,12 @@ struct Display : Node { + const static int bpp = 24; + Display(Node*); glm::ivec2 get_window_size(); + SDL_Surface* get_screen_surface(); + void get_screen_pixels(unsigned char*, int, int, int = 0, int = 0); }; diff --git a/src/Game.cpp b/src/Game.cpp index e576e9c..e33a8ce 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -130,6 +130,7 @@ void Game::run() last_frame_length = ticks - last_frame_timestamp; frame_time_overflow = last_frame_length + frame_time_overflow - frame_length; last_frame_timestamp = ticks; + recorder->update(); delegate->dispatch(); update(); } diff --git a/src/Input.hpp b/src/Input.hpp index 7fa417a..f385e06 100644 --- a/src/Input.hpp +++ b/src/Input.hpp @@ -40,7 +40,9 @@ struct Input : Node {"f9", SDLK_F9}, {"f10", SDLK_F10}, {"f11", SDLK_F11}, - {"f12", SDLK_F11} + {"f12", SDLK_F11}, + {"enter", SDLK_RETURN}, + {"space", SDLK_SPACE} }; std::vector key_map; diff --git a/src/Node.cpp b/src/Node.cpp index e39c197..16fe87d 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -15,16 +15,6 @@ nlohmann::json& Node::get_configuration() return get_root()->configuration->config; } -Delegate* Node::get_delegate() -{ - return get_root()->delegate; -} - -Display* Node::get_display() -{ - return get_root()->display; -} - Game* Node::get_root() { Node *current = this; @@ -35,6 +25,16 @@ Game* Node::get_root() return static_cast(current); } +Delegate* Node::get_delegate() +{ + return get_root()->delegate; +} + +Display* Node::get_display() +{ + return get_root()->display; +} + void Node::print_branch() { Node *current = this; diff --git a/src/Node.hpp b/src/Node.hpp index 7b4fade..477d697 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -11,6 +11,7 @@ struct Game; struct Delegate; struct Display; +struct TimeFilter; struct Node { diff --git a/src/Recorder.cpp b/src/Recorder.cpp index fb6af7a..9df5125 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -11,65 +11,71 @@ void Recorder::respond(SDL_Event& event) { capture_screen(); } + else if (get_delegate()->compare(event, "record")) + { + if (animation.playing) + { + end_recording(); + } + else + { + start_recording(); + } + } } void Recorder::capture_screen() { nlohmann::json config = get_configuration(); - SDL_Surface* surface = get_screen_surface(); + SDL_Surface* surface = get_display()->get_screen_surface(); fs::path directory = config["path"]["screenshots"]; fs::create_directories(directory); std::string prefix = config["display"]["screenshot-prefix"]. get(); std::string extension = config["display"]["screenshot-extension"]. get(); - std::stringstream file_pattern; - file_pattern << prefix << "(.*)" << extension; - fs::path query = directory / file_pattern.str(); - std::vector files = sfw::glob(query); int zfill = config["display"]["screenshot-zfill"].get(); - int index = 1; - if (files.size()) - { - const std::string last = files.back().string(); - std::smatch matches; - std::regex_match(last, matches, std::regex(query.string())); - index = std::stoi(matches[1]) + 1; - } - std::stringstream filename; - fs::path path; - do - { - filename << prefix << sfw::pad(index++, zfill) << extension; - path = directory / filename.str(); - filename.str(""); - filename.clear(); - } - while (fs::exists(path)); + fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension); IMG_SavePNG(surface, path.c_str()); - std::cout << "screenshot saved to " << path.string() << std::endl; + SDL_FreeSurface(surface); + std::cout << "Saved screenshot to " << path.string() << std::endl; } -SDL_Surface* Recorder::get_screen_surface() +void Recorder::start_recording() { - glm::ivec2 size = get_display()->get_window_size(); - unsigned char* pixels = new unsigned char[24 * size.x * size.y]; - get_screen_pixels(pixels, size.x, size.y); - SDL_Surface* surface = zoomSurface( - SDL_CreateRGBSurfaceFrom( - pixels, size.x, size.y, 24, 3 * size.x, 0, 0, 0, 0), - 1, -1, SMOOTHING_OFF); - delete[] pixels; - return surface; + std::cout << "Starting recording..." << std::endl; + animation.play(); } -void Recorder::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y) +void Recorder::add_frame_to_video() { - GLenum format; -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - format = GL_RGB; -#else - format = GL_BGR; -#endif - glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels); + frames.push_back(get_display()->get_screen_surface()); +} + +void Recorder::end_recording() +{ + std::cout << "Ending recording..." << std::endl; + animation.reset(); + SDL_Surface* frame; + nlohmann::json config = get_configuration(); + fs::path root = config["path"]["video"]; + fs::create_directories(root); + fs::path directory = sfw::get_next_file_name(root, 5, "video-"); + fs::create_directories(directory); + std::cout << "Writing recording to " << directory << "..." << std::endl; + for (int ii = 0; not frames.empty(); ii++) + { + frame = frames.front(); + std::stringstream name; + name << sfw::pad(ii, 5) << ".png"; + fs::path path = directory / name.str(); + IMG_SavePNG(frame, path.string().c_str()); + frames.erase(frames.begin()); + SDL_FreeSurface(frame); + } +} + +void Recorder::update() +{ + animation.update(); } diff --git a/src/Recorder.hpp b/src/Recorder.hpp index 6a27616..8031ff4 100644 --- a/src/Recorder.hpp +++ b/src/Recorder.hpp @@ -9,18 +9,10 @@ #define GLM_ENABLE_EXPERIMENTAL #include "glm/ext.hpp" -#define GL_GLEXT_PROTOTYPES -#define GLEW_STATIC -#include "glew/glew.h" - -#include -#include "sdl2-gfx/SDL2_gfxPrimitives.h" -#include "sdl2-gfx/SDL2_rotozoom.h" - #include "json/json.hpp" #include "filesystem.hpp" -#include "Node.hpp" +#include "Animation.hpp" #include "Delegate.hpp" #include "Display.hpp" #include "extension.hpp" @@ -28,11 +20,17 @@ struct Recorder : Node { + std::vector frames; + Animation animation = Animation(&Recorder::add_frame_to_video, this, 100); + Recorder(Node*); void respond(SDL_Event&); void capture_screen(); - SDL_Surface* get_screen_surface(); - void get_screen_pixels(unsigned char*, int, int, int = 0, int = 0); + void start_recording(); + void add_frame_to_video(); + void end_recording(); + void update(); + std::string get_class_name() { return "Recorder"; } }; diff --git a/src/Timer.cpp b/src/Timer.cpp new file mode 100644 index 0000000..9511a97 --- /dev/null +++ b/src/Timer.cpp @@ -0,0 +1,33 @@ +#include "Timer.hpp" + +Timer::Timer() +{ + ticks = SDL_GetTicks(); + ticks_previous = ticks; +} + +void Timer::toggle() +{ + is_timing = not is_timing; +} + +void Timer::toggle(bool state) +{ + is_timing = state; +} + +void Timer::reset() +{ + elapsed = 0; +} + +void Timer::update() +{ + ticks = SDL_GetTicks(); + frame_duration = ticks - ticks_previous; + if (is_timing) + { + elapsed += frame_duration; + } + ticks_previous = ticks; +} diff --git a/src/Timer.hpp b/src/Timer.hpp new file mode 100644 index 0000000..6f741f1 --- /dev/null +++ b/src/Timer.hpp @@ -0,0 +1,21 @@ +#ifndef Timer_h_ +#define Timer_h_ + +#include "SDL.h" + +struct Timer +{ + + int ticks, ticks_previous, frame_duration = 0, elapsed = 0; + bool is_timing = true; + + Timer(); + + void toggle(); + void toggle(bool); + void reset(); + void update(); + +}; + +#endif diff --git a/src/extension.cpp b/src/extension.cpp index 8a27f7f..9a58d79 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -9,7 +9,6 @@ std::vector sfw::glob(fs::path query) } std::regex expression(query.string()); std::vector files; - std::cout << basename << " " << query << std::endl; for (auto& entry: fs::directory_iterator(basename)) { if (std::regex_match(entry.path().string(), expression)) @@ -20,3 +19,31 @@ std::vector sfw::glob(fs::path query) std::sort(files.begin(), files.end()); return files; } + +fs::path sfw::get_next_file_name( + fs::path directory, int zfill, std::string prefix, std::string extension) +{ + std::stringstream file_pattern; + file_pattern << prefix << "([0-9]+)" << extension; + fs::path query = directory / file_pattern.str(); + std::vector files = sfw::glob(query); + int index = 1; + if (files.size()) + { + const std::string last = files.back().string(); + std::smatch matches; + std::regex_match(last, matches, std::regex(query.string())); + index = std::stoi(matches[1]) + 1; + } + std::stringstream filename; + fs::path path; + do + { + filename << prefix << sfw::pad(index++, zfill) << extension; + path = directory / filename.str(); + filename.str(""); + filename.clear(); + } + while (fs::exists(path)); + return path; +} diff --git a/src/extension.hpp b/src/extension.hpp index 781e29a..88c55b1 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -13,6 +13,8 @@ namespace sfw { std::vector glob(fs::path); + fs::path get_next_file_name( + fs::path, int = 0, std::string = "", std::string = ""); template std::string pad(T end, int width, char fill = '0')