From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/test/SDL_test_assert.c | 157 ++ contrib/SDL-3.2.8/src/test/SDL_test_common.c | 2862 +++++++++++++++++++++++++ contrib/SDL-3.2.8/src/test/SDL_test_compare.c | 218 ++ contrib/SDL-3.2.8/src/test/SDL_test_crc32.c | 160 ++ contrib/SDL-3.2.8/src/test/SDL_test_font.c | 159 ++ contrib/SDL-3.2.8/src/test/SDL_test_fuzzer.c | 494 +++++ contrib/SDL-3.2.8/src/test/SDL_test_harness.c | 864 ++++++++ contrib/SDL-3.2.8/src/test/SDL_test_log.c | 211 ++ contrib/SDL-3.2.8/src/test/SDL_test_md5.c | 342 +++ contrib/SDL-3.2.8/src/test/SDL_test_memory.c | 457 ++++ 10 files changed, 5924 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_assert.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_common.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_compare.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_crc32.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_font.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_fuzzer.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_harness.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_log.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_md5.c create mode 100644 contrib/SDL-3.2.8/src/test/SDL_test_memory.c (limited to 'contrib/SDL-3.2.8/src/test') diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_assert.c b/contrib/SDL-3.2.8/src/test/SDL_test_assert.c new file mode 100644 index 0000000..970e986 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_assert.c @@ -0,0 +1,157 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + + Used by the test framework and test cases. + +*/ +#include + +/* Enable to have color in logs */ +#if 1 +#define COLOR_RED "\033[0;31m" +#define COLOR_GREEN "\033[0;32m" +#define COLOR_YELLOW "\033[0;93m" +#define COLOR_BLUE "\033[0;94m" +#define COLOR_END "\033[0m" +#else +#define COLOR_RED "" +#define COLOR_GREEN "" +#define COLOR_BLUE "" +#define COLOR_YELLOW "" +#define COLOR_END "" +#endif + +/* Assert check message format */ +#define SDLTEST_ASSERT_CHECK_FORMAT "Assert '%s': %s" + +/* Assert summary message format */ +#define SDLTEST_ASSERT_SUMMARY_FORMAT "Assert Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_RED "Failed=%d" COLOR_END +#define SDLTEST_ASSERT_SUMMARY_FORMAT_OK "Assert Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_GREEN "Failed=%d" COLOR_END + +/* ! counts the failed asserts */ +static int SDLTest_AssertsFailed = 0; + +/* ! counts the passed asserts */ +static int SDLTest_AssertsPassed = 0; + +/* + * Assert that logs and break execution flow on failures (i.e. for harness errors). + */ +void SDLTest_Assert(int assertCondition, SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) +{ + va_list list; + char logMessage[SDLTEST_MAX_LOGMESSAGE_LENGTH]; + + /* Print assert description into a buffer */ + SDL_memset(logMessage, 0, SDLTEST_MAX_LOGMESSAGE_LENGTH); + va_start(list, assertDescription); + (void)SDL_vsnprintf(logMessage, SDLTEST_MAX_LOGMESSAGE_LENGTH - 1, assertDescription, list); + va_end(list); + + /* Log, then assert and break on failure */ + SDL_assert((SDLTest_AssertCheck(assertCondition, "%s", logMessage))); +} + +/* + * Assert that logs but does not break execution flow on failures (i.e. for test cases). + */ +int SDLTest_AssertCheck(int assertCondition, SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) +{ + va_list list; + char logMessage[SDLTEST_MAX_LOGMESSAGE_LENGTH]; + + /* Print assert description into a buffer */ + SDL_memset(logMessage, 0, SDLTEST_MAX_LOGMESSAGE_LENGTH); + va_start(list, assertDescription); + (void)SDL_vsnprintf(logMessage, SDLTEST_MAX_LOGMESSAGE_LENGTH - 1, assertDescription, list); + va_end(list); + + /* Log pass or fail message */ + if (assertCondition == ASSERT_FAIL) { + SDLTest_AssertsFailed++; + SDLTest_LogError(SDLTEST_ASSERT_CHECK_FORMAT, logMessage, COLOR_RED "Failed" COLOR_END); + } else { + SDLTest_AssertsPassed++; + SDLTest_Log(SDLTEST_ASSERT_CHECK_FORMAT, logMessage, COLOR_GREEN "Passed" COLOR_END); + } + + return assertCondition; +} + +/* + * Explicitly passing Assert that logs (i.e. for test cases). + */ +void SDLTest_AssertPass(SDL_PRINTF_FORMAT_STRING const char *assertDescription, ...) +{ + va_list list; + char logMessage[SDLTEST_MAX_LOGMESSAGE_LENGTH]; + + /* Print assert description into a buffer */ + SDL_memset(logMessage, 0, SDLTEST_MAX_LOGMESSAGE_LENGTH); + va_start(list, assertDescription); + (void)SDL_vsnprintf(logMessage, SDLTEST_MAX_LOGMESSAGE_LENGTH - 1, assertDescription, list); + va_end(list); + + /* Log pass message */ + SDLTest_AssertsPassed++; + SDLTest_Log(SDLTEST_ASSERT_CHECK_FORMAT, logMessage, COLOR_GREEN "Passed" COLOR_END); +} + +/* + * Resets the assert summary counters to zero. + */ +void SDLTest_ResetAssertSummary(void) +{ + SDLTest_AssertsPassed = 0; + SDLTest_AssertsFailed = 0; +} + +/* + * Logs summary of all assertions (total, pass, fail) since last reset + * as INFO (failed==0) or ERROR (failed > 0). + */ +void SDLTest_LogAssertSummary(void) +{ + int totalAsserts = SDLTest_AssertsPassed + SDLTest_AssertsFailed; + if (SDLTest_AssertsFailed == 0) { + SDLTest_Log(SDLTEST_ASSERT_SUMMARY_FORMAT_OK, totalAsserts, SDLTest_AssertsPassed, SDLTest_AssertsFailed); + } else { + SDLTest_LogError(SDLTEST_ASSERT_SUMMARY_FORMAT, totalAsserts, SDLTest_AssertsPassed, SDLTest_AssertsFailed); + } +} + +/* + * Converts the current assert state into a test result + */ +int SDLTest_AssertSummaryToTestResult(void) +{ + if (SDLTest_AssertsFailed > 0) { + return TEST_RESULT_FAILED; + } else { + if (SDLTest_AssertsPassed > 0) { + return TEST_RESULT_PASSED; + } else { + return TEST_RESULT_NO_ASSERT; + } + } +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_common.c b/contrib/SDL-3.2.8/src/test/SDL_test_common.c new file mode 100644 index 0000000..3203dde --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_common.c @@ -0,0 +1,2862 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* Ported from original test/common.c file. */ +#include + +#define SDL_MAIN_NOIMPL +#define SDL_MAIN_USE_CALLBACKS +#include + +static const char *common_usage[] = { + "[-h | --help]", + "[--trackmem]", + "[--randmem]", + "[--info all|video|modes|render|event|event_motion]", + "[--log all|error|system|audio|video|render|input]", + NULL +}; + +static const char *video_usage[] = { + "[--always-on-top]", + "[--aspect min-max]", + "[--auto-scale-content]", + "[--center | --position X,Y]", + "[--confine-cursor X,Y,W,H]", + "[--depth N]", + "[--display N]", + "[--flash-on-focus-loss]", + "[--fullscreen | --fullscreen-desktop | --windows N]", + "[--geometry WxH]", + "[--gldebug]", + "[--grab]", + "[--hidden]", + "[--hide-cursor]", + "[--high-pixel-density]", + "[--icon icon.bmp]", + "[--input-focus]", + "[--keyboard-grab]", + "[--logical-presentation disabled|match|stretch|letterbox|overscan|integer_scale]", + "[--logical-scale-quality nearest|linear|best]", + "[--logical WxH]", + "[--max-geometry WxH]", + "[--maximize]", + "[--metal-window | --opengl-window | --vulkan-window]", + "[--min-geometry WxH]", + "[--minimize]", + "[--mouse-focus]", + "[--noframe]", + "[--refresh R]", + "[--renderer driver]", + "[--resizable]", + "[--scale N]", + "[--title title]", + "[--transparent]", + "[--usable-bounds]", + "[--utility]", + "[--video driver]", + "[--gpu driver]", + "[--vsync]", + NULL +}; + +/* !!! FIXME: Float32? Sint32? */ +static const char *audio_usage[] = { + "[--audio driver]", + "[--rate N]", + "[--format U8|S8|S16|S16LE|S16BE|S32|S32LE|S32BE|F32|F32LE|F32BE]", + "[--channels N]", + NULL +}; + +static void SDL_snprintfcat(SDL_OUT_Z_CAP(maxlen) char *text, size_t maxlen, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) +{ + size_t length = SDL_strlen(text); + va_list ap; + + va_start(ap, fmt); + text += length; + maxlen -= length; + (void)SDL_vsnprintf(text, maxlen, fmt, ap); + va_end(ap); +} + +static void SDLCALL SDLTest_CommonArgParserFinalize(void *data) +{ + SDLTest_CommonState *state = data; + + if (!(state->flags & SDL_INIT_VIDEO)) { + state->video_argparser.usage = NULL; + } + if (!(state->flags & SDL_INIT_AUDIO)) { + state->audio_argparser.usage = NULL; + } +} + +#define SEARCHARG(dim) \ + while (*(dim) && *(dim) != ',') { \ + ++(dim); \ + } \ + if (!*(dim)) { \ + return -1; \ + } \ + *(dim)++ = '\0'; + +static int SDLCALL SDLTest_CommonStateParseCommonArguments(void *data, char **argv, int index) +{ + SDLTest_CommonState *state = data; + + if ((SDL_strcasecmp(argv[index], "-h") == 0) || (SDL_strcasecmp(argv[index], "--help") == 0)) { + /* Print the usage message */ + return -1; + } + if (SDL_strcasecmp(argv[index], "--trackmem") == 0) { + /* Already handled in SDLTest_CommonCreateState() */ + return 1; + } + if (SDL_strcasecmp(argv[index], "--randmem") == 0) { + /* Already handled in SDLTest_CommonCreateState() */ + return 1; + } + if (SDL_strcasecmp(argv[index], "--log") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + if (SDL_strcasecmp(argv[index], "all") == 0) { + SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE); + return 2; + } + if (SDL_strcasecmp(argv[index], "system") == 0) { + SDL_SetLogPriority(SDL_LOG_CATEGORY_SYSTEM, SDL_LOG_PRIORITY_VERBOSE); + return 2; + } + if (SDL_strcasecmp(argv[index], "audio") == 0) { + SDL_SetLogPriority(SDL_LOG_CATEGORY_AUDIO, SDL_LOG_PRIORITY_VERBOSE); + return 2; + } + if (SDL_strcasecmp(argv[index], "video") == 0) { + SDL_SetLogPriority(SDL_LOG_CATEGORY_VIDEO, SDL_LOG_PRIORITY_VERBOSE); + return 2; + } + if (SDL_strcasecmp(argv[index], "render") == 0) { + SDL_SetLogPriority(SDL_LOG_CATEGORY_RENDER, SDL_LOG_PRIORITY_VERBOSE); + return 2; + } + if (SDL_strcasecmp(argv[index], "input") == 0) { + SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_VERBOSE); + return 2; + } + return -1; + } + + if (SDL_strcasecmp(argv[index], "--info") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + if (SDL_strcasecmp(argv[index], "all") == 0) { + state->verbose |= + (VERBOSE_VIDEO | VERBOSE_MODES | VERBOSE_RENDER | + VERBOSE_EVENT); + return 2; + } + if (SDL_strcasecmp(argv[index], "video") == 0) { + state->verbose |= VERBOSE_VIDEO; + return 2; + } + if (SDL_strcasecmp(argv[index], "modes") == 0) { + state->verbose |= VERBOSE_MODES; + return 2; + } + if (SDL_strcasecmp(argv[index], "render") == 0) { + state->verbose |= VERBOSE_RENDER; + return 2; + } + if (SDL_strcasecmp(argv[index], "event") == 0) { + state->verbose |= VERBOSE_EVENT; + return 2; + } + if (SDL_strcasecmp(argv[index], "event_motion") == 0) { + state->verbose |= (VERBOSE_EVENT | VERBOSE_MOTION); + return 2; + } + return -1; + } + if (SDL_strcmp(argv[index], "-NSDocumentRevisionsDebugMode") == 0) { + /* Debug flag sent by Xcode */ + return 2; + } + return 0; +} + +static int SDLCALL SDLTest_CommonStateParseVideoArguments(void *data, char **argv, int index) +{ + SDLTest_CommonState *state = data; + + if (!(state->flags & SDL_INIT_VIDEO)) { + return 0; + } + + if (SDL_strcasecmp(argv[index], "--video") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->videodriver = argv[index]; + SDL_SetHint(SDL_HINT_VIDEO_DRIVER, state->videodriver); + return 2; + } + if (SDL_strcasecmp(argv[index], "--renderer") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->renderdriver = argv[index]; + SDL_SetHint(SDL_HINT_RENDER_DRIVER, state->renderdriver); + return 2; + } + if (SDL_strcasecmp(argv[index], "--gldebug") == 0) { + state->gl_debug = 1; + return 1; + } + if (SDL_strcasecmp(argv[index], "--display") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->display_index = SDL_atoi(argv[index]); + return 2; + } + if (SDL_strcasecmp(argv[index], "--metal-window") == 0) { + state->window_flags |= SDL_WINDOW_METAL; + return 1; + } + if (SDL_strcasecmp(argv[index], "--opengl-window") == 0) { + state->window_flags |= SDL_WINDOW_OPENGL; + return 1; + } + if (SDL_strcasecmp(argv[index], "--vulkan-window") == 0) { + state->window_flags |= SDL_WINDOW_VULKAN; + return 1; + } + if (SDL_strcasecmp(argv[index], "--fullscreen") == 0) { + state->window_flags |= SDL_WINDOW_FULLSCREEN; + state->fullscreen_exclusive = true; + state->num_windows = 1; + return 1; + } + if (SDL_strcasecmp(argv[index], "--fullscreen-desktop") == 0) { + state->window_flags |= SDL_WINDOW_FULLSCREEN; + state->fullscreen_exclusive = false; + state->num_windows = 1; + return 1; + } + if (SDL_strcasecmp(argv[index], "--windows") == 0) { + ++index; + if (!argv[index] || !SDL_isdigit((unsigned char) *argv[index])) { + return -1; + } + if (!(state->window_flags & SDL_WINDOW_FULLSCREEN)) { + state->num_windows = SDL_atoi(argv[index]); + } + return 2; + } + if (SDL_strcasecmp(argv[index], "--title") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->window_title = argv[index]; + return 2; + } + if (SDL_strcasecmp(argv[index], "--icon") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->window_icon = argv[index]; + return 2; + } + if (SDL_strcasecmp(argv[index], "--center") == 0) { + state->window_x = SDL_WINDOWPOS_CENTERED; + state->window_y = SDL_WINDOWPOS_CENTERED; + return 1; + } + if (SDL_strcasecmp(argv[index], "--position") == 0) { + char *x, *y; + ++index; + if (!argv[index]) { + return -1; + } + x = argv[index]; + y = argv[index]; + while (*y && *y != ',') { + ++y; + } + if (!*y) { + return -1; + } + *y++ = '\0'; + state->window_x = SDL_atoi(x); + state->window_y = SDL_atoi(y); + return 2; + } + if (SDL_strcasecmp(argv[index], "--confine-cursor") == 0) { + char *x, *y, *w, *h; + ++index; + if (!argv[index]) { + return -1; + } + x = argv[index]; + y = argv[index]; + SEARCHARG(y) + w = y; + SEARCHARG(w) + h = w; + SEARCHARG(h) + state->confine.x = SDL_atoi(x); + state->confine.y = SDL_atoi(y); + state->confine.w = SDL_atoi(w); + state->confine.h = SDL_atoi(h); + return 2; + } + if (SDL_strcasecmp(argv[index], "--usable-bounds") == 0) { + state->fill_usable_bounds = true; + return 1; + } + if (SDL_strcasecmp(argv[index], "--geometry") == 0) { + char *w, *h; + ++index; + if (!argv[index]) { + return -1; + } + w = argv[index]; + h = argv[index]; + while (*h && *h != 'x') { + ++h; + } + if (!*h) { + return -1; + } + *h++ = '\0'; + state->window_w = SDL_atoi(w); + state->window_h = SDL_atoi(h); + return 2; + } + if (SDL_strcasecmp(argv[index], "--min-geometry") == 0) { + char *w, *h; + ++index; + if (!argv[index]) { + return -1; + } + w = argv[index]; + h = argv[index]; + while (*h && *h != 'x') { + ++h; + } + if (!*h) { + return -1; + } + *h++ = '\0'; + state->window_minW = SDL_atoi(w); + state->window_minH = SDL_atoi(h); + return 2; + } + if (SDL_strcasecmp(argv[index], "--max-geometry") == 0) { + char *w, *h; + ++index; + if (!argv[index]) { + return -1; + } + w = argv[index]; + h = argv[index]; + while (*h && *h != 'x') { + ++h; + } + if (!*h) { + return -1; + } + *h++ = '\0'; + state->window_maxW = SDL_atoi(w); + state->window_maxH = SDL_atoi(h); + return 2; + } + if (SDL_strcasecmp(argv[index], "--aspect") == 0) { + char *min_aspect, *max_aspect; + ++index; + if (!argv[index]) { + return -1; + } + min_aspect = argv[index]; + max_aspect = argv[index]; + while (*max_aspect && *max_aspect != '-') { + ++max_aspect; + } + if (*max_aspect) { + *max_aspect++ = '\0'; + } else { + max_aspect = min_aspect; + } + state->window_min_aspect = (float)SDL_atof(min_aspect); + state->window_max_aspect = (float)SDL_atof(max_aspect); + return 2; + } + if (SDL_strcasecmp(argv[index], "--logical") == 0) { + char *w, *h; + ++index; + if (!argv[index]) { + return -1; + } + w = argv[index]; + h = argv[index]; + while (*h && *h != 'x') { + ++h; + } + if (!*h) { + return -1; + } + *h++ = '\0'; + state->logical_w = SDL_atoi(w); + state->logical_h = SDL_atoi(h); + return 2; + } + if (SDL_strcasecmp(argv[index], "--high-pixel-density") == 0) { + state->window_flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY; + return 1; + } + if (SDL_strcasecmp(argv[index], "--auto-scale-content") == 0) { + state->auto_scale_content = true; + + if (state->logical_presentation == SDL_LOGICAL_PRESENTATION_DISABLED) { + state->logical_presentation = SDL_LOGICAL_PRESENTATION_STRETCH; + } + return 1; + } + if (SDL_strcasecmp(argv[index], "--logical-presentation") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + if (SDL_strcasecmp(argv[index], "disabled") == 0) { + state->logical_presentation = SDL_LOGICAL_PRESENTATION_DISABLED; + return 2; + } + if (SDL_strcasecmp(argv[index], "stretch") == 0) { + state->logical_presentation = SDL_LOGICAL_PRESENTATION_STRETCH; + return 2; + } + if (SDL_strcasecmp(argv[index], "letterbox") == 0) { + state->logical_presentation = SDL_LOGICAL_PRESENTATION_LETTERBOX; + return 2; + } + if (SDL_strcasecmp(argv[index], "overscan") == 0) { + state->logical_presentation = SDL_LOGICAL_PRESENTATION_OVERSCAN; + return 2; + } + if (SDL_strcasecmp(argv[index], "integer_scale") == 0) { + state->logical_presentation = SDL_LOGICAL_PRESENTATION_INTEGER_SCALE; + return 2; + } + return -1; + } + if (SDL_strcasecmp(argv[index], "--scale") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->scale = (float) SDL_atof(argv[index]); + return 2; + } + if (SDL_strcasecmp(argv[index], "--depth") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->depth = SDL_atoi(argv[index]); + return 2; + } + if (SDL_strcasecmp(argv[index], "--refresh") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->refresh_rate = (float) SDL_atof(argv[index]); + return 2; + } + if (SDL_strcasecmp(argv[index], "--vsync") == 0) { + state->render_vsync = 1; + return 1; + } + if (SDL_strcasecmp(argv[index], "--noframe") == 0) { + state->window_flags |= SDL_WINDOW_BORDERLESS; + return 1; + } + if (SDL_strcasecmp(argv[index], "--resizable") == 0) { + state->window_flags |= SDL_WINDOW_RESIZABLE; + return 1; + } + if (SDL_strcasecmp(argv[index], "--transparent") == 0) { + state->window_flags |= SDL_WINDOW_TRANSPARENT; + return 1; + } + if (SDL_strcasecmp(argv[index], "--always-on-top") == 0) { + state->window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; + return 1; + } + if (SDL_strcasecmp(argv[index], "--minimize") == 0) { + state->window_flags |= SDL_WINDOW_MINIMIZED; + return 1; + } + if (SDL_strcasecmp(argv[index], "--maximize") == 0) { + state->window_flags |= SDL_WINDOW_MAXIMIZED; + return 1; + } + if (SDL_strcasecmp(argv[index], "--hidden") == 0) { + state->window_flags |= SDL_WINDOW_HIDDEN; + return 1; + } + if (SDL_strcasecmp(argv[index], "--input-focus") == 0) { + state->window_flags |= SDL_WINDOW_INPUT_FOCUS; + return 1; + } + if (SDL_strcasecmp(argv[index], "--mouse-focus") == 0) { + state->window_flags |= SDL_WINDOW_MOUSE_FOCUS; + return 1; + } + if (SDL_strcasecmp(argv[index], "--flash-on-focus-loss") == 0) { + state->flash_on_focus_loss = true; + return 1; + } + if (SDL_strcasecmp(argv[index], "--grab") == 0) { + state->window_flags |= SDL_WINDOW_MOUSE_GRABBED; + return 1; + } + if (SDL_strcasecmp(argv[index], "--keyboard-grab") == 0) { + state->window_flags |= SDL_WINDOW_KEYBOARD_GRABBED; + return 1; + } + if (SDL_strcasecmp(argv[index], "--utility") == 0) { + state->window_flags |= SDL_WINDOW_UTILITY; + return 1; + } + if (SDL_strcasecmp(argv[index], "--hide-cursor") == 0) { + state->hide_cursor = true; + return 1; + } + if (SDL_strcasecmp(argv[index], "--gpu") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->gpudriver = argv[index]; + SDL_SetHint(SDL_HINT_GPU_DRIVER, state->gpudriver); + return 2; + } + return 0; +} + +static int SDLCALL SDLTest_CommonStateParseAudioArguments(void *data, char **argv, int index) +{ + SDLTest_CommonState *state = data; + + if (!(state->flags & SDL_INIT_AUDIO)) { + return 0; + } + if (SDL_strcasecmp(argv[index], "--audio") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->audiodriver = argv[index]; + SDL_SetHint(SDL_HINT_AUDIO_DRIVER, state->audiodriver); + return 2; + } + if (SDL_strcasecmp(argv[index], "--rate") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->audio_freq = SDL_atoi(argv[index]); + return 2; + } + if (SDL_strcasecmp(argv[index], "--format") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + if (SDL_strcasecmp(argv[index], "U8") == 0) { + state->audio_format = SDL_AUDIO_U8; + return 2; + } + if (SDL_strcasecmp(argv[index], "S8") == 0) { + state->audio_format = SDL_AUDIO_S8; + return 2; + } + if (SDL_strcasecmp(argv[index], "S16") == 0) { + state->audio_format = SDL_AUDIO_S16; + return 2; + } + if (SDL_strcasecmp(argv[index], "S16LE") == 0) { + state->audio_format = SDL_AUDIO_S16LE; + return 2; + } + if (SDL_strcasecmp(argv[index], "S16BE") == 0) { + state->audio_format = SDL_AUDIO_S16BE; + return 2; + } + if (SDL_strcasecmp(argv[index], "S32") == 0) { + state->audio_format = SDL_AUDIO_S32; + return 2; + } + if (SDL_strcasecmp(argv[index], "S32LE") == 0) { + state->audio_format = SDL_AUDIO_S32LE; + return 2; + } + if (SDL_strcasecmp(argv[index], "S32BE") == 0) { + state->audio_format = SDL_AUDIO_S32BE; + return 2; + } + if (SDL_strcasecmp(argv[index], "F32") == 0) { + state->audio_format = SDL_AUDIO_F32; + return 2; + } + if (SDL_strcasecmp(argv[index], "F32LE") == 0) { + state->audio_format = SDL_AUDIO_F32LE; + return 2; + } + if (SDL_strcasecmp(argv[index], "F32BE") == 0) { + state->audio_format = SDL_AUDIO_F32BE; + return 2; + } + return -1; + } + if (SDL_strcasecmp(argv[index], "--channels") == 0) { + ++index; + if (!argv[index]) { + return -1; + } + state->audio_channels = (Uint8) SDL_atoi(argv[index]); + return 2; + } + return 0; +} + +SDLTest_CommonState *SDLTest_CommonCreateState(char **argv, SDL_InitFlags flags) +{ + int i; + SDLTest_CommonState *state; + + /* Do this first so we catch all allocations */ + for (i = 1; argv[i]; ++i) { + if (SDL_strcasecmp(argv[i], "--trackmem") == 0) { + SDLTest_TrackAllocations(); + } else if (SDL_strcasecmp(argv[i], "--randmem") == 0) { + SDLTest_RandFillAllocations(); + } + } + + state = (SDLTest_CommonState *)SDL_calloc(1, sizeof(*state)); + if (!state) { + return NULL; + } + + /* Initialize some defaults */ + state->argv = argv; + state->flags = flags; + state->window_title = argv[0]; + state->window_flags = SDL_WINDOW_HIDDEN; + state->window_x = SDL_WINDOWPOS_UNDEFINED; + state->window_y = SDL_WINDOWPOS_UNDEFINED; + state->window_w = DEFAULT_WINDOW_WIDTH; + state->window_h = DEFAULT_WINDOW_HEIGHT; + state->logical_presentation = SDL_LOGICAL_PRESENTATION_DISABLED; + state->num_windows = 1; + state->audio_freq = 22050; + state->audio_format = SDL_AUDIO_S16; + state->audio_channels = 2; + + /* Set some very sane GL defaults */ + state->gl_red_size = 8; + state->gl_green_size = 8; + state->gl_blue_size = 8; + state->gl_alpha_size = 8; + state->gl_buffer_size = 0; + state->gl_depth_size = 16; + state->gl_stencil_size = 0; + state->gl_double_buffer = 1; + state->gl_accum_red_size = 0; + state->gl_accum_green_size = 0; + state->gl_accum_blue_size = 0; + state->gl_accum_alpha_size = 0; + state->gl_stereo = 0; + state->gl_multisamplebuffers = 0; + state->gl_multisamplesamples = 0; + state->gl_retained_backing = 1; + state->gl_accelerated = -1; + state->gl_debug = 0; + + state->common_argparser.parse_arguments = SDLTest_CommonStateParseCommonArguments; + state->common_argparser.finalize = SDLTest_CommonArgParserFinalize; + state->common_argparser.usage = common_usage; + state->common_argparser.data = state; + state->common_argparser.next = &state->video_argparser; + + state->video_argparser.parse_arguments = SDLTest_CommonStateParseVideoArguments; + state->video_argparser.finalize = NULL; + state->video_argparser.usage = video_usage; + state->video_argparser.data = state; + state->video_argparser.next = &state->audio_argparser; + + state->audio_argparser.parse_arguments = SDLTest_CommonStateParseAudioArguments; + state->audio_argparser.finalize = NULL; + state->audio_argparser.usage = audio_usage; + state->audio_argparser.data = state; + + state->argparser = &state->common_argparser; + + return state; +} + +void SDLTest_CommonDestroyState(SDLTest_CommonState *state) { + SDL_free(state); + SDLTest_LogAllocations(); +} + +int SDLTest_CommonArg(SDLTest_CommonState *state, int index) +{ + SDLTest_ArgumentParser *argparser = state->argparser; + + /* Go back and parse arguments as we go */ + while (argparser) { + if (argparser->parse_arguments) { + int consumed = argparser->parse_arguments(argparser->data, state->argv, index); + if (consumed != 0) { + return consumed; + } + } + argparser = argparser->next; + } + return 0; +} + +void SDLTest_CommonLogUsage(SDLTest_CommonState *state, const char *argv0, const char **options) +{ + SDLTest_ArgumentParser *argparser; + + SDL_Log("USAGE: %s", argv0); + + for (argparser = state->argparser; argparser; argparser = argparser->next) { + if (argparser->finalize) { + argparser->finalize(argparser->data); + } + if (argparser->usage) { + int i; + for (i = 0; argparser->usage[i] != NULL; i++) { + SDL_Log(" %s", argparser->usage[i]); + } + } + } + if (options) { + int i; + for (i = 0; options[i] != NULL; i++) { + SDL_Log(" %s", options[i]); + } + } +} + +bool SDLTest_CommonDefaultArgs(SDLTest_CommonState *state, int argc, char **argv) +{ + int i = 1; + while (i < argc) { + const int consumed = SDLTest_CommonArg(state, i); + if (consumed <= 0) { + SDLTest_CommonLogUsage(state, argv[0], NULL); + return false; + } + i += consumed; + } + return true; +} + +static void SDLTest_PrintDisplayOrientation(char *text, size_t maxlen, SDL_DisplayOrientation orientation) +{ + switch (orientation) { + case SDL_ORIENTATION_UNKNOWN: + SDL_snprintfcat(text, maxlen, "UNKNOWN"); + break; + case SDL_ORIENTATION_LANDSCAPE: + SDL_snprintfcat(text, maxlen, "LANDSCAPE"); + break; + case SDL_ORIENTATION_LANDSCAPE_FLIPPED: + SDL_snprintfcat(text, maxlen, "LANDSCAPE_FLIPPED"); + break; + case SDL_ORIENTATION_PORTRAIT: + SDL_snprintfcat(text, maxlen, "PORTRAIT"); + break; + case SDL_ORIENTATION_PORTRAIT_FLIPPED: + SDL_snprintfcat(text, maxlen, "PORTRAIT_FLIPPED"); + break; + default: + SDL_snprintfcat(text, maxlen, "0x%8.8x", orientation); + break; + } +} + +static void SDLTest_PrintWindowFlag(char *text, size_t maxlen, SDL_WindowFlags flag) +{ + switch (flag) { + case SDL_WINDOW_FULLSCREEN: + SDL_snprintfcat(text, maxlen, "FULLSCREEN"); + break; + case SDL_WINDOW_OPENGL: + SDL_snprintfcat(text, maxlen, "OPENGL"); + break; + case SDL_WINDOW_OCCLUDED: + SDL_snprintfcat(text, maxlen, "OCCLUDED"); + break; + case SDL_WINDOW_HIDDEN: + SDL_snprintfcat(text, maxlen, "HIDDEN"); + break; + case SDL_WINDOW_BORDERLESS: + SDL_snprintfcat(text, maxlen, "BORDERLESS"); + break; + case SDL_WINDOW_RESIZABLE: + SDL_snprintfcat(text, maxlen, "RESIZABLE"); + break; + case SDL_WINDOW_MINIMIZED: + SDL_snprintfcat(text, maxlen, "MINIMIZED"); + break; + case SDL_WINDOW_MAXIMIZED: + SDL_snprintfcat(text, maxlen, "MAXIMIZED"); + break; + case SDL_WINDOW_MOUSE_GRABBED: + SDL_snprintfcat(text, maxlen, "MOUSE_GRABBED"); + break; + case SDL_WINDOW_INPUT_FOCUS: + SDL_snprintfcat(text, maxlen, "INPUT_FOCUS"); + break; + case SDL_WINDOW_MOUSE_FOCUS: + SDL_snprintfcat(text, maxlen, "MOUSE_FOCUS"); + break; + case SDL_WINDOW_EXTERNAL: + SDL_snprintfcat(text, maxlen, "EXTERNAL"); + break; + case SDL_WINDOW_MODAL: + SDL_snprintfcat(text, maxlen, "MODAL"); + break; + case SDL_WINDOW_HIGH_PIXEL_DENSITY: + SDL_snprintfcat(text, maxlen, "HIGH_PIXEL_DENSITY"); + break; + case SDL_WINDOW_MOUSE_CAPTURE: + SDL_snprintfcat(text, maxlen, "MOUSE_CAPTURE"); + break; + case SDL_WINDOW_MOUSE_RELATIVE_MODE: + SDL_snprintfcat(text, maxlen, "MOUSE_RELATIVE_MODE"); + break; + case SDL_WINDOW_ALWAYS_ON_TOP: + SDL_snprintfcat(text, maxlen, "ALWAYS_ON_TOP"); + break; + case SDL_WINDOW_UTILITY: + SDL_snprintfcat(text, maxlen, "UTILITY"); + break; + case SDL_WINDOW_TOOLTIP: + SDL_snprintfcat(text, maxlen, "TOOLTIP"); + break; + case SDL_WINDOW_POPUP_MENU: + SDL_snprintfcat(text, maxlen, "POPUP_MENU"); + break; + case SDL_WINDOW_KEYBOARD_GRABBED: + SDL_snprintfcat(text, maxlen, "KEYBOARD_GRABBED"); + break; + case SDL_WINDOW_VULKAN: + SDL_snprintfcat(text, maxlen, "VULKAN"); + break; + case SDL_WINDOW_METAL: + SDL_snprintfcat(text, maxlen, "METAL"); + break; + case SDL_WINDOW_TRANSPARENT: + SDL_snprintfcat(text, maxlen, "TRANSPARENT"); + break; + case SDL_WINDOW_NOT_FOCUSABLE: + SDL_snprintfcat(text, maxlen, "NOT_FOCUSABLE"); + break; + default: + SDL_snprintfcat(text, maxlen, "0x%16.16" SDL_PRIx64, flag); + break; + } +} + +static void SDLTest_PrintWindowFlags(char *text, size_t maxlen, SDL_WindowFlags flags) +{ + const SDL_WindowFlags window_flags[] = { + SDL_WINDOW_FULLSCREEN, + SDL_WINDOW_OPENGL, + SDL_WINDOW_OCCLUDED, + SDL_WINDOW_HIDDEN, + SDL_WINDOW_BORDERLESS, + SDL_WINDOW_RESIZABLE, + SDL_WINDOW_MINIMIZED, + SDL_WINDOW_MAXIMIZED, + SDL_WINDOW_MOUSE_GRABBED, + SDL_WINDOW_INPUT_FOCUS, + SDL_WINDOW_MOUSE_FOCUS, + SDL_WINDOW_EXTERNAL, + SDL_WINDOW_MODAL, + SDL_WINDOW_HIGH_PIXEL_DENSITY, + SDL_WINDOW_MOUSE_CAPTURE, + SDL_WINDOW_MOUSE_RELATIVE_MODE, + SDL_WINDOW_ALWAYS_ON_TOP, + SDL_WINDOW_UTILITY, + SDL_WINDOW_TOOLTIP, + SDL_WINDOW_POPUP_MENU, + SDL_WINDOW_KEYBOARD_GRABBED, + SDL_WINDOW_VULKAN, + SDL_WINDOW_METAL, + SDL_WINDOW_TRANSPARENT, + SDL_WINDOW_NOT_FOCUSABLE + }; + + int i; + int count = 0; + for (i = 0; i < (sizeof(window_flags) / sizeof(window_flags[0])); ++i) { + const SDL_WindowFlags flag = window_flags[i]; + if ((flags & flag) == flag) { + if (count > 0) { + SDL_snprintfcat(text, maxlen, " | "); + } + SDLTest_PrintWindowFlag(text, maxlen, flag); + ++count; + } + } +} + +static void SDLTest_PrintModStateFlag(char *text, size_t maxlen, SDL_Keymod flag) +{ + switch (flag) { + case SDL_KMOD_LSHIFT: + SDL_snprintfcat(text, maxlen, "LSHIFT"); + break; + case SDL_KMOD_RSHIFT: + SDL_snprintfcat(text, maxlen, "RSHIFT"); + break; + case SDL_KMOD_LEVEL5: + SDL_snprintfcat(text, maxlen, "LEVEL5"); + break; + case SDL_KMOD_LCTRL: + SDL_snprintfcat(text, maxlen, "LCTRL"); + break; + case SDL_KMOD_RCTRL: + SDL_snprintfcat(text, maxlen, "RCTRL"); + break; + case SDL_KMOD_LALT: + SDL_snprintfcat(text, maxlen, "LALT"); + break; + case SDL_KMOD_RALT: + SDL_snprintfcat(text, maxlen, "RALT"); + break; + case SDL_KMOD_LGUI: + SDL_snprintfcat(text, maxlen, "LGUI"); + break; + case SDL_KMOD_RGUI: + SDL_snprintfcat(text, maxlen, "RGUI"); + break; + case SDL_KMOD_NUM: + SDL_snprintfcat(text, maxlen, "NUM"); + break; + case SDL_KMOD_CAPS: + SDL_snprintfcat(text, maxlen, "CAPS"); + break; + case SDL_KMOD_MODE: + SDL_snprintfcat(text, maxlen, "MODE"); + break; + case SDL_KMOD_SCROLL: + SDL_snprintfcat(text, maxlen, "SCROLL"); + break; + default: + SDL_snprintfcat(text, maxlen, "0x%8.8x", (unsigned int) flag); + break; + } +} + +static void SDLTest_PrintModState(char *text, size_t maxlen, SDL_Keymod keymod) +{ + const SDL_Keymod kmod_flags[] = { + SDL_KMOD_LSHIFT, + SDL_KMOD_RSHIFT, + SDL_KMOD_LEVEL5, + SDL_KMOD_LCTRL, + SDL_KMOD_RCTRL, + SDL_KMOD_LALT, + SDL_KMOD_RALT, + SDL_KMOD_LGUI, + SDL_KMOD_RGUI, + SDL_KMOD_NUM, + SDL_KMOD_CAPS, + SDL_KMOD_MODE, + SDL_KMOD_SCROLL + }; + + int i; + int count = 0; + for (i = 0; i < SDL_arraysize(kmod_flags); ++i) { + const SDL_Keymod flag = kmod_flags[i]; + if ((keymod & flag) == flag) { + if (count > 0) { + SDL_snprintfcat(text, maxlen, " | "); + } + SDLTest_PrintModStateFlag(text, maxlen, flag); + ++count; + } + } +} + +static void SDLTest_PrintButtonMask(char *text, size_t maxlen, SDL_MouseButtonFlags flags) +{ + int i; + int count = 0; + for (i = 1; i <= 32; ++i) { + const Uint32 flag = SDL_BUTTON_MASK(i); + if ((flags & flag) == flag) { + if (count > 0) { + SDL_snprintfcat(text, maxlen, " | "); + } + SDL_snprintfcat(text, maxlen, "SDL_BUTTON_MASK(%d)", i); + ++count; + } + } +} + +static void SDLTest_PrintPixelFormat(char *text, size_t maxlen, Uint32 format) +{ + const char *name = SDL_GetPixelFormatName(format); + if (name) { + if (SDL_strncmp(name, "SDL_PIXELFORMAT_", 16) == 0) { + name += 16; + } + SDL_snprintfcat(text, maxlen, name); + } else { + SDL_snprintfcat(text, maxlen, "0x%8.8x", format); + } +} + +static void SDLTest_PrintLogicalPresentation(char *text, size_t maxlen, SDL_RendererLogicalPresentation logical_presentation) +{ + switch (logical_presentation) { + case SDL_LOGICAL_PRESENTATION_DISABLED: + SDL_snprintfcat(text, maxlen, "DISABLED"); + break; + case SDL_LOGICAL_PRESENTATION_STRETCH: + SDL_snprintfcat(text, maxlen, "STRETCH"); + break; + case SDL_LOGICAL_PRESENTATION_LETTERBOX: + SDL_snprintfcat(text, maxlen, "LETTERBOX"); + break; + case SDL_LOGICAL_PRESENTATION_OVERSCAN: + SDL_snprintfcat(text, maxlen, "OVERSCAN"); + break; + case SDL_LOGICAL_PRESENTATION_INTEGER_SCALE: + SDL_snprintfcat(text, maxlen, "INTEGER_SCALE"); + break; + default: + SDL_snprintfcat(text, maxlen, "0x%8.8x", logical_presentation); + break; + } +} + +static void SDLTest_PrintRenderer(SDL_Renderer *renderer) +{ + const char *name; + int i; + char text[1024]; + int max_texture_size; + const SDL_PixelFormat *texture_formats; + + name = SDL_GetRendererName(renderer); + + SDL_Log(" Renderer %s:", name); + if (SDL_strcmp(name, "gpu") == 0) { + SDL_GPUDevice *device = SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_GPU_DEVICE_POINTER, NULL); + SDL_Log(" Driver: %s", SDL_GetGPUDeviceDriver(device)); + } + SDL_Log(" VSync: %d", (int)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_VSYNC_NUMBER, 0)); + + texture_formats = (const SDL_PixelFormat *)SDL_GetPointerProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL); + if (texture_formats) { + (void)SDL_snprintf(text, sizeof(text), " Texture formats: "); + for (i = 0; texture_formats[i]; ++i) { + if (i > 0) { + SDL_snprintfcat(text, sizeof(text), ", "); + } + SDLTest_PrintPixelFormat(text, sizeof(text), texture_formats[i]); + } + SDL_Log("%s", text); + } + + max_texture_size = (int)SDL_GetNumberProperty(SDL_GetRendererProperties(renderer), SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, 0); + if (max_texture_size) { + SDL_Log(" Max Texture Size: %dx%d", max_texture_size, max_texture_size); + } +} + +static SDL_Surface *SDLTest_LoadIcon(const char *file) +{ + SDL_Surface *icon; + + /* Load the icon surface */ + icon = SDL_LoadBMP(file); + if (!icon) { + SDL_Log("Couldn't load %s: %s", file, SDL_GetError()); + return NULL; + } + + if (icon->format == SDL_PIXELFORMAT_INDEX8) { + /* Set the colorkey */ + SDL_SetSurfaceColorKey(icon, 1, *((Uint8 *)icon->pixels)); + } + + return icon; +} + +static SDL_HitTestResult SDLCALL SDLTest_ExampleHitTestCallback(SDL_Window *win, const SDL_Point *area, void *data) +{ + int w, h; + const int RESIZE_BORDER = 8; + const int DRAGGABLE_TITLE = 32; + + /*SDL_Log("Hit test point %d,%d", area->x, area->y);*/ + + SDL_GetWindowSize(win, &w, &h); + + if (area->x < RESIZE_BORDER) { + if (area->y < RESIZE_BORDER) { + SDL_Log("SDL_HITTEST_RESIZE_TOPLEFT"); + return SDL_HITTEST_RESIZE_TOPLEFT; + } else if (area->y >= (h - RESIZE_BORDER)) { + SDL_Log("SDL_HITTEST_RESIZE_BOTTOMLEFT"); + return SDL_HITTEST_RESIZE_BOTTOMLEFT; + } else { + SDL_Log("SDL_HITTEST_RESIZE_LEFT"); + return SDL_HITTEST_RESIZE_LEFT; + } + } else if (area->x >= (w - RESIZE_BORDER)) { + if (area->y < RESIZE_BORDER) { + SDL_Log("SDL_HITTEST_RESIZE_TOPRIGHT"); + return SDL_HITTEST_RESIZE_TOPRIGHT; + } else if (area->y >= (h - RESIZE_BORDER)) { + SDL_Log("SDL_HITTEST_RESIZE_BOTTOMRIGHT"); + return SDL_HITTEST_RESIZE_BOTTOMRIGHT; + } else { + SDL_Log("SDL_HITTEST_RESIZE_RIGHT"); + return SDL_HITTEST_RESIZE_RIGHT; + } + } else if (area->y >= (h - RESIZE_BORDER)) { + SDL_Log("SDL_HITTEST_RESIZE_BOTTOM"); + return SDL_HITTEST_RESIZE_BOTTOM; + } else if (area->y < RESIZE_BORDER) { + SDL_Log("SDL_HITTEST_RESIZE_TOP"); + return SDL_HITTEST_RESIZE_TOP; + } else if (area->y < DRAGGABLE_TITLE) { + SDL_Log("SDL_HITTEST_DRAGGABLE"); + return SDL_HITTEST_DRAGGABLE; + } + return SDL_HITTEST_NORMAL; +} + +bool SDLTest_CommonInit(SDLTest_CommonState *state) +{ + int i, j, m, n, w, h; + char text[1024]; + + if (state->flags & SDL_INIT_VIDEO) { + if (state->verbose & VERBOSE_VIDEO) { + n = SDL_GetNumVideoDrivers(); + if (n == 0) { + SDL_Log("No built-in video drivers"); + } else { + (void)SDL_snprintf(text, sizeof(text), "Built-in video drivers:"); + for (i = 0; i < n; ++i) { + if (i > 0) { + SDL_snprintfcat(text, sizeof(text), ","); + } + SDL_snprintfcat(text, sizeof(text), " %s", SDL_GetVideoDriver(i)); + } + SDL_Log("%s", text); + } + } + if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize video driver: %s", + SDL_GetError()); + return false; + } + if (state->verbose & VERBOSE_VIDEO) { + SDL_Log("Video driver: %s", + SDL_GetCurrentVideoDriver()); + } + + /* Upload GL settings */ + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, state->gl_red_size); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, state->gl_green_size); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, state->gl_blue_size); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, state->gl_alpha_size); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, state->gl_double_buffer); + SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, state->gl_buffer_size); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, state->gl_depth_size); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, state->gl_stencil_size); + SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, state->gl_accum_red_size); + SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, state->gl_accum_green_size); + SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, state->gl_accum_blue_size); + SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, state->gl_accum_alpha_size); + SDL_GL_SetAttribute(SDL_GL_STEREO, state->gl_stereo); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_RELEASE_BEHAVIOR, state->gl_release_behavior); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, state->gl_multisamplebuffers); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, state->gl_multisamplesamples); + if (state->gl_accelerated >= 0) { + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, + state->gl_accelerated); + } + SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, state->gl_retained_backing); + if (state->gl_major_version) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, state->gl_major_version); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, state->gl_minor_version); + } + if (state->gl_debug) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); + } + if (state->gl_profile_mask) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, state->gl_profile_mask); + } + + if (state->verbose & VERBOSE_MODES) { + SDL_DisplayID *displays; + SDL_Rect bounds, usablebounds; + SDL_DisplayMode **modes; + const SDL_DisplayMode *mode; + int bpp; + Uint32 Rmask, Gmask, Bmask, Amask; +#ifdef SDL_VIDEO_DRIVER_WINDOWS + int adapterIndex = 0; + int outputIndex = 0; +#endif + displays = SDL_GetDisplays(&n); + SDL_Log("Number of displays: %d", n); + for (i = 0; i < n; ++i) { + SDL_DisplayID displayID = displays[i]; + SDL_Log("Display %" SDL_PRIu32 ": %s", displayID, SDL_GetDisplayName(displayID)); + + SDL_zero(bounds); + SDL_GetDisplayBounds(displayID, &bounds); + + SDL_zero(usablebounds); + SDL_GetDisplayUsableBounds(displayID, &usablebounds); + + SDL_Log("Bounds: %dx%d at %d,%d", bounds.w, bounds.h, bounds.x, bounds.y); + SDL_Log("Usable bounds: %dx%d at %d,%d", usablebounds.w, usablebounds.h, usablebounds.x, usablebounds.y); + + mode = SDL_GetDesktopDisplayMode(displayID); + SDL_GetMasksForPixelFormat(mode->format, &bpp, &Rmask, &Gmask, + &Bmask, &Amask); + SDL_Log(" Desktop mode: %dx%d@%gx %gHz, %d bits-per-pixel (%s)", + mode->w, mode->h, mode->pixel_density, mode->refresh_rate, bpp, + SDL_GetPixelFormatName(mode->format)); + if (Rmask || Gmask || Bmask) { + SDL_Log(" Red Mask = 0x%.8" SDL_PRIx32, Rmask); + SDL_Log(" Green Mask = 0x%.8" SDL_PRIx32, Gmask); + SDL_Log(" Blue Mask = 0x%.8" SDL_PRIx32, Bmask); + if (Amask) { + SDL_Log(" Alpha Mask = 0x%.8" SDL_PRIx32, Amask); + } + } + + /* Print available fullscreen video modes */ + modes = SDL_GetFullscreenDisplayModes(displayID, &m); + if (m == 0) { + SDL_Log("No available fullscreen video modes"); + } else { + SDL_Log(" Fullscreen video modes:"); + for (j = 0; j < m; ++j) { + mode = modes[j]; + SDL_GetMasksForPixelFormat(mode->format, &bpp, &Rmask, + &Gmask, &Bmask, &Amask); + SDL_Log(" Mode %d: %dx%d@%gx %gHz, %d bits-per-pixel (%s)", + j, mode->w, mode->h, mode->pixel_density, mode->refresh_rate, bpp, + SDL_GetPixelFormatName(mode->format)); + if (Rmask || Gmask || Bmask) { + SDL_Log(" Red Mask = 0x%.8" SDL_PRIx32, + Rmask); + SDL_Log(" Green Mask = 0x%.8" SDL_PRIx32, + Gmask); + SDL_Log(" Blue Mask = 0x%.8" SDL_PRIx32, + Bmask); + if (Amask) { + SDL_Log(" Alpha Mask = 0x%.8" SDL_PRIx32, Amask); + } + } + } + } + SDL_free(modes); + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + /* Print the D3D9 adapter index */ + adapterIndex = SDL_GetDirect3D9AdapterIndex(displayID); + SDL_Log("D3D9 Adapter Index: %d", adapterIndex); + + /* Print the DXGI adapter and output indices */ + SDL_GetDXGIOutputInfo(displayID, &adapterIndex, &outputIndex); + SDL_Log("DXGI Adapter Index: %d Output Index: %d", adapterIndex, outputIndex); +#endif + } + SDL_free(displays); + } + + if (state->verbose & VERBOSE_RENDER) { + n = SDL_GetNumRenderDrivers(); + if (n == 0) { + SDL_Log("No built-in render drivers"); + } else { + SDL_Log("Built-in render drivers:"); + for (i = 0; i < n; ++i) { + SDL_Log(" %s", SDL_GetRenderDriver(i)); + } + } + } + + state->displayID = SDL_GetPrimaryDisplay(); + if (state->display_index > 0) { + SDL_DisplayID *displays = SDL_GetDisplays(&n); + if (state->display_index < n) { + state->displayID = displays[state->display_index]; + } + SDL_free(displays); + + if (SDL_WINDOWPOS_ISUNDEFINED(state->window_x)) { + state->window_x = SDL_WINDOWPOS_UNDEFINED_DISPLAY(state->displayID); + state->window_y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(state->displayID); + } else if (SDL_WINDOWPOS_ISCENTERED(state->window_x)) { + state->window_x = SDL_WINDOWPOS_CENTERED_DISPLAY(state->displayID); + state->window_y = SDL_WINDOWPOS_CENTERED_DISPLAY(state->displayID); + } + } + + { + bool include_high_density_modes = false; + if (state->window_flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { + include_high_density_modes = true; + } + SDL_GetClosestFullscreenDisplayMode(state->displayID, state->window_w, state->window_h, state->refresh_rate, include_high_density_modes, &state->fullscreen_mode); + } + + state->windows = + (SDL_Window **)SDL_calloc(state->num_windows, + sizeof(*state->windows)); + state->renderers = + (SDL_Renderer **)SDL_calloc(state->num_windows, + sizeof(*state->renderers)); + state->targets = + (SDL_Texture **)SDL_calloc(state->num_windows, + sizeof(*state->targets)); + if (!state->windows || !state->renderers) { + SDL_Log("Out of memory!"); + return false; + } + for (i = 0; i < state->num_windows; ++i) { + char title[1024]; + SDL_Rect r; + SDL_PropertiesID props; + + if (state->fill_usable_bounds) { + SDL_GetDisplayUsableBounds(state->displayID, &r); + } else { + r.x = state->window_x; + r.y = state->window_y; + r.w = state->window_w; + r.h = state->window_h; + if (state->auto_scale_content) { + float scale = SDL_GetDisplayContentScale(state->displayID); + r.w = (int)SDL_ceilf(r.w * scale); + r.h = (int)SDL_ceilf(r.h * scale); + } + } + + if (state->num_windows > 1) { + (void)SDL_snprintf(title, SDL_arraysize(title), "%s %d", + state->window_title, i + 1); + } else { + SDL_strlcpy(title, state->window_title, SDL_arraysize(title)); + } + props = SDL_CreateProperties(); + SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, r.x); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, r.y); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, r.w); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, r.h); + SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, state->window_flags); + state->windows[i] = SDL_CreateWindowWithProperties(props); + SDL_DestroyProperties(props); + if (!state->windows[i]) { + SDL_Log("Couldn't create window: %s", + SDL_GetError()); + return false; + } + if (state->window_minW || state->window_minH) { + SDL_SetWindowMinimumSize(state->windows[i], state->window_minW, state->window_minH); + } + if (state->window_maxW || state->window_maxH) { + SDL_SetWindowMaximumSize(state->windows[i], state->window_maxW, state->window_maxH); + } + if (state->window_min_aspect != 0.f || state->window_max_aspect != 0.f) { + SDL_SetWindowAspectRatio(state->windows[i], state->window_min_aspect, state->window_max_aspect); + } + SDL_GetWindowSize(state->windows[i], &w, &h); + if (!(state->window_flags & SDL_WINDOW_RESIZABLE) && (w != r.w || h != r.h)) { + SDL_Log("Window requested size %dx%d, got %dx%d", r.w, r.h, w, h); + state->window_w = w; + state->window_h = h; + } + if (state->window_flags & SDL_WINDOW_FULLSCREEN) { + if (state->fullscreen_exclusive) { + SDL_SetWindowFullscreenMode(state->windows[i], &state->fullscreen_mode); + } + SDL_SetWindowFullscreen(state->windows[i], true); + } + + /* Add resize/drag areas for windows that are borderless and resizable */ + if ((state->window_flags & (SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS)) == + (SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS)) { + SDL_SetWindowHitTest(state->windows[i], SDLTest_ExampleHitTestCallback, NULL); + } + + if (state->window_icon) { + SDL_Surface *icon = SDLTest_LoadIcon(state->window_icon); + if (icon) { + SDL_SetWindowIcon(state->windows[i], icon); + SDL_DestroySurface(icon); + } + } + + if (!SDL_RectEmpty(&state->confine)) { + SDL_SetWindowMouseRect(state->windows[i], &state->confine); + } + + if (!state->skip_renderer && (state->renderdriver || !(state->window_flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_VULKAN | SDL_WINDOW_METAL)))) { + state->renderers[i] = SDL_CreateRenderer(state->windows[i], state->renderdriver); + if (!state->renderers[i]) { + SDL_Log("Couldn't create renderer: %s", + SDL_GetError()); + return false; + } + if (state->logical_w == 0 || state->logical_h == 0) { + state->logical_w = state->window_w; + state->logical_h = state->window_h; + } + if (state->render_vsync) { + SDL_SetRenderVSync(state->renderers[i], state->render_vsync); + } + if (!SDL_SetRenderLogicalPresentation(state->renderers[i], state->logical_w, state->logical_h, state->logical_presentation)) { + SDL_Log("Couldn't set logical presentation: %s", SDL_GetError()); + return false; + } + if (state->scale != 0.0f) { + SDL_SetRenderScale(state->renderers[i], state->scale, state->scale); + } + if (state->verbose & VERBOSE_RENDER) { + SDL_Log("Current renderer:"); + SDLTest_PrintRenderer(state->renderers[i]); + } + } + + SDL_ShowWindow(state->windows[i]); + } + if (state->hide_cursor) { + SDL_HideCursor(); + } + } + + if (state->flags & SDL_INIT_AUDIO) { + if (state->verbose & VERBOSE_AUDIO) { + n = SDL_GetNumAudioDrivers(); + if (n == 0) { + SDL_Log("No built-in audio drivers"); + } else { + (void)SDL_snprintf(text, sizeof(text), "Built-in audio drivers:"); + for (i = 0; i < n; ++i) { + if (i > 0) { + SDL_snprintfcat(text, sizeof(text), ","); + } + SDL_snprintfcat(text, sizeof(text), " %s", SDL_GetAudioDriver(i)); + } + SDL_Log("%s", text); + } + } + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { + SDL_Log("Couldn't initialize audio driver: %s", + SDL_GetError()); + return false; + } + if (state->verbose & VERBOSE_AUDIO) { + SDL_Log("Audio driver: %s", + SDL_GetCurrentAudioDriver()); + } + + const SDL_AudioSpec spec = { state->audio_format, state->audio_channels, state->audio_freq }; + state->audio_id = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec); + if (!state->audio_id) { + SDL_Log("Couldn't open audio: %s", SDL_GetError()); + return false; + } + } + + if (state->flags & SDL_INIT_CAMERA) { + SDL_InitSubSystem(SDL_INIT_CAMERA); + } + + return true; +} + +static const char *SystemThemeName(void) +{ + switch (SDL_GetSystemTheme()) { +#define CASE(X) \ + case SDL_SYSTEM_THEME_##X: \ + return #X + CASE(UNKNOWN); + CASE(LIGHT); + CASE(DARK); +#undef CASE + default: + return "???"; + } +} + +static const char *DisplayOrientationName(int orientation) +{ + switch (orientation) { +#define CASE(X) \ + case SDL_ORIENTATION_##X: \ + return #X + CASE(UNKNOWN); + CASE(LANDSCAPE); + CASE(LANDSCAPE_FLIPPED); + CASE(PORTRAIT); + CASE(PORTRAIT_FLIPPED); +#undef CASE + default: + return "???"; + } +} + +static const char *GamepadAxisName(const SDL_GamepadAxis axis) +{ + switch (axis) { +#define AXIS_CASE(ax) \ + case SDL_GAMEPAD_AXIS_##ax: \ + return #ax + AXIS_CASE(INVALID); + AXIS_CASE(LEFTX); + AXIS_CASE(LEFTY); + AXIS_CASE(RIGHTX); + AXIS_CASE(RIGHTY); + AXIS_CASE(LEFT_TRIGGER); + AXIS_CASE(RIGHT_TRIGGER); +#undef AXIS_CASE + default: + return "???"; + } +} + +static const char *GamepadButtonName(const SDL_GamepadButton button) +{ + switch (button) { +#define BUTTON_CASE(btn) \ + case SDL_GAMEPAD_BUTTON_##btn: \ + return #btn + BUTTON_CASE(INVALID); + BUTTON_CASE(SOUTH); + BUTTON_CASE(EAST); + BUTTON_CASE(WEST); + BUTTON_CASE(NORTH); + BUTTON_CASE(BACK); + BUTTON_CASE(GUIDE); + BUTTON_CASE(START); + BUTTON_CASE(LEFT_STICK); + BUTTON_CASE(RIGHT_STICK); + BUTTON_CASE(LEFT_SHOULDER); + BUTTON_CASE(RIGHT_SHOULDER); + BUTTON_CASE(DPAD_UP); + BUTTON_CASE(DPAD_DOWN); + BUTTON_CASE(DPAD_LEFT); + BUTTON_CASE(DPAD_RIGHT); +#undef BUTTON_CASE + default: + return "???"; + } +} + +void SDLTest_PrintEvent(const SDL_Event *event) +{ + switch (event->type) { + case SDL_EVENT_SYSTEM_THEME_CHANGED: + SDL_Log("SDL EVENT: System theme changed to %s", SystemThemeName()); + break; + case SDL_EVENT_DISPLAY_ADDED: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " attached", + event->display.displayID); + break; + case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: + { + float scale = SDL_GetDisplayContentScale(event->display.displayID); + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " changed content scale to %d%%", + event->display.displayID, (int)(scale * 100.0f)); + } + break; + case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " desktop mode changed to %" SDL_PRIs32 "x%" SDL_PRIs32, + event->display.displayID, event->display.data1, event->display.data2); + break; + case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " current mode changed to %" SDL_PRIs32 "x%" SDL_PRIs32, + event->display.displayID, event->display.data1, event->display.data2); + break; + case SDL_EVENT_DISPLAY_MOVED: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " changed position", + event->display.displayID); + break; + case SDL_EVENT_DISPLAY_ORIENTATION: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " changed orientation to %s", + event->display.displayID, DisplayOrientationName(event->display.data1)); + break; + case SDL_EVENT_DISPLAY_REMOVED: + SDL_Log("SDL EVENT: Display %" SDL_PRIu32 " removed", + event->display.displayID); + break; + case SDL_EVENT_WINDOW_SHOWN: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " shown", event->window.windowID); + break; + case SDL_EVENT_WINDOW_HIDDEN: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " hidden", event->window.windowID); + break; + case SDL_EVENT_WINDOW_EXPOSED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " exposed", event->window.windowID); + break; + case SDL_EVENT_WINDOW_MOVED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " moved to %" SDL_PRIs32 ",%" SDL_PRIs32, + event->window.windowID, event->window.data1, event->window.data2); + break; + case SDL_EVENT_WINDOW_RESIZED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " resized to %" SDL_PRIs32 "x%" SDL_PRIs32, + event->window.windowID, event->window.data1, event->window.data2); + break; + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " changed pixel size to %" SDL_PRIs32 "x%" SDL_PRIs32, + event->window.windowID, event->window.data1, event->window.data2); + break; + case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " changed metal view size", + event->window.windowID); + break; + case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: { + SDL_Rect rect; + + SDL_GetWindowSafeArea(SDL_GetWindowFromEvent(event), &rect); + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " changed safe area to: %d,%d %dx%d", + event->window.windowID, rect.x, rect.y, rect.w, rect.h); + break; + } + case SDL_EVENT_WINDOW_MINIMIZED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " minimized", event->window.windowID); + break; + case SDL_EVENT_WINDOW_MAXIMIZED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " maximized", event->window.windowID); + break; + case SDL_EVENT_WINDOW_RESTORED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " restored", event->window.windowID); + break; + case SDL_EVENT_WINDOW_MOUSE_ENTER: + SDL_Log("SDL EVENT: Mouse entered window %" SDL_PRIu32, event->window.windowID); + break; + case SDL_EVENT_WINDOW_MOUSE_LEAVE: + SDL_Log("SDL EVENT: Mouse left window %" SDL_PRIu32, event->window.windowID); + break; + case SDL_EVENT_WINDOW_FOCUS_GAINED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " gained keyboard focus", + event->window.windowID); + break; + case SDL_EVENT_WINDOW_FOCUS_LOST: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " lost keyboard focus", + event->window.windowID); + break; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " closed", event->window.windowID); + break; + case SDL_EVENT_WINDOW_HIT_TEST: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " hit test", event->window.windowID); + break; + case SDL_EVENT_WINDOW_ICCPROF_CHANGED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " ICC profile changed", event->window.windowID); + break; + case SDL_EVENT_WINDOW_DISPLAY_CHANGED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " display changed to %" SDL_PRIs32, event->window.windowID, event->window.data1); + break; + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " display scale changed to %d%%", event->window.windowID, (int)(SDL_GetWindowDisplayScale(SDL_GetWindowFromEvent(event)) * 100.0f)); + break; + case SDL_EVENT_WINDOW_OCCLUDED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " occluded", event->window.windowID); + break; + case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " entered fullscreen", event->window.windowID); + break; + case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " left fullscreen", event->window.windowID); + break; + case SDL_EVENT_WINDOW_DESTROYED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " destroyed", event->window.windowID); + break; + case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: + SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " HDR %s", event->window.windowID, event->window.data1 ? "enabled" : "disabled"); + break; + case SDL_EVENT_KEYBOARD_ADDED: + SDL_Log("SDL EVENT: Keyboard %" SDL_PRIu32 " attached", + event->kdevice.which); + break; + case SDL_EVENT_KEYBOARD_REMOVED: + SDL_Log("SDL EVENT: Keyboard %" SDL_PRIu32 " removed", + event->kdevice.which); + break; + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: { + char modstr[64]; + if (event->key.mod) { + modstr[0] = '\0'; + SDLTest_PrintModState(modstr, sizeof (modstr), event->key.mod); + } else { + SDL_strlcpy(modstr, "NONE", sizeof (modstr)); + } + + SDL_Log("SDL EVENT: Keyboard: key %s in window %" SDL_PRIu32 ": scancode 0x%08X = %s, keycode 0x%08" SDL_PRIX32 " = %s, mods = %s", + (event->type == SDL_EVENT_KEY_DOWN) ? "pressed" : "released", + event->key.windowID, + event->key.scancode, + SDL_GetScancodeName(event->key.scancode), + event->key.key, SDL_GetKeyName(event->key.key), + modstr); + break; + } + case SDL_EVENT_TEXT_EDITING: + SDL_Log("SDL EVENT: Keyboard: text editing \"%s\" in window %" SDL_PRIu32, + event->edit.text, event->edit.windowID); + break; + case SDL_EVENT_TEXT_EDITING_CANDIDATES: + SDL_Log("SDL EVENT: Keyboard: text editing candidates in window %" SDL_PRIu32, + event->edit.windowID); + break; + case SDL_EVENT_TEXT_INPUT: + SDL_Log("SDL EVENT: Keyboard: text input \"%s\" in window %" SDL_PRIu32, + event->text.text, event->text.windowID); + break; + case SDL_EVENT_KEYMAP_CHANGED: + SDL_Log("SDL EVENT: Keymap changed"); + break; + case SDL_EVENT_MOUSE_ADDED: + SDL_Log("SDL EVENT: Mouse %" SDL_PRIu32 " attached", + event->mdevice.which); + break; + case SDL_EVENT_MOUSE_REMOVED: + SDL_Log("SDL EVENT: Mouse %" SDL_PRIu32 " removed", + event->mdevice.which); + break; + case SDL_EVENT_MOUSE_MOTION: + SDL_Log("SDL EVENT: Mouse: moved to %g,%g (%g,%g) in window %" SDL_PRIu32, + event->motion.x, event->motion.y, + event->motion.xrel, event->motion.yrel, + event->motion.windowID); + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + SDL_Log("SDL EVENT: Mouse: button %d pressed at %g,%g with click count %d in window %" SDL_PRIu32, + event->button.button, event->button.x, event->button.y, event->button.clicks, + event->button.windowID); + break; + case SDL_EVENT_MOUSE_BUTTON_UP: + SDL_Log("SDL EVENT: Mouse: button %d released at %g,%g with click count %d in window %" SDL_PRIu32, + event->button.button, event->button.x, event->button.y, event->button.clicks, + event->button.windowID); + break; + case SDL_EVENT_MOUSE_WHEEL: + SDL_Log("SDL EVENT: Mouse: wheel scrolled %g in x and %g in y (reversed: %d) in window %" SDL_PRIu32, + event->wheel.x, event->wheel.y, event->wheel.direction, event->wheel.windowID); + break; + case SDL_EVENT_JOYSTICK_ADDED: + SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 " attached", + event->jdevice.which); + break; + case SDL_EVENT_JOYSTICK_REMOVED: + SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 " removed", + event->jdevice.which); + break; + case SDL_EVENT_JOYSTICK_AXIS_MOTION: + SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 " axis %d value: %d", + event->jaxis.which, + event->jaxis.axis, + event->jaxis.value); + break; + case SDL_EVENT_JOYSTICK_BALL_MOTION: + SDL_Log("SDL EVENT: Joystick %" SDL_PRIs32 ": ball %d moved by %d,%d", + event->jball.which, event->jball.ball, event->jball.xrel, + event->jball.yrel); + break; + case SDL_EVENT_JOYSTICK_HAT_MOTION: + { + const char *position = "UNKNOWN"; + switch (event->jhat.value) { + case SDL_HAT_CENTERED: + position = "CENTER"; + break; + case SDL_HAT_UP: + position = "UP"; + break; + case SDL_HAT_RIGHTUP: + position = "RIGHTUP"; + break; + case SDL_HAT_RIGHT: + position = "RIGHT"; + break; + case SDL_HAT_RIGHTDOWN: + position = "RIGHTDOWN"; + break; + case SDL_HAT_DOWN: + position = "DOWN"; + break; + case SDL_HAT_LEFTDOWN: + position = "LEFTDOWN"; + break; + case SDL_HAT_LEFT: + position = "LEFT"; + break; + case SDL_HAT_LEFTUP: + position = "LEFTUP"; + break; + } + SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 ": hat %d moved to %s", + event->jhat.which, event->jhat.hat, position); + } break; + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: + SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 ": button %d pressed", + event->jbutton.which, event->jbutton.button); + break; + case SDL_EVENT_JOYSTICK_BUTTON_UP: + SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 ": button %d released", + event->jbutton.which, event->jbutton.button); + break; + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: + SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 ": battery at %d percent", + event->jbattery.which, event->jbattery.percent); + break; + case SDL_EVENT_GAMEPAD_ADDED: + SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 " attached", + event->gdevice.which); + break; + case SDL_EVENT_GAMEPAD_REMOVED: + SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 " removed", + event->gdevice.which); + break; + case SDL_EVENT_GAMEPAD_REMAPPED: + SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 " mapping changed", + event->gdevice.which); + break; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 " axis %d ('%s') value: %d", + event->gaxis.which, + event->gaxis.axis, + GamepadAxisName((SDL_GamepadAxis)event->gaxis.axis), + event->gaxis.value); + break; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 "button %d ('%s') down", + event->gbutton.which, event->gbutton.button, + GamepadButtonName((SDL_GamepadButton)event->gbutton.button)); + break; + case SDL_EVENT_GAMEPAD_BUTTON_UP: + SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 " button %d ('%s') up", + event->gbutton.which, event->gbutton.button, + GamepadButtonName((SDL_GamepadButton)event->gbutton.button)); + break; + case SDL_EVENT_CLIPBOARD_UPDATE: + SDL_Log("SDL EVENT: Clipboard updated"); + break; + + case SDL_EVENT_FINGER_MOTION: + SDL_Log("SDL EVENT: Finger: motion touch=%" SDL_PRIu64 ", finger=%" SDL_PRIu64 ", x=%f, y=%f, dx=%f, dy=%f, pressure=%f", + event->tfinger.touchID, + event->tfinger.fingerID, + event->tfinger.x, event->tfinger.y, + event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure); + break; + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_UP: + case SDL_EVENT_FINGER_CANCELED: + SDL_Log("SDL EVENT: Finger: %s touch=%" SDL_PRIu64 ", finger=%" SDL_PRIu64 ", x=%f, y=%f, dx=%f, dy=%f, pressure=%f", + (event->type == SDL_EVENT_FINGER_DOWN) ? "down" : + (event->type == SDL_EVENT_FINGER_UP) ? "up" : "cancel", + event->tfinger.touchID, + event->tfinger.fingerID, + event->tfinger.x, event->tfinger.y, + event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure); + break; + + case SDL_EVENT_RENDER_TARGETS_RESET: + SDL_Log("SDL EVENT: render targets reset in window %" SDL_PRIu32, event->render.windowID); + break; + case SDL_EVENT_RENDER_DEVICE_RESET: + SDL_Log("SDL EVENT: render device reset in window %" SDL_PRIu32, event->render.windowID); + break; + case SDL_EVENT_RENDER_DEVICE_LOST: + SDL_Log("SDL EVENT: render device lost in window %" SDL_PRIu32, event->render.windowID); + break; + + case SDL_EVENT_TERMINATING: + SDL_Log("SDL EVENT: App terminating"); + break; + case SDL_EVENT_LOW_MEMORY: + SDL_Log("SDL EVENT: App running low on memory"); + break; + case SDL_EVENT_WILL_ENTER_BACKGROUND: + SDL_Log("SDL EVENT: App will enter the background"); + break; + case SDL_EVENT_DID_ENTER_BACKGROUND: + SDL_Log("SDL EVENT: App entered the background"); + break; + case SDL_EVENT_WILL_ENTER_FOREGROUND: + SDL_Log("SDL EVENT: App will enter the foreground"); + break; + case SDL_EVENT_DID_ENTER_FOREGROUND: + SDL_Log("SDL EVENT: App entered the foreground"); + break; + case SDL_EVENT_DROP_BEGIN: + SDL_Log("SDL EVENT: Drag and drop beginning in window %" SDL_PRIu32, event->drop.windowID); + break; + case SDL_EVENT_DROP_POSITION: + SDL_Log("SDL EVENT: Drag and drop moving in window %" SDL_PRIu32 ": %g,%g", event->drop.windowID, event->drop.x, event->drop.y); + break; + case SDL_EVENT_DROP_FILE: + SDL_Log("SDL EVENT: Drag and drop file in window %" SDL_PRIu32 ": '%s'", event->drop.windowID, event->drop.data); + break; + case SDL_EVENT_DROP_TEXT: + SDL_Log("SDL EVENT: Drag and drop text in window %" SDL_PRIu32 ": '%s'", event->drop.windowID, event->drop.data); + break; + case SDL_EVENT_DROP_COMPLETE: + SDL_Log("SDL EVENT: Drag and drop ending"); + break; + case SDL_EVENT_AUDIO_DEVICE_ADDED: + SDL_Log("SDL EVENT: Audio %s device %" SDL_PRIu32 " available", + event->adevice.recording ? "recording" : "playback", + event->adevice.which); + break; + case SDL_EVENT_AUDIO_DEVICE_REMOVED: + SDL_Log("SDL EVENT: Audio %s device %" SDL_PRIu32 " removed", + event->adevice.recording ? "recording" : "playback", + event->adevice.which); + break; + case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: + SDL_Log("SDL EVENT: Audio %s device %" SDL_PRIu32 " format changed", + event->adevice.recording ? "recording" : "playback", + event->adevice.which); + break; + case SDL_EVENT_CAMERA_DEVICE_ADDED: + SDL_Log("SDL EVENT: Camera device %" SDL_PRIu32 " available", + event->cdevice.which); + break; + case SDL_EVENT_CAMERA_DEVICE_REMOVED: + SDL_Log("SDL EVENT: Camera device %" SDL_PRIu32 " removed", + event->cdevice.which); + break; + case SDL_EVENT_CAMERA_DEVICE_APPROVED: + SDL_Log("SDL EVENT: Camera device %" SDL_PRIu32 " permission granted", + event->cdevice.which); + break; + case SDL_EVENT_CAMERA_DEVICE_DENIED: + SDL_Log("SDL EVENT: Camera device %" SDL_PRIu32 " permission denied", + event->cdevice.which); + break; + case SDL_EVENT_SENSOR_UPDATE: + SDL_Log("SDL EVENT: Sensor update for %" SDL_PRIu32, + event->sensor.which); + break; + case SDL_EVENT_PEN_PROXIMITY_IN: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " entered proximity", + event->pproximity.which); + break; + case SDL_EVENT_PEN_PROXIMITY_OUT: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " left proximity", + event->ptouch.which); + break; + case SDL_EVENT_PEN_DOWN: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " touched down at %g,%g", + event->ptouch.which, event->ptouch.x, event->ptouch.y); + break; + case SDL_EVENT_PEN_UP: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " lifted off at %g,%g", + event->ptouch.which, event->ptouch.x, event->ptouch.y); + break; + case SDL_EVENT_PEN_BUTTON_DOWN: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " button %d pressed at %g,%g", + event->pbutton.which, event->pbutton.button, event->pbutton.x, event->pbutton.y); + break; + case SDL_EVENT_PEN_BUTTON_UP: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " button %d released at %g,%g", + event->pbutton.which, event->pbutton.button, event->pbutton.x, event->pbutton.y); + break; + case SDL_EVENT_PEN_MOTION: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " moved to %g,%g", + event->pmotion.which, event->pmotion.x, event->pmotion.y); + break; + case SDL_EVENT_PEN_AXIS: + SDL_Log("SDL EVENT: Pen %" SDL_PRIu32 " axis %d changed to %.2f", + event->paxis.which, event->paxis.axis, event->paxis.value); + break; + case SDL_EVENT_LOCALE_CHANGED: + SDL_Log("SDL EVENT: Locale changed"); + break; + case SDL_EVENT_QUIT: + SDL_Log("SDL EVENT: Quit requested"); + break; + case SDL_EVENT_USER: + SDL_Log("SDL EVENT: User event %" SDL_PRIs32, event->user.code); + break; + default: + SDL_Log("Unknown event 0x%4.4" SDL_PRIx32, event->type); + break; + } +} + +#define SCREENSHOT_FILE "screenshot.bmp" + +typedef struct +{ + void *image; + size_t size; +} SDLTest_ClipboardData; + +static void SDLCALL SDLTest_ScreenShotClipboardCleanup(void *context) +{ + SDLTest_ClipboardData *data = (SDLTest_ClipboardData *)context; + + SDL_Log("Cleaning up screenshot image data"); + + if (data->image) { + SDL_free(data->image); + } + SDL_free(data); +} + +static const void * SDLCALL SDLTest_ScreenShotClipboardProvider(void *context, const char *mime_type, size_t *size) +{ + SDLTest_ClipboardData *data = (SDLTest_ClipboardData *)context; + + if (SDL_strncmp(mime_type, "text", 4) == 0) { + SDL_Log("Providing screenshot title to clipboard!"); + + /* Return "Test screenshot" */ + *size = 15; + return "Test screenshot (but this isn't part of it)"; + } + + SDL_Log("Providing screenshot image to clipboard!"); + + if (!data->image) { + SDL_IOStream *file; + + file = SDL_IOFromFile(SCREENSHOT_FILE, "r"); + if (file) { + size_t length = (size_t)SDL_GetIOSize(file); + void *image = SDL_malloc(length); + if (image) { + if (SDL_ReadIO(file, image, length) != length) { + SDL_Log("Couldn't read %s: %s", SCREENSHOT_FILE, SDL_GetError()); + SDL_free(image); + image = NULL; + } + } + SDL_CloseIO(file); + + if (image) { + data->image = image; + data->size = length; + } + } else { + SDL_Log("Couldn't load %s: %s", SCREENSHOT_FILE, SDL_GetError()); + } + } + + *size = data->size; + return data->image; +} + +static void SDLTest_CopyScreenShot(SDL_Renderer *renderer) +{ + SDL_Surface *surface; + const char *image_formats[] = { + "text/plain;charset=utf-8", + "image/bmp" + }; + SDLTest_ClipboardData *clipboard_data; + + if (!renderer) { + return; + } + + surface = SDL_RenderReadPixels(renderer, NULL); + if (!surface) { + SDL_Log("Couldn't read screen: %s", SDL_GetError()); + return; + } + + if (!SDL_SaveBMP(surface, SCREENSHOT_FILE)) { + SDL_Log("Couldn't save %s: %s", SCREENSHOT_FILE, SDL_GetError()); + SDL_DestroySurface(surface); + return; + } + SDL_DestroySurface(surface); + + clipboard_data = (SDLTest_ClipboardData *)SDL_calloc(1, sizeof(*clipboard_data)); + if (!clipboard_data) { + SDL_Log("Couldn't allocate clipboard data"); + return; + } + SDL_SetClipboardData(SDLTest_ScreenShotClipboardProvider, SDLTest_ScreenShotClipboardCleanup, clipboard_data, image_formats, SDL_arraysize(image_formats)); + SDL_Log("Saved screenshot to %s and clipboard", SCREENSHOT_FILE); +} + +static void SDLTest_PasteScreenShot(void) +{ + const char *image_formats[] = { + "image/bmp", + "image/png", + "image/tiff", + }; + size_t i; + + for (i = 0; i < SDL_arraysize(image_formats); ++i) { + size_t size; + void *data = SDL_GetClipboardData(image_formats[i], &size); + if (data) { + char filename[16]; + SDL_IOStream *file; + + SDL_snprintf(filename, sizeof(filename), "clipboard.%s", image_formats[i] + 6); + file = SDL_IOFromFile(filename, "w"); + if (file) { + SDL_Log("Writing clipboard image to %s", filename); + SDL_WriteIO(file, data, size); + SDL_CloseIO(file); + } + SDL_free(data); + return; + } + } + SDL_Log("No supported screenshot data in the clipboard"); +} + +static void FullscreenTo(SDLTest_CommonState *state, int index, int windowId) +{ + int num_displays; + SDL_DisplayID *displays; + SDL_Window *window; + SDL_WindowFlags flags; + const SDL_DisplayMode *mode; + struct SDL_Rect rect = { 0, 0, 0, 0 }; + + displays = SDL_GetDisplays(&num_displays); + if (displays && index < num_displays) { + window = SDL_GetWindowFromID(windowId); + if (window) { + SDL_GetDisplayBounds(displays[index], &rect); + + flags = SDL_GetWindowFlags(window); + if (flags & SDL_WINDOW_FULLSCREEN) { + SDL_SetWindowFullscreen(window, false); + SDL_Delay(15); + } + + mode = SDL_GetWindowFullscreenMode(window); + if (mode) { + /* Try to set the existing mode on the new display */ + SDL_DisplayMode new_mode; + + SDL_memcpy(&new_mode, mode, sizeof(new_mode)); + new_mode.displayID = displays[index]; + if (!SDL_SetWindowFullscreenMode(window, &new_mode)) { + /* Try again with a default mode */ + bool include_high_density_modes = false; + if (state->window_flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { + include_high_density_modes = true; + } + if (SDL_GetClosestFullscreenDisplayMode(displays[index], state->window_w, state->window_h, state->refresh_rate, include_high_density_modes, &new_mode)) { + SDL_SetWindowFullscreenMode(window, &new_mode); + } + } + } + if (!mode) { + SDL_SetWindowPosition(window, rect.x, rect.y); + } + SDL_SetWindowFullscreen(window, true); + } + } + SDL_free(displays); +} + +SDL_AppResult SDLTest_CommonEventMainCallbacks(SDLTest_CommonState *state, const SDL_Event *event) +{ + int i; + + if (state->verbose & VERBOSE_EVENT) { + if ((event->type != SDL_EVENT_MOUSE_MOTION && + event->type != SDL_EVENT_FINGER_MOTION && + event->type != SDL_EVENT_PEN_MOTION && + event->type != SDL_EVENT_PEN_AXIS && + event->type != SDL_EVENT_JOYSTICK_AXIS_MOTION) || + (state->verbose & VERBOSE_MOTION)) { + SDLTest_PrintEvent(event); + } + } + + switch (event->type) { + case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: + if (state->auto_scale_content) { + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + float scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindow(window)); + int w = state->window_w; + int h = state->window_h; + + w = (int)SDL_ceilf(w * scale); + h = (int)SDL_ceilf(h * scale); + SDL_SetWindowSize(window, w, h); + } + } + break; + case SDL_EVENT_WINDOW_FOCUS_LOST: + if (state->flash_on_focus_loss) { + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_FlashWindow(window, SDL_FLASH_UNTIL_FOCUSED); + } + } + break; + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + { + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_HideWindow(window); + } + break; + } + case SDL_EVENT_KEY_DOWN: + { + bool withControl = !!(event->key.mod & SDL_KMOD_CTRL); + bool withShift = !!(event->key.mod & SDL_KMOD_SHIFT); + bool withAlt = !!(event->key.mod & SDL_KMOD_ALT); + + switch (event->key.key) { + /* Add hotkeys here */ + case SDLK_PRINTSCREEN: + { + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + for (i = 0; i < state->num_windows; ++i) { + if (window == state->windows[i]) { + SDLTest_CopyScreenShot(state->renderers[i]); + } + } + } + } break; + case SDLK_EQUALS: + if (withControl) { + /* Ctrl-+ double the size of the window */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + int w, h; + SDL_GetWindowSize(window, &w, &h); + SDL_SetWindowSize(window, w * 2, h * 2); + } + } + break; + case SDLK_MINUS: + if (withControl) { + /* Ctrl-- half the size of the window */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + int w, h; + SDL_GetWindowSize(window, &w, &h); + SDL_SetWindowSize(window, w / 2, h / 2); + } + } + break; + case SDLK_UP: + case SDLK_DOWN: + case SDLK_LEFT: + case SDLK_RIGHT: + if (withAlt) { + /* Alt-Up/Down/Left/Right switches between displays */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + int num_displays; + const SDL_DisplayID *displays = SDL_GetDisplays(&num_displays); + if (displays) { + SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); + int current_index = -1; + + for (i = 0; i < num_displays; ++i) { + if (displayID == displays[i]) { + current_index = i; + break; + } + } + if (current_index >= 0) { + SDL_DisplayID dest; + if (event->key.key == SDLK_UP || event->key.key == SDLK_LEFT) { + dest = displays[(current_index + num_displays - 1) % num_displays]; + } else { + dest = displays[(current_index + num_displays + 1) % num_displays]; + } + SDL_Log("Centering on display (%" SDL_PRIu32 ")", dest); + SDL_SetWindowPosition(window, + SDL_WINDOWPOS_CENTERED_DISPLAY(dest), + SDL_WINDOWPOS_CENTERED_DISPLAY(dest)); + } + } + } + } + if (withShift) { + /* Shift-Up/Down/Left/Right shift the window by 100px */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + const int delta = 100; + int x, y; + SDL_GetWindowPosition(window, &x, &y); + + if (event->key.key == SDLK_UP) { + y -= delta; + } + if (event->key.key == SDLK_DOWN) { + y += delta; + } + if (event->key.key == SDLK_LEFT) { + x -= delta; + } + if (event->key.key == SDLK_RIGHT) { + x += delta; + } + + SDL_Log("Setting position to (%d, %d)", x, y); + SDL_SetWindowPosition(window, x, y); + } + } + break; + case SDLK_O: + if (withControl) { + /* Ctrl-O (or Ctrl-Shift-O) changes window opacity. */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + float opacity = SDL_GetWindowOpacity(window); + if (withShift) { + opacity += 0.20f; + } else { + opacity -= 0.20f; + } + SDL_SetWindowOpacity(window, opacity); + } + } + break; + case SDLK_H: + if (withControl) { + /* Ctrl-H changes cursor visibility. */ + if (SDL_CursorVisible()) { + SDL_HideCursor(); + } else { + SDL_ShowCursor(); + } + } + break; + case SDLK_C: + if (withAlt) { + /* Alt-C copy awesome text to the primary selection! */ + SDL_SetPrimarySelectionText("SDL rocks!\nYou know it!"); + SDL_Log("Copied text to primary selection"); + + } else if (withControl) { + if (withShift) { + /* Ctrl-Shift-C copy screenshot! */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + for (i = 0; i < state->num_windows; ++i) { + if (window == state->windows[i]) { + SDLTest_CopyScreenShot(state->renderers[i]); + } + } + } + } else { + /* Ctrl-C copy awesome text! */ + SDL_SetClipboardText("SDL rocks!\nYou know it!"); + SDL_Log("Copied text to clipboard"); + } + break; + } + break; + case SDLK_V: + if (withAlt) { + /* Alt-V paste awesome text from the primary selection! */ + char *text = SDL_GetPrimarySelectionText(); + if (*text) { + SDL_Log("Primary selection: %s", text); + } else { + SDL_Log("Primary selection is empty"); + } + SDL_free(text); + + } else if (withControl) { + if (withShift) { + /* Ctrl-Shift-V paste screenshot! */ + SDLTest_PasteScreenShot(); + } else { + /* Ctrl-V paste awesome text! */ + char *text = SDL_GetClipboardText(); + if (*text) { + SDL_Log("Clipboard: %s", text); + } else { + SDL_Log("Clipboard is empty"); + } + SDL_free(text); + } + } + break; + case SDLK_F: + if (withControl) { + /* Ctrl-F flash the window */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_FlashWindow(window, SDL_FLASH_BRIEFLY); + } + } + break; + case SDLK_G: + if (withControl) { + /* Ctrl-G toggle mouse grab */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_SetWindowMouseGrab(window, !SDL_GetWindowMouseGrab(window)); + } + } + break; + case SDLK_K: + if (withControl) { + /* Ctrl-K toggle keyboard grab */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_SetWindowKeyboardGrab(window, !SDL_GetWindowKeyboardGrab(window)); + } + } + break; + case SDLK_M: + if (withControl) { + /* Ctrl-M maximize */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_WindowFlags flags = SDL_GetWindowFlags(window); + if (!(flags & SDL_WINDOW_RESIZABLE)) { + SDL_SetWindowResizable(window, true); + } + if (flags & SDL_WINDOW_MAXIMIZED) { + SDL_RestoreWindow(window); + } else { + SDL_MaximizeWindow(window); + } + if (!(flags & SDL_WINDOW_RESIZABLE)) { + SDL_SetWindowResizable(window, false); + } + } + } + if (withShift) { + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + const bool shouldCapture = !(SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_CAPTURE); + const bool rc = SDL_CaptureMouse(shouldCapture); + SDL_Log("%sapturing mouse %s!", shouldCapture ? "C" : "Unc", rc ? "succeeded" : "failed"); + } + } + break; + case SDLK_R: + if (withControl) { + /* Ctrl-R toggle mouse relative mode */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_SetWindowRelativeMouseMode(window, !SDL_GetWindowRelativeMouseMode(window)); + } + } + break; + case SDLK_T: + if (withControl) { + /* Ctrl-T toggle topmost mode */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_WindowFlags flags = SDL_GetWindowFlags(window); + if (flags & SDL_WINDOW_ALWAYS_ON_TOP) { + SDL_SetWindowAlwaysOnTop(window, false); + } else { + SDL_SetWindowAlwaysOnTop(window, true); + } + } + } + break; + case SDLK_Z: + if (withControl) { + /* Ctrl-Z minimize */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_MinimizeWindow(window); + } + } + break; + case SDLK_RETURN: + if (withControl) { + /* Ctrl-Enter toggle fullscreen */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_WindowFlags flags = SDL_GetWindowFlags(window); + if (!(flags & SDL_WINDOW_FULLSCREEN) || + !SDL_GetWindowFullscreenMode(window)) { + SDL_SetWindowFullscreenMode(window, &state->fullscreen_mode); + SDL_SetWindowFullscreen(window, true); + } else { + SDL_SetWindowFullscreen(window, false); + } + } + } else if (withAlt) { + /* Alt-Enter toggle fullscreen desktop */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + SDL_WindowFlags flags = SDL_GetWindowFlags(window); + if (!(flags & SDL_WINDOW_FULLSCREEN) || + SDL_GetWindowFullscreenMode(window)) { + SDL_SetWindowFullscreenMode(window, NULL); + SDL_SetWindowFullscreen(window, true); + } else { + SDL_SetWindowFullscreen(window, false); + } + } + } + + break; + case SDLK_B: + if (withControl) { + /* Ctrl-B toggle window border */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + const SDL_WindowFlags flags = SDL_GetWindowFlags(window); + const bool b = (flags & SDL_WINDOW_BORDERLESS) ? true : false; + SDL_SetWindowBordered(window, b); + } + } + break; + case SDLK_A: + if (withControl) { + /* Ctrl-A toggle aspect ratio */ + SDL_Window *window = SDL_GetWindowFromEvent(event); + if (window) { + float min_aspect = 0.0f, max_aspect = 0.0f; + + SDL_GetWindowAspectRatio(window, &min_aspect, &max_aspect); + if (min_aspect > 0.0f || max_aspect > 0.0f) { + min_aspect = 0.0f; + max_aspect = 0.0f; + } else { + min_aspect = 1.0f; + max_aspect = 1.0f; + } + SDL_SetWindowAspectRatio(window, min_aspect, max_aspect); + } + } + break; + case SDLK_0: + if (withControl) { + SDL_Window *window = SDL_GetWindowFromEvent(event); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Test Message", "You're awesome!", window); + } + break; + case SDLK_1: + if (withControl) { + FullscreenTo(state, 0, event->key.windowID); + } + break; + case SDLK_2: + if (withControl) { + FullscreenTo(state, 1, event->key.windowID); + } + break; + case SDLK_ESCAPE: + return SDL_APP_SUCCESS; + default: + break; + } + break; + } + case SDL_EVENT_QUIT: + return SDL_APP_SUCCESS; + default: + break; + } + + return SDL_APP_CONTINUE; +} + +void SDLTest_CommonEvent(SDLTest_CommonState *state, SDL_Event *event, int *done) +{ + if (SDLTest_CommonEventMainCallbacks(state, event) != SDL_APP_CONTINUE) { + *done = 1; + } +} + +void SDLTest_CommonQuit(SDLTest_CommonState *state) +{ + if (state) { + int i; + + if (state->targets) { + for (i = 0; i < state->num_windows; ++i) { + if (state->targets[i]) { + SDL_DestroyTexture(state->targets[i]); + } + } + SDL_free(state->targets); + } + if (state->renderers) { + for (i = 0; i < state->num_windows; ++i) { + if (state->renderers[i]) { + SDL_DestroyRenderer(state->renderers[i]); + } + } + SDL_free(state->renderers); + } + if (state->windows) { + for (i = 0; i < state->num_windows; i++) { + SDL_DestroyWindow(state->windows[i]); + } + SDL_free(state->windows); + } + } + SDL_Quit(); + SDLTest_CommonDestroyState(state); +} + +void SDLTest_CommonDrawWindowInfo(SDL_Renderer *renderer, SDL_Window *window, float *usedHeight) +{ + char text[1024]; + float textY = 0.0f; + const int lineHeight = 10; + int x, y, w, h; + float fx, fy; + SDL_Rect rect; + const SDL_DisplayMode *mode; + float scaleX, scaleY; + SDL_MouseButtonFlags flags; + SDL_DisplayID windowDisplayID = SDL_GetDisplayForWindow(window); + const char *name; + SDL_RendererLogicalPresentation logical_presentation; + + /* Video */ + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, 0.0f, textY, "-- Video --"); + textY += lineHeight; + + SDL_SetRenderDrawColor(renderer, 170, 170, 170, 255); + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetCurrentVideoDriver: %s", SDL_GetCurrentVideoDriver()); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + /* Renderer */ + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, 0.0f, textY, "-- Renderer --"); + textY += lineHeight; + + SDL_SetRenderDrawColor(renderer, 170, 170, 170, 255); + + name = SDL_GetRendererName(renderer); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetRendererName: %s", name); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + if (SDL_GetRenderOutputSize(renderer, &w, &h)) { + (void)SDL_snprintf(text, sizeof(text), "SDL_GetRenderOutputSize: %dx%d", w, h); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + } + + if (SDL_GetCurrentRenderOutputSize(renderer, &w, &h)) { + (void)SDL_snprintf(text, sizeof(text), "SDL_GetCurrentRenderOutputSize: %dx%d", w, h); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + } + + SDL_GetRenderViewport(renderer, &rect); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetRenderViewport: %d,%d, %dx%d", + rect.x, rect.y, rect.w, rect.h); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + SDL_GetRenderScale(renderer, &scaleX, &scaleY); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetRenderScale: %g,%g", + scaleX, scaleY); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + SDL_GetRenderLogicalPresentation(renderer, &w, &h, &logical_presentation); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetRenderLogicalPresentation: %dx%d ", w, h); + SDLTest_PrintLogicalPresentation(text, sizeof(text), logical_presentation); + textY += lineHeight; + + /* Window */ + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, 0.0f, textY, "-- Window --"); + textY += lineHeight; + + SDL_SetRenderDrawColor(renderer, 170, 170, 170, 255); + + SDL_GetWindowPosition(window, &x, &y); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetWindowPosition: %d,%d", x, y); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + SDL_GetWindowSize(window, &w, &h); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetWindowSize: %dx%d", w, h); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + SDL_GetWindowSafeArea(window, &rect); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetWindowSafeArea: %d,%d %dx%d", rect.x, rect.y, rect.w, rect.h); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetWindowFlags: "); + SDLTest_PrintWindowFlags(text, sizeof(text), SDL_GetWindowFlags(window)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + mode = SDL_GetWindowFullscreenMode(window); + if (mode) { + (void)SDL_snprintf(text, sizeof(text), "SDL_GetWindowFullscreenMode: %dx%d@%gx %gHz, (%s)", + mode->w, mode->h, mode->pixel_density, mode->refresh_rate, SDL_GetPixelFormatName(mode->format)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + } + + /* Display */ + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, 0.0f, textY, "-- Display --"); + textY += lineHeight; + + SDL_SetRenderDrawColor(renderer, 170, 170, 170, 255); + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetDisplayForWindow: %" SDL_PRIu32, windowDisplayID); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetDisplayName: %s", SDL_GetDisplayName(windowDisplayID)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + if (SDL_GetDisplayBounds(windowDisplayID, &rect)) { + (void)SDL_snprintf(text, sizeof(text), "SDL_GetDisplayBounds: %d,%d, %dx%d", + rect.x, rect.y, rect.w, rect.h); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + } + + mode = SDL_GetCurrentDisplayMode(windowDisplayID); + if (mode) { + (void)SDL_snprintf(text, sizeof(text), "SDL_GetCurrentDisplayMode: %dx%d@%gx %gHz, (%s)", + mode->w, mode->h, mode->pixel_density, mode->refresh_rate, SDL_GetPixelFormatName(mode->format)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + } + + mode = SDL_GetDesktopDisplayMode(windowDisplayID); + if (mode) { + (void)SDL_snprintf(text, sizeof(text), "SDL_GetDesktopDisplayMode: %dx%d@%gx %gHz, (%s)", + mode->w, mode->h, mode->pixel_density, mode->refresh_rate, SDL_GetPixelFormatName(mode->format)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + } + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetNaturalDisplayOrientation: "); + SDLTest_PrintDisplayOrientation(text, sizeof(text), SDL_GetNaturalDisplayOrientation(windowDisplayID)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetCurrentDisplayOrientation: "); + SDLTest_PrintDisplayOrientation(text, sizeof(text), SDL_GetCurrentDisplayOrientation(windowDisplayID)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetDisplayContentScale: %g", SDL_GetDisplayContentScale(windowDisplayID)); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + /* Mouse */ + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, 0.0f, textY, "-- Mouse --"); + textY += lineHeight; + + SDL_SetRenderDrawColor(renderer, 170, 170, 170, 255); + + flags = SDL_GetMouseState(&fx, &fy); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetMouseState: %g,%g ", fx, fy); + SDLTest_PrintButtonMask(text, sizeof(text), flags); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + flags = SDL_GetGlobalMouseState(&fx, &fy); + (void)SDL_snprintf(text, sizeof(text), "SDL_GetGlobalMouseState: %g,%g ", fx, fy); + SDLTest_PrintButtonMask(text, sizeof(text), flags); + SDLTest_DrawString(renderer, 0.0f, textY, text); + textY += lineHeight; + + /* Keyboard */ + + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDLTest_DrawString(renderer, 0, textY, "-- Keyboard --"); + textY += lineHeight; + + SDL_SetRenderDrawColor(renderer, 170, 170, 170, 255); + + (void)SDL_snprintf(text, sizeof(text), "SDL_GetModState: "); + SDLTest_PrintModState(text, sizeof(text), SDL_GetModState()); + SDLTest_DrawString(renderer, 0, textY, text); + textY += lineHeight; + + if (usedHeight) { + *usedHeight = textY; + } +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_compare.c b/contrib/SDL-3.2.8/src/test/SDL_test_compare.c new file mode 100644 index 0000000..c3ee039 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_compare.c @@ -0,0 +1,218 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + + Based on automated SDL_Surface tests originally written by Edgar Simo 'bobbens'. + + Rewritten for test lib by Andreas Schiffler. + +*/ +#include + +#define FILENAME_SIZE 128 + +/* Counter for _CompareSurface calls; used for filename creation when comparisons fail */ +static int _CompareSurfaceCount = 0; + +/* Compare surfaces */ +int SDLTest_CompareSurfaces(SDL_Surface *surface, SDL_Surface *referenceSurface, int allowable_error) +{ + int ret; + int i, j; + int dist; + int sampleErrorX = 0, sampleErrorY = 0, sampleDist = 0; + SDL_Color sampleReference = { 0, 0, 0, 0 }; + SDL_Color sampleActual = { 0, 0, 0, 0 }; + Uint8 R, G, B, A; + Uint8 Rd, Gd, Bd, Ad; + char imageFilename[FILENAME_SIZE]; + char referenceFilename[FILENAME_SIZE]; + + /* Validate input surfaces */ + if (!surface) { + SDLTest_LogError("Cannot compare NULL surface"); + return -1; + } + + if (!referenceSurface) { + SDLTest_LogError("Cannot compare NULL reference surface"); + return -1; + } + + /* Make sure surface size is the same. */ + if ((surface->w != referenceSurface->w) || (surface->h != referenceSurface->h)) { + SDLTest_LogError("Expected %dx%d surface, got %dx%d", referenceSurface->w, referenceSurface->h, surface->w, surface->h); + return -2; + } + + /* Sanitize input value */ + if (allowable_error < 0) { + allowable_error = 0; + } + + SDL_LockSurface(surface); + SDL_LockSurface(referenceSurface); + + ret = 0; + /* Compare image - should be same format. */ + for (j = 0; j < surface->h; j++) { + for (i = 0; i < surface->w; i++) { + int temp; + + temp = SDL_ReadSurfacePixel(surface, i, j, &R, &G, &B, &A); + if (!temp) { + SDLTest_LogError("Failed to retrieve pixel (%d,%d): %s", i, j, SDL_GetError()); + ret++; + continue; + } + + temp = SDL_ReadSurfacePixel(referenceSurface, i, j, &Rd, &Gd, &Bd, &Ad); + if (!temp) { + SDLTest_LogError("Failed to retrieve reference pixel (%d,%d): %s", i, j, SDL_GetError()); + ret++; + continue; + } + + dist = 0; + dist += (R - Rd) * (R - Rd); + dist += (G - Gd) * (G - Gd); + dist += (B - Bd) * (B - Bd); + + /* Allow some difference in blending accuracy */ + if (dist > allowable_error) { + ret++; + if (ret == 1) { + sampleErrorX = i; + sampleErrorY = j; + sampleDist = dist; + sampleReference.r = Rd; + sampleReference.g = Gd; + sampleReference.b = Bd; + sampleReference.a = Ad; + sampleActual.r = R; + sampleActual.g = G; + sampleActual.b = B; + sampleActual.a = A; + } + } + } + } + + SDL_UnlockSurface(surface); + SDL_UnlockSurface(referenceSurface); + + /* Save test image and reference for analysis on failures */ + _CompareSurfaceCount++; + if (ret != 0) { + SDLTest_LogError("Comparison of pixels with allowable error of %i failed %i times.", allowable_error, ret); + SDLTest_LogError("Reference surface format: %s", SDL_GetPixelFormatName(referenceSurface->format)); + SDLTest_LogError("Actual surface format: %s", SDL_GetPixelFormatName(surface->format)); + SDLTest_LogError("First detected occurrence at position %i,%i with a squared RGB-difference of %i.", sampleErrorX, sampleErrorY, sampleDist); + SDLTest_LogError("Reference pixel: R=%u G=%u B=%u A=%u", sampleReference.r, sampleReference.g, sampleReference.b, sampleReference.a); + SDLTest_LogError("Actual pixel : R=%u G=%u B=%u A=%u", sampleActual.r, sampleActual.g, sampleActual.b, sampleActual.a); + (void)SDL_snprintf(imageFilename, FILENAME_SIZE - 1, "CompareSurfaces%04d_TestOutput.bmp", _CompareSurfaceCount); + SDL_SaveBMP(surface, imageFilename); + (void)SDL_snprintf(referenceFilename, FILENAME_SIZE - 1, "CompareSurfaces%04d_Reference.bmp", _CompareSurfaceCount); + SDL_SaveBMP(referenceSurface, referenceFilename); + SDLTest_LogError("Surfaces from failed comparison saved as '%s' and '%s'", imageFilename, referenceFilename); + } + + return ret; +} + +int SDLTest_CompareMemory(const void *actual, size_t size_actual, const void *reference, size_t size_reference) { +#define WIDTH 16 + + const size_t size_max = SDL_max(size_actual, size_reference); + size_t i; + struct { + const char *header; + const Uint8 *data; + size_t size; + } columns[] = { + { + "actual", + actual, + size_actual, + }, + { + "reference", + reference, + size_reference, + }, + }; + char line_buffer[16 + SDL_arraysize(columns) * (4 * WIDTH + 1) + (SDL_arraysize(columns) - 1) * 2 + 1]; + + SDLTest_AssertCheck(size_actual == size_reference, "Sizes of memory blocks must be equal (actual=%" SDL_PRIu64 " expected=%" SDL_PRIu64 ")", (Uint64)size_actual, (Uint64)size_reference); + if (size_actual == size_reference) { + int equals; + equals = SDL_memcmp(actual, reference, size_max) == 0; + SDLTest_AssertCheck(equals, "Memory blocks contain the same data"); + if (equals) { + return 0; + } + } + + SDL_memset(line_buffer, ' ', sizeof(line_buffer)); + line_buffer[sizeof(line_buffer) - 1] = '\0'; + for (i = 0; i < SDL_arraysize(columns); i++) { + SDL_memcpy(line_buffer + 16 + 1 + i * (4 * WIDTH + 3), columns[i].header, SDL_strlen(columns[i].header)); + } + SDLTest_LogError("%s", line_buffer); + + for (i = 0; i < size_max; i += WIDTH) { + size_t pos = 0; + size_t col; + + pos += SDL_snprintf(line_buffer + pos, SDL_arraysize(line_buffer) - pos, "%016" SDL_PRIx64 , (Uint64)i); + + for (col = 0; col < SDL_arraysize(columns); col++) { + size_t j; + + for (j = 0; j < WIDTH; j++) { + if (i + j < columns[col].size) { + pos += SDL_snprintf(line_buffer + pos, SDL_arraysize(line_buffer) - pos, " %02x", columns[col].data[i + j]); + } else { + pos += SDL_snprintf(line_buffer + pos, SDL_arraysize(line_buffer) - pos, " "); + } + } + pos += SDL_snprintf(line_buffer + pos, SDL_arraysize(line_buffer) - pos, " "); + for (j = 0; j < WIDTH; j++) { + char c = ' '; + if (i + j < columns[col].size) { + c = columns[col].data[i + j]; + if (!SDL_isprint(c)) { + c = '.'; + } + } + pos += SDL_snprintf(line_buffer + pos, SDL_arraysize(line_buffer) - pos, "%c", c); + } + if (col < SDL_arraysize(columns) - 1) { + pos += SDL_snprintf(line_buffer + pos, SDL_arraysize(line_buffer), " |"); + } + } + SDLTest_LogError("%s", line_buffer); + SDL_assert(pos == SDL_arraysize(line_buffer) - 1); + } +#undef WIDTH + return 1; +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_crc32.c b/contrib/SDL-3.2.8/src/test/SDL_test_crc32.c new file mode 100644 index 0000000..f3d94b8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_crc32.c @@ -0,0 +1,160 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + + Used by the test execution component. + Original source code contributed by A. Schiffler for GSOC project. + +*/ +#include + +bool SDLTest_Crc32Init(SDLTest_Crc32Context *crcContext) +{ + int i, j; + CrcUint32 c; + + /* Sanity check context pointer */ + if (!crcContext) { + return SDL_InvalidParamError("crcContext"); + } + + /* + * Build auxiliary table for parallel byte-at-a-time CRC-32 + */ +#ifdef ORIGINAL_METHOD + for (i = 0; i < 256; ++i) { + for (c = i << 24, j = 8; j > 0; --j) { + c = c & 0x80000000 ? (c << 1) ^ CRC32_POLY : (c << 1); + } + crcContext->crc32_table[i] = c; + } +#else + for (i = 0; i < 256; i++) { + c = i; + for (j = 8; j > 0; j--) { + if (c & 1) { + c = (c >> 1) ^ CRC32_POLY; + } else { + c >>= 1; + } + } + crcContext->crc32_table[i] = c; + } +#endif + + return true; +} + +/* Complete CRC32 calculation on a memory block */ +bool SDLTest_Crc32Calc(SDLTest_Crc32Context *crcContext, CrcUint8 *inBuf, CrcUint32 inLen, CrcUint32 *crc32) +{ + if (!SDLTest_Crc32CalcStart(crcContext, crc32)) { + return false; + } + + if (!SDLTest_Crc32CalcBuffer(crcContext, inBuf, inLen, crc32)) { + return false; + } + + if (!SDLTest_Crc32CalcEnd(crcContext, crc32)) { + return false; + } + + return true; +} + +/* Start crc calculation */ + +bool SDLTest_Crc32CalcStart(SDLTest_Crc32Context *crcContext, CrcUint32 *crc32) +{ + /* Sanity check pointers */ + if (!crcContext) { + *crc32 = 0; + return SDL_InvalidParamError("crcContext"); + } + + /* + * Preload shift register, per CRC-32 spec + */ + *crc32 = 0xffffffff; + + return true; +} + +/* Finish crc calculation */ + +bool SDLTest_Crc32CalcEnd(SDLTest_Crc32Context *crcContext, CrcUint32 *crc32) +{ + /* Sanity check pointers */ + if (!crcContext) { + *crc32 = 0; + return SDL_InvalidParamError("crcContext"); + } + + /* + * Return complement, per CRC-32 spec + */ + *crc32 = (~(*crc32)); + + return true; +} + +/* Include memory block in crc */ + +bool SDLTest_Crc32CalcBuffer(SDLTest_Crc32Context *crcContext, CrcUint8 *inBuf, CrcUint32 inLen, CrcUint32 *crc32) +{ + CrcUint8 *p; + register CrcUint32 crc; + + if (!crcContext) { + *crc32 = 0; + return SDL_InvalidParamError("crcContext"); + } + + if (!inBuf) { + return SDL_InvalidParamError("inBuf"); + } + + /* + * Calculate CRC from data + */ + crc = *crc32; + for (p = inBuf; inLen > 0; ++p, --inLen) { +#ifdef ORIGINAL_METHOD + crc = (crc << 8) ^ crcContext->crc32_table[(crc >> 24) ^ *p]; +#else + crc = ((crc >> 8) & 0x00FFFFFF) ^ crcContext->crc32_table[(crc ^ *p) & 0xFF]; +#endif + } + *crc32 = crc; + + return true; +} + +bool SDLTest_Crc32Done(SDLTest_Crc32Context *crcContext) +{ + if (!crcContext) { + return SDL_InvalidParamError("crcContext"); + } + + return true; +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_font.c b/contrib/SDL-3.2.8/src/test/SDL_test_font.c new file mode 100644 index 0000000..00268f6 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_font.c @@ -0,0 +1,159 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include + +#define UTF8_IsTrailingByte(c) ((c) >= 0x80 && (c) <= 0xBF) + +int FONT_CHARACTER_SIZE = SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE; + +bool SDLTest_DrawCharacter(SDL_Renderer *renderer, float x, float y, Uint32 c) +{ + char str[5]; + char *ptr = SDL_UCS4ToUTF8(c, str); + *ptr = '\0'; + return SDL_RenderDebugText(renderer, x, y, str); +} + +bool SDLTest_DrawString(SDL_Renderer *renderer, float x, float y, const char *s) +{ + return SDL_RenderDebugText(renderer, x, y, s); +} + +SDLTest_TextWindow *SDLTest_TextWindowCreate(float x, float y, float w, float h) +{ + SDLTest_TextWindow *textwin = (SDLTest_TextWindow *)SDL_malloc(sizeof(*textwin)); + + if (!textwin) { + return NULL; + } + + textwin->rect.x = x; + textwin->rect.y = y; + textwin->rect.w = w; + textwin->rect.h = h; + textwin->current = 0; + textwin->numlines = (int)SDL_ceilf(h / FONT_LINE_HEIGHT); + textwin->lines = (char **)SDL_calloc(textwin->numlines, sizeof(*textwin->lines)); + if (!textwin->lines) { + SDL_free(textwin); + return NULL; + } + return textwin; +} + +void SDLTest_TextWindowDisplay(SDLTest_TextWindow *textwin, SDL_Renderer *renderer) +{ + int i; + float y; + + for (y = textwin->rect.y, i = 0; i < textwin->numlines; ++i, y += FONT_LINE_HEIGHT) { + if (textwin->lines[i]) { + SDLTest_DrawString(renderer, textwin->rect.x, y, textwin->lines[i]); + } + } +} + +void SDLTest_TextWindowAddText(SDLTest_TextWindow *textwin, const char *fmt, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, fmt); + (void)SDL_vsnprintf(text, sizeof(text), fmt, ap); + va_end(ap); + + SDLTest_TextWindowAddTextWithLength(textwin, text, SDL_strlen(text)); +} + +void SDLTest_TextWindowAddTextWithLength(SDLTest_TextWindow *textwin, const char *text, size_t len) +{ + size_t existing; + bool newline = false; + char *line; + + if (len > 0 && text[len - 1] == '\n') { + --len; + newline = true; + } + + if (textwin->lines[textwin->current]) { + existing = SDL_strlen(textwin->lines[textwin->current]); + } else { + existing = 0; + } + + if (*text == '\b') { + if (existing) { + while (existing > 1 && UTF8_IsTrailingByte((Uint8)textwin->lines[textwin->current][existing - 1])) { + --existing; + } + --existing; + textwin->lines[textwin->current][existing] = '\0'; + } else if (textwin->current > 0) { + SDL_free(textwin->lines[textwin->current]); + textwin->lines[textwin->current] = NULL; + --textwin->current; + } + return; + } + + line = (char *)SDL_realloc(textwin->lines[textwin->current], existing + len + 1); + if (line) { + SDL_memcpy(&line[existing], text, len); + line[existing + len] = '\0'; + textwin->lines[textwin->current] = line; + if (newline) { + if (textwin->current == textwin->numlines - 1) { + SDL_free(textwin->lines[0]); + SDL_memmove(&textwin->lines[0], &textwin->lines[1], (textwin->numlines - 1) * sizeof(textwin->lines[1])); + textwin->lines[textwin->current] = NULL; + } else { + ++textwin->current; + } + } + } +} + +void SDLTest_TextWindowClear(SDLTest_TextWindow *textwin) +{ + int i; + + for (i = 0; i < textwin->numlines; ++i) { + if (textwin->lines[i]) { + SDL_free(textwin->lines[i]); + textwin->lines[i] = NULL; + } + } + textwin->current = 0; +} + +void SDLTest_TextWindowDestroy(SDLTest_TextWindow *textwin) +{ + if (textwin) { + SDLTest_TextWindowClear(textwin); + SDL_free(textwin->lines); + SDL_free(textwin); + } +} + +void SDLTest_CleanupTextDrawing(void) +{ +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_fuzzer.c b/contrib/SDL-3.2.8/src/test/SDL_test_fuzzer.c new file mode 100644 index 0000000..67638b5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_fuzzer.c @@ -0,0 +1,494 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + + Data generators for fuzzing test data in a reproducible way. + +*/ +#include + +#include /* Needed for FLT_MAX and DBL_EPSILON */ +#include /* Needed for UCHAR_MAX, etc. */ + +/** + * Counter for fuzzer invocations + */ +static int fuzzerInvocationCounter = 0; + +/** + * Context for shared random number generator + */ +static Uint64 rndContext; + +/* + * Note: doxygen documentation markup for functions is in the header file. + */ + +void SDLTest_FuzzerInit(Uint64 execKey) +{ + rndContext = execKey; + fuzzerInvocationCounter = 0; +} + +int SDLTest_GetFuzzerInvocationCount(void) +{ + return fuzzerInvocationCounter; +} + +Uint8 SDLTest_RandomUint8(void) +{ + fuzzerInvocationCounter++; + + return (Uint8)(SDL_rand_bits_r(&rndContext) >> 24); +} + +Sint8 SDLTest_RandomSint8(void) +{ + fuzzerInvocationCounter++; + + return (Sint8)(SDL_rand_bits_r(&rndContext) >> 24); +} + +Uint16 SDLTest_RandomUint16(void) +{ + fuzzerInvocationCounter++; + + return (Uint16)(SDL_rand_bits_r(&rndContext) >> 16); +} + +Sint16 SDLTest_RandomSint16(void) +{ + fuzzerInvocationCounter++; + + return (Sint16)(SDL_rand_bits_r(&rndContext) >> 16); +} + +Uint32 SDLTest_RandomUint32(void) +{ + fuzzerInvocationCounter++; + + return SDL_rand_bits_r(&rndContext); +} + +Sint32 SDLTest_RandomSint32(void) +{ + fuzzerInvocationCounter++; + + return (Sint32)SDL_rand_bits_r(&rndContext); +} + +Uint64 SDLTest_RandomUint64(void) +{ + union + { + Uint64 v64; + Uint32 v32[2]; + } value; + + fuzzerInvocationCounter++; + + value.v32[0] = SDLTest_RandomUint32(); + value.v32[1] = SDLTest_RandomUint32(); + + return value.v64; +} + +Sint64 SDLTest_RandomSint64(void) +{ + union + { + Uint64 v64; + Uint32 v32[2]; + } value; + + fuzzerInvocationCounter++; + + value.v32[0] = SDLTest_RandomUint32(); + value.v32[1] = SDLTest_RandomUint32(); + + return (Sint64)value.v64; +} + +Sint32 SDLTest_RandomIntegerInRange(Sint32 min, Sint32 max) +{ + fuzzerInvocationCounter++; + + if (min == max) { + return min; + } + + if (min > max) { + Sint32 temp = min; + min = max; + max = temp; + } + + Uint64 range = (Sint64)max - (Sint64)min; + if (range < SDL_MAX_SINT32) { + return min + SDL_rand_r(&rndContext, (Sint32) range + 1); + } else { + Uint64 add = SDL_rand_bits_r(&rndContext) | ((Uint64) SDL_rand_bits_r(&rndContext) << 32); + return (Sint32) (min + (Sint64) (add % (range + 1))); + } +} + +/** + * Generates a unsigned boundary value between the given boundaries. + * Boundary values are inclusive. See the examples below. + * If boundary2 < boundary1, the values are swapped. + * If boundary1 == boundary2, value of boundary1 will be returned + * + * Generating boundary values for Uint8: + * BoundaryValues(UINT8_MAX, 10, 20, True) -> [10,11,19,20] + * BoundaryValues(UINT8_MAX, 10, 20, False) -> [9,21] + * BoundaryValues(UINT8_MAX, 0, 15, True) -> [0, 1, 14, 15] + * BoundaryValues(UINT8_MAX, 0, 15, False) -> [16] + * BoundaryValues(UINT8_MAX, 0, 0xFF, False) -> [0], error set + * + * Generator works the same for other types of unsigned integers. + * + * \param maxValue The biggest value that is acceptable for this data type. + * For instance, for Uint8 -> 255, Uint16 -> 65536 etc. + * \param boundary1 defines lower boundary + * \param boundary2 defines upper boundary + * \param validDomain Generate only for valid domain (for the data type) + * + * \returns Returns a random boundary value for the domain or 0 in case of error + */ +static Uint64 SDLTest_GenerateUnsignedBoundaryValues(const Uint64 maxValue, Uint64 boundary1, Uint64 boundary2, bool validDomain) +{ + Uint64 b1, b2; + Uint64 delta; + Uint64 tempBuf[4]; + Uint8 index; + + /* Maybe swap */ + if (boundary1 > boundary2) { + b1 = boundary2; + b2 = boundary1; + } else { + b1 = boundary1; + b2 = boundary2; + } + + index = 0; + if (validDomain == true) { + if (b1 == b2) { + return b1; + } + + /* Generate up to 4 values within bounds */ + delta = b2 - b1; + if (delta < 4) { + do { + tempBuf[index] = b1 + index; + index++; + } while (index < delta); + } else { + tempBuf[index] = b1; + index++; + tempBuf[index] = b1 + 1; + index++; + tempBuf[index] = b2 - 1; + index++; + tempBuf[index] = b2; + index++; + } + } else { + /* Generate up to 2 values outside of bounds */ + if (b1 > 0) { + tempBuf[index] = b1 - 1; + index++; + } + + if (b2 < maxValue) { + tempBuf[index] = b2 + 1; + index++; + } + } + + if (index == 0) { + /* There are no valid boundaries */ + SDL_Unsupported(); + return 0; + } + + return tempBuf[SDLTest_RandomUint8() % index]; +} + +Uint8 SDLTest_RandomUint8BoundaryValue(Uint8 boundary1, Uint8 boundary2, bool validDomain) +{ + /* max value for Uint8 */ + const Uint64 maxValue = UCHAR_MAX; + return (Uint8)SDLTest_GenerateUnsignedBoundaryValues(maxValue, + (Uint64)boundary1, (Uint64)boundary2, + validDomain); +} + +Uint16 SDLTest_RandomUint16BoundaryValue(Uint16 boundary1, Uint16 boundary2, bool validDomain) +{ + /* max value for Uint16 */ + const Uint64 maxValue = USHRT_MAX; + return (Uint16)SDLTest_GenerateUnsignedBoundaryValues(maxValue, + (Uint64)boundary1, (Uint64)boundary2, + validDomain); +} + +Uint32 SDLTest_RandomUint32BoundaryValue(Uint32 boundary1, Uint32 boundary2, bool validDomain) +{ +/* max value for Uint32 */ +#if ((ULONG_MAX) == (UINT_MAX)) + const Uint64 maxValue = ULONG_MAX; +#else + const Uint64 maxValue = UINT_MAX; +#endif + return (Uint32)SDLTest_GenerateUnsignedBoundaryValues(maxValue, + (Uint64)boundary1, (Uint64)boundary2, + validDomain); +} + +Uint64 SDLTest_RandomUint64BoundaryValue(Uint64 boundary1, Uint64 boundary2, bool validDomain) +{ + /* max value for Uint64 */ + const Uint64 maxValue = UINT64_MAX; + return SDLTest_GenerateUnsignedBoundaryValues(maxValue, + boundary1, boundary2, + validDomain); +} + +/** + * Generates a signed boundary value between the given boundaries. + * Boundary values are inclusive. See the examples below. + * If boundary2 < boundary1, the values are swapped. + * If boundary1 == boundary2, value of boundary1 will be returned + * + * Generating boundary values for Sint8: + * SignedBoundaryValues(SCHAR_MIN, SCHAR_MAX, -10, 20, True) -> [-10,-9,19,20] + * SignedBoundaryValues(SCHAR_MIN, SCHAR_MAX, -10, 20, False) -> [-11,21] + * SignedBoundaryValues(SCHAR_MIN, SCHAR_MAX, -30, -15, True) -> [-30, -29, -16, -15] + * SignedBoundaryValues(SCHAR_MIN, SCHAR_MAX, -127, 15, False) -> [16] + * SignedBoundaryValues(SCHAR_MIN, SCHAR_MAX, -127, 127, False) -> [0], error set + * + * Generator works the same for other types of signed integers. + * + * \param minValue The smallest value that is acceptable for this data type. + * For instance, for Uint8 -> -127, etc. + * \param maxValue The biggest value that is acceptable for this data type. + * For instance, for Uint8 -> 127, etc. + * \param boundary1 defines lower boundary + * \param boundary2 defines upper boundary + * \param validDomain Generate only for valid domain (for the data type) + * + * \returns Returns a random boundary value for the domain or 0 in case of error + */ +static Sint64 SDLTest_GenerateSignedBoundaryValues(const Sint64 minValue, const Sint64 maxValue, Sint64 boundary1, Sint64 boundary2, bool validDomain) +{ + Sint64 b1, b2; + Sint64 delta; + Sint64 tempBuf[4]; + Uint8 index; + + /* Maybe swap */ + if (boundary1 > boundary2) { + b1 = boundary2; + b2 = boundary1; + } else { + b1 = boundary1; + b2 = boundary2; + } + + index = 0; + if (validDomain == true) { + if (b1 == b2) { + return b1; + } + + /* Generate up to 4 values within bounds */ + delta = b2 - b1; + if (delta < 4) { + do { + tempBuf[index] = b1 + index; + index++; + } while (index < delta); + } else { + tempBuf[index] = b1; + index++; + tempBuf[index] = b1 + 1; + index++; + tempBuf[index] = b2 - 1; + index++; + tempBuf[index] = b2; + index++; + } + } else { + /* Generate up to 2 values outside of bounds */ + if (b1 > minValue) { + tempBuf[index] = b1 - 1; + index++; + } + + if (b2 < maxValue) { + tempBuf[index] = b2 + 1; + index++; + } + } + + if (index == 0) { + /* There are no valid boundaries */ + SDL_Unsupported(); + return minValue; + } + + return tempBuf[SDLTest_RandomUint8() % index]; +} + +Sint8 SDLTest_RandomSint8BoundaryValue(Sint8 boundary1, Sint8 boundary2, bool validDomain) +{ + /* min & max values for Sint8 */ + const Sint64 maxValue = SCHAR_MAX; + const Sint64 minValue = SCHAR_MIN; + return (Sint8)SDLTest_GenerateSignedBoundaryValues(minValue, maxValue, + (Sint64)boundary1, (Sint64)boundary2, + validDomain); +} + +Sint16 SDLTest_RandomSint16BoundaryValue(Sint16 boundary1, Sint16 boundary2, bool validDomain) +{ + /* min & max values for Sint16 */ + const Sint64 maxValue = SHRT_MAX; + const Sint64 minValue = SHRT_MIN; + return (Sint16)SDLTest_GenerateSignedBoundaryValues(minValue, maxValue, + (Sint64)boundary1, (Sint64)boundary2, + validDomain); +} + +Sint32 SDLTest_RandomSint32BoundaryValue(Sint32 boundary1, Sint32 boundary2, bool validDomain) +{ +/* min & max values for Sint32 */ +#if ((ULONG_MAX) == (UINT_MAX)) + const Sint64 maxValue = LONG_MAX; + const Sint64 minValue = LONG_MIN; +#else + const Sint64 maxValue = INT_MAX; + const Sint64 minValue = INT_MIN; +#endif + return (Sint32)SDLTest_GenerateSignedBoundaryValues(minValue, maxValue, + (Sint64)boundary1, (Sint64)boundary2, + validDomain); +} + +Sint64 SDLTest_RandomSint64BoundaryValue(Sint64 boundary1, Sint64 boundary2, bool validDomain) +{ + /* min & max values for Sint64 */ + const Sint64 maxValue = INT64_MAX; + const Sint64 minValue = INT64_MIN; + return SDLTest_GenerateSignedBoundaryValues(minValue, maxValue, + boundary1, boundary2, + validDomain); +} + +float SDLTest_RandomUnitFloat(void) +{ + return SDL_randf_r(&rndContext); +} + +float SDLTest_RandomFloat(void) +{ + union + { + float f; + Uint32 v32; + } value; + + do { + value.v32 = SDLTest_RandomUint32(); + } while (SDL_isnanf(value.f) || SDL_isinff(value.f)); + + return value.f; +} + +double SDLTest_RandomUnitDouble(void) +{ + return (double)(SDLTest_RandomUint64() >> (64-53)) * 0x1.0p-53; +} + +double SDLTest_RandomDouble(void) +{ + union + { + double d; + Uint64 v64; + } value; + + do { + value.v64 = SDLTest_RandomUint64(); + } while (SDL_isnan(value.d) || SDL_isinf(value.d)); + + return value.d; +} + +char *SDLTest_RandomAsciiString(void) +{ + return SDLTest_RandomAsciiStringWithMaximumLength(255); +} + +char *SDLTest_RandomAsciiStringWithMaximumLength(int maxLength) +{ + int size; + + if (maxLength < 1) { + SDL_InvalidParamError("maxLength"); + return NULL; + } + + size = (SDLTest_RandomUint32() % (maxLength + 1)); + if (size == 0) { + size = 1; + } + return SDLTest_RandomAsciiStringOfSize(size); +} + +char *SDLTest_RandomAsciiStringOfSize(int size) +{ + char *string; + int counter; + + if (size < 1) { + SDL_InvalidParamError("size"); + return NULL; + } + + string = (char *)SDL_malloc((size + 1) * sizeof(char)); + if (!string) { + return NULL; + } + + for (counter = 0; counter < size; ++counter) { + string[counter] = (char)SDLTest_RandomIntegerInRange(32, 126); + } + + string[counter] = '\0'; + + fuzzerInvocationCounter++; + + return string; +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_harness.c b/contrib/SDL-3.2.8/src/test/SDL_test_harness.c new file mode 100644 index 0000000..d6a3b8d --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_harness.c @@ -0,0 +1,864 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include + +#include /* Needed for exit() */ + +/* Enable to have color in logs */ +#if 1 +#define COLOR_RED "\033[0;31m" +#define COLOR_GREEN "\033[0;32m" +#define COLOR_YELLOW "\033[0;93m" +#define COLOR_BLUE "\033[0;94m" +#define COLOR_END "\033[0m" +#else +#define COLOR_RED "" +#define COLOR_GREEN "" +#define COLOR_BLUE "" +#define COLOR_YELLOW "" +#define COLOR_END "" +#endif + +/* Invalid test name/description message format */ +#define SDLTEST_INVALID_NAME_FORMAT "(Invalid)" + +/* Log summary message format */ +#define SDLTEST_LOG_SUMMARY_FORMAT "%s Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_RED "Failed=%d" COLOR_END " " COLOR_BLUE "Skipped=%d" COLOR_END +#define SDLTEST_LOG_SUMMARY_FORMAT_OK "%s Summary: Total=%d " COLOR_GREEN "Passed=%d" COLOR_END " " COLOR_GREEN "Failed=%d" COLOR_END " " COLOR_BLUE "Skipped=%d" COLOR_END + +/* Final result message format */ +#define SDLTEST_FINAL_RESULT_FORMAT COLOR_YELLOW ">>> %s '%s':" COLOR_END " %s\n" + +struct SDLTest_TestSuiteRunner { + struct + { + SDLTest_TestSuiteReference **testSuites; + char *runSeed; + Uint64 execKey; + char *filter; + int testIterations; + bool randomOrder; + } user; + + SDLTest_ArgumentParser argparser; +}; + +/* ! Timeout for single test case execution */ +static Uint32 SDLTest_TestCaseTimeout = 3600; + +static const char *common_harness_usage[] = { + "[--iterations #]", + "[--execKey #]", + "[--seed string]", + "[--filter suite_name|test_name]", + "[--random-order]", + NULL +}; + +char *SDLTest_GenerateRunSeed(char *buffer, int length) +{ + Uint64 randomContext = SDL_GetPerformanceCounter(); + int counter; + + if (!buffer) { + SDLTest_LogError("Input buffer must not be NULL."); + return NULL; + } + + /* Sanity check input */ + if (length <= 0) { + SDLTest_LogError("The length of the harness seed must be >0."); + return NULL; + } + + /* Generate a random string of alphanumeric characters */ + for (counter = 0; counter < length; counter++) { + char ch; + int v = SDL_rand_r(&randomContext, 10 + 26); + if (v < 10) { + ch = (char)('0' + v); + } else { + ch = (char)('A' + v - 10); + } + buffer[counter] = ch; + } + buffer[length] = '\0'; + + return buffer; +} + +/** + * Generates an execution key for the fuzzer. + * + * \param runSeed The run seed to use + * \param suiteName The name of the test suite + * \param testName The name of the test + * \param iteration The iteration count + * + * \returns The generated execution key to initialize the fuzzer with. + * + */ +static Uint64 SDLTest_GenerateExecKey(const char *runSeed, const char *suiteName, const char *testName, int iteration) +{ + SDLTest_Md5Context md5Context; + Uint64 *keys; + char iterationString[16]; + size_t runSeedLength; + size_t suiteNameLength; + size_t testNameLength; + size_t iterationStringLength; + size_t entireStringLength; + char *buffer; + + if (!runSeed || runSeed[0] == '\0') { + SDLTest_LogError("Invalid runSeed string."); + return 0; + } + + if (!suiteName || suiteName[0] == '\0') { + SDLTest_LogError("Invalid suiteName string."); + return 0; + } + + if (!testName || testName[0] == '\0') { + SDLTest_LogError("Invalid testName string."); + return 0; + } + + if (iteration <= 0) { + SDLTest_LogError("Invalid iteration count."); + return 0; + } + + /* Convert iteration number into a string */ + SDL_memset(iterationString, 0, sizeof(iterationString)); + (void)SDL_snprintf(iterationString, sizeof(iterationString) - 1, "%d", iteration); + + /* Combine the parameters into single string */ + runSeedLength = SDL_strlen(runSeed); + suiteNameLength = SDL_strlen(suiteName); + testNameLength = SDL_strlen(testName); + iterationStringLength = SDL_strlen(iterationString); + entireStringLength = runSeedLength + suiteNameLength + testNameLength + iterationStringLength + 1; + buffer = (char *)SDL_malloc(entireStringLength); + if (!buffer) { + SDLTest_LogError("Failed to allocate buffer for execKey generation."); + return 0; + } + (void)SDL_snprintf(buffer, entireStringLength, "%s%s%s%d", runSeed, suiteName, testName, iteration); + + /* Hash string and use half of the digest as 64bit exec key */ + SDLTest_Md5Init(&md5Context); + SDLTest_Md5Update(&md5Context, (unsigned char *)buffer, (unsigned int)entireStringLength); + SDLTest_Md5Final(&md5Context); + SDL_free(buffer); + keys = (Uint64 *)md5Context.digest; + + return keys[0]; +} + +/** + * Set timeout handler for test. + * + * \param timeout Timeout interval in seconds. + * \param callback Function that will be called after timeout has elapsed. + * + * \return Timer id or -1 on failure. + */ +static SDL_TimerID SDLTest_SetTestTimeout(int timeout, SDL_TimerCallback callback) +{ + Uint32 timeoutInMilliseconds; + SDL_TimerID timerID; + + if (!callback) { + SDLTest_LogError("Timeout callback can't be NULL"); + return 0; + } + + if (timeout < 0) { + SDLTest_LogError("Timeout value must be bigger than zero."); + return 0; + } + + /* Set timer */ + timeoutInMilliseconds = timeout * 1000; + timerID = SDL_AddTimer(timeoutInMilliseconds, callback, 0x0); + if (timerID == 0) { + SDLTest_LogError("Creation of SDL timer failed: %s", SDL_GetError()); + return 0; + } + + return timerID; +} + +/** + * Timeout handler. Aborts test run and exits harness process. + */ +static Uint32 SDLCALL SDLTest_BailOut(void *userdata, SDL_TimerID timerID, Uint32 interval) +{ + SDLTest_LogError("TestCaseTimeout timer expired. Aborting test run."); + exit(TEST_ABORTED); /* bail out from the test */ + return 0; +} + +/** + * Execute a test using the given execution key. + * + * \param testSuite Suite containing the test case. + * \param testCase Case to execute. + * \param execKey Execution key for the fuzzer. + * \param forceTestRun Force test to run even if test was disabled in suite. + * + * \returns Test case result. + */ +static int SDLTest_RunTest(SDLTest_TestSuiteReference *testSuite, const SDLTest_TestCaseReference *testCase, Uint64 execKey, bool forceTestRun) +{ + SDL_TimerID timer = 0; + int testCaseResult = 0; + int testResult = 0; + int fuzzerCount; + void *data = NULL; + + if (!testSuite || !testCase || !testSuite->name || !testCase->name) { + SDLTest_LogError("Setup failure: testSuite or testCase references NULL"); + return TEST_RESULT_SETUP_FAILURE; + } + + if (!testCase->enabled && forceTestRun == false) { + SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, "Skipped (Disabled)"); + return TEST_RESULT_SKIPPED; + } + + /* Initialize fuzzer */ + SDLTest_FuzzerInit(execKey); + + /* Reset assert tracker */ + SDLTest_ResetAssertSummary(); + + /* Set timeout timer */ + timer = SDLTest_SetTestTimeout(SDLTest_TestCaseTimeout, SDLTest_BailOut); + + /* Maybe run suite initializer function */ + if (testSuite->testSetUp) { + testSuite->testSetUp(&data); + if (SDLTest_AssertSummaryToTestResult() == TEST_RESULT_FAILED) { + SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Suite Setup", testSuite->name, COLOR_RED "Failed" COLOR_END); + return TEST_RESULT_SETUP_FAILURE; + } + } + + /* Run test case function */ + testCaseResult = testCase->testCase(data); + + /* Convert test execution result into harness result */ + if (testCaseResult == TEST_SKIPPED) { + /* Test was programmatically skipped */ + testResult = TEST_RESULT_SKIPPED; + } else if (testCaseResult == TEST_STARTED) { + /* Test did not return a TEST_COMPLETED value; assume it failed */ + testResult = TEST_RESULT_FAILED; + } else if (testCaseResult == TEST_ABORTED) { + /* Test was aborted early; assume it failed */ + testResult = TEST_RESULT_FAILED; + } else { + /* Perform failure analysis based on asserts */ + testResult = SDLTest_AssertSummaryToTestResult(); + } + + /* Maybe run suite cleanup function (ignore failed asserts) */ + if (testSuite->testTearDown) { + testSuite->testTearDown(data); + } + + /* Cancel timeout timer */ + if (timer) { + SDL_RemoveTimer(timer); + } + + /* Report on asserts and fuzzer usage */ + fuzzerCount = SDLTest_GetFuzzerInvocationCount(); + if (fuzzerCount > 0) { + SDLTest_Log("Fuzzer invocations: %d", fuzzerCount); + } + + /* Final log based on test execution result */ + if (testCaseResult == TEST_SKIPPED) { + /* Test was programmatically skipped */ + SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, COLOR_BLUE "Skipped (Programmatically)" COLOR_END); + } else if (testCaseResult == TEST_STARTED) { + /* Test did not return a TEST_COMPLETED value; assume it failed */ + SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, COLOR_RED "Failed (test started, but did not return TEST_COMPLETED)" COLOR_END); + } else if (testCaseResult == TEST_ABORTED) { + /* Test was aborted early; assume it failed */ + SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", testCase->name, COLOR_RED "Failed (Aborted)" COLOR_END); + } else { + SDLTest_LogAssertSummary(); + } + + return testResult; +} + +/* Prints summary of all suites/tests contained in the given reference */ +#if 0 +static void SDLTest_LogTestSuiteSummary(SDLTest_TestSuiteReference *testSuites) +{ + int suiteCounter; + int testCounter; + SDLTest_TestSuiteReference *testSuite; + SDLTest_TestCaseReference *testCase; + + /* Loop over all suites */ + suiteCounter = 0; + while (&testSuites[suiteCounter]) { + testSuite=&testSuites[suiteCounter]; + suiteCounter++; + SDLTest_Log("Test Suite %i - %s\n", suiteCounter, + (testSuite->name) ? testSuite->name : SDLTEST_INVALID_NAME_FORMAT); + + /* Loop over all test cases */ + testCounter = 0; + while (testSuite->testCases[testCounter]) { + testCase=(SDLTest_TestCaseReference *)testSuite->testCases[testCounter]; + testCounter++; + SDLTest_Log(" Test Case %i - %s: %s", testCounter, + (testCase->name) ? testCase->name : SDLTEST_INVALID_NAME_FORMAT, + (testCase->description) ? testCase->description : SDLTEST_INVALID_NAME_FORMAT); + } + } +} +#endif + +/* Gets a timer value in seconds */ +static float GetClock(void) +{ + float currentClock = SDL_GetPerformanceCounter() / (float)SDL_GetPerformanceFrequency(); + return currentClock; +} + +/** + * Execute a test suite using the given run seed and execution key. + * + * The filter string is matched to the suite name (full comparison) to select a single suite, + * or if no suite matches, it is matched to the test names (full comparison) to select a single test. + * + * \param runner The runner to execute. + * + * \returns Test run result; 0 when all tests passed, 1 if any tests failed. + */ +int SDLTest_ExecuteTestSuiteRunner(SDLTest_TestSuiteRunner *runner) +{ + int totalNumberOfTests = 0; + int failedNumberOfTests = 0; + int suiteCounter; + int testCounter; + int iterationCounter; + SDLTest_TestSuiteReference *testSuite; + const SDLTest_TestCaseReference *testCase; + const char *runSeed = NULL; + const char *currentSuiteName; + const char *currentTestName; + Uint64 execKey; + float runStartSeconds; + float suiteStartSeconds; + float testStartSeconds; + float runEndSeconds; + float suiteEndSeconds; + float testEndSeconds; + float runtime; + int suiteFilter = 0; + const char *suiteFilterName = NULL; + int testFilter = 0; + const char *testFilterName = NULL; + bool forceTestRun = false; + int testResult = 0; + int runResult = 0; + int totalTestFailedCount = 0; + int totalTestPassedCount = 0; + int totalTestSkippedCount = 0; + int testFailedCount = 0; + int testPassedCount = 0; + int testSkippedCount = 0; + int countSum = 0; + const SDLTest_TestCaseReference **failedTests; + char generatedSeed[16 + 1]; + int nbSuites = 0; + int i = 0; + int *arraySuites = NULL; + + /* Sanitize test iterations */ + if (runner->user.testIterations < 1) { + runner->user.testIterations = 1; + } + + /* Generate run see if we don't have one already */ + if (!runner->user.runSeed || runner->user.runSeed[0] == '\0') { + runSeed = SDLTest_GenerateRunSeed(generatedSeed, 16); + if (!runSeed) { + SDLTest_LogError("Generating a random seed failed"); + return 2; + } + } else { + runSeed = runner->user.runSeed; + } + + /* Reset per-run counters */ + totalTestFailedCount = 0; + totalTestPassedCount = 0; + totalTestSkippedCount = 0; + + /* Take time - run start */ + runStartSeconds = GetClock(); + + /* Log run with fuzzer parameters */ + SDLTest_Log("::::: Test Run /w seed '%s' started\n", runSeed); + + /* Count the total number of tests */ + suiteCounter = 0; + while (runner->user.testSuites[suiteCounter]) { + testSuite = runner->user.testSuites[suiteCounter]; + suiteCounter++; + testCounter = 0; + while (testSuite->testCases[testCounter]) { + testCounter++; + totalNumberOfTests++; + } + } + + if (totalNumberOfTests == 0) { + SDLTest_LogError("No tests to run?"); + return -1; + } + + /* Pre-allocate an array for tracking failed tests (potentially all test cases) */ + failedTests = (const SDLTest_TestCaseReference **)SDL_malloc(totalNumberOfTests * sizeof(SDLTest_TestCaseReference *)); + if (!failedTests) { + SDLTest_LogError("Unable to allocate cache for failed tests"); + return -1; + } + + /* Initialize filtering */ + if (runner->user.filter && runner->user.filter[0] != '\0') { + /* Loop over all suites to check if we have a filter match */ + suiteCounter = 0; + while (runner->user.testSuites[suiteCounter] && suiteFilter == 0) { + testSuite = runner->user.testSuites[suiteCounter]; + suiteCounter++; + if (testSuite->name && SDL_strcasecmp(runner->user.filter, testSuite->name) == 0) { + /* Matched a suite name */ + suiteFilter = 1; + suiteFilterName = testSuite->name; + SDLTest_Log("Filtering: running only suite '%s'", suiteFilterName); + break; + } + + /* Within each suite, loop over all test cases to check if we have a filter match */ + testCounter = 0; + while (testSuite->testCases[testCounter] && testFilter == 0) { + testCase = testSuite->testCases[testCounter]; + testCounter++; + if (testCase->name && SDL_strcasecmp(runner->user.filter, testCase->name) == 0) { + /* Matched a test name */ + suiteFilter = 1; + suiteFilterName = testSuite->name; + testFilter = 1; + testFilterName = testCase->name; + SDLTest_Log("Filtering: running only test '%s' in suite '%s'", testFilterName, suiteFilterName); + break; + } + } + } + + if (suiteFilter == 0 && testFilter == 0) { + SDLTest_LogError("Filter '%s' did not match any test suite/case.", runner->user.filter); + for (suiteCounter = 0; runner->user.testSuites[suiteCounter]; ++suiteCounter) { + testSuite = runner->user.testSuites[suiteCounter]; + if (testSuite->name) { + SDLTest_Log("Test suite: %s", testSuite->name); + } + + /* Within each suite, loop over all test cases to check if we have a filter match */ + for (testCounter = 0; testSuite->testCases[testCounter]; ++testCounter) { + testCase = testSuite->testCases[testCounter]; + SDLTest_Log(" test: %s%s", testCase->name, testCase->enabled ? "" : " (disabled)"); + } + } + SDLTest_Log("Exit code: 2"); + SDL_free((void *)failedTests); + return 2; + } + + runner->user.randomOrder = false; + } + + /* Number of test suites */ + while (runner->user.testSuites[nbSuites]) { + nbSuites++; + } + + arraySuites = SDL_malloc(nbSuites * sizeof(int)); + if (!arraySuites) { + SDL_free((void *)failedTests); + return SDL_OutOfMemory(); + } + for (i = 0; i < nbSuites; i++) { + arraySuites[i] = i; + } + + /* Mix the list of suites to run them in random order */ + { + /* Exclude last test "subsystemsTestSuite" which is said to interfere with other tests */ + nbSuites--; + + if (runner->user.execKey != 0) { + execKey = runner->user.execKey; + } else { + /* dummy values to have random numbers working */ + execKey = SDLTest_GenerateExecKey(runSeed, "random testSuites", "initialisation", 1); + } + + /* Initialize fuzzer */ + SDLTest_FuzzerInit(execKey); + + i = 100; + while (i--) { + int a, b; + int tmp; + a = SDLTest_RandomIntegerInRange(0, nbSuites - 1); + b = SDLTest_RandomIntegerInRange(0, nbSuites - 1); + /* + * NB: prevent swapping here to make sure the tests start with the same + * random seed (whether they are run in order or not). + * So we consume same number of SDLTest_RandomIntegerInRange() in all cases. + * + * If some random value were used at initialization before the tests start, the --seed wouldn't do the same with or without randomOrder. + */ + /* Swap */ + if (runner->user.randomOrder) { + tmp = arraySuites[b]; + arraySuites[b] = arraySuites[a]; + arraySuites[a] = tmp; + } + } + + /* re-add last lest */ + nbSuites++; + } + + /* Loop over all suites */ + for (i = 0; i < nbSuites; i++) { + suiteCounter = arraySuites[i]; + testSuite = runner->user.testSuites[suiteCounter]; + currentSuiteName = (testSuite->name ? testSuite->name : SDLTEST_INVALID_NAME_FORMAT); + suiteCounter++; + + /* Filter suite if flag set and we have a name */ + if (suiteFilter == 1 && suiteFilterName && testSuite->name && + SDL_strcasecmp(suiteFilterName, testSuite->name) != 0) { + /* Skip suite */ + SDLTest_Log("===== Test Suite %i: '%s' " COLOR_BLUE "skipped" COLOR_END "\n", + suiteCounter, + currentSuiteName); + } else { + + int nbTestCases = 0; + int *arrayTestCases; + int j; + while (testSuite->testCases[nbTestCases]) { + nbTestCases++; + } + + arrayTestCases = SDL_malloc(nbTestCases * sizeof(int)); + if (!arrayTestCases) { + SDL_free(arraySuites); + SDL_free((void *)failedTests); + return SDL_OutOfMemory(); + } + for (j = 0; j < nbTestCases; j++) { + arrayTestCases[j] = j; + } + + /* Mix the list of testCases to run them in random order */ + j = 100; + while (j--) { + int a, b; + int tmp; + a = SDLTest_RandomIntegerInRange(0, nbTestCases - 1); + b = SDLTest_RandomIntegerInRange(0, nbTestCases - 1); + /* Swap */ + /* See previous note */ + if (runner->user.randomOrder) { + tmp = arrayTestCases[b]; + arrayTestCases[b] = arrayTestCases[a]; + arrayTestCases[a] = tmp; + } + } + + /* Reset per-suite counters */ + testFailedCount = 0; + testPassedCount = 0; + testSkippedCount = 0; + + /* Take time - suite start */ + suiteStartSeconds = GetClock(); + + /* Log suite started */ + SDLTest_Log("===== Test Suite %i: '%s' started\n", + suiteCounter, + currentSuiteName); + + /* Loop over all test cases */ + for (j = 0; j < nbTestCases; j++) { + testCounter = arrayTestCases[j]; + testCase = testSuite->testCases[testCounter]; + currentTestName = (testCase->name ? testCase->name : SDLTEST_INVALID_NAME_FORMAT); + testCounter++; + + /* Filter tests if flag set and we have a name */ + if (testFilter == 1 && testFilterName && testCase->name && + SDL_strcasecmp(testFilterName, testCase->name) != 0) { + /* Skip test */ + SDLTest_Log("===== Test Case %i.%i: '%s' " COLOR_BLUE "skipped" COLOR_END "\n", + suiteCounter, + testCounter, + currentTestName); + } else { + /* Override 'disabled' flag if we specified a test filter (i.e. force run for debugging) */ + if (testFilter == 1 && !testCase->enabled) { + SDLTest_Log("Force run of disabled test since test filter was set"); + forceTestRun = true; + } + + /* Take time - test start */ + testStartSeconds = GetClock(); + + /* Log test started */ + SDLTest_Log(COLOR_YELLOW "----- Test Case %i.%i: '%s' started" COLOR_END, + suiteCounter, + testCounter, + currentTestName); + if (testCase->description && testCase->description[0] != '\0') { + SDLTest_Log("Test Description: '%s'", + (testCase->description) ? testCase->description : SDLTEST_INVALID_NAME_FORMAT); + } + + /* Loop over all iterations */ + iterationCounter = 0; + while (iterationCounter < runner->user.testIterations) { + iterationCounter++; + + if (runner->user.execKey != 0) { + execKey = runner->user.execKey; + } else { + execKey = SDLTest_GenerateExecKey(runSeed, testSuite->name, testCase->name, iterationCounter); + } + + SDLTest_Log("Test Iteration %i: execKey %" SDL_PRIu64, iterationCounter, execKey); + testResult = SDLTest_RunTest(testSuite, testCase, execKey, forceTestRun); + + if (testResult == TEST_RESULT_PASSED) { + testPassedCount++; + totalTestPassedCount++; + } else if (testResult == TEST_RESULT_SKIPPED) { + testSkippedCount++; + totalTestSkippedCount++; + } else { + testFailedCount++; + totalTestFailedCount++; + } + } + + /* Take time - test end */ + testEndSeconds = GetClock(); + runtime = testEndSeconds - testStartSeconds; + if (runtime < 0.0f) { + runtime = 0.0f; + } + + if (runner->user.testIterations > 1) { + /* Log test runtime */ + SDLTest_Log("Runtime of %i iterations: %.1f sec", runner->user.testIterations, runtime); + SDLTest_Log("Average Test runtime: %.5f sec", runtime / (float)runner->user.testIterations); + } else { + /* Log test runtime */ + SDLTest_Log("Total Test runtime: %.1f sec", runtime); + } + + /* Log final test result */ + switch (testResult) { + case TEST_RESULT_PASSED: + SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Test", currentTestName, COLOR_GREEN "Passed" COLOR_END); + break; + case TEST_RESULT_FAILED: + SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", currentTestName, COLOR_RED "Failed" COLOR_END); + break; + case TEST_RESULT_NO_ASSERT: + SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Test", currentTestName, COLOR_BLUE "No Asserts" COLOR_END); + break; + } + + /* Collect failed test case references for repro-step display */ + if (testResult == TEST_RESULT_FAILED) { + failedTests[failedNumberOfTests] = testCase; + failedNumberOfTests++; + } + } + } + + /* Take time - suite end */ + suiteEndSeconds = GetClock(); + runtime = suiteEndSeconds - suiteStartSeconds; + if (runtime < 0.0f) { + runtime = 0.0f; + } + + /* Log suite runtime */ + SDLTest_Log("Total Suite runtime: %.1f sec", runtime); + + /* Log summary and final Suite result */ + countSum = testPassedCount + testFailedCount + testSkippedCount; + if (testFailedCount == 0) { + SDLTest_Log(SDLTEST_LOG_SUMMARY_FORMAT_OK, "Suite", countSum, testPassedCount, testFailedCount, testSkippedCount); + SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Suite", currentSuiteName, COLOR_GREEN "Passed" COLOR_END); + } else { + SDLTest_LogError(SDLTEST_LOG_SUMMARY_FORMAT, "Suite", countSum, testPassedCount, testFailedCount, testSkippedCount); + SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Suite", currentSuiteName, COLOR_RED "Failed" COLOR_END); + } + + SDL_free(arrayTestCases); + } + } + + SDL_free(arraySuites); + + /* Take time - run end */ + runEndSeconds = GetClock(); + runtime = runEndSeconds - runStartSeconds; + if (runtime < 0.0f) { + runtime = 0.0f; + } + + /* Log total runtime */ + SDLTest_Log("Total Run runtime: %.1f sec", runtime); + + /* Log summary and final run result */ + countSum = totalTestPassedCount + totalTestFailedCount + totalTestSkippedCount; + if (totalTestFailedCount == 0) { + runResult = 0; + SDLTest_Log(SDLTEST_LOG_SUMMARY_FORMAT_OK, "Run", countSum, totalTestPassedCount, totalTestFailedCount, totalTestSkippedCount); + SDLTest_Log(SDLTEST_FINAL_RESULT_FORMAT, "Run /w seed", runSeed, COLOR_GREEN "Passed" COLOR_END); + } else { + runResult = 1; + SDLTest_LogError(SDLTEST_LOG_SUMMARY_FORMAT, "Run", countSum, totalTestPassedCount, totalTestFailedCount, totalTestSkippedCount); + SDLTest_LogError(SDLTEST_FINAL_RESULT_FORMAT, "Run /w seed", runSeed, COLOR_RED "Failed" COLOR_END); + } + + /* Print repro steps for failed tests */ + if (failedNumberOfTests > 0) { + SDLTest_Log("Harness input to repro failures:"); + for (testCounter = 0; testCounter < failedNumberOfTests; testCounter++) { + SDLTest_Log(COLOR_RED " --seed %s --filter %s" COLOR_END, runSeed, failedTests[testCounter]->name); + } + } + SDL_free((void *)failedTests); + + SDLTest_Log("Exit code: %d", runResult); + return runResult; +} + +static int SDLCALL SDLTest_TestSuiteCommonArg(void *data, char **argv, int index) +{ + SDLTest_TestSuiteRunner *runner = data; + + if (SDL_strcasecmp(argv[index], "--iterations") == 0) { + if (argv[index + 1]) { + runner->user.testIterations = SDL_atoi(argv[index + 1]); + if (runner->user.testIterations < 1) { + runner->user.testIterations = 1; + } + return 2; + } + } + else if (SDL_strcasecmp(argv[index], "--execKey") == 0) { + if (argv[index + 1]) { + (void)SDL_sscanf(argv[index + 1], "%" SDL_PRIu64, &runner->user.execKey); + return 2; + } + } + else if (SDL_strcasecmp(argv[index], "--seed") == 0) { + if (argv[index + 1]) { + runner->user.runSeed = SDL_strdup(argv[index + 1]); + return 2; + } + } + else if (SDL_strcasecmp(argv[index], "--filter") == 0) { + if (argv[index + 1]) { + runner->user.filter = SDL_strdup(argv[index + 1]); + return 2; + } + } + else if (SDL_strcasecmp(argv[index], "--random-order") == 0) { + runner->user.randomOrder = true; + return 1; + } + return 0; +} + +SDLTest_TestSuiteRunner *SDLTest_CreateTestSuiteRunner(SDLTest_CommonState *state, SDLTest_TestSuiteReference *testSuites[]) +{ + SDLTest_TestSuiteRunner *runner; + SDLTest_ArgumentParser *argparser; + + if (!state) { + SDLTest_LogError("SDL Test Suites require a common state"); + return NULL; + } + + runner = SDL_calloc(1, sizeof(SDLTest_TestSuiteRunner)); + if (!runner) { + SDLTest_LogError("Failed to allocate memory for test suite runner"); + return NULL; + } + runner->user.testSuites = testSuites; + + runner->argparser.parse_arguments = SDLTest_TestSuiteCommonArg; + runner->argparser.usage = common_harness_usage; + runner->argparser.data = runner; + + /* Find last argument description and append our description */ + argparser = state->argparser; + for (;;) { + if (argparser->next == NULL) { + argparser->next = &runner->argparser; + break; + } + argparser = argparser->next; + + } + + return runner; +} + +void SDLTest_DestroyTestSuiteRunner(SDLTest_TestSuiteRunner *runner) { + + SDL_free(runner->user.filter); + SDL_free(runner->user.runSeed); + SDL_free(runner); +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_log.c b/contrib/SDL-3.2.8/src/test/SDL_test_log.c new file mode 100644 index 0000000..95bc2a1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_log.c @@ -0,0 +1,211 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + + Used by the test framework and test cases. + +*/ + +/* quiet windows compiler warnings */ +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif +#include + +#include /* Needed for localtime() */ + +/* work around compiler warning on older GCCs. */ +#if (defined(__GNUC__) && (__GNUC__ <= 2)) +static size_t strftime_gcc2_workaround(char *s, size_t max, const char *fmt, const struct tm *tm) +{ + return strftime(s, max, fmt, tm); +} +#ifdef strftime +#undef strftime +#endif +#define strftime strftime_gcc2_workaround +#endif + +/** + * Converts unix timestamp to its ascii representation in localtime + * + * Note: Uses a static buffer internally, so the return value + * isn't valid after the next call of this function. If you + * want to retain the return value, make a copy of it. + * + * \param timestamp A Timestamp, i.e. time(0) + * + * \return Ascii representation of the timestamp in localtime in the format '08/23/01 14:55:02' + */ +static const char *SDLTest_TimestampToString(const time_t timestamp) +{ + time_t copy; + static char buffer[64]; + struct tm *local; + size_t result = 0; + + SDL_memset(buffer, 0, sizeof(buffer)); + copy = timestamp; + local = localtime(©); + result = strftime(buffer, sizeof(buffer), "%x %X", local); + if (result == 0) { + return ""; + } + + return buffer; +} + +/* + * Prints given message with a timestamp in the TEST category and INFO priority. + */ +void SDLTest_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) +{ + va_list list; + char logMessage[SDLTEST_MAX_LOGMESSAGE_LENGTH]; + + /* Print log message into a buffer */ + SDL_memset(logMessage, 0, SDLTEST_MAX_LOGMESSAGE_LENGTH); + va_start(list, fmt); + (void)SDL_vsnprintf(logMessage, SDLTEST_MAX_LOGMESSAGE_LENGTH - 1, fmt, list); + va_end(list); + + /* Log with timestamp and newline */ + SDL_LogMessage(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_INFO, " %s: %s", SDLTest_TimestampToString(time(0)), logMessage); +} + +/* + * Prints given message with a timestamp in the TEST category and the ERROR priority. + */ +void SDLTest_LogError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) +{ + va_list list; + char logMessage[SDLTEST_MAX_LOGMESSAGE_LENGTH]; + + /* Print log message into a buffer */ + SDL_memset(logMessage, 0, SDLTEST_MAX_LOGMESSAGE_LENGTH); + va_start(list, fmt); + (void)SDL_vsnprintf(logMessage, SDLTEST_MAX_LOGMESSAGE_LENGTH - 1, fmt, list); + va_end(list); + + /* Log with timestamp and newline */ + SDL_LogMessage(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_ERROR, "%s: %s", SDLTest_TimestampToString(time(0)), logMessage); +} + +static char nibble_to_char(Uint8 nibble) +{ + if (nibble < 0xa) { + return '0' + nibble; + } else { + return 'a' + nibble - 10; + } +} + +void SDLTest_LogEscapedString(const char *prefix, const void *buffer, size_t size) +{ + const Uint8 *data = buffer; + char logMessage[SDLTEST_MAX_LOGMESSAGE_LENGTH]; + + if (data) { + size_t i; + size_t pos = 0; + #define NEED_X_CHARS(N) \ + if (pos + (N) > sizeof(logMessage) - 2) { \ + break; \ + } + + logMessage[pos++] = '"'; + for (i = 0; i < size; i++) { + Uint8 c = data[i]; + size_t pos_start = pos; + switch (c) { + case '\0': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = '0'; + break; + case '"': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = '"'; + break; + case '\n': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = 'n'; + break; + case '\r': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = 'r'; + break; + case '\t': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = 't'; + break; + case '\f': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = 'f'; + break; + case '\b': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = 'b'; + break; + case '\\': + NEED_X_CHARS(2); + logMessage[pos++] = '\\'; + logMessage[pos++] = '\\'; + break; + default: + if (SDL_isprint(c)) { + NEED_X_CHARS(1); + logMessage[pos++] = c; + } else { + NEED_X_CHARS(4); + logMessage[pos++] = '\\'; + logMessage[pos++] = 'x'; + logMessage[pos++] = nibble_to_char(c >> 4); + logMessage[pos++] = nibble_to_char(c & 0xf); + } + break; + } + if (pos == pos_start) { + break; + } + } + if (i < size) { + logMessage[sizeof(logMessage) - 4] = '.'; + logMessage[sizeof(logMessage) - 3] = '.'; + logMessage[sizeof(logMessage) - 2] = '.'; + logMessage[sizeof(logMessage) - 1] = '\0'; + } else { + logMessage[pos++] = '"'; + logMessage[pos] = '\0'; + } + } else { + SDL_strlcpy(logMessage, "(nil)", sizeof(logMessage)); + } + + SDLTest_Log("%s%s", prefix, logMessage); +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_md5.c b/contrib/SDL-3.2.8/src/test/SDL_test_md5.c new file mode 100644 index 0000000..0cc0896 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_md5.c @@ -0,0 +1,342 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + *********************************************************************** + ** RSA Data Security, Inc. MD5 Message-Digest Algorithm ** + ** Created: 2/17/90 RLR ** + ** Revised: 1/91 SRD,AJ,BSK,JT Reference C ver., 7/10 constant corr. ** + *********************************************************************** + */ + +/* + *********************************************************************** + ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. ** + ** ** + ** License to copy and use this software is granted provided that ** + ** it is identified as the "RSA Data Security, Inc. MD5 Message- ** + ** Digest Algorithm" in all material mentioning or referencing this ** + ** software or this function. ** + ** ** + ** License is also granted to make and use derivative works ** + ** provided that such works are identified as "derived from the RSA ** + ** Data Security, Inc. MD5 Message-Digest Algorithm" in all ** + ** material mentioning or referencing the derived work. ** + ** ** + ** RSA Data Security, Inc. makes no representations concerning ** + ** either the merchantability of this software or the suitability ** + ** of this software for any particular purpose. It is provided "as ** + ** is" without express or implied warranty of any kind. ** + ** ** + ** These notices must be retained in any copies of any part of this ** + ** documentation and/or software. ** + *********************************************************************** + */ +#include + +/* Forward declaration of static helper function */ +static void SDLTest_Md5Transform(MD5UINT4 *buf, const MD5UINT4 *in); + +static unsigned char MD5PADDING[64] = { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* F, G, H and I are basic MD5 functions */ +#define F(x, y, z) (((x) & (y)) | ((~(x)) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~(z)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~(z)))) + +/* ROTATE_LEFT rotates x left n bits */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */ + +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s, ac) \ + { \ + (a) += F((b), (c), (d)) + (x) + (MD5UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) \ + { \ + (a) += G((b), (c), (d)) + (x) + (MD5UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) \ + { \ + (a) += H((b), (c), (d)) + (x) + (MD5UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) \ + { \ + (a) += I((b), (c), (d)) + (x) + (MD5UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } + +/* + The routine MD5Init initializes the message-digest context + mdContext. All fields are set to zero. +*/ + +void SDLTest_Md5Init(SDLTest_Md5Context *mdContext) +{ + if (!mdContext) { + return; + } + + mdContext->i[0] = mdContext->i[1] = (MD5UINT4)0; + + /* + * Load magic initialization constants. + */ + mdContext->buf[0] = (MD5UINT4)0x67452301; + mdContext->buf[1] = (MD5UINT4)0xefcdab89; + mdContext->buf[2] = (MD5UINT4)0x98badcfe; + mdContext->buf[3] = (MD5UINT4)0x10325476; +} + +/* + The routine MD5Update updates the message-digest context to + account for the presence of each of the characters inBuf[0..inLen-1] + in the message whose digest is being computed. +*/ + +void SDLTest_Md5Update(SDLTest_Md5Context *mdContext, unsigned char *inBuf, + unsigned int inLen) +{ + MD5UINT4 in[16]; + int mdi; + unsigned int i, ii; + + if (!mdContext) { + return; + } + if (!inBuf || inLen < 1) { + return; + } + + /* + * compute number of bytes mod 64 + */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* + * update number of bits + */ + if ((mdContext->i[0] + ((MD5UINT4)inLen << 3)) < mdContext->i[0]) { + mdContext->i[1]++; + } + mdContext->i[0] += ((MD5UINT4)inLen << 3); + mdContext->i[1] += ((MD5UINT4)inLen >> 29); + + while (inLen--) { + /* + * add new character to buffer, increment mdi + */ + mdContext->in[mdi++] = *inBuf++; + + /* + * transform if necessary + */ + if (mdi == 0x40) { + for (i = 0, ii = 0; i < 16; i++, ii += 4) { + in[i] = (((MD5UINT4)mdContext->in[ii + 3]) << 24) | (((MD5UINT4)mdContext->in[ii + 2]) << 16) | (((MD5UINT4)mdContext->in[ii + 1]) << 8) | ((MD5UINT4)mdContext->in[ii]); + } + SDLTest_Md5Transform(mdContext->buf, in); + mdi = 0; + } + } +} + +/* + The routine MD5Final terminates the message-digest computation and + ends with the desired message digest in mdContext->digest[0...15]. +*/ + +void SDLTest_Md5Final(SDLTest_Md5Context *mdContext) +{ + MD5UINT4 in[16]; + int mdi; + unsigned int i, ii; + unsigned int padLen; + + if (!mdContext) { + return; + } + + /* + * save number of bits + */ + in[14] = mdContext->i[0]; + in[15] = mdContext->i[1]; + + /* + * compute number of bytes mod 64 + */ + mdi = (int)((mdContext->i[0] >> 3) & 0x3F); + + /* + * pad out to 56 mod 64 + */ + padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi); + SDLTest_Md5Update(mdContext, MD5PADDING, padLen); + + /* + * append length in bits and transform + */ + for (i = 0, ii = 0; i < 14; i++, ii += 4) { + in[i] = (((MD5UINT4)mdContext->in[ii + 3]) << 24) | (((MD5UINT4)mdContext->in[ii + 2]) << 16) | (((MD5UINT4)mdContext->in[ii + 1]) << 8) | ((MD5UINT4)mdContext->in[ii]); + } + SDLTest_Md5Transform(mdContext->buf, in); + + /* + * store buffer in digest + */ + for (i = 0, ii = 0; i < 4; i++, ii += 4) { + mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF); + mdContext->digest[ii + 1] = + (unsigned char)((mdContext->buf[i] >> 8) & 0xFF); + mdContext->digest[ii + 2] = + (unsigned char)((mdContext->buf[i] >> 16) & 0xFF); + mdContext->digest[ii + 3] = + (unsigned char)((mdContext->buf[i] >> 24) & 0xFF); + } +} + +/* Basic MD5 step. Transforms buf based on in. + */ +static void SDLTest_Md5Transform(MD5UINT4 *buf, const MD5UINT4 *in) +{ + MD5UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; + + /* + * Round 1 + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 + FF(a, b, c, d, in[0], S11, 3614090360u); /* 1 */ + FF(d, a, b, c, in[1], S12, 3905402710u); /* 2 */ + FF(c, d, a, b, in[2], S13, 606105819u); /* 3 */ + FF(b, c, d, a, in[3], S14, 3250441966u); /* 4 */ + FF(a, b, c, d, in[4], S11, 4118548399u); /* 5 */ + FF(d, a, b, c, in[5], S12, 1200080426u); /* 6 */ + FF(c, d, a, b, in[6], S13, 2821735955u); /* 7 */ + FF(b, c, d, a, in[7], S14, 4249261313u); /* 8 */ + FF(a, b, c, d, in[8], S11, 1770035416u); /* 9 */ + FF(d, a, b, c, in[9], S12, 2336552879u); /* 10 */ + FF(c, d, a, b, in[10], S13, 4294925233u); /* 11 */ + FF(b, c, d, a, in[11], S14, 2304563134u); /* 12 */ + FF(a, b, c, d, in[12], S11, 1804603682u); /* 13 */ + FF(d, a, b, c, in[13], S12, 4254626195u); /* 14 */ + FF(c, d, a, b, in[14], S13, 2792965006u); /* 15 */ + FF(b, c, d, a, in[15], S14, 1236535329u); /* 16 */ + + /* + * Round 2 + */ +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 + GG(a, b, c, d, in[1], S21, 4129170786u); /* 17 */ + GG(d, a, b, c, in[6], S22, 3225465664u); /* 18 */ + GG(c, d, a, b, in[11], S23, 643717713u); /* 19 */ + GG(b, c, d, a, in[0], S24, 3921069994u); /* 20 */ + GG(a, b, c, d, in[5], S21, 3593408605u); /* 21 */ + GG(d, a, b, c, in[10], S22, 38016083u); /* 22 */ + GG(c, d, a, b, in[15], S23, 3634488961u); /* 23 */ + GG(b, c, d, a, in[4], S24, 3889429448u); /* 24 */ + GG(a, b, c, d, in[9], S21, 568446438u); /* 25 */ + GG(d, a, b, c, in[14], S22, 3275163606u); /* 26 */ + GG(c, d, a, b, in[3], S23, 4107603335u); /* 27 */ + GG(b, c, d, a, in[8], S24, 1163531501u); /* 28 */ + GG(a, b, c, d, in[13], S21, 2850285829u); /* 29 */ + GG(d, a, b, c, in[2], S22, 4243563512u); /* 30 */ + GG(c, d, a, b, in[7], S23, 1735328473u); /* 31 */ + GG(b, c, d, a, in[12], S24, 2368359562u); /* 32 */ + + /* + * Round 3 + */ +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 + HH(a, b, c, d, in[5], S31, 4294588738u); /* 33 */ + HH(d, a, b, c, in[8], S32, 2272392833u); /* 34 */ + HH(c, d, a, b, in[11], S33, 1839030562u); /* 35 */ + HH(b, c, d, a, in[14], S34, 4259657740u); /* 36 */ + HH(a, b, c, d, in[1], S31, 2763975236u); /* 37 */ + HH(d, a, b, c, in[4], S32, 1272893353u); /* 38 */ + HH(c, d, a, b, in[7], S33, 4139469664u); /* 39 */ + HH(b, c, d, a, in[10], S34, 3200236656u); /* 40 */ + HH(a, b, c, d, in[13], S31, 681279174u); /* 41 */ + HH(d, a, b, c, in[0], S32, 3936430074u); /* 42 */ + HH(c, d, a, b, in[3], S33, 3572445317u); /* 43 */ + HH(b, c, d, a, in[6], S34, 76029189u); /* 44 */ + HH(a, b, c, d, in[9], S31, 3654602809u); /* 45 */ + HH(d, a, b, c, in[12], S32, 3873151461u); /* 46 */ + HH(c, d, a, b, in[15], S33, 530742520u); /* 47 */ + HH(b, c, d, a, in[2], S34, 3299628645u); /* 48 */ + + /* + * Round 4 + */ +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + II(a, b, c, d, in[0], S41, 4096336452u); /* 49 */ + II(d, a, b, c, in[7], S42, 1126891415u); /* 50 */ + II(c, d, a, b, in[14], S43, 2878612391u); /* 51 */ + II(b, c, d, a, in[5], S44, 4237533241u); /* 52 */ + II(a, b, c, d, in[12], S41, 1700485571u); /* 53 */ + II(d, a, b, c, in[3], S42, 2399980690u); /* 54 */ + II(c, d, a, b, in[10], S43, 4293915773u); /* 55 */ + II(b, c, d, a, in[1], S44, 2240044497u); /* 56 */ + II(a, b, c, d, in[8], S41, 1873313359u); /* 57 */ + II(d, a, b, c, in[15], S42, 4264355552u); /* 58 */ + II(c, d, a, b, in[6], S43, 2734768916u); /* 59 */ + II(b, c, d, a, in[13], S44, 1309151649u); /* 60 */ + II(a, b, c, d, in[4], S41, 4149444226u); /* 61 */ + II(d, a, b, c, in[11], S42, 3174756917u); /* 62 */ + II(c, d, a, b, in[2], S43, 718787259u); /* 63 */ + II(b, c, d, a, in[9], S44, 3951481745u); /* 64 */ + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} diff --git a/contrib/SDL-3.2.8/src/test/SDL_test_memory.c b/contrib/SDL-3.2.8/src/test/SDL_test_memory.c new file mode 100644 index 0000000..b90c4a5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/test/SDL_test_memory.c @@ -0,0 +1,457 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include + +#ifdef HAVE_LIBUNWIND_H +#define UNW_LOCAL_ONLY +#include +#ifndef unw_get_proc_name_by_ip +#define SDLTEST_UNWIND_NO_PROC_NAME_BY_IP +static bool s_unwind_symbol_names = true; +#endif +#endif + +#ifdef SDL_PLATFORM_WIN32 +#include +#include + +static struct { + SDL_SharedObject *module; + BOOL (WINAPI *pSymInitialize)(HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess); + BOOL (WINAPI *pSymFromAddr)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFO Symbol); + BOOL (WINAPI *pSymGetLineFromAddr64)(HANDLE hProcess, DWORD64 qwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line); +} dyn_dbghelp; + +/* older SDKs might not have this: */ +__declspec(dllimport) USHORT WINAPI RtlCaptureStackBackTrace(ULONG FramesToSkip, ULONG FramesToCapture, PVOID* BackTrace, PULONG BackTraceHash); +#define CaptureStackBackTrace RtlCaptureStackBackTrace + +#endif + +/* This is a simple tracking allocator to demonstrate the use of SDL's + memory allocation replacement functionality. + + It gets slow with large numbers of allocations and shouldn't be used + for production code. +*/ + +#define MAXIMUM_TRACKED_STACK_DEPTH 32 + +typedef struct SDL_tracked_allocation +{ + void *mem; + size_t size; + Uint64 stack[MAXIMUM_TRACKED_STACK_DEPTH]; + struct SDL_tracked_allocation *next; +#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP + char stack_names[MAXIMUM_TRACKED_STACK_DEPTH][256]; +#endif +} SDL_tracked_allocation; + +static SDLTest_Crc32Context s_crc32_context; +static SDL_malloc_func SDL_malloc_orig = NULL; +static SDL_calloc_func SDL_calloc_orig = NULL; +static SDL_realloc_func SDL_realloc_orig = NULL; +static SDL_free_func SDL_free_orig = NULL; +static int s_previous_allocations = 0; +static int s_unknown_frees = 0; +static SDL_tracked_allocation *s_tracked_allocations[256]; +static bool s_randfill_allocations = false; +static SDL_AtomicInt s_lock; + +#define LOCK_ALLOCATOR() \ + do { \ + if (SDL_CompareAndSwapAtomicInt(&s_lock, 0, 1)) { \ + break; \ + } \ + SDL_CPUPauseInstruction(); \ + } while (true) +#define UNLOCK_ALLOCATOR() do { SDL_SetAtomicInt(&s_lock, 0); } while (0) + +static unsigned int get_allocation_bucket(void *mem) +{ + CrcUint32 crc_value; + unsigned int index; + SDLTest_Crc32Calc(&s_crc32_context, (CrcUint8 *)&mem, sizeof(mem), &crc_value); + index = (crc_value & (SDL_arraysize(s_tracked_allocations) - 1)); + return index; +} + +static SDL_tracked_allocation* SDL_GetTrackedAllocation(void *mem) +{ + SDL_tracked_allocation *entry; + LOCK_ALLOCATOR(); + int index = get_allocation_bucket(mem); + for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { + if (mem == entry->mem) { + UNLOCK_ALLOCATOR(); + return entry; + } + } + UNLOCK_ALLOCATOR(); + return NULL; +} + +static size_t SDL_GetTrackedAllocationSize(void *mem) +{ + SDL_tracked_allocation *entry = SDL_GetTrackedAllocation(mem); + + return entry ? entry->size : SIZE_MAX; +} + +static bool SDL_IsAllocationTracked(void *mem) +{ + return SDL_GetTrackedAllocation(mem) != NULL; +} + +static void SDL_TrackAllocation(void *mem, size_t size) +{ + SDL_tracked_allocation *entry; + int index = get_allocation_bucket(mem); + + if (SDL_IsAllocationTracked(mem)) { + return; + } + entry = (SDL_tracked_allocation *)SDL_malloc_orig(sizeof(*entry)); + if (!entry) { + return; + } + LOCK_ALLOCATOR(); + entry->mem = mem; + entry->size = size; + + /* Generate the stack trace for the allocation */ + SDL_zeroa(entry->stack); +#ifdef HAVE_LIBUNWIND_H + { + int stack_index; + unw_cursor_t cursor; + unw_context_t context; + + unw_getcontext(&context); + unw_init_local(&cursor, &context); + + stack_index = 0; + while (unw_step(&cursor) > 0) { + unw_word_t pc; +#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP + unw_word_t offset; + char sym[236]; +#endif + + unw_get_reg(&cursor, UNW_REG_IP, &pc); + entry->stack[stack_index] = pc; + +#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP + if (s_unwind_symbol_names && unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) { + SDL_snprintf(entry->stack_names[stack_index], sizeof(entry->stack_names[stack_index]), "%s+0x%llx", sym, (unsigned long long)offset); + } +#endif + ++stack_index; + + if (stack_index == SDL_arraysize(entry->stack)) { + break; + } + } + } +#elif defined(SDL_PLATFORM_WIN32) + { + Uint32 count; + PVOID frames[63]; + Uint32 i; + + count = CaptureStackBackTrace(1, SDL_arraysize(frames), frames, NULL); + + count = SDL_min(count, MAXIMUM_TRACKED_STACK_DEPTH); + for (i = 0; i < count; i++) { + entry->stack[i] = (Uint64)(uintptr_t)frames[i]; + } + } +#endif /* HAVE_LIBUNWIND_H */ + + entry->next = s_tracked_allocations[index]; + s_tracked_allocations[index] = entry; + UNLOCK_ALLOCATOR(); +} + +static void SDL_UntrackAllocation(void *mem) +{ + SDL_tracked_allocation *entry, *prev; + int index = get_allocation_bucket(mem); + + LOCK_ALLOCATOR(); + prev = NULL; + for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { + if (mem == entry->mem) { + if (prev) { + prev->next = entry->next; + } else { + s_tracked_allocations[index] = entry->next; + } + SDL_free_orig(entry); + UNLOCK_ALLOCATOR(); + return; + } + prev = entry; + } + s_unknown_frees += 1; + UNLOCK_ALLOCATOR(); +} + +static void rand_fill_memory(void* ptr, size_t start, size_t end) +{ + Uint8* mem = (Uint8*) ptr; + size_t i; + + if (!s_randfill_allocations) + return; + + for (i = start; i < end; ++i) { + mem[i] = SDLTest_RandomUint8(); + } +} + +static void * SDLCALL SDLTest_TrackedMalloc(size_t size) +{ + void *mem; + + mem = SDL_malloc_orig(size); + if (mem) { + SDL_TrackAllocation(mem, size); + rand_fill_memory(mem, 0, size); + } + return mem; +} + +static void * SDLCALL SDLTest_TrackedCalloc(size_t nmemb, size_t size) +{ + void *mem; + + mem = SDL_calloc_orig(nmemb, size); + if (mem) { + SDL_TrackAllocation(mem, nmemb * size); + } + return mem; +} + +static void * SDLCALL SDLTest_TrackedRealloc(void *ptr, size_t size) +{ + void *mem; + size_t old_size = 0; + if (ptr) { + old_size = SDL_GetTrackedAllocationSize(ptr); + SDL_assert(old_size != SIZE_MAX); + } + mem = SDL_realloc_orig(ptr, size); + if (ptr) { + SDL_UntrackAllocation(ptr); + } + if (mem) { + SDL_TrackAllocation(mem, size); + if (size > old_size) { + rand_fill_memory(mem, old_size, size); + } + } + return mem; +} + +static void SDLCALL SDLTest_TrackedFree(void *ptr) +{ + if (!ptr) { + return; + } + + if (s_previous_allocations == 0) { + SDL_assert(SDL_IsAllocationTracked(ptr)); + } + SDL_UntrackAllocation(ptr); + SDL_free_orig(ptr); +} + +void SDLTest_TrackAllocations(void) +{ + if (SDL_malloc_orig) { + return; + } + + SDLTest_Crc32Init(&s_crc32_context); + + s_previous_allocations = SDL_GetNumAllocations(); + if (s_previous_allocations < 0) { + SDL_Log("SDL was built without allocation count support, disabling free() validation"); + } else if (s_previous_allocations != 0) { + SDL_Log("SDLTest_TrackAllocations(): There are %d previous allocations, disabling free() validation", s_previous_allocations); + } +#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP + do { + /* Don't use SDL_GetHint: SDL_malloc is off limits. */ + const char *env_trackmem = SDL_getenv_unsafe("SDL_TRACKMEM_SYMBOL_NAMES"); + if (env_trackmem) { + if (SDL_strcasecmp(env_trackmem, "1") == 0 || SDL_strcasecmp(env_trackmem, "yes") == 0 || SDL_strcasecmp(env_trackmem, "true") == 0) { + s_unwind_symbol_names = true; + } else if (SDL_strcasecmp(env_trackmem, "0") == 0 || SDL_strcasecmp(env_trackmem, "no") == 0 || SDL_strcasecmp(env_trackmem, "false") == 0) { + s_unwind_symbol_names = false; + } + } + } while (0); + +#elif defined(SDL_PLATFORM_WIN32) + do { + dyn_dbghelp.module = SDL_LoadObject("dbghelp.dll"); + if (!dyn_dbghelp.module) { + goto dbghelp_failed; + } + dyn_dbghelp.pSymInitialize = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymInitialize"); + dyn_dbghelp.pSymFromAddr = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymFromAddr"); + dyn_dbghelp.pSymGetLineFromAddr64 = (void *)SDL_LoadFunction(dyn_dbghelp.module, "SymGetLineFromAddr64"); + if (!dyn_dbghelp.pSymInitialize || !dyn_dbghelp.pSymFromAddr || !dyn_dbghelp.pSymGetLineFromAddr64) { + goto dbghelp_failed; + } + if (!dyn_dbghelp.pSymInitialize(GetCurrentProcess(), NULL, TRUE)) { + goto dbghelp_failed; + } + break; +dbghelp_failed: + if (dyn_dbghelp.module) { + SDL_UnloadObject(dyn_dbghelp.module); + dyn_dbghelp.module = NULL; + } + } while (0); +#endif + + SDL_GetMemoryFunctions(&SDL_malloc_orig, + &SDL_calloc_orig, + &SDL_realloc_orig, + &SDL_free_orig); + + SDL_SetMemoryFunctions(SDLTest_TrackedMalloc, + SDLTest_TrackedCalloc, + SDLTest_TrackedRealloc, + SDLTest_TrackedFree); +} + +void SDLTest_RandFillAllocations(void) +{ + SDLTest_TrackAllocations(); + + s_randfill_allocations = true; +} + +void SDLTest_LogAllocations(void) +{ + char *message = NULL; + size_t message_size = 0; + char line[256], *tmp; + SDL_tracked_allocation *entry; + int index, count, stack_index; + Uint64 total_allocated; + + if (!SDL_malloc_orig) { + return; + } + + message = SDL_realloc_orig(NULL, 1); + if (!message) { + return; + } + *message = 0; + +#define ADD_LINE() \ + message_size += (SDL_strlen(line) + 1); \ + tmp = (char *)SDL_realloc_orig(message, message_size); \ + if (!tmp) { \ + return; \ + } \ + message = tmp; \ + SDL_strlcat(message, line, message_size) + + SDL_strlcpy(line, "Memory allocations:\n", sizeof(line)); + ADD_LINE(); + + count = 0; + total_allocated = 0; + for (index = 0; index < SDL_arraysize(s_tracked_allocations); ++index) { + for (entry = s_tracked_allocations[index]; entry; entry = entry->next) { + (void)SDL_snprintf(line, sizeof(line), "Allocation %d: %d bytes\n", count, (int)entry->size); + ADD_LINE(); + /* Start at stack index 1 to skip our tracking functions */ + for (stack_index = 1; stack_index < SDL_arraysize(entry->stack); ++stack_index) { + char stack_entry_description[256] = "???"; + + if (!entry->stack[stack_index]) { + break; + } +#ifdef HAVE_LIBUNWIND_H + { +#ifdef SDLTEST_UNWIND_NO_PROC_NAME_BY_IP + if (s_unwind_symbol_names) { + (void)SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s", entry->stack_names[stack_index]); + } +#else + char name[256] = "???"; + unw_word_t offset = 0; + unw_get_proc_name_by_ip(unw_local_addr_space, entry->stack[stack_index], name, sizeof(name), &offset, NULL); + (void)SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s+0x%llx", name, (long long unsigned int)offset); +#endif + } +#elif defined(SDL_PLATFORM_WIN32) + { + DWORD64 dwDisplacement = 0; + char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbol_buffer; + DWORD lineColumn = 0; + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + IMAGEHLP_LINE64 dbg_line; + dbg_line.SizeOfStruct = sizeof(dbg_line); + dbg_line.FileName = ""; + dbg_line.LineNumber = 0; + + if (dyn_dbghelp.module) { + if (!dyn_dbghelp.pSymFromAddr(GetCurrentProcess(), entry->stack[stack_index], &dwDisplacement, pSymbol)) { + SDL_strlcpy(pSymbol->Name, "???", MAX_SYM_NAME); + dwDisplacement = 0; + } + dyn_dbghelp.pSymGetLineFromAddr64(GetCurrentProcess(), (DWORD64)entry->stack[stack_index], &lineColumn, &dbg_line); + } + SDL_snprintf(stack_entry_description, sizeof(stack_entry_description), "%s+0x%I64x %s:%u", pSymbol->Name, dwDisplacement, dbg_line.FileName, (Uint32)dbg_line.LineNumber); + } +#endif + (void)SDL_snprintf(line, sizeof(line), "\t0x%" SDL_PRIx64 ": %s\n", entry->stack[stack_index], stack_entry_description); + + ADD_LINE(); + } + total_allocated += entry->size; + ++count; + } + } + (void)SDL_snprintf(line, sizeof(line), "Total: %.2f Kb in %d allocations", total_allocated / 1024.0, count); + ADD_LINE(); + if (s_unknown_frees != 0) { + (void)SDL_snprintf(line, sizeof(line), ", %d unknown frees", s_unknown_frees); + ADD_LINE(); + } + (void)SDL_snprintf(line, sizeof(line), "\n"); + ADD_LINE(); +#undef ADD_LINE + + SDL_Log("%s", message); +} -- cgit v1.2.3