-
Notifications
You must be signed in to change notification settings - Fork 190
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
Separate function-pointer structs between instance and device #734
Conversation
97e731c
to
01eada4
Compare
Having large numbers of types with one of two suffices feels a bit like we're reimplementing the module system. A ZST purely for the sake of the name constant also feels a bit weird. Possible alternative approaches:
|
I also wonder if we should scrap the panicing stub functions and fail loaders if any function doesn't load, since that's already bitten us pretty badly by making the errors subtle (and confused users/contributors). I guess that's tricky for versioned stuff. |
We already have modules but only for the sake of
I've been thinking the same thing for the suggested ZST above, but then would wonder if we could keep the struct name "private" in favour of always referring to it as Unfortunately these so-called pub struct CalibratedTimestamps;
impl CalibratedTimestamps {
// inherent associated types are unstable
// see issue #8995 <https://github.com/rust-lang/rust/issues/8995> for more information
pub type Device = CalibratedTimestampsDevice;
pub type Instance = Instance;
}
The downside here is that it forces reloading instance functions, if you already have them or only care about device functions. OTOH we can find a middle-ground here a chain of constructors either loads Additionally we could keep a mode or constructor where the device functions are loaded via
That'd be nice and generic, if a bit harder to manage for the folks that import structs rather than modules, but nothing that can't be fixed. Perhaps worth investigating given that associated types on ZSTs are the "clearer" (?) variant of this but not yet stabilized. In this case I'd name them
Doing the |
We should definitely have a mode where this is caught up-front, yes. Just thinking about how we should represent |
01eada4
to
7b1292b
Compare
…ctions This is a minimal, semver-compatible backport of #734 to the `0.37-stable` branch, warning Ash users of the problem outlined below while the issue is properly being solved in the next breaking Ash release (by separating `Instance` and `Device` functions in the generator to avert this problem entirely while also always providing optimal `Device`-specific functions for extension wrappers that are currently already loading _everything_ via `Instance` to forgo the problem). As discovered and detailed in #727 a few extension wrappers were loading and calling `Instance` functions via `Device` and `get_device_proc_addr()` which is [defined] to only return non-`NULL` function pointers for `Device` functions. Those wrapper functions will always call into Ash's panicking NULL-stub functions as the desired `Instance` function could not be loaded. Deprecate the `new()` functions for extension wrappers that were doing this, while pointing the reader to `new_from_instance()` and explaining in the docs what function will always `panic!()` when the struct was loaded using `new()` instead. This function always takes a raw `vk::Device` directly to fill `handle` (rather than `ash::Device` to retrieve `handle()` from), allowing users to pass `vk::Device::null()` when they do intend to load this extension wrapper just for calling the `Instance` function. [defined]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetDeviceProcAddr.html#_description
…ctions This is a minimal, semver-compatible backport of #734 to the `0.37-stable` branch, warning Ash users of the problem outlined below while the issue is properly being solved in the next breaking Ash release (by separating `Instance` and `Device` functions in the generator to avert this problem entirely while also always providing optimal `Device`-specific functions for extension wrappers that are currently already loading _everything_ via `Instance` to forgo the problem). As discovered and detailed in #727 a few extension wrappers were loading and calling `Instance` functions via `Device` and `get_device_proc_addr()` which is [defined] to only return non-`NULL` function pointers for `Device` functions. Those wrapper functions will always call into Ash's panicking NULL-stub functions as the desired `Instance` function could not be loaded. Deprecate the `new()` functions for extension wrappers that were doing this, while pointing the reader to `new_from_instance()` and explaining in the docs what function will always `panic!()` when the struct was loaded using `new()` instead. This function always takes a raw `vk::Device` directly to fill `handle` (rather than `ash::Device` to retrieve `handle()` from), allowing users to pass `vk::Device::null()` when they do intend to load this extension wrapper just for calling the `Instance` function. [defined]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetDeviceProcAddr.html#_description
…ctions This is a minimal, semver-compatible backport of #734 to the `0.37-stable` branch, warning Ash users of the problem outlined below while the issue is properly being solved in the next breaking Ash release (by separating `Instance` and `Device` functions in the generator to avert this problem entirely while also always providing optimal `Device`-specific functions for extension wrappers that are currently already loading _everything_ via `Instance` to forgo the problem). As discovered and detailed in #727 a few extension wrappers were loading and calling `Instance` functions via `Device` and `get_device_proc_addr()` which is [defined] to only return non-`NULL` function pointers for `Device` functions. Those wrapper functions will always call into Ash's panicking NULL-stub functions as the desired `Instance` function could not be loaded. Deprecate the `new()` functions for extension wrappers that were doing this, while pointing the reader to `new_from_instance()` and explaining in the docs what function will always `panic!()` when the struct was loaded using `new()` instead. This function always takes a raw `vk::Device` directly to fill `handle` (rather than `ash::Device` to retrieve `handle()` from), allowing users to pass `vk::Device::null()` when they do intend to load this extension wrapper just for calling the `Instance` function. [defined]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetDeviceProcAddr.html#_description
…`Instance` functions (#754) extensions: Provide `new_from_instance()` fallback for `Instance` functions This is a minimal, semver-compatible backport of #734 to the `0.37-stable` branch, warning Ash users of the problem outlined below while the issue is properly being solved in the next breaking Ash release (by separating `Instance` and `Device` functions in the generator to avert this problem entirely while also always providing optimal `Device`-specific functions for extension wrappers that are currently already loading _everything_ via `Instance` to forgo the problem). As discovered and detailed in #727 a few extension wrappers were loading and calling `Instance` functions via `Device` and `get_device_proc_addr()` which is [defined] to only return non-`NULL` function pointers for `Device` functions. Those wrapper functions will always call into Ash's panicking NULL-stub functions as the desired `Instance` function could not be loaded. Deprecate the `new()` functions for extension wrappers that were doing this, while pointing the reader to `new_from_instance()` and explaining in the docs what function will always `panic!()` when the struct was loaded using `new()` instead. This function always takes a raw `vk::Device` directly to fill `handle` (rather than `ash::Device` to retrieve `handle()` from), allowing users to pass `vk::Device::null()` when they do intend to load this extension wrapper just for calling the `Instance` function. [defined]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkGetDeviceProcAddr.html#_description
@Ralith WDYT? This is one of the few PRs remaining before cutting |
This needs a proper think, will try to get back to you on it later today. Thanks for driving everything forwards! |
Aw.
That's what I was thinking. Something like: struct SomeExt {
instance_handle: vk::Instance,
instance_fn: InstanceFn,
device: Option<(vk::Device, DeviceFn)>,
}
impl SomeExt {
fn load(instance: &Instance, device: &Device) -> Self { ... }
fn load_instance(instance: &Instance) -> Self { ... }
fn load_device(&mut self, device: &Device) { ... }
} This has the drawback of forcing dynamic checks on every device fn, of course, so I'm not sold on it. But maybe if the
That was my thinking. We'd set a convention of always importing at most the modules, never their contents, similar to idiomatic use of e.g.
What's the difference? An item's an item, as far as scoping is concerned.
Makes sense to me.
It's not obvious to me that ZSTs are clearer in this application. Generally we use types to describe values, and in that case there's no values, so why have a type at all?
Yeah, I think that's probably the wrong way to go. It makes it difficult to survey the contents of an extension, and there's no single graceful place to hang the |
It seems to be more common to import items in scope rather than strategically importing modules (and this may also be driven by how use ash::extensions::khr::swapchain::{NAME, DeviceFn}; And now have a meaningless
Given the above, inherent associated types cannot be imported in scope yet (e.g. |
Re. "how use std::collections::hash_map::Collection;
use std::collections::hash_set::Collection;
use std::collections::vec::Collection; (Disregard the double because all the modules have identically-named items. |
Contrast
This doesn't exist because there's necessarily a sensible "main" type for any collection.
Yeah, there's already some pressure for this today for associated constants, which would help bitfields and similar patterns feel first class. |
Besides (Side note: I did find the various Just playing devil's advocate here 😉, making sure we properly think through the opportunities and drawbacks this approach has - even if it likely is the only way to implement the split right now given a lack of stabilized inherent associated types. How should we do this for the generated code, by the way? I was thinking to just have a bunch of in-line: mod swapchain {
use super::*; // Import global scope, or reference everything with `super::`.
const NAME = ...;
struct DeviceFn {
...
}
struct InstanceFn {
...
}
} but haven't yet thought about how to structure the
Agreed, if the users writes |
I've named them explicitly a number of times (e.g.
The point was that since there's two of them, neither has inherent primacy.
It's appealing to group everything defined by an extension inside the same module. This minimizes reference-chasing and makes it easy to inventory, and maps closely to idiomatic Rust. Might be nice to isolate the function pointer definitions a little since usually folks aren't looking for those, but e.g. having miscellaneous struct definitions right there alongside the new methods sounds nice. Perhaps Unfortunate that this proposal transposes the current organization of the generator output, though... seems like that might be churny to implement. |
Agreed.
Indeed, either has it, but it's not about the primacy between these two elements within the module.
Main problem here is that PFNs are used outside of and across extensions as well; that idea might fly but we have to be more careful where the first definition of the PFN is going to be (currently it is all over the place) and adjust the generator to import it from the right place. We'll need to do Good point on the churny-ness: I've already received comments about |
Do we have any open questions here? |
@Ralith I think I was going to Still unsure what the best approach / naming would be. |
I stand by my preference for module-per-extension as discussed, but I don't feel strongly enough to block things. |
Yeah I think that is totally reasonable, just hope the rename from Still okay to keep |
I think it's mitigated by a few things: most people are probably mostly using core functions, and extension struct types don't need to be named very often. Hard to see how we'd get out of this hole without renaming them somehow, and with a small number of references, the cost of having to make a change at all dominates the specifics of the change.
I don't really like the |
My plan was still to have the separate |
6b07904
to
60b5179
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
60b5179
to
a994e4e
Compare
7252c03
to
e0c1c76
Compare
e0c1c76
to
03ad1f4
Compare
Fixed up the CI failures, and implemented the "create modules for vendor prefixes" task. This required a hack for extensions whose names begin with a number (e.g. |
b06c835
to
e3f64d8
Compare
e3f64d8
to
1ac974d
Compare
…ing `vk::Device` argument When introducing this new `VK_KHR_calibrated_timestamps` extension based on `VK_EXT_calibrated_timestamps` in #890 in parallel to finalizing and merging the `Instance`/`Device` separation in #734, I seem to have missed an opportunity to use the newly available `self.handle` for this extension from `struct Device`, instead leaving an unnecessary `device: vk::Device` argument in place.
…ing `vk::Device` argument (#898) When introducing this new `VK_KHR_calibrated_timestamps` extension based on `VK_EXT_calibrated_timestamps` in #890 in parallel to finalizing and merging the `Instance`/`Device` separation in #734, I seem to have missed an opportunity to use the newly available `self.handle` for this extension from `struct Device`, instead leaving an unnecessary `device: vk::Device` argument in place.
Depends on #733Fixes #727
Not only is it more efficient to load specialized device functions via
get_device_proc_addr()
instead ofget_instance_proc_addr()
(saves a device-based jump in the ICD), loading instance functions viaget_device_proc_addr()
(which was the case inVK_KHR_swapchain
,VK_KHR_device_group
andVK_EXT_full_screen_exclusive
) always results in aNULL
causing their instance functions panic no matter what.Low-level
*Fn
structs are separated out between instance and device pointers for every extension that contains both, forcing users (typically high-level extension writers) to explicitly account for this.