Imagination PowerVR SDK Blog

Batching geometry using different textures in ES3


Is there a recommended way to batch geometry using different textures?
Should I go for 2D texture arrays?
Should I try using multiple texture units and an array of sampler? Is there such a thing as a sampler array? Could I use an if/switch instead?


Generally, in OpenGL you should aim to change render state as little as possible, as there is significant overhead in doing so.

Texture atlases and texture arrays are useful for this, since you can upload many textures to the graphics driver without rebinding. Use sampler2DArray to access the array in your shader.

Conditional branching in shaders can be unpredictable performance-wise on PowerVR hardware, so is generally best avoided. Particularly on Series 5 hardware, when sampling textures, as it can bypass texture prefetching resulting in a dependent texture read, which is very bad for performance.


Related to your question on using 2D texture arrays Slion, if an application can’t pre-upload all layers (2D images) to a 2D texture array on startup and needs to upload slices dynamically…

Each time you update a slice in a texture array:

  1. Is the warm-up cost that for the entire 2D texture array, or just for the updated layer(s)?

  2. If you upload to layers which have not been used in the last 2-3 frames, but a draw referencing other layers has been issued in the last 2-3 frames, do you risk ghosting the entire texture array?

Trying to decide for some future work when to prefer texture arrays on PowerVR?


Related: If entire 2D tex array ghosting and/or warm-ups for dynamic updates is a problem…

Is there some equivalent to bindless textures (see ARB_bindless_texture from OpenGL-land) for PowerVR? That would work as well.

(FWIW, bindless texture gets rid of concept of texture units and the indirect mapping they impose (texture handles -> texture units -> shader), which inhibits batching across many 2D textures, and allows shaders to sample from textures based on texture handle directly.)


That’s why I was wondering if I could have an array of samplers each using a different unit. Thus I could batch my geometry by adding the expected index in the sampler array as an extra vertex attribute or in a third texture coordinate dimension.

Something like:

uniform sampler2D uTextures[8];
varying vec3 vTexCoord;
gl_FragColor=texture2D(uTextures[vTexCoord.z], vTexCoord);


You can think of texture arrays just as a group of textures allocated together, but the textures are still referenced as individual textures. glTexStorage will create the space on the GPU in one shot, but each texture must then DMA into a unique texture object.

So, after the initial allocation of the array, you shouldn’t have to worry about it, every call to glTex(Sub)Image will only incur the cost of upload into the slot in the array, and then the subsequent warm-up cost of copying into the texture object.


Ok, thanks Paul! So if uploads are per slot (layer), warm-ups are per layer as well – good!

How about ghosting when updating layers in a 2D texture array? Will the whole texture array ghost (in the example I mentioned above)? --Thanks!


Edit: What I had written previously was incorrect, please disregard.

Yes, it would ghost the entire array. Because the entire texture array is bound, it isn’t possible to know ahead of time which layers will be read by the shader.

You should think of updating texture arrays as having the same caveats as updating a plain 2D texture, the situations where ghosting would occur are the same but the cost would be far greater.


Ok, thanks again Paul! I appreciate you checking into that. That’s very helpful info to know up-front!

So just to make sure my understanding is solid, if you upload a new layer to a 2D texture, would the warm-up/swizzle be done only on that layer, or would you incur a re-warmup on the entire array?


I should add I was also incorrect about the warm-up behaviour. A call to glTexSubImage when backed by glTexStorage will perform the warm-up step immediately.

This warm-up is only on the updated layer rather than the entire array.

Additionally, after a call to glTexSubImage only the area of the texture that was updated needs to be twiddled again.


Good to know – thanks!