I’ve found that resources on getting started with SDL and OpenGL in common lisp to be lacking, beyond some basic examples on the cl-sdl2 repository, so I’m going to do a quick write-up on it.
Make sure you have quicklisp installed. Use the guide on their site. Next, install SDL2 using your system package manager or by building from source. Pick your poison. I won’t explain the process in this post, as there are many guides on this already. One great resource is lazyfoo’s hello world tutorial.
Ok, this is where things get interesting. You’ll need to install the cl-sdl2 package through quicklisp. The readme on the repository says that there is no quicklisp distribution, but the information is a little outdated, so that’s not valid anymore.
This is as easy as doing a
(ql:quickload "sdl2"), followed by a (ql:quickload "cl-opengl") for the cl-opengl package.
Alright, lets define our entry point. This is the function we will call to initialise our SDL game.
;; Load required libraries. (require :sdl2) (require :cl-opengl) (defun main () "The entry point of our game." (sdl2:with-init (:everything) (debug-log "Using SDL library version: ~D.~D.~D~%" sdl2-ffi:+sdl-major-version+ sdl2-ffi:+sdl-minor-version+ sdl2-ffi:+sdl-patchlevel+) (sdl2:with-window (win :flags '(:shown :opengl)) (sdl2:with-gl-context (gl-context win) ;; Basic window/gl setup (setup-gl win gl-context) ;; Run main loop (main-loop win #'render)))))
I’ve changed things up from the original basic.lisp example shown in the examples folder of the cl-sdl2 repository, by adding a little more structure. I’ve split the setup of the GL context and the main loop to its own function. Also, I’ve defined the function
debug-log to simplify logging to the standard output, rather than using
Here’s the rest:
(defun debug-log (msg &rest args) "Output and flush MSG to STDOUT with arguments ARGS" (apply #'format t msg args) ;; Flush to standard out (finish-output)) (defun setup-gl (win gl-context) "Setup OpenGL with the window WIN and the gl context of GL-CONTEXT" (debug-log "Setting up window/gl.~%") (sdl2:gl-make-current win gl-context) (gl:viewport 0 0 800 600) (gl:matrix-mode :projection) (gl:ortho -2 2 -2 2 -2 2) (gl:matrix-mode :modelview) (gl:load-identity) ;; Clear to black (gl:clear-color 0.0 0.0 0.0 1.0)) (defun render () (gl:clear :color-buffer) ;; Draw a demo triangle (gl:begin :triangles) (gl:color 1.0 0.0 0.0) (gl:vertex 0.0 1.0) (gl:vertex -1.0 -1.0) (gl:vertex 1.0 -1.0) (gl:end) (gl:flush)) (defun main-loop (win render-fn) "Run the game loop that handles input, rendering through the render function RENDER-FN, amongst others." (sdl2:with-event-loop (:method :poll) (:idle () (funcall render-fn) ;; Swap back buffer (sdl2:gl-swap-window win)) (:quit () t)))
:idle is called when no events are taking place, which is where we do our rendering.
:quit is pretty obvious; it’s called when a quit event is received. I feel like passing the render-fn as an argument is a better solution than calling it directly, since it increases the extensibility later on, though either way works.
Alright, now to run our “game”. I personaly use SBCL, so I’ll be explaining the process for that implementation, though it should be quite similar in other implementations too.
sbcl --load game.lisp on the command line should load the entire file in to the SBCL REPL, where we can easily start our game with a simple
(main) function call.
If everything went according to plan, you should see a window like this (maybe without all the OS X’y stuff):
For OS X users
Using OS X, however, sooner or later you will realise there is a small problem… With no buttons and whatnot, how do you exit this thing?
One possible solution ([and that] suggested in the SDL examples) is using capturing keyboard events and sending a :quit event on the release of the ESCAPE key. Another solution is to add the standard exit window button.
Neither method will work for now. For some reason key events aren’t handled on OS X and windows aren’t properly created. According to this issue, the problem can be resolved by using
(sdl2:make-this-thread-main #'main) on the SBCL REPL, but personally I haven’t had success with that so far.
Maybe other lisp implementations don’t have this issue; I’m not sure. If anyone has a solution to this, feel free to comment or email me and I’ll update this post.
So, after lots of coffee and a day long battle with SBCL I have found a partial solution; don’t use SBCL. I used Clozure CL with the same method and it worked out fine. So if you’re running on OS X and have the same issue as I do, I recommend using that implementation instead.