10

I am attempting to call one of many functions based on the value of a variable. The variable is set during runtime, so code on the CPU will not work. Using an if/switch statement will be slow due to the large number of possibilities.[probably incorrect]

I am looking for a way to store the functions in an array, either by using function pointers, or by storing the actual equations (e.g. texcoord.x*2) in an array.

Example psuedo-code:

    in vec2 texcoord;
    out vec4 color;

    int number; //Assigned a number between 0 and 300 during runtime

    float func1(void) {
      return texcoord.x + texcoord.y;
    }

    float func2(void) {
      return texcoord.x*2;
    }

    ...

    float func299(void) {
      return texcoord.y - 7;
    }

    void main(void) {
      number = <something calculated during runtime>;
      float output = func<number>(); //              <--------------
      color = vec4(output, output, output, 1);
    }
18107
  • 688
  • 5
  • 9
  • 2
    "Using an if/switch statement will be slow due to the large number of possibilities." That's a pretty bold assumption to be making. – Nicol Bolas Dec 25 '15 at 23:44
  • 2
    If all of your functions are linear equations, you can create an array of vectors, and get the dot product of the tex coord and the vector. (Ex. `func1(texcoord) == dot(vec3(texcoord, 1), vec3(1,1,0))`, `func299(texcoord) == dot(vec3(texcoord, 1), vec3(0, 1, -7))` – Colonel Thirty Two Dec 26 '15 at 00:19

2 Answers2

11

GLSL does not have function pointers. Even SPIR-V doesn't have function pointers. Shaders represent a limited execution environment. And one of those limitations is the lack of a requirement for a stack. And you can't really have arbitrary function pointers without one of those.

The horribly il-advised1 shader subroutines feature of GLSL 4.00 probably won't help either. It would only help if your <something calculated during runtime> were a CPU-generated value, and that seems unlikely in your case.

The only general solution you have is a switch statement. And quite frankly, there's nothing wrong with that.

I mean, you're already going to absolutely murder your performance by doing this kind of condition anyway. No matter how it gets implemented, if different instances in the same wavefront are executing separate code, you're kinda screwed performance-wise.

Not to mention, a switch statement does not necessarily get slower based on how many conditions there are. How it gets implemented depends entirely on the hardware. If jump tables are available, it can be done reasonably efficiently (ignoring the above performance issue, of course).

Also, there is Colonel Thirty Two's idea, where you encode each function's operation as a dot-product with a constant vector. Obviously, this limits what your various functions can actually execute. But if it works in your case, it works.

1 If you want to contest this, consider that SPIR-V provides an analog to every feature of GLSL, no matter how redundant, silly, or unnecessary... except shader subroutines.

Nicol Bolas
  • 378,677
  • 53
  • 635
  • 829
1

There are no function pointers in GLSL, but it is possible to define a structure with a list of function names:

struct functions_list {
    int sin;
    int cos;
    int tan;
    int fract;
};
const functions_list functions = functions_list(1,2,3,4);

Using this list of function names, it is possible to simulate a callback:

float callback(int func,float arg){
    if(func == functions.sin)
        return sin(arg);
    else if(func == functions.cos)
        return cos(arg);
    else if(func == functions.tan)
        return tan(arg);
    else if (func == functions.fract)
        return fract(arg);
    else
        return 0.0;
}

These "function pointers" can be used to simulate higher-order functions in GLSL.

Nicol Bolas
  • 378,677
  • 53
  • 635
  • 829
Anderson Green
  • 25,996
  • 59
  • 164
  • 297