Skip to content

Implement OpTypeMatrix #738

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

Merged
merged 12 commits into from
Aug 30, 2021
Merged

Implement OpTypeMatrix #738

merged 12 commits into from
Aug 30, 2021

Conversation

hatoo
Copy link
Contributor

@hatoo hatoo commented Aug 29, 2021

Fixes #82.

This PR adds #[spirv(matrix(ty, m, n))] attribute. A type with #[spirv(matrix(..))] is expressed as OpTypeMatrix in SPIR-V.
An example usage is tests/ui/spirv-attr/matrix-type.rs.

While OpTypeVector is implemented by utilizing #[repr(simd)], this PR implements OpTypeMatrix by introducing the new trait Matrix because I couldn't find a usable attribute.

When converting Ty to SPIRV type, it uses OpTypeMatrix if the Ty implements Matrix.

TODO

  • Make glam matrices OpMatrix
    Since many operations in glam's matrices cast self to inner type, we need to add attributes to glam matrices and its inner types.
    ``Currently, we can't add the attribute to glam::core::storage::Column*<V> because we can't know its vector length at definition.
    We may handle it by adding `#[spirv(matrix(m, param(n)))]` syntax and refer nth generics type (e.g. glam::Vec3) and infer vector length from it...

@hatoo
Copy link
Contributor Author

hatoo commented Aug 29, 2021

I've realized that we can implement this with custom attributes and I think it's better.
I've removed Matrix trait and added #[spirv(matrix(..))].
I'll revert it if you prefer the Matrix trait.

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

I added

  • #[spirv(matrix(ty, m))]
  • #[spirv(matrix(ty))]
  • #[spirv(matrix)]

These infer missing parameters from type.

- #[spirv(matrix(ty, m, n))]
  Specify all of type, rows, columns.
- #[spirv(matrix(ty, m))]
  Specify all of type, rows. Infer columns.
- #[spirv(matrix(ty))]
  Specify all of type. Infer others.
- #[spirv(matrix)]
  Infer all.
@khyperia
Copy link
Contributor

Please only implement #[spirv(matrix)] (the inferred one), it's both the preferential design and simplifies a lot of stuff.

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

Make sense!
I dropped other than #[spirv(matrix)]

Copy link
Contributor

@khyperia khyperia left a comment

Choose a reason for hiding this comment

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

I believe tests/ui/spirv-attr/{invalid-target.rs,multiple.rs} need to be updated.

count: m as u32,
}
.def(span, cx))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't have to (and shouldn't) do this digging into field types for this, and should use the standard type translation tools on the fields instead. Also, this is missing quite a bit of validation (e.g. if fields are different types, or there are no fields - IIRC your code ICEs if there's no fields) - there should be tests for each of the three kinds of error. Something like this should work instead:

IntrinsicType::Matrix => {
    let field_types = (0..ty.fields.count())
        .map(|i| trans_type_impl(cx, span, ty.field(cx, i), false))
        .collect::<Vec<_>>();
    if field_types.is_empty() {
        cx.tcx
            .sess
            .err("#[spirv(matrix)] type must have at least one field");
        return Err(ErrorReported);
    }
    let elem_type = field_types[0];
    if !field_types.iter().all(|&ty| ty == elem_type) {
        cx.tcx
            .sess
            .err("#[spirv(matrix)] type fields must all be the same type");
        return Err(ErrorReported);
    }
    match cx.lookup_type(elem_type) {
        SpirvType::Vector { .. } => (),
        ty => {
            cx.tcx
                .sess
                .struct_err("#[spirv(matrix)] type fields must all be vectors")
                .note(&format!("field type is {}", ty.debug(elem_type, cx)))
                .emit();
            return Err(ErrorReported);
        }
    }

    Ok(SpirvType::Matrix {
        element: elem_type,
        count: field_types.len() as u32,
    }
    .def(span, cx))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks.
I updated the code and change Matrix length validation since OpTypeMatrix requires a length of at least 2.
https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#OpTypeMatrix

#[spirv(object_to_world)] _object_to_world: Affine3,
#[spirv(world_to_object)] _world_to_object: Affine3,
) {
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this isn't actually testing much due to the majority of it being dead code. (Also, nit, the name Affine3 is a little misleading - it's not an affine transformation - but isn't super important for a test, haha)

I would like to see field accesses/etc. tested as well, I'm nervous about just crossing our fingers and hoping that matricies behave exactly like structs in all ways and no instructions need to be modified to handle matricies specially.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, I chose Affine3 because glam::f32::Affine3A has the same layout https://docs.rs/glam/0.18.0/glam/f32/struct.Affine3A.html (Sry, I don't know about math 😨).

I feel adding more tests for field operations in tests/ui/spirv-attr/* is not appropriate because it may should contain only about attr tests.
Is it OK? or is there any better place?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh! whoops, misread it as 4 Vec4s, not 4 Vec3s, 4 Vec3s definitely is an affine transform, haha, sorry

Yeah, not totally sure about where to put tests, anywhere is probably fine

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 added tests to matrix-type.rs I think this is enough but are there suggestions for more cases?

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

Thanks for pointed out.
I updated tests/ui/spirv-attr/multiple.rs but failed to update tests/ui/spirv-attr/invalid-target.rs...

I simply replaced sampler, block, sampled_image, generic_image_type, // struct-only to sampler, block, sampled_image, generic_image_type, matrix, // struct-only.

But got the error

error: failed to decode compiler output as json: `EOF while parsing a string at line 1 column 239`

@khyperia
Copy link
Contributor

I simply replaced sampler, block, sampled_image, generic_image_type, // struct-only to sampler, block, sampled_image, generic_image_type, matrix, // struct-only.

But got the error

error: failed to decode compiler output as json: `EOF while parsing a string at line 1 column 239`

Looks like compiletest is just straight up broken if output is too long - it injects this into the output and then later tries to parse the output as json, tripping over its own trimmed marker.

Feel free to just skip updating invalid-target.rs for now. (or, split it into multiple files if you feel like it so the output is shorter, but, I can do that after this PR is merged)

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

I can't reproduce the CI failure.
I've tested on Windows 11 and Ubuntu20.04 (both WSL and native) and pass.

@khyperia
Copy link
Contributor

khyperia commented Aug 30, 2021

I'm also unable to reproduce, even with CI's exact version of spirv-tools. hmm. I'll try rerunning tests, I guess?

edit: oh, I mucked up getting CI's exact version of spirv-tools, I can reproduce locally now. Assuming linux, download/extract this tarball, put the folder on PATH, then run tests via cargo run -p compiletests --release --no-default-features --features "use-installed-tools" -- --target-env vulkan1.1,spv1.3

@khyperia
Copy link
Contributor

Was able to reproduce locally, the full error was cut off in CI:

error: line 70: OpEntryPoint Entry Point <id> '4[%4]'s callgraph contains function <id> 4[%4], which cannot be used with the current execution model:
[VUID-StandaloneSpirv-None-04644] in Vulkan evironment, Output Storage Class must not be used in GLCompute, RayGenerationKHR, IntersectionKHR, AnyHitKHR, ClosestHitKHR, MissKHR, or CallableKHR execution models

  %4 = OpFunction %void None %28

the issue seems to be an output (&mut) var being used in closest_hit functions

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

Thank you!
I updated test cases.

@khyperia
Copy link
Contributor

could you add a // build-fail test that triggers the three new errors? (also remove the accidentally commited module file) - other than that, looks pretty good!

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

I don't still understand why

#[spirv(fragment)]
pub fn main_add(in1: Affine3, in2: Affine3, out: &mut glam::Vec3) {
    *out = in1.x + in1.y + in1.z + in1.w + in2.x + in2.y + in2.z + in2.w;
}

failed but reverted to previous code for now.

@khyperia
Copy link
Contributor

khyperia commented Aug 30, 2021

Ah, I think that failure is due to a similar thing as #513, which, might become more pressing to fix once people actually start using matrices. TL;DR matricies take up a handful of binding slots, so incrementing binding slot by 1 per parameter doesn't work (and results in overlapping parameter slots)

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

I added failing tests.

I added two test files because it seems not able to test #[(spirv(matrix))] for empty struct with others in the same file.

Error messages feel unkind because it doesn't show type definition.
But we can't get a span of type definition in trans_intrinsic_type, right?

@khyperia
Copy link
Contributor

Oh, oof, no, that's why we add tests, haha, I completely screwed up that prototype impl - there is a span in scope (it's passed into trans_intrinsic_type, should be able to use span_err instead of err (and struct_span_err instead of struct_err)

@khyperia
Copy link
Contributor

Ah, but you're right that we probably want to point the error at the struct itself, in which case, this gives you the span for the struct - you might have to re-match on the TyKind::Adt to pull it out again, I forget if there's a helper that makes it easier

span = cx.tcx.def_span(adt.did);

@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

Thanks, I choose to use def_id_for_spirv_type_adt and it will returns never None because of trans_type_impl.

Copy link
Contributor

@khyperia khyperia left a comment

Choose a reason for hiding this comment

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

🎉

@khyperia khyperia merged commit b0676cb into EmbarkStudios:main Aug 30, 2021
@hatoo
Copy link
Contributor Author

hatoo commented Aug 30, 2021

Woo! 🎉 🎉 🎉
I really appreciate your support and thank you for your time! 🙏

@hatoo hatoo deleted the op-type-matrix branch August 30, 2021 13:51
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

Successfully merging this pull request may close these issues.

repr(simd) for matrix types
2 participants