glkit

https://github.com/lispgames/glkit.git

git clone 'https://github.com/lispgames/glkit.git'

(ql:quickload :glkit)
4

glkit

This is a utility kit for functionality related to OpenGL. Right now, it provides the following:

Shaders

kit.gl.shader provides a thin, extensible, program-oriented model for GL shaders. This closely mimics how GL works:

(defdict shaders-3.3 (:shader-path #P"...")
   (shader basic-vertex :vertex-shader (:file "vertex.glsl"))
   (program :solid (:color)
     (:vertex-shader basic-vertex)
     (:fragment-shader "..."))
   (program (:sprite
              :uniforms ((:texture "texture_id"))
              :attrs ((:vertex 0)
                      (:uv 1)
                      ...))
     (:vertex-shader basic-vertex)
     (:fragment-shader (:file "..."))))

This defines a series of shader programs grouped into a “dictionary”. Once in a GL context, one or more dictionaries may be compiled, programs activated, and uniforms set, all symbolically.

You may specify any legal combination of shaders in the shaders section. Reusing text between shaders is easy by defining named shaders with the SHADER directive. Text may also be loaded from a file using (:file PATHNAME) as above. Additional handling may be defined; see below.

To actually compile and use a dictionary, call the following; it will attempt to compile and link all the specified programs, reporting any errors along the way to *error-output*:

(compile-shader-dictionary (dict shaders-3.3))
  ;; => DICTIONARY

This requires a valid GL context, will only work when it is otherwise legal to make GL calls. As well, the returned SHADER-DICTIONARY object is only valid in the GL context in which it was compiled. It will not work in others. If desired, more than one dictionary may be compiled in a context; nothing besides convenience groups programs in a dictionary.

Once you have this object, you may do interesting things with it:

(kit.gl.shader:use-program DICTIONARY :name)

;; Note these apply only to the *current program*,
;; different programs have different sets of uniforms
(kit.gl.shader:uniformi DICTIONARY :v1 0)
(kit.gl.shader:uniformf DICTIONARY :v2 x y)

;; etc

Note these are different functions than the cl-opengl variety; they take the dictionary object, as well as symbolic names, rather than IDs.

Customizing

It's also possible to define other ways to produce shader strings, by specializing either of the following generic functions:

(parse-shader-source SOURCE SHADER-TYPE SHADER-LIST)
(parse-shader-source-complex KEY PARAMS SHADER-TYPE SHADER-LIST)

The first specializes on a few SOURCE types by default; do not alter these:

The SHADER-TYPE parameter is any valid shader type, e.g. :vertex-shader; SHADER-LIST is the current list of “named” shaders, in the form (NAME . (TYPE VALUE)). Notably, VALUE is not processed, and should be passed recursively to PARSE-SHADER-SOURCE if used.

To process forms like (:file PATHNAME), PARSE-SHADER-SOURCE-COMPLEX takes the CAR and CDR or that list, as well other parameters similar to PARSE-SHADER-SOURCE.

These are mostly useful for projects which desire to add extended shader capability, such as a shader DSL, or loading in some other manner.

VAOs

kit.gl.vao provides an easy way to define VAO layouts, as well as instantiate, bind, and draw them. It aims to provide complete VAO functionality.

To use, first one defines a VAO:

(defvao NAME ()
  (LAYOUT-TYPE (OPTS)
    (ATTR :type COUNT)
    ...)
  (LAYOUT-TYPE
    ...))

For example:

(defvao vertex-color ()
  (:separate ()
    (vertex :float 3)
    (color :float 3)))

This defines a VAO with VERTEX and COLOR attributes, which are each 3 :float values. This uses a separate VBO for each. (The LAYOUT-TYPE will be covered below.)

Using a VAO is just as easy:

(let ((vao (make-instance 'vao :type 'vertex-color)))
  (vao-buffer-data vao 0 (* 4 VERTEX-FLOAT-COUNT) POINTER-TO-VERTEX-FLOATS)
  (vao-buffer-data vao 1 (* 4 COLOR-FLOAT-COUNT) POINTER-TO-COLOR-FLOATS)

  ;; Establish more stuff here.. active shaders, uniforms, etc, then:
  (vao-draw vao :count VERTEX-COUNT))

This requires a valid, active GL context, just like other GL functions. DEFVAO does not, but everything else, including the make-instance, does.

Alternatively, you can use VAO-BUFFER-VECTOR (and VAO-BUFFER-SUB-VECTOR), and supply a vector of :element-type 'single-float or :element-type 'double-float instead of a pointer. This is only available if your implementation supports static-vectors (most do). This is for convenience; managing the data yourself can reduce copying and consing considerably.

Note the numbers above require you fill in a few specific things:

The pointer data you must supply pre-formatted. However, for separate VBOs, this is reasonably easy to accomplish with something like static-vectors, or you can use the less-efficient -vector variants which do this for you.

Dictionary

Layouts

There are three layout types:

You may specify one or more groups to a VAO definition:

(defvao NAME
  (:separate () ...)
  (:separate () ...)
  (:interleave () ...)
  ...)

You must be aware of the underlying VBO layout to the extent that you must specify the correct index to vao-buffer-data. In the future, you will be able to specify a valid symbolic name, though this may not be as efficient.

You may also specify a :divisor option to the group, which corresponds to the DIVISOR parameter to glVertexAttribDivisor, allowing one attribute for multiple vertices. Note that because this is per-group, if you wish to have separate divisors per attribute, they must be in separate groups.