-
Notifications
You must be signed in to change notification settings - Fork 246
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
Bindings proposal #19
Conversation
db5aee9
to
b329f26
Compare
At first glance, declaring globals with attributes seems more flexible than implicitly deriving data from a location in a parameter list - probably want to explore that option in the Alternatives section? |
I have been talking to @Jasper-Bekkers about the "struct arguments" option and IMO that's the most ergonomic and future proof option - it matches what most consider "state of the art" in this language design space AFAIK. Also there's nothing that really needs to change to enable descriptor indexing; it's based only on how the shader is coded. For instance: struct ShadingInputs {
albedo: Texture::<f32>,
normal_map: Texture::<f32>,
smoothness: Texture::<f32>,
}
fn main(inputs: &ShadingInputs[3]) {
let mut T = brdf(&inputs[0]);
} Enabling an array here works all the way back to Vulkan 1.0, assuming static indexing. Dynamic indexing could be made to work on Vulkan 1.0 for a subset of functionality - but it would involve putting switch statements into the generated spir-v. This might instead serve as a useful cutoff for requiring the dynamic indexing extension though, assuming this can be detected. Arrays could also just be the deciding factor here. The only wrinkle is that in Vulkan this would mean translating each element of the struct to an array individually, but that shouldn't be too hard to deal with? So I guess that option has my vote at least 😅 |
One other thing we talked about was inline constants in those inputs, a la: struct ShadingInputs {
albedo: Texture::<f32>,
normal_map: Texture::<f32>,
smoothness: Texture::<f32>,
someParameter: f32, // <------ LOOK HERE
}
fn main(inputs: &ShadingInputs[3]) {
let mut T = brdf(&inputs[0]);
} There's a Vulkan extension - VK_EXT_inline_uniform_block - which lets you put those directly in blocks in descriptor sets. Using that is an option, but as with dynamic indexing, it doesn't have wide support on mobile; and even desktop isn't fully covered. The fallback would be to reserve a single uniform buffer (cbuffer) binding to collect any inline constants and source them from there - this will require some remapping, but it seems like it'd be feasible. |
In my experience, in larger code-bases (100k-1mln lines of GPU code) having globals becomes a /huge/ mess. You don't end up known what uses them, which ones are available, which ones should be bound under what conditions etc. The biggest thing is when you're modifying some shader code, and you decide to invoke a function somewhere which in one of the leaf-functions that it calls suddenly needs a new binding to be available but you don't know because it's a global. In smaller shaders where this is all in one file that doesn't really matter, but if you're talking about having libraries of shaders that are potentially published and created by third-party users it quickly becomes a thing if all bindings are implicit like that. I can add this argument to the RFC directly too, since I think it ties into our higher level goals quite a bit more too. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does anyone have objections to this proposal? Let's merge it if it's roughly on track with what we want.
I'm pretty happy with it, aside from some minor nitpicks, e.g. I think using attributes instead of constructor arguments for the globals case would be much better, but, that's a minor detail that can be changed later.
Just to be clear, the proposal recommends against having global bindings. The constructors are there because rust wants these variables to be initialized. |
I like it. It would be good to see the CPU-side as well, though: ultimately, the bindings are about tying the CPU and the GPU together, so it's kind of odd to see only one side here ;) |
Actually, in the parameters proposal, how do you expect attributes like Flat, NoPerspective, or Centroid to be applied? What about Index? DescriptorSet and Offset? And others that I likely missed when scanning through the spec |
I've left the CPU side out for now because we haven't discussed the CPU part of the API at all yet, most of that will have to come once we start on our rendering abstraction / render graph, but it definitely warrants attention. My ultimate hope for this would be that this is relatively minimal / easy to use at the start, I'd love the CPU side to closely represent a function call, or at least represent
I'll add something to the doc, my initial suggestion would be to have Edit: We'll probably need |
A few thoughts:
Is the idea here to basically just map each " If so, two concerns:
struct StageInputs {
vs_world_pos: Vec3,
#[flat]
vs_material_index: usize,
}
struct ViewGlobals {
model: Mat4,
view_proj: Mat4,
}
struct MaterialInputs {
albedo: Texture<f32>,
normal_map: Texture<f32>,
smoothness: Texture<f32>,
someParameter: f32,
}
struct StageOutputs {
out_color: Vec4, // potentially add an RGBA type?
}
fn main(
stage_inputs: &StageInputs,
view_inputs: &ViewGlobals,
material_inputs: &MaterialInputs[3]
) -> StageOutputs {
// this would require an extension for dynamic indexing
let mut col = brdf(&material_inputs[stage_inputs.vs_material_index]);
StageOutputs {
out_color: col,
}
} This ends up feeling pretty similar to the way HLSL handles stage inputs/outputs, which I think is positive because it's both IMO nice to use and is familiar to many people already. |
Since several platforms have a very low maximum number of descriptor sets that can be bound, my initial suggestion would be to bind them all to descriptor set 0.
Essentially yes - I would love for a follow-up PR to potentially propose a
In this proposal I didn't include "other data types" other then bindings (e.g. floats between bindings etc) on purpose, same for inter-stage arguments. I think those are important but potentially require a follow-up PR where we think through those a bit more. For example, if we want to have types of data between the bindings in Same for inter-stage arguments; do we want to take control of packing these (optimizations possible) into vec4's or do we let the user do that. Can those structs also contain bindings (does it even make sense to), etc. |
* Initial commit for binding proposal, nees some cleanup * Continue at home * Explain global binding model * Explain function binging model * Final suggestion * Add more ups & downs * Add another downside to static * Add another downside to static
This uses the template in #18, it's a small proposal to get the discussion started on how we want to end up doing bindings in this project.
I'm personally not entirely keen on the proposed syntax; at least the
RuntimeArray
type, for example, I wouldn't want to have if we can get rid of it -it's pretty closesly modeled after SPIR-V directly for example and is quite verbose.Things to also think about is how do we access buffers and textures - but that can be another RFC after we agree on this.