complex-function-plotter icon indicating copy to clipboard operation
complex-function-plotter copied to clipboard

How to increase OpenGL version?

Open MattiKemp opened this issue 2 years ago • 6 comments

I have been implementing various number theory functions with shaders, and started running into single-precision floating point overflow problems. So, I need double precision, tried implementing doubles but the compiler kept spitting out version errors. I modified some of the server's code and the context claims to be WebGL GLSL ES 3.00: Screenshot from 2024-01-11 00-56-58

This can't be true though, as I also can't initialize arrays with constructors. Tried for a while to increase the version to at least 4.00, but nothing seemed to work. Any ideas? Is it some node module that needs to be upgraded or is there something limiting the version I can select?

Love the software, I managed to implement Riemann's explicit formula for the prime counting function. It isn't completely accurate due to those floating point problems I described earlier: Screenshot from 2024-01-11 01-05-50 Screenshot from 2024-01-11 01-07-19

Look at the lower part of the graph, you can clearly see the geometry converging at each of the primes and also at the prime powers! This is only with 15 zeros! Your graphing software is amazing, this would be so much more difficult to implement in Python, and it wouldn't look anywhere near as nice. If you want to try out the shader: Riemann_Counting_Function.glsl.txt

Just be warned, this is really laggy, even on my RTX 3090. You'll probably want to adjust how many iterations the logarithmic integral is computed for, as well as how many zeros you are summing. (update, the code above is wrong, but the Riemann counting function is correct, it doesn't handle the zeros properly)

MattiKemp avatar Jan 11 '24 07:01 MattiKemp

Great job with the shader, the results look super interesting!

The code that you write is injected into a larger shader. The GL version is defined by the first line of this shader (no version declaration; i.e. GL ES 1.00). I chose this version for maximum compatibility (as some devices still only support WebGL 1).

If you are modifying the server yourself, you can try adding a #version 300 es declaration to the larger shader (no newline before). You'll also need to request WebGL 2 in the initializeWebGL call in the FunctionPlot component. A lot of the GL code was written with GL ES 1.00 assumed, so I have no idea how much will break. Of course it can be done with a finite amount of work, but I suspect it will be a tedious task...

wgxli avatar Jan 11 '24 13:01 wgxli

I appreciate the quick response.

I have already requested WebGL 2 in FunctionPlot: Screenshot from 2024-01-12 05-27-01

Does this look right? It states in the console.log after it that it is using WebGL 2.

So, would I change the shader in shader.js to something like this? Screenshot from 2024-01-12 05-26-21

It seems to accept it, only problem is it throws "Shader failed to compile: ERROR: 0:149: 'gl_FragColor' : undeclared identifier". So, I'm guessing I need to do what you suggested, and rewrite some some of the GL code. Hopefully, it's as simple as modifying some of the framework in shader.js. I'll leave an update if I make any progress with this. Once again, love your software, it's easily the best free graphing program.

MattiKemp avatar Jan 12 '24 11:01 MattiKemp

Update

I am still having difficulties upgrading to versions >= 4.00, it may just be a configuration problem with my system. However, I believe I managed to get version 3.00 ES working. I have tested every implementation in Supported Functions, and they appear to all be graphing correctly. Although, I can't ensure stability or correctness with every predefined function. For example, there seems to be an error when inserting code into the first line of the custom function editor. Appending a few empty lines at the top seems to resolve this issue.

Having version 3.00 ES selected means that arrays can be initialized with constructors at compile time, arrays can be indexed with non-constant values, and for-loops no longer require constant parameters. These features alone, make the development of complex shaders much easier, and improves compute drastically. There are also many other added features that I haven't listed, information pertaining to them can be found in the specifications for The OpenGL ES Shading Language Version 3.00

To get version 300 ES working:

  1. Modify initializeWebGL() in src/components/FunctionPlot/index.js, so that it appears as such:
    Screenshot from 2024-01-12 06-45-35

    One can test if this change was successful, and whether their system supports WebGL 2, by first starting the server, accessing their browser's console on the graphing webpage, and searching for:
    Screenshot from 2024-01-12 08-11-32

  2. Modify the shader vertexShaderSource in src/gl-code/shaders.js:
    Screenshot from 2024-01-12 07-03-58
    So that it becomes:

    Screenshot from 2024-01-12 07-03-27


  3. Modify the shader returned by getFragmentShaderSource in src/gl-code/shaders.js:
    Change the first few lines of the shader:
    Screenshot from 2024-01-12 07-09-17
    So that it becomes:
    Screenshot from 2024-01-12 07-10-36
    Then change the last line of the shader:
    Screenshot from 2024-01-12 07-18-44
    So that it becomes:
    Screenshot from 2024-01-12 07-19-22

Save these changes and start the server as you normally would. Everything should appear as it typically would before the changes. To test if the OpenGL ES Shading version has been properly upgraded, paste the following shader code into the Custom Function editor:


float a[5] = float[5](.4, 0.2, 0.1, 0.05, 0.025);
int spot = 0;
int iters = 100;

vec2 mandelbrot(vec2 z){
  z += t; 
  vec2 c = z;
  for(int j=0; j<1; j++){
    for(int i=j; i<iters; i++) {
      z = cmul(z, z) + c;
      if(spot < 5){
        z += vec2(a[spot],0.0);
        spot += 1;
      }
    }
  }
  return z;
}

vec2 mapping(vec2 z) {
  return mandelbrot(z);
}
  • Note the empty line at the top of the shader code, this appears to be necessary due to some parsing error related to the previous modifications to the Fragment Shader. The page will likely crash without it.

If the OpenGL Shading version has been upgraded, the output of the shader should be the truncated Mandelbrot set displayed below:
Screenshot from 2024-01-12 08-00-57

This shader code will not run in the default OpenGL Shading version 1.00. With the browser's console throwing an error related to this:
Screenshot from 2024-01-12 07-49-38

I'll leave a final update comment detailing the process of upgrading to OpenGL Shading versions >= 4.00. Hopefully this will be helpful to others.

MattiKemp avatar Jan 12 '24 14:01 MattiKemp

Thanks, this is fantastic! (And much easier than I thought.)

Given how few changes are necessary, it seems feasible to check for WebGL 2 support at runtime and inject the appropriate version strings. This would allow for ES 3.00 syntax on most devices. I'll see whether I can get this running stably.

I'll of course add you to the acknowledgements (let me know if you have a preferred display name).

edit: The max supported version for WebGL 2 is nominally 3.00 ES, which explains why you can't get 4.00 working.

wgxli avatar Jan 12 '24 14:01 wgxli

I'll of course add you to the acknowledgements (let me know if you have a preferred display name).

That is very kind of you, Matti Kemp would be fine. I plan on making a few more contributions in the coming weeks.

edit: The max supported version for WebGL 2 is nominally 3.00 ES, which explains why you can't get 4.00 working.

You are correct, I have learned this the hard way. I spent the last two days trying to get hardware level double precision/64 bit integer support working. There are OpenGL extensions packaged in most modern drivers that provide this, but to my knowledge, there is no way to get WebGL to reference these. Which means double precision software emulation is necessary. Luckily, this isn't too hard to do, and Henry Thasler has great articles about implementing robust emulated double precision in OpenGL. Considering the state of hardware double precision support on modern consumer GPUs, the compute overhead from emulation is not as severe as one would initially believe. Here is a working implementation of the default mandelbrot set shader with emulated double precision:

WebGL_Emulated_DP_Mandelbrot.txt

He has also implemented quadruple precision floating point numbers. Although, I would not recommend their use due to the increased time complexity for basic arithmetic operations, on top of the added development complexity. Here is a working implementation of the default mandelbrot set shader with emulated quadruple precision:

WebGL_Emulated_QP_Mandelbrot.txt

All credit for these implementations goes to Henry Thasler. He developed the emulation procedures for an OpenGL mandelbrot set program. All I had to do was port his examples so that they worked with the shader mapping function. One will not get the full expected precision out of the box with emulation, due to the shader parsing and evaluation system only working with single precision floating point numbers. I'll see if I can resolve this in the next few days, and possibly make it easier to develop shaders with these emulated floating point numbers.

MattiKemp avatar Jan 14 '24 14:01 MattiKemp

Here, I believe, is an example of the benefits that emulated double precision can bring. Take the real component of z, real(z), and divide it by 2*real(z). This works out to 1/2 + imag(z)*i. Implemented in the built in equation editor: Screenshot from 2024-01-14 12-23-34

Which, when graphed:

Screenshot from 2024-01-14 12-25-32

Or, when implemented in the shader editor:

Screenshot from 2024-01-14 12-28-36

There is clearly some kind of floating point error that is being propagated throughout the graph, due to the division operation. If one implements the same shader using Henry Thasler's emulated double precision floating point:

Screenshot from 2024-01-14 12-34-59

The error is no longer visible in the given graphing range. Some of the first graph's error is due to the real(z) and imag(z) operations, likely due to precision truncation. Which is why the single precision shader implementation appears different to the first graph.

MattiKemp avatar Jan 14 '24 18:01 MattiKemp