c++openglglslvertex-shadervertex-attributes

OpenGL vertex shader is fast on Linux, but extremely slow on Windows


To draw power spectral density of a signal (which is very similar to heatmap), I use this vertex shader program. It receives value of power at each vertex, takes logarithm to show result in dB, normalizes within the range of colormap array, and assigns a color to vertex.

#version 130

uniform float max_val;
uniform float min_val;
uniform int height;

attribute float val; // power-spectral-density value assigned to each vertex

// colormap values
const float r[512] = float[]( /* red values come here */ );
const float g[512] = float[]( /* green values come here */ );
const float b[512] = float[]( /* blue values come here */ );

void main() {
  // set vertex position based on its ID
  int x = gl_VertexID / height;
  int y = gl_VertexID - x * height;
  gl_Position = gl_ModelViewProjectionMatrix * vec4(x, y, -1.0, 1.0);

  float e = log(max_val / min_val);
  float d = log(val / min_val);

  // set color
  int idx = int(d * (512 - 1) / e); // find normalized index that falls in range [0, 512)
  gl_FrontColor = vec4(r[idx], g[idx], b[idx], 1.0); // set color
}

Corresponding C++ code is here:

QOpenGLShaderProgram glsl_program;
// initialization code is omitted

glsl_program.bind();
glsl_program.setUniformValue(vshader_max_uniform, max_val);
glsl_program.setUniformValue(vshader_min_uniform, min_val);
glsl_program.setUniformValue(vshader_height_uniform, max_colormap_height);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, colormap); // colormap is a vector that contains value of power at each vertex
glDrawElements(GL_TRIANGLE_STRIP, vertices_length, GL_UNSIGNED_INT, nullptr); // vertex_length is size of colormap
glDisableVertexAttribArray(0);

glsl_program.release();

This program runs fast enough on Linux. But in Windows, it is very slow and takes a lot of CPU time. If I change this line of GLSL:

// int idx = int(d * (512 - 1) / e);
int idx = 0;

then the app runs fast on Windows too. So, It has to be a problem with GLSL code.

How should I fix it?


Solution

  • What you're doing there belongs into the fragment shader, not the vertex shader. And you submit both the color lookup table and the spectral density data as a texture. Although vertex setup is not that expensive, it comes with a certain overhead and in general you want to cover as many pixels with the least number of vertices possible.

    Also learn logarithm calculation rules (e.g. log(a/b) = log(a) - log(b)) and avoid doing calculations that are uniform over the whole draw call and precalculate on the host.

    /* vertex shader */
    #version 130
    
    varying vec2 pos;    
    
    void main() {
      // set vertex position based on its ID
      // To fill the viewport, we need just three vertices
      // of a rectangular triangle of with and height 2
      pos.x = gl_VertexID % 2;
      pos.y = gl_VertexID / 2;
    
      // screen position is controlled using glViewport/glScissor
      gl_Position = vec4(2*pos, 0, 1.0);
    }
    

    -

    /* fragment shader */
    #version 130
    
    varying vec2 pos;
    
    uniform sampler2D values;
    uniform sampler1D colors;
    
    uniform float log_min;
    uniform float log_max;
    
    void main() {
      float val = texture2D(values, pos).x;
      float e = log_max - log_min;
      float d = (log(val) - log_min) / e;
    
      gl_FragColor = vec4(texture1D(colors, d).rgb, 1.0); // set color
    }
    

    In later versions of GLSL some keywords have changed. Varyings are defined using in and out instead of varying and texture access functions have been unified to cover all sampler types.

    glsl_program.bind();
    glsl_program.setUniformValue(vshader_log_max_uniform, log(max_val));
    glsl_program.setUniformValue(vshader_log_min_uniform, log(min_val));
    
    // specify where to draw in window pixel coordinates.
    glEnable(GL_SCISSOR_TEST);
    glViewport(x, y, width, height);
    glScissor(x, y, width, height);
    
    glBindTexture(GL_TEXTURE_2D, values_texture);
    glTexSubImage2D(GL_TEXTURE_2D, ..., spectral_density_data);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    
    glsl_program.release();