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

Safe and convenient resource destruction #288

Closed
3 of 7 tasks
kvark opened this issue Aug 26, 2014 · 11 comments · Fixed by #633
Closed
3 of 7 tasks

Safe and convenient resource destruction #288

kvark opened this issue Aug 26, 2014 · 11 comments · Fixed by #633

Comments

@kvark
Copy link
Member

kvark commented Aug 26, 2014

Would be nice to have our resources self-destructed, though I'm not sure we can really do that with our architecture and potential other device back-ends.

The path seems to be mostly cleared now (at last!). Here are the steps we will take:

  • wrap resources inside handles with Arc<>
  • make Factory to store the references to all created resources
  • implement a sweep() pass for the device/factory. The device may be able to enumerate all the factories and call sweep() on them.
  • add a trait that is implemented by both Rc and Arc. Examples.
  • generically parametrize all the handle types by this trait
  • make sure using the Graphics enforces Rc, while the general way goes with Arc
  • proceed with @csherratt's mutability protection. It's big, so needs its own issue.

PR #633 covers the basics.
Issue #636 will cover Rc usage.

@kvark
Copy link
Member Author

kvark commented Jan 26, 2015

Actually, screw RAII. We really just need linear types for the resources.

Edit: that was a silly idea! Linear types are great, but not for our case.

@bvssvni
Copy link
Contributor

bvssvni commented Jan 26, 2015

Does Rust support linear types?

@kvark
Copy link
Member Author

kvark commented Jan 26, 2015

AFAIK, not yet, unfortunately. I hope someone will post the status of linear types support here.

@brendanzab
Copy link
Contributor

Could we cc the linear type RFC? Would be good to show them a real use case.

@kvark
Copy link
Member Author

kvark commented Jan 27, 2015

@bjz what RFC is that? I only found pre-RFC some time ago and already commented on that.

@brendanzab
Copy link
Contributor

Ohh, I rembered it, but forgot it wasn't up on Github yet.

@kvark
Copy link
Member Author

kvark commented Jan 27, 2015

@bjz I find it weird that linear types are still in pre-RFC stage. For our purpose, it would suffice to just be able to disable going out-of-scope for some type: generate a compile error whenever a variable of this type goes out of scope. The only way to destruct it would be to deconstruct it, and since the type is provided by our library, we can prevent user from deconstructing it by adding any private fields.

@kvark kvark changed the title RAII for single-threaded resources Safe and convenient resource destruction Feb 3, 2015
@kvark
Copy link
Member Author

kvark commented Feb 18, 2015

Here is a use-case shared by @XMPPwocky:

it's nice to be able to, for example, have a loaded mesh be a single struct and then you can Arc that

Unfortunately, linear types will not allow this case. Time to brainstorm it again?

@kvark
Copy link
Member Author

kvark commented Mar 5, 2015

Notes by @amaranth:

You also need to tie resources to command buffers and make sure they live on the CPU until the GPU has processed them
Otherwise you might delete something from the GPU before it gets done using it
Submitting it and then forgetting it isn't enough
You'll drop all the references and generate destroy resource calls that'll happen before the command is actually completed
GL will handle that for you I think so it won't break but apparently Vulkan at least won't
You actually need this for bindless resources in GL too afaik

By @tomaka :

yeah you need a fence for vulcan
yeah, glium already creates fences automatically when you have persistent mapped buffers

@kvark
Copy link
Member Author

kvark commented Mar 5, 2015

I gave it a little think time, and came up with the following plan (extending @csherratt's proposal):

Plan

Every device-associated resource is stored by Arc<T>. Take Shader, for example:

#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Shader<R: Resources>(Arc<<R as Resources>::Shader>, shade::Stage);
  • Fortunately, with Split of referrence batches into Core/non-Core #628 we don't have Copy for the resource-related types, thus using Arc is backwards-compatible.
  • When a Factory creates a resource, it stores a reference to it.
  • When a resource is used by a command buffer, the latter also implicitly stores a reference.
  • When the device finishes presenting a frame, it needs to go through all the factories and clean up those resources, which are not references by anything else (but the factory).

Analysis

It's not clear how much of this code we'll be able to store in the device-independent part. Hopefully - all of it, but that leaves the question open about user-facing delete_X() routines. Perhaps, they will simply not do anything if the resource still have references.

One obvious drawback is performance. I don't know how much Arc would cost us. However, I don't see any other way to provide resource safety either, so we've got to accept the tradeoff. Note: linear types wouldn't help here simply because it would be impossible to use/reference a resource inside a command buffer (at least, without Rc/Arc).

Speaking of the costs, it would be great to allow Rc instead of Arc for single-threaded apps. Does this need HKT to get correctly? Or, perhaps, we can go with a build configuration.

Problems

How do we check that an Arc<T> or Rc<T> is the last reference (and transform it back into T)? At some point long ago (when I was doing exactly the same resource management approach for Claymore, btw) I filed a rust-lang/rust#14908 . Now that I'm looking at it, I see std::rc::is_unique() (here) but nothing like that for Arc. Time for another PR!

@ghost
Copy link

ghost commented Mar 5, 2015

There is another option that I have thought about. It has a different set of trade-offs from what is described above.

  • All resources are immutable by default
  • Modifying a resource means creating a new logical one
  • If a resource is owned (one reference) it may be updated in place, rather then creating a new handle

Like @kvark's suggestion this would probably have to use Arc pointers to track resources.

There are some advantages:

  • A command buffer would only contain valid references, meaning it could be held onto forever (if that is useful). The command buffer would own a copy of the resources.
  • Multiple threads can safely share and mutate data since shared resources will create a resource forks rather then data races
  • Resource writes (or allocations) can be done lazily, when the resource is first used by the renderer. (solving Asynchronous resource creation #570)

Some disadvantages:

  • Cannot reuse a command buffer by modifying a resource between frames (positions for example).

Some differences:

  • The task factories would not own a reference. When an Arc is dropped it enqueues a delete request the device. (This could be applied to @kvark's design)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants