You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rendering images of a selected area with arbitrarily large precision. This means allowing a user-controlled amount of floats of precision for each complex number, the amount of iterations executed for non-elementary function, and possibly a different method of rendering colors to make sure it's more precise.
1
+
Problem!
2
2
3
-
UHPM should be used only to render static "images", so it shouldn't care about performance. It should be fine to wait a couple of seconds to wait for a picture of a specific range in the graph to be rendered, if this is going to be used for specific analysis, or for an image to be used in, for instance, a scientific paper.
3
+
Currently, our Ultra High Precision Mode math library is written in a functional manner. This means that all functions are pure, beautiful and return things. This allows us to transform operations like `x * (y + z)` nicely into `hp_mult(x,hp_add(y,z))`
4
4
5
-
Okay, let's attempt to do this.
5
+
The problem is that GLSL really doesn't like when we return arrays, and nesting functions _also_ introduce multiple temporary variables, which occupate registers. It would much rather have this expression turned into `number expr; hp_add(y,z,expr); hp_mult(expr,x);`. It _also, also_ dislikes temporary variables being allocated in functions.
6
6
7
-
In order to get a functioning high precision library, there is some basics that we have to do.
8
-
First, we need the four basic operations, exponentiation, logarithm and trigonometric functions. We can use whatever numerical method we want for this so long as it runs in a non-absurd manner (a good limit to set is, at maximum, 3 seconds for a whole frame.)
9
-
Once we have the four operations down, all those other functions can be computed quite fast through Newton-Raphson and their Taylor Series. Since precision is our main objective, we should run these numerical methods a number of times needed to give us an error smaller than our current precision.
10
-
This also means making the number of iterations in our non-elementary functions user-defined.
7
+
To fix this, we have to write our code like assembly! This means that we'll have to keep a list of global registers that are used whenever needed by a function, and we do composition on these registers!
11
8
12
-
We don't want to be rewriting all our functions defined for regular precision in higher precision. So, once we have these basic functions down, and some other helper functions used by GLSL (like length, step, mix, etc), we will need to write a transpiler that transforms functions' implementations to use our higher precision library.
13
-
For instance, the function `cadd(vec2 a, vec2 b){return a+b;}` should become `cadd(hp_vec2 a, hp_vec2 b) {return hp_vec2(hp_add(a.x,b.x),hp_add(a.y,b.y)));}`. It might be useful to manually replace all occurances of regular additions with a helper function `scalar_add(a,b)` in order to make this parsing easier (and avoid having to create yet another RPN interpreter)
9
+
This means lots of work ahead:
14
10
15
-
There will be lots of precomputing necessary on the CPU side, namely for constants like PI, E and lookup tables for non-elementary functions. This means that we will need to have a C++ way to generate these arbitrary precision `number` structs.
16
-
17
-
Optionally, we could simply write a parser that transforms GLSL into C++ in build time, giving us the possibility of running our "shader" in the CPU if necessary. This might be such case if we ask for a very high limb count (for instance, 32 `uint` limbs, which equates to 1024 bits), which might cause the GPU to run for too long and end up losing context.
18
-
This would give us a more stable, albeit far slower, method of drawing functions with any precision. Since this would all be transpiled from GLSL, we actually wouldn't need to rewrite any code, and would give us some pretty nice luxuries, like arbitrary function constant folding.
19
-
20
-
In short, our steps to implement UHPM are:
21
-
1. Implementing functions
22
-
1.1. Implementing the four operations
23
-
1.2. Implementing ln, exp, sin, cos, tan
24
-
1.3. Implementing other GLSL specific functions used in other pieces of code
25
-
26
-
2. Writing a transpiler from low-precision GLSL to high-precision GLSL
27
-
28
-
3. Working with CPU side arbitrary precision
29
-
3.1 Sending arbitrary precision numbers to the shader
30
-
3.2 Precomputing constants
31
-
3.3 Precomputing non-elementary function lookup tables
32
-
33
-
4. Writing a GLSL to C++ transpiler (optional)
34
-
4.1 Hooking up GLSL function names to their respective C++ functions automatically
35
-
4.1.1 Constant folding with C++ GLSL definitions
36
-
4.2 Running shaders in the CPU
11
+
1. Change math library to use `inout` rather than `return`
12
+
2. Change transpiler to expand nested expressions
13
+
3. Change `generate_glsl_string` to expand nested expressions
14
+
4. Rewrite transpiler to automatically use registers
0 commit comments