siam-farmer/src/rl.clj

424 lines
15 KiB
Clojure

(ns rl
"Hand-written raylib bindings via coffi/Panama. Only what sand needs.
Struct layouts are written against raylib 6.0's raylib.h -- a mismatch here is
silent memory corruption, not an error, so check src/raylib.h before bumping."
(:require
[coffi.mem :as mem :refer [defalias]]
[coffi.ffi :as ffi :refer [defcfn]])
(:import
(java.lang.foreign Arena MemorySegment)))
(ffi/load-library (or (System/getProperty "raylib.path")
"libraylib.so.600"))
;;; ---------------------------------------------------------------- primitives
;; coffi's ::mem/byte is signed; raylib's Color fields are unsigned char, so 230
;; would overflow on the way in. Round-trip through unchecked-byte instead.
(defmethod mem/primitive-type ::ubyte [_type] ::mem/byte)
(defmethod mem/serialize* ::ubyte [obj _type _scope] (unchecked-byte obj))
(defmethod mem/deserialize* ::ubyte [obj _type] (Byte/toUnsignedLong obj))
;; C bool is one byte.
(defmethod mem/primitive-type ::bool [_type] ::mem/byte)
(defmethod mem/serialize* ::bool [obj _type _scope] (byte (if obj 1 0)))
(defmethod mem/deserialize* ::bool [obj _type] (not (zero? obj)))
;;; ------------------------------------------------------------------- structs
(defalias ::color
[::mem/struct [[:r ::ubyte] [:g ::ubyte] [:b ::ubyte] [:a ::ubyte]]])
(defalias ::vector-2
[::mem/struct [[:x ::mem/float] [:y ::mem/float]]])
(defalias ::rectangle
[::mem/struct [[:x ::mem/float] [:y ::mem/float]
[:width ::mem/float] [:height ::mem/float]]])
(defalias ::texture
[::mem/struct [[:id ::mem/int] [:width ::mem/int] [:height ::mem/int]
[:mipmaps ::mem/int] [:format ::mem/int]]])
(defalias ::image
[::mem/struct [[:data ::mem/pointer] [:width ::mem/int] [:height ::mem/int]
[:mipmaps ::mem/int] [:format ::mem/int]]])
(defalias ::font
[::mem/struct [[:base-size ::mem/int] [:glyph-count ::mem/int] [:glyph-padding ::mem/int]
[:texture ::texture] [:recs ::mem/pointer] [:glyphs ::mem/pointer]]])
(defalias ::n-patch-info
[::mem/struct [[:source ::rectangle] [:left ::mem/int] [:top ::mem/int]
[:right ::mem/int] [:bottom ::mem/int] [:layout ::mem/int]]])
;;; ----------------------------------------------------------------- constants
;; KeyboardKey, raylib.h.
(def ^:const key-null 0)
(def ^:const key-apostrophe 39)
(def ^:const key-comma 44)
(def ^:const key-minus 45)
(def ^:const key-period 46)
(def ^:const key-slash 47)
(def ^:const key-zero 48)
(def ^:const key-one 49)
(def ^:const key-two 50)
(def ^:const key-three 51)
(def ^:const key-four 52)
(def ^:const key-five 53)
(def ^:const key-six 54)
(def ^:const key-seven 55)
(def ^:const key-eight 56)
(def ^:const key-nine 57)
(def ^:const key-semicolon 59)
(def ^:const key-equal 61)
(def ^:const key-a 65)
(def ^:const key-b 66)
(def ^:const key-c 67)
(def ^:const key-d 68)
(def ^:const key-e 69)
(def ^:const key-f 70)
(def ^:const key-g 71)
(def ^:const key-h 72)
(def ^:const key-i 73)
(def ^:const key-j 74)
(def ^:const key-k 75)
(def ^:const key-l 76)
(def ^:const key-m 77)
(def ^:const key-n 78)
(def ^:const key-o 79)
(def ^:const key-p 80)
(def ^:const key-q 81)
(def ^:const key-r 82)
(def ^:const key-s 83)
(def ^:const key-t 84)
(def ^:const key-u 85)
(def ^:const key-v 86)
(def ^:const key-w 87)
(def ^:const key-x 88)
(def ^:const key-y 89)
(def ^:const key-z 90)
(def ^:const key-left-bracket 91)
(def ^:const key-backslash 92)
(def ^:const key-right-bracket 93)
(def ^:const key-grave 96)
(def ^:const key-space 32)
(def ^:const key-escape 256)
(def ^:const key-enter 257)
(def ^:const key-tab 258)
(def ^:const key-backspace 259)
(def ^:const key-insert 260)
(def ^:const key-delete 261)
(def ^:const key-right 262)
(def ^:const key-left 263)
(def ^:const key-down 264)
(def ^:const key-up 265)
(def ^:const key-page-up 266)
(def ^:const key-page-down 267)
(def ^:const key-home 268)
(def ^:const key-end 269)
(def ^:const key-caps-lock 280)
(def ^:const key-scroll-lock 281)
(def ^:const key-num-lock 282)
(def ^:const key-print-screen 283)
(def ^:const key-pause 284)
(def ^:const key-f1 290)
(def ^:const key-f2 291)
(def ^:const key-f3 292)
(def ^:const key-f4 293)
(def ^:const key-f5 294)
(def ^:const key-f6 295)
(def ^:const key-f7 296)
(def ^:const key-f8 297)
(def ^:const key-f9 298)
(def ^:const key-f10 299)
(def ^:const key-f11 300)
(def ^:const key-f12 301)
(def ^:const key-left-shift 340)
(def ^:const key-left-control 341)
(def ^:const key-left-alt 342)
(def ^:const key-left-super 343)
(def ^:const key-right-shift 344)
(def ^:const key-right-control 345)
(def ^:const key-right-alt 346)
(def ^:const key-right-super 347)
(def ^:const key-kb-menu 348)
(def ^:const key-kp-0 320)
(def ^:const key-kp-1 321)
(def ^:const key-kp-2 322)
(def ^:const key-kp-3 323)
(def ^:const key-kp-4 324)
(def ^:const key-kp-5 325)
(def ^:const key-kp-6 326)
(def ^:const key-kp-7 327)
(def ^:const key-kp-8 328)
(def ^:const key-kp-9 329)
(def ^:const key-kp-decimal 330)
(def ^:const key-kp-divide 331)
(def ^:const key-kp-multiply 332)
(def ^:const key-kp-subtract 333)
(def ^:const key-kp-add 334)
(def ^:const key-kp-enter 335)
(def ^:const key-kp-equal 336)
(def ^:const mouse-button-left 0)
(def ^:const mouse-button-right 1)
(def ^:const mouse-button-middle 2)
(def ^:const log-warning 4)
(def ^:const pixelformat-r8g8b8a8 7)
(def ^:const texture-filter-point 0)
(def ^:const num-keys [key-zero key-one key-two key-three key-four
key-five key-six key-seven key-eight key-nine])
;;; ----------------------------------------------------------------- functions
(defcfn init-window! "InitWindow" [::mem/int ::mem/int ::mem/c-string] ::mem/void)
(defcfn close-window! "CloseWindow" [] ::mem/void)
(defcfn set-target-fps! "SetTargetFPS" [::mem/int] ::mem/void)
(defcfn set-trace-log-level! "SetTraceLogLevel" [::mem/int] ::mem/void)
(defcfn window-should-close? "WindowShouldClose" [] ::bool)
(defcfn window-ready? "IsWindowReady" [] ::bool)
(defcfn begin-drawing! "BeginDrawing" [] ::mem/void)
(defcfn end-drawing! "EndDrawing" [] ::mem/void)
(defcfn draw-fps "DrawFPS" [::mem/int ::mem/int] ::mem/void)
(defcfn get-fps "GetFPS" [] ::mem/int)
(defcfn key-pressed? "IsKeyPressed" [::mem/int] ::bool)
(defcfn key-down? "IsKeyDown" [::mem/int] ::bool)
(defcfn key-released? "IsKeyPressed" [::mem/int] ::bool)
(defcfn mouse-button-down? "IsMouseButtonDown" [::mem/int] ::bool)
(defcfn mouse-button-pressed? "IsMouseButtonPressed" [::mem/int] ::bool)
(defcfn mouse-button-released? "IsMouseButtonReleased" [::mem/int] ::bool)
(defcfn get-mouse-position "GetMousePosition" [] ::vector-2)
(defcfn get-mouse-wheel-move "GetMouseWheelMove" [] ::mem/float)
(defcfn get-mouse-wheel-move-v "GetMouseWheelMoveV" [] ::vector-2)
;; The !* forms take pre-serialized struct segments and allocate nothing per
;; call. Everything in a per-frame path uses these.
(def clear-background!*
(ffi/make-downcall "ClearBackground" [::color] ::mem/void))
(def ^:private draw-rectangle-raw!*
(ffi/make-downcall "DrawRectangle"
[::mem/int ::mem/int ::mem/int ::mem/int ::color] ::mem/void))
(defn draw-rectangle!*
[x y w h color]
(draw-rectangle-raw!* (int x) (int y) (int w) (int h) color))
(def ^:private draw-rectangle-lines-raw!*
(ffi/make-downcall "DrawRectangleLines"
[::mem/int ::mem/int ::mem/int ::mem/int ::color] ::mem/void))
(defn draw-rectangle-lines!*
[x y w h color]
(draw-rectangle-lines-raw!* (int x) (int y) (int w) (int h) color))
(def ^:private draw-rectangle-lines-ex-raw!*
(ffi/make-downcall "DrawRectangleLinesEx"
[::rectangle ::mem/float ::color] ::mem/void))
(defn draw-rectangle-lines-ex!* [rec line-thick color]
(draw-rectangle-lines-ex-raw!* rec (float line-thick) color))
(def update-texture!*
(ffi/make-downcall "UpdateTexture" [::texture ::mem/pointer] ::mem/void))
(def ^:private draw-texture-raw!*
(ffi/make-downcall "DrawTexture" [::texture ::mem/int ::mem/int ::color] ::mem/void))
(defn draw-texture!*
[texture x y color]
(draw-texture-raw!* texture (int x) (int y) color))
(def draw-texture-v!*
(ffi/make-downcall "DrawTextureV" [::texture ::vector-2 ::color] ::mem/void))
(def ^:private draw-texture-ex-raw!*
(ffi/make-downcall "DrawTextureEx"
[::texture ::vector-2 ::mem/float ::mem/float ::color] ::mem/void))
(defn draw-texture-ex!*
[texture position rotation scale color]
(draw-texture-ex-raw!* texture position (float rotation) (float scale) color))
(def draw-texture-rec!*
(ffi/make-downcall "DrawTextureRec"
[::texture ::rectangle ::vector-2 ::color] ::mem/void))
(def draw-texture-pro!*
(ffi/make-downcall "DrawTexturePro"
[::texture ::rectangle ::rectangle ::vector-2 ::mem/float ::color]
::mem/void))
(def draw-texture-n-patch!*
(ffi/make-downcall "DrawTextureNPatch"
[::texture ::n-patch-info ::rectangle ::vector-2 ::mem/float ::color]
::mem/void))
;; Called once at startup, so the map-taking form is fine.
(defcfn load-texture-raw* "LoadTexture" [::mem/c-string] ::texture)
(defcfn load-texture-from-image "LoadTextureFromImage" [::image] ::texture)
(defcfn unload-texture! "UnloadTexture" [::texture] ::mem/void)
(defcfn set-texture-filter! "SetTextureFilter" [::texture ::mem/int] ::mem/void)
(defcfn get-font-default "GetFontDefault" [] ::font)
(defcfn measure-text "MeasureText" [::mem/c-string ::mem/int] ::mem/int)
(def ^:private draw-text-raw!*
(ffi/make-downcall "DrawText"
[::mem/c-string ::mem/int ::mem/int ::mem/int ::color] ::mem/void))
(def ^:private draw-text-ex-raw!*
(ffi/make-downcall "DrawTextEx"
[::font ::mem/c-string ::vector-2 ::mem/float ::mem/float ::color]
::mem/void))
(def ^:private measure-text-ex-raw!*
(ffi/make-downcall "MeasureTextEx"
[::font ::mem/c-string ::mem/float ::mem/float] ::vector-2))
;;; --------------------------------------------------- macros
(defmacro with-drawing!
[& body]
`(do
(begin-drawing!)
(try
~@body
(finally
(end-drawing!)))))
;;; --------------------------------------------------- native value allocation
(defonce ^Arena arena (Arena/ofAuto))
(defn color-seg
"Serialize a packed 0xRRGGBB int into a reusable native Color, once."
([^long hex] (color-seg hex 255))
([^long hex ^long alpha]
(mem/serialize {:r (bit-and (bit-shift-right hex 16) 0xFF)
:g (bit-and (bit-shift-right hex 8) 0xFF)
:b (bit-and hex 0xFF)
:a alpha}
::color arena))
([^long r ^long g ^long b ^long a]
(mem/serialize {:r r :g g :b b :a a} ::color arena)))
(def black (color-seg 0x000000))
(def white (color-seg 0xFFFFFF))
(def cornflower-blue (color-seg 0x6495ED))
;; raylib.h's named palette.
(def light-gray (color-seg 200 200 200 255))
(def gray (color-seg 130 130 130 255))
(def dark-gray (color-seg 80 80 80 255))
(def yellow (color-seg 253 249 0 255))
(def gold (color-seg 255 203 0 255))
(def orange (color-seg 255 161 0 255))
(def pink (color-seg 255 109 194 255))
(def red (color-seg 230 41 55 255))
(def maroon (color-seg 190 33 55 255))
(def green (color-seg 0 228 48 255))
(def lime (color-seg 0 158 47 255))
(def dark-green (color-seg 0 117 44 255))
(def sky-blue (color-seg 102 191 255 255))
(def blue (color-seg 0 121 241 255))
(def dark-blue (color-seg 0 82 172 255))
(def purple (color-seg 200 122 255 255))
(def violet (color-seg 135 60 190 255))
(def dark-purple (color-seg 112 31 126 255))
(def beige (color-seg 211 176 131 255))
(def brown (color-seg 127 106 79 255))
(def dark-brown (color-seg 76 63 47 255))
(def blank (color-seg 0 0 0 0))
(def magenta (color-seg 255 0 255 255))
(def ray-white (color-seg 245 245 245 255))
(defn rect-seg [x y w h]
(mem/serialize {:x (float x) :y (float y) :width (float w) :height (float h)}
::rectangle arena))
(defn vec2-seg [x y]
(mem/serialize {:x (float x) :y (float y)} ::vector-2 arena))
(defn texture-seg [tex]
(mem/serialize tex ::texture arena))
(defn load-texture [path]
(let [tex (load-texture-raw* path)]
(assoc tex :seg (texture-seg tex))))
(defn n-patch-info-seg [{:keys [source left top right bottom layout]}]
(mem/serialize {:source source :left (int left) :top (int top)
:right (int right) :bottom (int bottom) :layout (int layout)}
::n-patch-info arena))
(defn font-seg [font]
(mem/serialize font ::font arena))
;; GetFontDefault needs a window/GL context, so this is a fn, not a def --
;; call it once after init-window! and hang onto the result.
(defn default-font-seg []
(font-seg (get-font-default)))
;; The raw !* downcalls above take fully native args -- no auto marshaling,
;; unlike defcfn. text/x/y change every call anyway (so a per-call c-string
;; alloc is unavoidable), but color/font are expected pre-serialized (rl/white,
;; a font-seg) same as the other !* draw calls.
(defn draw-text!*
[text x y font-size color]
(draw-text-raw!* (mem/serialize text ::mem/c-string arena)
(int x) (int y) (int font-size) color))
(defn draw-text-ex!*
[font text x y font-size spacing color]
(draw-text-ex-raw!* font
(mem/serialize text ::mem/c-string arena)
(vec2-seg x y)
(float font-size) (float spacing) color))
(defn measure-text-ex!*
"Returns a struct by value, so the raw downcall needs an allocator (arena)
as its first arg to write the result into, and the segment it hands back
needs an explicit deserialize -- unlike defcfn, make-downcall doesn't do
either automatically."
[font text font-size spacing]
(mem/deserialize
(measure-text-ex-raw!* arena font
(mem/serialize text ::mem/c-string arena)
(float font-size) (float spacing))
::vector-2))
;; TODO: LoadFont
(def ^:private set-clipboard-text-raw!*
(ffi/make-downcall "SetClipboardText" [::mem/c-string] ::mem/void))
(defn set-clipboard-text!* [text]
(set-clipboard-text-raw!* (mem/serialize text ::mem/c-string arena)))
(defn alloc-pixels
"An RGBA8888 pixel buffer, native so UpdateTexture can read it directly."
^MemorySegment [^long n-pixels]
(.allocate arena (* 4 n-pixels) 4))
(defn rgba-le
"0xRRGGBB -> an int whose little-endian bytes are R,G,B,A, matching
PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 in memory."
^long [^long hex]
(unchecked-int
(bit-or (bit-and (bit-shift-right hex 16) 0xFF)
(bit-shift-left (bit-and (bit-shift-right hex 8) 0xFF) 8)
(bit-shift-left (bit-and hex 0xFF) 16)
(bit-shift-left 0xFF 24))))