LPSG ยป LPSG

Overview

LPSG is a Common Lisp library for rendering graphics using OpenGL. It presents a programming interface that allows it to implement optimizations that are important on modern graphics hardware. LPSG's target applications include 3D content creation programs, games, and geospatial viewing programs -- in short, any domain in which excellent interactive rendering of complex scenes is required. Another important class of applications that LPSG will support is 2D applications such as text editors which need fast, portable rendering.

Note: LPSG is a work in progress. Throughout this manual future work or features will be indicated [like this.]

While LPSG does provide classes and functions for common 3D rendering tasks, its essential purpose is to aid the creation of fast programs that use OpenGL. A typical application that uses LPSG will specialize a few LPSG public classes and generic functions in order to inject some OpenGL code, such as a custom shader program, into LPSG's rendering code.

The main optimization attempted by LPSG is to reduce the memory traffic between the CPU and GPU. It does this by storing the user's data in OpenGL buffer objects which normally reside on the GPU. Most updates to a graphics view, such as matrices that specify eye or object positions, are small; these are handled through a system of parameters called usets. On the other hand, LPSG does [will] support efficient methods for uploading large updates of user data.

Another important optimization is reducing CPU cache misses during rendering. This is often caused by traversing large tree-like structures or scene graphs in the user's application. LPSG uses a different approach: the application submits graphics objects to LPSG, which is from then on responsible for traversing the objects and displaying them. LPSG can use data structures that are more efficient than the application's, [and much of the traversal may even be moved to the GPU.] Newer versions of OpenGL contain interfaces that can reduce the number of cache misses inside the driver itself, [LPSG will take of advantage of them.]

Another optimization which was historically crucial, and is still helpful, is reducing the number of state changes in the graphics hardware. Changing the rendering target, current shader program, or set of bound textures requires reinitializing a lot of hardware state and may cause a stall on the GPU. [LPSG will sort the rendering order of graphics objects to minimize state changes.]

A side effect of all these optimizations is to reduce the number of calls to the OpenGL API. This is good for OpenGL performance in general, as each OpenGL call requires work from the driver to at least validate it and check for errors. This is especially good in Common Lisp, as "foreign function calls" can be quite expensive.

Conventions

LPSG is described in terms of protocols and protocol classes. A protocol is a set of generic functions that collaborate to do something. The functions in a protocol must restrict some of their arguments to classes that have certain properties. These classes are protocol classes, abstract classes that serve only as superclasses. A protocol class defines accessors, which behave like normal slot accessors, although they can take more than one argument. It also defines normal generic functions that are considered to be part of the class, even though classes don't implement functions and methods in Common Lisp.

If a class inherits from a protocol class, then there must exist applicable methods for all the accessors and generic functions defined by the protocol class when the derived class appears as an argument to them. LPSG provides mixins that contain slots whose accessor methods implement the protocol classes' accessor generic functions.

Protocol classes are defined using the define-protocol-class macro:

define-protocol-class(name super-classes generic-functions &rest options)
Define a protocol class.

LPSG provides instantiable classes that are subclasses of its protocol classes. These have names beginning with "standard-" or "simple-" and are documented along with the protocol classes.

High level LPSG

LPSG contains a renderer class that controls all rendering to the screen. The user creates shapes containing per-vertex attributes, such as position or color data. The shape also contains an effect object which controls its appearence on the screen.

Once a shape has been prepared, it is submitted to the renderer using the generic function submit. This invokes the submit-with-effect generic function which, dispatching on its renderer, shape, and effect arguments, creates the objects that are used in the actual OpenGL rendering and places them on various queues. These are used when the user calls the renderer's draw method; at that time OpenGL objects are created on the GPU, any necessary initialization is performed, and shapes are rendered. On future calls to draw, these objects will be rendered again. The user effectively gives up ownership of a shape object until the retract method is called on it. This removes the objects created in the submit call from the renderer's queues and destroys any now-uneeded OpenGL objects.

Scenes in 3D applications are very dynamic; if they weren't, there wouldn't be much point in displaying them in 3D. At the least, it is possible for the viewpoint to change at every redraw of the screen. The geometric objects in the scene may be animated as well, and these are often evaluated within systems of nested coordinate systems. To support this dynamism, LPSG implements a system of parameters that are connected in dependency chains. These incremental-node objects use lazy evaluation to supply values that affect OpenGL rendering. These values are most often used to set the uniform variables in OpenGL shader programs. LPSG aggregates such variables into sets, in order to choose the optimal way to upload them to OpenGL; these sets are called "uniform sets" or, throughout LPSG, "usets."

Getting Started with LPSG

1. Prerequisites

LPSG and its example programs require OpenGL drivers that support version 3.3 at a minimum. It might work in older versions of OpenGL with some hacking. It has been developed using SBCL on x86_64 Linux and Clozure Common Lisp on ARM Linux and Windows.

2. Dependencies and Quicklisp

LPSG depends on several Common Lisp packages, including cl-opengl for interfacing with OpenGL. The easiest way to install these dependencies is to use @link[uri="https://www.quicklisp.org/beta/"'(Qucklisp). LPSG itself is not [yet] in Quicklisp, so the easiest way to use Quicklisp is to put the LPSG source in your quicklisp/local-projects directory and type

(ql:quickload :lpsg)

to load LPSG and its Common Lisp dependencies.

3. Examples

LPSG comes with some simple example programs in the examples subdirectory:

The Renderer

The renderer class that controls all rendering to the screen. Generic functions that dispatch on renderer ultimately perform all OpenGL calls. open-renderer performs any OpenGL initialization needed; close-renderer cleans up when the application is finished rendering.

The user calls the submit method to register objects with a renderer. Geometric objects, called "shapes," that have been submitted will then be rendered when the draw generic function is called. Submitted objects persist between calls to draw.

The retract generic function removes an object from a renderer. It will not be rendered anymore, and any OpenGL objects used by it will be deallocated if not used by any other LPSG objects. This deallocation is not immediate because it relies on garbage collection to notice when an object is no longer used.

renderer
The class responsible for all rendering.
    open-renderer(renderer)

    Intialize renderer for rendering.

    The OpenGL context that will be used to do all rendering must be current when this is called. This method verifies that the context can support the rendering done by lpsg; that is, the version of OpenGL supports the features needed by lpsg or has extensions that support them. It also records parameters and capabilities of the OpenGL implementation, such as the number of texture units.

    close-renderer(renderer &key deallocate-objects)

    Close renderer for rendering.

    If deallocate-objects is t, then all OpenGL objects that are still allocated by LPSG will be explicitly deallocated. The default, nil, doesn't deallocate these objects; it assumes that the context will soon be destroyed.

    draw(renderer)

    Draw all graphic objects that have been registered with renderer.

    Perform all outstanding operations in renderer: finalize all objects on the finalize queue(s), do any upload operations in the upload queue, then traverse the render stages and their render queues to render all bundles. The renderer does not swap OpenGL front and back buffers; that is done by the application outside of LPSG.

    standard-renderer
    The standard instantiable class of renderer.
    • buffers
      private
    • bundles
      private
    • current-state
      private
    • predraw-queue
      private
    • finalize-queue
      private
    • upload-queue
      private
    • render-stage
      The top-level (default) render stage.
    • vao-cache
      private
    • gl-objects
      List of all OpenGL objects allocated by calls in LPSG.
    • context-parameters
      Parameters of the OpenGL context.
    submit(object renderer)
    Submit OBJECT to RENDERER.
    retract(object renderer)
    Remove OBJECT from consideration by RENDERER. This may deallocate graphics API resources.

    Attributes and shapes

    The shape class represents geometry that is rendred by OpenGL. In that sense it contains a collection of vertex attributes i.e. the per-vertex data used in the rendering. This might include a position in space (coordinates); a surface normal vector, colors, texture coordinates, or anything else in this modern world of shaders that could possibly be stored per-vertex in a shape. Each vertex attribute is stored in a vertex-attribute object.

    shape
    Protocol class for rendered objects.
      standard-shape

      Class for geometry coupled with an effect.

      When a shape is submitted to the renderer, all its input nodes are copied into the environment objects that might be created. This means that a shape's inputs cannot be setf'ed after submission.

      Inputs

      visiblep - true if shape is visible, false if not

      • attributes
        Alist of (name . attribute). The names are later mapped to a vertex binding index.
      • effect
        private
      • usets
        private
      • drawable
        private
      • bundles
        The bundles that are created by EFFECT for this shape.

      Buffers

      The vertex attributes used to render a shape are stored in a vertex buffer objects(VBO) within OpenGL. This object often resides in the GPU's memory. Different vertex attributes from different shapes can be stored in the same buffer object. LPSG references them with a buffer-area object that describes the location and format of data within a buffer.

      The data in a buffer object is in the format required by the GPU and not easily accessible from a program running on the CPU. It is convenient for a Lisp program to manipulate shape attributes in Lisp arrays. The mirrored-buffer-resource class, which is a subclass of buffer-area, describes the location and layout of data in a Lisp array that will be uploaded by LPSG into a buffer. The source data of a mirrored-buffer-resource is described in terms of the elements of array, whereas the data location and format in the target buffer is defined in terms of bytes.

      buffer-area

      Class for formatted attribute data stored somewhere in a buffer.

      This class describes data in a vertex buffer object that will be bound using %gl:vertex-attrib-pointer.

      • buffer
        The OpenGL buffer object containing attribute data.
      • resource-size
        total size, in bytes, of attribute's data in buffer
      • components
        number of components in each element of an attribute
      • buffer-type
        GL format of data in buffer
      • normalizedp
        If true, OpenGL will normalize integer format values to [-1,1] for signed data and [0,-1] for unsigned.
      • stride
        Byte offset between the first bytes of consecutive attributes. 0 indicates that the attributes are tightly packed.
      • offset
        Offset used when binding a buffer with e.g., %gl:vertex-attrib-pointer.
      mirrored-buffer-resource

      Class holding Lisp data that will be uploaded to an OpenGL buffer object.

      The array storing the data can have any dimensionality; it will be accessed using ROW-MAJOR-AREF.

      • data
        An array of data. Each element corresponds to a component of attribute data stored in a buffer.
      • data-offset
        Offset of buffer data from the beginning of the data array.
      • data-count
        number of elements
      • data-stride
        offset between start of each element, or 0 if elements are tightly packed.
      • num-components
        number of components per element. Redundant with buffer-area components?
      • buffer-offset
        Offset of the data in the target buffer.
      • upload-fn
        Function to upload Lisp data to a mapped buffer. Will be created automatically, but must be specified for now.

      The object stored in the buffer slot of a buffer-area object is of class gl-buffer, described in the OpenGL Objects and State section. It can be shared among many buffer-area objects, thus storing their data in a single buffer object in OpenGL.vertex-attribute is a subclass of mirrored-buffer-resource.

      shape contains an object called a "drawable" which describes the specific shape i.e., whether it is a collection of points, lines, or triangles, how many of these objects there are, and also whether the shape is indexed or not. This refers to whether the vertex attributes are just straight arrays of one value for every vertex in the shape, or whether the individual parts of the shape are described by indices which are indexes into the arrays of vertex attributes. Indexed shapes are important because not only do they offer a big size savings on meshes where a lot of the vertices would be repeated in the shape, they also enable another class of optimizations where the vertex attributes can be shared among different shapes.

      drawable
      Superclass for classes that describe the format, number, and layout of vertex data.
      • mode
        A mode for an OpenGL draw-elements or draw-array call, e.g. :triangles
      • vertex-count
        The total number of vertices in this geometry.
      array-drawable
      Drawable class for a shape that stores its attribute values in linear arrays.
      • first-vertex
        The starting index in the enabled arrays.
      indexed-drawable
      Drawable class for a shape that
      • index-type
        OpenGL type of the index values
      • base-vertex
        index offset to the shape's data in the attribute arrays, as used in the gl:draw-elements-base-vertex function
      • element-array
        Lisp array of element indices
      A shape is registered with the renderer using the submit method. After that, lpsg handles everything. There only thing that can be directly done to the shape is to remove it from the renderer. This is done by the retract generic function, which causes the shape to not be displayed anymore and removes the shape from LPSG.
      gl-buffer

      A buffer object allocated in OpenGL.

      In OpenGL, the usage and target parameters are hints and it is legal to use a buffer differently, but that can impact performance.

      • size
        The size of the buffer object in OpenGL. Note: this value is mutable until gl-finalize is called on the gl-buffer object.
      • usage
        Usage hint for the buffer object. Value is a cl-opengl keyword e.g., :STATIC-DRAW.
      • target
        OpenGL targert for the buffer object. Value is a cl-opengl keyword e.g., :ARRAY-BUFFER.
      attributes(shape)
      Alist of (name . attribute). The names are later mapped to a vertex binding index.
      vertex-attribute
      class for vertex attributes of shapes

        An object called an effect controls the appearance of a shape. Its role is described in the "Submit Protocol" section.

        A shape's slots may not be changed after it has been submitted to the renderer. The individual parts of the shape are not necessarily immutable; the drawable part is mutable, and one can change the number of primitives drawn, which might be useful to selectively draw parts of a shape. The vertex attributes can be mutable. It is important to be able to upload new data for many kinds of dynamic animation effects. The environment object is immutable, but the rendering it controls is influenced by uset parameters that attached to the shape via the incremental computation system, decribed next.

        Incremental computation

        One of the goals of LPSG is to optimize the amount of memory traffic to the GPU. To that end, LPSG provides a system of lazy evaluation, called "incremental nodes," which ultimately connects to the classes representing OpenGL structures and feeds them values. An application may change the value of a parameter; through a chain of intermediate connected incremental nodes, which have inputs (called "sources") and an output (called a "sink"), this causes a value to be marked as invalid. When that value is actually needed, the chain of incremental nodes that calculate the value is traversed and updated. Incremental nodes that calculate a value based on their inputs, called computation-nodes, store the result, so the value is not recomputed if it is not invalid.

        LPSG calls the generic function notify-invalid-input to mark a node as invalid. This function can be specialized to perform other actions at that time e.g., putting a node on a queue to perform an upload to the GPU at a later time.

        source-node
        Note: the input names belong to the sink nodes of this node.
          source-node-mixin
          Mixin class that provides slots and some methods for the source-node protocol class.
          • validp
            slot for validp accessor
          • sinks
            slot for sinks accessor
          sink-node
          Node that consumes values via named inputs.
            sink-node-mixin
            Mixin class that provides slots and some methods for the sink-node protocol class
            • inputs
              slot for inputs accessor
            source-sink-mixin
            Convenience mixin class the slots necessary to implement sources and sinks.
              computation-node
              an incremental computation node that has inputs (sources) and clients that use its value (sinks).
                computation-node-mixin
                convenience mixin class for implementing computation-node protocol classes.
                • cached-value
                  slot for cached-value accessor
                if-then-node
                Choose the value of the then or else input, based on the value of the if input.
                  delete-sink(node sink input-name)
                  Remove a sink node that depends on this source.
                  value(node)
                  Return node's value.
                  compute(node)
                  computes the value of this node
                  notify-invalid-input(node invalid-source input-name)
                  Called when a source of node is invalidated.
                  input-value-node
                  A node whose value can be set. Useful as the source to multiple nodes.
                  • value
                    value of the node, which is set instead of computed
                  input(node name)
                  Returns the input of a node named name. Second value indicates if input exists or not.
                  input-value(node input-name)
                  Get the value of the input input-name in node.

                  An example:

                  (progn
                    (defclass plus-node (computation-node computation-node-mixin source-sink-mixin)
                      ())
                  
                    (defmethod compute ((node plus-node))
                      (let ((arg1 (input-value node 'arg1))
                            (arg2 (input-value node 'arg2)))
                        (+ arg1 arg2)))
                  
                    (defclass mult-node (computation-node computation-node-mixin source-sink-mixin)
                      ())
                  
                    (defmethod compute ((node mult-node))
                      (let ((arg1 (input-value node 'arg1))
                            (arg2 (input-value node 'arg2)))
                        (* arg1 arg2)))
                  
                    (defparameter *source1* (make-instance 'input-value-node))
                    (defparameter *source2* (make-instance 'input-value-node))
                  
                    (defparameter *plus-node* (make-instance 'plus-node))
                    (defparameter *mult-node* (make-instance 'mult-node))
                  
                    (setf (input *plus-node* 'arg1) *source1*)
                    (setf (input *plus-node* 'arg2) *source2*)
                  
                    (setf (input *mult-node* 'arg1) *source1*)
                    (setf (input *mult-node* 'arg2) *source2*)
                  
                    (setf (value *source1*) 4)
                    (setf (value *source2*) 8)
                  
                    (defparameter *mult-node2* (make-instance 'mult-node))
                    (setf (input *mult-node2* 'arg1) *plus-node*)
                    (setf (input *mult-node2* 'arg2) *mult-node*)
                    ;; Value of *mult-node2* should be 384.
                    (format t "~%*mult-node2*: ~S" (value *mult-node2*))
                  
                    (defparameter *mult-node3* (make-instance 'mult-node))
                    (setf (input *mult-node3* 'arg1) *mult-node2*)
                    (setf (input *mult-node3* 'arg2) 10)
                    ;; Value of *mult-node3* should be 3840.
                    (format t "~%*mult-node3*: ~S" (value *mult-node3*))
                    )
                  

                  source-node objects can produce any type of value.

                  Uniform sets

                  This is all not very useful in terms of writing 3D applications. because if you put a shape on the screen with all these immutable attributes, then you can't navigate in the scene! You can't move the objects, no animation, can't do anything. This problem is resolved in lpsg by specifying parameters that end up affecting the environment. The parameters are grouped into sets, which we call "uniform sets," or "usets" as is used throughout lpsg. The idea is that parameters that are related to each other are grouped into sets and can eventually be updated in OpenGL in the same time, or at with the same frequency, and can be stored together. Uniform sets comes from the name that variables have in shader programs: "uniform variables." These are variables that change slowly; they do not change in the course of the rendering of a single shape. Their values might change between different shapes. And this is how an environment object could be used to render different shapes on the screen because one parameter of the environment would have to be the model matrix, which specifies where the object is in space. This is a parameter that is different for every shape, and it could change during the execution of the user's program. Another example of a uset are the parameters describing the viewpoint and camera. These are usually know as the view matrix and projection matrix. -- As well as other matrices associated with the camera, such as an inverse, transposed matrix used to transform normal vectors.

                  define-uset(name variables &rest desc-args &key (strategy default strategyp) descriptor-class)

                  Define a uset.

                  A uset is a set of variables that parameterize a shader program. In OpenGL, these variables are called `uniforms', and change infrequently. DEFINE-USET defines a class that stores the values, and for each value specifies how to load the value into the program.

                  VARIABLES is a list of uniform definitions, much like slot definitions in DEFCLASS. A uniform definition looks like: (gl-namestring uniform-type slot-name &rest slot-args)

                  GL-NAMESTRING is the name of the uniform in the OpenGL shader program, as a string. UNIFORM-TYPE is an OpenGL uniform type, specified as a keyword from cl-opengl. SLOT-NAME and SLOT-ARGS are the same as in DEFCLASS.

                  Because incremental computation source-node objects can produce any type of value, they can also produce uniform sets. The LPSG user assigns standard-shape nodes as sinks for incremental computation nodes that produce uset values, using the name of the uset as the input name. A shape doesn't actually have a role at render time, but it serves as a placeholder for objects that will need to receive the uset values and update rendering parameters. The appropriate connections are made when the shape is submitted. [This may change when the incremental computation protocol is based more on the MOP and is a bit less dynamic in terms of input slots. Also, it makes more sense to attach incremental nodes to the effect object than to the shape.] The usets produced by incremental nodes should not be freshly allocated at each update, but should be updated in place. Environments currently rely on this, which is kind of brittle and should be fixed.

                  Textures

                  Textures are raster data which are most often used to map images onto polygonal faces in the rendered scene. They are treated differently from buffer objects by OpenGL because they are used differently. the individual texels of a texture are usually sampled in a 2D region rather than linearly, so a conventional cacheing strategy doesn't work well. Also, different resolutions of the texture are often stored in a mipmap to support filtering. Following the lead of OpenGL, LPSG provides classes that are not a buffer-area or mirrored-buffer-resource for managing textures.

                  texture-area
                  Superclass for image data that will be stored in a texture.
                  • level
                    Mipmap level of this data in the texture.
                  • texture
                    OpenGL texture object
                  • x-offset
                    X offset of data in texture image.
                  • y-offset
                    Y offset of data in texture image.
                  • width
                    Width of image in pixels
                  • height
                    Height of image in pixels
                  • generate-mipmap-p
                    If true (default), a mipmap will be generated in the texture.
                  raw-mirrored-texture-resource
                  Class for texture data stored in foreign memory.
                  • data
                    foreign memory containing the image data
                  • data-offset
                    offset of image data in foreign memory
                  • data-count
                    number of elements
                  • row-alignment
                    alignment, in bytes, of each row of the image data

                  There is an asymmetry in the LPSG interfaces, and their concrete implementations, for buffer-area and texture-area classes. The buffer area provides offset and stride parameters for accessing source data and packing them in a buffer for OpenGL. In contrast, texture-area and its subclasses provide x-offset, y-offset, and row-alignment parameters for accessing an arbitrary "rectangle" of data in a larger image. This reflects the functionality offered by OpenGL, as well as the different uses of buffer objects and textures.[A less excusable asymmetry currently exists in the implementation: the mirrored-buffer-resource only supports loading buffers from Lisp arrays, whereas raw-mirrored-texture-resource, the only subclass of texture-resource, only supports loading from "foreign" memory. This is due to pragmatism -- image loading libraries are likely to return image data as foreign memory -- as well as laziness. It will be fixed soon.]

                  The Submit Protocol

                  The effect object stored in a shape is not used directly to perform OpenGL rendering. It does, however, control how the rendering will be done. This is accomplished in the submit generic function, when a shape is submitted to the renderer. submit immediately calls another generic function, submit-with-effect, which can be specialized on its shape and effect arguments.

                  submit-with-effect(shape renderer effect)

                  Submit SHAPE to RENDERER.

                  This function creates all the bundles necessary to render SHAPE with the appearance defined by EFFECT. Usually the effect is stored in the shape, so this method doesn't need to be called directly; (submit shape renderer) is equivalent.

                  The protocol class effect is the superclass of all effect objects.

                  effect

                  Class that represents the rendered appearance of a shape.

                  EFFECT is responsable for creating bundles and their environments and putting them in the appropriate render queues. The effect object contains graphics environments. The SIMPLE-EFFECT class only has one environment, but other effects might have different environments for different passes.

                  • attribute-map
                    list of (symbol glsl-name) where glsl-name is a string

                  The attribute-map is an essential link between a shape and its on-screen representation. It maps the names of attributes in the shape to vertex attributes in an OpenGL shaper program.submit-with-effect creates objects that are used to make calls to OpenGL and to do rendering. These objects are similar to the attribute, shape and effect objects that the user manipulates, but they are less flexible and more specific to their role in OpenGL rendering. The include:

                  submit-with-effect can create several render-bundle objects from a shape, each with a different environment(and attribute set, if necessary). These bundles are are added to render-queue objects, so they will be drawn at a later time. In this way submit-with-effect can arrange for a shape's geometry to be rendered several times with different environments.submit-with-effect also performs other important functions. Any object that refers to OpenGL objects may need to be finalized and should be placed on the "finalize" queue. "Finalization" here does not refer to garbage collector finalization, but to the actions required to create and intialize objects via calls to OpenGL. The generic function gl-finalize, described in the section "OpenGL Objects and State," is called on every object in the finalize queue.

                  All data that will be used by OpenGL must be scheduled for upload to the driver and GPU.

                  Uploading Data

                  The "upload queue" is an opaque object not directly accessible to the user. The upload of some types of objects can be optimized by grouping all uploads for a certain object e.g., a buffer object, together. The generic function schedule-upload is used to schedule the upload; this will happen after the finalize queue has been traversed, but before any shapes are drawn.

                  schedule-upload(renderer object)
                  Register an object to be uploaded to OpenGL.

                  All these queues are traversed by the draw generic function, which is described in the "Draw protocol" section.

                  [The interfaces to queues are very ad hoc at the moment. Some queues are exposed as lists; others have a more abstract interface. This all needs to be abstracted so that queue contents can be maintained in sorted order.]

                  render-queue

                  A container class for objects, including render-queue objects too.

                  This class contains the objects that are traversed to render a scene. This class does not guarantee a traversal order for objects in the queue. Subclasses of this class might sort the objects to obtain an optimal order, or in fact guarantee an order.

                    unordered-render-queue
                    A queue that contains bundles to be rendered.
                    • bundles
                      private
                    ordered-render-queue
                    Class for a queue of objects that are rendered in order.add-rendered-object will add an object to the end of the queue.
                    • queue-object
                      private
                    render-stage
                    A render queue with designated read and draw buffers [default for now]
                      add-rendered-object(render-queue object)

                      Add object to render-queue.

                      The order in which objects in the queue are rendered is undefined. This function is used in the implementation of SUBMIT-WITH-EFFECT.

                      remove-rendered-object(render-queue object)
                      Remove object from render-queue.
                      map-render-queue(render-queue function)
                      Call function on each object stored in the render-queue.
                      do-render-queue((var queue) &body body)
                      Call body with each object in the render-queuequeue bound to var.
                      find-if-queue(predicate render-queue)
                      Search for an object in render-queue that satisfies predicate

                      The finalize and upload queues are cleared after each call to draw, while the render queues are not. [Should some uploads be preserved i.e., have the option to be done every frame?]

                      [What about download (from the GPU) operations?]

                      Methods defined on the simple-effect class perform the basic actions of creating render bundles from a shape and scheduling any necessary finalization and upload.

                      simple-effect
                      This class supports effects which are simply the application of OpenGL state, with uset parameters, to a shape.
                      • gl-state
                        graphics-state object used to render shapes that use this effect
                      • uset-names
                        names (symbols) of usets used by the effect

                      Environments

                      The environment object contained in a render-bundle ultimately controls the appearance of the bundle.

                      environment
                      class that controls rendering of shapes.
                      • attribute-map
                        list of (symbol glsl-name) where glsl-name is a string
                      • effect
                        back pointer to effect object
                      • gl-state
                        the state to apply when rendering
                      • uniform-sets
                        uset values (from inputs) used to update the environment's shader uniforms
                      • renderer
                        the renderer

                      The gl-state slot contains a graphics-state object that represents the OpenGL state in effect when the environment is used to render a bundle. This contains things such as the shader program, and the the bound textures, and is described in the OpenGL Objects and State section.

                      The usets slot contains the uniform set parameters for the shader program contained in the graphics state. These are kept up-to-date by the incremental computation mechanism, as the environment is a sink object that receives incremental results. There are different possible strategies for making uniform values available to shader programs. At present, LPSG uses the classic OpenGL glUniform family of functions to upload values into shader programs. [In the future, we want to support using Uniform Buffer Objects.]

                      OpenGL Objects and State

                      LPSG uses the cl-opengl library to call functions in the OpenGL "C" ABI. Most programs that use LPSG will not need to call OpenGL directly, but cl-opengl is recommended if needed. It provides good perfomance while mapping the OpenGL interface to Lisp in a natural way. For example, the OpenGL function glVertexAttribPointer is called as gl:vertex-attrib-pointer in Lisp. In this documentation, we refer to OpenGL functions by their Lisp name.

                      Any OpenGL rendering requires the creation of resources that are managed by the OpenGL driver. This typically follows a pattern of generating a name (a 32 bit integer) for the resource and then initializing it. When the resource is no longer needed, it is destroyed and the name can be reused by the driver. LPSG manages this process with a class called gl-object. Subclasses are defined for specific OpenGL resources such as textures and shader programs.

                      gl-object
                      Class representing any OpenGL object.

                        The OpenGL resource associated with a gl-object is not allocated or initialized when the gl-object is created via make-instance. [This is common graphics programming practice, and also is compatible with a future implementation of LPSG where OpenGL calls are made in a seperate thread from the user program.] The generic function gl-finalize is called by LPSG at an appropriate time to allocate the OpenGL resource and initialize it. This time is most often at the beginning of a call to draw, when objects in the renderer's finalize queue are visited, but gl-finalize may be called at other times too.

                        gl-finalize(obj &optional errorp)

                        Allocate any OpenGL resources needed for obj and perform any tasks needed to use it (e.g. link a shader program).

                        Returns t if finalize actions were performed, nil otherwise.

                        This is called when the renderer's OpenGL context is current. The renderer is accessible in *renderer*.

                        gl-finalized-p(obj)
                        Returns t if object has already been finalized.

                        gl-finalize and gl-finalized-p are not restricted to take only subclasses of gl-object. Methods for those functions can be specialized on higher-level classes that containgl-object objects; such a method would make recursive calls to gl-finalize to initialize all its objects. An around method on gl-finalize calls gl-finalized-p, so it is not necessary to explicitly call gl-finalized-p before calling gl-finalize.

                        In Common Lisp implementations that support garbage collector finalization, LPSG will delete the OpenGL resource associated with a gl-object when that object is collected. For a variety of reasons it is not possible to perform the actual deletion in the context of a garbage collector finalization routine. Instead, LPSG uses weak pointers and auxilliary classes called proxy objects to defer the deletion to an appropriate time. [The user should be able to delete OpenGL objects explicitly; how / when does this happen?]

                        gl-buffer

                        A buffer object allocated in OpenGL.

                        In OpenGL, the usage and target parameters are hints and it is legal to use a buffer differently, but that can impact performance.

                        • size
                          The size of the buffer object in OpenGL. Note: this value is mutable until gl-finalize is called on the gl-buffer object.
                        • usage
                          Usage hint for the buffer object. Value is a cl-opengl keyword e.g., :STATIC-DRAW.
                        • target
                          OpenGL targert for the buffer object. Value is a cl-opengl keyword e.g., :ARRAY-BUFFER.
                        texture-2d
                        class representing OpenGL Texture 2D
                        • width
                          width of texture
                        • height
                          height of texture
                        sampler
                        class representing OpenGL sampler object, which controls texture filtering
                        • wrap-s
                          wrap mode in the S dimension, a cl-opengl keyword
                        • wrap-t
                          wrap mode in the T dimension, a cl-opengl keyword
                        • wrap-r
                          wrap mode in the R dimension, a cl-opengl keyword
                        • border-color
                          the texture border color, an array of 4 floats.
                        • min-filter
                          minification filter - a cl-opengl keyword
                        • mag-filter
                          magniffication filter - a cl-opengl keyword
                        • min-lod
                          minimum texture LOD level
                        • max-lod
                          minimum texture LOD level
                        • lod-bias
                          texture LOD bias
                        • compare-mode
                          comparison mode, for depth textures
                        • compare-func
                          comparison function, for depth textures
                        shader
                        The LPSG object that holds the source code for an OpenGL shader, information about its usets, ID in OpenGL, and any errors that result from its compilation.
                        • status
                          status of shader compilation
                        • compiler-log
                          log of shader compilation errors and warnings
                        program
                        The representation of an OpenGL shader program.
                        • shaders
                          shader objects that compose this program
                        • uniforms
                          Information on uniforms declared within the program shader source.
                        • status
                          status of shader program link
                        • link-log
                          log of errors and warnings from linking shader program
                        • uset-alist
                          private
                        • vertex-attribs
                          private

                        The graphics-state object stored in an environment contains OpenGL objects that should be bound in the OpenGL context, [as well as settings that are enabled,] when rendering is done using that environment.

                        graphics-state
                        Class that stores most OpenGL state.
                          No node with name units.
                          No node with name program.

                          The Draw Protocol

                          The generic function draw causes all submitted shapes to be rendered.

                          draw(renderer)

                          Draw all graphic objects that have been registered with renderer.

                          Perform all outstanding operations in renderer: finalize all objects on the finalize queue(s), do any upload operations in the upload queue, then traverse the render stages and their render queues to render all bundles. The renderer does not swap OpenGL front and back buffers; that is done by the application outside of LPSG.

                          draw performs these actions:

                          Any rendering is done in OpenGL's default framebuffer.

                          Utilities

                          compute-shape-allocation(allocator shape)
                          Allocate buffer storage for all of a shape's attributes.
                          simple-allocator

                          Class for managing allocations from several buffer objects.

                          Allocations are made from one buffer per target, sequentially, with no provision for freeing storage.

                          • buffers
                            alist of (target . buffer). TARGET is the name of an OpenGL binding target e.g., :array-buffer. BUFFER is a gl-buffer object.
                          interleaved-attribute-allocator
                          Allocator that supports interleaving attributes and allocating them from a single buffer.
                          • allocator-alist

                            Alist of (attributes-descriptor . simple-allocator).

                            Different layouts of attributes are allocated from separate simple allocators.

                          open-allocator(allocator)
                          Prepare ALLOCATOR for allocation.
                          allocate-target(allocator target size alignment)

                          Allocate SIZE storage with ALIGNMENT from a buffer object that will TARGET usage.

                          Returns values (buffer offset-in-buffer allocated-size)

                          close-allocator(allocator)
                          Stop allocating from ALLOCATOR's buffers.
                          with-allocator((allocator &rest allocator-args) &body body)
                          Bind allocator to an allocator created with allocator-args in body. The allocator is opened before and closed after executing body
                          make-cube-shapenil

                          Create a cube shape.

                          The cube has VERTEX and NORMAL vertex attributes. The resulting shape has an indexed drawable.