-
Couldn't load subscription status.
- Fork 1.1k
Retain labels for resources #931
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
Conversation
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.
This is an autogenerated code review.
Checker summary (by rust_clippy):
The tool has found 0 warnings, 1 errors.
|
Thank you for the PR! What was the original motivation for this? What problem are you solving? Just trying to get better error messages? What is the end goal that you see in this direction? Every error on any object carrying around the label strings? |
|
Yes, in the rust community it is pretty easy to get spoiled by really good error messages, I think. And having an error message that pinpoints the whole context is, I think, such a breath of fresh air after years of looking at Showing the label of all the involved resources would put it really far in that direction. I haven't researched what different errors would look like before/after, but if wgpu knows the context of an error, it would be really nice to show it. Especially in debug builds. I would rather have the errors have the ids of the resources than the label itself, there would be less cloning of the label, and most of the work would just be in Error Display time. And it would give the possibility of showing even more context of the resource, if useful. |
|
Thank you for explanation!
This appears to not be possible, since we need the global context in order to grab the labels. What I was thinking, originally, is that: if you see an error that is confusing, you could record an API trace. Then you'd be able to see what descriptors (including the label, but also other useful things) associated with any IDs mentioned in the error messages.
With that other workflow in mind (using an API trace), my concern was whether labels are going to be enough. If we'd then decide to go all the way and basically provide the full descriptor (passed to resource creation), then it would seem inferior to just making the users to record API traces. If, on the other hand, labels is all we need, then I think it's a good improvement, assuming it doesn't add too much code complexity and maintenance load. TL;DR: I think the feature is useful. We should probably just do a |
d09dc86 to
a57f7bc
Compare
|
I removed the Option. I also tried to use the Oh, and the I'm a bit annoyed that I'm again making the error generation more verbose, and adding those labels to all kinds of error variants all over is going to be a bit messy and annoying work.
For complex cases, you may be right, that reaching for other tools might be better. Focusing on making the simple cases crystal clear is certainly enough. There is value to have the simpler error explained in-situ, without trying to reproduce with tools. |
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.
Thinking about this some more, I believe this logic should (once again!) land in wgpu-rs. It can resolve all the errors and replace the IDs (which are totally useless for wgpu-rs consumer) to the labels.
|
Notably, the public methods like |
|
Heads up - this PR is still very much needed, just focused on bringing the logic on wgpu-rs side. |
|
Here is a more concrete proposal. First of all, the goal we are pursuing here is to have more descriptive messages. So in a sense you don't actually need the proper error structs with I think we can do this by just being smart in the error handler. For example, transfer operations in wgpu-rs could use the following code to produce an error string: pub trait PrettyError {
fn fmt_pretty(&self, f: &mut fmt::Formatter<'_>, context: &Context) -> Result<(), fmt::Error>;
}
impl PrettyError for wgc::command::TransferError {
fn fmt_pretty(&self, f: &mut fmt::Formatter<'_>, context: &Context) -> Result<(), fmt::Error> {
use wgc::command::TransferError as Te;
match *self {
Te::InvalidBuffer(id) => {
let name = context.0.buffer_label(id);
f.debug_tuple("InvalidBuffer").field(&name).finish()
}
Te::InvalidTexture(id) => {
let name = context.0.texture_label(id);
f.debug_tuple("InvalidTexture").field(&name).finish()
}
_ => self.fmt(f), // relies on the classy `Debug` implementation
}
}
}As you can see, it skips all the boilerplate for variants that don't contain IDs. It takes care of the ID variants only, and that boilerplate turns out to be pretty light. We'll need that trait implemented for all the errors that we are currently unwrapping with And of course, we'd need those |
|
Ahh, but I need to traverse the errors.. by going through With matching concrete type, I did manage to make it work for a single instance! For that I added the buffer id to And this of course means that the |
a57f7bc to
4fcb113
Compare
|
The latest commits makes this pull just about retaining the labels and related things. The formatting would then happen in in wgpu-rs side, with gfx-rs/wgpu-rs#617 |
wgpu-core/src/device/mod.rs
Outdated
| #[allow(unused_variables)] | ||
| match buffer_guard.get(buffer_id) { | ||
| Ok(buffer) => { | ||
| let buffer = buffer; // silence warning |
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.
aren't we already allowing unused variables?
wgpu-core/src/device/mod.rs
Outdated
| Ok(buffer) => { | ||
| let buffer = buffer; // silence warning | ||
| #[cfg(debug_assertions)] | ||
| return buffer.label.clone(); |
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.
we shouldn't need return
wgpu-core/src/hub.rs
Outdated
|
|
||
| pub(crate) fn label_for_invalid_id(&self, id: I) -> &str { | ||
| let (index, _, _) = id.unzip(); | ||
| match &self.map[index as usize] { |
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.
let's not match on a reference, please
| Vacant, | ||
| Occupied(T, Epoch), | ||
| #[cfg(debug_assertions)] | ||
| Error(Epoch, String), |
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.
I think we can carry String unconditionally here. If it's empty, it doesn't cost much, and the Occupied already takes more size, so adding String to Error variant wouldn't change the total size of the enum.
wgpu-core/src/hub.rs
Outdated
| impl<B: hal::Backend> Access<RenderPipeline<B>> for Device<B> {} | ||
| impl<B: hal::Backend> Access<RenderPipeline<B>> for BindGroup<B> {} | ||
| impl<B: hal::Backend> Access<RenderPipeline<B>> for ComputePipeline<B> {} | ||
| impl<B: hal::Backend> Access<RenderPipeline<B>> for Root {} |
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.
let's not modify this list please. Until we get a stronger mean of protection, we should strive to keep this minimal.
For your purposes, all of these things (RenderPipeline, Buffer, Texture) are accessible in the Device context. So you can lock the device guard for reading first, and then get any of these.
626b0fb to
e5a2e08
Compare
|
Alright, I organized some stuff differently, in attempt to DRY it a bit. I added the It kind of goes around the Token system, I'm not entirely sure how important that is here, but it would of had to make some custom token with all access anyway, as I understood it. |
|
Implemented retaining of the labels for the rest of the types. |
wgpu-core/src/binding_model.rs
Outdated
| pub(crate) bind_group_layout_ids: ArrayVec<[Valid<BindGroupLayoutId>; MAX_BIND_GROUPS]>, | ||
| pub(crate) push_constant_ranges: ArrayVec<[wgt::PushConstantRange; SHADER_STAGE_COUNT]>, | ||
| #[cfg(debug_assertions)] | ||
| pub(crate) label: String, |
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.
I'm wondering... if this is on all objects, and life_guard is in all objects, can we put the label inside it?
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.
At least I can't see it on BindGroupLayout or CommandBuffer. But I guess it could be moved for the ones that do have it?
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.
Yes. It looks like all the things that have life_guard also need the label, so we should move it in.
For others few we can have it aside, like your PR does now.
77cf2b0 to
4acfe98
Compare
|
Moved some labels to |
|
Oh, also, I didn't yet update my -rs side locally, so the latest hasn't seen as much testing, but It Compiles(tm) |
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.
Great work here! I have a few suggestions on how we can make it better, and avoid repeating ourselves in the codebase.
wgpu-core/src/hub.rs
Outdated
| }; | ||
|
|
||
| #[allow(unused_imports)] | ||
| use crate::LabelHelpers; |
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.
same here
wgpu-core/src/hub.rs
Outdated
| } | ||
|
|
||
| pub(crate) fn insert_error(&mut self, id: I) { | ||
| #[allow(unused_variables)] |
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.
why does it need unused variables?
wgpu-core/src/hub.rs
Outdated
| Some(value) | ||
| } | ||
| Element::Error(_) => None, | ||
| Element::Error(_, ..) => None, |
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.
uh, that's a weird thing, why not just Error(..)?
wgpu-core/src/lib.rs
Outdated
| ref_count: Option<RefCount>, | ||
| submission_index: AtomicUsize, | ||
| #[cfg(debug_assertions)] | ||
| pub(crate) label: Label<'static>, |
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.
any reason to not make it a String?
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.
So yeah, since I noticed this Label typed used elsewhere, I figured might as well use it here too. The main benefit is that some of the internal labels don't need to be constructed as String, but can utilize the Cow<'static, str> here, for example in swap_chain_get_current_texture_view the label for the TextureView.
If it made a heap allocation here, it would be tempting to leave it be a empty string (which I think doesn't heap allocate). But it would actually be nice to have the internally created resource to be labeled too.
But this is a tiny thing, I'm fine plain 'ol String as well.
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.
I think we should be able to deal with swap_chain_get_current_texture_view later. For now, just make it something like
if cfg!(debug_assertions) { "swapchain".to_string() } else { String::new() }
wgpu-core/src/hub.rs
Outdated
| fn get_surface_mut(surface: &mut Surface) -> &mut Self::Surface; | ||
| } | ||
|
|
||
| pub trait HasLabel { |
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.
It seems to me that this trait repeats logic that we pretty much have already in the code.
First, the label thing. We have Borrow<RefCount> implemented for all the resources, and all of them just return &life_guard.ref_count. So we should instead probably do something like:
trait Resource {
const TYPE: &'static str;
fn life_guard(&self) -> &LifeGuard;
}We'd then use this trait in the state tracker instead of relying on Borrow<RefCount>.
Secondly, the "type" thing is already specified in Hub::new function when registries are created. We should unite the two, possibly by having something like this:
impl<T: Resource, I: TypedId, F: IdentityHandlerFactory<I>> Registry<T, I, F> {
fn new_resource(backend: Backend, factory: &F) -> Self {
Self::new(backend, factory, T::TYPE)
}
}And then call Registry::new_resource instead of Registry::new for everything that implements Resource.
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.
There are few of them that don't have LifeGuard, quickly glancing at least CommandBuffer and BindGroupLayout.
I'm not sure if there is really a semantic relation with LifeGuard and having a label, or is it just something convenient many of them have and could be coerced to function as something common for all of them? Might just be a naming thing.
The type name makes sense though.
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.
Just convenience, I suppose. I don't feel strongly about this. If you feel that it shouldn't be a part of LifeGuard, feel free to discard that idea.
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.
Ooh, didn't notice that the kind str is already given at Registry::new, should I just ask it from the Storage?
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.
That's one way to reduce duplication, I suppose.
What I suggested above was to use the Resource::TYPE to give it to the storage, instead
4acfe98 to
7f1f7fc
Compare
|
Fixed the stupid mistakes, but did not yet touch the haslabel/lifeguard/typename thing. |
|
I tried the It wasn't obvious to me how the I should probably prepare a PR in |
5d5051d to
93e923e
Compare
|
A toned down the use of |
|
Made the |
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.
Thank you! It's looking great, I think we are almost done.
There is a few more suggestions, plus we'd need this squashed for landing.
wgpu-core/src/lib.rs
Outdated
| pub type Label<'a> = Option<Cow<'a, str>>; | ||
|
|
||
| trait LabelHelpers<'a> { | ||
| fn new_borrowed(s: &'a str) -> Label<'a>; |
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.
since it's used only once, maybe we don't need this helper?
wgpu-core/src/lib.rs
Outdated
| impl LifeGuard { | ||
| fn new() -> Self { | ||
| #[allow(unused_variables)] | ||
| fn new(label: String) -> Self { |
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.
Can we instead do something like this:
pub fn new(label: &str) -> Self {
// something under `cfg(debug_assertions)` inside
}This would allow all the call sites to not care about debug_assertions
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.
I don't understand, how does the callsite here care about debug_assertions?
It could be &str though I guess
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.
Ah, I misread. It will only affect the call sites with static strings, like
#[cfg(debug_assertions)]
life_guard: LifeGuard::new("<device>".to_string()),
#[cfg(not(debug_assertions))]
life_guard: LifeGuard::new(String::new()),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.
Last note, and please squash!
| }, | ||
| life_guard: LifeGuard::new(), | ||
| #[cfg(debug_assertions)] | ||
| life_guard: LifeGuard::new("<SwapChain View>"), |
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.
no need to split that
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.
Oh duh, of course :)
| queue_group, | ||
| life_guard: LifeGuard::new(), | ||
| #[cfg(debug_assertions)] | ||
| life_guard: LifeGuard::new("<device>"), |
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.
no need to split that
| limits, | ||
| life_guard: LifeGuard::new(), | ||
| #[cfg(debug_assertions)] | ||
| life_guard: LifeGuard::new("<Adapter>"), |
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.
same here
| let swap_chain = swap_chain::SwapChain { | ||
| life_guard: LifeGuard::new(), | ||
| #[cfg(debug_assertions)] | ||
| life_guard: LifeGuard::new("<SwapChain>"), |
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.
and here
As a braking api change, adds also label for the error id generation, for labeling invalid ids too. Also adds query methods the label.
7cc02da to
4498f17
Compare
|
squash |
|
Thank you!
bors r+
… On Nov 18, 2020, at 17:16, Mikko Lehtonen ***@***.***> wrote:
squash
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
1036: Stop using Borrow<RefCount> r=scoopr a=kvark **Connections** Follow-up to #931 **Description** Takes advantage of the new `trait Resource` in the state tracker, reducing the code. **Testing** Not tested outside of `cargo test` Co-authored-by: Dzmitry Malyshau <[email protected]>
This is wip, mostly a conversation starter.
I've attempted to have some labels show up in the errors, outside of only creation errors.
Steps to do that include,
Buffer). It is guarded bydebug_assertionsElement::Errorvariant, so that invalid ids retain the label also. Didn't guard it withdebug_assertionsyet, but probably should.buffer_errornow takes the label option.Global::buffer_labelfor returning the label from the struct, or the invalid id.buffer_labelwhen creating the errorwith that (and little bit of fudging to get in to the error state), I can get
instead of
Some of the hub handling etc. is a bit iffy for me, I really have no idea what I'm doing.
The point would be to massage this to the point that there would be a simple steps to follow on how to decorate all the errors with labels to whatever they refer to.
Other ideas I had, included trying to have
impl Debug for Id<BufferId>to print out the label (from the struct, wouldn't work for invalid ids), but in that context I don't think I have access to the Global, so I'm not sure how resolve the id to anything useful. I suppose there could a whole separate singleton storage for id to label maps which could be easier to access in any context. Then the errors could just store the Id, and not have a separate label field.