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

Atomic counter for render resource ids and Render to Gpu rename #2895

Closed

Conversation

kurtkuehnert
Copy link
Contributor

Objective

Solution

  • renamed RenderDevice, RenderQueue, RenderInstance, RenderContext to GpuDevice, GpuQueue, GpuInstance, GpuContext
  • replaced the UUIDs in the render resource ids with an atomic u64 counter for better performance and no chance of collision
    • might be replaced by the ids of wgpu if they expose them directly

@inodentry
Copy link
Contributor

inodentry commented Oct 1, 2021

Maybe it would be a good idea to use AtomicUsize instead of AtomicU64, for the sake of platform support/compatibility? (similar to #2827 )

I know there is actually a realistic chance of overflow / running out of unique IDs here, if the counter is 32-bit, but AtomicUsize would still actually be 64-bit on all the 64-bit architectures, so I don't think using it would be a regression, it would just allow bevy to compile on more targets.

On platforms where AtomicU64 exists, using AtomicUsize would be equivalent anyway, but AtomicUsize exists on all platforms (because std requires it).

@bjorn3
Copy link
Contributor

bjorn3 commented Oct 1, 2021

AtomicUsize is 32bit on ARM despite AtomicU64 working on Linux ARM targets, so it is a regression.

@inodentry
Copy link
Contributor

OK, so apparently I was wrong, and the common 32-bit targets (arm, x86, wasm) should all work fine with AtomicU64. Any platforms that would be unsupported, would be very obscure, and bevy likely wouldn't work there anyway. So, never mind. AtomicU64 is the better solution.

@inodentry inodentry added A-Rendering Drawing game state to the screen C-Code-Quality A section of code that is hard to understand or change labels Oct 1, 2021
@inodentry
Copy link
Contributor

I'm not sure if I like this new panicking thing, but that's not my call to make. Is this hot code? How many of these IDs are generated per frame? If it's not, then it's good, I think. If it is, maybe it's not worth the overhead.

BTW, now that we are starting to have a lot of these "global atomic counter for unique ids" all over the bevy codebase (renderer, ecs, ...), I wonder if it would make sense to have some sort of abstraction type for it (maybe in bevy_util)?

Especially if we go with this panicking thing, and if we potentially want more methods / extra functionality for these ID counters in the future, would be nice to not have all of these duplicate / mostly identical impl blocks for each counter type.

@kurtkuehnert
Copy link
Contributor Author

Yeah the panicking is the necessary evil required or we would run into UB on overflow. I don't know how atomic vs UUID affects performance, but we are generating about 70000 per minute (at 60 fps) for the simple 3d scene pipelined example. I am curious to know what that number is for a complex scene.

At least for u64 the atomic counter overflow seems like a none issue.
Here my calculation:
2^64/3600/24/365/300 = 1949808057.85
2 billion ids per frame for an entire year running at 300 fps. But I might be wrong.

The problem with an abstraction is that if we want one static counter per Id type we need to implement a unique method (new) for each. I could move the next_id function to bey_util and use it everywhere which would probably reduce the noise a bit and unify the implementation.
Or we could just have one counter for all of them, but I don't know how well this scales across multiple threads.

I am also not quite sure how and where to panic. We could do it inside of next_id or in the code creating the ids (current impl). As it is nearly impossible to ever happen and totally unrecoverable we might not need to duplicate expect everywhere.

If anyone has a better idea for handling the static counters (a macro maybe?) let me know.

refactored all ids which previously used Uuid or random
@kurtkuehnert
Copy link
Contributor Author

So I have made a derive macro which auto generates the new method with a unique counter per type of id. This method will panic with an error message mentioning the type. As this panic can't be prevented/recovered from anyway I don't see any harm in hiding it inside the macro.

Creating a new unique id is now as simple as:

#[derive(Id, Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct MyId(IdType);

I've also used this macro for every id that still used uuid or random and this was easily applicable.

Let me know what you think. :)

#[proc_macro_derive(Id)]
pub fn derive_id(input: TokenStream) -> TokenStream {
id::derive_id(input)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe make it a regular macro? Something like define_id!(pub SystemId) which defines the struct and add all appropriate derives.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good point! But this makes it also harder to extend those types, right? And a derive trait seems like the more idiomatic choice?

Copy link
Contributor

Choose a reason for hiding this comment

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

There is not really anything that can or should be extended about those ids. Using more than one field or a non-integer field doesn't make sense for an id. Also you can still use impl SystemId {} of you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, that makes sense. I will try it :)
Thank you

Copy link
Contributor Author

Choose a reason for hiding this comment

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

mhh I am not sure whether defining a new type inside a macro is even possible, because my generated struct is not visible across files and thus can't be imported. Any ideas on how to make the compiler know this struct before expanding the macro?

define_id!(pub MyId);


pub struct IdStruct {
    pub vis: Visibility,
    pub ident: Ident,
}

impl Parse for IdStruct {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            vis: input.parse()?,
            ident: input.parse()?,
        })
    }
}

pub fn define_id(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as ItemStruct);

    let struct_name = &ast.ident;
    let visibility = &ast.vis;
    let error_message = format!("The system ran out of unique `{}`s.", struct_name);

    TokenStream::from(quote! {

        #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
        #visibility struct #struct_name(u64);

        impl #struct_name {
            pub fn new() -> Self {
                use std::sync::atomic::{AtomicU64, Ordering};
                static COUNTER: AtomicU64 = AtomicU64::new(0);
                COUNTER
                    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |val| {
                        val.checked_add(1)
                    })
                    .map(Self)
                    .expect(#error_message)
            }
        }
    })
}

pub fn primary() -> Self {
WindowId(Uuid::from_u128(0))
WindowId(0)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is problematic, because the first WindowId generated via new() will be the primary window.
Should we really encode this information into the id itself anyway?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe use !0 as primary window id?

@mockersf
Copy link
Member

Hey @kurtkuehnert , this PR was somehow lost in the new renderer migration. Would you be willing to update it now to target main branch? Sorry for the delay!

@mockersf mockersf added the S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! label Dec 15, 2022
@kurtkuehnert
Copy link
Contributor Author

Hey @mockersf , yes I can try rebasing this PR tomorrow. It will probably be easier to redo it from scratch.

I think it should be split though. Is there still any interest in renaming the types to GPU? I would definitely prefer this naming scheme, but what does @cart think?
The atomic resource counting should be a seperate PR and I am not sure whether this is still a good idea.

@mockersf mockersf removed the S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! label Dec 16, 2022
@cart
Copy link
Member

cart commented Dec 16, 2022

I'm on board for both the renames and the move to atomic counters. I agree that they should be separate PRs. Also a small note: I think if we're going to do an Id derive, it should actually implement some Id trait. Using it for adding normal functions to the base type feels a bit confusing.

The Id trait could have the new function, and it would allow us to add new shared functionality in the future if needed.

@kurtkuehnert
Copy link
Contributor Author

kurtkuehnert commented Dec 16, 2022

Closed in favor of #6968 and #6988.

@kurtkuehnert kurtkuehnert deleted the atomic-counter branch December 21, 2022 21:42
bors bot pushed a commit that referenced this pull request Dec 25, 2022
# Objective

- alternative to #2895 
- as mentioned in #2535 the uuid based ids in the render module should be replaced with atomic-counted ones

## Solution
- instead of generating a random UUID for each render resource, this implementation increases an atomic counter
- this might be replaced by the ids of wgpu if they expose them directly in the future

- I have not benchmarked this solution yet, but this should be slightly faster in theory.
- Bevymark does not seem to be affected much by this change, which is to be expected.

- Nothing of our API has changed, other than that the IDs have lost their IMO rather insignificant documentation.
- Maybe the documentation could be added back into the macro, but this would complicate the code.
bors bot pushed a commit that referenced this pull request Dec 25, 2022
# Objective

- alternative to #2895 
- as mentioned in #2535 the uuid based ids in the render module should be replaced with atomic-counted ones

## Solution
- instead of generating a random UUID for each render resource, this implementation increases an atomic counter
- this might be replaced by the ids of wgpu if they expose them directly in the future

- I have not benchmarked this solution yet, but this should be slightly faster in theory.
- Bevymark does not seem to be affected much by this change, which is to be expected.

- Nothing of our API has changed, other than that the IDs have lost their IMO rather insignificant documentation.
- Maybe the documentation could be added back into the macro, but this would complicate the code.
alradish pushed a commit to alradish/bevy that referenced this pull request Jan 22, 2023
# Objective

- alternative to bevyengine#2895 
- as mentioned in bevyengine#2535 the uuid based ids in the render module should be replaced with atomic-counted ones

## Solution
- instead of generating a random UUID for each render resource, this implementation increases an atomic counter
- this might be replaced by the ids of wgpu if they expose them directly in the future

- I have not benchmarked this solution yet, but this should be slightly faster in theory.
- Bevymark does not seem to be affected much by this change, which is to be expected.

- Nothing of our API has changed, other than that the IDs have lost their IMO rather insignificant documentation.
- Maybe the documentation could be added back into the macro, but this would complicate the code.
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

- alternative to bevyengine#2895 
- as mentioned in bevyengine#2535 the uuid based ids in the render module should be replaced with atomic-counted ones

## Solution
- instead of generating a random UUID for each render resource, this implementation increases an atomic counter
- this might be replaced by the ids of wgpu if they expose them directly in the future

- I have not benchmarked this solution yet, but this should be slightly faster in theory.
- Bevymark does not seem to be affected much by this change, which is to be expected.

- Nothing of our API has changed, other than that the IDs have lost their IMO rather insignificant documentation.
- Maybe the documentation could be added back into the macro, but this would complicate the code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Code-Quality A section of code that is hard to understand or change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants