bohonghuang / 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
Apache License 2.0
39 stars 3 forks source link

+TITLE: claw-raylib

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

Fully auto-generate Common Lisp bindings to [[https://www.raylib.com/][Raylib]] (as well as Raymath, Rlgl and Raygui) using [[https://github.com/borodust/claw][claw]] and [[https://github.com/bohonghuang/cffi-object][cffi-object]].

Tip: You can clone the [[https://github.com/bohonghuang/claw-raylib/tree/prebuild][prebuild]] branch to skip steps 1 and 2.

  1. Clone, build and install [[https://github.com/borodust/libresect][libresect]], [[https://github.com/raysan5/raylib][raylib]] and [[https://github.com/raysan5/raygui][raygui]] (optional).
  2. Generate the bindings using Clozure CL. \

    +BEGIN_SRC lisp

    (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)

    +END_SRC

    /Notes for Windows:/

    Replace the line like:

    +BEGIN_SRC c

    include

    +END_SRC

    with:

    +BEGIN_SRC c

    include

    define GetModuleHandle GetModuleHandleA

    HMODULE GetModuleHandleA(LPCTSTR name); FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);

    +END_SRC

  3. Compile the adapters. \

    +BEGIN_SRC lisp

    (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))))))

    +END_SRC

  4. Load the system. \

    +BEGIN_SRC lisp

    (ql:quickload :claw-raylib)

    +END_SRC

    /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.

    +BEGIN_SRC lisp

    (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))))))

    +END_SRC

A complete [[file:examples/raygui/controls-test-suite.lisp][example]] of using ~claw-raylib~ to rewrite the [[https://github.com/raysan5/raygui/blob/master/examples/controls_test_suite/controls_test_suite.c][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 [[https://www.reddit.com/r/lisp/comments/ygebes/passing_c_struct_by_value_cffilibffi_is_250x/][this post]]) or heavy GC pressure when calling functions that accept C structures as parameters (which is the case for most functions in Raylib).

+BEGIN_SRC lisp

;;; 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

+END_SRC

** 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.

+BEGIN_SRC lisp

(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>

+END_SRC

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

+BEGIN_SRC lisp

(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)

+END_SRC

See the [[file:examples/][examples]] directory. To run all examples, eval this in your REPL:

+BEGIN_SRC lisp

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

+END_SRC

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