Next: , Previous: , Up: Graphics   [Contents][Index]


5.3.12 Buffers

Alright, let’s brush aside all of those pretty high level abstractions and discuss what is going on under the hood. The GPU exists as a discrete piece of hardware separate from the CPU. In order to make it draw things, we must ship lots of data out of our memory space and into the GPU. The (chickadee graphics buffer) module provides an API for manipulating GPU buffers.

In OpenGL terminology, a chunk of data allocated on the GPU is a “vertex buffer object” or VBO. For example, here is a bytevector that could be transformed into a GPU buffer that packs together vertex position and texture coordinates:

(use-modules (chickadee graphics buffer) (srfi srfi-4))
(define data
  (f32vector -8.0 -8.0 ; 2D vertex
             0.0 0.0   ; 2D texture coordinate
             8.0 -8.0  ; 2D vertex
             1.0 0.0   ; 2D texture coordinate
             8.0 8.0   ; 2D vertex
             1.0 1.0   ; 2D texture coordinate
             -8.0 8.0  ; 2D vertex
             0.0 1.0)) ; 2D texture coordinate

This data represents a textured 16x16 square centered on the origin. To send this data to the GPU, the make-buffer procedure is needed:

(define buffer (make-buffer data #:stride 16))

The #:stride keyword argument indicates how many bytes make up each element of the buffer. In this case, there are 4 floats per element: 2 for the vertex, and 2 for the texture coordinate. A 32-bit float is 4 bytes in length, so the buffer’s stride is 16.

Within a VBO, one or more “attributes”, as OpenGL calls them, may be present. Attributes are subregions within the buffer that have a particular data type. In this case, there are two attributes packed into the buffer. To define vertex attributes, the make-vertex-attribute procedure is needed:

(define vertices
  (make-vertex-attribute #:buffer buffer
                         #:type 'vec2
                         #:component-type 'float
                         #:length 4))
(define texcoords
  (make-vertex-attribute #:buffer buffer
                         #:type 'vec2
                         #:component-type 'float
                         #:length 4
                         #:offset 8))

To render a square, the GPU needs to draw two triangles, which means we need 6 vertices in total. However, the above buffer only contains data for 4 vertices. This is because there are only 4 unique vertices for a square, but 2 of them must be repeated for each triangle. To work with deduplicated vertex data, an “index buffer” must be created.

(define index-buffer
  (make-buffer (u32vector 0 3 2 0 2 1)
               #:target 'index)
(define indices
  (make-vertex-attribute #:type 'scalar
                         #:component-type 'unsigned-int
                         #:buffer index-buffer))

Note the use of the #:target keyword argument. It is required because the GPU treats index data in a special way and must be told which data is index data.

Now that the vertex attributes have been created, all that’s left is to bind them all together in a vertex array. Vertex arrays associate each vertex attribute with an attribute index on the GPU. The indices that are chosen must correspond with the indices that the shader (see Shaders) expects for each attribute.

(define vertex-array
  (make-vertex-array #:indices indices
                     #:attributes `((0 . ,vertices)
                                    (1 . ,texcoords))))

With the vertex array created, the GPU is now fully aware of how to interpret the data that it has been given in the original buffer. Actually rendering this square is left as an exercise to the reader. See the Shaders section and the shader-apply procedure in Rendering Engine for the remaining pieces of a successful draw call. Additionally, consider reading the source code for sprites, shapes, or particles to see GPU buffers in action.

Without further ado, the API reference:

Procedure: make-buffer data [#:name "anonymous"] [#:length] [#:offset 0] [#:stride 0] [#:target vertex] [#:usage static]

Upload data, a bytevector, to the GPU. By default, the entire bytevector is uploaded. A subset of the data may be uploaded by specifying the offset, the index of the first byte to be uploaded, and length, the number of bytes to upload.

If data is #f, allocate length bytes of fresh GPU memory instead.

target and usage are hints that tell the GPU how the buffer is intended to be used.

target may be:

usage may be:

name is simply an arbitrary string for debugging purposes that is never sent to the GPU.

Procedure: buffer? obj

Return #t if obj is a GPU buffer.

Procedure: index-buffer? buffer

Return #t if buffer is an index buffer.

Variable: null-buffer

Represents the absence of a buffer.

Procedure: buffer-name buffer

Return the name of buffer.

Procedure: buffer-length buffer

Return the length of buffer.

Procedure: buffer-stride buffer

Return the amount of space, in bytes, between each element in buffer.

Procedure: buffer-target buffer

Return the the intended usage of buffer, either vertex or index.

Procedure: buffer-usage buffer

Return the intended usage of buffer, either static for buffer data that will not change once sent to the GPU, or stream for buffer data that will be frequently updated from the client-side.

Procedure: buffer-data buffer

Return a bytevector containing all the data within buffer. If buffer has not been mapped (see with-mapped-buffer) then this procedure will return #f.

Syntax: with-mapped-buffer buffer body …

Evaluate body in the context of buffer having its data synced from GPU memory to RAM. In this context, buffer-data will return a bytevector of all the data stored in buffer. When program execution exits this form, the data (including any modifications) is synced back to the GPU.

Procedure: current-buffer

Return the current buffer.

Variable: g:buffer

Render state for buffers (see Rendering Engine.)

This form is useful for streaming buffers that need to update their contents dynamically, such as a sprite batch.

Procedure: make-vertex-attribute #:buffer #:type #:component-type #:length [#:offset 0] [#:divisor 1] [#:name "anonymous"]

Return a new vertex attribute for buffer starting at byte index offset of length elements, where each element is of type and composed of component-type values.

Valid values for type are:

Valid values for component-type are:

divisor is only needed for instanced rendering applications (see shader-apply/instanced in Rendering Engine) and represents how many instances each vertex element applies to. A divisor of 0 means that a single element is used for every instance and is used for the data being instanced. A divisor of 1 means that each element is used for 1 instance. A divisor of 2 means that each element is used for 2 instances, and so on.

Procedure: vertex-attribute? obj

Return #t if obj is a vertex attribute.

Procedure: vertex-attribute->buffer vertex-attribute

Return the buffer that vertex-attribute is using.

Procedure: vertex-attribute-name vertex-attribute

Return the name of vertex-attribute.

Procedure: vertex-attribute-offset vertex-attribute

Return the byte offset of vertex-attribute.

Procedure: vertex-attribute-type vertex-attribute

Return the data type of vertex-attribute.

Procedure: vertex-attribute-component-type vertex-attribute

Return the component data type of vertex-attribute

Procedure: vertex-attribute-divisor vertex-attribute

Return the instance divisor for vertex-attribute.

Syntax: with-mapped-vertex-attribute vertex-attribute body …

Evaluate body in the context of vertex-attribute having its data synced from GPU memory to RAM. See with-mapped-buffer for more information.

Procedure: make-vertex-array #:indices #:attributes [#:mode triangles]

Return a new vertex array using the index data within the vertex attributes indices and the vertex attribute data within attributes.

attributes is an alist mapping shader attribute indices to vertex attributes:

`((1 . ,vertex-attribute-a)
  (2 . ,vertex-attribute-b)
  ...)

By default, the vertex array is interpreted as containing a series of triangles. If another primtive type is desired, the mode keyword argument may be overridden. The following values are supported:

Variable: null-vertex-array

Represents the absence of a vertex array.

Procedure: vertex-array? obj

Return #t if obj is a vertex array.

Procedure: vertex-array-indices vertex-array

Return the buffer view containing index data for vertex-array.

Procedure: vertex-array-attributes vertex-array

Return the attribute index -> buffer view mapping of vertex attribute data for vertex-array.

Procedure: vertex-array-mode vertex-array

Return the primitive rendering mode for vertex-array.

Procedure: current-vertex-array

Return the current vertex array.

Variable: g:vertex-array

Render state for vertex arrays (see Rendering Engine.)


Next: , Previous: , Up: Graphics   [Contents][Index]