stoe/shader/glsl.lisp

157 lines
6.3 KiB
Common Lisp

#|
This file is a part of stoe project.
Copyright (c) 2015 Renaud Casenave-Péré (renaud@casenave-pere.fr)
|#
(uiop:define-package :stoe/shader/glsl
(:use :cl
:stoe/core/utils
:stoe/engine/viewport
:stoe/shader/walker
:stoe/shader/shader)
(:export #:glsl-compile #:glsl-print))
(in-package :stoe/shader/glsl)
(defvar *form-handlers* (make-hash-table)
"Hash table for the form handlers for a glsl dsl.")
(defvar *current-indent* 0
"Keep the number of space needed for the next directive.")
(defvar *env* nil
"A special variable used as a container for the macro environment.")
(defvar *verbose* nil
"Configure the verbosity of the compiler.
if non-nil, the dsl will be printed in comments together with the glsl code.")
(defvar *version-profiles* '((1.3 . "#version 130
// #extension ARB_explicit_attrib_location : require")
(3.3 . "#version 330 core")
(4.4 . "#version 440 core")
(4.5 . "#version 450 core")))
(defvar *glsl-symbols* '(:gl-position "gl_Position"
:gl-fragcoord "gl_FragCoord"
:gl-fragcolor "gl_FragColor")
"Keep a table of reserved glsl symbols.")
(defun glsl-name (cl-name)
"Convert a variable name in common-lisp convention to a glsl compliant name."
(or (getf *glsl-symbols* (intern (symbol-name cl-name) :keyword))
(string-downcase (substitute #\_ #\- (symbol-name cl-name)))))
(defmacro noop-handler (body)
`(format nil "/* ~((~{~a~^ ~})~) */~%" ,body))
(defhandler (declare block return-from catch load-time-value
macrolet symbol-macrolet flet labels let*
eval-when defun lambda tagbody setq function
the go unwind-protect progv quote) (&rest body)
"Handle the unsupported keywords. For the time being,
the forms comprised of these keywords will be printed in comments."
(noop-handler body))
(defhandler newshader (newshader lambda-list &rest body)
(declare (ignore newshader lambda-list))
(walk-list body))
(defhandler (progn locally) (progn &rest body)
"Handle the progn special form."
(declare (ignore progn))
(format nil "~{~a;~%~}" (walk-list body)))
(defhandler let (let bindings &rest body)
"Handle the variables declaration special form."
(declare (ignore let))
(format nil "~{~a;~%~}~%~a"
(loop for binding in bindings
collect (format nil "~@[~vt~]~(~a~) ~a~@[ = ~a~]"
(when (> *current-indent* 0)
*current-indent*)
(second binding) (glsl-name (first binding))
(if (and (cddr binding)
(not (cdddr binding)))
(walk-1 (third binding))
(walk-list (cddr binding)))))
(format nil "~{~a~^;~%~}" (walk-list body))))
(defhandler setf (setf &rest pairs)
"Handle the assignment special form."
(declare (ignore setf))
(let ((vars (loop for s in pairs by #'cddr collect s)))
(let ((expanded (loop for n in vars
for r in (rest pairs) by #'cddr
collect (glsl-name n) collect (if (symbolp r)
(glsl-name r)
(walk-1 r)))))
(format nil "~@[~vt~]~{~a = ~a~^;~%~}"
(when (> *current-indent* 0)
*current-indent*)
expanded))))
(defhandler (+ - * / %) (op &rest body)
"Handle the standard infix operations."
(labels ((expand (a)
(cond
((listp a) (format nil "(~a)" (walk-1 a)))
((symbolp a) (glsl-name a))
(t a)))
(oper (a b)
(format nil "~a ~(~a~) ~a"
(expand a) op (expand b))))
(reduce #'oper body)))
(defhandler (x y z w xy xz xw
yz yw zw xyz yzw xyzw) (attribs &rest rest)
"Handle swizzle."
(let ((symbol (first rest)))
(format nil "~a.~(~a~)"
(if (symbolp symbol)
(glsl-name symbol)
(walk-list symbol))
attribs)))
(defun default-handler (first &rest rest)
"Handle a simple function call."
(format nil "~@[~vt~]~a (~{~a~^, ~})"
(when (> *current-indent* 0)
*current-indent*)
(if (symbolp first)
(glsl-name first)
(walk-1 first)) (walk-list rest)))
(defun symbol-handler (sym)
"Handle a single symbol."
(glsl-name sym))
(defun handle-preamble (form)
"Handle a preamble declaration."
(let ((location (second (member :location form)))
(interp (second (member :interp form))))
(make-var (intern (symbol-name (first form)) :keyword) (glsl-name (first form))
(second form) (cddr form)
(format nil "~@[layout (location = ~a) ~]~@[~(~a~) ~]~(~a~) ~(~a~) ~(~a~);~%"
location interp (third form) (second form) (glsl-name (first form))))))
(defun glsl-compile (lambda-list body)
"Compile the shader defined in BODY to glsl format.
The forms contained in LAMBDA-LIST are used to define the global variables of
the shader."
(merge-shaders (make-shader :version (cdr (assoc (glsl-version) *version-profiles*
:test #'equal)))
(flet ((merge-preamble (sh1 sh2)
(merge-shaders sh1 (handle-preamble sh2))))
(reduce #'merge-preamble (cons (handle-preamble (first lambda-list))
(rest lambda-list))))
(make-exp (format nil "void main ()~%{~%~a}~%"
(let ((*current-indent* 2))
(walk (cons 'progn body) *form-handlers* #'default-handler #'symbol-handler *env*))))))
(defun glsl-print (shader)
"Returns a string containing the complete SHADER in glsl format."
(format nil "~@[~a~%~%~]~{~a~}~%~a"
(or (shader-version shader) (cdr (assoc (glsl-version) *version-profiles*
:test #'equal)))
(loop for var in (shader-vars shader)
collect (var-exp var)) (shader-exp shader)))