restored gl screen capture; get points on circle utility function

This commit is contained in:
frank 2021-08-02 20:18:56 -04:00
parent 14759a1c79
commit 2831f2fc60
8 changed files with 135 additions and 72 deletions

View File

@ -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}

View File

@ -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<SDL_Renderer*>(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<SDL_Window*>(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);
}
}

View File

@ -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)

View File

@ -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<typename T>
float weight(T amount)
{

View File

@ -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>();
std::string extension = config["recording"]["screenshot-extension"].
get<std::string>();
std::string prefix = config["recording"]["screenshot-prefix"].get<std::string>();
std::string extension = config["recording"]["screenshot-extension"].get<std::string>();
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<void()> f =
std::bind(&Recorder::write_most_recent_frames, this);
std::function<void()> 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<void(Stash*)> f =
std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1);
std::function<void(Stash*)> 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<std::string>();
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<Recorder*>(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();

View File

@ -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<Stash> 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

View File

@ -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<glm::vec2> sfw::get_points_on_circle(int count, float radius, const glm::vec2& center, float offset)
{
std::vector<glm::vec2> points;
points.reserve(count);
float step = glm::two_pi<float>() / 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<int>(color.r) << ", " << static_cast<int>(color.g) << ", " <<

View File

@ -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<glm::vec2> 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<std::vector<Box>> get_blinds_boxes(glm::vec2, float = 0.05f, int = 4);
@ -187,6 +189,7 @@ std::ostream& operator<<(std::ostream& out, const std::vector<T>& 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