Encode and write screenshots on a background thread
This reduces in-game stuttering when a screenshot is taken, especially if multiple are taken during a short time frame. The pixel transfer from the renderer to client memory is still synchronous, so some spikes are to be expected; however, png encoding is usually much slower. Also, TODO: convert this code into a generic multi-threaded task manager/worker pool (similar to python's futures), and make the asynchronous resource loading code suck less using it. Another useful application is asynchronous logging, but that probably should be turned off in debug builds by default.
This commit is contained in:
parent
f3616f3496
commit
bb0f9ce0d4
1 changed files with 160 additions and 40 deletions
200
src/video.c
200
src/video.c
|
@ -17,19 +17,23 @@
|
|||
|
||||
Video video;
|
||||
|
||||
static VideoMode common_modes[] = {
|
||||
{RESX, RESY},
|
||||
typedef struct ScreenshotCommand {
|
||||
LIST_INTERFACE(struct ScreenshotCommand);
|
||||
char *dest_path;
|
||||
void *data;
|
||||
uint width;
|
||||
uint height;
|
||||
} ScreenshotCommand;
|
||||
|
||||
{640, 480},
|
||||
{800, 600},
|
||||
{1024, 768},
|
||||
{1280, 960},
|
||||
{1152, 864},
|
||||
{1400, 1050},
|
||||
{1440, 1080},
|
||||
|
||||
{0, 0},
|
||||
};
|
||||
static struct {
|
||||
struct {
|
||||
ScreenshotCommand *queue;
|
||||
SDL_mutex *mutex;
|
||||
SDL_cond *cond;
|
||||
SDL_Thread *thread;
|
||||
bool running;
|
||||
} screenshots;
|
||||
} _video;
|
||||
|
||||
static void video_add_mode(int width, int height) {
|
||||
if(video.modes) {
|
||||
|
@ -256,37 +260,24 @@ void video_set_mode(int w, int h, bool fs, bool resizable) {
|
|||
SDL_SetWindowResizable(video.window, resizable);
|
||||
}
|
||||
|
||||
void video_take_screenshot(void) {
|
||||
SDL_RWops *out;
|
||||
char outfile[128], *outpath, *syspath;
|
||||
time_t rawtime;
|
||||
struct tm * timeinfo;
|
||||
uint width, height;
|
||||
uint8_t *pixels = r_screenshot(&width, &height);
|
||||
static void video_screenshot_execute_command(ScreenshotCommand *cmd) {
|
||||
log_debug("%u %u %s", cmd->width, cmd->height, cmd->dest_path);
|
||||
|
||||
if(!pixels) {
|
||||
log_warn("Failed to take a screenshot");
|
||||
uint width = cmd->width;
|
||||
uint height = cmd->height;
|
||||
uint8_t *pixels = cmd->data;
|
||||
|
||||
SDL_RWops *output = vfs_open(cmd->dest_path, VFS_MODE_WRITE);
|
||||
|
||||
if(!output) {
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
return;
|
||||
}
|
||||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(outfile, 128, "taisei_%Y%m%d_%H-%M-%S%z.png", timeinfo);
|
||||
|
||||
outpath = strjoin("storage/screenshots/", outfile, NULL);
|
||||
syspath = vfs_repr(outpath, true);
|
||||
char *syspath = vfs_repr(cmd->dest_path, true);
|
||||
log_info("Saving screenshot as %s", syspath);
|
||||
free(syspath);
|
||||
|
||||
out = vfs_open(outpath, VFS_MODE_WRITE);
|
||||
free(outpath);
|
||||
|
||||
if(!out) {
|
||||
log_warn("VFS error: %s", vfs_get_error());
|
||||
free(pixels);
|
||||
return;
|
||||
}
|
||||
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
|
||||
|
@ -306,7 +297,7 @@ void video_take_screenshot(void) {
|
|||
memcpy(row_pointers[y], pixels + width * 3 * (height - 1 - y), width * 3);
|
||||
}
|
||||
|
||||
png_init_rwops_write(png_ptr, out);
|
||||
png_init_rwops_write(png_ptr, output);
|
||||
png_set_rows(png_ptr, info_ptr, row_pointers);
|
||||
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
|
||||
|
||||
|
@ -315,9 +306,121 @@ void video_take_screenshot(void) {
|
|||
}
|
||||
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
SDL_RWclose(output);
|
||||
}
|
||||
|
||||
free(pixels);
|
||||
SDL_RWclose(out);
|
||||
static void video_screenshot_free_command_resources(ScreenshotCommand *cmd) {
|
||||
free(cmd->data);
|
||||
free(cmd->dest_path);
|
||||
}
|
||||
|
||||
static void video_screenshot_push_command(ScreenshotCommand *cmd) {
|
||||
if(_video.screenshots.thread != NULL) {
|
||||
log_debug("%u %u %s", cmd->width, cmd->height, cmd->dest_path);
|
||||
|
||||
SDL_LockMutex(_video.screenshots.mutex);
|
||||
list_push(&_video.screenshots.queue, (ScreenshotCommand*)memdup(cmd, sizeof(*cmd)));
|
||||
SDL_CondSignal(_video.screenshots.cond);
|
||||
SDL_UnlockMutex(_video.screenshots.mutex);
|
||||
} else {
|
||||
video_screenshot_execute_command(cmd);
|
||||
video_screenshot_free_command_resources(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
static int video_screenshot_thread(void *arg) {
|
||||
if(SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW) < 0) {
|
||||
log_warn("SDL_SetThreadPriority() failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
bool running = _video.screenshots.running;
|
||||
|
||||
while(running) {
|
||||
SDL_LockMutex(_video.screenshots.mutex);
|
||||
ScreenshotCommand *cmd = list_pop(&_video.screenshots.queue);
|
||||
running = _video.screenshots.running;
|
||||
|
||||
if(running && !cmd) {
|
||||
log_debug("Sleeping");
|
||||
SDL_CondWait(_video.screenshots.cond, _video.screenshots.mutex);
|
||||
log_debug("Waking up");
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(_video.screenshots.mutex);
|
||||
|
||||
if(cmd) {
|
||||
video_screenshot_execute_command(cmd);
|
||||
video_screenshot_free_command_resources(cmd);
|
||||
free(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void video_stop_screenshot_thread(void) {
|
||||
if(_video.screenshots.thread != NULL) {
|
||||
SDL_LockMutex(_video.screenshots.mutex);
|
||||
_video.screenshots.running = false;
|
||||
SDL_CondSignal(_video.screenshots.cond);
|
||||
SDL_UnlockMutex(_video.screenshots.mutex);
|
||||
SDL_WaitThread(_video.screenshots.thread, NULL);
|
||||
_video.screenshots.thread = NULL;
|
||||
} else {
|
||||
_video.screenshots.running = false;
|
||||
}
|
||||
|
||||
SDL_DestroyCond(_video.screenshots.cond);
|
||||
_video.screenshots.cond = NULL;
|
||||
|
||||
SDL_DestroyMutex(_video.screenshots.mutex);
|
||||
_video.screenshots.mutex = NULL;
|
||||
}
|
||||
|
||||
static void video_start_screenshot_thread(void) {
|
||||
if(!(_video.screenshots.mutex = SDL_CreateMutex())) {
|
||||
log_warn("SDL_CreateMutex() failed: %s", SDL_GetError());
|
||||
video_stop_screenshot_thread();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(_video.screenshots.cond = SDL_CreateCond())) {
|
||||
log_warn("SDL_CreateCond() failed: %s", SDL_GetError());
|
||||
video_stop_screenshot_thread();
|
||||
return;
|
||||
}
|
||||
|
||||
_video.screenshots.running = true;
|
||||
|
||||
if(!(_video.screenshots.thread = SDL_CreateThread(video_screenshot_thread, "video.screenshot", NULL))) {
|
||||
log_warn("SDL_CreateThread() failed: %s", SDL_GetError());
|
||||
video_stop_screenshot_thread();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void video_take_screenshot(void) {
|
||||
ScreenshotCommand cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.data = r_screenshot(&cmd.width, &cmd.height);
|
||||
|
||||
log_debug("Screenshot requested");
|
||||
|
||||
if(!cmd.data) {
|
||||
log_warn("Failed to take a screenshot");
|
||||
return;
|
||||
}
|
||||
|
||||
char outfile[128];
|
||||
time_t rawtime;
|
||||
struct tm * timeinfo;
|
||||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(outfile, 128, "taisei_%Y%m%d_%H-%M-%S%z.png", timeinfo);
|
||||
cmd.dest_path = strjoin("storage/screenshots/", outfile, NULL);;
|
||||
|
||||
video_screenshot_push_command(&cmd);
|
||||
}
|
||||
|
||||
bool video_is_resizable(void) {
|
||||
|
@ -473,6 +576,20 @@ void video_init(void) {
|
|||
|
||||
// Then, add some common 4:3 modes for the windowed mode if they are not there yet.
|
||||
// This is required for some multihead setups.
|
||||
VideoMode common_modes[] = {
|
||||
{RESX, RESY},
|
||||
|
||||
{640, 480},
|
||||
{800, 600},
|
||||
{1024, 768},
|
||||
{1280, 960},
|
||||
{1152, 864},
|
||||
{1400, 1050},
|
||||
{1440, 1080},
|
||||
|
||||
{0, 0},
|
||||
};
|
||||
|
||||
for(int i = 0; common_modes[i].width; ++i) {
|
||||
video_add_mode(common_modes[i].width, common_modes[i].height);
|
||||
}
|
||||
|
@ -500,16 +617,19 @@ void video_init(void) {
|
|||
.event_type = SDL_WINDOWEVENT,
|
||||
};
|
||||
|
||||
video_start_screenshot_thread();
|
||||
|
||||
events_register_handler(&h);
|
||||
log_info("Video subsystem initialized");
|
||||
}
|
||||
|
||||
void video_shutdown(void) {
|
||||
events_unregister_handler(video_handle_window_event);
|
||||
SDL_DestroyWindow(video.window);
|
||||
r_shutdown();
|
||||
free(video.modes);
|
||||
SDL_VideoQuit();
|
||||
events_unregister_handler(video_handle_window_event);
|
||||
video_stop_screenshot_thread();
|
||||
}
|
||||
|
||||
void video_swap_buffers(void) {
|
||||
|
|
Loading…
Reference in a new issue