/claw-raylib

Fully auto-generated Common Lisp bindings to Raylib (4.5/5.0) and Raygui (3.0/4.0) using claw and cffi-object

Primary LanguageCommon LispApache License 2.0Apache-2.0

claw-raylib

https://github.com/raysan5/raylib/raw/master/logo/raylib_logo_animation.gif

Fully auto-generate Common Lisp bindings to Raylib (as well as Raymath, Rlgl and Raygui) using claw and cffi-object.

Installation

claw-raylib and some of its dependencies are not available in Quicklisp currently. You need to clone this repository and the following projects into the local-projects folder of your Quicklisp installation path:

Build

claw-raylib does not ship any prebuilt binary files. You need to ensure that Raylib, Raygui, and a compatible C compiler are installed on your system to manually build the adapters required by claw.

Tip: You can clone the prebuild branch to skip steps 1 and 2.

  1. Clone, build and install libresect, raylib and raygui (optional).
  2. Generate the bindings using Clozure CL.
    (ql:quickload :claw-raylib/gen)
    (pushnew :claw-regen-adapter *features*)
    (cffi:load-foreign-library #P"/path/to/libresect.so")
    (claw:load-wrapper :raylib)
    (claw:load-wrapper :raygui)
    (claw:load-wrapper :rlgl)
        

    Notes for Windows:

    Since windows.h contains many symbols that conflict with symbols in raylib.h and also includes the notorious near and far keywords, if you are developing on Windows or deploying an application for Windows, you need to manually perform the following operations on the generated libraylib-adapter.i686-pc-windows-gnu.c and libraylib-adapter.x86_64-pc-windows-gnu.c (whose parent path can be retrieved by evaluating (merge-pathnames #P"lib/" (asdf:component-pathname (asdf:find-system '#:claw-raylib))) in the Lisp REPL):

    1. Replace the line like:
      #include <windows.h>
              

      with:

      #include <minwindef.h>
      HMODULE GetModuleHandle(LPCTSTR name);
              
    2. Since minwindef.h still contains the near and far keywords, you also need to replace all near and far identifiers with identifiers like _near and _far. You can find them with RegExp \(near\|far\)\(,\|)\) and replace them with _\1\2 in Emacs.
  3. Compile the adapters.
    (let ((arch "x86_64-pc-linux-gnu")
          (path (merge-pathnames #P"lib/" (asdf:component-pathname (asdf:find-system '#:claw-raylib)))))
      (dolist (lib '("raylib" "rlgl" "raygui"))
        (uiop:run-program
         (list "gcc" "-O3" "-fPIC" "-shared" "-o"
               (namestring (merge-pathnames (format nil "lib~A-adapter.so" lib) path))
               (namestring (merge-pathnames (format nil "lib~A-adapter.~A.c" lib arch) path))))))
        
  4. Load the system.
    (ql:quickload :claw-raylib)
        

    Notes for SBCL:

    During this process, SBCL may consume a significant amount of memory, potentially leading to heap exhaustion. You may need to add --dynamic-space-size 4096 to the SBCL command-line arguments before the first load of claw-raylib.

Features

Up-to-date & Complete

Raylib, Rlgl, Raymath, and Raygui’s low-level and high-level APIs are automatically generated using claw and cffi-object, along with some tricks during the build process. You can use any released version or even the Git version of Raylib without changing the claw-raylib version, as claw-raylib can automatically generate the both levels of APIs. This also means that all APIs of these libraries are available, just like in C.

(let ((camera (raylib:make-camera-3d :position (raylib:make-vector3 :x 10.0 :y 10.0 :z 10.0)
                                     :target (raylib:vector3-zero)
                                     :up (raylib:make-vector3 :x 0.0 :y 1.0 :z 0.0)
                                     :fovy 33.3 :projection #.(cffi:foreign-enum-value 'raylib:camera-projection :perspective)))
      (model (raylib:load-model "/path/to/model"))
      (position (raylib:vector3-zero)))
  (raylib:with-window ("Simple Model Viewer" (1280 720))
    (loop :until (raylib:window-should-close)
          :do (raylib:with-drawing
                (raylib:update-camera camera #.(cffi:foreign-enum-value 'raylib:camera-mode :free))
                (raylib:with-mode-3d camera
                  (raylib:clear-background raylib:+raywhite+)
                  (raylib:draw-grid 100 1.0)
                  (rlgl:disable-backface-culling)
                  (raylib:draw-model model position 1.0 raylib:+white+)
                  (rlgl:enable-backface-culling))))))

A complete example of using claw-raylib to rewrite the control test suite from Raygui is available.

High-performance

Thanks to the adapters generated by claw, claw-raylib does not use cffi-libffi and incurs no expensive performance overhead (according to this post) or heavy GC pressure when calling functions that accept C structures as parameters (which is the case for most functions in Raylib).

;;; The overhead of FFI calls is no longer a performance bottleneck for the system.

;;            Self        Total        Cumul
;;   Nr  Count     %  Count     %  Count     %    Calls  Function
;; ------------------------------------------------------------------------
;;    1    261  32.2    261  32.2    261  32.2        -  foreign function rlVertex3f
;;    2    109  13.4    450  55.5    370  45.6        -  foreign function DrawTexturePro
;;    3     43   5.3     56   6.9    413  50.9        -  (LAMBDA (&OPTIONAL POSITION ORIGIN SCALE ROTATION TINT) :IN TILED-LAYER-RENDERER)
;;    4     31   3.8    277  34.2    444  54.7        -  foreign function rlVertex2f
;;    5     23   2.8     23   2.8    467  57.6        -  foreign function rlTexCoord2f
;;    6     18   2.2     18   2.2    485  59.8        -  foreign function __sched_yield
;;    7     16   2.0     19   2.3    501  61.8        -  foreign function rlSetTexture
;;    8     15   1.8    495  61.0    516  63.6        -  foreign function __claw_DrawTexturePro
;;    9     14   1.7     14   1.7    530  65.4        -  (LAMBDA (POSITION SCALE) :IN TILED-LAYER-RENDERER)
;;   10     11   1.4     11   1.4    541  66.7        -  foreign function rlBegin

High-level

claw-raylib utilizes cffi-object to automatically wrap Raylib’s types, allowing you to completely disregard memory concerns. All types from Raylib can be seamlessly integrated into CLOS, and the API style remains highly similar to Common Lisp, and for all structure parameters in FFI functions, cffi-object objects are passed by default instead of raw pointers, greatly reducing the disconnect often associated with cross-language interoperations.

(raylib:vector2-normalize
 (raylib:vector2-add
  (raylib:make-vector2 :x 1.0 :y 2.0)
  (raylib:vector2-one)))
;; => #<VECTOR2 :X 0.5547002 :Y 0.8320503 @0x00007FF59C000D70>

(raylib:fade (raylib:color-brightness (raylib:get-color #xCE42EFFF) -0.5) 0.5)
;; => #<COLOR :R 103 :G 33 :B 119 :A 127 @0x00007FF59C000E50>

(defgeneric vector-add (v1 v2))

(defmethod vector-add ((v1 raylib:vector2) (v2 raylib:vector2))
  (raylib:vector2-add v1 v2))

(defmethod vector-add ((v1 raylib:vector3) (v2 raylib:vector3))
  (raylib:vector3-add v1 v2))

(defmethod vector-add ((v1 raylib:vector4) (v2 raylib:vector4))
  (raylib:quaternion-add v1 v2))

(vector-add (raylib:vector3-one) (raylib:vector3-one))
;; => #<VECTOR3 :X 2.0 :Y 2.0 :Z 2.0 @0x00007FF59C000ED0>

Low-level

In performance-intensive scenarios, directly using the low-level functions exposed by claw-raylib (whose names are prefixed with %) in conjunction with cffi-ops for GC-free programming is a better choice. Modules written using this approach can achieve performance levels close to that of C.

(use-package :cffi-ops)

(defun camera-3d-normalize (camera)
  (declare (optimize (speed 3)
                     (debug 0)
                     (safety 0)))
  (clet* ((camera (cthe (:pointer (:struct raylib:camera-3d)) (& camera)))
          (up (& (-> camera raylib:up)))
          (right up)
          (look (foreign-alloca '(:struct raylib:vector3)))) ; Stack memory allocation
    (raylib:%vector3-subtract look (& (-> camera raylib:target)) (& (-> camera raylib:position)))
    (raylib:%vector3-cross-product right look up)
    (raylib:%vector3-cross-product up right look)
    (raylib:%vector3-normalize up up))
  camera)

Examples

examples/screenshots/controls-test-suite.png

See the examples directory. To run all examples, eval this in your REPL:

(ql:quickload :claw-raylib/examples)
(do-external-symbols (symbol :claw-raylib.examples)
  (funcall symbol))

Opening a PR for contributions is welcome. Encountering any problem, feel free to open an issue.

Related Projects

  • cl-raylib: A manually written Raylib binding that uses 3d-matrics and 3d-vectors as the math library. It offers high usability but is not suitable for performance-intensive scenarios due to its use of cffi-libffi. For example, it may not be suitable for a Tiled map renderer that requires pretty frequent calls to DrawTexturePro.
  • claylib: A game framework based on Raylib that provides practical features for game development, such as scenes and interactive programming. It also utilizes claw to automatically generate low-level bindings for Raylib and then manually writes the exported high-level APIs. claw-raylib, on the other hand, is just a Lispy Raylib wrapper that does not offer any functionality beyond Raylib itself. It is suitable for cases where you only want to use Raylib or develop your own game engine/framework based on Raylib in Common Lisp.

Projects using claw-raylib

  • eon: An easy-to-use but flexible game framework for Common Lisp.
  • cl-universal-tween-engine: Common Lisp port of the Universal Tween Engine, whose demo is made by claw-raylib.