| # 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. |