Add a screenshot hotkey to the SDL port, closes #364

This commit is contained in:
Lior Halphon
2026-02-28 00:12:06 +02:00
parent 18afec6c91
commit 7cae71d098
8 changed files with 259 additions and 11 deletions
+20 -2
View File
@@ -180,13 +180,19 @@ RGBGFX_FLAGS := $(if $(filter $(shell echo 'println __RGBDS_MAJOR__ || (!__RGBDS
# Set compilation and linkage flags based on target, platform and configuration
OPEN_DIALOG = SDL/open_dialog/gtk.c
SAVE_PNG = SDL/save_png/libpng.c
NEED_LIBPNG = 1
ifeq ($(PLATFORM),windows32)
OPEN_DIALOG = SDL/open_dialog/windows.c
SAVE_PNG = SDL/save_png/windows.c
NEED_LIBPNG = 0
endif
ifeq ($(PLATFORM),Darwin)
OPEN_DIALOG = SDL/open_dialog/cocoa.m
SAVE_PNG = SDL/save_png/appkit.m
NEED_LIBPNG = 0
endif
# These must come before the -Wno- flags
@@ -271,15 +277,22 @@ endif
ifeq (,$(PKG_CONFIG))
GL_LDFLAGS := -lGL
ifeq ($(NEED_LIBPNG),1)
SDL_LDFLAGS += -lpng
endif
else
GL_CFLAGS := $(shell $(PKG_CONFIG) --cflags gl)
GL_LDFLAGS := $(shell $(PKG_CONFIG) --libs gl || echo -lGL)
ifeq ($(NEED_LIBPNG),1)
SDL_LDFLAGS += $(shell $(PKG_CONFIG) --cflags libpng)
SDL_CFLAGS += $(shell $(PKG_CONFIG) --libs libpng || echo -lpng)
endif
endif
ifeq ($(PLATFORM),windows32)
CFLAGS += -IWindows -Drandom=rand --target=x86_64-pc-windows
LDFLAGS += -lmsvcrt -lkernel32 -Wl,/MANIFESTFILE:NUL --target=x86_64-pc-windows
SDL_LDFLAGS := -lSDL2 -lcomdlg32 -luser32 -lshell32 -lole32 -ladvapi32 -ldwmapi -lSDL2main
SDL_LDFLAGS := -lSDL2 -lcomdlg32 -luser32 -lshell32 -lole32 -ladvapi32 -ldwmapi -lwindowscodecs -lSDL2main
GL_LDFLAGS := -lopengl32
ifneq ($(REDIST_XAUDIO),)
CFLAGS += -DREDIST_XAUDIO
@@ -417,7 +430,7 @@ endif
CORE_SOURCES := $(filter-out $(CORE_FILTER),$(shell ls Core/*.c))
CORE_HEADERS := $(shell ls Core/*.h)
SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) $(patsubst %,SDL/audio/%.c,$(SDL_AUDIO_DRIVERS))
SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG) $(SAVE_PNG) $(patsubst %,SDL/audio/%.c,$(SDL_AUDIO_DRIVERS))
TESTER_SOURCES := $(shell ls Tester/*.c)
IOS_SOURCES := $(filter-out iOS/installer.m, $(shell ls iOS/*.m)) $(shell ls AppleCommon/*.m)
COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) $(shell ls JoyKit/*.m) $(shell ls AppleCommon/*.m)
@@ -510,6 +523,11 @@ $(OBJ)/HexFiend/%.m.o: HexFiend/%.m
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@ -fno-objc-arc -include HexFiend/HexFiend_2_Framework_Prefix.pch
# Apple-specific code in SDL
$(OBJ)/SDL/%.m.o: SDL/%.m
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(OCFLAGS) $(SDL_CFLAGS) -c $< -o $@
$(OBJ)/%.m.o: %.m
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) $(FRONTEND_CFLAGS) $(FAT_FLAGS) $(OCFLAGS) -c $< -o $@
+8 -3
View File
@@ -81,21 +81,26 @@ void render_texture(void *pixels, void *previous)
}
static const char *help[] = {
// Shortcuts
"Keyboard Shortcuts:\n"
" Open Menu: Escape\n"
" Open menu: Escape\n"
" Open ROM: " MODIFIER_NAME "+O\n"
" Reset: " MODIFIER_NAME "+R\n"
" Pause: " MODIFIER_NAME "+P\n"
" Save state: " MODIFIER_NAME "+(0-9)\n"
" Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n"
" Toggle Fullscreen " MODIFIER_NAME "+F\n"
" Toggle fullscreen " MODIFIER_NAME "+F\n"
#ifdef __APPLE__
" Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n"
" Save screenshot: " MODIFIER_NAME "+S\n"
#else
" Mute/Unmute: " MODIFIER_NAME "+M\n"
" Save screenshot: F12\n"
#endif
" Toggle channel: " ALT_STRING "+(1-4)\n"
" Break Debugger: " CTRL_STRING "+C",
" Break debugger: " CTRL_STRING "+C",
// About
"\n"
"SameBoy\n"
"Version " GB_VERSION "\n\n"
+54 -6
View File
@@ -14,6 +14,7 @@
#include "shader.h"
#include "audio/audio.h"
#include "console.h"
#include "save_png/save_png.h"
#ifdef _WIN32
#include <Windows.h>
@@ -29,6 +30,7 @@ static bool underclock_down = false, rewind_down = false, do_rewind = false, rew
static bool rapid_a = false, rapid_b = false;
static uint8_t rapid_a_count = 0, rapid_b_count = 0;
static double clock_mutliplier = 1.0;
static bool pending_screenshot = false;
char *filename = NULL;
static typeof(free) *free_function = NULL;
@@ -305,6 +307,32 @@ static void configure_console(void)
GB_set_async_input_callback(&gb, async_input_callback);
}
static void save_screenshot(void)
{
static char png_path[PATH_MAX] = {0,};
time_t now = time(NULL);
struct tm *local = localtime(&now);
char timestring[20];
strftime(timestring, sizeof(timestring), "%Y-%m-%d %H.%M.%S", local);
replace_extension(filename, strlen(filename), png_path, "");
snprintf(png_path + strlen(png_path), sizeof(png_path) - 1 - strlen(png_path), " %s.png", timestring);
unsigned i = 2;
while (access(png_path, F_OK) == 0) {
replace_extension(filename, strlen(filename), png_path, "");
snprintf(png_path + strlen(png_path), sizeof(png_path) - 1 - strlen(png_path), " %s %u.png", timestring, i);
i++;
}
if (save_png(png_path, GB_get_screen_width(&gb), GB_get_screen_height(&gb), active_pixel_buffer, pixel_format)) {
show_osd_text("Screenshot saved");
}
else {
show_osd_text("Failed to save screenshot");
}
}
static void handle_events(GB_gameboy_t *gb)
{
SDL_Event event;
@@ -567,6 +595,19 @@ static void handle_events(GB_gameboy_t *gb)
GB_audio_set_paused(GB_audio_is_playing());
}
break;
#ifdef __APPLE__
case SDL_SCANCODE_S:
if (!(event.key.keysym.mod & MODIFIER)) break;
#else
case SDL_SCANCODE_F12:
#endif
if (osd_countdown && configuration.osd) {
pending_screenshot = true;
break;
}
save_screenshot();
break;
case SDL_SCANCODE_F:
if (event.key.keysym.mod & MODIFIER) {
@@ -688,13 +729,20 @@ static void vblank(GB_gameboy_t *gb, GB_vblank_type_t type)
show_osd_text("Rewinding...");
}
if (pending_screenshot) {
pending_screenshot = false;
save_screenshot();
}
if (osd_countdown && configuration.osd) {
unsigned width = GB_get_screen_width(gb);
unsigned height = GB_get_screen_height(gb);
draw_text(active_pixel_buffer,
width, height, 8, height - 8 - osd_text_lines * 12, osd_text,
rgb_encode(gb, 255, 255, 255), rgb_encode(gb, 0, 0, 0),
true);
if (osd_countdown != 1) {
unsigned width = GB_get_screen_width(gb);
unsigned height = GB_get_screen_height(gb);
draw_text(active_pixel_buffer,
width, height, 8, height - 8 - osd_text_lines * 12, osd_text,
rgb_encode(gb, 255, 255, 255), rgb_encode(gb, 0, 0, 0),
true);
}
osd_countdown--;
}
if (type != GB_VBLANK_TYPE_REPEAT) {
+29
View File
@@ -0,0 +1,29 @@
#import <AppKit/AppKit.h>
#import <SDL.h>
#import "save_png.h"
bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format)
{
if (pixel_format->format != SDL_PIXELFORMAT_ABGR8888 &&
pixel_format->format != SDL_PIXELFORMAT_XBGR8888) {
return false;
}
size_t stride = width * 4;
@autoreleasepool {
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:3
hasAlpha:false
isPlanar:false
colorSpaceName:NSDeviceRGBColorSpace
bitmapFormat:0
bytesPerRow:4 * width
bitsPerPixel:32];
memcpy(rep.bitmapData, pixels, stride * height);
NSData *data = [rep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
return [data writeToFile:@(filename) atomically:true];
}
}
+54
View File
@@ -0,0 +1,54 @@
#include <png.h>
#include <SDL.h>
#include <stdbool.h>
#include "save_png.h"
bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format)
{
FILE *f = fopen(filename, "wb");
if (!f) return false;
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info = png_create_info_struct(png);
uint8_t *row = malloc(width * 3);
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
fclose(f);
free(row);
return false;
}
png_init_io(png, f);
png_set_IHDR(png,
info,
width,
height,
8,
PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, info);
for (uint32_t y = 0; y < height; y++) {
const uint32_t *src = (uint32_t *)pixels + y * width;
uint8_t *dest = row;
for (uint32_t x = 0; x < width; x++) {
uint8_t dummy;
SDL_GetRGBA(*(src++), pixel_format, &dest[0], &dest[1], &dest[2], &dummy);
dest += 3;
}
png_write_row(png, row);
}
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
fclose(f);
return true;
}
+5
View File
@@ -0,0 +1,5 @@
#include <stdint.h>
#include <stdbool.h>
#include <SDL.h>
bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format);
+88
View File
@@ -0,0 +1,88 @@
#define COBJMACROS
#include "save_png.h"
#include <windows.h>
#include <wincodec.h>
static wchar_t *utf8_to_wide(const char *utf8)
{
int length = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (length <= 0) {
return NULL;
}
wchar_t *ret = malloc(length * sizeof(*ret));
if (!ret) {
return NULL;
}
if (!MultiByteToWideChar(CP_UTF8, 0, utf8, -1, ret, length)) {
free(ret);
return NULL;
}
return ret;
}
bool save_png(const char *filename, uint32_t width, uint32_t height, const void *pixels, SDL_PixelFormat *pixel_format)
{
bool success = false;
wchar_t *wfilename = utf8_to_wide(filename);
IWICImagingFactory *factory = NULL;
IWICStream *stream = NULL;
IWICBitmapEncoder *encoder = NULL;
IWICBitmapFrameEncode *frame = NULL;
uint8_t *row = malloc(width * 3);
CoInitialize(NULL);
if (CoCreateInstance(&CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
&IID_IWICImagingFactory,
(void **)&factory)) {
goto done;
}
if (IWICImagingFactory_CreateStream(factory, &stream)) goto done;
if (IWICStream_InitializeFromFilename(stream, wfilename, GENERIC_WRITE)) goto done;
if (IWICImagingFactory_CreateEncoder(factory, &GUID_ContainerFormatPng, NULL, &encoder)) goto done;
if (IWICBitmapEncoder_Initialize(encoder, (IStream *)stream, WICBitmapEncoderNoCache)) goto done;
if (IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL)) goto done;
if (IWICBitmapFrameEncode_Initialize(frame, NULL)) goto done;
if (IWICBitmapFrameEncode_SetSize(frame, width, height)) goto done;
WICPixelFormatGUID pixel_format_guid = GUID_WICPixelFormat24bppRGB;
if (IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format_guid)) goto done;
for (uint32_t y = 0; y < height; y++) {
const uint32_t *src = (uint32_t *)pixels + y * width;
uint8_t *dest = row;
for (uint32_t x = 0; x < width; x++) {
uint8_t dummy;
SDL_GetRGBA(*(src++), pixel_format, &dest[2], &dest[1], &dest[0], &dummy);
dest += 3;
}
if (IWICBitmapFrameEncode_WritePixels(frame, 1, width * 3, width * 3, (void *)row)) goto done;
}
if (IWICBitmapFrameEncode_Commit(frame)) goto done;
if (IWICBitmapEncoder_Commit(encoder)) goto done;
success = true;
done:
if (frame) IWICBitmapFrameEncode_Release(frame);
if (encoder) IWICBitmapEncoder_Release(encoder);
if (stream) IWICStream_Release(stream);
if (factory) IWICImagingFactory_Release(factory);
CoUninitialize();
free(wfilename);
free(row);
return success;
}
+1
View File
@@ -18,6 +18,7 @@
int access(const char *filename, int mode);
#define R_OK 4
#define W_OK 2
#define F_OK 0
#ifndef __MINGW32__
#ifndef __LIBRETRO__