From bb5025d4c1f2f9b013a3b0704f8585cdf3fc7518 Mon Sep 17 00:00:00 2001 From: kcgen Date: Wed, 9 Nov 2022 00:03:29 -0800 Subject: Support Alt+F5 screenshots of rendered output --- include/hardware.h | 2 +- src/dos/program_intro.cpp | 6 +- src/gui/sdlmain.cpp | 152 +++++++++++++++++++++++++++++++++++++++++++--- src/hardware/hardware.cpp | 36 ++++++----- 4 files changed, 169 insertions(+), 27 deletions(-) diff --git a/include/hardware.h b/include/hardware.h index 383ba4fc9..9a09fb4bc 100644 --- a/include/hardware.h +++ b/include/hardware.h @@ -47,7 +47,7 @@ bool TS_Get_Address(Bitu& tsaddr, Bitu& tsirq, Bitu& tsdma); extern uint8_t adlib_commandreg; -std::string GetScreenshotFilename(const char *type, const char *ext); +std::string CAPTURE_GetScreenshotFilename(const char *type, const char *ext); FILE *OpenCaptureFile(const char *type, const char *ext); void CAPTURE_AddWave(uint32_t freq, uint32_t len, int16_t * data); diff --git a/src/dos/program_intro.cpp b/src/dos/program_intro.cpp index b20f22f0d..2d6a1ffd8 100644 --- a/src/dos/program_intro.cpp +++ b/src/dos/program_intro.cpp @@ -32,7 +32,8 @@ void INTRO::WriteOutProgramIntroSpecial() PRIMARY_MOD_PAD, PRIMARY_MOD_NAME, // Ctrl/Cmd, for swap disk image PRIMARY_MOD_PAD, - PRIMARY_MOD_NAME, // Ctrl/Cmd, for screenshot + MMOD2_NAME, // Alt, to screenshot the rendered output + PRIMARY_MOD_NAME, // Ctrl/Cmd, to screenshot the image source PRIMARY_MOD_PAD, PRIMARY_MOD_NAME, // Ctrl/Cmd, for sound recording PRIMARY_MOD_PAD, @@ -212,7 +213,8 @@ void INTRO::AddMessages() { "[color=yellow]%s+Pause[reset] Pause/Unpause emulator.\n" "[color=yellow]%s+F1[reset] %s Start the [color=light-yellow]keymapper[reset].\n" "[color=yellow]%s+F4[reset] %s Swap mounted disk image, scan for changes on all drives.\n" - "[color=yellow]%s+F5[reset] %s Save a screenshot.\n" + "[color=yellow]%s+F5[reset] Screenshot the video's rendered output.\n" + "[color=yellow]%s+F5[reset] %s Screenshot the video's source image.\n" "[color=yellow]%s+F6[reset] %s Start/Stop recording sound output to a wave file.\n" "[color=yellow]%s+F7[reset] %s Start/Stop recording video output to a zmbv file.\n" "[color=yellow]%s+F8[reset] %s Mute/Unmute the audio.\n" diff --git a/src/gui/sdlmain.cpp b/src/gui/sdlmain.cpp index 10a38bfaa..d26c019da 100644 --- a/src/gui/sdlmain.cpp +++ b/src/gui/sdlmain.cpp @@ -46,6 +46,9 @@ #if C_OPENGL #include #endif +#if C_SDL_IMAGE +# include +#endif #include "../ints/int10.h" #include "control.h" @@ -54,6 +57,7 @@ #include "debug.h" #include "fs_utils.h" #include "gui_msgs.h" +#include "hardware.h" #include "joystick.h" #include "keyboard.h" #include "mapper.h" @@ -199,8 +203,6 @@ PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL; SDL_Block sdl; // Masks to be passed when creating SDL_Surface. -// Remove ifndef if they'll be needed for MacOS X builds. -#ifndef MACOSX #if SDL_BYTEORDER == SDL_BIG_ENDIAN constexpr uint32_t RMASK = 0xff000000; constexpr uint32_t GMASK = 0x00ff0000; @@ -212,7 +214,6 @@ constexpr uint32_t GMASK = 0x0000ff00; constexpr uint32_t BMASK = 0x00ff0000; constexpr uint32_t AMASK = 0xff000000; #endif -#endif // !MACOSX // Size and ratio constants // ------------------------ @@ -3386,7 +3387,132 @@ static void set_output(Section *sec, bool should_stretch_pixels) SDL_SetWindowOpacity(sdl.window, alpha); } -//extern void UI_Run(bool); +static std::optional get_rendered_surface() +{ + // Variables common to all screen-modes + const auto renderer = SDL_GetRenderer(sdl.window); + const auto canvas = get_canvas_size(sdl.desktop.type); + +#if C_OPENGL + // Get the OpenGL-renderer surface + // ------------------------------- + if (sdl.desktop.type == SCREEN_OPENGL) { + // Setup our OpenGL image properties + constexpr int gl_channels = 3; // RBG (no alpha) + constexpr int gl_bits_per_pixel = gl_channels * 8; // 8-bpp + const size_t bytes_per_row = gl_channels * canvas.w; + + // Allocate a 24-bit surface to be populated + const auto surface = SDL_CreateRGBSurface(SDL_SWSURFACE, + canvas.w, + canvas.h, + gl_bits_per_pixel, + RMASK, + GMASK, + BMASK, + AMASK); + if (!surface) { + LOG_WARNING("SDL: Failed creating a surface for OpenGL because %s", + SDL_GetError()); + return {}; + } + // Per OpenGL's documentation: + // The glReadPixels function starts at the lower left corner: (x + // + i, y + j) and iterates for 0 <= i < width and 0 <= j < + // height. It describes the pxiels as being "the i'th pixel in + // the j'th row". Pixels are returned in row-order from the + // lowest to the highest row, left to right in each row. + std::vector pixels(bytes_per_row * canvas.h); + glReadPixels(0, 0, canvas.w, canvas.h, GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); + + // To match SDL's surface ordering, we invert the rows (outer) + // and lines (inner): + auto surface_pixels = static_cast(surface->pixels); + for (int j = 0; j < canvas.h; ++j) { + auto target_row = surface_pixels + surface->pitch * j; + const auto source_row = pixels.data() + + bytes_per_row * (canvas.h - j - 1); + memcpy(target_row, source_row, bytes_per_row); + } + return surface; + } +#endif + + // Get the SDL texture-renderer surface + // ------------------------------------ + else if (sdl.desktop.type == SCREEN_TEXTURE) { + // Get the renderer's pixel format + SDL_RendererInfo rinfo; + if (SDL_GetRendererInfo(renderer, &rinfo) != 0) { + LOG_MSG("SDL: Failed to get a rendering info because %s", + SDL_GetError()); + return {}; + } + const auto pixel_format = rinfo.texture_formats[0]; + + // Create a 32-bit surface with color format matching the renderer + const auto surface = SDL_CreateRGBSurfaceWithFormat( + SDL_SWSURFACE, canvas.w, canvas.h, 32, pixel_format); + if (!surface || !renderer) { + LOG_WARNING("SDL: Failed creating a surface because %s", + SDL_GetError()); + return {}; + } + // Copy the pixels from the renderer into our new surface + if (SDL_RenderReadPixels(renderer, + nullptr, + surface->format->format, + surface->pixels, + surface->pitch) != 0) { + LOG_WARNING("SDL: Failed rendering to the surface because %s", + SDL_GetError()); + SDL_FreeSurface(surface); + return {}; + } + return surface; + } + + // We're already in surface-mode, how convenient ;) + // ----------------------------------------------- + else if (sdl.desktop.type == SCREEN_SURFACE) { + assert(sdl.surface); + // Simply return a copy + return SDL_ConvertSurfaceFormat(sdl.surface, + sdl.surface->format->format, + 0); + } + + LOG_WARNING("SDL: unhandled screen-type (bug)"); + return {}; +} + +static void screenshot_rendered_surface(bool pressed) +{ + if (!pressed) + return; + + const auto surface = get_rendered_surface(); + if (!surface) + return; + +#if C_SDL_IMAGE + const auto filename = CAPTURE_GetScreenshotFilename("Screenshot", ".png"); + const auto is_saved = IMG_SavePNG(*surface, filename.c_str()) == 0; +#else + const auto filename = CAPTURE_GetScreenshotFilename("Screenshot", ".bmp"); + const auto is_saved = SDL_SaveBMP(*surface, filename.c_str()) == 0; +#endif + SDL_FreeSurface(*surface); + + if (is_saved) + LOG_MSG("SDL: Captured rendered output to %s", filename.c_str()); + else + LOG_MSG("SDL: Failed capturing rendered output to %s because %s", + filename.c_str(), + SDL_GetError()); +} + +// extern void UI_Run(bool); void Restart(bool pressed); static void ApplyActiveSettings() @@ -3542,12 +3668,18 @@ static void GUI_StartUp(Section *sec) /* Get some Event handlers */ MAPPER_AddHandler(GFX_RequestExit, SDL_SCANCODE_F9, PRIMARY_MOD, "shutdown", "Shutdown"); - MAPPER_AddHandler(SwitchFullScreen, SDL_SCANCODE_RETURN, MMOD2, - "fullscr", "Fullscreen"); - MAPPER_AddHandler(Restart, SDL_SCANCODE_HOME, MMOD1 | MMOD2, - "restart", "Restart"); - MAPPER_AddHandler(MOUSE_ToggleUserCapture, SDL_SCANCODE_F10, PRIMARY_MOD, - "capmouse", "Cap Mouse"); + MAPPER_AddHandler(screenshot_rendered_surface, + SDL_SCANCODE_F5, + MMOD2, + "rendshot", + "Rend Screenshot"); + MAPPER_AddHandler(SwitchFullScreen, SDL_SCANCODE_RETURN, MMOD2, "fullscr", "Fullscreen"); + MAPPER_AddHandler(Restart, SDL_SCANCODE_HOME, MMOD1 | MMOD2, "restart", "Restart"); + MAPPER_AddHandler(MOUSE_ToggleUserCapture, + SDL_SCANCODE_F10, + PRIMARY_MOD, + "capmouse", + "Cap Mouse"); #if C_DEBUG /* Pause binds with activate-debugger */ diff --git a/src/hardware/hardware.cpp b/src/hardware/hardware.cpp index 5ee309a13..db8b9ec0f 100644 --- a/src/hardware/hardware.cpp +++ b/src/hardware/hardware.cpp @@ -92,30 +92,33 @@ static struct { } capture = {}; -FILE * OpenCaptureFile(const char * type,const char * ext) { - if(capturedir.empty()) { +std::string CAPTURE_GetScreenshotFilename(const char *type, const char *ext) +{ + if (capturedir.empty()) { LOG_MSG("Please specify a capture directory"); return 0; } + char file_start[16]; + dir_information *dir; /* Find a filename to open */ - dir_information *dir = open_directory(capturedir.c_str()); + dir = open_directory(capturedir.c_str()); if (!dir) { // Try creating it first if (create_dir(capturedir.c_str(), 0700, OK_IF_EXISTS) != 0) { - LOG_MSG("ERROR: Can't create dir '%s': %s", - capturedir.c_str(), safe_strerror(errno).c_str()); + LOG_WARNING("Can't create dir '%s' for capturing: %s", + capturedir.c_str(), + safe_strerror(errno).c_str()); + return 0; } - dir = open_directory(capturedir.c_str()); if (!dir) { - LOG_MSG("ERROR: Can't open dir '%s' for capturing %s", - capturedir.c_str(), type); + LOG_MSG("Can't open dir %s for capturing %s", + capturedir.c_str(), + type); return 0; } } - - char file_start[16]; safe_strcpy(file_start, RunningProgram); lowcase(file_start); strcat(file_start,"_"); @@ -137,12 +140,17 @@ FILE * OpenCaptureFile(const char * type,const char * ext) { char file_name[CROSS_LEN]; sprintf(file_name, "%s%c%s%03d%s", capturedir.c_str(), CROSS_FILESPLIT, file_start, last, ext); - /* Open the actual file */ - FILE * handle=fopen(file_name,"wb"); + return file_name; +} + +FILE *OpenCaptureFile(const char *type, const char *ext) +{ + const auto file_name = CAPTURE_GetScreenshotFilename(type, ext); + FILE *handle = fopen(file_name.c_str(), "wb"); if (handle) { - LOG_MSG("Capturing %s to %s",type,file_name); + LOG_MSG("Capturing %s to %s", type, file_name.c_str()); } else { - LOG_MSG("Failed to open %s for capturing %s",file_name,type); + LOG_MSG("Failed to open %s for capturing %s", file_name.c_str(), type); } return handle; } -- cgit v1.2.3