summaryrefslogtreecommitdiff
path: root/chickadee/graphics/shader.scm
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee/graphics/shader.scm')
-rw-r--r--chickadee/graphics/shader.scm1782
1 files changed, 912 insertions, 870 deletions
diff --git a/chickadee/graphics/shader.scm b/chickadee/graphics/shader.scm
index ab0a8cf..4990487 100644
--- a/chickadee/graphics/shader.scm
+++ b/chickadee/graphics/shader.scm
@@ -1,5 +1,5 @@
;;; Chickadee Game Toolkit
-;;; Copyright © 2016, 2019, 2021 David Thompson <dthompson2@worcester.edu>
+;;; Copyright © 2016-2023 David Thompson <dthompson2@worcester.edu>
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
@@ -25,881 +25,923 @@
#:use-module (srfi srfi-4)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-9 gnu)
- #:use-module (gl)
#:use-module (chickadee data bytestruct)
#:use-module (chickadee math matrix)
#:use-module (chickadee math vector)
#:use-module (chickadee math rect)
- #:use-module (chickadee graphics buffer)
- #:use-module (chickadee graphics color)
- #:use-module (chickadee graphics engine)
- #:use-module (chickadee graphics gl)
- #:use-module (chickadee graphics texture)
+ #:use-module ((chickadee graphics backend) #:prefix gpu:)
+ ;; #:use-module (chickadee graphics buffer)
+ ;; #:use-module (chickadee graphics color)
+ ;; #:use-module (chickadee graphics texture)
#:use-module (chickadee utils)
- #:export (shader-data-type?
- bool
- int
- unsigned-int
- float
- float-vec2
- float-vec3
- float-vec4
- mat3
- mat4
- sampler-2d
- sampler-cube
- local-field
- define-shader-type
- uniform-namespace?
- uniform-namespace-ref
- uniform-namespace-for-each
- make-shader
+ #:export (make-shader
+ destroy-shader
shader?
- null-shader
- g:shader
- current-shader
- load-shader
- strings->shader
- shader-uniform
- shader-uniforms
- shader-attributes
- shader-uniform-set!
- shader-uniform-for-each
- set-uniform-value!
- uniform?
- uniform-name
- uniform-type
- uniform-value
- attribute?
- attribute-name
- attribute-location
- attribute-type
- shader-apply
- shader-apply*
- shader-apply/instanced*
- shader-apply/instanced))
-
-
-;;;
-;;; Primitive Shader Data Types
-;;;
-
-(define-record-type <shader-primitive-type>
- (%make-shader-primitive-type name size validator serializer setter null)
- shader-primitive-type?
- (name shader-primitive-type-name)
- (size shader-primitive-type-size)
- (validator shader-primitive-type-validator)
- (serializer shader-primitive-type-serializer)
- (setter shader-primitive-type-setter)
- (null shader-primitive-type-null))
-
-(define (display-shader-primitive-type type port)
- (format port "#<shader-primitive-type name: ~a size: ~d null: ~a>"
- (shader-primitive-type-name type)
- (shader-primitive-type-size type)
- (shader-primitive-type-null type)))
-
-(set-record-type-printer! <shader-primitive-type> display-shader-primitive-type)
-
-(define* (make-shader-primitive-type #:key name size validator serializer setter null)
- (%make-shader-primitive-type name size validator serializer setter null))
-
-(define (shader-primitive-type-serialize type bv data)
- (let ((serialize (shader-primitive-type-serializer type)))
- (if (vector? data)
- (let ((size (shader-primitive-type-size type)))
- (for-range ((i (vector-length data)))
- (serialize bv (* i size) (vector-ref data i))))
- (serialize bv 0 data))))
-
-(define (shader-primitive-type-apply-uniform type location count pointer)
- ((shader-primitive-type-setter type) location count pointer))
-
-(define (shader-primitive-type-validate type data)
- (let ((valid? (shader-primitive-type-validator type)))
- (if (vector? data)
- (let loop ((i 0))
- (if (and (< i (vector-length data))
- (valid? (vector-ref data i)))
- (loop (+ i 1))
- #t))
- (valid? data))))
-
-(define-syntax-rule (define-shader-primitive-type var . args)
- (define var (make-shader-primitive-type . args)))
-
-;; Primitive types:
-(define-shader-primitive-type bool
- #:name 'bool
- #:size 4
- #:validator boolean?
- #:serializer
- (lambda (bv i bool)
- (bytevector-s32-native-set! bv i (if bool 1 0)))
- #:setter gl-uniform1iv
- #:null #false)
-
-(define-shader-primitive-type int
- #:name 'int
- #:size 4
- #:validator integer?
- #:serializer
- (lambda (bv i n)
- (bytevector-s32-native-set! bv i n))
- #:setter gl-uniform1iv
- #:null 0)
-
-(define-shader-primitive-type unsigned-int
- #:name 'unsigned-int
- #:size 4
- #:validator
- (lambda (i)
- (and (integer? i) (>= i 0)))
- #:serializer
- (lambda (bv i u)
- (bytevector-u32-native-set! bv i u))
- #:setter gl-uniform1uiv
- #:null 0)
-
-(define-shader-primitive-type float
- #:name 'float
- #:size 4
- #:validator number?
- #:serializer
- (lambda (bv i f)
- (bytevector-ieee-single-native-set! bv i f))
- #:setter gl-uniform1fv
- #:null 0.0)
-
-(define-shader-primitive-type float-vec2
- #:name 'float-vec2
- #:size 8 ; 8 bytes = 2 floats = 1 vec2
- #:validator vec2?
- #:serializer
- (lambda (bv i v)
- (bytestruct-pack! <vec2> ((() v)) bv i))
- #:setter gl-uniform2fv
- #:null (vec2 0.0 0.0))
-
-(define-shader-primitive-type float-vec3
- #:name 'float-vec3
- #:size 12 ; 12 bytes = 3 floats = 1 vec3
- #:validator vec3?
- #:serializer
- (lambda (bv i v)
- (bytestruct-pack! <vec3> ((() v)) bv i))
- #:setter gl-uniform3fv
- #:null (vec3 0.0 0.0 0.0))
-
-(define-shader-primitive-type float-vec4
- #:name 'float-vec4
- #:size 16 ; 16 bytes = 4 floats = 1 vec4
- #:validator (lambda (x) (or (rect? x) (color? x)))
- #:serializer
- (lambda (bv i x)
- ;; As of now, there is no vec4 Scheme type, but we do want to
- ;; accept colors and rects as vec4s since there is no special
- ;; color or rect type in GLSL.
- (if (rect? x)
- (bytestruct-pack! <rect> ((() x)) bv i)
- (bytestruct-pack! <color> ((() x)) bv i)))
- #:setter gl-uniform4fv
- #:null (make-null-rect))
-
-(define-shader-primitive-type mat3
- #:name 'mat3
- #:size (* 3 3 4) ; 3 rows x 3 columns x 4 byte floats
- #:validator matrix3?
- #:serializer
- (lambda (bv i m)
- (bytestruct-pack! <matrix3> ((() m)) bv i))
- #:setter (lambda (location count ptr)
- (gl-uniform-matrix3fv location count #f ptr))
- #:null (make-identity-matrix3))
-
-(define-shader-primitive-type mat4
- #:name 'mat4
- #:size (bytestruct-sizeof <matrix4>) ; 4 rows x 4 columns = 16 floats x 4 bytes each = 64 bytes
- #:validator matrix4?
- #:serializer
- (lambda (bv i m)
- ;; (match m
- ;; (($ <matrix4> src offset)
- ;; (bytevector-copy! src offset bv i
- ;; (bytestruct-sizeof <matrix4>))))
- (bytestruct-pack! <matrix4> ((() m)) bv i)
- )
- #:setter (lambda (location count ptr)
- (gl-uniform-matrix4fv location count #f ptr))
- #:null (make-identity-matrix4))
-
-(define-shader-primitive-type sampler-2d
- #:name 'sampler-2d
- #:size 4
- #:validator integer?
- #:serializer
- (lambda (bv i texture-unit)
- (bytevector-s32-native-set! bv i texture-unit))
- #:setter gl-uniform1iv
- #:null 0)
-
-(define-shader-primitive-type sampler-cube
- #:name 'sampler-cube
- #:size 4
- #:validator integer?
- #:serializer
- (lambda (bv i texture-unit)
- (bytevector-s32-native-set! bv i texture-unit))
- #:setter gl-uniform1iv
- #:null 0)
-
-
-;;;
-;;; Compound Shader Data Types
-;;;
-
-;; A meta-vtable that has two additional slots: one for the struct
-;; name, and another for the lookup table that maps struct field names
-;; to their respective struct index and shader data type.
-(define <shader-struct>
- (make-vtable (string-append standard-vtable-fields "pwpw")
- (lambda (vt port)
- (format port "#<shader-struct ~a>"
- (shader-struct-fields vt)))))
-
-(define local-field (gensym "local-shader-field-"))
-
-(define (shader-struct? struct)
- (eq? (struct-vtable (struct-vtable struct)) <shader-struct>))
-
-(define shader-struct-name-index vtable-offset-user)
-(define shader-struct-fields-index (+ vtable-offset-user 1))
-
-(define (shader-struct-name vtable)
- (struct-ref vtable shader-struct-name-index))
-
-(define (shader-struct-fields vtable)
- (struct-ref vtable shader-struct-fields-index))
-
-(define (shader-struct-type-check vtable field value)
- (match (assq-ref (shader-struct-fields vtable) field)
- ((_ type size)
- (define (validate value)
- (unless (or (and (struct? value) (eq? (struct-vtable value) type))
- (shader-primitive-type-validate type value))
- (error "invalid type for shader struct field" field value)))
- (cond
- ((eq? type local-field)
- #t)
- ((= size 1)
- (validate value))
- ((and (vector? value)
- (= (vector-length value) size))
- (for-range ((i (vector-length value)))
- (validate (vector-ref value i))))
- ((vector? value)
- (error "incorrect vector size" value))
- (else
- (error "expected vector, got" value))))))
-
-(define (shader-struct-default vtable field)
- (match (assq-ref (shader-struct-fields vtable) field)
- ((_ type size)
- (let ((default (cond
- ((eq? type local-field)
- #f)
- ((eq? (struct-vtable type) <shader-struct>)
- (apply make-struct/no-tail type
- (map (match-lambda
- ((sub-field _ _ _)
- (shader-struct-default type sub-field)))
- (shader-struct-fields type))))
- (else
- (shader-primitive-type-null type)))))
- (if (= size 1)
- default
- (make-vector size default))))))
-
-(define (make-shader-struct-field-table fields+types)
- ;; Map field names to their struct indices and shader types.
- (let loop ((i 0)
- (fields+types fields+types))
- (match fields+types
- (() '())
- (((name #(type size)) . rest)
- (cons (list name i type size)
- (loop (+ i 1) rest)))
- (((name type) . rest)
- (cons (list name i type 1)
- (loop (+ i 1) rest))))))
-
-(define (display-shader-struct-instance obj port)
- (let ((vtable (struct-vtable obj)))
- (format port "#<~a" (shader-struct-name vtable))
- (let loop ((fields (shader-struct-fields vtable)))
- (match fields
- (() #f)
- (((name index type size) . rest)
- (format port " ~a[~d ~a]: ~a"
- name size
- (if (eq? type local-field)
- 'local
- (shader-primitive-type-name type))
- (struct-ref obj index))
- (loop rest))))
- (display ">" port)))
-
-(define (make-display-name sym)
- (let ((str (symbol->string sym)))
- (if (and (string-prefix? "<" str)
- (string-suffix? ">" str))
- (substring str 1 (- (string-length str) 1))
- str)))
-
-(define (make-shader-struct name fields+types)
- (make-struct/no-tail <shader-struct>
- (make-struct-layout
- (string-concatenate
- (map (const "pw") fields+types)))
- display-shader-struct-instance
- (make-display-name name)
- (make-shader-struct-field-table fields+types)))
-
-(define (shader-struct-ref struct field)
- (match (assq-ref (shader-struct-fields (struct-vtable struct)) field)
- (#f
- (error "unknown struct field" field))
- ((index _ _)
- (struct-ref struct index))))
-
-(define-syntax define-accessors
- (syntax-rules ()
- ((_ <struct> field getter)
- (define getter
- (let ((index (match (assq-ref (shader-struct-fields <struct>) 'field)
- ((i _ _) i))))
- (lambda (obj)
- (struct-ref obj index)))))
- ((_ <struct> field getter setter)
- (begin
- (define-accessors <struct> field getter)
- (define setter
- (let ((index (match (assq-ref (shader-struct-fields <struct>) 'field)
- ((i _ _) i))))
- (lambda (obj value)
- (shader-struct-type-check <struct> 'field value)
- (struct-set! obj index value))))))))
-
-(define-syntax define-shader-type
- (syntax-rules ()
- ((_ <name> constructor predicate (field-type field-name . field-rest) ...)
- (begin
- (define <name>
- (make-shader-struct '<name> (list (list 'field-name field-type) ...)))
- (define* (constructor #:key (field-name (shader-struct-default <name> 'field-name)) ...)
- (shader-struct-type-check <name> 'field-name field-name) ...
- (make-struct/no-tail <name> field-name ...))
- (define (predicate obj)
- (and (struct? obj) (eq? (struct-vtable obj) <name>)))
- (define-accessors <name> field-name . field-rest) ...))))
-
-
-;;;
-;;; Shaders
-;;;
+ shader-vertex
+ shader-fragment
+ shader-destroyed?
+
+ ;; shader-data-type?
+ ;; bool
+ ;; int
+ ;; unsigned-int
+ ;; float
+ ;; float-vec2
+ ;; float-vec3
+ ;; float-vec4
+ ;; mat3
+ ;; mat4
+ ;; sampler-2d
+ ;; sampler-cube
+ ;; local-field
+ ;; define-shader-type
+ ;; uniform-namespace?
+ ;; uniform-namespace-ref
+ ;; uniform-namespace-for-each
+ ;; make-shader
+ ;; shader?
+ ;; null-shader
+ ;; g:shader
+ ;; current-shader
+ ;; load-shader
+ ;; strings->shader
+ ;; shader-uniform
+ ;; shader-uniforms
+ ;; shader-attributes
+ ;; shader-uniform-set!
+ ;; shader-uniform-for-each
+ ;; set-uniform-value!
+ ;; uniform?
+ ;; uniform-name
+ ;; uniform-type
+ ;; uniform-value
+ ;; attribute?
+ ;; attribute-name
+ ;; attribute-location
+ ;; attribute-type
+ ;; shader-apply
+ ;; shader-apply*
+ ;; shader-apply/instanced*
+ ;; shader-apply/instanced
+ ))
(define-record-type <shader>
- (%make-shader id attributes uniforms scratch scratch-pointer)
+ (%make-shader gpu handle name state)
shader?
- (id shader-id)
- (attributes shader-attributes)
- (uniforms shader-uniforms)
- ;; Scratch space for serializing uniform values.
- (scratch shader-scratch)
- ;; Create the pointer once and hold onto it to reduce needless
- ;; garbage creation.
- (scratch-pointer shader-scratch-pointer))
-
-;; Represents a single active uniform location in a shader program.
-(define-record-type <uniform>
- (make-uniform name location type size value)
- uniform?
- (name uniform-name)
- (location uniform-location)
- (type uniform-type)
- (size uniform-size)
- (value uniform-value %set-uniform-value!))
-
-(define (sampler? uniform)
- (or (eq? (uniform-type uniform) sampler-2d)
- (eq? (uniform-type uniform) sampler-cube)))
-
-;; variable name -> {uniform, namespace, uniform array} map
-(define-record-type <uniform-namespace>
- (make-uniform-namespace name members)
- uniform-namespace?
- (name uniform-namespace-name)
- (members uniform-namespace-members))
-
-(define (fresh-uniform-namespace name)
- (make-uniform-namespace name (make-hash-table)))
-
-(define (uniform-namespace-set! namespace name value)
- (hashq-set! (uniform-namespace-members namespace) name value))
-
-(define (uniform-namespace-ref namespace name)
- (hashq-ref (uniform-namespace-members namespace) name))
-
-(define (uniform-namespace-ref-or-create-namespace namespace name)
- (or (uniform-namespace-ref namespace name)
- (let ((new-ns (fresh-uniform-namespace name)))
- (uniform-namespace-set! namespace name new-ns)
- new-ns)))
-
-(define (uniform-namespace-ref-or-create-array namespace name)
- (or (uniform-namespace-ref namespace name)
- (let ((new-arr (fresh-uniform-array name)))
- (uniform-namespace-set! namespace name new-arr)
- new-arr)))
-
-(define (uniform-namespace-for-each proc namespace)
- (hash-for-each proc (uniform-namespace-members namespace)))
-
-;; variable name -> {uniform, namespace} map
-(define-record-type <uniform-array>
- (make-uniform-array name namespaces size)
- uniform-array?
- (name uniform-array-name)
- (namespaces uniform-array-namespaces)
- (size uniform-array-size set-uniform-array-size!))
-
-(define (fresh-uniform-array name)
- (make-uniform-array name (make-hash-table) 0))
-
-(define (uniform-array-namespace-ref array i)
- (hashv-ref (uniform-array-namespaces array) i))
-
-(define (uniform-array-namespace-add! array i)
- (let ((ns (fresh-uniform-namespace (uniform-array-name array))))
- (hashv-set! (uniform-array-namespaces array) i ns)
- (set-uniform-array-size! array (max (uniform-array-size array) (+ i 1)))
- ns))
-
-(define (uniform-array-ref-or-create array i)
- (or (uniform-array-namespace-ref array i)
- (uniform-array-namespace-add! array i)))
-
-(define-record-type <attribute>
- (make-attribute name location type)
- attribute?
- (name attribute-name)
- (location attribute-location)
- (type attribute-type))
-
-(define null-shader (%make-shader 0 (make-hash-table) (make-hash-table) #f #f))
-
-(define (bind-shader shader)
- (gl-use-program (shader-id shader)))
-
-(define (free-shader shader)
- (gl-delete-program (shader-id shader)))
-
-(define-graphics-finalizer shader-finalizer
- #:predicate shader?
- #:free free-shader)
-
-(define-graphics-state g:shader
- current-shader
- #:default null-shader
- #:bind bind-shader)
-
-(define (make-shader vertex-port fragment-port)
- "Read GLSL source from VERTEX-PORT and FRAGMENT-PORT and compile
-them into a GPU shader program."
- (define (shader-compiled? id)
- (let ((status (make-u32vector 1)))
- (gl-get-shaderiv id (version-2-0 compile-status)
- (bytevector->pointer status))
- (= (u32vector-ref status 0) 1)))
- (define (shader-linked? id)
- (let ((status (make-u32vector 1)))
- (gl-get-programiv id (version-2-0 link-status)
- (bytevector->pointer status))
- (= (u32vector-ref status 0) 1)))
- (define (info-log length-proc log-proc id)
- (let ((log-length-bv (make-u32vector 1)))
- (length-proc id (version-2-0 info-log-length)
- (bytevector->pointer log-length-bv))
- (u32vector-ref log-length-bv 0)
- ;; Add one byte to account for the null string terminator.
- (let* ((log-length (u32vector-ref log-length-bv 0))
- (log (make-u8vector (1+ log-length))))
- (log-proc id log-length %null-pointer (bytevector->pointer log))
- (utf8->string log))))
- (define (compilation-error id)
- (info-log gl-get-shaderiv gl-get-shader-info-log id))
- (define (linking-error id)
- (info-log gl-get-programiv gl-get-program-info-log id))
- (define (glsl-preprocessor-source)
- ;; Set up preprocessor directives dynamically based on the current
- ;; OpenGL context's GLSL version so that we can write shaders that
- ;; are compatible with as many systems as possible.
- (let ((glsl-version (graphics-engine-glsl-version)))
- (cond
- ((string>= glsl-version "3.3")
- "#version 330
-#define GLSL330
-")
- ((string>= glsl-version "1.3")
- "#version 130
-#define GLSL130
-")
- ((string>= glsl-version "1.2")
- "#version 120
-#define GLSL120
-")
- (else
- (error "incompatible GLSL version" glsl-version)))))
- (define (make-shader-stage type port)
- (let ((id (gl-create-shader type))
- (source (string->utf8
- (string-append (glsl-preprocessor-source)
- (get-string-all port)))))
- (gl-shader-source id 1
- (bytevector->pointer
- (u64vector
- (pointer-address (bytevector->pointer source))))
- (bytevector->pointer
- (u32vector (bytevector-length source))))
- (gl-compile-shader id)
- (unless (shader-compiled? id)
- (let ((error-log (compilation-error id)))
- (gl-delete-shader id) ; clean up GPU resource.
- (display "shader compilation failed:\n")
- (display error-log (current-error-port))
- (error "failed to compile shader")))
- id))
- (define (uniform-count id)
- (let ((bv (make-u32vector 1)))
- (gl-get-programiv id
- (arb-shader-objects active-uniforms)
- (bytevector->pointer bv))
- (u32vector-ref bv 0)))
- (define (utf8->string* bv length)
- (let ((bv* (make-bytevector length)))
- (bytevector-copy! bv 0 bv* 0 length)
- (utf8->string bv*)))
- (define (parse-data-type type)
- (cond
- ((= type (version-2-0 bool)) bool)
- ((= type (data-type int)) int)
- ((= type (data-type unsigned-int)) unsigned-int)
- ((= type (data-type float)) float)
- ((= type (version-2-0 float-vec2)) float-vec2)
- ((= type (version-2-0 float-vec3)) float-vec3)
- ((= type (version-2-0 float-vec4)) float-vec4)
- ((= type (version-2-0 float-mat3)) mat3)
- ((= type (version-2-0 float-mat4)) mat4)
- ((= type (version-2-0 sampler-2d)) sampler-2d)
- ((= type (version-2-0 sampler-cube)) sampler-cube)
- (else
- (error "unsupported OpenGL type" type))))
- (define (camel->snake str)
- (list->string
- (let loop ((i 0))
- (if (< i (string-length str))
- (let ((c (string-ref str i)))
- (if (char-set-contains? char-set:upper-case c)
- (cons* #\- (char-downcase c) (loop (+ i 1)))
- (cons c (loop (+ i 1)))))
- '()))))
- (define (uniform-name->symbol name)
- ;; array uniform names have a suffix like "[0]" that needs to be
- ;; removed to produce the actual uniform variable name that our
- ;; shader interface will recognize.
- (string->symbol
- (let ((i (string-contains name "[")))
- (camel->snake
- (if i (substring name 0 i) name)))))
- (define (parse-array-index name)
- (let* ((start (string-contains name "["))
- (end (- (string-length name) 1)))
- (and start (string->number (substring name (+ start 1) end)))))
- (define (struct? name)
- (string-contains name "."))
- (define (parse-struct name uniform namespace)
- ;; Deconstruct the uniform name to produce a path through the
- ;; namespace tree, follow it to the end and add the uniform as a
- ;; leaf node in the tree.
- (let inner ((path (string-split name #\.))
- (namespace namespace))
- (match path
- ;; Yay, we're done!
- ((leaf)
- (uniform-namespace-set! namespace (uniform-name->symbol leaf) uniform))
- ((branch . rest)
- (let ((new-name (uniform-name->symbol branch))
- (index (parse-array-index branch)))
- ;; If there is an index included in the branch name like
- ;; "[1]" then that means we have a nested array of structs
- ;; within the struct. Otherwise, it's just a single nested
- ;; struct.
- (if index
- (let ((new-namespace
- (uniform-array-ref-or-create
- (uniform-namespace-ref-or-create-array namespace
- new-name)
- index)))
- (inner rest new-namespace))
- (let ((new-namespace
- (uniform-namespace-ref-or-create-namespace namespace
- new-name)))
- (inner rest new-namespace))))))))
- (define (extract-uniforms id)
- (let ((total (uniform-count id))
- (namespace (fresh-uniform-namespace "root")))
- ;; OpenGL has an API for shader program introspection that we
- ;; use to extract all active uniforms. This uniform data must
- ;; then be parsed and turned into a data structure that can be
- ;; used to translate Scheme data (either primitives or compound
- ;; structures) to the GPU when it comes time to actually render
- ;; something with the shader.
- (let loop ((i 0)
- (texture-unit 0)
- (scratch-size 0))
- (if (< i total)
- (let ((length-bv (make-u32vector 1))
- (size-bv (make-u32vector 1))
- (type-bv (make-u32vector 1))
- (name-bv (make-bytevector 255)))
- (gl-get-active-uniform id i
- (bytevector-length name-bv)
- (bytevector->pointer length-bv)
- (bytevector->pointer size-bv)
- (bytevector->pointer type-bv)
- (bytevector->pointer name-bv))
- (let* ((name-length (u32vector-ref length-bv 0))
- (name (utf8->string* name-bv name-length))
- (location (gl-get-uniform-location id name))
- (size (u32vector-ref size-bv 0))
- (type (parse-data-type (u32vector-ref type-bv 0)))
- (sampler? (or (eq? type sampler-2d) (eq? type sampler-cube)))
- (default (cond
- (sampler?
- texture-unit)
- ((= size 1)
- (shader-primitive-type-null type))
- (else
- (make-vector size (shader-primitive-type-null type)))))
- (uniform (make-uniform name location type size default)))
- (if (struct? name)
- ;; The complicated path: Parse struct name and
- ;; build a branch of a tree structure.
- (parse-struct name uniform namespace)
- ;; The easy path: A top-level primitive.
- (uniform-namespace-set! namespace
- (uniform-name->symbol name)
- uniform))
- (loop (1+ i)
- ;; A sampler uniform uses up one texture unit,
- ;; so move on to the next one in that case.
- (if sampler?
- (1+ texture-unit)
- texture-unit)
- ;; Ensure we have enough space to serialize the
- ;; largest bit of data we send to the shader.
- (max scratch-size
- (* size
- (shader-primitive-type-size type))))))
- (values namespace scratch-size)))))
- (define (attribute-count id)
- (let ((bv (make-u32vector 1)))
- (gl-get-programiv id
- (arb-shader-objects active-attributes)
- (bytevector->pointer bv))
- (u32vector-ref bv 0)))
- (define (extract-attributes id)
- (let ((total (attribute-count id))
- (table (make-hash-table)))
- (for-range ((i total))
- (let ((length-bv (make-u32vector 1))
- (size-bv (make-u32vector 1))
- (type-bv (make-u32vector 1))
- (name-bv (make-bytevector 255)))
- (gl-get-active-attrib id i
- (bytevector-length name-bv)
- (bytevector->pointer length-bv)
- (bytevector->pointer size-bv)
- (bytevector->pointer type-bv)
- (bytevector->pointer name-bv))
- (let* ((length (u32vector-ref length-bv 0))
- (name (utf8->string* name-bv length))
- (size (u32vector-ref size-bv 0))
- (type (parse-data-type (u32vector-ref type-bv 0)))
- (location (gl-get-attrib-location id name)))
- (unless (= size 1)
- (error "unsupported attribute size" name size))
- (hash-set! table name (make-attribute name location type)))))
- table))
- (assert-current-graphics-engine)
- (let ((vertex-id (make-shader-stage (version-2-0 vertex-shader)
- vertex-port))
- (fragment-id (make-shader-stage (version-2-0 fragment-shader)
- fragment-port))
- (id (gl-create-program)))
- (gl-attach-shader id vertex-id)
- (gl-attach-shader id fragment-id)
- (gl-link-program id)
- (unless (shader-linked? id)
- (let ((error-log (linking-error id)))
- (gl-delete-program id)
- (error "failed to link shader" error-log)))
- (gl-delete-shader vertex-id)
- (gl-delete-shader fragment-id)
- (call-with-values
- (lambda () (extract-uniforms id))
- (lambda (namespace scratch-size)
- (let* ((scratch (make-bytevector scratch-size))
- (scratch-ptr (bytevector->pointer scratch))
- (shader (%make-shader id (extract-attributes id) namespace
- scratch scratch-ptr)))
- (graphics-engine-guard! shader)
- shader)))))
-
-(define (load-shader vertex-source-file fragment-source-file)
- "Compile the GLSL source code within VERTEX-SOURCE-FILE and
-FRAGMENT-SOURCE-FILE into a GPU shader program."
- (call-with-input-file vertex-source-file
- (lambda (vertex-port)
- (call-with-input-file fragment-source-file
- (lambda (fragment-port)
- (make-shader vertex-port fragment-port))))))
-
-(define (strings->shader vertex-source fragment-source)
- "Compile VERTEX-SOURCE, the GLSL code for the vertex shader,
-and FRAGMENT-SOURCE, the GLSL code for the fragment shader, into a GPU
-shader program."
- (call-with-input-string vertex-source
- (lambda (vertex-port)
- (call-with-input-string fragment-source
- (lambda (fragment-port)
- (make-shader vertex-port fragment-port))))))
-
-(define (shader-uniform shader name)
- "Return the metadata for the uniform NAME in SHADER."
- (let ((uniform (uniform-namespace-ref (shader-uniforms shader) name)))
- (or uniform (error "no such uniform" name))))
-
-(define (set-uniform-value! shader uniform value)
- ;; TODO: Figure out a way to avoid unnecessary uniform
- ;; updates. Maybe UBOs would help address this?
- (let ((type (uniform-type uniform)))
- (shader-primitive-type-serialize type (shader-scratch shader) value)
- (shader-primitive-type-apply-uniform type (uniform-location uniform) 1
- (shader-scratch-pointer shader))
- (%set-uniform-value! uniform value)))
-
-(define (shader-uniform-for-each* proc shader thing)
- (cond
- ((uniform? thing)
- (proc thing))
- ((uniform-namespace? thing)
- (uniform-namespace-for-each
- (lambda (key uniform)
- (shader-uniform-for-each* proc shader uniform))
- thing))
- ((uniform-array? thing)
- (for-range ((i (uniform-array-size thing)))
- (shader-uniform-for-each* proc shader (uniform-array-namespace-ref thing i))))))
-
-(define (shader-uniform-for-each proc shader)
- (shader-uniform-for-each* proc shader (shader-uniforms shader)))
-
-;; TODO: This walks the entire tree every time, but it should instead
-;; stop traversing once it finds the correct leaf node.
-(define (%shader-uniform-set shader uniform value)
- (cond
- ;; A leaf node of the uniform tree representing a single uniform
- ;; location as determined by OpenGL.
- ((uniform? uniform)
- ;; A vector of a primitive type must be the exact size that
- ;; the shader expects.
- (when (and (> (uniform-size uniform) 1)
- (not (= (uniform-size uniform) (vector-length value))))
- (error "vector size mismatch for uniform" uniform-name))
- (set-uniform-value! shader uniform value))
- ;; A nested namespace indicates that this must be a struct.
- ((uniform-namespace? uniform)
- (if (shader-struct? value)
- (uniform-namespace-for-each
- (lambda (key uniform)
- ;; Samplers are opaque types and you cannot pass them
- ;; into the shader as uniform values like you can with
- ;; most other values. In the case of samplers, they are
- ;; mapped to OpenGL's "texture units", so we need to
- ;; ignore them here.
- (unless (sampler? uniform)
- (%shader-uniform-set shader uniform
- (shader-struct-ref value key))))
- uniform)
- (error "expected shader struct" value)))
- ;; A nested array namespace indicates that this must be an array
- ;; of structs.
- ((uniform-array? uniform)
- (let ((size (uniform-array-size uniform)))
- ;; Vector size must match what the shader expects.
- (if (and (vector? value)
- (= size (vector-length value)))
- (for-range ((i size))
- (%shader-uniform-set shader
- (uniform-array-namespace-ref uniform i)
- (vector-ref value i)))
- (error "vector size mismatch for uniform"
- (uniform-array-name uniform)))))))
-
-(define (shader-uniform-set! shader uniform-name x)
- ;; Walk the uniform namespace tree until we get to a leaf node or
- ;; nodes.
- (%shader-uniform-set shader (shader-uniform shader uniform-name) x))
-
-(define-syntax uniform-apply
- (lambda (x)
- (syntax-case x ()
- ((_ shader ()) (datum->syntax x #t))
- ((_ shader (name value . rest))
- (with-syntax ((sname (datum->syntax x (keyword->symbol
- (syntax->datum #'name)))))
- #'(begin
- (shader-uniform-set! shader 'sname value)
- (uniform-apply shader rest)))))))
-
-(define-syntax-rule (shader-apply** shader* vertex-array uniforms exp)
- (with-graphics-state! ((g:shader shader*))
- (uniform-apply shader* uniforms)
- ;; Sampler2D values aren't explicitly passed as uniform values via
- ;; shader-apply, so we have to bind them to the proper texture units
- ;; behind the scenes.
- (shader-uniform-for-each
- (lambda (uniform)
- (when (or (eq? (uniform-type uniform) sampler-2d)
- (eq? (uniform-type uniform) sampler-cube))
- (set-uniform-value! shader* uniform (uniform-value uniform))))
- shader*)
- exp))
-
-(define-syntax-rule (shader-apply* shader vertex-array offset count . uniforms)
- (shader-apply** shader vertex-array uniforms
- (render-vertices vertex-array count offset)))
-
-(define-syntax-rule (shader-apply shader vertex-array uniforms ...)
- (shader-apply* shader vertex-array 0 #f uniforms ...))
-
-(define-syntax-rule (shader-apply/instanced* shader vertex-array offset count instances .
- uniforms)
- (shader-apply** shader vertex-array uniforms
- (render-vertices/instanced vertex-array instances count offset)))
-
-(define-syntax-rule (shader-apply/instanced shader vertex-array instances
- uniforms ...)
- (shader-apply/instanced* shader vertex-array 0 #f instances uniforms ...))
+ (gpu shader-gpu)
+ (handle shader-handle)
+ (name shader-name)
+ (state shader-state set-shader-state!))
+
+(define (print-shader shader port)
+ (match shader
+ (($ <shader> _ _ name)
+ (format #t "#<shader name: ~s>" name))))
+
+(set-record-type-printer! <shader> print-shader)
+
+(define (shader-available? shader)
+ (eq? (shader-state shader) 'available))
+
+(define (shader-destroyed? shader)
+ (eq? (shader-state shader) 'destroyed))
+
+(define* (make-shader source #:key name)
+ (let* ((gpu (gpu:current-gpu))
+ (handle (gpu:make-shader gpu source)))
+ (%make-shader gpu handle name 'available)))
+
+(define (destroy-shader module)
+ (unless (shader-destroyed? module)
+ (gpu:destroy-shader (shader-gpu module) (shader-handle module))
+ (set-shader-state! module 'destroyed)))
+
+;;
+;; ;;;
+;; ;;; Primitive Shader Data Types
+;; ;;;
+
+;; (define-record-type <shader-primitive-type>
+;; (%make-shader-primitive-type name size validator serializer setter null)
+;; shader-primitive-type?
+;; (name shader-primitive-type-name)
+;; (size shader-primitive-type-size)
+;; (validator shader-primitive-type-validator)
+;; (serializer shader-primitive-type-serializer)
+;; (setter shader-primitive-type-setter)
+;; (null shader-primitive-type-null))
+
+;; (define (display-shader-primitive-type type port)
+;; (format port "#<shader-primitive-type name: ~a size: ~d null: ~a>"
+;; (shader-primitive-type-name type)
+;; (shader-primitive-type-size type)
+;; (shader-primitive-type-null type)))
+
+;; (set-record-type-printer! <shader-primitive-type> display-shader-primitive-type)
+
+;; (define* (make-shader-primitive-type #:key name size validator serializer setter null)
+;; (%make-shader-primitive-type name size validator serializer setter null))
+
+;; (define (shader-primitive-type-serialize type bv data)
+;; (let ((serialize (shader-primitive-type-serializer type)))
+;; (if (vector? data)
+;; (let ((size (shader-primitive-type-size type)))
+;; (for-range ((i (vector-length data)))
+;; (serialize bv (* i size) (vector-ref data i))))
+;; (serialize bv 0 data))))
+
+;; (define (shader-primitive-type-apply-uniform type location count pointer)
+;; ((shader-primitive-type-setter type) location count pointer))
+
+;; (define (shader-primitive-type-validate type data)
+;; (let ((valid? (shader-primitive-type-validator type)))
+;; (if (vector? data)
+;; (let loop ((i 0))
+;; (if (and (< i (vector-length data))
+;; (valid? (vector-ref data i)))
+;; (loop (+ i 1))
+;; #t))
+;; (valid? data))))
+
+;; (define-syntax-rule (define-shader-primitive-type var . args)
+;; (define var (make-shader-primitive-type . args)))
+
+;; ;; Primitive types:
+;; (define-shader-primitive-type bool
+;; #:name 'bool
+;; #:size 4
+;; #:validator boolean?
+;; #:serializer
+;; (lambda (bv i bool)
+;; (bytevector-s32-native-set! bv i (if bool 1 0)))
+;; #:setter gl-uniform1iv
+;; #:null #false)
+
+;; (define-shader-primitive-type int
+;; #:name 'int
+;; #:size 4
+;; #:validator integer?
+;; #:serializer
+;; (lambda (bv i n)
+;; (bytevector-s32-native-set! bv i n))
+;; #:setter gl-uniform1iv
+;; #:null 0)
+
+;; (define-shader-primitive-type unsigned-int
+;; #:name 'unsigned-int
+;; #:size 4
+;; #:validator
+;; (lambda (i)
+;; (and (integer? i) (>= i 0)))
+;; #:serializer
+;; (lambda (bv i u)
+;; (bytevector-u32-native-set! bv i u))
+;; #:setter gl-uniform1uiv
+;; #:null 0)
+
+;; (define-shader-primitive-type float
+;; #:name 'float
+;; #:size 4
+;; #:validator number?
+;; #:serializer
+;; (lambda (bv i f)
+;; (bytevector-ieee-single-native-set! bv i f))
+;; #:setter gl-uniform1fv
+;; #:null 0.0)
+
+;; (define-shader-primitive-type float-vec2
+;; #:name 'float-vec2
+;; #:size 8 ; 8 bytes = 2 floats = 1 vec2
+;; #:validator vec2?
+;; #:serializer
+;; (let ((unwrap-vec2 (@@ (chickadee math vector) unwrap-vec2)))
+;; (lambda (bv i v)
+;; (bytevector-copy! (unwrap-vec2 v) 0 bv i 8)))
+;; #:setter gl-uniform2fv
+;; #:null (vec2 0.0 0.0))
+
+;; (define-shader-primitive-type float-vec3
+;; #:name 'float-vec3
+;; #:size 12 ; 12 bytes = 3 floats = 1 vec3
+;; #:validator vec3?
+;; #:serializer
+;; (let ((unwrap-vec3 (@@ (chickadee math vector) unwrap-vec3)))
+;; (lambda (bv i v)
+;; (bytevector-copy! (unwrap-vec3 v) 0 bv i 12)))
+;; #:setter gl-uniform3fv
+;; #:null (vec3 0.0 0.0 0.0))
+
+;; (define-shader-primitive-type float-vec4
+;; #:name 'float-vec4
+;; #:size 16 ; 16 bytes = 4 floats = 1 vec4
+;; #:validator (lambda (x) (or (rect? x) (color? x)))
+;; #:serializer
+;; (let ((unwrap-rect (@@ (chickadee math rect) unwrap-rect)))
+;; (lambda (bv i v)
+;; ;; As of now, there is no vec4 Scheme type, but we do want to
+;; ;; accept colors and rects as vec4s since there is no special
+;; ;; color or rect type in GLSL.
+;; (if (rect? v)
+;; (bytevector-copy! (unwrap-rect v) 0 bv i 16)
+;; (begin
+;; (bytevector-ieee-single-native-set! bv i (color-r v))
+;; (bytevector-ieee-single-native-set! bv (+ i 4) (color-g v))
+;; (bytevector-ieee-single-native-set! bv (+ i 8) (color-b v))
+;; (bytevector-ieee-single-native-set! bv (+ i 12) (color-a v))))))
+;; #:setter gl-uniform4fv
+;; #:null (make-null-rect))
+
+;; (define-shader-primitive-type mat3
+;; #:name 'mat3
+;; #:size (* 3 3 4) ; 3 rows x 3 columns x 4 byte floats
+;; #:validator matrix3?
+;; #:serializer
+;; (let ((matrix3-bv (@@ (chickadee math matrix) matrix3-bv)))
+;; (lambda (bv i m)
+;; (bytevector-copy! (matrix3-bv m) 0 bv i (* 3 3 4))))
+;; #:setter (lambda (location count ptr)
+;; (gl-uniform-matrix3fv location count #f ptr))
+;; #:null (make-identity-matrix3))
+
+;; (define-shader-primitive-type mat4
+;; #:name 'mat4
+;; #:size 64 ; 4 rows x 4 columns = 16 floats x 4 bytes each = 64 bytes
+;; #:validator matrix4?
+;; #:serializer
+;; (let ((matrix4-bv (@@ (chickadee math matrix) matrix4-bv)))
+;; (lambda (bv i m)
+;; ;; 4 rows x 4 columns x 4 bytes per float = 4^3
+;; (bytevector-copy! (matrix4-bv m) 0 bv i (* 4 4 4))))
+;; #:setter (lambda (location count ptr)
+;; (gl-uniform-matrix4fv location count #f ptr))
+;; #:null (make-identity-matrix4))
+
+;; (define-shader-primitive-type sampler-2d
+;; #:name 'sampler-2d
+;; #:size 4
+;; #:validator integer?
+;; #:serializer
+;; (lambda (bv i texture-unit)
+;; (bytevector-s32-native-set! bv i texture-unit))
+;; #:setter gl-uniform1iv
+;; #:null 0)
+
+;; (define-shader-primitive-type sampler-cube
+;; #:name 'sampler-cube
+;; #:size 4
+;; #:validator integer?
+;; #:serializer
+;; (lambda (bv i texture-unit)
+;; (bytevector-s32-native-set! bv i texture-unit))
+;; #:setter gl-uniform1iv
+;; #:null 0)
+
+;;
+;; ;;;
+;; ;;; Compound Shader Data Types
+;; ;;;
+
+;; ;; A meta-vtable that has two additional slots: one for the struct
+;; ;; name, and another for the lookup table that maps struct field names
+;; ;; to their respective struct index and shader data type.
+;; (define <shader-struct>
+;; (make-vtable (string-append standard-vtable-fields "pwpw")
+;; (lambda (vt port)
+;; (format port "#<shader-struct ~a>"
+;; (shader-struct-fields vt)))))
+
+;; (define local-field (gensym "local-shader-field-"))
+
+;; (define (shader-struct? struct)
+;; (eq? (struct-vtable (struct-vtable struct)) <shader-struct>))
+
+;; (define shader-struct-name-index vtable-offset-user)
+;; (define shader-struct-fields-index (+ vtable-offset-user 1))
+
+;; (define (shader-struct-name vtable)
+;; (struct-ref vtable shader-struct-name-index))
+
+;; (define (shader-struct-fields vtable)
+;; (struct-ref vtable shader-struct-fields-index))
+
+;; (define (shader-struct-type-check vtable field value)
+;; (match (assq-ref (shader-struct-fields vtable) field)
+;; ((_ type size)
+;; (define (validate value)
+;; (unless (or (and (struct? value) (eq? (struct-vtable value) type))
+;; (shader-primitive-type-validate type value))
+;; (error "invalid type for shader struct field" field value)))
+;; (cond
+;; ((eq? type local-field)
+;; #t)
+;; ((= size 1)
+;; (validate value))
+;; ((and (vector? value)
+;; (= (vector-length value) size))
+;; (for-range ((i (vector-length value)))
+;; (validate (vector-ref value i))))
+;; ((vector? value)
+;; (error "incorrect vector size" value))
+;; (else
+;; (error "expected vector, got" value))))))
+
+;; (define (shader-struct-default vtable field)
+;; (match (assq-ref (shader-struct-fields vtable) field)
+;; ((_ type size)
+;; (let ((default (cond
+;; ((eq? type local-field)
+;; #f)
+;; ((eq? (struct-vtable type) <shader-struct>)
+;; (apply make-struct/no-tail type
+;; (map (match-lambda
+;; ((sub-field _ _ _)
+;; (shader-struct-default type sub-field)))
+;; (shader-struct-fields type))))
+;; (else
+;; (shader-primitive-type-null type)))))
+;; (if (= size 1)
+;; default
+;; (make-vector size default))))))
+
+;; (define (make-shader-struct-field-table fields+types)
+;; ;; Map field names to their struct indices and shader types.
+;; (let loop ((i 0)
+;; (fields+types fields+types))
+;; (match fields+types
+;; (() '())
+;; (((name #(type size)) . rest)
+;; (cons (list name i type size)
+;; (loop (+ i 1) rest)))
+;; (((name type) . rest)
+;; (cons (list name i type 1)
+;; (loop (+ i 1) rest))))))
+
+;; (define (display-shader-struct-instance obj port)
+;; (let ((vtable (struct-vtable obj)))
+;; (format port "#<~a" (shader-struct-name vtable))
+;; (let loop ((fields (shader-struct-fields vtable)))
+;; (match fields
+;; (() #f)
+;; (((name index type size) . rest)
+;; (format port " ~a[~d ~a]: ~a"
+;; name size
+;; (if (eq? type local-field)
+;; 'local
+;; (shader-primitive-type-name type))
+;; (struct-ref obj index))
+;; (loop rest))))
+;; (display ">" port)))
+
+;; (define (make-display-name sym)
+;; (let ((str (symbol->string sym)))
+;; (if (and (string-prefix? "<" str)
+;; (string-suffix? ">" str))
+;; (substring str 1 (- (string-length str) 1))
+;; str)))
+
+;; (define (make-shader-struct name fields+types)
+;; (make-struct/no-tail <shader-struct>
+;; (make-struct-layout
+;; (string-concatenate
+;; (map (const "pw") fields+types)))
+;; display-shader-struct-instance
+;; (make-display-name name)
+;; (make-shader-struct-field-table fields+types)))
+
+;; (define (shader-struct-ref struct field)
+;; (match (assq-ref (shader-struct-fields (struct-vtable struct)) field)
+;; (#f
+;; (error "unknown struct field" field))
+;; ((index _ _)
+;; (struct-ref struct index))))
+
+;; (define-syntax define-accessors
+;; (syntax-rules ()
+;; ((_ <struct> field getter)
+;; (define getter
+;; (let ((index (match (assq-ref (shader-struct-fields <struct>) 'field)
+;; ((i _ _) i))))
+;; (lambda (obj)
+;; (struct-ref obj index)))))
+;; ((_ <struct> field getter setter)
+;; (begin
+;; (define-accessors <struct> field getter)
+;; (define setter
+;; (let ((index (match (assq-ref (shader-struct-fields <struct>) 'field)
+;; ((i _ _) i))))
+;; (lambda (obj value)
+;; (shader-struct-type-check <struct> 'field value)
+;; (struct-set! obj index value))))))))
+
+;; (define-syntax define-shader-type
+;; (syntax-rules ()
+;; ((_ <name> constructor predicate (field-type field-name . field-rest) ...)
+;; (begin
+;; (define <name>
+;; (make-shader-struct '<name> (list (list 'field-name field-type) ...)))
+;; (define* (constructor #:key (field-name (shader-struct-default <name> 'field-name)) ...)
+;; (shader-struct-type-check <name> 'field-name field-name) ...
+;; (make-struct/no-tail <name> field-name ...))
+;; (define (predicate obj)
+;; (and (struct? obj) (eq? (struct-vtable obj) <name>)))
+;; (define-accessors <name> field-name . field-rest) ...))))
+
+;;
+;; ;;;
+;; ;;; Shaders
+;; ;;;
+
+;; (define-record-type <shader>
+;; (%make-shader id attributes uniforms scratch scratch-pointer)
+;; shader?
+;; (id shader-id)
+;; (attributes shader-attributes)
+;; (uniforms shader-uniforms)
+;; ;; Scratch space for serializing uniform values.
+;; (scratch shader-scratch)
+;; ;; Create the pointer once and hold onto it to reduce needless
+;; ;; garbage creation.
+;; (scratch-pointer shader-scratch-pointer))
+
+;; ;; Represents a single active uniform location in a shader program.
+;; (define-record-type <uniform>
+;; (make-uniform name location type size value)
+;; uniform?
+;; (name uniform-name)
+;; (location uniform-location)
+;; (type uniform-type)
+;; (size uniform-size)
+;; (value uniform-value %set-uniform-value!))
+
+;; (define (sampler? uniform)
+;; (or (eq? (uniform-type uniform) sampler-2d)
+;; (eq? (uniform-type uniform) sampler-cube)))
+
+;; ;; variable name -> {uniform, namespace, uniform array} map
+;; (define-record-type <uniform-namespace>
+;; (make-uniform-namespace name members)
+;; uniform-namespace?
+;; (name uniform-namespace-name)
+;; (members uniform-namespace-members))
+
+;; (define (fresh-uniform-namespace name)
+;; (make-uniform-namespace name (make-hash-table)))
+
+;; (define (uniform-namespace-set! namespace name value)
+;; (hashq-set! (uniform-namespace-members namespace) name value))
+
+;; (define (uniform-namespace-ref namespace name)
+;; (hashq-ref (uniform-namespace-members namespace) name))
+
+;; (define (uniform-namespace-ref-or-create-namespace namespace name)
+;; (or (uniform-namespace-ref namespace name)
+;; (let ((new-ns (fresh-uniform-namespace name)))
+;; (uniform-namespace-set! namespace name new-ns)
+;; new-ns)))
+
+;; (define (uniform-namespace-ref-or-create-array namespace name)
+;; (or (uniform-namespace-ref namespace name)
+;; (let ((new-arr (fresh-uniform-array name)))
+;; (uniform-namespace-set! namespace name new-arr)
+;; new-arr)))
+
+;; (define (uniform-namespace-for-each proc namespace)
+;; (hash-for-each proc (uniform-namespace-members namespace)))
+
+;; ;; variable name -> {uniform, namespace} map
+;; (define-record-type <uniform-array>
+;; (make-uniform-array name namespaces size)
+;; uniform-array?
+;; (name uniform-array-name)
+;; (namespaces uniform-array-namespaces)
+;; (size uniform-array-size set-uniform-array-size!))
+
+;; (define (fresh-uniform-array name)
+;; (make-uniform-array name (make-hash-table) 0))
+
+;; (define (uniform-array-namespace-ref array i)
+;; (hashv-ref (uniform-array-namespaces array) i))
+
+;; (define (uniform-array-namespace-add! array i)
+;; (let ((ns (fresh-uniform-namespace (uniform-array-name array))))
+;; (hashv-set! (uniform-array-namespaces array) i ns)
+;; (set-uniform-array-size! array (max (uniform-array-size array) (+ i 1)))
+;; ns))
+
+;; (define (uniform-array-ref-or-create array i)
+;; (or (uniform-array-namespace-ref array i)
+;; (uniform-array-namespace-add! array i)))
+
+;; (define-record-type <attribute>
+;; (make-attribute name location type)
+;; attribute?
+;; (name attribute-name)
+;; (location attribute-location)
+;; (type attribute-type))
+
+;; (define null-shader (%make-shader 0 (make-hash-table) (make-hash-table) #f #f))
+
+;; (define (bind-shader shader)
+;; (gl-use-program (shader-id shader)))
+
+;; (define (free-shader shader)
+;; (gl-delete-program (shader-id shader)))
+
+;; (define-graphics-finalizer shader-finalizer
+;; #:predicate shader?
+;; #:free free-shader)
+
+;; (define-graphics-state g:shader
+;; current-shader
+;; #:default null-shader
+;; #:bind bind-shader)
+
+;; (define (make-shader vertex-port fragment-port)
+;; "Read GLSL source from VERTEX-PORT and FRAGMENT-PORT and compile
+;; them into a GPU shader program."
+;; (define (shader-compiled? id)
+;; (let ((status (make-u32vector 1)))
+;; (gl-get-shaderiv id (version-2-0 compile-status)
+;; (bytevector->pointer status))
+;; (= (u32vector-ref status 0) 1)))
+;; (define (shader-linked? id)
+;; (let ((status (make-u32vector 1)))
+;; (gl-get-programiv id (version-2-0 link-status)
+;; (bytevector->pointer status))
+;; (= (u32vector-ref status 0) 1)))
+;; (define (info-log length-proc log-proc id)
+;; (let ((log-length-bv (make-u32vector 1)))
+;; (length-proc id (version-2-0 info-log-length)
+;; (bytevector->pointer log-length-bv))
+;; (u32vector-ref log-length-bv 0)
+;; ;; Add one byte to account for the null string terminator.
+;; (let* ((log-length (u32vector-ref log-length-bv 0))
+;; (log (make-u8vector (1+ log-length))))
+;; (log-proc id log-length %null-pointer (bytevector->pointer log))
+;; (utf8->string log))))
+;; (define (compilation-error id)
+;; (info-log gl-get-shaderiv gl-get-shader-info-log id))
+;; (define (linking-error id)
+;; (info-log gl-get-programiv gl-get-program-info-log id))
+;; (define (glsl-preprocessor-source)
+;; ;; Set up preprocessor directives dynamically based on the current
+;; ;; OpenGL context's GLSL version so that we can write shaders that
+;; ;; are compatible with as many systems as possible.
+;; (let ((glsl-version (graphics-engine-glsl-version)))
+;; (cond
+;; ((string>= glsl-version "3.3")
+;; "#version 330
+;; #define GLSL330
+;; ")
+;; ((string>= glsl-version "1.3")
+;; "#version 130
+;; #define GLSL130
+;; ")
+;; ((string>= glsl-version "1.2")
+;; "#version 120
+;; #define GLSL120
+;; ")
+;; (else
+;; (error "incompatible GLSL version" glsl-version)))))
+;; (define (make-shader-stage type port)
+;; (let ((id (gl-create-shader type))
+;; (source (string->utf8
+;; (string-append (glsl-preprocessor-source)
+;; (get-string-all port)))))
+;; (gl-shader-source id 1
+;; (bytevector->pointer
+;; (u64vector
+;; (pointer-address (bytevector->pointer source))))
+;; (bytevector->pointer
+;; (u32vector (bytevector-length source))))
+;; (gl-compile-shader id)
+;; (unless (shader-compiled? id)
+;; (let ((error-log (compilation-error id)))
+;; (gl-delete-shader id) ; clean up GPU resource.
+;; (display "shader compilation failed:\n")
+;; (display error-log (current-error-port))
+;; (error "failed to compile shader")))
+;; id))
+;; (define (uniform-count id)
+;; (let ((bv (make-u32vector 1)))
+;; (gl-get-programiv id
+;; (arb-shader-objects active-uniforms)
+;; (bytevector->pointer bv))
+;; (u32vector-ref bv 0)))
+;; (define (utf8->string* bv length)
+;; (let ((bv* (make-bytevector length)))
+;; (bytevector-copy! bv 0 bv* 0 length)
+;; (utf8->string bv*)))
+;; (define (parse-data-type type)
+;; (cond
+;; ((= type (version-2-0 bool)) bool)
+;; ((= type (data-type int)) int)
+;; ((= type (data-type unsigned-int)) unsigned-int)
+;; ((= type (data-type float)) float)
+;; ((= type (version-2-0 float-vec2)) float-vec2)
+;; ((= type (version-2-0 float-vec3)) float-vec3)
+;; ((= type (version-2-0 float-vec4)) float-vec4)
+;; ((= type (version-2-0 float-mat3)) mat3)
+;; ((= type (version-2-0 float-mat4)) mat4)
+;; ((= type (version-2-0 sampler-2d)) sampler-2d)
+;; ((= type (version-2-0 sampler-cube)) sampler-cube)
+;; (else
+;; (error "unsupported OpenGL type" type))))
+;; (define (camel->snake str)
+;; (list->string
+;; (let loop ((i 0))
+;; (if (< i (string-length str))
+;; (let ((c (string-ref str i)))
+;; (if (char-set-contains? char-set:upper-case c)
+;; (cons* #\- (char-downcase c) (loop (+ i 1)))
+;; (cons c (loop (+ i 1)))))
+;; '()))))
+;; (define (uniform-name->symbol name)
+;; ;; array uniform names have a suffix like "[0]" that needs to be
+;; ;; removed to produce the actual uniform variable name that our
+;; ;; shader interface will recognize.
+;; (string->symbol
+;; (let ((i (string-contains name "[")))
+;; (camel->snake
+;; (if i (substring name 0 i) name)))))
+;; (define (parse-array-index name)
+;; (let* ((start (string-contains name "["))
+;; (end (- (string-length name) 1)))
+;; (and start (string->number (substring name (+ start 1) end)))))
+;; (define (struct? name)
+;; (string-contains name "."))
+;; (define (parse-struct name uniform namespace)
+;; ;; Deconstruct the uniform name to produce a path through the
+;; ;; namespace tree, follow it to the end and add the uniform as a
+;; ;; leaf node in the tree.
+;; (let inner ((path (string-split name #\.))
+;; (namespace namespace))
+;; (match path
+;; ;; Yay, we're done!
+;; ((leaf)
+;; (uniform-namespace-set! namespace (uniform-name->symbol leaf) uniform))
+;; ((branch . rest)
+;; (let ((new-name (uniform-name->symbol branch))
+;; (index (parse-array-index branch)))
+;; ;; If there is an index included in the branch name like
+;; ;; "[1]" then that means we have a nested array of structs
+;; ;; within the struct. Otherwise, it's just a single nested
+;; ;; struct.
+;; (if index
+;; (let ((new-namespace
+;; (uniform-array-ref-or-create
+;; (uniform-namespace-ref-or-create-array namespace
+;; new-name)
+;; index)))
+;; (inner rest new-namespace))
+;; (let ((new-namespace
+;; (uniform-namespace-ref-or-create-namespace namespace
+;; new-name)))
+;; (inner rest new-namespace))))))))
+;; (define (extract-uniforms id)
+;; (let ((total (uniform-count id))
+;; (namespace (fresh-uniform-namespace "root")))
+;; ;; OpenGL has an API for shader program introspection that we
+;; ;; use to extract all active uniforms. This uniform data must
+;; ;; then be parsed and turned into a data structure that can be
+;; ;; used to translate Scheme data (either primitives or compound
+;; ;; structures) to the GPU when it comes time to actually render
+;; ;; something with the shader.
+;; (let loop ((i 0)
+;; (texture-unit 0)
+;; (scratch-size 0))
+;; (if (< i total)
+;; (let ((length-bv (make-u32vector 1))
+;; (size-bv (make-u32vector 1))
+;; (type-bv (make-u32vector 1))
+;; (name-bv (make-bytevector 255)))
+;; (gl-get-active-uniform id i
+;; (bytevector-length name-bv)
+;; (bytevector->pointer length-bv)
+;; (bytevector->pointer size-bv)
+;; (bytevector->pointer type-bv)
+;; (bytevector->pointer name-bv))
+;; (let* ((name-length (u32vector-ref length-bv 0))
+;; (name (utf8->string* name-bv name-length))
+;; (location (gl-get-uniform-location id name))
+;; (size (u32vector-ref size-bv 0))
+;; (type (parse-data-type (u32vector-ref type-bv 0)))
+;; (sampler? (or (eq? type sampler-2d) (eq? type sampler-cube)))
+;; (default (cond
+;; (sampler?
+;; texture-unit)
+;; ((= size 1)
+;; (shader-primitive-type-null type))
+;; (else
+;; (make-vector size (shader-primitive-type-null type)))))
+;; (uniform (make-uniform name location type size default)))
+;; (if (struct? name)
+;; ;; The complicated path: Parse struct name and
+;; ;; build a branch of a tree structure.
+;; (parse-struct name uniform namespace)
+;; ;; The easy path: A top-level primitive.
+;; (uniform-namespace-set! namespace
+;; (uniform-name->symbol name)
+;; uniform))
+;; (loop (1+ i)
+;; ;; A sampler uniform uses up one texture unit,
+;; ;; so move on to the next one in that case.
+;; (if sampler?
+;; (1+ texture-unit)
+;; texture-unit)
+;; ;; Ensure we have enough space to serialize the
+;; ;; largest bit of data we send to the shader.
+;; (max scratch-size
+;; (* size
+;; (shader-primitive-type-size type))))))
+;; (values namespace scratch-size)))))
+;; (define (attribute-count id)
+;; (let ((bv (make-u32vector 1)))
+;; (gl-get-programiv id
+;; (arb-shader-objects active-attributes)
+;; (bytevector->pointer bv))
+;; (u32vector-ref bv 0)))
+;; (define (extract-attributes id)
+;; (let ((total (attribute-count id))
+;; (table (make-hash-table)))
+;; (for-range ((i total))
+;; (let ((length-bv (make-u32vector 1))
+;; (size-bv (make-u32vector 1))
+;; (type-bv (make-u32vector 1))
+;; (name-bv (make-bytevector 255)))
+;; (gl-get-active-attrib id i
+;; (bytevector-length name-bv)
+;; (bytevector->pointer length-bv)
+;; (bytevector->pointer size-bv)
+;; (bytevector->pointer type-bv)
+;; (bytevector->pointer name-bv))
+;; (let* ((length (u32vector-ref length-bv 0))
+;; (name (utf8->string* name-bv length))
+;; (size (u32vector-ref size-bv 0))
+;; (type (parse-data-type (u32vector-ref type-bv 0)))
+;; (location (gl-get-attrib-location id name)))
+;; (unless (= size 1)
+;; (error "unsupported attribute size" name size))
+;; (hash-set! table name (make-attribute name location type)))))
+;; table))
+;; (assert-current-graphics-engine)
+;; (let ((vertex-id (make-shader-stage (version-2-0 vertex-shader)
+;; vertex-port))
+;; (fragment-id (make-shader-stage (version-2-0 fragment-shader)
+;; fragment-port))
+;; (id (gl-create-program)))
+;; (gl-attach-shader id vertex-id)
+;; (gl-attach-shader id fragment-id)
+;; (gl-link-program id)
+;; (unless (shader-linked? id)
+;; (let ((error-log (linking-error id)))
+;; (gl-delete-program id)
+;; (error "failed to link shader" error-log)))
+;; (gl-delete-shader vertex-id)
+;; (gl-delete-shader fragment-id)
+;; (call-with-values
+;; (lambda () (extract-uniforms id))
+;; (lambda (namespace scratch-size)
+;; (let* ((scratch (make-bytevector scratch-size))
+;; (scratch-ptr (bytevector->pointer scratch))
+;; (shader (%make-shader id (extract-attributes id) namespace
+;; scratch scratch-ptr)))
+;; (graphics-engine-guard! shader)
+;; shader)))))
+
+;; (define (load-shader vertex-source-file fragment-source-file)
+;; "Compile the GLSL source code within VERTEX-SOURCE-FILE and
+;; FRAGMENT-SOURCE-FILE into a GPU shader program."
+;; (call-with-input-file vertex-source-file
+;; (lambda (vertex-port)
+;; (call-with-input-file fragment-source-file
+;; (lambda (fragment-port)
+;; (make-shader vertex-port fragment-port))))))
+
+;; (define (strings->shader vertex-source fragment-source)
+;; "Compile VERTEX-SOURCE, the GLSL code for the vertex shader,
+;; and FRAGMENT-SOURCE, the GLSL code for the fragment shader, into a GPU
+;; shader program."
+;; (call-with-input-string vertex-source
+;; (lambda (vertex-port)
+;; (call-with-input-string fragment-source
+;; (lambda (fragment-port)
+;; (make-shader vertex-port fragment-port))))))
+
+;; (define (shader-uniform shader name)
+;; "Return the metadata for the uniform NAME in SHADER."
+;; (let ((uniform (uniform-namespace-ref (shader-uniforms shader) name)))
+;; (or uniform (error "no such uniform" name))))
+
+;; (define (set-uniform-value! shader uniform value)
+;; ;; TODO: Figure out a way to avoid unnecessary uniform
+;; ;; updates. Maybe UBOs would help address this?
+;; (let ((type (uniform-type uniform)))
+;; (shader-primitive-type-serialize type (shader-scratch shader) value)
+;; (shader-primitive-type-apply-uniform type (uniform-location uniform) 1
+;; (shader-scratch-pointer shader))
+;; (%set-uniform-value! uniform value)))
+
+;; (define (shader-uniform-for-each* proc shader thing)
+;; (cond
+;; ((uniform? thing)
+;; (proc thing))
+;; ((uniform-namespace? thing)
+;; (uniform-namespace-for-each
+;; (lambda (key uniform)
+;; (shader-uniform-for-each* proc shader uniform))
+;; thing))
+;; ((uniform-array? thing)
+;; (for-range ((i (uniform-array-size thing)))
+;; (shader-uniform-for-each* proc shader (uniform-array-namespace-ref thing i))))))
+
+;; (define (shader-uniform-for-each proc shader)
+;; (shader-uniform-for-each* proc shader (shader-uniforms shader)))
+
+;; ;; TODO: This walks the entire tree every time, but it should instead
+;; ;; stop traversing once it finds the correct leaf node.
+;; (define (%shader-uniform-set shader uniform value)
+;; (cond
+;; ;; A leaf node of the uniform tree representing a single uniform
+;; ;; location as determined by OpenGL.
+;; ((uniform? uniform)
+;; ;; A vector of a primitive type must be the exact size that
+;; ;; the shader expects.
+;; (when (and (> (uniform-size uniform) 1)
+;; (not (= (uniform-size uniform) (vector-length value))))
+;; (error "vector size mismatch for uniform" uniform-name))
+;; (set-uniform-value! shader uniform value))
+;; ;; A nested namespace indicates that this must be a struct.
+;; ((uniform-namespace? uniform)
+;; (if (shader-struct? value)
+;; (uniform-namespace-for-each
+;; (lambda (key uniform)
+;; ;; Samplers are opaque types and you cannot pass them
+;; ;; into the shader as uniform values like you can with
+;; ;; most other values. In the case of samplers, they are
+;; ;; mapped to OpenGL's "texture units", so we need to
+;; ;; ignore them here.
+;; (unless (sampler? uniform)
+;; (%shader-uniform-set shader uniform
+;; (shader-struct-ref value key))))
+;; uniform)
+;; (error "expected shader struct" value)))
+;; ;; A nested array namespace indicates that this must be an array
+;; ;; of structs.
+;; ((uniform-array? uniform)
+;; (let ((size (uniform-array-size uniform)))
+;; ;; Vector size must match what the shader expects.
+;; (if (and (vector? value)
+;; (= size (vector-length value)))
+;; (for-range ((i size))
+;; (%shader-uniform-set shader
+;; (uniform-array-namespace-ref uniform i)
+;; (vector-ref value i)))
+;; (error "vector size mismatch for uniform"
+;; (uniform-array-name uniform)))))))
+
+;; (define (shader-uniform-set! shader uniform-name x)
+;; ;; Walk the uniform namespace tree until we get to a leaf node or
+;; ;; nodes.
+;; (%shader-uniform-set shader (shader-uniform shader uniform-name) x))
+
+;; (define-syntax uniform-apply
+;; (lambda (x)
+;; (syntax-case x ()
+;; ((_ shader ()) (datum->syntax x #t))
+;; ((_ shader (name value . rest))
+;; (with-syntax ((sname (datum->syntax x (keyword->symbol
+;; (syntax->datum #'name)))))
+;; #'(begin
+;; (shader-uniform-set! shader 'sname value)
+;; (uniform-apply shader rest)))))))
+
+;; (define-syntax-rule (shader-apply** shader* vertex-array uniforms exp)
+;; (with-graphics-state! ((g:shader shader*))
+;; (uniform-apply shader* uniforms)
+;; ;; Sampler2D values aren't explicitly passed as uniform values via
+;; ;; shader-apply, so we have to bind them to the proper texture units
+;; ;; behind the scenes.
+;; (shader-uniform-for-each
+;; (lambda (uniform)
+;; (when (or (eq? (uniform-type uniform) sampler-2d)
+;; (eq? (uniform-type uniform) sampler-cube))
+;; (set-uniform-value! shader* uniform (uniform-value uniform))))
+;; shader*)
+;; exp))
+
+;; (define-syntax-rule (shader-apply* shader vertex-array offset count . uniforms)
+;; (shader-apply** shader vertex-array uniforms
+;; (render-vertices vertex-array count offset)))
+
+;; (define-syntax-rule (shader-apply shader vertex-array uniforms ...)
+;; (shader-apply* shader vertex-array 0 #f uniforms ...))
+
+;; (define-syntax-rule (shader-apply/instanced* shader vertex-array offset count instances .
+;; uniforms)
+;; (shader-apply** shader vertex-array uniforms
+;; (render-vertices/instanced vertex-array instances count offset)))
+
+;; (define-syntax-rule (shader-apply/instanced shader vertex-array instances
+;; uniforms ...)
+;; (shader-apply/instanced* shader vertex-array 0 #f instances uniforms ...))