| # How Impeller Works Around The Lack of Uniform Buffers in Open GL ES 2.0. |
| |
| The Impeller Renderer API allows callers to specify uniform data in a discrete |
| buffer. Indeed, it is conventional for the higher level layers of the Impeller |
| stack to specify all uniform data for a render pass in a single buffer. This |
| jumbo buffer is allocated using a simple bump allocator on the host before being |
| transferred over to VRAM. The ImpellerC reflection engine generates structs with |
| the correct padding and alignment such that the caller can just populate uniform |
| struct members and `memcpy` them into the jumbo buffer, or use placement-new. |
| Placement-new is used in cases where device buffers can be memory mapped into |
| the client address space. |
| |
| This works extremely well when using a modern rendering backend like Metal. |
| However, OpenGL ES 2.0 does not support uniform buffer objects. Instead, uniform |
| data must be specified to GL from the client side using the `glUniform*` family |
| of APIs. This poses a problem for the OpenGL backend implementation. From a view |
| (an offset and range) into a buffer pointing to uniform data, it must infer the |
| right uniform locations within a program object and bind uniform data at the |
| right offsets within the buffer view. |
| |
| Since command generation is strongly typed, a pointer to metadata about the |
| uniform information is stashed along with the buffer view in the command stream. |
| This metadata is generated by the offline reflection engine part of ImpellerC. |
| The metadata is usually a collection of items the runtime would need to infer |
| the right `glUniform*` calls. An item in this collection would look like the |
| following: |
| |
| ``` |
| struct ShaderStructMemberMetadata { |
| ShaderType type; // the data type (bool, int, float, etc.) |
| std::string name; // the uniform member name "frame_info.mvp" |
| size_t offset; |
| size_t size; |
| size_t array_elements; |
| }; |
| ``` |
| |
| Using this mechanism, the runtime knows how to specify data from a buffer view |
| to GL. But, this is still not sufficient as the buffer bindings are not known |
| until after program link time. |
| |
| To solve this issue, Impeller queries all active uniforms after program link |
| time using `glGet` with `GL_ACTIVE_UNIFORMS`. It then iterates over these |
| uniforms and notes their location using `glGetUniformLocation`. This uniform |
| location in the program is mapped to the reflection engine's notion of the |
| uniform location in the pipeline. This mapping is maintained in the pipeline |
| state generated once during the Impeller runtime setup. In this way, even though |
| there is no explicit notion of a pipeline state object in OpenGL ES, Impeller |
| still maintains one for this backend. |
| |
| Since all commands in the command stream reference the pipeline state object |
| associated with the command, the render pass implementation in the OpenGL ES 2 |
| backend can access the uniform bindings map and use that to bind uniforms using |
| the pointer to metadata already located next to the commands' uniform buffer |
| views. |
| |
| And that’s it. This is convoluted in its implementation, but the higher levels |
| of the tech stack don’t have to care about not having access to uniform buffer |
| objects. Moreover, all the reflection happens offline and reflection information |
| is specified in the command stream via just a pointer. The uniform bindings map |
| is also generated just once during the setup of the faux pipeline state object. |
| This makes the whole scheme extremely low overhead. Moreover, in a modern |
| backend with uniform buffers, this mechanism is entirely irrelevant. |