SDL2 Erlang NIF.
This project tries to implement SDL2 and some of its extensions in one convenient NIF. The supported versions and features are listed below.
-
SDL >= 2.0.5
-
SDL_image 2.0.3
-
SDL_ttf 2.0.14
The implementation is cut into sections corresponding to the public headers.
-
'SDL.h'
-
'SDL_blendmode.h'
-
'SDL_clipboard.h'
-
'SDL_cpuinfo.h'
-
'SDL_filesystem.h'
-
'SDL_keyboard.h'
-
'SDL_keycode.h'
-
'SDL_mouse.h'
-
'SDL_platform.h'
-
'SDL_power.h'
-
'SDL_rect.h'
-
'SDL_scancode.h'
-
'SDL_events.h': The following events and functions are missing:
-
SDL_SYSWMEVENT
-
SDL_TEXTEDITING
-
SDL_TEXTINPUT
-
SDL_JOYAXISMOTION
-
SDL_JOYBALLMOTION
-
SDL_JOYHATMOTION
-
SDL_JOYBUTTONDOWN
-
SDL_JOYBUTTONUP
-
SDL_JOYDEVICEADDED
-
SDL_JOYDEVICEREMOVED
-
SDL_CONTROLLERAXISMOTION
-
SDL_CONTROLLERBUTTONDOWN
-
SDL_CONTROLLERBUTTONUP
-
SDL_CONTROLLERDEVICEADDED
-
SDL_CONTROLLERDEVICEREMOVED
-
SDL_CONTROLLERDEVICEREMAPPED
-
SDL_FINGERDOWN
-
SDL_FINGERUP
-
SDL_FINGERMOTION
-
SDL_DOLLARGESTURE
-
SDL_DOLLARRECORD
-
SDL_MULTIGESTURE
-
SDL_DROPFILE
-
SDL_DROPTEXT
-
SDL_DROPBEGIN
-
SDL_DROPCOMPLETE
-
SDL_AUDIODEVICEADDED
-
SDL_AUDIODEVICEREMOVED
-
SDL_PeepEvents
with action argumentSDL_ADDEVENT
-
SDL_PushEvents
-
SDL_SetEventFilter
-
SDL_GetEventFilter
-
SDL_AddEventWatch
-
SDL_DelEventWatch
-
SDL_FilterEvents
-
SDL_EventState
andSDL_GetEventState
-
SDL_RegisterEvents
-
-
'SDL_hints.h': We only have a proof of concept callback system.
-
'SDL_pixels.h': Only the pixel format enum and conversion function is defined.
-
'SDL_render.h': The following elements are missing:
-
SDL_TextureAccess
enum -
SDL_TextureModulate
enum -
SDL_CreateWindowAndRenderer
is currently located insdl_window
. Move it? -
SDL_CreateSoftwareRenderer
(renderer) -
SDL_CreateTexture
(texture) -
SDL_QueryTexture
(texture) -
SDL_UpdateTexture
(texture) -
SDL_UpdateYUVTexture
(texture) -
SDL_LockTexture
(texture) -
SDL_UnlockTexture
(texture) -
SDL_SetRenderTarget
(renderer) -
SDL_GetRenderTarget
(renderer) -
SDL_RenderIsClipEnabled
(renderer) -
SDL_RenderReadPixels
(renderer)
-
-
'SDL_stdinc.h': Erlang does not come with the functions
copysign
andscalbn
. -
'SDL_surface.h': Only surface creation (via
IMG_Load
) and destruction is implemented. Might be better to move IMG_* functions in their own space.-
Everything in this header can be implemented as simple functions (as opposed to cast/call).
-
We’ll need to add functions to retrieve the pixel format and possibly pitch (though format+dimensions is enough to get it).
-
We’ll need to add extra functions for direct access to the pixels.
-
-
'SDL_ttf.h':
-
A function to obtain the SDL_ttf version could be useful
-
TTF_Linked_Version
(if it makes sense, depends on how the library is loaded) -
TTF_OpenFontRW
(unclear if we need it) -
TTF_OpenFontIndexRW
(unclear if we need it)
-
-
'SDL_version.h':
SDL_GetRevisionNumber
must be implemented. The macros may also be useful. -
'SDL_video.h': The following elements are missing:
-
SDL_WINDOWPOS_*
values for different displays -
SDL_GetWindowSurface
(window) -
SDL_UpdateWindowSurface
(window) -
SDL_UpdateWindowSurfaceRects
(window)
-
-
'SDL_audio.h'
-
'SDL_error.h' (for completion)
-
'SDL_gamecontroller.h'
-
'SDL_gesture.h'
-
'SDL_haptic.h'
-
'SDL_joystick.h'
-
'SDL_messagebox.h'
-
'SDL_rwops.h' (unclear if we need it)
-
'SDL_shape.h'
-
'SDL_system.h'
-
'SDL_syswm.h'
-
'SDL_timer.h' (unclear if we need it)
-
'SDL_touch.h'
Other SDL extensions need to be investigated and implemented. We definitely want at least some of SDL_image and SDL_mixer. We probably do not need SDL_net or SDL_rtf.
For OpenGL we need to figure out whether we can call the functions from wxErlang. If we can, great! If not, find an automated way to provide access to OpenGL.
The following functions are related to OpenGL and Vulkan and still need to be implemented:
-
'SDL_render.h':
-
SDL_GL_BindTexture
-
SDL_GL_UnbindTexture
-
-
'SDL_video.h':
-
SDL_GL_LoadLibrary
(unclear if we need it) -
SDL_GL_GetProcAddress
(unclear if we need it) -
SDL_GL_UnloadLibrary
(unclear if we need it) -
SDL_GL_ExtensionSupported
-
SDL_GL_ResetAttributes
-
SDL_GL_SetAttribute
-
SDL_GL_GetAttribute
-
SDL_GL_MakeCurrent
-
SDL_GL_GetCurrentWindow
-
SDL_GL_GetCurrentContext
-
SDL_GL_GetDrawableSize
-
SDL_GL_SetSwapInterval
-
SDL_GL_GetSwapInterval
-
-
'SDL_vulkan.h'
These don’t make a lot of sense for Erlang.
-
'SDL_assert.h'
-
'SDL_atomic.h'
-
'SDL_bits.h'
-
'SDL_endian.h'
-
'SDL_events.h': the functions
SDL_WaitEvent
andSDL_WaitEventTimeout
are blocking. -
'SDL_loadso.h'
-
'SDL_log.h'
-
'SDL_main.h'
-
'SDL_mutex.h'
-
'SDL_quit.h' (only necessary when using
SDL_Main
?) -
'SDL_stdinc.h': only a few functions are implemented, others are not interesting.
-
'SDL_thread.h'
-
'SDL_ttf.h': the rendering functions for
Text
andUNICODE
are not interesting because they internally call theUTF8
functions so we may as well provide utf8-encoded binaries directly. TheTTF_ByteSwappedUNICODE
also falls in this category and is not provided. -
'SDL_video.h': the functions
SDL_CreateWindowFrom
,SDL_SetWindowData
andSDL_GetWindowData
take external data as argument.
These are either private headers, duplicated OpenGL/Vulkan headers or simply deprecated.
-
'SDL_config.h'
-
'SDL_config_android.h'
-
'SDL_config_iphoneos.h'
-
'SDL_config_macosx.h'
-
'SDL_config_minimal.h'
-
'SDL_config_pandora.h'
-
'SDL_config_psp.h'
-
'SDL_config_windows.h'
-
'SDL_config_winrt.h'
-
'SDL_config_wiz.h'
-
'SDL_copying.h'
-
'SDL_egl.h'
-
'SDL_name.h'
-
'SDL_opengl.h'
-
'SDL_opengl_glext.h'
-
'SDL_opengles.h'
-
'SDL_opengles2.h'
-
'SDL_opengles2_gl2.h'
-
'SDL_opengles2_gl2ext.h'
-
'SDL_opengles2_gl2platform.h'
-
'SDL_opengles2_khrplatform.h'
-
'SDL_revision.h'
-
'SDL_test.h'
-
'SDL_test_assert.h'
-
'SDL_test_common.h'
-
'SDL_test_compare.h'
-
'SDL_test_crc32.h'
-
'SDL_test_font.h'
-
'SDL_test_fuzzer.h'
-
'SDL_test_harness.h'
-
'SDL_test_images.h'
-
'SDL_test_log.h'
-
'SDL_test_md5.h'
-
'SDL_test_memory.h'
-
'SDL_test_random.h'
-
'SDL_types.h'
-
'begin_code.h'
-
'close_code.h'
SDL2 has a number of callback interfaces. While we probably do not want to implement some of them (like the OS-specific callbacks for Windows and iOS) we do need others.
Callbacks that have no return value are easy to implement. The idea is to have an Erlang process that waits for messages containing the callback MFA to execute. The following callbacks have no return value and no other caveat:
-
SDL_AudioCallback
-
SDL_iOSSetAnimationCallback
(iOS) -
SDL_SetWindowsMessageHook
(Windows)
The callback functions for hints do not have a return value
either, but they have an extra caveat: there can be more
than one per hint. SDL2 identifies these callbacks with the
tuple (callback, userdata)
and we need to give SDL2 this
same tuple in order to remove the callback.
The best way to handle this is probably to do it mostly via
Erlang where a process would take care of the callbacks and
would enable/disable the SDL2 callbacks when required. The
userdata
would in this case always be NULL
since all the
handling would be done from the Erlang side.
The alternative would be to create a resource per callback that the user would have to keep around and that’s not very convenient.
The functions in question are:
-
SDL_AddHintCallback
-
SDL_DelHintCallback
Other callbacks have a return value but otherwise work in a slightly different manner from each other. The callback can be invoked in a similar manner to others, by sending a message to the Erlang code. The difficulty comes in returning the result to the NIF code. The solution for doing that will vary depending on the callback in question.
There can be one window hit test callback per window. This means we can use the window’s user data for storing the result and then signal the NIF to read from it using the NIF mutex/cond mechanism. Both of those can also be stored in the window’s user data.
This means the window must be sent to Erlang and passed back to the NIF when giving the result back, which should be trivial. The callback data can stay empty since we store everything in the window data.
The function in question is:
-
SDL_SetWindowHitTest
The final set of callbacks is timers. When you add a timer
it returns a TimerID
and you can use it to remove the
timer. In addition, the callback can decide to change the
timer interval or to stop the timer. Unlike for windows
there is no way to attach information to a TimerID
so
a separate solution will be necessary. Since there can
be any number of timers and they can fire off at any
time then some kind of queue will be necessary to store
return values.
Even though we don’t know the TimerID
when setting up
the timer, we should be able to keep it around in the
same data structure used for the callback extra parameter.
There is however the concern of memory allocation: we
will probably need to hook into all functions that can
remove timers to make sure we free the memory we allocated
too.
They’re the hardest callback functions to implement, but thankfully they’re also some of the least interesting considering Erlang already comes with many ways to deal with timers.
Even if we do implement them, their scope may be reduced so that we always return the same interval as a return value and therefore don’t allow changing the interval or stopping the timer from inside a timer callback.
The functions in question are:
-
SDL_AddTimer
-
SDL_RemoveTimer
Other than hints, it should be possible to have a common mechanism for all callbacks. The following messages may be sent from the NIF:
-
{callback, M, F, Args}
forvoid
-
{callback, M, F, Args, ResF, ResExtraArg}
for others
The esdl2:ResF(Result, ResExtraArg)
function would be
called in the second case after the callback returns.
The NIF function can then decide what the appropriate
behavior is for sending the result back to the SDL2
callback.
The main concern when dealing with SDL2 callbacks is the memory allocations since SDL2 will not free the memory we allocate. Solutions should be extra careful not to introduce leaks and try to avoid allocating memory entirely for callbacks. When not possible then the memory must be allocated and freed in the course of running the Erlang callback and not be kept any longer.