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 Hongkun,
Thanks for the bug report.
First question: What device, Mali GPU, and Mali driver version are you trying to use this on? The quickest means to find the GPU information is to open the URL "chrome://gpu" in the Chrome browser, and look for the GL_RENDERER and GL_VERSION strings in the resulting page.
Second question: Can you share the complete shader (GLSL and SPIRV) that causes the problem so we can try to reproduce the issue you are seeing.
Cheers, Pete
Hi Pete,
Following is the device info I collected by Vulkan API:
******* Device = HUAWEI P30******* Vulkan Info = Vulkan, Vendor:ARM, Driver: 1.1.82, Device: Mali-G76******* Vulkan Surface size = 1080 X 2265
*************************************************************** Device = HUAWEI Mate8******* Vulkan Info = Vulkan, Vendor:ARM, Driver: 1.0.53, Device: Mali-T880******* Vulkan Surface size = 1080 X 1920
Attached are GLSL source code and the Windows script to compile them into Spir-V. I use "glslangValidator.exe" bundled in LunarG Vulkan SDK (version: 1.1.82.1), so if you have LunarG Vulkan SDK installed, you can run "call %VULKAN_SDK%\Bin\glslangValidator.exe -V Vulkan3D.vert -o Vulkan3D.vert.spv -S vert" to get Spir-V code.
#version 450 struct DirectionalLight //32-bytes { vec4 color; vec3 direction; bool isActive; }; //....................................................... layout (std140, set = 0, binding = 0) uniform PerObjMatrixBuffer //Per-obj dynamic uniform buffer { mat4 modelViewMat; //64-bytes mat4 texcoordMat; //64-bytes }; //....................................................... #define NUM_DIR_LIGHT 5 layout (std140, set = 1, binding = 0) uniform ProjMatrixBuffer //Per-scene dynamic uniform buffer { mat4 projMat; //64-bytes vec4 ambientColor; //16-bytes DirectionalLight lights[NUM_DIR_LIGHT]; //32xN-bytes uint activeLightNum; //4-bytes }; //....................................................... layout(push_constant, std140) uniform per_frame_PC //Push constants { uniform vec4 colorConst; //0 bool textureEnabled; //4 bool alphaTestEnabled; //5 bool texcoordTransformationEnabled; //6 bool imageByLuminance; //7 bool shadingEnabled; //8 bool fogEnabled; //9 float fogStart; //10 float fogEnd; //11 vec3 fogColor; //12 bool flatShading; //15 bool specularEnabled; //16 bool generateReflectionTexCoord; //17 uint mtlSpecularPower; //18 float zBias; //19 vec4 mtlDiffuse; //20 (Note: alignment!) vec4 mtlEmissive; //24 (Note: alignment!) vec4 mtlSpecular; //28 (Note: alignment!) }; //....................................................... // Vertex attributes layout (location = 0) in vec3 in_pos; //Vertex position layout(location = 1) in vec3 in_normal; //Vertex normal layout (location = 2) in vec2 in_texcoord; //Vertex tex-coord //....................................................... layout (location = 0) out vec2 tex0; layout (location = 1) flat out vec4 colFlatDiffuse; layout (location = 2) flat out vec4 colFlatSpecular; layout (location = 3) out vec4 colDiffuse; layout (location = 4) out vec4 colSpecular; layout (location = 5) out float fogDist; // ============================================================================ struct LightingColorOutput { vec4 Diffuse; vec4 Specular; }; const vec3 VIEW_VEC = vec3(0.f, 0.f, +1.f); //We always do shading in the view coordinate LightingColorOutput blinnPhongShading(vec3 normal) { LightingColorOutput outputCol; outputCol.Diffuse = ambientColor; // Diffuse initial value set to ambient light outputCol.Specular = vec4(0.f); // Specular initial value set to BLACK normal = normal * mat3(modelViewMat); // Transform normal (note: muste be normalized) for(int i=0; i<activeLightNum; i++) // Calculate lighting effect by directional lights { DirectionalLight dirLight = lights[i]; vec3 lightVec = dirLight.direction; vec4 lightCol = dirLight.color; //Accumulate diffuse color float diffuseIntensity = clamp( dot(normal, lightVec), 0.f, 1.f); outputCol.Diffuse += lightCol * diffuseIntensity; //Accumulate speular color if(specularEnabled) { vec3 halfVec = normalize( lightVec + VIEW_VEC ); float specularIntensity = pow( clamp(dot(normal, halfVec), 0.f, 1.f), float(mtlSpecularPower) ); outputCol.Specular += lightCol * mtlSpecular * specularIntensity; } } return outputCol; } // ============================================================================ void main() { vec4 posInView = vec4(in_pos, 1.f) * modelViewMat; //Got the vertex position in view coordinate gl_Position = posInView * projMat; fogDist = posInView.z; //................................. if(generateReflectionTexCoord) tex0 = (in_normal * mat3(modelViewMat)).xy; else tex0 = texcoordTransformationEnabled? (vec4(in_texcoord, 0.f, 1.f) * texcoordMat).xy : in_texcoord; if(shadingEnabled) //Shading is enabled, calculate directional light intensity by Blinn-Phong model { LightingColorOutput colLight = blinnPhongShading(in_normal); colDiffuse = colFlatDiffuse = colLight.Diffuse; //Save diffuse light as vertex shader output colSpecular = colFlatSpecular = colLight.Specular; //Save specular light as vertex shader output } gl_Position.z += zBias; //Add the Z-Bias }
One more thing: My apps have dual graphics back end, OpenGL ES and Vulkan, I can switch between OpenGL and Vulkan.
The GLSL shader used for two back ends are almost same, which means I access light sources in vertex shader by the same code like "DirectionalLight dirLight = lights[ i ];" at OpenGL side and there is no any problem.
Thanks for your help.
Thanks. Can you share the GL_VERSION string from OpenGL ES too? It tends to be more useful for us than the Vulkan version number.
We have one known issue which has similar symptoms to this on our very early Vulkan drivers which might impact the Mate 8, but this should not impact the P30 which is a lot newer than this.
This is from HUAWEI P30
This is from HUAWEI Mate8
Thank you. We'll investigate more here and get back to you.
Our compiler team has had a look and can't see anything immediately obvious going wrong. Would it be possible to share a complete reproducer APK we can try here?
I can not post a APK file right inside this forum, should I Zip it first? I'll try to upload my APK to a ftp serverand let you know the link.
GameEngineDemo.zip
The attached ZIP file is the APK file "GameEngineDemo.apk". The binary is built with Debug info and Vulkan validation.
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
Update:
I finally use an unrolled loop in my vertex shader, temporarily for Mali GPUs. The code is:
// Accumulate lighting effect (diffuse and specular) of directional lights //Solution 1 - for loop /* for(int i=0; i<activeLightNum; i++) blinnPhongShading(normal, lights[i]); */ //Solution 2 - unrolled loop (Mali GPU got all-zero value from lights[i] in for loop, so we have to use unrolled loop) while(true) { if(lights[0].isActive) blinnPhongShading(normal, lights[0]); else break; if(lights[1].isActive) blinnPhongShading(normal, lights[1]); else break; if(lights[2].isActive) blinnPhongShading(normal, lights[2]); else break; if(lights[3].isActive) blinnPhongShading(normal, lights[3]); else break; if(lights[4].isActive) blinnPhongShading(normal, lights[4]); //Must break the while loop at the end break; }
It works well on latest Mali-G76, but still shows all-black lighting effect on older Mail-T880. See the screenshot
Hope this can be solved in the driver since loop unrolling is impossible if want to support large number of light sources.
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,
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