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

Non-square matrices. #27

Open
tangent-vector opened this issue Oct 7, 2016 · 2 comments
Open

Non-square matrices. #27

tangent-vector opened this issue Oct 7, 2016 · 2 comments

Comments

@tangent-vector
Copy link
Collaborator

Ideally, I'd want to have the whole suite of matNxM types, where 2 <= N, M <= 4, and be able to index into them etc.

I started trying to add this myself, but the number of places I had to touch was daunting. The main issue is that each matrix type is reflected direclty in an enum (with hard-coded values, which seem to need to match another enum of IL-level types (?)). As a result, there are long conditional chains all throughout the code that need to handle all the cases (I would have preferred switch statements, since they are easier to add a case to on the fly), and all of those places need to be fixed up to handle a new type. One particularly worrying spot is trying to guess the IL type to use based on the size of a constant (so 9 floats -> mat3 and 16 floats -> mat4), but this breaks down for non-square matrices.

I'd really like to see a pretty fundamental change to the type representation in Spire, so that is has a general thing like a struct MatrixType { Type* elementType; int rows; int cols; } and then Type is either an inheritance hierarchy or a tagged union...

@tangent-vector
Copy link
Collaborator Author

One important issue to keep in mind if/when adding non-square matrices is that HLSL and GLSL interpret the same notation differently.

In GLSL, matrices are logically column-major, and the type is written matCxR for C columns and R rows:

mat3x4 m; // 3 columns, 4 rows
vec4 v = m[1]; // extracts a column vector (a matrix acts like an array of columns)
float f = m[c][r]; // extract an element, given column and row number

In HLSL, matrices are logically row-major, and the type is written floatRxC for R columns and C rows:

float3x4 m; // 3 rows, 4 columns
float4 v = m[1]; // extracts a row vector (a matrix acts like an array of rows)
float f= m[r][c]; // extract an element, given row and column number.

Now, anybody looking at the above would probably say "those look identical, and you just switched up what you were calling a 'row' or 'column.'" And you are pretty much right, and I'd actually strongly advocate that any front-end that wants to be portable should pick a uniform convention where you generate both matAxB and floatAxB uniformly, and just make your own decision about what you call a "row" vs. a "column."

Where the rubber hits the road is two places:

  1. What is the memory layout when somebody actually fills in a buffer with matrices in it?
  2. Matrix-vector and matrix-matrix products.

Issue (1) is pretty easy to deal with, since both GLSL and HLSL support modifiers on declarations that change the matrix layout conventions. The main difficulty for any portable shading language is just being consistent, and remembering when you've flipped conventions (so if you want HLSL conventiosn and row-major layout, then you'll actually want to ask GLSL for column-major layout).

Issue (2) is just pointing out the fact that because the conventions are flipped in GLSL/HLSL, if I have HLSL code like this:

float4x3 a;
float3x2 b;
float4x2 c = mul(a,b); // (4 rows, 3 cols) * (3 rows, 2 cols) -> (4 rows, 2 cols)

Then the equivalent GLSL requires that I swap the order of operands to my multiply:

mat4x3 a;
mat3x2 b;
mat4x2 c = b * a; // (2 rows, 3 cols) * (3 rows, 4 cols) -> (2 rows, 4 cols)

Long story short, if you generate both HLSL and GLSL from a common input language then the Right Answer is:

  • Output floatAxB and matAxB equivalently (don't swap for one target)
  • Output m[i] operations equivalently (don't try to swap for one target)
  • Flip operands to matrix-marix and matrix-vector products for one target (based on the convention you want)
  • Ensure the in-memory storage layout convention is flipped for one target (based on the convention you want)

My own bias on this is that the HLSL RxC row-major convention is the Right Way, and confusion is minimized if you always use row-major storage (or at least default to it).

@csyonghe
Copy link
Owner

Thanks for the notes, Tim.

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

No branches or pull requests

2 participants