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

Move Output vars to return position (was: Storage class inference interface) #416

Open
khyperia opened this issue Feb 11, 2021 · 8 comments
Assignees
Labels
mcp: accepted A major change to the compiler that has been accepted.

Comments

@khyperia
Copy link
Contributor

Storage class inference is now a thing as of #300/#414. This details the design for the interface system taking advantage of inference, pointing out all the rules of how storage classes are specified in entry points.

There are a handful of ways of specifying a storage class:

  1. direct attribute: fn main(#[spirv(input)] x: &f32). This uses the current list of storage classes.
  2. via builtin: fn main(#[spirv(position)] x: &Vector3) (inferred as input). This uses the current list of builtins (a table is needed to map builtin to storage class, I think some are input, some are output, and there may be others)
  3. via image: fn main(x: &Image2d) (inferred as uniform_constant). I thiiink images are always uniform_constant and not uniform, but I'm not sure.
  4. unspecified future inference rules that we haven't discovered yet (the spec is light on what goes in what storage class), suggestions are welcome

If exactly one of these rules matches, then use the storage class it specifies.

If more than one of these rules matches, and they all compute the same storage class, emit a warning (e.g. #[spirv(position, input)], warn on input and say it's redundant). If more than one of these rules matches, and they compute different storage classes, emit an error.

If none of these rules matches, then we have an open design question. The options here are a trade-off between catching user errors that may be difficult to diagnose/guiding users with explicit syntax suggestions, and not annoying people with overly explicit syntax that takes a while to type out and read.

  1. fn main(x: &f32) -> is this an error, or does it default to input? (or something else)
  2. fn main(x: &Struct) -> is this an error, does it default to input, or does it default to uniform? (or something else)
  3. fn main(x: &mut f32) -> is this an error, or does it default to output? (or something else)

For 2, I don't know if it's valid to have a struct (or any other non-scalar) be an input/output variable, more research is needed.


An alternative is to keep the current system of Input<T> and friends. We would remove the .load() and .store() methods entirely, and implement Deref/DerefMut (when applicable) for them. I much prefer the readability, recognizability, and usability of using plain references, but I understand others don't feel the same way~

@khyperia khyperia added the mcp: proposed A major change to the compiler, that hasn't yet been approved. label Feb 11, 2021
@khyperia khyperia self-assigned this Feb 11, 2021
@eddyb
Copy link
Contributor

eddyb commented Feb 11, 2021

via builtin: fn main(#[spirv(position)] x: &Vector3) (inferred as input). This uses the current list of builtins (a table is needed to map builtin to storage class, I think some are input, some are output, and there may be others)

Some can be either, but I think the best default would be to picking input vs output based on the kind of reference, and use attributes to override that - we could also support outputs via returns, tho multiple/named outputs would require a struct.

@eddyb
Copy link
Contributor

eddyb commented Feb 11, 2021

For Vulkan 1.2, if anyone is curious, the valid Storage Classes for each builtin are listed here:
https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#interfaces-builtin-variables

Not sure if the information is available in a machine-readable format (haven't checked yet), but Ctrl+F Storage Class can quickly highlight that each builtin varies with Execution Model, usually either Input or Output, but in some cases both are allowed.

@Hentropy
Copy link
Contributor

How would a function like this be handled? Compiler error I guess?

fn shader(#[spirv(input)] foo: &mut Vec4) {}

How does the SampleMask builtin work? It can be either Input or Output into a frag shader, so will it always need to be specified? Maybe Input is a sensible default, and it only needs to be specified if you want Output (or vice versa)?

Output is uninitialized, so reading before writing is bad. How will this be prevented?

(As you both know, I prefer a type based syntax for this because I think it's easier to read and write, more idiomatic, and will help with implementing safe CPU-GPU interop)

@eddyb
Copy link
Contributor

eddyb commented Feb 11, 2021

Output is uninitialized, so reading before writing is bad. How will this be prevented?

Oh right, that's where I was suggesting &mut MaybeUninit<T> on Discord for Output.

Alternatively, we could return the value from the entry-point - this gets more interesting with multiple outputs (which may be builtins so they would need a place to attach attributes to anyway) - probably be the best thing in that case is returning a struct.

@XAMPPRocky
Copy link
Member

Oh right, that's where I was suggesting &mut MaybeUninit on Discord for Output.

Small note that, that is blocked on #415

@eddyb
Copy link
Contributor

eddyb commented Feb 11, 2021

Based on #415 (comment) I think we're fine - I doubt ZST inputs/outputs would be common, if at all supported by SPIR-V and/or Vulkan.

@msiglreith
Copy link
Contributor

Alternatively, we could return the value from the entry-point - this gets more interesting with multiple outputs (which may be builtins so they would need a place to attach attributes to anyway) - probably be the best thing in that case is returning a struct.

This would be nice imo as it allows to default to input storage class for every parameter and output for the return type. (Feels also a bit nicer to write!)

@XAMPPRocky XAMPPRocky added mcp: accepted A major change to the compiler that has been accepted. and removed mcp: proposed A major change to the compiler, that hasn't yet been approved. labels Feb 18, 2021
@Hentropy
Copy link
Contributor

Hentropy commented Mar 2, 2021

How would descriptor indexing work?

#[spirv(uniform, descriptor_set = 0, binding = 0)] way_1: [&T],
#[spirv(uniform, descriptor_set = 0, binding = 0)] way_2: &[T],
#[spirv(uniform_array, descriptor_set = 0, binding = 0)] way_3: &T,
#[spirv(uniform_array, descriptor_set = 0, binding = 0)] way_4: &[T],
#[spirv(uniform_array, descriptor_set = 0, binding = 0)] way_5: &[&T],

way_1 is not valid Rust, function parameters must be sized.
way_2 is ambiguous: is it a runtime descriptor array or a variably sized buffer?
way_3 does not require indexing before accessing the pointer.
way_4 requires T: Sized and makes the runtime descriptor array a fat pointer with an unknown size.
way_5 introduces an extra pointer that does not exist and makes the runtime descriptor array a fat pointer with an unknown size.

Descriptor array bindings are created in Vulkan by specifying a count > 1 in VkDescriptorSetLayoutBinding. Runtime descriptor array bindings additionally require the appropriate flag to be set in VkDescriptorSetLayoutBindingFlagsCreateInfo.

@khyperia khyperia changed the title Storage class inference interface Move Output vars to return position (was: Storage class inference interface) Apr 1, 2021
@eddyb eddyb self-assigned this Mar 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mcp: accepted A major change to the compiler that has been accepted.
Projects
None yet
Development

No branches or pull requests

6 participants
@eddyb @khyperia @XAMPPRocky @msiglreith @Hentropy and others