GithubHelp home page GithubHelp logo

Comments (15)

DominikWitczakAMD avatar DominikWitczakAMD commented on June 9, 2024 2

Addressed internally. This one took nearly a whole week to complete ..

Will post the latest version early next week.

from anvil.

DominikWitczakAMD avatar DominikWitczakAMD commented on June 9, 2024 1

Good point. Unique pointers it shall be then!

from anvil.

DominikWitczakAMD avatar DominikWitczakAMD commented on June 9, 2024

This is one of the (many) topics that would earn their well deserved place in a documentation, had we had one.

The reason Anvil ultimately ended up with the reversed Instance<-PhysicalDevice<-Device dependency organization was because of the following reasons:

1a. If the app destroys a Vulkan Instance, all Vulkan handles created from that instance become obsolete, and so do their wrapper instances.
1b. It wouldn't take a tremendous effort to modify Anvil so that it would release all living Vulkan object handles while handling app's request to destroy a Vulkan Instance handle. However, that would imply all wrapper instances which are still alive at this point effectively become useless. Can we guarantee all app developers will understand the implications? Don't forget Anvil is about prototyping Vulkan apps after all.
2. An orthogonal aspect is that in Vulkan, an Instance owns all Physical Devices. A Device instance created from a Physical Device, but with the parent Instance handle already destroyed, means bad stuff coming to your app very shortly. Since Anvil is about prototyping, not assuming the developers care about all this dependency stuff, the lowest-hanging fruit was to use the design we currently have.
3. Finally, it proved much more easier to go with the current design, than the way you're suggesting (which I agree would have been much cleaner but perhaps a tad more bitter on the required longer-term maintenance effort). I hadn't spent much time on fleshing it out, but fixing all the cyclic references turned out to be a non-trivial task.

If you feel strong about the current model being misleading, let me know. If the reasons are convincing enough, I'm happy to reconsider my view.

from anvil.

marti4d avatar marti4d commented on June 9, 2024

Based on your points above though, it actually sounds like you're arguing exactly for why my suggestion is actually more inline with what you want!

Examples may help:

Usage With Current Implementation

std::weak_ptr<Anvil::SGPUDevice> createAnvilDevice()
{
    std::shared_ptr<Anvil::Instance> pInstance = Anvil::Instance::create(...);

    std::weak_ptr<Anvil::PhysicalDevice> pPhysicalDevice = Anvil::PhysicalDevice::create(pInstance, ...);

    std::weak_ptr<Anvil::SGPUDevice> pDevice = Anvil::SGPUDevice::create(pPhysicalDevice, ...);
    
    return pDevice;
}

int main(int argc, char** argv) 
{
    std::weak_ptr<Anvil::SGPUDevice> pDevice = createAnvilDevice();
    
    pDevice->something(); // This is invalid!!! The device was already destroyed because I let the 
                          // shared_ptr to the Instance expire at the end of the function, and it cleaned up everything
                          // it owned
    return 0;
}

Now let's look at the same idea using a model where Device has shared ownership of PhysicalDevice, and that has shared ownership of Instance

Usage With Purposed Change

std::shared_ptr<Anvil::SGPUDevice> createAnvilDevice()
{
    std::shared_ptr<Anvil::Instance> pInstance = Anvil::Instance::create(...);
    
    std::shared_ptr<Anvil::PhysicalDevice> pPhysicalDevice = Anvil::PhysicalDevice::create(pInstance, ...);

    std::shared_ptr<Anvil::SGPUDevice> pDevice = Anvil::SGPUDevice::create(pPhysicalDevice, ...);
    
    return pDevice;
}

int main(int argc, char** argv) 
{
    std::shared_ptr<Anvil::SGPUDevice> pDevice = createAnvilDevice();
    
    pDevice->something(); // This is good - We have a strong pointer to the Device keeping the PhysicalDevice alive,
                          // the PhysicalDevice has a strong pointer to the Instance keeping that alive

    // When we return from this function, the shared_ptr will hit '0', which will free the VkDevice and lower the
    // ref count on the PhysicalDevice, which will then hit '0'. That will free the VkPhysicalDevice and lower the
    // ref count on the Instance, which will finally free the VkInstance.
    
    // All of this happens automatically without me, the user, having to do anything.
    
    return 0;
}

from anvil.

marti4d avatar marti4d commented on June 9, 2024

I think your current implementation exists to allow the user to destroy the Anvil::Instance and have Anvil automatically clean up everything downstream of it, but I have to ask - Is that really something that you actually want? Based on your answers above, it sounds like 'no'.

With the behavior the way it is now - allowing the user to delete Anvil::Instance directly and having it clean up everything - then that means that now the user potentially has a ton of weak_ptr<Anvil::xxx> lying all over their code pointing to destroyed objects. Is that really what the user would want or expect?

I tend to think that you probably don't want to ever allow the user to directly delete an Anvil::Instance. It should probably only be deleted automatically when it's not needed anymore, which is when everything that references it is destroyed.

Going back to my example above to also illustrate:

    std::shared_ptr<Anvil::Instance> pInstance = Anvil::Instance::create(...);

    std::weak_ptr<Anvil::PhysicalDevice> pPhysicalDevice = Anvil::PhysicalDevice::create(pInstance, ...);

    std::weak_ptr<Anvil::SGPUDevice> pDevice = Anvil::SGPUDevice::create(pPhysicalDevice, ...);
    
    pInstance.reset();

    // User now has to know that pPhysicalDevice and pDevice are both invalid pointers now

from anvil.

marti4d avatar marti4d commented on June 9, 2024

Also, think in terms of multiples - If you have 10 Device on a single PhysicalDevice, don't you want the PhysicalDevice to have a refcount of 10? And then if the user frees one of the devices, you don't want PhysicalDevice to be destroyed - you just want its refcount to drop to 9.

from anvil.

 avatar commented on June 9, 2024

Not sure if anything should be changed at this point due to backward compatibility, but if you asked me for my preferred model, I'd say no implicit ownership rules at all (apart from the relationships as in the original Vulkan API, e.g a physical device already exists in an instance, you only enumerate it).

I would be perfectly happy with simply adding RAII to Vulkan objects (think unique_ptr). Then the user can model whatever ownership rules that they need with usual language features. If an instance goes out of scope before a device, then oops you have a bug, deal with it.

Full disclosure: my view may be skewed by how Vulkan CTS framework handles this problem.

from anvil.

marti4d avatar marti4d commented on June 9, 2024

@MaciejJesionowskiAMD I would say that pushing ownership management to the end-user is a much lower abstraction then what Anvil seems to aim to provide. Yes, it's efficient in-that you're not using any CPU cycles for reference counting things that may only ever have a 1-to-1 mapping, but I would say that if a user is using Anvil then they've decided they're okay for trading off raw performance for a little bit of luxury. Users who care about nothing-but-performance are always free to just use the Vulkan API directly.

I'm not sure at this point that "backward compatibility" is an argument that should really be made about Anvil - I don't think it's being used very much in the wild right now, and I think that it's understood that it's kind of "in-development" software at this point, and that breaking changes may be made to it.

from anvil.

marti4d avatar marti4d commented on June 9, 2024

After thinking about the lifetime model a bit more, I realize one of the things that would easily improve it in Anvil is just to think about what you would need to do to remove all the Destroy() methods from Instance, PhysicalDevice, and Device.

Basically, you'd have to do these things, which are all good ideas(TM):

  1. Remove all the code that does the whole "recursive destroy" from Instance all the way to Device.
  2. Move the logic to "deregister" the object to the dtor, which is good because RAII
  3. Change the register/deregister calls to store weak_ptr<T> instead of the current shared_ptr<T>. If you don't do this, the dtor will never be called because Device and PhysicalDevice would keep each other alive by mutual shared_ptr
  4. Have the create functions return a shared_ptr to the user. If you don't do that, the object will delete itself immediately, since it will no longer be kept alive by its parent (which is actually how it should work)

from anvil.

DominikWitczakAMD avatar DominikWitczakAMD commented on June 9, 2024

Anvil is going to make a move to raw pointers in a coming update. Apps can still use automated memory management by wrapping the returned ptrs in unique/shared ptrs, but I agree it was a bad design move to rely on automated ptrs on the library level.

from anvil.

marti4d avatar marti4d commented on June 9, 2024

@DominikWitczakAMD But then why wouldn't you at-least return a std::unique_ptr<T>? Bjarne Stroustrup would have a fit if he knew you were returning raw pointers from an API in 2018 😛

from anvil.

marti4d avatar marti4d commented on June 9, 2024

FWIW don't even take my word for it:

C++ Core Guildelines - I.11: Never transfer ownership by a raw pointer (T*) or reference (T&)

from anvil.

DominikWitczakAMD avatar DominikWitczakAMD commented on June 9, 2024

I'm a bit on a fence when it comes to exposing unique pointers. They are a good idea BUT if they were to be used to wrap all objects instantiated by the library, we'd be introducing an implicit requirement of apps using exactly the same implementation of STL as Anvil. This is not a problem in general case, but in situations where someone needs to use the lib built using a different compiler, things can get quickly out of control.

Of course, it's still OK for Anvil to use STL internally and I'm not paranoid enough to start reimplementing all containers and algorithms used by the library just to avoid the dependency ;-) It's really the client<->library interfaces that are my biggest worry here.

from anvil.

marti4d avatar marti4d commented on June 9, 2024

In general you can’t design a c++ API around that use case anyway - c++ has no defined ABI, so many implementation details can and do change between compiler versions. If a user were to do that, unique_ptr would be the least of their troubles.

Hell - if they even link to a different runtime lib than you do, they’re screwed. Msvcrt.lib and msvcrtxx.dll are mutually incompatible.

So already your choices are to provide a different lib for msvc 2013, 2015, 2017 and static/dll versions of each, or make them compile it themselves.

from anvil.

DominikWitczakAMD avatar DominikWitczakAMD commented on June 9, 2024

The latest version addresses this issue. Thanks for reporting.

from anvil.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.