diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 83a1f1a0..0bebae5d 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -51,7 +51,8 @@ jobs: cmake-generator: Ninja cmake-toolchain-file: ${{ matrix.platform.cmake-toolchain-file }} discriminator: ${{ matrix.platform.arch }} - version: 2-latest + version: 3-latest + version-sdl2-compat: 2-head version-sdl12-compat: 1-head - name: 'Prepare sources for release' if: ${{ startsWith(github.ref, 'refs/tags/') }} @@ -71,6 +72,7 @@ jobs: -DCMAKE_TOOLCHAIN_FILE=${{ matrix.platform.cmake-toolchain-file }} \ -DDETHRACE_PLATFORM_SDL1=ON \ -DDETHRACE_PLATFORM_SDL2=ON \ + -DDETHRACE_PLATFORM_SDL3=ON \ -DDETHRACE_PLATFORM_SDL_DYNAMIC=ON \ ${{ matrix.platform.cmake-args }} - name: 'Build (CMake)' diff --git a/CMakeLists.txt b/CMakeLists.txt index ed33e4fd..5e86658c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ option(DETHRACE_SOUND_ENABLED "Include audio support" ON) option(DETHRACE_NET_ENABLED "Include net support" ON) option(DETHRACE_PLATFORM_SDL1 "Support SDL 1.2 platform driver" OFF) option(DETHRACE_PLATFORM_SDL2 "Support SDL 2 platform driver" ON) +option(DETHRACE_PLATFORM_SDL3 "Support SDL 3 platform driver" OFF) option(MSVC_42_FOR_RECCMP "Build with MSVC 4.2 to match assembly" OFF) @@ -54,6 +55,7 @@ if(MSVC_42_FOR_RECCMP) set(DETHRACE_NET_ENABLED OFF) set(DETHRACE_PLATFORM_SDL1 OFF) set(DETHRACE_PLATFORM_SDL2 OFF) + set(DETHRACE_PLATFORM_SDL3 OFF) set(BRENDER_BUILD_DRIVERS OFF) set(CMAKE_C_FLAGS_DEBUG "/Od /Oi /Zi /MLd") @@ -107,6 +109,12 @@ if(DETHRACE_PLATFORM_SDL2) math(EXPR count_sdl_platforms "${count_sdl_platforms} + 1") endif() +if(DETHRACE_PLATFORM_SDL3) + find_package(SDL3 CONFIG REQUIRED) + list(APPEND DETHRACE_PLATFORMS SDL3) + math(EXPR count_sdl_platforms "${count_sdl_platforms} + 1") +endif() + if(count_sdl_platforms GREATER 1) # Force dynamic SDL when enabling 2 (or more) SDL platform backends set(dynamic_sdl_force TRUE) diff --git a/src/DETHRACE/CMakeLists.txt b/src/DETHRACE/CMakeLists.txt index 19777345..4961e5e0 100644 --- a/src/DETHRACE/CMakeLists.txt +++ b/src/DETHRACE/CMakeLists.txt @@ -295,6 +295,12 @@ if(DETHRACE_INSTALL) OPTIONAL ) endif() + if(DETHRACE_PLATFORM_SDL3) + install(FILES "$" + DESTINATION "." + OPTIONAL + ) + endif() endif() endif() endif() diff --git a/src/harness/CMakeLists.txt b/src/harness/CMakeLists.txt index 52819007..52d11057 100644 --- a/src/harness/CMakeLists.txt +++ b/src/harness/CMakeLists.txt @@ -99,7 +99,7 @@ endif() if(DETHRACE_PLATFORM_SDL2) target_sources(harness PRIVATE platforms/sdl2.c - platforms/sdl_scancode_map.h + platforms/sdl2_scancode_map.h platforms/sdl2_syms.h ) target_compile_definitions(harness PRIVATE DETHRACE_PLATFORM_SDL2) @@ -112,6 +112,21 @@ if(DETHRACE_PLATFORM_SDL2) endif() endif() +if(DETHRACE_PLATFORM_SDL3) + target_sources(harness PRIVATE + platforms/sdl3.c + platforms/sdl2_scancode_map.h + platforms/sdl3_syms.h + ) + target_compile_definitions(harness PRIVATE DETHRACE_PLATFORM_SDL3) + if(DETHRACE_PLATFORM_SDL_DYNAMIC) + set_property(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/platforms/sdl3.c" APPEND PROPERTY INCLUDE_DIRECTORIES "$") + set_property(GLOBAL APPEND PROPERTY DETHRACE_BUILD_RPATHS "$") + else() + target_link_libraries(harness PRIVATE SDL3::SDL3) + endif() +endif() + if(DETHRACE_PLATFORM_SDL_DYNAMIC) target_compile_definitions(harness PRIVATE DETHRACE_SDL_DYNAMIC) target_link_libraries(harness PRIVATE ${CMAKE_DL_LIBS}) diff --git a/src/harness/harness.c b/src/harness/harness.c index 3aa38de6..3ea92512 100644 --- a/src/harness/harness.c +++ b/src/harness/harness.c @@ -22,16 +22,22 @@ extern void Harness_Platform_Init(tHarness_platform* platform); extern const tPlatform_bootstrap SDL1_bootstrap; extern const tPlatform_bootstrap SDL2_bootstrap; +extern const tPlatform_bootstrap SDL3_bootstrap; static const tPlatform_bootstrap* platform_bootstraps[] = { -#if defined(DETHRACE_PLATFORM_SDL2) && defined(DETHRACE_PLATFORM_SDL1) +#if defined(DETHRACE_PLATFORM_SDL2) &SDL2_bootstrap, - &SDL1_bootstrap -#elif defined(DETHRACE_PLATFORM_SDL2) - &SDL2_bootstrap -#elif defined(DETHRACE_PLATFORM_SDL1) - &SDL1_bootstrap -#else +#define HAS_PLATFORM_BOOTSTRAP +#endif +#if defined(DETHRACE_PLATFORM_SDL3) + &SDL3_bootstrap, +#define HAS_PLATFORM_BOOTSTRAP +#endif +#if defined(DETHRACE_PLATFORM_SDL1) + &SDL1_bootstrap, +#define HAS_PLATFORM_BOOTSTRAP +#endif +#ifndef HAS_PLATFORM_BOOTSTRAP // This is the case for MSVC 4.20 builds NULL #endif diff --git a/src/harness/include/harness/trace.h b/src/harness/include/harness/trace.h index 1a61bea7..73458f43 100644 --- a/src/harness/include/harness/trace.h +++ b/src/harness/include/harness/trace.h @@ -14,7 +14,7 @@ void debug_print_matrix4(const char* fmt, const char* fn, char* name, br_matrix4 #define BLUE -#if 1 // _MSC_VER == 1020 +#if _MSC_VER == 1020 #define LOG_TRACE() #define LOG_TRACE8() @@ -86,6 +86,13 @@ void debug_print_matrix4(const char* fmt, const char* fn, char* name, br_matrix4 stub_printed = 1; \ } +#define LOG_INFO2(a, b) LOG_INFO(a, b) +#define LOG_INFO3(a, b, c) LOG_INFO(a, b, c) +#define LOG_WARN2(a, b) LOG_WARN(a, b) +#define LOG_WARN3(a, b, c) LOG_WARN(a, b, c) +#define LOG_PANIC2(a, b) LOG_PANIC(a, b) +#define LOG_DEBUG2(a, b) LOG_DEBUG(a, b) + #endif #endif // ifdef diff --git a/src/harness/platforms/sdl1.c b/src/harness/platforms/sdl1.c index f730968d..4e3c491e 100644 --- a/src/harness/platforms/sdl1.c +++ b/src/harness/platforms/sdl1.c @@ -49,12 +49,11 @@ static const char* const possible_locations[] = { "libSDL-1.2.so", }; #endif -#endif -#ifdef DETHRACE_SDL_DYNAMIC static void* sdl1_so; #endif +#define SDL_NAME "SDL1" #define OBJECT_NAME sdl1_so #define SYMBOL_PREFIX SDL1_ #define FOREACH_SDLX_SYM FOREACH_SDL1_SYM diff --git a/src/harness/platforms/sdl2.c b/src/harness/platforms/sdl2.c index 4d8cc700..55c5df81 100644 --- a/src/harness/platforms/sdl2.c +++ b/src/harness/platforms/sdl2.c @@ -4,8 +4,8 @@ #include "harness/config.h" #include "harness/hooks.h" #include "harness/trace.h" +#include "sdl2_scancode_map.h" #include "sdl2_syms.h" -#include "sdl_scancode_map.h" SDL_COMPILE_TIME_ASSERT(sdl2_platform_requires_SDL2, SDL_MAJOR_VERSION == 2); @@ -21,7 +21,7 @@ static int render_width, render_height; static Uint32 last_frame_time; -void (*gKeyHandler_func)(void); +static void (*gKeyHandler_func)(void); // 32 bytes, 1 bit per key. Matches dos executable behavior static br_uint_32 key_state[8]; @@ -59,12 +59,11 @@ static const char* const possible_locations[] = { "libSDL2-2.0.so", }; #endif -#endif -#ifdef DETHRACE_SDL_DYNAMIC static void* sdl2_so; #endif +#define SDL_NAME "SDL2" #define OBJECT_NAME sdl2_so #define SYMBOL_PREFIX SDL2_ #define FOREACH_SDLX_SYM FOREACH_SDL2_SYM diff --git a/src/harness/platforms/sdl_scancode_map.h b/src/harness/platforms/sdl2_scancode_map.h similarity index 96% rename from src/harness/platforms/sdl_scancode_map.h rename to src/harness/platforms/sdl2_scancode_map.h index eae855c9..f8e1d087 100644 --- a/src/harness/platforms/sdl_scancode_map.h +++ b/src/harness/platforms/sdl2_scancode_map.h @@ -4,7 +4,9 @@ #include "dethrace_scancodes.h" #include -int sdl_scancode_map[SDL_NUM_SCANCODES] = { +SDL_COMPILE_TIME_ASSERT(sdl2_scancode_map_requires_SDL2, SDL_MAJOR_VERSION == 2); + +static int sdl_scancode_map[SDL_NUM_SCANCODES] = { [SDL_SCANCODE_ESCAPE] = SCANCODE_ESCAPE, [SDL_SCANCODE_1] = SCANCODE_1, [SDL_SCANCODE_2] = SCANCODE_2, diff --git a/src/harness/platforms/sdl2_syms.h b/src/harness/platforms/sdl2_syms.h index f6d8bc2b..1b056100 100644 --- a/src/harness/platforms/sdl2_syms.h +++ b/src/harness/platforms/sdl2_syms.h @@ -42,6 +42,4 @@ X(GetPrefPath, char*, (const char* org, const char* app)) \ X(free, void, (void*)) -#undef SDL2_SYM - #endif /* sdl2_syms_h */ diff --git a/src/harness/platforms/sdl3.c b/src/harness/platforms/sdl3.c new file mode 100644 index 00000000..9da79ccb --- /dev/null +++ b/src/harness/platforms/sdl3.c @@ -0,0 +1,426 @@ +#include + +#include "harness.h" +#include "harness/config.h" +#include "harness/hooks.h" +#include "harness/trace.h" +#include "sdl3_scancode_map.h" +#include "sdl3_syms.h" + +SDL_COMPILE_TIME_ASSERT(sdl3_platform_requires_SDL3, SDL_MAJOR_VERSION == 3); + +static SDL_Window* window; +static SDL_Renderer* renderer; +static SDL_Texture* screen_texture; +static uint32_t converted_palette[256]; +static br_pixelmap* last_screen_src; + +static SDL_GLContext gl_context; + +static int render_width, render_height; + +static Uint32 last_frame_time; + +static void (*gKeyHandler_func)(void); + +// 32 bytes, 1 bit per key. Matches dos executable behavior +static br_uint_32 key_state[8]; + +static struct { + int x, y; + float scale_x, scale_y; +} viewport; + +// Callbacks back into original game code +extern void QuitGame(void); +extern br_pixelmap* gBack_screen; + +#ifdef DETHRACE_SDL_DYNAMIC +#ifdef _WIN32 +static const char * const possible_locations[] = { + "SDL3.dll", +}; +#elif defined(__APPLE__) +#define SHARED_OBJECT_NAME "libSDL3" +#define SDL3_LIBNAME "libSDL3.dylib" +#define SDL3_FRAMEWORK "SDL3.framework/Versions/A/SDL3" +static const char * const possible_locations[] = { + "@loader_path/" SDL3_LIBNAME, /* MyApp.app/Contents/MacOS/libSDL3_dylib */ + "@loader_path/../Frameworks/" SDL3_FRAMEWORK, /* MyApp.app/Contents/Frameworks/SDL3_framework */ + "@executable_path/" SDL3_LIBNAME, /* MyApp.app/Contents/MacOS/libSDL3_dylib */ + "@executable_path/../Frameworks/" SDL3_FRAMEWORK, /* MyApp.app/Contents/Frameworks/SDL3_framework */ + NULL, /* /Users/username/Library/Frameworks/SDL3_framework */ + "/Library/Frameworks" SDL3_FRAMEWORK, /* /Library/Frameworks/SDL3_framework */ + SDL3_LIBNAME /* oh well, anywhere the system can see the .dylib (/usr/local/lib or whatever) */ +}; +#else +static const char * const possible_locations[] = { + "libSDL3.so.0", + "libSDL3.so", +}; +#endif + +static void *sdl3_so; +#endif + +#define SDL_NAME "SDL3" +#define OBJECT_NAME sdl3_so +#define SYMBOL_PREFIX SDL3_ +#define FOREACH_SDLX_SYM FOREACH_SDL3_SYM + +#include "sdl_dyn_common.h" + +static void calculate_viewport(int window_width, int window_height) { + int vp_width, vp_height; + float target_aspect_ratio; + float aspect_ratio; + + aspect_ratio = (float)window_width / window_height; + target_aspect_ratio = (float)gBack_screen->width / gBack_screen->height; + + vp_width = window_width; + vp_height = window_height; + if (aspect_ratio != target_aspect_ratio) { + if (aspect_ratio > target_aspect_ratio) { + vp_width = window_height * target_aspect_ratio + .5f; + } else { + vp_height = window_width / target_aspect_ratio + .5f; + } + } + viewport.x = (window_width - vp_width) / 2; + viewport.y = (window_height - vp_height) / 2; + viewport.scale_x = (float)vp_width / gBack_screen->width; + viewport.scale_y = (float)vp_height / gBack_screen->height; +} + +static int SDL3_Harness_SetWindowPos(void* hWnd, int x, int y, int nWidth, int nHeight) { + // SDL_SetWindowPosition(hWnd, x, y); + if (nWidth == 320 && nHeight == 200) { + nWidth = 640; + nHeight = 400; + } + SDL3_SetWindowSize(hWnd, nWidth, nHeight); + return 0; +} + +static void SDL3_Harness_DestroyWindow(void) { + // SDL3_GL_DeleteContext(context); + if (window != NULL) { + SDL3_DestroyWindow(window); + } + SDL3_Quit(); + window = NULL; +} + +// Checks whether the `flag_check` is the only modifier applied. +// e.g. is_only_modifier(event.key.keysym.mod, KMOD_ALT) returns true when only the ALT key was pressed +static int is_only_key_modifier(SDL_Keymod modifier_flags, SDL_Keymod flag_check) { + return (modifier_flags & flag_check) && (modifier_flags & (SDL_KMOD_CTRL | SDL_KMOD_SHIFT | SDL_KMOD_ALT | SDL_KMOD_GUI)) == (modifier_flags & flag_check); +} + +static void SDL3_Harness_ProcessWindowMessages(void) { + SDL_Event event; + + while (SDL3_PollEvent(&event)) { + switch (event.type) { + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + if (event.key.windowID != SDL3_GetWindowID(window)) { + continue; + } + if (event.key.key == SDLK_RETURN) { + if (event.key.type == SDL_EVENT_KEY_DOWN) { + if ((event.key.mod & (SDL_KMOD_CTRL | SDL_KMOD_SHIFT | SDL_KMOD_ALT | SDL_KMOD_GUI))) { + // Ignore keydown of RETURN when used together with some modifier + return; + } + } else if (event.key.type == SDL_EVENT_KEY_UP) { + if (is_only_key_modifier(event.key.mod, SDL_KMOD_ALT)) { + SDL3_SetWindowFullscreen(window, (SDL3_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) ? 0 : SDL_WINDOW_FULLSCREEN); + } + } + } + + // Map incoming SDL scancode to PC scan code as used by game code + int dethrace_scancode = sdl_scancode_map[event.key.scancode]; + if (dethrace_scancode == 0) { + LOG_WARN3("unexpected scan code %s (%d)", SDL3_GetScancodeName(event.key.scancode), event.key.scancode); + return; + } + + if (event.type == SDL_EVENT_KEY_DOWN) { + key_state[dethrace_scancode >> 5] |= (1 << (dethrace_scancode & 0x1F)); + } else { + key_state[dethrace_scancode >> 5] &= ~(1 << (dethrace_scancode & 0x1F)); + } + gKeyHandler_func(); + break; + + case SDL_EVENT_WINDOW_RESIZED: + calculate_viewport(event.window.data1, event.window.data2); + break; + + case SDL_EVENT_QUIT: + QuitGame(); + } + } +} + +static void SDL3_Harness_SetKeyHandler(void (*handler_func)(void)) { + gKeyHandler_func = handler_func; +} + +static void SDL3_Harness_GetKeyboardState(br_uint_32* buffer) { + memcpy(buffer, key_state, sizeof(key_state)); +} + +static int SDL3_Harness_GetMouseButtons(int* pButton1, int* pButton2) { + if (SDL3_GetMouseFocus() != window) { + *pButton1 = 0; + *pButton2 = 0; + return 0; + } + int state = SDL3_GetMouseState(NULL, NULL); + *pButton1 = state & SDL_BUTTON_LMASK; + *pButton2 = state & SDL_BUTTON_RMASK; + return 0; +} + +static int SDL3_Harness_GetMousePosition(int* pX, int* pY) { + int window_width, window_height; + float fWX, fWY; + float fX, fY; + + if (SDL3_GetMouseFocus() != window) { + return 0; + } + SDL3_GetWindowSize(window, &window_width, &window_height); + + SDL3_GetMouseState(&fWX, &fWY); + if (renderer != NULL) { + // software renderer + SDL3_RenderCoordinatesFromWindow(renderer, fWX, fWY, &fX, &fY); + } else { + // hardware renderer + // handle case where window is stretched larger than the pixel size + fX = fWX * (640.0f / window_width); + fY = fWY * (480.0f / window_height); + } + *pX = (int)fX; + *pY = (int)fY; + return 0; +} + +static void limit_fps(void) { + Uint32 now = SDL3_GetTicks(); + if (last_frame_time != 0) { + unsigned int frame_time = now - last_frame_time; + last_frame_time = now; + if (frame_time < 100) { + int sleep_time = (1000 / harness_game_config.fps) - frame_time; + if (sleep_time > 5) { + gHarness_platform.Sleep(sleep_time); + } + } + } + last_frame_time = SDL3_GetTicks(); +} + +static int SDL3_Harness_ShowErrorMessage(char* text, char* caption) { + fprintf(stderr, "%s", text); + SDL3_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, caption, text, window); + return 0; +} + +static void SDL3_Harness_CreateWindow(const char* title, int width, int height, tHarness_window_type window_type) { + int window_width, window_height; + + render_width = width; + render_height = height; + + window_width = width; + window_height = height; + + // special case lores and make a bigger window + if (width == 320 && height == 200) { + window_width = 640; + window_height = 480; + } + + if (!SDL3_Init(SDL_INIT_VIDEO)) { + LOG_PANIC2("SDL_INIT_VIDEO error: %s", SDL3_GetError()); + } + + if (window_type == eWindow_type_opengl) { + + window = SDL3_CreateWindow(title, + window_width, window_height, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + + if (window == NULL) { + LOG_PANIC2("Failed to create window: %s", SDL3_GetError()); + } + + SDL3_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + gl_context = SDL3_GL_CreateContext(window); + + if (gl_context == NULL) { + LOG_WARN2("Failed to create OpenGL core profile: %s. Trying OpenGLES...", SDL3_GetError()); + SDL3_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL3_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + gl_context = SDL3_GL_CreateContext(window); + } + if (gl_context == NULL) { + LOG_PANIC2("Failed to create OpenGL context: %s", SDL3_GetError()); + } + SDL3_GL_SetSwapInterval(1); + + } else { + window = SDL3_CreateWindow(title, + window_width, window_height, + SDL_WINDOW_RESIZABLE); + if (window == NULL) { + LOG_PANIC2("Failed to create window: %s", SDL3_GetError()); + } + + renderer = SDL3_CreateRenderer(window, NULL); + if (renderer == NULL) { + LOG_PANIC2("Failed to create renderer: %s", SDL3_GetError()); + } + SDL3_SetRenderVSync(renderer, 1); + SDL3_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + SDL3_SetRenderLogicalPresentation(renderer, render_width, render_height, SDL_LOGICAL_PRESENTATION_LETTERBOX); + + screen_texture = SDL3_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); + if (screen_texture == NULL) { + const SDL_PixelFormat *renderer_formats = NULL; + SDL_PropertiesID renderer_props = SDL3_GetRendererProperties(renderer); + if (renderer_props) { + renderer_formats = SDL3_GetPointerProperty(renderer_props, SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL); + if (renderer_formats) { + for (Uint32 i = 0; renderer_formats[i] != SDL_PIXELFORMAT_UNKNOWN; i++) { + LOG_INFO2("%s\n", SDL3_GetPixelFormatName(renderer_formats[i])); + } + } + } + LOG_PANIC2("Failed to create renderer texture (%s)", SDL3_GetError()); + } + } + + SDL3_ShowCursor(); + + viewport.x = 0; + viewport.y = 0; + viewport.scale_x = 1; + viewport.scale_y = 1; + + if (harness_game_config.start_full_screen) { + SDL3_SetWindowFullscreen(window, true); + } +} + +static void SDL3_Harness_Swap(br_pixelmap* back_buffer) { + + SDL3_Harness_ProcessWindowMessages(); + + if (gl_context != NULL) { + SDL3_GL_SwapWindow(window); + } else { + uint8_t* src_pixels = back_buffer->pixels; + uint32_t* dest_pixels; + int dest_pitch; + + SDL3_LockTexture(screen_texture, NULL, (void**)&dest_pixels, &dest_pitch); + for (int i = 0; i < back_buffer->height * back_buffer->width; i++) { + *dest_pixels = converted_palette[*src_pixels]; + dest_pixels++; + src_pixels++; + } + SDL3_UnlockTexture(screen_texture); + SDL3_RenderClear(renderer); + SDL3_RenderTexture(renderer, screen_texture, NULL, NULL); + SDL3_RenderPresent(renderer); + last_screen_src = back_buffer; + } + + if (harness_game_config.fps != 0) { + limit_fps(); + } +} + +static void SDL3_Harness_PaletteChanged(br_colour entries[256]) { + for (int i = 0; i < 256; i++) { + converted_palette[i] = (0xff << 24 | BR_RED(entries[i]) << 16 | BR_GRN(entries[i]) << 8 | BR_BLU(entries[i])); + } + if (last_screen_src != NULL) { + SDL3_Harness_Swap(last_screen_src); + } +} + +static void SDL3_Harness_GetViewport(int* x, int* y, float* width_multipler, float* height_multiplier) { + *x = viewport.x; + *y = viewport.y; + *width_multipler = viewport.scale_x; + *height_multiplier = viewport.scale_y; +} + +static void SDL3_Harness_GetPrefPath(char* path, char* app_name) { + char* sdl_path = SDL3_GetPrefPath(NULL, app_name); + if (sdl_path == NULL) { + LOG_PANIC("Failed to get preferences path (%s)", SDL3_GetError()); + } + strcpy(path, sdl_path); + SDL3_free(sdl_path); +} + +static uint32_t SDL3_Harness_GetTicks(void) { + return SDL3_GetTicks(); +} + +static int SDL3_Harness_ShowCursor(int show) { + if (show) { + SDL3_ShowCursor(); + } else { + SDL3_HideCursor(); + } + return 0; +} + +static void* SDL3_Harness_GL_GetProcAddress(const char* name) { + return SDL3_GL_GetProcAddress(name); +} + +static int SDL3_Harness_Platform_Init(tHarness_platform* platform) { + if (SDL3_LoadSymbols() != 0) { + return 1; + } + platform->ProcessWindowMessages = SDL3_Harness_ProcessWindowMessages; + platform->Sleep = SDL3_Delay; + platform->GetTicks = SDL3_Harness_GetTicks; + platform->ShowCursor = SDL3_Harness_ShowCursor; + platform->SetWindowPos = SDL3_Harness_SetWindowPos; + platform->DestroyWindow = SDL3_Harness_DestroyWindow; + platform->SetKeyHandler = SDL3_Harness_SetKeyHandler; + platform->GetKeyboardState = SDL3_Harness_GetKeyboardState; + platform->GetMousePosition = SDL3_Harness_GetMousePosition; + platform->GetMouseButtons = SDL3_Harness_GetMouseButtons; + platform->ShowErrorMessage = SDL3_Harness_ShowErrorMessage; + + platform->CreateWindow_ = SDL3_Harness_CreateWindow; + platform->Swap = SDL3_Harness_Swap; + platform->PaletteChanged = SDL3_Harness_PaletteChanged; + platform->GL_GetProcAddress = SDL3_Harness_GL_GetProcAddress; + platform->GetViewport = SDL3_Harness_GetViewport; + platform->GetPrefPath = SDL3_Harness_GetPrefPath; + return 0; +}; + +const tPlatform_bootstrap SDL3_bootstrap = { + "sdl3", + "SDL3 video backend (libsdl.org)", + ePlatform_cap_software | ePlatform_cap_opengl, + SDL3_Harness_Platform_Init, +}; diff --git a/src/harness/platforms/sdl3_scancode_map.h b/src/harness/platforms/sdl3_scancode_map.h new file mode 100644 index 00000000..47358437 --- /dev/null +++ b/src/harness/platforms/sdl3_scancode_map.h @@ -0,0 +1,113 @@ +#ifndef SDL_SCANCODE_MAP_H +#define SDL_SCANCODE_MAP_H + +#include "dethrace_scancodes.h" +#include + +SDL_COMPILE_TIME_ASSERT(sdl2_scancode_map_requires_SDL3, SDL_MAJOR_VERSION == 3); + +static int sdl_scancode_map[SDL_SCANCODE_COUNT] = { + [SDL_SCANCODE_ESCAPE] = SCANCODE_ESCAPE, + [SDL_SCANCODE_1] = SCANCODE_1, + [SDL_SCANCODE_2] = SCANCODE_2, + [SDL_SCANCODE_3] = SCANCODE_3, + [SDL_SCANCODE_4] = SCANCODE_4, + [SDL_SCANCODE_5] = SCANCODE_5, + [SDL_SCANCODE_6] = SCANCODE_6, + [SDL_SCANCODE_7] = SCANCODE_7, + [SDL_SCANCODE_8] = SCANCODE_8, + [SDL_SCANCODE_9] = SCANCODE_9, + [SDL_SCANCODE_0] = SCANCODE_0, + [SDL_SCANCODE_MINUS] = SCANCODE_MINUS, + [SDL_SCANCODE_EQUALS] = SCANCODE_EQUALS, + [SDL_SCANCODE_BACKSPACE] = SCANCODE_BACK, + [SDL_SCANCODE_TAB] = SCANCODE_TAB, + [SDL_SCANCODE_Q] = SCANCODE_Q, + [SDL_SCANCODE_W] = SCANCODE_W, + [SDL_SCANCODE_E] = SCANCODE_E, + [SDL_SCANCODE_R] = SCANCODE_R, + [SDL_SCANCODE_T] = SCANCODE_T, + [SDL_SCANCODE_Y] = SCANCODE_Y, + [SDL_SCANCODE_U] = SCANCODE_U, + [SDL_SCANCODE_I] = SCANCODE_I, + [SDL_SCANCODE_O] = SCANCODE_O, + [SDL_SCANCODE_P] = SCANCODE_P, + [SDL_SCANCODE_LEFTBRACKET] = SCANCODE_LBRACKET, + [SDL_SCANCODE_RIGHTBRACKET] = SCANCODE_RBRACKET, + [SDL_SCANCODE_RETURN] = SCANCODE_RETURN, + [SDL_SCANCODE_LCTRL] = SCANCODE_LCONTROL, + [SDL_SCANCODE_A] = SCANCODE_A, + [SDL_SCANCODE_S] = SCANCODE_S, + [SDL_SCANCODE_D] = SCANCODE_D, + [SDL_SCANCODE_F] = SCANCODE_F, + [SDL_SCANCODE_G] = SCANCODE_G, + [SDL_SCANCODE_H] = SCANCODE_H, + [SDL_SCANCODE_J] = SCANCODE_J, + [SDL_SCANCODE_K] = SCANCODE_K, + [SDL_SCANCODE_L] = SCANCODE_L, + [SDL_SCANCODE_SEMICOLON] = SCANCODE_SEMICOLON, + [SDL_SCANCODE_APOSTROPHE] = SCANCODE_APOSTROPHE, + [SDL_SCANCODE_GRAVE] = SCANCODE_GRAVE, + [SDL_SCANCODE_LSHIFT] = SCANCODE_LSHIFT, + [SDL_SCANCODE_BACKSLASH] = SCANCODE_BACKSLASH, + [SDL_SCANCODE_Z] = SCANCODE_Z, + [SDL_SCANCODE_X] = SCANCODE_X, + [SDL_SCANCODE_C] = SCANCODE_C, + [SDL_SCANCODE_V] = SCANCODE_V, + [SDL_SCANCODE_B] = SCANCODE_B, + [SDL_SCANCODE_N] = SCANCODE_N, + [SDL_SCANCODE_M] = SCANCODE_M, + [SDL_SCANCODE_COMMA] = SCANCODE_COMMA, + [SDL_SCANCODE_PERIOD] = SCANCODE_PERIOD, + [SDL_SCANCODE_SLASH] = SCANCODE_SLASH, + [SDL_SCANCODE_RSHIFT] = SCANCODE_RSHIFT, + [SDL_SCANCODE_KP_MULTIPLY] = SCANCODE_MULTIPLY, + [SDL_SCANCODE_LALT] = SCANCODE_LALT, + [SDL_SCANCODE_SPACE] = SCANCODE_SPACE, + [SDL_SCANCODE_CAPSLOCK] = SCANCODE_CAPITAL, + [SDL_SCANCODE_F1] = SCANCODE_F1, + [SDL_SCANCODE_F2] = SCANCODE_F2, + [SDL_SCANCODE_F3] = SCANCODE_F3, + [SDL_SCANCODE_F4] = SCANCODE_F4, + [SDL_SCANCODE_F5] = SCANCODE_F5, + [SDL_SCANCODE_F6] = SCANCODE_F6, + [SDL_SCANCODE_F7] = SCANCODE_F7, + [SDL_SCANCODE_F8] = SCANCODE_F8, + [SDL_SCANCODE_F9] = SCANCODE_F9, + [SDL_SCANCODE_F10] = SCANCODE_F10, + [SDL_SCANCODE_NUMLOCKCLEAR] = SCANCODE_NUMLOCK, + [SDL_SCANCODE_SCROLLLOCK] = SCANCODE_SCROLL, + [SDL_SCANCODE_KP_7] = SCANCODE_NUMPAD7, + [SDL_SCANCODE_KP_8] = SCANCODE_NUMPAD8, + [SDL_SCANCODE_KP_9] = SCANCODE_NUMPAD9, + [SDL_SCANCODE_KP_MINUS] = SCANCODE_SUBTRACT, + [SDL_SCANCODE_KP_4] = SCANCODE_NUMPAD4, + [SDL_SCANCODE_KP_5] = SCANCODE_NUMPAD5, + [SDL_SCANCODE_KP_6] = SCANCODE_NUMPAD6, + [SDL_SCANCODE_KP_PLUS] = SCANCODE_ADD, + [SDL_SCANCODE_KP_1] = SCANCODE_NUMPAD1, + [SDL_SCANCODE_KP_2] = SCANCODE_NUMPAD2, + [SDL_SCANCODE_KP_3] = SCANCODE_NUMPAD3, + [SDL_SCANCODE_KP_0] = SCANCODE_NUMPAD0, + [SDL_SCANCODE_KP_PERIOD] = SCANCODE_DECIMAL, + [SDL_SCANCODE_NONUSBACKSLASH] = SCANCODE_OEM_102, + [SDL_SCANCODE_F11] = SCANCODE_F11, + [SDL_SCANCODE_F12] = SCANCODE_F12, + [SDL_SCANCODE_KP_ENTER] = SCANCODE_NUMPADENTER, + [SDL_SCANCODE_RCTRL] = SCANCODE_RCONTROL, + [SDL_SCANCODE_KP_DIVIDE] = SCANCODE_DIVIDE, + [SDL_SCANCODE_RALT] = SCANCODE_RALT, + [SDL_SCANCODE_PAUSE] = SCANCODE_PAUSE, + [SDL_SCANCODE_HOME] = SCANCODE_HOME, + [SDL_SCANCODE_UP] = SCANCODE_UP, + [SDL_SCANCODE_PAGEUP] = SCANCODE_PGUP, + [SDL_SCANCODE_LEFT] = SCANCODE_LEFT, + [SDL_SCANCODE_RIGHT] = SCANCODE_RIGHT, + [SDL_SCANCODE_END] = SCANCODE_END, + [SDL_SCANCODE_DOWN] = SCANCODE_DOWN, + [SDL_SCANCODE_PAGEDOWN] = SCANCODE_PGDN, + [SDL_SCANCODE_INSERT] = SCANCODE_INSERT, + [SDL_SCANCODE_DELETE] = SCANCODE_DELETE, +}; + +#endif /* _SDL2_SCANCODE_TO_DINPUT_H_ */ diff --git a/src/harness/platforms/sdl3_syms.h b/src/harness/platforms/sdl3_syms.h new file mode 100644 index 00000000..523a38a2 --- /dev/null +++ b/src/harness/platforms/sdl3_syms.h @@ -0,0 +1,49 @@ +#ifndef sdl3_syms_h +#define sdl3_syms_h + +#include + +#define FOREACH_SDL3_SYM(X) \ + X(free, void, (void *)) \ + X(Init, bool, (Uint32)) \ + X(Quit, void, (void)) \ + X(Delay, void, (Uint32)) \ + X(GetTicks, Uint64, (void)) \ + X(GetError, const char*, (void)) \ + X(GetPointerProperty, void*, (SDL_PropertiesID, const char*, void*)) \ + X(PollEvent, bool, (SDL_Event*)) \ + X(ShowSimpleMessageBox, bool, (SDL_MessageBoxFlags flags, const char*, const char *, SDL_Window*)) \ + X(CreateWindow, SDL_Window*, (const char*, int, int, SDL_WindowFlags)) \ + X(DestroyWindow, void, (SDL_Window*)) \ + X(GetWindowFlags, SDL_WindowFlags, (SDL_Window*)) \ + X(GetPrefPath, char*, (const char *, const char *)) \ + X(GetWindowID, SDL_WindowID, (SDL_Window*)) \ + X(GetWindowSize, bool, (SDL_Window*, int*, int*)) \ + X(HideCursor, bool, (void)) \ + X(SetWindowFullscreen, bool, (SDL_Window*, bool)) \ + X(SetWindowSize, bool, (SDL_Window*, int, int)) \ + X(CreateRenderer, SDL_Renderer*, (SDL_Window*, const char*)) \ + X(RenderClear, bool, (SDL_Renderer*)) \ + X(RenderTexture, bool, (SDL_Renderer*, SDL_Texture*, const SDL_FRect*, const SDL_FRect*)) \ + X(RenderPresent, bool, (SDL_Renderer*)) \ + X(RenderCoordinatesFromWindow, bool, (SDL_Renderer*, float, float, float*, float*)) \ + X(GetRendererName, const char*, (SDL_Renderer*)) \ + X(GetRendererProperties, SDL_PropertiesID, (SDL_Renderer*)) \ + X(SetRenderLogicalPresentation, bool, (SDL_Renderer*, int, int, SDL_RendererLogicalPresentation)) \ + X(SetRenderDrawBlendMode, bool, (SDL_Renderer*, SDL_BlendMode)) \ + X(SetRenderVSync, bool, (SDL_Renderer*, int)) \ + X(CreateTexture, SDL_Texture*, (SDL_Renderer*, SDL_PixelFormat, SDL_TextureAccess, int, int)) \ + X(LockTexture, bool, (SDL_Texture*, const SDL_Rect*, void**, int*)) \ + X(UnlockTexture, void, (SDL_Texture*)) \ + X(GetMouseFocus, SDL_Window*, (void)) \ + X(GetMouseState, SDL_MouseButtonFlags, (float*, float*)) \ + X(ShowCursor, bool, (void)) \ + X(GetPixelFormatName, const char*, (SDL_PixelFormat)) \ + X(GetScancodeName, const char *, (SDL_Scancode)) \ + X(GL_CreateContext, SDL_GLContext, (SDL_Window*)) \ + X(GL_GetProcAddress, SDL_FunctionPointer, (const char*)) \ + X(GL_SetAttribute, bool, (SDL_GLAttr, int)) \ + X(GL_SetSwapInterval, bool, (int)) \ + X(GL_SwapWindow, bool, (SDL_Window*)) + +#endif /* sdl3_syms_h */ diff --git a/src/harness/platforms/sdl_dyn_common.h b/src/harness/platforms/sdl_dyn_common.h index e5a77b8d..8586fd46 100644 --- a/src/harness/platforms/sdl_dyn_common.h +++ b/src/harness/platforms/sdl_dyn_common.h @@ -16,6 +16,21 @@ static void Harness_UnloadObject(void *obj) { static void *Harness_LoadFunction(void *obj, const char *name) { return GetProcAddress(obj, name); } +static const char *Harness_LoadError(void) { + static char buffer[512]; + DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + sizeof(buffer)-1, + NULL); + if (cchMsg == 0) { + strncpy(buffer, "GetProcAddress failed", sizeof(buffer)); + buffer[sizeof(buffer)-1] = '\0'; + } + return buffer; +} #else #include static void *Harness_LoadObject(const char *name) { @@ -27,6 +42,9 @@ static void Harness_UnloadObject(void *obj) { static void *Harness_LoadFunction(void *obj, const char *name) { return dlsym(obj, name); } +static const char *Harness_LoadError(void) { + return dlerror(); +} #endif #endif @@ -38,7 +56,8 @@ static void *Harness_LoadFunction(void *obj, const char *name) { #ifdef DETHRACE_SDL_DYNAMIC #define X_LOAD_FUNCTION(name, ret, args) \ STR_JOIN(SYMBOL_PREFIX, name) = Harness_LoadFunction(OBJECT_NAME, "SDL_" #name); \ - if (STR_JOIN(SYMBOL_PREFIX, name) == NULL) { \ + if (STR_JOIN(SYMBOL_PREFIX, name) == NULL) { \ + fprintf(stderr, "Failed to load %s function: %s (%s)\n", SDL_NAME, "SDL_" #name, Harness_LoadError()); \ goto failure; \ } #else @@ -57,6 +76,7 @@ static int STR_JOIN(SYMBOL_PREFIX,LoadSymbols)(void) { } } if (OBJECT_NAME == NULL) { + fputs("Could not find " SDL_NAME " library\n", stderr); return 1; } #endif