'OHRRPGCE - SDL 2 graphics backend '(C) Copyright 1997-2023 James Paige, Ralph Versteegen, and the OHRRPGCE Developers 'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability. #include "config.bi" #ifdef __FB_WIN32__ 'In FB >= 1.04 SDL.bi includes windows.bi; we have to include it first to do the necessary conflict prevention include_windows_bi() #endif #include "crt.bi" #include "gfx.bi" #include "surface.bi" #include "common.bi" #include "allmodex.bi" 'For set_scale_factor #include "unicode.bi" #include "scancodes.bi" #include "backendinfo.bi" '#define NEED_SDL_GETENV #ifdef __FB_UNIX__ 'In FB >= 1.04 SDL.bi includes Xlib.bi; fix a conflict #undef font #endif #include "SDL2\SDL.bi" #ifdef __FB_BLACKBOX__ 'Always run at 1x zoom, ignoring all resize requests (Blackbox handles the scaling) #define NO_ZOOM #endif EXTERN "C" #define KMOD_META KMOD_GUI 'Renamed in SDL2 'Older FB releases (before 1.06?) only have headers for SDL 2.0.3 (Mar 2014). Declaring these is 'simpler than requiring a recent FB. Whether they're in FB's headers doesn't tell whether they're on 'the system #ifndef SDL_GameControllerFromInstanceID 'SDL 2.0.4+ (Jan 2016) declare function SDL_GameControllerFromInstanceID(byval joyid as SDL_JoystickID) as SDL_GameController ptr #endif #ifndef SDL_CaptureMouse 'SDL 2.0.4+ declare function SDL_CaptureMouse(byval enabled as SDL_bool) as long declare function SDL_GetGlobalMouseState(byval x as long ptr, byval y as long ptr) as Uint32 declare function SDL_WarpMouseGlobal(byval x as long, byval y as long) as long #endif #ifndef SDL_GetDisplayUsableBounds 'SDL 2.0.5+ (Oct 2016) declare function SDL_GetDisplayUsableBounds(byval displayIndex as long, byval rect as SDL_Rect ptr) as long #endif #ifndef SDL_RenderSetIntegerScale 'SDL 2.0.5+ declare function SDL_RenderSetIntegerScale(byval renderer as SDL_Renderer ptr, byval enable as SDL_bool) as long #endif #ifndef SDL_SetWindowResizable 'SDL 2.0.5+ declare sub SDL_SetWindowResizable(byval window as SDL_Window ptr, byval resizable as SDL_bool) #endif #ifndef SDL_JoystickGetDeviceInstanceID 'SDL 2.0.6+ (Sept 2017). Not used declare function SDL_JoystickGetDeviceInstanceID(byval device_index as long) as SDL_JoystickID #endif 'Dynamically loaded functions 'SDL 2.0.12+ #ifndef SDL_GameControllerType type SDL_GameControllerType as long #endif #undef SDL_GameControllerTypeForIndex dim shared SDL_GameControllerTypeForIndex as function(byval joystick_index as long) as SDL_GameControllerType 'SDL 2.0.12+ '#undef SDL_SetTextureScaleMode 'dim shared SDL_SetTextureScaleMode as function(byval texture as SDL_Texture ptr, byval scaleMode as SDL_ScaleMode) as long #IFDEF __FB_ANDROID__ 'This function shows/hides the sdl virtual gamepad declare sub SDL_ANDROID_SetScreenKeyboardShown (byval shown as integer) 'This function toggles the display of the android virtual keyboard. always returns 1 no matter what declare function SDL_ANDROID_ToggleScreenKeyboardWithoutTextInput() as integer 'WARNING: SDL_ANDROID_IsScreenKeyboardShown seems unreliable. Don't use it! It is only declared here to document its existance. see the virtual_keyboard_shown variable instead declare function SDL_ANDROID_IsScreenKeyboardShown() as bool declare function SDL_ANDROID_IsRunningOnConsole () as bool declare function SDL_ANDROID_IsRunningOnOUYA () as bool declare sub SDL_ANDROID_set_java_gamepad_keymap(byval A as integer, byval B as integer, byval C as integer, byval X as integer, byval Y as integer, byval Z as integer, byval L1 as integer, byval R1 as integer, byval L2 as integer, byval R2 as integer, byval LT as integer, byval RT as integer) declare sub SDL_ANDROID_set_ouya_gamepad_keymap(byval player as integer, byval udpad as integer, byval rdpad as integer, byval ldpad as integer, byval ddpad as integer, byval O as integer, byval A as integer, byval U as integer, byval Y as integer, byval L1 as integer, byval R1 as integer, byval L2 as integer, byval R2 as integer, byval LT as integer, byval RT as integer) declare function SDL_ANDROID_SetScreenKeyboardButtonKey(byval buttonId as integer, byval key as integer) as integer declare function SDL_ANDROID_SetScreenKeyboardButtonDisable(byval buttonId as integer, byval disable as bool) as integer declare sub SDL_ANDROID_SetOUYADeveloperId (byval devId as zstring ptr) declare sub SDL_ANDROID_OUYAPurchaseRequest (byval identifier as zstring ptr, byval keyDer as zstring ptr, byval keyDerSize as integer) declare function SDL_ANDROID_OUYAPurchaseIsReady () as bool declare function SDL_ANDROID_OUYAPurchaseSucceeded () as bool declare sub SDL_ANDROID_OUYAReceiptsRequest (byval keyDer as zstring ptr, byval keyDerSize as integer) declare function SDL_ANDROID_OUYAReceiptsAreReady () as bool declare function SDL_ANDROID_OUYAReceiptsResult () as zstring ptr #ENDIF DECLARE FUNCTION recreate_window(byval bitdepth as integer = 0) as bool DECLARE FUNCTION recreate_screen_texture() as bool DECLARE FUNCTION screen_buffer_size() as XYPair DECLARE SUB set_viewport(for_windowed as bool) DECLARE FUNCTION windowsize_to_resolution(byval wsize as XYPair) as XYPair DECLARE FUNCTION windowsize_to_ratio(byval windowsz as XYPair) as double DECLARE FUNCTION gfx_sdl2_set_resizable(enable as bool, min_width as integer, min_height as integer) as bool DECLARE FUNCTION present_internal2(srcsurf as SDL_Surface ptr, raw as any ptr, imagesz as XYPair, pitch as integer, bitdepth as integer) as bool DECLARE SUB update_state() DECLARE FUNCTION update_mouse() as integer DECLARE SUB update_mouse_visibility() DECLARE SUB set_forced_mouse_clipping(byval newvalue as bool) DECLARE SUB update_mouserect() DECLARE SUB internal_disable_virtual_gamepad() DECLARE FUNCTION scOHR2SDL(byval ohr_scancode as KBScancode, byval default_sdl_scancode as integer=0) as integer DECLARE SUB log_error(failed_call as zstring ptr, funcname as zstring ptr) #define CheckOK(condition, otherwise...) IF condition THEN log_error(#condition, __FUNCTION__) : otherwise #ifdef NO_ZOOM CONST zoom = 1 #else DIM SHARED zoom as double = 2 '(Average) size of a pixel #endif DIM SHARED smooth as integer = 0 'Upscaler to use: 0 (nearest-neighbour) or 1 (smooth) DIM SHARED upscaler_zoom as integer = 2 'Amount of upscaler zoom, before stretching result to the window DIM SHARED bilinear as bool = NO 'Use bilinear smoothing to stretch to window DIM SHARED mainwindow as SDL_Window ptr = NULL DIM SHARED mainrenderer as SDL_Renderer ptr = NULL DIM SHARED maintexture as SDL_Texture ptr = NULL 'Aka the screen buffer DIM SHARED screenbuffer as SDL_Surface ptr = NULL DIM SHARED last_bitdepth as integer 'Bitdepth of the last gfx_present call DIM SHARED windowedmode as bool = YES 'Windowed rather than fullscreen? (Should we trust this, or call SDL_GetWindowFlags?) DIM SHARED resizable_window as bool = YES '(Always true!) Allow user to change the window size, changing either the resolution or the scaling. DIM SHARED resizable_resolution as bool = NO 'Adjust resolution when the window size changes, rather than the scaling DIM SHARED resize_requested as bool = NO 'The window size has changed (usually by WM or gfx_set_window_size) but gfx_get_resize hasn't been called yet DIM SHARED resize_pending as bool = NO 'gfx_get_resize called after a resize, but gfx_present hasn't been called yet DIM SHARED resize_request as XYPair DIM SHARED min_window_resolution as XYPair = XY(10, 10) 'Used only if 'resizable_resolution' true. Excludes zoom factor. DIM SHARED remember_window_size as XYPair 'Remembered size before fullscreening DIM SHARED recenter_window_hint as bool = NO DIM SHARED remember_windowtitle as string DIM SHARED mouse_visibility as CursorVisibility = cursorDefault DIM SHARED sdlpalette as SDL_Palette ptr DIM SHARED framesize as XYPair = (320, 200) 'Size of the unscaled image DIM SHARED mouseclipped as bool = NO 'Whether we are ACTUALLY clipped DIM SHARED forced_mouse_clipping as bool = NO DIM SHARED remember_mouserect as RectPoints = ((-1, -1), (-1, -1)) 'Args at the last call to io_mouserect DIM SHARED mousebounds as RectPoints = ((-1, -1), (-1, -1)) 'These are the actual clip bounds, in window coords DIM SHARED privatempos as XYPair 'Mouse position in window coords DIM SHARED keybdstate(127) as KeyBits '"real"time keyboard array. See io_sdl2_keybits for docs. DIM SHARED input_buffer as ustring DIM SHARED mouseclicks as integer 'Bitmask of mouse buttons clicked (SDL order, not OHR), since last io_mousebits DIM SHARED mousewheel as integer 'Position of the wheel. A multiple of 120 DIM SHARED virtual_keyboard_shown as bool = NO DIM SHARED allow_virtual_gamepad as bool = YES DIM SHARED safe_zone_margin as single = 0.0 DIM SHARED libsdl_handle as any ptr #define USE_SDL2 #include "gfx_sdl_common.bi" END EXTERN ' Can't put assignment statements in an extern block 'Translate SDL scancodes into a OHR scancodes 'Of course, scancodes can only be correctly mapped to OHR scancodes on a US keyboard. 'SDL scancodes say what's the unmodified character on a key. For example 'on a German keyboard the +/*/~ key is SDLK_PLUS, gets mapped to 'scPlus, which is the same as scEquals, so you get = when you press 'it. 'If there is no ASCII equivalent character, the key has a SDLK_WORLD_## scancode. DIM SHARED scantrans(0 to SDL_NUM_SCANCODES) as KBScancode scantrans(SDL_SCANCODE_UNKNOWN) = 0 scantrans(SDL_SCANCODE_BACKSPACE) = scBackspace scantrans(SDL_SCANCODE_TAB) = scTab scantrans(SDL_SCANCODE_CLEAR) = 0 scantrans(SDL_SCANCODE_RETURN) = scEnter scantrans(SDL_SCANCODE_PAUSE) = scPause scantrans(SDL_SCANCODE_ESCAPE) = scEsc scantrans(SDL_SCANCODE_SPACE) = scSpace scantrans(SDL_SCANCODE_APOSTROPHE) = scQuote scantrans(SDL_SCANCODE_COMMA) = scComma scantrans(SDL_SCANCODE_PERIOD) = scPeriod scantrans(SDL_SCANCODE_SLASH) = scSlash scantrans(SDL_SCANCODE_0) = sc0 scantrans(SDL_SCANCODE_1) = sc1 scantrans(SDL_SCANCODE_2) = sc2 scantrans(SDL_SCANCODE_3) = sc3 scantrans(SDL_SCANCODE_4) = sc4 scantrans(SDL_SCANCODE_5) = sc5 scantrans(SDL_SCANCODE_6) = sc6 scantrans(SDL_SCANCODE_7) = sc7 scantrans(SDL_SCANCODE_8) = sc8 scantrans(SDL_SCANCODE_9) = sc9 scantrans(SDL_SCANCODE_SEMICOLON) = scSemicolon scantrans(SDL_SCANCODE_EQUALS) = scEquals scantrans(SDL_SCANCODE_LEFTBRACKET) = scLeftBracket scantrans(SDL_SCANCODE_BACKSLASH) = scBackslash scantrans(SDL_SCANCODE_RIGHTBRACKET) = scRightBracket scantrans(SDL_SCANCODE_MINUS) = scMinus scantrans(SDL_SCANCODE_GRAVE) = scBackquote scantrans(SDL_SCANCODE_a) = scA scantrans(SDL_SCANCODE_b) = scB scantrans(SDL_SCANCODE_c) = scC scantrans(SDL_SCANCODE_d) = scD scantrans(SDL_SCANCODE_e) = scE scantrans(SDL_SCANCODE_f) = scF scantrans(SDL_SCANCODE_g) = scG scantrans(SDL_SCANCODE_h) = scH scantrans(SDL_SCANCODE_i) = scI scantrans(SDL_SCANCODE_j) = scJ scantrans(SDL_SCANCODE_k) = scK scantrans(SDL_SCANCODE_l) = scL scantrans(SDL_SCANCODE_m) = scM scantrans(SDL_SCANCODE_n) = scN scantrans(SDL_SCANCODE_o) = scO scantrans(SDL_SCANCODE_p) = scP scantrans(SDL_SCANCODE_q) = scQ scantrans(SDL_SCANCODE_r) = scR scantrans(SDL_SCANCODE_s) = scS scantrans(SDL_SCANCODE_t) = scT scantrans(SDL_SCANCODE_u) = scU scantrans(SDL_SCANCODE_v) = scV scantrans(SDL_SCANCODE_w) = scW scantrans(SDL_SCANCODE_x) = scX scantrans(SDL_SCANCODE_y) = scY scantrans(SDL_SCANCODE_z) = scZ scantrans(SDL_SCANCODE_DELETE) = scDelete scantrans(SDL_SCANCODE_KP_0) = scNumpad0 scantrans(SDL_SCANCODE_KP_1) = scNumpad1 scantrans(SDL_SCANCODE_KP_2) = scNumpad2 scantrans(SDL_SCANCODE_KP_3) = scNumpad3 scantrans(SDL_SCANCODE_KP_4) = scNumpad4 scantrans(SDL_SCANCODE_KP_5) = scNumpad5 scantrans(SDL_SCANCODE_KP_6) = scNumpad6 scantrans(SDL_SCANCODE_KP_7) = scNumpad7 scantrans(SDL_SCANCODE_KP_8) = scNumpad8 scantrans(SDL_SCANCODE_KP_9) = scNumpad9 scantrans(SDL_SCANCODE_KP_PERIOD) = scNumpadPeriod scantrans(SDL_SCANCODE_KP_DIVIDE) = scNumpadSlash scantrans(SDL_SCANCODE_KP_MULTIPLY) = scNumpadAsterisk scantrans(SDL_SCANCODE_KP_MINUS) = scNumpadMinus scantrans(SDL_SCANCODE_KP_PLUS) = scNumpadPlus scantrans(SDL_SCANCODE_KP_ENTER) = scNumpadEnter scantrans(SDL_SCANCODE_KP_EQUALS) = scEquals scantrans(SDL_SCANCODE_UP) = scUp scantrans(SDL_SCANCODE_DOWN) = scDown scantrans(SDL_SCANCODE_RIGHT) = scRight scantrans(SDL_SCANCODE_LEFT) = scLeft scantrans(SDL_SCANCODE_INSERT) = scInsert scantrans(SDL_SCANCODE_HOME) = scHome scantrans(SDL_SCANCODE_END) = scEnd scantrans(SDL_SCANCODE_PAGEUP) = scPageup scantrans(SDL_SCANCODE_PAGEDOWN) = scPagedown scantrans(SDL_SCANCODE_F1) = scF1 scantrans(SDL_SCANCODE_F2) = scF2 scantrans(SDL_SCANCODE_F3) = scF3 scantrans(SDL_SCANCODE_F4) = scF4 scantrans(SDL_SCANCODE_F5) = scF5 scantrans(SDL_SCANCODE_F6) = scF6 scantrans(SDL_SCANCODE_F7) = scF7 scantrans(SDL_SCANCODE_F8) = scF8 scantrans(SDL_SCANCODE_F9) = scF9 scantrans(SDL_SCANCODE_F10) = scF10 scantrans(SDL_SCANCODE_F11) = scF11 scantrans(SDL_SCANCODE_F12) = scF12 scantrans(SDL_SCANCODE_F13) = scF13 scantrans(SDL_SCANCODE_F14) = scF14 scantrans(SDL_SCANCODE_F15) = scF15 scantrans(SDL_SCANCODE_NUMLOCKCLEAR) = scNumlock 'Clear key on Macs scantrans(SDL_SCANCODE_CAPSLOCK) = scCapslock scantrans(SDL_SCANCODE_SCROLLLOCK) = scScrollLock scantrans(SDL_SCANCODE_RSHIFT) = scRightShift scantrans(SDL_SCANCODE_LSHIFT) = scLeftShift scantrans(SDL_SCANCODE_RCTRL) = scRightCtrl scantrans(SDL_SCANCODE_LCTRL) = scLeftCtrl scantrans(SDL_SCANCODE_RALT) = scRightAlt scantrans(SDL_SCANCODE_LALT) = scLeftAlt scantrans(SDL_SCANCODE_RGUI) = scRightMeta scantrans(SDL_SCANCODE_LGUI) = scLeftMeta scantrans(SDL_SCANCODE_MODE) = scRightAlt 'Possibly (probably not) Alt Gr? So treat it as alt scantrans(SDL_SCANCODE_HELP) = 0 scantrans(SDL_SCANCODE_PRINTSCREEN) = scPrintScreen scantrans(SDL_SCANCODE_SYSREQ) = scPrintScreen scantrans(SDL_SCANCODE_PAUSE) = scPause scantrans(SDL_SCANCODE_MENU) = scContext scantrans(SDL_SCANCODE_APPLICATION) = scContext scantrans(SDL_SCANCODE_POWER) = 0 scantrans(SDL_SCANCODE_UNDO) = 0 EXTERN "C" PRIVATE SUB log_error(failed_call as zstring ptr, funcname as zstring ptr) debugerror *funcname & " " & *failed_call & ": " & *SDL_GetError() END SUB #MACRO TRYLOAD(procedure) IF hfile THEN procedure = dylibsymbol(hfile, #procedure) ELSE procedure = NULL END IF #ENDMACRO 'Load pointers to optional SDL functions, to support a range of SDL versions LOCAL SUB load_SDL_syms() IF libsdl_handle = NULL THEN 'Especially on Linux must make sure we don't load a different (system) .so 'to the one we're linked to (possibly a library in linux/$arch/) libsdl_handle = dylib_noload(libsdl2_name) 'Dynamic loading is only used for optional functions, so if the load failed we can continue IF libsdl_handle = NULL THEN debug "dylib_noload(" & libsdl2_name & ") failed. Continuing" END IF END IF DIM hFile as any ptr = libsdl_handle TRYLOAD(SDL_GameControllerTypeForIndex) END SUB FUNCTION gfx_sdl2_init(byval terminate_signal_handler as sub cdecl (), byval windowicon as zstring ptr, byval info_buffer as zstring ptr, byval info_buffer_size as integer) as integer #ifdef USE_X11 'Xlib will kill the program if most errors occur, such as if OpenGL on the machine is broken 'so the window can't be created. We need to install an error handler to prevent that set_X11_error_handlers #endif load_SDL_syms #ifdef __FB_JS__ SDL_SetHint(SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT, "#canvas") #endif 'Not needed, seems to work without 'SDL_SetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON, windowicon) #ifndef IS_GAME 'By default SDL prevents the screensaver (new in SDL 2.0.2) SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1") #endif SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest") 'By default SDL disables VM compositing in order to allow higher framerates, but this is causes problems under KWin (KDE). (SDL 2.0.8+) SDL_SetHint("SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR", "0") 'We don't need shaders 'NOTE: commented out, because this caused the window to be stuck white under X11. It used to work fine in 'SDL 2.0.10 but is broken in SDL 2.0.12. 'SDL_SetHint(SDL_HINT_RENDER_OPENGL_SHADERS, "0") 'I guess this sets the render driver? 'SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "0") 'software, opengl, direct3d, opengles2, opengles, metal 'Maybe want to set SDL_HINT_VIDEO_X11_NET_WM_PING off, since we detect hung scripts ourselves? 'Make Ctrl-click on Mac send a right-click event SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1") 'IMEs should provide their own UIs for inputting characters, as we don't handle SDL_TEXTEDITING events SDL_SetHint("SDL_IME_INTERNAL_EDITING", "1") 'SDL_HINT_IME_INTERNAL_EDITING not in old FB headers 'Return key on on-screen keyboard acts as 'done' SDL_SetHint("SDL_RETURN_KEY_HIDES_IME", "1") 'SDL_HINT_RETURN_KEY_HIDES_IME not in FB's header yet 'Don't minimise the window if it loses focus while fullscreen - it's annoying if you 'have multiple monitors and you move the mouse to another monitor SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0") 'This controls whether SDL will wait for vsync (causing the framerate to cap to 60fps), or 'to triple buffer (adding a frame of latency) - only some drivers 'SDL_SetHint("SDL_VIDEO_DOUBLE_BUFFER", "1") 'SDL_HINT_VIDEO_DOUBLE_BUFFER not in FB's header yet 'Possibly useful in future: 'SDL_SetHint(SDL_HINT_RENDER_LOGICAL_SIZE_MODE, "overscan") 'Causes left/right of screen to be clipped instead of letterboxing 'SDL disables batching if you ask for a specific render driver rather than letting it choose SDL_SetHint("SDL_RENDER_BATCHING", "1") 'To receive controller updates while in the background, SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS DIM ver as SDL_version SDL_GetVersion(@ver) DIM ret as string ret = "SDL " & ver.major & "." & ver.minor & "." & ver.patch DIM video_already_init as bool = (SDL_WasInit(SDL_INIT_VIDEO) <> 0) IF SDL_Init(SDL_INIT_VIDEO) THEN ret = "Can't start SDL (gfx_sdl2): " & *SDL_GetError() & !"\n" & ret *info_buffer = LEFT(ret, info_buffer_size) RETURN 0 END IF 'Initialising joystick fails, for example, in Firefox when accessed over unsecured HTTP IF SDL_Init(SDL_INIT_JOYSTICK OR SDL_INIT_GAMECONTROLLER) THEN debug "SDL_Init JOY/GAMEPAD failed: " & *SDL_GetError() END IF 'Clear keyboard state because if we re-initialise the backend (switch backend) 'some key-up events can easily get lost memset(@keybdstate(0), 0, (UBOUND(keybdstate) + 1) * SIZEOF(keybdstate(0))) 'Enable controller events, so don't have to call SDL_GameControllerUpdate SDL_GameControllerEventState(SDL_ENABLE) ret &= " (" & SDL_NumJoysticks() & " joysticks) Driver: " & *SDL_GetCurrentVideoDriver() remember_window_size = 0 sdlpalette = SDL_AllocPalette(256) CheckOK(sdlpalette = NULL, RETURN 0) #IFDEF __FB_ANDROID__ IF SDL_ANDROID_IsRunningOnConsole() THEN debuginfo "Running on a console, disable the virtual gamepad" internal_disable_virtual_gamepad ELSE debuginfo "Not running on a console, leave the virtual gamepad visible" END IF #ENDIF DIM retcode as integer retcode = recreate_window() ret &= " Render driver: " DIM rendererinfo as SDL_RendererInfo IF SDL_GetRendererInfo(mainrenderer, @rendererinfo) = 0 THEN ret &= *rendererinfo.name END IF DIM moreinfo as string moreinfo = "gfx_sdl2 Drivers:" FOR i as integer = 0 TO SDL_GetNumVideoDrivers() - 1 moreinfo &= " " & *SDL_GetVideoDriver(i) NEXT moreinfo &= " Render Drivers:" FOR idx as integer = 0 TO 9 IF SDL_GetRenderDriverInfo(idx, @rendererinfo) THEN EXIT FOR moreinfo &= strprintf(" %s (%s%s%s)", rendererinfo.name, _ IIF(rendererinfo.flags AND SDL_RENDERER_ACCELERATED, @"hwaccel,", @""), _ IIF(rendererinfo.flags AND SDL_RENDERER_PRESENTVSYNC, @"vsync,", @""), _ IIF(rendererinfo.flags AND SDL_RENDERER_TARGETTEXTURE, @"textarget", @"")) NEXT ' engine_settings_menu hides the part after " // " (because it's too verbose), while the whole line beginning ' with "gfx_sdl2" it's still picked by ' by misc/process_crashreports.py will pull ret &= " // " & moreinfo *info_buffer = LEFT(ret, info_buffer_size) RETURN retcode END FUNCTION LOCAL FUNCTION recreate_window(byval bitdepth as integer = 0) as bool IF mainrenderer THEN SDL_DestroyRenderer(mainrenderer) 'Also destroys textures mainrenderer = NULL maintexture = NULL IF mainwindow THEN SDL_DestroyWindow(mainwindow) mainwindow = NULL DIM flags as Uint32 = 0 IF resizable_window THEN flags = flags OR SDL_WINDOW_RESIZABLE IF windowedmode = NO THEN 'TODO: when "true fullscreen" is used and you quit from fullscreen using alt-F4, on linux/KDE at least, 'the screen doesn't restore to its original resolution. So need to return to windowed mode when 'quitting gfx_sdl2 'flags = flags OR SDL_WINDOW_FULLSCREEN ' This means don't change the resolution, instead create a fullscreen window, like gfx_directx flags = flags OR SDL_WINDOW_FULLSCREEN_DESKTOP END IF DIM windowpos as integer IF recenter_window_hint THEN windowpos = SDL_WINDOWPOS_CENTERED ELSE windowpos = SDL_WINDOWPOS_UNDEFINED END IF recenter_window_hint = NO 'Start with initial zoom and repeatedly decrease it if it is too large '(This is necessary to run in fullscreen in OSX IIRC) DO DIM windowsize as XYPair = framesize * zoom debuginfo "setvideomode zoom=" & zoom & " w*h = " & windowsize mainwindow = SDL_CreateWindow(remember_windowtitle, windowpos, windowpos, _ windowsize.w, windowsize.h, flags) IF mainwindow = NULL THEN #ifndef NO_ZOOM 'This crude hack won't work for everyone if the SDL error messages are internationalised... IF zoom > 1 ANDALSO strstr(SDL_GetError(), "No video mode large enough") THEN debug "Failed to open display (windowed = " & windowedmode & ") (retrying with smaller zoom): " & *SDL_GetError zoom -= 1 CONTINUE DO END IF #endif debug "Failed to open display (windowed = " & windowedmode & "): " & *SDL_GetError RETURN 0 END IF EXIT DO LOOP DIM force_driver as string = read_config_str("gfx.gfx_sdl2.render_driver") IF LEN(force_driver) THEN 'If the driver name is invalid SDL_CreateRender will ignore it SDL_SetHint(SDL_HINT_RENDER_DRIVER, force_driver) END IF 'The flags to SDL_CreateRenderer have two purposes: firstly, only render drivers 'that have all those flags are selected, but secondly PRESENTVSYNC tells the 'driver whether to enable vsync. So if we can't get vsync, fallback to without. '(SDL_HINT_RENDER_VSYNC is equivalent to passing SDL_RENDERER_PRESENTVSYNC) mainrenderer = SDL_CreateRenderer(mainwindow, -1, SDL_RENDERER_PRESENTVSYNC) IF mainrenderer = NULL THEN 'If we get here most likely it's because the drivers failed to load, not because 'there's none that support vsync. Don't loop through them all again (which might 'be painfully slow), just try first one. mainrenderer = SDL_CreateRenderer(mainwindow, 0, 0) END IF ' Don't kill the program yet; the software renderer should work IF mainrenderer = NULL THEN log_error("SDL_CreateRenderer failed; falling back to software renderer", "") 'Doing SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "software") or "0" instead didn't help to 'recover from a broken X11 OpenGL implementation SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software") mainrenderer = SDL_CreateRenderer(mainwindow, -1, SDL_RENDERER_PRESENTVSYNC) CheckOK(mainrenderer = NULL, RETURN 0) END IF 'Whether to stick to integer scaling amounts when using SDL_RenderSetLogicalSize. SDL 2.0.5+ '(Turning this on just adds black bars around every edge when the window is a non-integer zooms, quite ugly. 'SDL_RenderSetIntegerScale(mainrenderer, NO) set_viewport windowedmode IF recreate_screen_texture() = NO THEN RETURN 0 /' WITH *mainwindow->format debuginfo "gfx_sdl2: created mainwindow size=" & mainwindow->w & "*" & mainwindow->h _ & " depth=" & .BitsPerPixel & " flags=0x" & HEX(mainwindow->flags) _ & " R=0x" & hex(.Rmask) & " G=0x" & hex(.Gmask) & " B=0x" & hex(.Bmask) 'FIXME: should handle the screen surface not being BGRA, or ask SDL for a surface in that encoding END WITH '/ update_mouse_visibility() RETURN 1 END FUNCTION 'The screen texture (aka screen buffer) needs recreating when its size changes LOCAL FUNCTION recreate_screen_texture() as bool IF mainrenderer = NULL THEN RETURN NO 'Called before backend init 'In SDL 2.0.12+ we can call SDL_SetTextureScaleMode instead of setting this hint (which affects SDL_CreateTexture only) SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, IIF(bilinear, @"linear", @"nearest")) IF maintexture THEN SDL_DestroyTexture(maintexture) DIM buffersize as XYPair = screen_buffer_size maintexture = SDL_CreateTexture(mainrenderer, _ SDL_PIXELFORMAT_ARGB8888, _ SDL_TEXTUREACCESS_STREAMING, _ buffersize.w, buffersize.h) CheckOK(maintexture = NULL, RETURN NO) RETURN YES END FUNCTION 'The amount of zooming to do in software (using upscaler) before stretching to the window. LOCAL FUNCTION screen_buffer_zoom() as integer DIM z as integer = 1 'When using bilinear scaling, nearest-neighbour upscaling isn't a noop. IF smooth OR bilinear THEN z = upscaler_zoom 'When bilinear smoothing, scaling up to zoom+1 continues to increase sharpness without issues, while going further 'introduces the shimmering artifacts seen without bilinear filtering. 'When using an upscaler, going above CEIL(frac_zoom) makes no sense either. z = small(z, CEIL(zoom)) 'smooth upscaler only supports 2x, 3x, 4x, 6x, 8x, 9x, 12x, 16x, with >3x implemented 'by running repeatedly. Which is very slow and results aren't worth it, so don't go higher than x6. IF smooth ANDALSO (z = 5 ORELSE z >= 7) THEN z = 3 RETURN z END FUNCTION 'The required size of maintexture LOCAL FUNCTION screen_buffer_size() as XYPair RETURN framesize * screen_buffer_zoom() END FUNCTION 'Set/update the part of the window/screen on which to draw the frame, including scaling and letterboxing. 'for_windowed: true to set it for windowed mode, false for fullscreen LOCAL SUB set_viewport(for_windowed as bool) IF for_windowed = NO ORELSE resizable_resolution = NO THEN 'Fullscreen or stretchable window: 'Ask SDL to scale, center and letterbox automatically. '(Aspect ratio is always preserved. SDL_RenderSetIntegerScale is optional) 'But this will lead to ugly wobbling while resizing a window. DIM buffersize as XYPair = screen_buffer_size SDL_RenderSetLogicalSize(mainrenderer, buffersize.w, buffersize.h) ELSE 'No centering while windowed, fixed scale amount. '(There's no need to use SDL_RenderSetScale) DIM rect as SDL_Rect rect.w = zoom * framesize.w rect.h = zoom * framesize.h 'Calling SDL_RenderSetLogicalSize overrides previous SDL_RenderSetViewport, 'but calling SDL_RenderSetViewport does not turn off SDL_RenderSetLogicalSize automatically. SDL_RenderSetLogicalSize(mainrenderer, 0, 0) SDL_RenderSetViewport(mainrenderer, @rect) END IF END SUB 'Set the minimum resolution (when resizable_resolution) and the minimum window 'size (which might be necessary before calling SDL_SetWindowSize). LOCAL SUB set_window_min_resolution(minres as XYPair = XY(0,0)) DIM minsize as XYPair IF resizable_window = NO THEN 'The min window size may (depending on OS?) still be enforced (even on 'SDL_SetWindowSize) even if we disable resizing, so have to change it so setting a 'small zoom or frame size works. But passing zero width/height to 'SDL_SetWindowMinimumSize is invalid, so set to something tiny. minsize = XY(MinResolutionX, MinResolutionY) ELSEIF resizable_resolution THEN 'User resizes don't change the zoom min_window_resolution = large(XY(MinResolutionX, MinResolutionY), minres) minsize = min_window_resolution * zoom ELSE 'Zoom can be changed to less than 1x, so just set an arbitrary lower limit minsize = XY(MinResolutionX, MinResolutionY) END IF 'debuginfo "SDL_SetWindowMinimumSize " & minsize SDL_SetWindowMinimumSize(mainwindow, minsize.w, minsize.h) END SUB 'Note that gfx_sdl2_set_window_size wraps this. 'actually_resize can (and should) be false if the window was resized by the WM; 'don't changing the size while the user is doing the same, as that causes some 'window resize events to get lost on KDE (bug #1190) LOCAL SUB set_window_size(newframesize as XYPair, newzoom as double, actually_resize as bool) framesize = newframesize #ifndef NO_ZOOM zoom = newzoom #endif IF debugging_io THEN debuginfo "set_window_size " & newframesize & " x" & newzoom END IF IF mainwindow THEN 'Note the windows's display is whichever one its center is on, which might change when it resizes DIM displayindex as integer = large(0, SDL_GetWindowDisplayIndex(mainwindow)) 'May be necessary even if the window isn't resizable set_window_min_resolution min_window_resolution IF actually_resize THEN 'If we're fullscreened, takes effect when unfullscreening (unless resizable_window, 'in which case we restore previous size) SDL_SetWindowSize(mainwindow, zoom * framesize.w, zoom * framesize.h) END IF 'Still should update the viewport if actually_resize = NO, because the window size 'may have been changed externally (without this, the window becomes quite wobbly) set_viewport windowedmode 'Recentering the window while fullscreen can cause the window to move to 0,0 'when exiting fullscreen on Windows (SDL bug gh#4750, fixed in 2.0.18) (oddly, under X11/xfce4 that 'only happens if you do it immediately after exiting (SDL bug gh#4749)). IF windowedmode ANDALSO recenter_window_hint THEN IF debugging_io THEN debuginfo "recentering window" 'Without calling SDL_SetWindowPosition, if the window is resized so it would 'go over the screen edges: '-under WinXP+SDL2.0.14, it isn't moved '-under X11+xfce4+SDL2.0.16, it's moved to fit onscreen, but mispositioned so it's ' slightly over the screen edge! 'Undocumented SDL feature: add in the display index to center on that display SDL_SetWindowPosition(mainwindow, SDL_WINDOWPOS_CENTERED + displayindex, SDL_WINDOWPOS_CENTERED) END IF recenter_window_hint = NO recreate_screen_texture update_mouserect END IF END SUB LOCAL SUB quit_video_subsystem() IF mainrenderer THEN SDL_DestroyRenderer(mainrenderer) 'Also destroys textures mainrenderer = NULL maintexture = NULL IF mainwindow THEN SDL_DestroyWindow(mainwindow) mainwindow = NULL IF screenbuffer THEN SDL_FreeSurface(screenbuffer) screenbuffer = NULL IF sdlpalette THEN SDL_FreePalette(sdlpalette) sdlpalette = NULL SDL_QuitSubSystem(SDL_INIT_VIDEO) END SUB FUNCTION gfx_sdl2_getversion() as integer RETURN 1 END FUNCTION 'Handles smoothing and changes to the frame size, then calls present_internal2 'to update the screen LOCAL FUNCTION present_internal(raw as any ptr, imagesz as XYPair, bitdepth as integer) as integer 'debuginfo "gfx_sdl2_present_internal(" & imagesz & ", bitdepth=" & bitdepth & ")" last_bitdepth = bitdepth 'variable resolution handling IF framesize <> imagesz THEN 'debuginfo "gfx_sdl2_present_internal: framesize changing from " & framesize & " to " & imagesz 'Don't actually resize the window if the WM/user is (maybe still) resizing it set_window_size(imagesz, zoom, resize_requested = NO ANDALSO resize_pending = NO) END IF resize_pending = NO DIM pitch as integer = imagesz.w * IIF(bitdepth = 32, 4, 1) 'This is zoom ratio from raw to screenbuffer. Usually less than window zoom. DIM bufferzoom as integer = screen_buffer_zoom() DIM buffersize as XYPair = screen_buffer_size() IF bufferzoom > 1 ORELSE bitdepth = 8 THEN ' We need screenbuffer. So check it exists and is the right size IF screenbuffer THEN IF XY(screenbuffer->w, screenbuffer->h) <> buffersize ORELSE _ screenbuffer->format->BitsPerPixel <> bitdepth THEN SDL_FreeSurface(screenbuffer) screenbuffer = NULL END IF END IF IF screenbuffer = NULL THEN IF bitdepth = 32 THEN 'screenbuffer = SDL_CreateRGBSurfaceFrom(raw, w, h, 8, w, 0,0,0,0) 'screenbuffer = SDL_CreateRGBSurfaceWithFormat(0, buffersize.w, buffersize.h, 32, SDL_PIXELFORMAT_ARGB8888) screenbuffer = SDL_CreateRGBSurface(0, buffersize.w, buffersize.h, bitdepth, &h00ff0000, &h0000ff00, &h000000ff, &hff000000) ELSE screenbuffer = SDL_CreateRGBSurface(0, buffersize.w, buffersize.h, bitdepth, 0,0,0,0) END IF END IF IF screenbuffer = NULL THEN debugc errDie, "present_internal: Failed to allocate page wrapping surface, " & *SDL_GetError() END IF END IF IF bufferzoom > 1 THEN ' Intermediate step: do an enlarged blit to a surface and then do smoothing IF bitdepth = 8 THEN smoothzoomblit_8_to_8bit(raw, screenbuffer->pixels, imagesz, screenbuffer->pitch, bufferzoom, smooth) ELSE '32 bit surface 'smoothzoomblit takes the pitch in pixels, not bytes! smoothzoomblit_32_to_32bit(cast(RGBcolor ptr, raw), cast(uint32 ptr, screenbuffer->pixels), imagesz, screenbuffer->pitch \ 4, bufferzoom, smooth) END IF raw = screenbuffer->pixels pitch = screenbuffer->pitch ELSEIF bitdepth = 8 THEN 'Need to make a copy of the input, in case gfx_setpal is called 'Copy over 'smoothzoomblit_8_to_8bit(raw, screenbuffer->pixels, imagesz, screenbuffer->pitch, 1, smooth) SDL_ConvertPixels(imagesz.w, imagesz.h, SDL_PIXELFORMAT_INDEX8, raw, pitch, SDL_PIXELFORMAT_INDEX8, screenbuffer->pixels, screenbuffer->pitch) ELSE ' Can copy directly to maintexture: screenbuffer is not used END IF RETURN present_internal2(screenbuffer, raw, buffersize, pitch, bitdepth) END FUNCTION 'Updates the screen. Assumes all size changes have been handled. 'If bitdepth=8 then srcsurf is used, otherwise raw is used, and is a block of 'pixels in SDL_PIXELFORMAT_ARGB8888 with the given pitch. 'The surface or block of pixels must be the same size as maintexture. LOCAL FUNCTION present_internal2(srcsurf as SDL_Surface ptr, raw as any ptr, imagesz as XYPair, pitch as integer, bitdepth as integer) as bool DIM ret as bool = YES DIM as integer texw, texh DIM texpixels as any ptr DIM texpitch as integer SDL_QueryTexture(maintexture, NULL, NULL, @texw, @texh) CheckOK(SDL_LockTexture(maintexture, NULL, @texpixels, @texpitch), RETURN NO) IF bitdepth = 8 THEN 'SDL2 has two different ways to specify a pixel format: ' struct SDL_PixelFormat - the struct used by SDL_Surfaces. Very flexible, includes an SDL_Palette* ' enum SDL_PixelFormatEnum - available texture formats. 'Conversion functions: ' SDL_ConvertPixels - convert raw pixel buffer from one SDL_PixelFormatEnum to another ' SDL_ConvertSurface - copy of a Surface converted to a SDL_PixelFormat ' SDL_ConvertSurfaceFormat - copy of a Surface converted to a SDL_PixelFormatEnum ' SDL_BlitSurface - between Surfaces. Does a conversion 'Also relevant: ' SDL_AllocFormat - Get a SDL_PixelFormat from a SDL_PixelFormatEnum ' SDL_SetSurfacePalette - Modify's a Surface's SDL_PixelFormat ' SDL_CreateRGBSurfaceFrom - A Surface wrapping an existing pixel buffer, defined by masks ' SDL_CreateRGBSurfaceWithFormatFrom - A Surface wrapping an existing pixel buffer, defined by SDL_PixelFormatEnum. 'So can't use SDL_ConvertPixels as it doesn't support a palette. CheckOK(SDL_SetSurfacePalette(srcsurf, sdlpalette)) DIM destsurf as SDL_Surface ptr 'Avoid SDL_CreateRGBSurfaceWithFormatFrom because it's SDL 2.0.5+ 'destsurf = SDL_CreateRGBSurfaceWithFormatFrom(texpixels, texw, texh, 32, texpitch, SDL_PIXELFORMAT_ARGB8888) destsurf = SDL_CreateRGBSurfaceFrom(texpixels, texw, texh, 32, texpitch, &h00ff0000, &h0000ff00, &h000000ff, &hff000000) CheckOK(destsurf = NULL) CheckOK(SDL_BlitSurface(srcsurf, NULL, destsurf, NULL), ret = NO) '? texw, texh, srcsurf->w, srcsurf->h, imagew, imageh SDL_FreeSurface(destsurf) ELSE 'Formats are the same, so this will be a simple copy CheckOK(SDL_ConvertPixels(texw, texh, SDL_PIXELFORMAT_ARGB8888, raw, pitch, SDL_PIXELFORMAT_ARGB8888, texpixels, texpitch), ret = NO) 'CheckOK(SDL_UpdateTexture(maintexture, NULL, raw, pitch), ret = NO) END IF SDL_UnlockTexture(maintexture) 'Clearing the screen first is necessary in fullscreen, when the window size may not match the maintexture size '(this clears the black bars) '(This has a cost. Could probably be skipped on some other targets too?) #ifndef __FB_BLACKBOX__ SDL_RenderClear(mainrenderer) #endif 'DIM dstrect as SDL_Rect = (0, 0, framesize.w * zoom, framesize.h * zoom) 'imagew, imageh CheckOK(SDL_RenderCopy(mainrenderer, maintexture, NULL, NULL /'@dstrect'/), ret = NO) SDL_RenderPresent(mainrenderer) update_state() RETURN ret END FUNCTION 'Copies an RGBColor[256] array to sdlpalette LOCAL SUB set_palette(pal as RGBColor ptr) DIM cols(255) as SDL_Color FOR i as integer = 0 TO 255 cols(i).r = pal[i].r cols(i).g = pal[i].g cols(i).b = pal[i].b NEXT SDL_SetPaletteColors(sdlpalette, @cols(0), 0, 256) END SUB SUB gfx_sdl2_setpal(byval pal as RGBcolor ptr) IF last_bitdepth = 8 THEN set_palette pal 'Re-render the contents of screenbuffer present_internal2(screenbuffer, NULL, XY(screenbuffer->w, screenbuffer->h), screenbuffer->pitch, 8) ELSE debuginfo "gfx_sdl2_setpal called after a 32bit present" END IF update_state() END SUB FUNCTION gfx_sdl2_present(byval surfaceIn as Surface ptr, byval pal as RGBPalette ptr) as integer WITH *surfaceIn IF .format = SF_8bit AND pal <> NULL THEN set_palette @pal->col(0) END IF DIM ret as integer ret = present_internal(.pColorData, .size, IIF(.format = SF_8bit, 8, 32)) update_state() RETURN ret END WITH END FUNCTION 'Only used when testing BACKEND_GOVERNED_FRAMERATE FUNCTION gfx_sdl2_native_framerate() as double RETURN 60 END FUNCTION 'Only used when testing BACKEND_GOVERNED_FRAMERATE SUB gfx_sdl2_wait_one_frame() 'CheckOK(SDL_RenderCopy(mainrenderer, maintexture, NULL, NULL /'@dstrect'/)) SDL_RenderPresent(mainrenderer) update_state() END SUB FUNCTION gfx_sdl2_screenshot(byval fname as zstring ptr) as integer gfx_sdl2_screenshot = 0 END FUNCTION SUB gfx_sdl2_setwindowed(byval towindowed as bool) IF debugging_io THEN debuginfo "setwindowed " & towindowed IF mainwindow = NULL THEN windowedmode = towindowed EXIT SUB END IF DIM entering_fullscreen as bool DIM leaving_fullscreen as bool entering_fullscreen = (towindowed = NO ANDALSO windowedmode = YES) leaving_fullscreen = (towindowed = YES ANDALSO windowedmode = NO) IF entering_fullscreen THEN SDL_GetWindowSize(mainwindow, @remember_window_size.w, @remember_window_size.h) IF debugging_io THEN debuginfo "remembering window size " & remember_window_size END IF 'Turn on or off scaling/centering/letterboxing '(This may not be strictly needed when leaving fullscreen) '(This has to be done before switching to fullscreen to avoid SDL bug gh#4715) set_viewport towindowed #IFDEF USE_X11 'At least on X11/xfce4, clearing the screen at this point helps to reduce the 'likelihood of flicker due to the screen texture getting stretched to the new 'window size, but it seems to be impossible to avoid entirely. 'On the other hand, it adds flicker on Windows (XP) when the window is non-resizable, '(but has neglible effect when it's resizable). SDL_RenderClear(mainrenderer) SDL_RenderPresent(mainrenderer) #ENDIF DIM mousepos as XYPair SDL_GetGlobalMouseState @mousepos.x, @mousepos.y DIM flags as int32 = 0 IF towindowed = NO THEN flags = SDL_WINDOW_FULLSCREEN_DESKTOP IF SDL_SetWindowFullscreen(mainwindow, flags) THEN showerror "Could not toggle fullscreen mode: " & *SDL_GetError() EXIT SUB END IF 'Work around SDL bug gh#3132 (on X11/xfce4): if the window is resizable, after you leave 'fullscreen the mouse gets warped across the screen. If it's not resizable, it instead 'moves a little when entering fullscreen. SDL_WarpMouseGlobal mousepos.x, mousepos.y windowedmode = towindowed IF leaving_fullscreen THEN 'Changing resizability while fullscreened doesn't work, so do it now SDL_SetWindowResizable(mainwindow, resizable_window) 'gfx_sdl2_set_resizable resizable, min_window_resolution.w, min_window_resolution.h END IF IF leaving_fullscreen ANDALSO resizable_window ANDALSO remember_window_size <> 0 THEN 'When you fulscreen while resizable the window size is maximised. While it automatically 'unmaximises under X11/xfce4 (at least), this doesn't happen on WinXP or Win10, so do it manually. 'Likewise, on Windows if you change the zoom while fullscreened the window doesn't 'restore its position when unfullscreening, though it does otherwise, and on xfce4. IF debugging_io THEN debuginfo "Restoring window size to " & remember_window_size IF resizable_resolution THEN resize_request = windowsize_to_resolution(remember_window_size) 'If the remembered size isn't different, nothing to do resize_requested = (resize_request <> framesize) END IF SDL_SetWindowSize mainwindow, remember_window_size.w, remember_window_size.h 'A SDL_WINDOWEVENT_RESIZED will be generated, which handles updating framesize and zoom '(maybe indirectly via resize_request) END IF 'Mouse region needs recomputing after either scale/zoom or window size change update_mouserect END SUB SUB gfx_sdl2_windowtitle(byval title as zstring ptr) IF SDL_WasInit(SDL_INIT_VIDEO) then SDL_SetWindowTitle(mainwindow, title) END IF remember_windowtitle = *title END SUB FUNCTION gfx_sdl2_getwindowstate() as WindowState ptr STATIC state as WindowState state.structsize = WINDOWSTATE_SZ DIM flags as uint32 = SDL_GetWindowFlags(mainwindow) 'TODO: what about SDL_WINDOW_SHOWN/SDL_WINDOW_HIDDEN? state.focused = (flags AND SDL_WINDOW_INPUT_FOCUS) <> 0 state.minimised = (flags AND SDL_WINDOW_MINIMIZED) = 0 state.fullscreen = (flags AND (SDL_WINDOW_FULLSCREEN OR SDL_WINDOW_FULLSCREEN_DESKTOP)) <> 0 state.mouse_over = (flags AND SDL_WINDOW_MOUSE_FOCUS) <> 0 SDL_GetWindowSize(mainwindow, @state.windowsize.w, @state.windowsize.h) state.zoom = CINT(zoom) state.maximised = (flags AND SDL_WINDOW_MAXIMIZED) <> 0 RETURN @state END FUNCTION SUB gfx_sdl2_get_screen_size(wide as integer ptr, high as integer ptr) 'If we already have a window, query the size of its display, since we will want to 'know what we can resize to. Otherwise the first display. DIM displayindex as integer = 0 IF mainwindow THEN displayindex = large(0, SDL_GetWindowDisplayIndex(mainwindow)) END IF DIM rect as SDL_Rect 'SDL_GetDisplayUsableBounds excludes area for taskbar, OSX menubar, dock, etc., IF SDL_GetDisplayUsableBounds(displayindex, @rect) THEN debug "SDL_GetDisplayUsableBounds: " & *SDL_GetError() *wide = 0 *high = 0 ELSE *wide = rect.w *high = rect.h END IF END SUB FUNCTION gfx_sdl2_supports_variable_resolution() as bool 'Safe even in fullscreen, I think RETURN YES END FUNCTION FUNCTION gfx_sdl2_vsync_supported() as bool #IFDEF __FB_DARWIN__ ' OSX always has vsync, and drawing the screen will block until vsync, so this needs ' special treatment (as opposed to most other WMs which also do vsync compositing) RETURN YES #ELSE 'FIXME: this is usually wrong RETURN NO #ENDIF END FUNCTION 'Set whether the *resolution* is user-resizable, and the min resolution. The window is always resizable. FUNCTION gfx_sdl2_set_resizable(enable as bool, min_width as integer = 0, min_height as integer = 0) as bool IF debugging_io THEN debuginfo "set_resizable " & enable resizable_resolution = enable IF enable THEN resizable_window = YES IF mainwindow = NULL THEN RETURN resizable_resolution set_window_min_resolution XY(min_width, min_height) 'Note: Can't change resizability of a fullscreen window; SDL just ignores the call. 'We'll try again in gfx_sdl2_setwindowed SDL_SetWindowResizable(mainwindow, resizable_window) RETURN resizable_resolution END FUNCTION FUNCTION gfx_sdl2_get_resize(byref ret as XYPair) as bool IF resize_requested THEN ret = resize_request resize_requested = NO resize_pending = YES RETURN YES END IF RETURN NO END FUNCTION 'The next time zoom or resolution changes recenter the window. Afterwards the flag is removed. SUB gfx_sdl2_recenter_window_hint() debuginfo "recenter_window_hint()" 'IF running_under_Custom = NO THEN 'Don't display the window straight on top of Custom's 'No, DO recenter the window, because it's really bad if a large window goes over the screen edges 'because we didn't recenter it. (Some OSes/WMs may do so automatically.) recenter_window_hint = YES 'END IF END SUB 'This is the new API for changing window size, an alternative to calling gfx_present with a resized frame. 'Unlike gfx_present, it causes the window to resize but doesn't repaint it yet. SUB gfx_sdl2_set_window_size (byval newframesize as XYPair = XY(-1,-1), newzoom as integer = -1) IF newframesize.w <= 0 THEN newframesize = framesize IF newzoom < 1 ORELSE newzoom > 16 THEN newzoom = zoom DIM zoomchanged as bool = ABS(zoom - newzoom) > 0.001 IF newframesize <> framesize ANDALSO mainwindow THEN resize_request = newframesize resize_requested = YES 'debuginfo " (resize_requested)" END IF IF zoomchanged THEN gfx_sdl2_recenter_window_hint() 'Recenter because it's pretty ugly to go from centered to uncentered END IF IF zoomchanged ORELSE newframesize <> framesize THEN debuginfo "gfx_sdl2_set_window_size " & newframesize & ", zoom=" & newzoom '(We don't actually need to call set_window_size here and could instead mark 'that gfx_present should call it if the zoom changed. But that's more code 'and seems to behave identically) set_window_size(newframesize, newzoom, zoomchanged) END IF END SUB SUB gfx_sdl2_get_settings(byref settings as GfxSettings) settings.resizable_window = resizable_window settings.resizable_resolution = resizable_resolution settings.upscaler = smooth '0/1 settings.upscaler_zoom = upscaler_zoom settings.bilinear = bilinear END SUB SUB gfx_sdl2_set_settings(settings as GfxSettings) smooth = settings.upscaler IF settings.upscaler_zoom > 0 THEN upscaler_zoom = settings.upscaler_zoom bilinear = settings.bilinear recreate_screen_texture IF settings.resizable_resolution <> resizable_resolution ORELSE settings.resizable_window <> resizable_window THEN resizable_window = settings.resizable_window gfx_sdl2_set_resizable(settings.resizable_resolution, settings.min_resolution.w, settings.min_resolution.h) END IF END SUB FUNCTION gfx_sdl2_setoption(byval opt as zstring ptr, byval arg as zstring ptr) as integer DIM ret as integer = 0 DIM value as integer = str2int(*arg, -1) IF *opt = "zoom" or *opt = "z" THEN gfx_sdl2_set_window_size( , bound(VAL(*arg), 0.1, 16)) ret = 1 ELSEIF *opt = "smooth" OR *opt = "s" THEN IF value = 1 OR value = -1 THEN 'arg optional (-1) smooth = 1 ELSE smooth = 0 END IF ret = 1 END IF 'all these take an optional numeric argument, so gobble the arg if it is 'a number, whether or not it was valid IF ret = 1 AND parse_int(*arg) THEN ret = 2 RETURN ret END FUNCTION FUNCTION gfx_sdl2_describe_options() as zstring ptr return @"-z -zoom [1...16] Scale screen to 1,2, ... up to 16x normal size (2x default)" LINE_END _ "-s -smooth Enable smoothing filter for zoom modes (default off)" END FUNCTION FUNCTION gfx_sdl2_get_safe_zone_margin() as single RETURN safe_zone_margin END FUNCTION SUB gfx_sdl2_set_safe_zone_margin(margin as single) 'FIXME: Not implemented! safe_zone_margin = margin END SUB FUNCTION gfx_sdl2_supports_safe_zone_margin() as bool #IFDEF __FB_ANDROID__ RETURN YES #ELSE RETURN NO #ENDIF END FUNCTION SUB gfx_sdl2_ouya_purchase_request(dev_id as string, identifier as string, key_der as string) #IFDEF __FB_ANDROID__ SDL_ANDROID_SetOUYADeveloperId(dev_id) SDL_ANDROID_OUYAPurchaseRequest(identifier, key_der, LEN(key_der)) #ENDIF END SUB FUNCTION gfx_sdl2_ouya_purchase_is_ready() as bool #IFDEF __FB_ANDROID__ RETURN SDL_ANDROID_OUYAPurchaseIsReady() <> 0 #ENDIF RETURN YES END FUNCTION FUNCTION gfx_sdl2_ouya_purchase_succeeded() as bool #IFDEF __FB_ANDROID__ RETURN SDL_ANDROID_OUYAPurchaseSucceeded() <> 0 #ENDIF RETURN NO END FUNCTION SUB gfx_sdl2_ouya_receipts_request(dev_id as string, key_der as string) debuginfo "gfx_sdl2_ouya_receipts_request" #IFDEF __FB_ANDROID__ SDL_ANDROID_SetOUYADeveloperId(dev_id) SDL_ANDROID_OUYAReceiptsRequest(key_der, LEN(key_der)) #ENDIF END SUB FUNCTION gfx_sdl2_ouya_receipts_are_ready() as bool #IFDEF __FB_ANDROID__ RETURN SDL_ANDROID_OUYAReceiptsAreReady() <> 0 #ENDIF RETURN YES END FUNCTION FUNCTION gfx_sdl2_ouya_receipts_result() as string #IFDEF __FB_ANDROID__ DIM zresult as zstring ptr zresult = SDL_ANDROID_OUYAReceiptsResult() DIM result as string = *zresult RETURN result #ENDIF RETURN "" END FUNCTION SUB io_sdl2_init 'nothing needed at the moment... END SUB LOCAL SUB keycombos_logic(evnt as SDL_Event) 'Check for platform-dependent key combinations IF evnt.key.keysym.mod_ AND KMOD_ALT THEN IF evnt.key.keysym.sym = SDLK_RETURN THEN 'alt-enter (not processed normally when using SDL) gfx_sdl2_setwindowed(windowedmode XOR YES) post_event(eventFullscreened, windowedmode = NO) END IF IF evnt.key.keysym.sym = SDLK_F4 THEN 'alt-F4 post_terminate_signal END IF END IF #IFDEF __FB_DARWIN__ 'Unlike SDL 1.2, shortcuts likes Cmd-Q, Cmd-M, Cmd-H and Cmd-Shift-H (Quit, Minimise, Hide, Hide Others) 'which are attached to items in the menu bar just work without us having to do anything. IF evnt.key.keysym.mod_ AND KMOD_META THEN 'Command key 'The shortcut (in the menubar) for fullscreen is Ctrl-Cmd-F, but also 'support Cmd-F, which is what gfx_sdl uses. IF evnt.key.keysym.sym = SDLK_f THEN gfx_sdl2_setwindowed(windowedmode XOR YES) post_event(eventFullscreened, windowedmode = NO) END IF 'SDL doesn't actually seem to send SDLK_QUESTION... 'FIXME: this doesn't work properly, the Apple menu is opened, 'and only when it closes does this get sent... but then the 'keys get stuck!! IF evnt.key.keysym.sym = SDLK_SLASH AND evnt.key.keysym.mod_ AND KMOD_SHIFT THEN keybdstate(scF1) = 2 END IF FOR i as integer = 1 TO 4 IF evnt.key.keysym.sym = SDLK_0 + i THEN #IFDEF IS_CUSTOM set_scale_factor i, NO #ELSE set_scale_factor i, YES #ENDIF END IF NEXT END IF #ENDIF END SUB SUB gfx_sdl2_process_events() 'I assume this uses SDL_PeepEvents instead of SDL_PollEvent because the latter calls SDL_PumpEvents DIM evnt as SDL_Event WHILE SDL_PeepEvents(@evnt, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) SELECT CASE evnt.type CASE SDL_QUIT_ IF debugging_io THEN debuginfo "SDL_QUIT" END IF post_terminate_signal CASE SDL_KEYDOWN keycombos_logic(evnt) DIM as integer key = scantrans(evnt.key.keysym.scancode) IF debugging_io THEN debuginfo "SDL_KEYDOWN scan=" & evnt.key.keysym.scancode & " key=" & evnt.key.keysym.sym & " -> ohr=" & key & " (" & scancodename(key) & ") prev_keystate=" & keybdstate(key) END IF IF key ANDALSO evnt.key.repeat = 0 THEN 'Filter out key repeats (key already down, or we just saw a keyup): 'On Windows (XP at least) we get key repeats even if we don't enable 'SDL's key repeats, but with a much longer initial delay than the SDL ones. 'SDL repeats keys by sending extra KEYDOWNs, while Windows sends keyup-keydown 'pairs. Unfortunately for some reason we don't always get the keydown until 'the next tick, so that it doesn't get filtered out. 'gfx_fb suffers the same problem. IF keybdstate(key) = 0 THEN keybdstate(key) OR= 2 'new keypress keybdstate(key) OR= 1 'key down END IF CASE SDL_KEYUP DIM as integer key = scantrans(evnt.key.keysym.scancode) IF debugging_io THEN debuginfo "SDL_KEYUP scan=" & evnt.key.keysym.scancode & " key=" & evnt.key.keysym.sym & " -> ohr=" & key & " (" & scancodename(key) & ") prev_keystate=" & keybdstate(key) END IF 'Clear 2nd bit (new keypress) and turn on 3rd bit (keyup) IF key THEN keybdstate(key) = (keybdstate(key) AND 2) OR 4 CASE SDL_TEXTINPUT input_buffer += evnt.text.text 'UTF8 CASE SDL_CONTROLLERDEVICEADDED IF debugging_io THEN debuginfo "SDL_CONTROLLERDEVICEADDED joynum=" & evnt.cdevice.which END IF CASE SDL_CONTROLLERDEVICEREMOVED IF debugging_io THEN debuginfo "SDL_CONTROLLERDEVICEREMOVED instance_id=" & evnt.cdevice.which END IF CASE SDL_JOYDEVICEADDED IF debugging_io THEN debuginfo "SDL_JOYDEVICEADDED joynum=" & evnt.jdevice.which & " " & SDL_JoystickNameForIndex(evnt.jdevice.which) ' & " instance_id=" & SDL_JoystickGetDeviceInstanceID(evnt.jdevice.which) END IF CASE SDL_JOYDEVICEREMOVED IF debugging_io THEN debuginfo "SDL_JOYDEVICEREMOVED instance_id=" & evnt.jdevice.which END IF CASE SDL_CONTROLLERBUTTONDOWN DIM btn as integer = evnt.cbutton.button DIM ok as bool = sdl2_joy_button_press(btn, evnt.cbutton.which) IF debugging_io THEN debuginfo "SDL_CONTROLLERBUTTONDOWN instance_id=" & evnt.cbutton.which & " sdlbtn=" & evnt.cbutton.button & " ok=" & ok END IF CASE SDL_JOYBUTTONDOWN DIM btn as integer = evnt.jbutton.button DIM ok as bool DIM joynum as integer = instance_to_joynum(evnt.jbutton.which) IF joynum >= 0 ANDALSO joystickinfo(joynum).have_bindings = NO THEN 'Only process buttons for joysticks not recognised as gamepads, they're handled by SDL_CONTROLLERBUTTONDOWN ok = sdl2_joy_button_press(btn, evnt.jbutton.which) END IF IF debugging_io THEN debuginfo "SDL_JOYBUTTONDOWN instance_id=" & evnt.jbutton.which & " joynum=" & joynum & " sdlbtn=" & evnt.jbutton.button & " ok=" & ok END IF CASE SDL_MOUSEBUTTONDOWN 'note SDL_GetMouseState is still used, while SDL_GetKeyState isn't 'Interestingly, although (on Linux/X11) SDL doesn't report mouse motion events 'if the window isn't focused, it does report mouse wheel button events '(other buttons focus the window). 'So that dragging off the window reports positions outside the window. 'Since SDL 2.0.22, the mouse is automatically captured (input is grabbed) when dragging off the window anyway. SDL_CaptureMouse(YES) WITH evnt.button mouseclicks OR= SDL_BUTTON(.button) IF debugging_io THEN debuginfo "SDL_MOUSEBUTTONDOWN mouse " & .which & " button " & .button & " at " & XY(.x, .y) END IF END WITH CASE SDL_MOUSEBUTTONUP 'In order to wait until all buttons are up, we end mouse capture in update_mouse WITH evnt.button IF debugging_io THEN debuginfo "SDL_MOUSEBUTTONUP mouse " & .which & " button " & .button & " at " & XY(.x, .y) END IF END WITH CASE SDL_MOUSEWHEEL IF debugging_io THEN debuginfo "SDL_MOUSEWHEEL " & evnt.wheel.x & "," & evnt.wheel.y & " mouse=" & evnt.wheel.which 'SDL 2.0.4+: & " dir=" & evnt.wheel.direction END IF 'I'm surprised that SDL reports only 1 or -1 per wheel click... how does it reports wheels with 'higher resolutions? mousewheel += evnt.wheel.y * 120 'TODO: report evnt.wheel.x too CASE SDL_WINDOWEVENT IF debugging_io THEN DIM eventnames(...) as zstring ptr = { _ @"NONE", @"SHOWN", @"HIDDEN", @"EXPOSED", @"MOVED", @"RESIZED", _ @"SIZE_CHANGED", @"MINIMIZED", @"MAXIMIZED", @"RESTORED", _ @"ENTER", @"LEAVE", @"FOCUS_GAINED", @"FOCUS_LOST", @"CLOSE", _ @"TAKE_FOCUS", @"HIT_TEST" _ } WITH evnt.window IF in_bound(.event, 0, UBOUND(eventnames)) THEN 'Only SDL_WINDOWEVENT_RESIZED, SDL_WINDOWEVENT_SIZE_CHANGED (undocumented), 'SDL_WINDOWEVENT_MOVED have args debuginfo strprintf("SDL_WINDOWEVENT_%s %d,%d", eventnames(.event), .data1, .data2) ELSE debuginfo "SDL_WINDOWEVENT event=" & .event END IF END WITH END IF IF evnt.window.event = SDL_WINDOWEVENT_ENTER THEN 'Gained mouse focus /' IF evnt.active.gain = 0 THEN SDL_ShowCursor(1) ELSE update_mouse_visibility() END IF '/ END IF IF evnt.window.event = SDL_WINDOWEVENT_RESIZED THEN 'This event is delivered when the window size is changed by the user/WM 'rather than because we changed it (unlike SDL_WINDOWEVENT_SIZE_CHANGED) DIM windowsize as XYPair = XY(evnt.window.data1, evnt.window.data2) 'The viewport is automatically updated when window is resized. In fullscreen '(SDL_RenderSetLogicalSize in use) that's good, but when windowed, the viewport 'resets to cover the window, causing the image to momentarily stretch. Undo that IF windowedmode THEN set_viewport windowedmode IF resizable_resolution ANDALSO resizable_window THEN resize_request = windowsize_to_resolution(windowsize) IF framesize <> resize_request THEN '(This is from gfx_sdl, possibly obsolete) 'On Windows (XP), changing the window size causes an SDL_VIDEORESIZE event 'to be sent with the size you just set... this would produce annoying overlay 'messages in screen_size_update() if we don't filter them out. resize_requested = YES END IF 'Nothing happens until the engine calls gfx_get_resize, 'changes its internal window size (windowsize) as a result, 'and starts pushing Frames with the new size to gfx_present. 'Calling SDL_SetVideoMode changes the window size. Unfortunately it's not possible 'to reliably override a user resize event with a different window size, at least with 'X11+KDE, because the window size isn't changed by SDL_SetVideoMode while the user is 'still dragging the window, and as far as I can tell there is no way to tell what the 'actual window size is, or whether the user still has the mouse button down while 'resizing (it isn't reported); usually they do hold it down until after they've 'finished moving their mouse. One possibility would be to hook into X11, or to do 'some delayed SDL_SetVideoMode calls. ELSEIF resizable_resolution = NO ANDALSO resizable_window THEN DIM newzoom as double = windowsize_to_ratio(windowsize) set_window_size framesize, newzoom, NO 'Update zoom only ELSE ' resizable_window = NO 'If a resize happens that we don't want, override it. 'If we don't think the window is resizable, maybe we just disabled it and 'the event was generated right before. Or maybe we switched in/out of fullscreen, 'which generates resizes to/from the display size. (Ignore the former.) 'In particular, resizes when switching out of fullscreen if the 'window was erroneously set to resizable (because it's not possible 'to change resizability while fullscreened) need to be overridden. IF windowedmode THEN IF resizable_window = NO THEN IF debugging_io THEN debuginfo "set_window_size in response to SDL_WINDOWEVENT_RESIZED" set_window_size framesize, zoom, YES END IF END IF END IF END IF END SELECT WEND END SUB 'may only be called from the main thread LOCAL SUB update_state() /'#IFDEF __FB_JS__ IF is_html_input_focused() = 1 THEN RETURN END IF #ENDIF'/ SDL_PumpEvents() update_mouse() gfx_sdl2_process_events() END SUB SUB io_sdl2_pollkeyevents() 'might need to redraw the screen if exposed /' IF SDL_Flip(mainwindow) THEN debug "pollkeyevents: SDL_Flip failed: " & *SDL_GetError END IF '/ update_state() END SUB SUB io_sdl2_waitprocessing() update_state() END SUB LOCAL SUB keymod_to_keybdstate(modstate as integer, key as KBScancode) keybdstate(key) = (keybdstate(key) AND 6) OR IIF(modstate, 1, 0) END SUB SUB io_sdl2_keybits (byval keybdarray as KeyBits ptr) 'keybdarray bits: ' bit 0 - key down ' bit 1 - new keypress event 'keybdstate bits: ' bit 0 - key down ' bit 1 - new keypress event ' bit 2 - keyup event 'In SDL2, unlike SDL 1.2 (unless SDL_DISABLE_LOCK_KEYS is set), the *lock 'keys act like normal keys instead of telling whether the respective lock is on. '(Pause/Break still doesn't act as a normal key). 'Maybe we should just report modifier state separately from button state, the same 'way SDL2 does it. DIM kmod as SDL_Keymod = SDL_GetModState() keymod_to_keybdstate kmod AND KMOD_NUM, scNumlock keymod_to_keybdstate kmod AND KMOD_CAPS, scCapslock 'scScrollLock: No way to check scoll lock state? DIM msg as string FOR a as KBScancode = 0 TO &h7f keybdstate(a) = keybdstate(a) and 3 'Clear key-up bit keybdarray[a] = keybdstate(a) IF debugging_io ANDALSO keybdarray[a] THEN msg &= " key[" & a & "](" & scancodename(a) & ")=" & keybdarray[a] END IF keybdstate(a) = keybdstate(a) and 1 'Clear new-keypress bit NEXT IF LEN(msg) THEN debuginfo "io_sdl2_keybits returning:" & msg keybdarray[scShift] = keybdarray[scLeftShift] OR keybdarray[scRightShift] keybdarray[scUnfilteredAlt] = keybdarray[scLeftAlt] OR keybdarray[scRightAlt] keybdarray[scCtrl] = keybdarray[scLeftCtrl] OR keybdarray[scRightCtrl] END SUB SUB io_sdl2_updatekeys(byval keybd as KeyBits ptr) 'supports io_keybits instead END SUB 'Enabling unicode will cause combining keys to go dead on X11 (on non-US 'layouts that have them). This usually means certain punctuation keys such as ' 'On both X11 and Windows, disabling unicode input means SDL_KEYDOWN events 'don't report the character value (.unicode_). SUB io_sdl2_enable_textinput (byval enable as integer) END SUB SUB io_sdl2_textinput (byval buf as wstring ptr, byval bufsize as integer) DIM out as wstring ptr = utf8_decode(@input_buffer[0]) IF out = NULL THEN debug "io_sdl2_textinput: utf8_decode failed" ELSE *buf = LEFT(*out, bufsize) DEALLOCATE out END IF input_buffer = "" END SUB SUB io_sdl2_show_virtual_keyboard() 'Does nothing on platforms that have real keyboards #IFDEF __FB_ANDROID__ if not virtual_keyboard_shown then SDL_ANDROID_ToggleScreenKeyboardWithoutTextInput() virtual_keyboard_shown = YES end if #ENDIF END SUB SUB io_sdl2_hide_virtual_keyboard() 'Does nothing on platforms that have real keyboards #IFDEF __FB_ANDROID__ if virtual_keyboard_shown then SDL_ANDROID_ToggleScreenKeyboardWithoutTextInput() virtual_keyboard_shown = NO end if #ENDIF END SUB SUB io_sdl2_show_virtual_gamepad() 'Does nothing on other platforms #IFDEF __FB_ANDROID__ if allow_virtual_gamepad then SDL_ANDROID_SetScreenKeyboardShown(YES) else debuginfo "io_sdl2_show_virtual_gamepad was supressed because of a previous call to internal_disable_virtual_gamepad" end if #ENDIF END SUB SUB io_sdl2_hide_virtual_gamepad() 'Does nothing on other platforms #IFDEF __FB_ANDROID__ SDL_ANDROID_SetScreenKeyboardShown(NO) #ENDIF END SUB LOCAL SUB internal_disable_virtual_gamepad() 'Does nothing on other platforms #IFDEF __FB_ANDROID__ io_sdl2_hide_virtual_gamepad allow_virtual_gamepad = NO #ENDIF END SUB SUB io_sdl2_remap_android_gamepad(byval player as integer, gp as GamePadMap) 'Does nothing on non-android #IFDEF __FB_ANDROID__ SELECT CASE player CASE 0 SDL_ANDROID_set_java_gamepad_keymap ( _ scOHR2SDL(gp.A, SDL_SCANCODE_RETURN), _ scOHR2SDL(gp.B, SDL_SCANCODE_ESCAPE), _ 0, _ scOHR2SDL(gp.X, SDL_SCANCODE_ESCAPE), _ scOHR2SDL(gp.Y, SDL_SCANCODE_ESCAPE), _ 0, _ scOHR2SDL(gp.L1, SDL_SCANCODE_PAGEUP), _ scOHR2SDL(gp.R1, SDL_SCANCODE_PAGEDOWN), _ scOHR2SDL(gp.L2, SDL_SCANCODE_HOME), _ scOHR2SDL(gp.R2, SDL_SCANCODE_END), _ 0, 0) CASE 1 TO 3 SDL_ANDROID_set_ouya_gamepad_keymap ( _ player, _ scOHR2SDL(gp.Ud, SDL_SCANCODE_UP), _ scOHR2SDL(gp.Rd, SDL_SCANCODE_RIGHT), _ scOHR2SDL(gp.Dd, SDL_SCANCODE_DOWN), _ scOHR2SDL(gp.Ld, SDL_SCANCODE_LEFT), _ scOHR2SDL(gp.A, SDL_SCANCODE_RETURN), _ scOHR2SDL(gp.B, SDL_SCANCODE_ESCAPE), _ scOHR2SDL(gp.X, SDL_SCANCODE_ESCAPE), _ scOHR2SDL(gp.Y, SDL_SCANCODE_ESCAPE), _ scOHR2SDL(gp.L1, SDL_SCANCODE_PAGEUP), _ scOHR2SDL(gp.R1, SDL_SCANCODE_PAGEDOWN), _ scOHR2SDL(gp.L2, SDL_SCANCODE_HOME), _ scOHR2SDL(gp.R2, SDL_SCANCODE_END), _ 0, 0) CASE ELSE debug "WARNING: io_sdl2_remap_android_gamepad: invalid player number " & player END SELECT #ENDIF END SUB SUB io_sdl2_remap_touchscreen_button(byval button_id as integer, byval ohr_scancode as integer) 'Pass a scancode of 0 to disabled/hide the button 'Does nothing on non-android #IFDEF __FB_ANDROID__ SDL_ANDROID_SetScreenKeyboardButtonDisable(button_id, (ohr_scancode = 0)) SDL_ANDROID_SetScreenKeyboardButtonKey(button_id, scOHR2SDL(ohr_scancode, 0)) #ENDIF END SUB FUNCTION io_sdl2_running_on_console() as bool #IFDEF __FB_ANDROID__ RETURN SDL_ANDROID_IsRunningOnConsole() #ENDIF RETURN NO END FUNCTION FUNCTION io_sdl2_running_on_ouya() as bool #IFDEF __FB_ANDROID__ RETURN SDL_ANDROID_IsRunningOnOUYA() #ENDIF RETURN NO END FUNCTION PRIVATE SUB update_mouse_visibility() DIM vis as integer IF mouse_visibility = cursorDefault THEN IF windowedmode THEN vis = 1 ELSE vis = 0 ELSEIF mouse_visibility = cursorVisible THEN vis = 1 ELSE vis = 0 END IF SDL_ShowCursor(vis) #IFDEF __FB_DARWIN__ ' FIXME: still true in SDL2? 'Force clipping in fullscreen, and undo when leaving, because you 'can move the cursor to the screen edge, where it will be visible 'regardless of whether SDL_ShowCursor is used. set_forced_mouse_clipping (windowedmode = NO AND vis = 0) #ENDIF END SUB SUB io_sdl2_setmousevisibility(visibility as CursorVisibility) mouse_visibility = visibility update_mouse_visibility() END SUB 'Used only if resizable_resolution true. FUNCTION windowsize_to_resolution(byval windowsz as XYPair) as XYPair 'Round upwards. TODO: This results in cut-off pixels around the screen edge, 'and ideally we would resize the window to a multiple of the resolution. RETURN large(min_window_resolution, XY(windowsz.w + zoom - 1, windowsz.h + zoom - 1) \ zoom) END FUNCTION 'Used only if resizable_resolution false. FUNCTION windowsize_to_ratio(byval windowsz as XYPair) as double RETURN small(windowsz.w / framesize.w, windowsz.h / framesize.h) END FUNCTION 'Get the origin of the displayed image, in unscaled window coordinates, 'and the actual zoom ratio in use (may differ from 'zoom', e.g. when full-screened) SUB get_image_origin_and_ratio(byref origin as XYPair, byref ratio as double) DIM windowsz as XYPair SDL_GetWindowSize(mainwindow, @windowsz.w, @windowsz.h) ratio = windowsize_to_ratio(windowsz) ' Subtract for the origin position since when the window is fullscreened and ' not resizable window the image will be centred on the screen. origin = (windowsz - framesize * ratio) / 2 END SUB 'Convert a position on the client area of the window (e.g. as returned by SDL_GetMouseState) 'to position in the original unscaled image 'TODO: do SDL_GetWindowSize and SDL_GetMouseState return in screen coords or pixel coords? 'Not clear, but they don't agree, this will bread on high-DPI displays FUNCTION windowpos_to_pixelpos(windowpos as XYPair, clamp as bool) as XYPair DIM origin as XYPair DIM ratio as double get_image_origin_and_ratio origin, ratio DIM pixelpos as XYPair 'Should to use INT here (the floor function), NOT CINT, which is round-to-nearest, 'rounding x.5 towards even, making cursor movement un-smooth at pixel-scale. pixelpos.x = INT((windowpos.x - origin.x) / ratio) pixelpos.y = INT((windowpos.y - origin.y) / ratio) IF clamp THEN pixelpos.x = bound(pixelpos.x, 0, framesize.w - 1) pixelpos.y = bound(pixelpos.y, 0, framesize.h - 1) END IF RETURN pixelpos END FUNCTION 'Convert a position on the original unscaled image to position in the client area of the window 'TODO: do SDL_GetWindowSize and SDL_GetMouseState return in screen coords or pixel coords? 'Not clear, but they don't agree, this will bread on high-DPI displays FUNCTION pixelpos_to_windowpos(pixelpos as XYPair) as XYPair DIM origin as XYPair DIM ratio as double get_image_origin_and_ratio origin, ratio RETURN origin + pixelpos * ratio + ratio / 2 END FUNCTION 'Change from SDL to OHR mouse button numbering (swap middle and right) PRIVATE FUNCTION fix_buttons(byval buttons as integer) as integer DIM mbuttons as integer = 0 IF SDL_BUTTON(SDL_BUTTON_LEFT) AND buttons THEN mbuttons = mbuttons OR mouseLeft IF SDL_BUTTON(SDL_BUTTON_RIGHT) AND buttons THEN mbuttons = mbuttons OR mouseRight IF SDL_BUTTON(SDL_BUTTON_MIDDLE) AND buttons THEN mbuttons = mbuttons OR mouseMiddle RETURN mbuttons END FUNCTION ' Returns currently down mouse buttons, in SDL order, not OHR order LOCAL FUNCTION update_mouse() as integer DIM x as int32 DIM y as int32 DIM buttons as int32 IF SDL_GetWindowFlags(mainwindow) AND SDL_WINDOW_MOUSE_FOCUS THEN buttons = SDL_GetMouseState(@privatempos.x, @privatempos.y) IF mouseclipped THEN 'SDL clips the mouse to the window, but we have to clip it within a smaller rect IF NOT in_bound(privatempos.x, mousebounds.p1.x, mousebounds.p2.x) ORELSE _ NOT in_bound(privatempos.y, mousebounds.p1.y, mousebounds.p2.y) THEN privatempos.x = bound(privatempos.x, mousebounds.p1.x, mousebounds.p2.x) privatempos.y = bound(privatempos.y, mousebounds.p1.y, mousebounds.p2.y) SDL_WarpMouseInWindow(mainwindow, privatempos.x, privatempos.y) END IF END IF END IF IF buttons = 0 THEN SDL_CaptureMouse(NO) 'Any mouse drag ended RETURN buttons END FUNCTION SUB io_sdl2_mousebits (byref mx as integer, byref my as integer, byref mwheel as integer, byref mbuttons as integer, byref mclicks as integer) DIM buttons as integer buttons = update_mouse() DIM pixelpos as XYPair = windowpos_to_pixelpos(privatempos, YES) 'clamp=YES '?"mouse at " & privatempos & "(window), " & pixelpos & "(px) mx = pixelpos.x my = pixelpos.y mwheel = mousewheel mclicks = fix_buttons(mouseclicks) mbuttons = fix_buttons(buttons or mouseclicks) mouseclicks = 0 END SUB SUB io_sdl2_getmouse(byref mx as integer, byref my as integer, byref mwheel as integer, byref mbuttons as integer) 'supports io_mousebits instead END SUB SUB io_sdl2_setmouse(byval x as integer, byval y as integer) DIM windowpos as XYPair = pixelpos_to_windowpos(XY(x, y)) '?"warp mouse to " & XY(x,y) & "(px) -> " & windowpos & "(window)" privatempos = windowpos IF SDL_GetWindowFlags(mainwindow) AND SDL_WINDOW_INPUT_FOCUS THEN SDL_WarpMouseInWindow mainwindow, windowpos.x, windowpos.y SDL_PumpEvents 'Needed for SDL_WarpMouse to work? #IFDEF __FB_DARWIN__ ' FIXME: still true in SDL2? ' SDL Mac bug (SDL 1.2.14, OS 10.8.5): if the cursor is off the window ' when SDL_WarpMouse is called then the mouse gets moved onto the window, ' but SDL forgets to hide the cursor if it was previously requested, and further, ' SDL_ShowCursor(0) does nothing because SDL thinks it's already hidden. ' So call SDL_ShowCursor twice in a row as workaround. SDL_ShowCursor(1) update_mouse_visibility() #ENDIF END IF END SUB LOCAL SUB internal_set_mouserect(rect as RectPoints) mouseclipped = (rect.p1.x >= 0) 'Grabs just mouse, not keyboard (WM combos?) unless SDL_HINT_GRAB_KEYBOARD set '(SDL_SetWindowMouseGrab is new in SDL 2.0.16) SDL_SetWindowGrab(mainwindow, mouseclipped) 'This uses the centers of the pixels as bounds... is that OK? mousebounds.p1 = pixelpos_to_windowpos(rect.p1) mousebounds.p2 = pixelpos_to_windowpos(rect.p2) update_mouse() 'Move mouse into the rect END SUB 'Update the mouse clip rectangle, either because it changed (or was enabled/disabled) or the window size changed LOCAL SUB update_mouserect() IF remember_mouserect.p1.x > -1 THEN internal_set_mouserect remember_mouserect ELSEIF forced_mouse_clipping THEN 'We're now meant to be unclipped, but clip to the window internal_set_mouserect TYPE<RectPoints>((0, 0), framesize.w - 1, framesize.h - 1) ELSE 'Unclipped: remember_mouserect == ((-1,-1),(-1,-1)) internal_set_mouserect remember_mouserect END IF END SUB 'This turns forced mouse clipping on or off LOCAL SUB set_forced_mouse_clipping(byval newvalue as bool) newvalue = (newvalue <> 0) IF newvalue <> forced_mouse_clipping THEN forced_mouse_clipping = newvalue update_mouserect END IF END SUB SUB io_sdl2_mouserect(byval xmin as integer, byval xmax as integer, byval ymin as integer, byval ymax as integer) 'Should we clamp the rect? remember_mouserect = TYPE<RectPoints>((xmin, ymin), (xmax, ymax)) update_mouserect END SUB PRIVATE FUNCTION scOHR2SDL(byval ohr_scancode as KBScancode, byval default_sdl_scancode as integer=0) as integer 'Convert an OHR scancode into an SDL scancode '(the reverse can be accomplished just by using the scantrans array) IF ohr_scancode = 0 THEN RETURN default_sdl_scancode FOR i as integer = 0 TO UBOUND(scantrans) IF scantrans(i) = ohr_scancode THEN RETURN i NEXT i RETURN 0 END FUNCTION SUB io_sdl2_set_clipboard_text(text as zstring ptr) 'ustring CheckOK(SDL_SetClipboardText(text)) END SUB FUNCTION io_sdl2_get_clipboard_text() as zstring ptr 'ustring RETURN SDL_GetClipboardText() END FUNCTION FUNCTION gfx_sdl2_setprocptrs() as integer gfx_init = @gfx_sdl2_init gfx_close = @gfx_sdl2_close gfx_getversion = @gfx_sdl2_getversion gfx_setpal = @gfx_sdl2_setpal gfx_screenshot = @gfx_sdl2_screenshot gfx_setwindowed = @gfx_sdl2_setwindowed gfx_windowtitle = @gfx_sdl2_windowtitle gfx_getwindowstate = @gfx_sdl2_getwindowstate gfx_get_screen_size = @gfx_sdl2_get_screen_size gfx_set_window_size = @gfx_sdl2_set_window_size gfx_supports_variable_resolution = @gfx_sdl2_supports_variable_resolution gfx_vsync_supported = @gfx_sdl2_vsync_supported gfx_get_resize = @gfx_sdl2_get_resize gfx_set_resizable = @gfx_sdl2_set_resizable gfx_recenter_window_hint = @gfx_sdl2_recenter_window_hint gfx_get_settings = @gfx_sdl2_get_settings gfx_set_settings = @gfx_sdl2_set_settings gfx_setoption = @gfx_sdl2_setoption gfx_describe_options = @gfx_sdl2_describe_options gfx_get_safe_zone_margin = @gfx_sdl2_get_safe_zone_margin gfx_set_safe_zone_margin = @gfx_sdl2_set_safe_zone_margin gfx_supports_safe_zone_margin = @gfx_sdl2_supports_safe_zone_margin gfx_ouya_purchase_request = @gfx_sdl2_ouya_purchase_request gfx_ouya_purchase_is_ready = @gfx_sdl2_ouya_purchase_is_ready gfx_ouya_purchase_succeeded = @gfx_sdl2_ouya_purchase_succeeded gfx_ouya_receipts_request = @gfx_sdl2_ouya_receipts_request gfx_ouya_receipts_are_ready = @gfx_sdl2_ouya_receipts_are_ready gfx_ouya_receipts_result = @gfx_sdl2_ouya_receipts_result io_init = @io_sdl2_init io_pollkeyevents = @io_sdl2_pollkeyevents io_waitprocessing = @io_sdl2_waitprocessing io_keybits = @io_sdl2_keybits io_updatekeys = @io_sdl2_updatekeys io_enable_textinput = @io_sdl2_enable_textinput io_textinput = @io_sdl2_textinput io_get_clipboard_text = @io_sdl2_get_clipboard_text io_set_clipboard_text = @io_sdl2_set_clipboard_text io_show_virtual_keyboard = @io_sdl2_show_virtual_keyboard io_hide_virtual_keyboard = @io_sdl2_hide_virtual_keyboard io_show_virtual_gamepad = @io_sdl2_show_virtual_gamepad io_hide_virtual_gamepad = @io_sdl2_hide_virtual_gamepad io_remap_android_gamepad = @io_sdl2_remap_android_gamepad io_remap_touchscreen_button = @io_sdl2_remap_touchscreen_button io_running_on_console = @io_sdl2_running_on_console io_running_on_ouya = @io_sdl2_running_on_ouya io_mousebits = @io_sdl2_mousebits io_setmousevisibility = @io_sdl2_setmousevisibility io_getmouse = @io_sdl2_getmouse io_setmouse = @io_sdl2_setmouse io_mouserect = @io_sdl2_mouserect io_get_joystick_state = @io_sdl2_get_joystick_state gfx_present = @gfx_sdl2_present RETURN 1 END FUNCTION #include "gfx_sdl_common.bas" END EXTERN