This is a fork of hu.dwim.sdl. The details are in the subsections below, but here's the summary:
hu.dwim.sdl.ffiwas removed in favor of separate packages, one for each sdl module.- Everything is exported in the generated files.
- Generated names are lispy.
- Automatic boolean conversion and error checking of return values (where applicable).
- Convenience macros for working with structs and slots.
New dependencies: cl-ppcre for regexps and parachute for some tests.
Also, now, the generated files rely on stuff from the hu.dwim.sdl package. I
don't think this to be a big deal, though.
- SDL 2.28.3
- TTF 2.20.2
- GFX 1.0.4
- Image 2.6.3
(note that you can generate the specs yourself if you want to)
hu.dwim.sdl/core-- bindings for SDL2,hu.dwim.sdl/gfx-- bindings for SDL2_gfx,hu.dwim.sdl/ttf-- bindings for SDL2_ttf,hu.dwim.sdl/image-- bindings for SDL_image,hu.dwim.sdl-- here reside the utilities for generation in addition to some high-level API that doesn't belong to any particular SDL package).
hu.dwim.sdl-- containshu.dwim.sdlandhu.dwim.sdl/corepackages.hu.dwim.sdl/gfxhu.dwim.sdl/ttfhu.dwim.sdl/image
Instead of |SDL_CreateWindow| you now simply write create-window. See
ffi-name-transformer in name-translation for
details, but basically:
- all underscores are replaced with dashes;
- camel case is converted to dashes;
- Abbreviations are treated nicely (such as
GL,GUID,RGBAetc.); - all constants and enum members are braced with pluses, e.g.
+have-pow+; SDL_,TTF_,GFX_,IMG_prefixes are removed;_SDL_,_TTF_,_GFX_,_IMG_prefixes are converted to_;SDLK_prefix is converted toK_;- and these table substitutions take place:
SDL_log->LOG*SDL_PRIX64->+PRI-X64*+.
The autogenerated convenience macros/functions reside in the same package
with the rest of the generated code. This means there's a possibility of name
clashes were SDL to introduce something that's named, say, with-rect. That, I
think, is very unlikely, but in case it happens, the name of the newly
introduced offending function will be renamed (suffixed, probably).
See the Convenience macros and functions for info on these.
It wouldn't be bad if CFFI could generate multiple functions per one C function,
or even simply give an option to include the original unmodified function but
with a custom name e.g. init and %init. This could be useful when one
doesn't want the overhead of conversion or error checking. Alternatively,
another file could be generated with its own CFFI options.
There aren't any GC additions so far, though.
Everything is exported, except:
- [TTF]
get-font-kerning-size, which is deprecated (useget-font-kerning-size-glyphsinstead).
Functions in SDL2 return error codes, and there are several types. They aren't indicated in any way, other than on wiki, and that's what I did. Some were easy to pick with regexps, others I looked through manually. The assortment is exhaustive and is meant to stay that way.
It should have been easier has this issue been resolved, filed for the exact same purpose of this library, but it hasn't been.
The following expansions of return values take place:
- NULL signals an error
- certain enum value signals an error
- constant (such as 0) signals an error
- negative number signals an error
- string starts with something indicating an error
- bool-like value like
SDL_booland where0indicates false are converted tonilon0(equalsSDL_FALSE) andtotherwise. - same as above, but where false signals an error
These are fairly easy to add should there be more cases.
The values are expanded based on the actual return type of the function - the expansion methods are defined at the time of return type name conversion and the code with custom type details is inserted into the generated file, right before the function definition in question.
Each function has it's own error condition: <function-name>-error and it is
exported. All error conditions inherit from hu.dwim.sdl:sdl-error. The actual
errors are quite informative, naming the return value, its type, and showing
the SDL_GetError output.
Finding out if a function does any error checking / conversion is simple: go to
its definition and look at the return type. The typedefs for return values are
unique for each function with custom expansion to make it possible to set up an
expand method individually. These typedefs have the format: <function name>/<expansion procedure>/<actual type>. It will be clear from the
<expansion procedure> what you are dealing with. Otherwise you shouldn't
really have to know or care about this long type name, the actual type should be
enough.
Another way is to go to type-conversion-lists and see for yourself.
I skipped most of the functions listed at https://wiki.libsdl.org/ToDo, which
includes C function wrappers/equivalents (e.g. SDL_sscanf). Some functions I
wasn't sure about. Some weren't documented. See hu.dwim.sdl::*skip/core* for
details. However, I didn't skip the GameController functions (they are a
draft) as they had documentation.
Also, SDL_DequeueAudio and SDL_RWwrite are exceptions: the user passes a
number and they return another number, and if the number returned is less than
the one passed, that's an error and the user calls SDL_GetError. I don't think
C2FFI supports this case. Anyway, I redefined these manually with error checks
in core-extras. I haven't tested them and, in fact, the
current specs are out of date and don't have them at all. Unmodified versions
prefixed with % should be available for these.
There are many functions, some were picked with regexps, others were picked by hand, some could've been misjudged: there could have been errors. If something doesn't work, always make sure to go and check the generated definition along with the relevant SDL documentation.
As a precaution, if you generate new specs with new functions in the sense that they aren't in the conversion lists, you will get warned about them, and if those functions have unknown abbreviations, you will be told about that as well.
wiki is unclear on these:
SDL_AudioStreamFlush: int, no mention of errorSDL_GetAudioDeviceStatus: wiki shows gibberishSDL_JoystickGetType: no error info on wikiSDL_GameControllerGetTouchpadFinger: int, but wiki gives no info on return value
TODO these return structs that sometimes indicate an error condition:
SDL_GameControllerGetBindForButton: returns a struct, but wiki assumes enumSDL_GameControllerGetBindForAxis: returns a struct, but wiki assumes enumSDL_JoystickGetGUID: a 0-GUID is returned, not checking for it nowSDL_JoystickGetDeviceGUID: a 0-GUID is returned, not checking for it nowSDL_GetPrimarySelectionText: returns empty string on failure (error)
Instead of allocating foreign objects to pass to a function for value return,
make a wrapping to do it for you. For example,ttf-size-text takes pointers to
width and height variables which it will set, our goal is to make a function
ttf-size-text* that does it for us, eliminating the need for these arguments:
(with-foreign-objects ((w :int) (h :int))
(ttf-size-text font text w h)
(values (mem-ref w :int) (mem-ref h :int)))
becomes simply
(ttf-size-text* font text)
For the list of functions for which this is done (and for a place to add more), see other-lists.lisp.
In place of cffi:foreign-alloc + cffi:with-foreign-slots and setf'ing the
slots yourself, you can just do this instead:
(let ((r (sdl:make-rect :x 0 :y 0 :w 10 :h 10)))
;; ...
(cffi:foreign-free r))
or this, if you want to leave some slots uninitialized:
(let ((r (sdl:make*rect)))
;; ...
(cffi:foreign-free r))
The following are like the make constructs above, but cffi:foreign-free is
done automatically:
(sdl:with-rect (r :x 0 :y 0 :w 10 :h 10)
;; ...
)
(sdl:with*rect (r)
;; ...
)
These are just like with-<struct-name> and with*<struct-name> except they
are for multiple forms:
(sdl:with-point* ((point-a :x 0 :y 0)
(point-b :x 5 :y 5))
;; ...
)
Example: (with-window w (<args to create-window>) <body>) binds w to the
result of create-window, executes the body in unwind-protect clause, which
finishes with a call to destroy-window.
The name of the macro is built by throwing away the SDL_Create part of the
function, except in these cases:
SDL_GL_CreateContext-->with-gl-contextSDL_Metal_CreateView-->with-metal-view.
See other-lists.lisp for the list of these macros.
This macro lets you easily access slots of a struct. Here's an example:
(defpackage #:sdl2-example
(:use :cl)
(:local-nicknames (sdl hu.dwim.sdl/core))
(:import-from #:hu.dwim.sdl #:with-sdl-slots #:with-sdl-slots*))
(in-package :sdl2-example)
(sdl:with-point (p :x 0 :y 1)
(with-sdl-slots ((x y) p point)
(values x y))) ; => 0, 1
Just like with cffi:with-foreign-slots, you can use (:pointer <type>)
syntax:
(sdl:with-point (p :x 0 :y 1)
(with-sdl-slots (((x0 x) (y0 (:pointer y))) p point)
(values x0 y0))) ; => 0, #.(SB-SYS:INT-SAP #X7F9AEE437FF4)
Multiplexed version is also available:
(sdl:with-point* ((point-a :x 0 :y 1)
(point-b :x 2 :y 3))
(with-sdl-slots* ((((x0 x) (y0 y)) point-a point)
(((x1 x) (y1 y)) point-b point))
(values x0 y0 x1 y1))) ; => 0, 1, 2, 3
Note that with cffi:with-foreign-slots you would have to name the package like
this:
(sdl:with-point (a :x 0 :y 5)
(cffi:with-foreign-slots ((sdl:x sdl:y) a (:struct sdl:rect))
(values sdl:x sdl:y)))
In contrast, with-sdl-slots finds rect in one of the sdl packages at
macroexpansion time, assumes x and y to be in the package where rect was
found and sets up a symbol-macrolet to replace x with hu.dwim.sdl/core:x
and y with hu.dwim.sdl/core:y.
The following example opens a window and shows a rectangle for two seconds:
(defpackage #:sdl2-example
(:use :cl)
(:local-nicknames (sdl hu.dwim.sdl/core)))
(in-package :sdl2-example)
(progn
(sdl:init sdl:+init-video+)
(sdl:with-window window ("a square"
sdl:+windowpos-undefined+ sdl:+windowpos-undefined+
0 0
(logior sdl:+window-resizable+ sdl:+window-fullscreen-desktop+))
(sdl:with-renderer renderer (window -1 sdl:+renderer-accelerated+)
(dotimes (time 120)
;; clear
(sdl:set-render-draw-color renderer 255 255 255 255)
(sdl:render-clear renderer)
;; draw a rectangle
(sdl:set-render-draw-color renderer 0 0 0 255)
(multiple-value-bind (w h) (sdl:get-window-size* window)
(sdl:with-rect (rect :x (- (floor w 2) 100)
:y (- (floor h 2) 100)
:w 200
:h 200)
(sdl:render-draw-rect renderer rect)))
;; show
(sdl:render-present renderer)
(sdl:delay 16))))
(sdl:quit))
The following example opens a window and shows a rectangle for two seconds, but
uses cffi:with-foreign-array, cffi::foreign-array-type and
gfx:aapolygon-rgba. gfx:aapolygon-rgba wants CFFI arrays for arguments.
(TODO perhaps there's a simpler way to accomplish, some function in CFFI?)
(progn
(sdl:init sdl:+init-video+)
(sdl:with-window window ("another square"
sdl:+windowpos-undefined+ sdl:+windowpos-undefined+
0 0
(logior sdl:+window-resizable+ sdl:+window-fullscreen-desktop+))
(sdl:with-renderer renderer (window -1 sdl:+renderer-accelerated+)
(dotimes (time 120)
;; clear
(sdl:set-render-draw-color renderer 255 255 255 255)
(sdl:render-clear renderer)
(let ((type (make-instance 'cffi::foreign-array-type :element-type :int16 :dimensions '(4))))
(cffi:with-foreign-array (a (make-array '(4) :initial-contents
'(0 100 100 0) :element-type '(signed-byte 16))
type)
(cffi:with-foreign-array (b (make-array '(4) :initial-contents
'(0 0 100 100) :element-type '(signed-byte 16))
type)
(gfx:aapolygon-rgba renderer
a b
4
0 0 0 255))))
;; show
(sdl:render-present renderer)
(sdl:delay 16))))
(sdl:quit))
API is not frozen.
I think everything is pretty solid, but there may be errors in judgement about the functions in the conversion lists: there could be some stray functions that may not really need error checking or a bool conversion, or, on the contrary, those that may. If there's a clear cut case of such an error that you find, don't rely on it, it will be likely considered a bug and will be fixed. So, please, report dubious cases.
All the breaking changes will be listed in this file.
- Definitions for
SDL_DequeueAudioandSDL_RWwritewere done manually to add special cases of error checking (see above about them), but I didn't test them. The original definitions should be available with % prefix, though. - This fork relies on support prologue code pull request being merged in
cffi. The change is only a few lines of code and shouldn't be a problem. set-window-shapeissues warnings on+invalid-shape-argument+and+nonshapeable-window+, because these are list later than the function (and are needed for enum error checking) in the c2ffi output. See rpav/c2ffi#28 for a feature request on this.- The unfinished/undecide list of functions listed above.
To add some details to the How section below, the libraries listed above all
use cl-autowrap and this library is using
cffi with its ASDF-integrated
c2ffi. They both use the c2ffi utility to generate the specs, but to the best
of my knowledge, cffi has these advantages:
- it generates a file which you can "goto definition", while
cl-autowrapprovides a macro, which isn't so easy to work with, and - it provides a way to build compiler-macro return value expansions (used extensively in this project for return value conversion and checking).
It's a Common Lisp FFI for http://libsdl.org/ (SDL2).
The alternative projects are partial, while this one uses cffi/c2ffi to automatically generate the complete CFFI bindings for the various subsystems of SDL2.
It only requires vanilla CFFI when used, no extra dependencies.
Written by attila@lendvai.name.
The primary communication channel is the facilities on the project's GitHub page.
This project uses CFFI/C2FFI, whose ASDF extension does two things:
-
When needed, it can invoke c2ffi to process a C header file and emit a c2ffi spec file (a json file) that contains every detail needed to generate an FFI for a given platform. But yours truely has run this phase, and checked in the resulting spec files into the c2ffi-spec/ directory. This way users don't need to have a working c2ffi executable, nor the SDL dev headers installed.
-
Based on the above mentioned spec file, it generates the CFFI forms into a lisp file (placed next to the spec file), and continues as if it was just another lisp file written by hand. (These lisp files could also be committed into the repo, but for now they are not, because their regeneration is automatic and painless.)
Has FFI for sdl.h, sdl-gfx.h, sdl-ttf.h, and sdl-image.h.