One of my game engine demo (Android) app runs well on devices with Adreno and PowerVR Gpus. However, when I test it on devices with Mali Gpus, I can not get any lighting effect on 3D objects, See the attached device screenshot (left) and RenderDoc snapshot (right):
I narrowed down the problem and found that it cause by accessing array elements by an variable index inside an Uniform buffer.
My shading model is a simple Blinn-Phong shading model. All light sources are passed in by a per-scene uniform buffer. There is 1 ambient light and 5 (up to) direction lights defined in this uniform buffer:
struct DirectionalLight{ vec4 color; vec3 direction; bool isActive; //Is this light in use?};
struct DirectionalLight
{
vec4 color;
vec3 direction;
bool isActive; //Is this light in use?
};
#define NUM_DIR_LIGHT 5layout (std140, set = 1, binding = 0) uniform ProjMatrixBuffer{ mat4 projMat; // Scene projection matrix
#define NUM_DIR_LIGHT 5
layout (std140, set = 1, binding = 0) uniform ProjMatrixBuffer
mat4 projMat; // Scene projection matrix
vec4 ambientColor; // Ambient color DirectionalLight lights[NUM_DIR_LIGHT]; // 5 directional light uint activeLightNum; // Num of active light};
vec4 ambientColor; // Ambient color
DirectionalLight lights[NUM_DIR_LIGHT]; // 5 directional light
uint activeLightNum; // Num of active light
The light effect is calculated by the following function:
LightingColorOutput blinnPhongShading(vec3 normal){
LightingColorOutput blinnPhongShading(vec3 normal)
... for(int i=0; i<activeLightNum; i++) // Loop through all directional lightings { DirectionalLight dirLight = lights[i]; <<<<======== Problem! using [i] makes dirLight has value of all-zero! vec3 lightVec = dirLight.direction; vec4 lightCol = dirLight.color;
for(int i=0; i<activeLightNum; i++) // Loop through all directional lightings
DirectionalLight dirLight = lights[i]; <<<<======== Problem! using [i] makes dirLight has value of all-zero!
vec3 lightVec = dirLight.direction;
vec4 lightCol = dirLight.color;
calculate the light effect generate by this direction light ...
...
}
The line "DirectionalLight dirLight = lights[ i ];" cause the problem. When I use variable "i" to access array element, the structure has all 0 values, and therefore 3D objects appear to be black or very dark.
When I hard code the array index by 0, 1, 2, ..., then I got the correct values that I passed in by Vulkan code.
So the workaround is that I can do the loop unrolling like:
if(lights[0].isActive) calculateLightEffect( lights[0]);if(lights[1].isActive) calculateLightEffect( lights[1]);if(lights[2].isActive) calculateLightEffect( lights[2]);if(lights[3].isActive) calculateLightEffect( lights[3]); ...
if(lights[0].isActive)
calculateLightEffect( lights[0]);
if(lights[1].isActive)
calculateLightEffect( lights[1]);
if(lights[2].isActive)
calculateLightEffect( lights[2]);
if(lights[3].isActive)
calculateLightEffect( lights[3]);
Certainly this is not a nice solution.
Note that my Vulkan shader works well on nVidia, AMD, Intel, PowerVR and Adreno Gpus and I think it complies with Spir-V specs. Is there any thing wrong when I use variable "i" as array index to access array elements? Or is there a special requirement for Mali Gpu? Thanks for any suggestions.
Hi Peter,
Wondering anything your GPU team has found for this problem. Anything I can do help to address the cause?
Cheers,
Hongkun
www.omnigsoft.com
Hongkun Wang said:Wondering anything your GPU team has found for this problem. Anything I can do help to address the cause?
Hi, sorry for the slow reply - we're still investigating - nothing more needed from you for now.
Pete
Just a quick note to confirm that we've reproduced this one internally, and it does look like a compiler bug.
As a workaround if you replace the loop iteration with a literal constant rather than a uniform it seems to work correctly; while not ideal it's a lot better than manual loop unrolling. We'll try and come up with a better workaround for you.
// This breaks for(int i=0; i<activeLightNum; i++) { // Do stuff here } // This works for(int i=0; i<5; i++) { // Do stuff here }
Thanks for your suggestions, I use the following for-loop to make it work for Mail-G76:
for(int i=0; i< NUM_DIR_LIGHT; i++) { if(lights[i].isActive) blinnPhongShading(normal, lights[i]);
However, when I apply a small optimization by adding a "break" when detected a light source is inactive (then all the following light sources are all inactive in my engine), it does not work again, here is the optimized for-loop:
for(int i=0; i< NUM_DIR_LIGHT; i++){ if(lights[i].isActive) blinnPhongShading(normal, lights[i]);
else
break; // <---- this bother us again!
By the way, the older Mail-T880 still generate all-black lighting effect, there must be something else bother, but seeing that it is an aging GPU, it is not quite important to me to fix it.
Thanks,
Hi Hongkun,
We've done some more debugging and we think the problem is that your shaders are overflowing the buffer limit of the uniform buffer.
The application uses a number of dynamic uniform buffers (VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC), and tracing calls to vkUpdateDescriptorSets() shows that these dynamic uniform buffers are given an offset of 0 and a range of 64 via the VkDescriptorBufferInfo structure.
Looking at the shader the ProjMatrixBuffer requires 84 bytes + 32 bytes for each DirectionalLight item - i.e. for 5 items the data length is 244 bytes. Any access outside the buffer extents is undefined behavior.
Kind regards, Pete
Thank you and your team for the wonderful support! I have fixed the bug and it's now working well on both Mali-T880 and Mali-G76.
I did not pay enough attention on uniform buffer range since I thought the minimum alignment of dynamic uniform buffer is 256 bytes anyway and unfortunately none of other Vulkan platform treats this as an overflow error.
Now we are glad to see it works, very happy!
No problem at all; glad we were able to get it working for you,