Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GLSL code printer #12713

Merged
merged 14 commits into from Aug 2, 2017
Merged

GLSL code printer #12713

merged 14 commits into from Aug 2, 2017

Conversation

micahscopes
Copy link
Contributor

Hi there, I've implemented a rudimentary, generic GLSL code printer with basic tests. The code and tests were adapted from the javascript printer. I made a modification to allow the option of printing common operators as functions. In GLSL, there is no a**b operator, so that one always prints as pow(a,b), but it's also possible to render a+b as add(a,b), a*b as mul(a,b), etc.

I've used a simpler version of this printer for a while to develop Fragmentarium shaders. Using a simplified printer, I've been able to generate GLSL functions from all kinds of algebras in Sage, including Clifford Algebras.

I'd love to see this in the sympy. I just finished it a moment ago, but am feeling pretty confident about it, since it passes all the tests adapted from the javascript printer. That said, I haven't used this version of the code in practice. So it could use some human testing and review, and maybe some organization, as a lot of it is copy/pasted from other printers.

@micahscopes micahscopes closed this Jun 6, 2017
@micahscopes
Copy link
Contributor Author

micahscopes commented Jun 6, 2017

I had closed this to fix the documentation.

@micahscopes micahscopes reopened this Jun 6, 2017
@micahscopes
Copy link
Contributor Author

I need to point out that one significant thing is missing from this printer: printing small matrices and vectors as their native GLSL types. So for example, a 3x4 matrix could be printed as a mat3x4. The reason I overlooked this is because I have been using this printer exclusively to print stuff that doesn't have native representation in GLSL, mostly Clifford algebra. I was so stuck in the Clifford algebra that it never even occurred to me until just now: people will probably want to print native GLSL matrices. Duh! 🙄

@micahscopes
Copy link
Contributor Author

micahscopes commented Jun 7, 2017

Today I implemented the family of vec and mat glsl types, along with multidimensional arrays, which are supported in GLSL 4.3 and higher. I also wrote ridiculous amounts of tests, since these things are so complicated and I included a lot of options.

This code could really use a second set of eyes, and hopefully just one set. The tests could use some basic sanity checks. I've been staring at the screen all day ⚡️ . There were some complicated decisions to be made about the fact that GLSL assumes column-major indexing for its matrix operations. I decided to provide a mat_transpose that just transposes all matrices. By default it's turned off, meaning that the printer ignores the GLSL-style indexing by default. I was reading around that a lot of people ignore it anyway, and just flip their matrix multiplication around. If need be, the mat_transpose option can be set to True by default.

There's a bunch of other stuff in there too. Looking forward to some feedback 😄

@micahscopes micahscopes changed the title a rudimentary GLSL code printer GLSL code printer Jun 7, 2017
…new features that are being tested: glsl matrix/vector type printing.
@bjodah
Copy link
Member

bjodah commented Jun 7, 2017

This looks really interesting @micahscopes
I will take a closer look later today or tomorrow. I am not familiar with GLSL, is it different enough from C that it needs its own class or could it just subclass one of the C printers?

@micahscopes
Copy link
Contributor Author

@bjodah it's very similar to C, but with some different keywords and unique types. I subclassed CodePrinter, but it might have made sense to subclass the C printer. maybe that would have meant a little less copy/paste? I think for the most part, the class is now quite capable.

@bjodah
Copy link
Member

bjodah commented Jun 7, 2017

Yes, that was my thought. If almost all methods are just copies of the methods in say the C99CodePrinter (or is GLSL based on C89?), then subclassing would be preferred to avoid code-duplication (which eventually becomes a burden with respect to maintenance). I am currently working on implementing a Type class to represent special types (it's a work in progress in #12693). I will try to make it capable enough to support the GLSL types (are they syntactically equivalent to structs?)

@micahscopes
Copy link
Contributor Author

micahscopes commented Jun 8, 2017

@bjodah There are quite a few nuances, it seems. So it's hard to say. I've been referencing this wiki, although it's a little sparse on examples. A lot of stuff seems to come more from C++ than C, but I don't write much of either of those languages, so it's not easy for me to say with certainty.

As far as structs go, GLSL has structs that are more similar to C++ structs than C structs.

But GLSL has built-in matrix and vector types (only for dimensions of < 4), each with their own funny keywords. There are built in functions and operations for these types, such as dot and transpose. As I mentioned in a previous comment, the matrix multiplication in GLSL is backwards from how a lot of people learn in school, but OpenGL is the standard, so that's how it goes.

In GLSL you can also have arrays, I believe of most any type, although in OpenGL < 4.3, you can't make arrays of arrays. There might be some other variations on the kinds of arrays supported with different versions of OpenGL. In >= Opengl 4.3, multidimensional arrays are supported.

My personal use case is doing Clifford algebra on the graphics card. So I used Sage to generate symbolic expressions for various Clifford algebraic operations (mostly the geometric product), then I used the Sympy printer to convert these expressions into GLSL expressions. I represented Clifford algebraic objects (multivectors) as float or double arrays... (really, this is enough for anything that can be represented as a finite vector space)...

In early tests, that's all I needed. I just wanted a way for little expressions to be translated into arrays. I didn't even include any functionality for typing or assignment in my early printer. I did all that with a python string/templating tools.

But I can see how it would be desirable to interact with the standard GLSL types. People doing standard 3D stuff use standard types a lot.

I can also see how it might be useful to provide ways of rendering sympy objects into structs. To use my Clifford algebra example, maybe I'd want to have a Multivector type that keeps track of components by name, i.e.

struct MultivectorCl3
{
  float c;
  float x;
  float y;
  float z;
  float xy;
  float xz;
  float yz;
  float xyz;
};

Then a multivector might be used like:

MultivectorCl3 A = MultivectorCl3(0,1,-2,4,0,0,0,0)
float length = sqrt(pow(A.x,2.0)+pow(A.y,2.0)+pow(A.z,2.0))

Code generated in this way would be much more human readable, and easier to check.

Here's the product for the real Clifford algebra Cl(-1,-1), generated by my old printer. See for yourself how difficult it is to read:

const int N = 4;
float[N] mul(float u[N], float v[N]) {
    return float[N](u[0]*v[0] - 2.0*u[1]*v[1] - 4.0*u[2]*v[2] - 2.0*u[3]*v[3], u[0]*v[1] + u[1]*v[0] - 2.0*u[2]*v[3] + 2.0*u[3]*v[2], u[0]*v[2] + u[1]*v[3] + u[2]*v[0] - u[3]*v[1], u[0]*v[3] - 2.0*u[1]*v[2] + 2.0*u[2]*v[1] + u[3]*v[0]);
}

(by the way, the only part actually generated from the printer is the stuff inside of float[N](...)
How much nicer would it be if the code printer had been able to render expressions into a Multivector type, something like this:

const int N = 4;
Multivector {
  float c;
  float x;
  float y;
  float xy;
}

Multivector mul(Multivector u, Multivector v) {
    return Multivector(
        u.c*u.c - 2.0*u.x*v.x - 4.0*u.y*v.y - 2.0*u.xy*v.xy,
        u.c*v.x + u.x*v.c - 2.0*u.y*v.xy + 2.0*u.xy*v.y,
        u.c*v.y + u.x*v.xy + u.y*v.c - u.xy*v.y,
        u.xy*v.xy - 2.0*u.x*v.y + 2.0*u.y*v.x + u.xy*v.c
    );
}

Again, the part inside of Multivector(...) is what I'd expect the printer to output. It'd be great if it took care of the function stuff too, but I don't know if that's in the scope of what printers are intended for.

@micahscopes
Copy link
Contributor Author

micahscopes commented Jun 8, 2017

Here's an example of an 8-dimensional planar rotation function generated using the old version of this printer and Sage's CliffordAlgebra module: https://gist.github.com/micahscopes/e2cd2f9aa7e2daa9a433a42fffaf8122

That 8-dimensional rotation function actually takes some to bake out on a powerful machine. I think it took something like 30 seconds at least.

Believe it or not, I've tested this function and it really does work! Here's an example rotating an 8 dimensional fractal: https://vimeo.com/210915603 . If you you're looking at a 3-dimensional slice of an 8 dimensional space, and only rotate in planes that are subspaces of that visible slice, it behaves just as you'd expect, it looks just like an ordinary 3-dimensional rotation. But if you're rotating in planes that are outside of the visible slice, all bets are off and stuff starts morphing in and out of thin air 😮


'order': None,
'full_prec': 'auto',
'precision': 32,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'precision' doesn't actually refer to the number of bits used, but rather number of significant digits. Using 9 would be the right number for float32. We are working on better representation of types in the printers.



def test_glsl_code_constants_mathh():
assert glsl_code(exp(1)) == "float E = 2.7182818284590452353602874713527;\nE"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this number will have 8 decimals once precision is changed to 9...

assert glsl_code(Rational(3, 7)) == "3/7"
assert glsl_code(Rational(18, 9)) == "2"
assert glsl_code(Rational(3, -7)) == "-3/7"
assert glsl_code(Rational(-3, -7)) == "3/7"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this integer division? Wouldn't you want 3F/7F?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, absolutely. The syntax for casting non-constant integers to float is to use constructors. So if i and j were functions that returned int types, then you'd wanna do float(i())/float(j()). But for integer constants, usually people add a decimal after the number, i.e. float x = 2.; In this case I think it'd be more readable to do something like 3.0/7.0



def test_glsl_code_functions():
assert glsl_code(sin(x) ** cos(x)) == "pow(sin(x), cos(x))"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The little GLSL code I've seen they've used the 32 bit versions of math functions i.e. powf etc., but I don't know what is preferred. Does GLSL support fp64 too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a powf function (I tried searching for it here). GLSL has a double type. I believe there's trouble if the second argument in pow isn't a float or a double.

@bjodah
Copy link
Member

bjodah commented Jun 9, 2017

@micahscopes that's really fascinating stuff 👍. I made some minor comments. But since I haven't actually used GLSL myself I can't really give any in-depth feedback on GLSL specific parts.

@micahscopes
Copy link
Contributor Author

micahscopes commented Jun 9, 2017

thanks @bjodah! I'll make some of these little changes and see if I can't subclass the c printer to get rid of some copy/paste. I'm also gonna check into using initializer lists for multidimensional arrays. I think I read somewhere that this is possible, and it'd look nicer than what's in there now.

@asmeurer
Copy link
Member

asmeurer commented Jun 9, 2017

My feeling is that we shouldn't subclass the C printer if GLSL isn't actually a superset of the language. Otherwise, the printer will generate code for things that isn't valid GLSL.

@bjodah
Copy link
Member

bjodah commented Jun 12, 2017

If there is a considerable number of methods which are now copied & pasted we could create a private super class (e.g. _CFamilyCodePrinter) which both CCodePrinter and GLSLCodePrinter could inherit from (using inheritance then only to reduce code duplication). But that can also be changed at a later point, so I'm fine with having some code duplicated in this PR.

@bjodah
Copy link
Member

bjodah commented Jun 12, 2017

So then we should focus on the tests here, I don't know if there are any people in the SymPy community working with GLSL. I googled github "sympy" glsl and found some people who may be interested in this new functionality. I am going out on a limb here and pinging some of you:

Would any of you be interested in this functionality @prideout @yuri-karadzhov @ataber ? Do you have any feedback on the functionality? (I hope you don't mind being pinged, please feel free to ignore the ping and sorry if it disturbs you, I am simply guessing that you might be interested).

@micahscopes
Copy link
Contributor Author

@bjodah I made some changes based on your review.

Next thursday I'll give a talk related to this code printer at a local Python meetup. Maybe I can find some people who might feel confident in offering feedback!

@micahscopes micahscopes mentioned this pull request Jul 12, 2017
@asmeurer
Copy link
Member

Is this ready to be merged?

@micahscopes
Copy link
Contributor Author

micahscopes commented Aug 2, 2017

@asmeurer As it stands, this is useful and seems to be pretty solid/stable. I think it'd be safe to merge it. Maybe that would help generate some people actually using it?

there is work to be done on it, but it seems to be on par with many of the other code printers.

I would still like to get a few actually useful examples in (it would be helpful for users, but also for me to discover stuff I may have overlooked). but I'm not sure that would need to be done before merging.

@asmeurer
Copy link
Member

asmeurer commented Aug 2, 2017

It's easy to make another PR later with more changes. @bjodah do you agree this can be merged?

@bjodah bjodah merged commit 0b61acb into sympy:master Aug 2, 2017
@bjodah
Copy link
Member

bjodah commented Aug 2, 2017

Yes, :)
Thank you @micahscopes. We are looking forward to your additional examples (examples will most probably also lead to more people using it).

@micahscopes
Copy link
Contributor Author

@asmeurer @bjodah thanks ya'll... this is a big deal for me! looking forward to getting some more examples in there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants