Next: Shaders, Previous: Rendering Engine, Up: Graphics [Contents][Index]
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 provided a typed view into a buffer, the
make-buffer-view
procedure is needed:
(define vertices (make-buffer-view #:buffer buffer #:type 'vec2 #:component-type 'float #:length 4)) (define texcoords (make-buffer-view #: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-buffer-view #: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 buffer views representing each attribute have been created, all that’s left is to bind them all together in a “vertex array object”, or VAO. Vertex arrays associate each buffer view 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:
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:
vertex
Vertex attribute data.
index
Index buffer data.
usage may be:
static
The buffer data will not be modified after creation.
stream
The buffer data will be modified frequently.
name is simply an arbitrary string for debugging purposes that is never sent to the GPU.
Return #t
if obj is a GPU buffer.
Return #t
if buffer is an index buffer.
Represents the absence of a buffer.
Return the name of buffer.
Return the length of buffer.
Return the amount of space, in bytes, between each element in buffer.
Return the the intended usage of buffer, either vertex
or
index
.
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.
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
.
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.
This form is useful for streaming buffers that need to update their contents dynamically, such as a sprite batch.
0
] [#:divisor 1
] [#:name "anonymous"
]Return a new buffer view 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:
scalar
single number
vec2
2D vector
vec3
3D vector
vec4
4D vector
mat2
2x2 matrix
mat3
3x3 matrix
mat4
4x4 matrix
Valid values for component-type are:
byte
unsigned-byte
short
unsigned-short
int
unsigned-int
float
double
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.
Return #t
if obj is a buffer view.
Return the buffer that buffer-view is using.
Return the name of buffer-view.
Return the byte offset of buffer-view.
Return the data type of buffer-view.
Return the component data type of buffer-view
Return the instance divisor for buffer-view.
Evaluate body in the context of buffer-view having its
data synced from GPU memory to RAM. See with-mapped-buffer
for
more information.
triangles
]Return a new vertex array using the index data within the buffer view indices and the vertex attribute data within attributes.
attributes is an alist mapping shader attribute indices to typed buffers containing vertex data:
`((1 . ,buffer-view-a) (2 . ,buffer-view-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:
points
lines
line-loop
line-strip
triangles
triangle-strip
triangle-fan
Represents the absence of a vertex array.
Return #t
if obj is a vertex array.
Return the buffer view containing index data for vertex-array.
Return the attribute index -> buffer view mapping of vertex attribute data for vertex-array.
Return the primitive rendering mode for vertex-array.
Next: Shaders, Previous: Rendering Engine, Up: Graphics [Contents][Index]