aosp12/external/angle/doc/BufferImplementation.md

123 lines
7.0 KiB
Markdown

# Introduction
Since Direct3D 9 only supports buffers that either contain vertex or index data,
and OpenGL buffers can contain both, ANGLE waits till a draw call is issued to
determine which resources to create/update. The generic implementation 'streams'
the data into global vertex and index buffers. This streaming buffer
implementation works in all circumstances, but does not offer optimal
performance. When buffer data isn't updated, there's no reason to copy the data
again. For these cases a 'static' buffer implementation is used.
The OpenGL ES 2.0 glBufferData() function allows to specify a usage hint
parameter (GL\_STREAM\_DRAW, GL\_DYNAMIC\_DRAW or GL\_STATIC\_DRAW). Both
GL\_STREAM\_DRAW and GL\_DYNAMIC\_DRAW use the streaming buffer implementation.
With the GL\_STATIC\_DRAW hint, ANGLE will attempt to use the static buffer
implementation. If you update the buffer data after it has already been used in
a draw call, it falls back to the streaming buffer implementation, because
updating static ones would involve creating new ones, which is slower than
updating streaming ones (more on this later).
Because some applications use GL\_STREAM\_DRAW or GL\_DYNAMIC\_DRAW even when
the data is not or very infrequently updated, ANGLE also has a heuristic to
promote buffers to use the static implementation.
# Streaming buffers
The streaming buffers implementation uses one Context-global vertex buffer
(VertexDataManager::mStreamingBuffer) and two index buffers
(IndexDataManager::mStreamingBufferShort and
IndexDataManager::mStreamingBufferInt). The streaming behavior is achieved by
writing new data behind previously written data (i.e. without overwriting old
data). Direct3D 9 allows to efficiently update vertex and index buffers when
you're not reading or overwriting anything (it won't stall waiting for the GPU
finish using it).
When the end of these streaming buffers is reached, they are 'recycled' by
discarding their content. D3D9 will still keep a copy of the data that's in use,
so this recycling efficiently renames the driver level buffers. ANGLE can then
write new data to the beginning of the vertex or index buffer.
The ArrayVertexBuffer::mWritePosition variable holds the current end position of
the last data that was written. StreamingVertexBuffer::reserveRequiredSpace()
allocates space to write the data, and StreamingVertexBuffer::map() actually
locks the D3D buffer and updates the write position. Similar for index buffers.
# Static buffers
Each GL buffer object can have a corresponding static vertex or index buffer
(Buffer::mVertexBuffer and Buffer::mIndexBuffer). When a GL buffer with static
usage is used in a draw call for the first time, all of its data is converted to
a D3D vertex or index buffer, based on the attribute or index formats
respectively. If a subsequent draw call uses different formats, the static
buffer is invalidated (deleted) and the streaming buffer implementation is used
for this buffer object instead. So for optimal performance it's important to
store only a single format of vertices or indices in a buffer. This is highly
typical, and even when in some cases it falls back to the streaming buffer
implementation the performance isn't bad at all.
The StreamingVertexBuffer and StaticVertexBuffer classes share a common base
class, ArrayVertexBuffer. StaticVertexBuffer also has access to the write
position, but it's used only for the initial conversion of the data. So the
interfaces of both classes are not that different. Static buffers have an exact
size though, and can't be changed afterwards (streaming buffers can grow to
handle draw calls which use more data, and avoid excessive recycling).
StaticVertexBuffer has a lookupAttribute() method to retrieve the location of a
certain attribute (this is also used to verify that the formats haven't changed,
which would result in invalidating the static buffer). The descriptions of all
the attribute formats a static buffer contains are stored in the
StaticVertexBuffer::mCache vector.
StaticIndexBuffer also caches information about what's stored in them, namely
the minimum and maximum value for certain ranges of indices. This information is
required by the Direct3D 9 draw calls, and is also used to know the range of
vertices that need to be copied to the streaming vertex buffer in case it needs
to be used (e.g. it is not uncommon to have a buffer with static vertex position
data and a buffer with streaming texture coordinate data for skinning).
# Constant attributes
Aside from using array buffers to feed attribute data to the vertex shader,
OpenGL also supports attributes which remain constant for all vertices used in a
draw call. Direct3D 9 doesn't have a similar concept, at least not explicitly.
Constant attributes are implemented using separate (static) vertex buffers,
and uses a stride of 0 to ensure that every vertex retrieves the same data.
Using a stride of 0 is not possible with streaming buffers because on some
hardware it is incompatible with the D3DUSAGE\_DYNAMIC flag. We found that with
static usage, all hardware tested so far can handle stride 0 fine.
This functionality was implemented in a ConstantVertexBuffer class, and it
integrates nicely with the rest of the static buffer implementation.
# Line loops
Direct3D 9 does not support the 'line loop' primitive type directly. This is
implemented by drawing the 'closing' line segment separately, constructing a
tiny temporary index buffer connecting the last and first vertex.
# Putting it all together
glDrawElements() calls IndexDataManager::prepareIndexData() to retrieve a
Direct3D index buffer containing the necessary data. If an element array is used
(i.e. a buffer object), it has static usage, and it hasn't been invalidated, the
GL buffer's static D3D index buffer will be returned. Else the updated streaming
index buffer is returned, as well as the index offset (write position) where the
new data is located. When prepareIndexData() does find a static index buffer,
but it's empty, it means the GL buffer's data hasn't been converted and stored
in the D3D index buffer yet. So in the convertIndices() call it will convert the
entire buffer. prepareIndexData() will also look up the min/max value of a range
of indices, or computes it when not already in the static buffer or when a
streaming buffer is used.
Similarly, both glDrawElements() and glDrawArrays() both call
VertexDataManager::prepareVertexData() to retrieve a set of Direct3D vertex
buffers and their translated format and offset information. It's implementation
is more complicated than prepareIndexData() because buffer objects can contain
multiple vertex attributes, and multiple buffers can be used as input to the
vertex shader. So first it accumulates how much storage space is required for
each of the buffers in use. For all static non-empty buffers in use, it
determines whether the stored attributes still match what is required by the
draw call, and invalidates them if not (at which point more space is allocated
in the streaming buffer). Converting the GL buffer object's data into D3D
compatible vertex formats is still done by specialized template functions.