arrays of arrays as vertex output/fragment input

Hello,

I want to use an array of arrays (vec4[3][1]) as vertex output and fragment input. If the array has the form vec4 a[2][1]my program works but if the first index is larger than 2, my program shows only the clear color.
The array has no effect on the rest of the shaders, other than an identity matrix multiplication to avoid that that array gets optimized out. The full fragment and vertex shaders:
vert:


 #version 450

#define MAX_NUM_LIGHTS 10
#define NUM_CASCADES 2

in layout(location = 0) vec4 vertPosition_ModelSpace;
in layout(location = 1) vec3 vertNormal_ModelSpace;
in layout(location = 2) vec3 vertTangent_ModelSpace;
in layout(location = 3) vec2 textureCoordinate;

uniform mat4 modelToWorld;
uniform mat4 worldToProjection;
uniform mat4 worldToLight[NUM_CASCADES][MAX_NUM_LIGHTS];

uniform vec3 cameraPosition_WorldSpace;
uniform vec3 lightPosition_WorldSpace[MAX_NUM_LIGHTS];
uniform int numLights;

out VsOut
{
  vec3 fragPosition_TangentSpace;
  vec2 textureCoordinate;
  vec3 cameraPosition_TangentSpace;
  vec3 lightPosition_TangentSpace[MAX_NUM_LIGHTS];
  vec4 v[3][1];
  vec4 fragPosition_LightSpace[NUM_CASCADES][MAX_NUM_LIGHTS];
} vsOut;

void main()
{
  vsOut.v[0][0] = vec4(1.0);
  vec3 vertNormal_WorldSpace = normalize(vec3(modelToWorld * vec4(vertNormal_ModelSpace, 0.0)));
  vec3 vertTangent_WorldSpace = normalize(vec3(modelToWorld * vec4(vertTangent_ModelSpace, 0.0)));
  vec3 vertBiTangent_WorldSpace = normalize(cross(vertNormal_WorldSpace, vertTangent_WorldSpace));
  mat3 worldToTangentSpace = transpose(mat3(
        vertTangent_WorldSpace,
        vertBiTangent_WorldSpace,
        vertNormal_WorldSpace
    ));

  vec3 vertPosition_WorldSpace = vec3(modelToWorld * vertPosition_ModelSpace);
  gl_Position = worldToProjection * vec4(vertPosition_WorldSpace, 1.0);

  vsOut.fragPosition_TangentSpace = worldToTangentSpace * vertPosition_WorldSpace;
  vsOut.cameraPosition_TangentSpace = worldToTangentSpace * cameraPosition_WorldSpace;
  for(int i = 0; i < numLights; i++)
  {
    vsOut.lightPosition_TangentSpace[i] = worldToTangentSpace * lightPosition_WorldSpace[i];
    for(int cascadeIndex = 0; cascadeIndex<NUM_CASCADES; cascadeIndex++)
    {
      vsOut.fragPosition_LightSpace[cascadeIndex][i] = worldToLight[cascadeIndex][i] * vec4(vertPosition_WorldSpace, 1.0);
    }
  }
  vsOut.textureCoordinate = textureCoordinate;
}

frag:


#version 450

#define MAX_NUM_LIGHTS 10
#define NUM_CASCADES 2

in VsOut
{
  vec3 fragPosition_TangentSpace;
  vec2 textureCoordinate;
  vec3 cameraPosition_TangentSpace;
  vec3 lightPosition_TangentSpace[MAX_NUM_LIGHTS];
  vec4 v[3][1];
  vec4 fragPosition_LightSpace[NUM_CASCADES][MAX_NUM_LIGHTS];
} vsOut;

layout(location = 0) out vec4 outColor;

uniform float lightPower[MAX_NUM_LIGHTS];
uniform vec3 lightColor[MAX_NUM_LIGHTS];
uniform int numLights;
uniform vec3 ambientColor;
uniform vec3 diffuseColor;
uniform vec3 specularColor;
uniform float transparency;
uniform float shininess;
uniform sampler2D diffuseTexture;
uniform sampler2D normalMap;
uniform sampler2D depthMap[NUM_CASCADES][MAX_NUM_LIGHTS];

uniform float cascadeDistances[NUM_CASCADES];

float shadowCalculation(int lightIndex, int cascadeIndex)
{
  vec3 projCoords = vsOut.fragPosition_LightSpace[cascadeIndex][lightIndex].xyz / vsOut.fragPosition_LightSpace[cascadeIndex][lightIndex].w;
  projCoords = projCoords * 0.5 + 0.5;
  float currentDepth = projCoords.z;
  float shadow = 0.0;
  vec2 texelSize = 1.0 / textureSize(depthMap[cascadeIndex][lightIndex], 0);
  float bias = 0.0;
  for(int x = -1; x <= 1; ++x)
  {
    for(int y = -1; y <= 1; ++y)
    {
      float pcfDepth = texture(depthMap[cascadeIndex][lightIndex], projCoords.xy + vec2(x, y) * texelSize).r;
      shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
    }
  }
  shadow /= 9.0;

  return clamp(shadow, 0.0, 1.0);
}

float calculateLocalLightPower(float lightDistance, float lightPower)
{
  return clamp(lightPower/pow(lightDistance, 2.0), 0.0, 1.0);
}

vec3 ambientLight(vec3 ambientColor)
{
  return ambientColor * vec3(0.0, 0.0, 0.0);
}

vec3 diffuseLight(
  vec3 diffuseColor,
  vec3 toLight[MAX_NUM_LIGHTS],
  vec3 lightColor[MAX_NUM_LIGHTS],
  float localLightPower[MAX_NUM_LIGHTS],
  vec3 normal
)
{
  vec3 diffuseLight = vec3(0.0, 0.0, 0.0);
  for(int i = 0; i < numLights; i++)
  {
    diffuseLight +=
      lightColor[i] * clamp(dot(toLight[i], normal), 0.0, 1.0) * localLightPower[i];
  }
  return diffuseColor * clamp(diffuseLight, 0.0, 1.0);
}

vec3 specularLight(
  vec3 specularColor,
  vec3 toLight[MAX_NUM_LIGHTS],
  vec3 lightColor[MAX_NUM_LIGHTS],
  float localLightPower[MAX_NUM_LIGHTS],
  vec3 toCamera,
  vec3 normal
)
{
  vec3 specularLight = vec3(0.0, 0.0, 0.0);
  for(int i = 0; i < numLights; i++)
  {
    vec3 halfDir = normalize(toLight[i] + toCamera);
    float specAngle = clamp(dot(halfDir, normal), 0.0, 1.0);
    specularLight += lightColor[i] * pow(specAngle, shininess) * localLightPower[i];
  }
  return specularColor * specularLight;
}

void main()
{
  vec4 diffuseTextureColor = texture2D(diffuseTexture, vsOut.textureCoordinate);

  vec3 fragNormal_TangentSpace = normalize(texture2D(normalMap, vsOut.textureCoordinate).rgb * 2.0 - vec3(1.0, 1.0, 1.0));

  int cascadeIndex;
  float currentCascadeDistance = distance(vsOut.fragPosition_TangentSpace, vsOut.cameraPosition_TangentSpace);
  for(int i = 0; i<NUM_CASCADES; i++)
  {
    cascadeIndex = i;
    if(currentCascadeDistance<cascadeDistances[i])
    {
      break;
    }
  }

  vec3 toLight_TangentSpace[MAX_NUM_LIGHTS];
  float localLightPower[MAX_NUM_LIGHTS];
  for(int i = 0; i < numLights; i++)
  {
    toLight_TangentSpace[i] = normalize(vsOut.lightPosition_TangentSpace[i] - vsOut.fragPosition_TangentSpace);
    float lightDistance = distance(vsOut.lightPosition_TangentSpace[i], vsOut.fragPosition_TangentSpace);
    localLightPower[i] =
      calculateLocalLightPower(lightDistance, lightPower[i])*(1.0 - shadowCalculation(i, cascadeIndex));
  }

  vec3 toCamera = normalize(vsOut.cameraPosition_TangentSpace - vsOut.fragPosition_TangentSpace);

  vec3 ambientLight = ambientLight(ambientColor);
  vec3 diffuseLight = diffuseLight(diffuseColor, toLight_TangentSpace, lightColor, localLightPower, fragNormal_TangentSpace);
  vec3 specularLight = specularLight(specularColor, toLight_TangentSpace, lightColor, localLightPower, toCamera, fragNormal_TangentSpace);

  vec3 finalLight = clamp(ambientLight +
    (diffuseLight + specularLight)
    , 0.0, 1.0);

  outColor = diffuseTextureColor * vec4(finalLight,  transparency)*vsOut.v[0][0];
}

As they are these shaders do not work as I intended, they work when vec4 v[3][1] gets replaced with vec4 v[2][1].
I am sorry that i can’t provide a minimal example but it would be too much of a headache to rewrite the renderer.

system information:
KAOS Linux, Intel Open Source Technology Center, 4.5 (Core Profile) Mesa 17.3.9, Mesa DRI Intel(R) UHD Graphics 620 (Kabylake GT2)
using C++, glew, glfw

You can only output so much stuff, and you’re shoving way too much between the VS and FS. You have 28 individual outputs/inputs. That’s a huge amount.

Where can I find information about the limits of the inputs and outputs of fragment/vertex shaders and what are possible solutions for that problem?

Edit:

Do the elements of a vertex output array get interpolated as if they were individual elements?

Shaders have all kinds of resource limitations that you can query.

what are possible solutions for that problem?

Besides not passing quite so much stuff?

I don’t know much about cascading shadow maps, so I can’t help you there (though I think 10 maps is probably seriously overdoing it). But when dealing with complex shadow casting algorithms, it’s probably best to do each light in a separate pass rather than all at once. That alone would almost half the number of outputs.

glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS) retrieves the maximum value for the total number of components of vertex shader outputs. Your VsOut structure has 130 components (80 of those are in fragPosition_LightSpace), so the limit is presumably 128 on your system. The standard only requires that the limit is at least 64.

Splitting the shadow calculations across multiple passes would significantly reduce the number of outputs.