stoe/shader/compiler.lisp

150 lines
6.2 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/compiler
(:use :cl
:stoe/core/utils
:stoe/engine/gl-utils
:stoe/engine/viewport
:stoe/shader/shader
:stoe/shader/glsl)
(:export #:defshader
#:defprogram
#:compile-all-shaders
#:destroy-all-shaders))
(in-package :stoe/shader/compiler)
(defvar *shaders-table* (make-hash-table))
(defvar *programs-table* (make-hash-table))
(defun %defshader (lambda-list body)
(glsl-compile lambda-list body))
(defmacro defshader (name lambda-list &body body)
"Define a shader defining function.
The newly created shader will be put in a special package: `%stoe.shaders'."
`(progn
(defparameter ,name (%defshader ',lambda-list ',body))
(when (not (null (glsl-version)))
(mapc (lambda (program)
(delete-program program)
(compile-program program)) (gethash ',name *shaders-table*)))))
(defun %defprogram (lambda-list body)
(make-program lambda-list body))
(defun clean-dep (name shader-list)
(loop for shader in (cdr shader-list) by #'cddr
do (setf (gethash shader *shaders-table*)
(delete name (gethash shader *shaders-table*)))))
(defun add-dep (name shader-list)
(loop for shader in (cdr shader-list) by #'cddr
do (pushnew name (gethash shader *shaders-table*))))
(defmacro defprogram (name lambda-list &body body)
"Define a new program comprised of all the specified shaders."
`(progn
(when (gethash ',name *programs-table*)
(clean-dep ',name (gethash ',name *programs-table*)))
(defparameter ,name (%defprogram ',lambda-list ',body))
(setf (gethash ',name *programs-table*) ',body)
(add-dep ',name ',body)
(when (not (null (glsl-version)))
(compile-program ',name))))
(defun compile-shader (type shader)
"Compile the shader into opengl."
(let ((shader-obj (gl-assert (gl:create-shader type))))
(when (= shader-obj 0)
(error "Couldn't create shader object."))
(handler-case
(progn
(gl-assert (gl:shader-source shader-obj (glsl-print shader))
(gl:compile-shader shader-obj))
(unless (gl:get-shader shader-obj :compile-status)
(error "Compile failure in ~(~s~) shader:~%~a~%~2i~a~%" type
(glsl-print shader) (gl:get-shader-info-log shader-obj)))
shader-obj)
(error (condition)
(gl:delete-shader shader-obj)
(error condition)))))
(defun initialize-program (program)
"Initialize the program.
Retrieve the attributes and uniform locations."
(flet ((retrieve-location (var)
(unless (member :location (var-qualifiers var))
(let ((target (var-target var)))
(setf (var-qualifiers var)
(append (var-qualifiers var)
(list :location (funcall (cond
((eq target :in) #'gl:get-attrib-location)
((eq target :uniform) #'gl:get-uniform-location))
(program-id program)
(var-name var)))))))))
(mapc #'retrieve-location (program-vars program))))
(defun compile-program (symbol)
"Compile and link the program."
(loop
while (restart-case
(let ((program (symbol-value symbol))
compiled-shaders)
(unless program
(error "The program ~s is undefined." symbol))
(unwind-protect
(progn
(loop for type in (program-stages program) by #'cddr
for shader in (cdr (program-stages program)) by #'cddr
do (push (compile-shader type (symbol-value shader)) compiled-shaders))
(let ((prog-id (gl-assert (gl:create-program))))
(when (= prog-id 0)
(error "Couldn't create program object."))
(unwind-protect
(progn
(mapc (lambda (shader) (gl-assert (gl:attach-shader prog-id shader)))
compiled-shaders)
(gl-assert (gl:link-program prog-id))
(unless (gl:get-program prog-id :link-status)
(error "Link failure in shader program ~s:~%~2i~a~%" symbol
(gl:get-program-info-log prog-id)))
(setf (program-id program) prog-id)
(initialize-program program))
(mapc (lambda (shader) (gl:detach-shader prog-id shader))
(gl:get-attached-shaders prog-id)))))
(mapc (lambda (shader) (gl:delete-shader shader)) compiled-shaders))
nil)
(retry ()
:report (lambda (stream) (format stream "Retry compiling the shader program ~a."
symbol))
t)
(give-up ()
:report (lambda (stream) (format stream "Give up on the shader program ~a."
symbol))
nil))))
(defun delete-program (symbol)
"Delete the program."
(let ((program (symbol-value symbol)))
(when (program-id program)
(gl:delete-program (program-id program))
(setf (program-id program) nil))))
(defun compile-all-shaders ()
"Compile and link all the shaders into opengl."
(loop-with-progress "Compiling shaders"
for symbol being the hash-key in *programs-table*
do (progn
(compile-program symbol)
progress-step)))
(defun destroy-all-shaders ()
"Destroy the programs registered in opengl."
(loop-with-progress "Deleting shaders"
for symbol being the hash-key in *programs-table*
do (progn
(delete-program symbol)
progress-step)))