webgpu-native / webgpu-headers

https://webgpu-native.github.io/webgpu-headers/
BSD 3-Clause "New" or "Revised" License
348 stars 40 forks source link

Ownership of returned strings #143

Open kvark opened 2 years ago

kvark commented 2 years ago

Related to #82

We had a small discussion about wgpuAdapterGetProperties. It needs to return a name and some description. When are these going to be freed?

It seems to me that we should make the storage totally owned by the user. So they'd provide a pointer and the capacity, and we'd just fill out the storage with some chars.

kainino0x commented 2 years ago

I would probably just have them owned by the WGPUAdapter. Though, if doing that, the whole WGPUAdapterProperties could just be owned by the WGPUAdapter and you wouldn't need this writable pointer thing at all.

kvark commented 2 years ago

Ok, right, that's an option. It's just not super convenient for us, because it's a C string, and our adapters don't like holding onto those kind.

kainino0x commented 1 year ago

We discussed a proposal in today's meeting and I think we were pretty happy with it:

typedef struct WGPUAdapterPropertiesImpl* WGPUAdapterProperties;

void wgpuAdapterPropertiesReference(WGPUAdapterProperties);
void wgpuAdapterPropertiesRelease(WGPUAdapterProperties);

WGPUAdapterProperties wgpuAdapterGetProperties(WGPUAdapter);

typedef struct WGPUAdapterPropertiesV1 {
    // (no chained struct here)
    uint32_t vendorID;
    char const * vendorName;
    char const * architecture;
    uint32_t deviceID;
    char const * name;
    char const * driverDescription;
    WGPUAdapterType adapterType;
    WGPUBackendType backendType;
} WGPUAdapterPropertiesV1;

typedef struct WGPUAdapterPropertiesDAWN {
    // example extension
} WGPUAdapterPropertiesDAWN;

// The structs are owned by AdapterProperties, returned by reference, freed when it's freed
WGPUAdapterPropertiesV1 const * wgpuAdapterPropertiesGetV1(WGPUAdapterProperties);
WGPUAdapterPropertiesDAWN const * wgpuAdapterPropertiesGetDAWN(WGPUAdapterProperties);
// Extensions can also return things other than structs
int wgpuAdapterPropertiesGetFavoriteNumber(WGPUAdapterProperties);
WGPUSurface

This is a bit of a different paradigm than we use for input structs. It's a bit more like NXT's original design where input structs were all builders instead of using chained structs. But I like it a lot more than the Vulkan style where you have to call twice - once to ask for the length, and one to get the value. It was raised in the meeting that that style is especially painful when you're returning something that's generated on the fly (like some strings) and you have to generate the exact same string twice or somehow cache it.

It's a little heavyweight with the refcounting, but anyway right now we only have a few cases like this (AdapterProperties, SupportedLimits/EnumerateFeatures, ?) so I think it's fine.

austinEng commented 1 year ago

I think I'm neutral about it because the extra object still feels unnecessary to me:

kainino0x commented 1 year ago

In last thursday's meeting we settled on the following proposal (IIRC - it's been a few days):

struct WGPUGetMappedRangeError {
    char const * errorMessage;
}

void * wgpuBufferGetMappedRange(WGPUBuffer buffer, size_t offset, size_t size, WGPUGetMappedRangeError * Error WGPU_NULLABLE);

void wgpuGetMappedRangeResultFreeMembers(WGPUGetMappedRangeError);

Also considered and rejected:

kainino0x commented 1 year ago

tentatively marking as "has resolution"

Kangz commented 1 year ago

This seems like it could work, it's a bunch of work to implement and makes the C++ wrapper harder to generate, but is otherwise doable. We also need to handle extensible out structures this way.

austinEng commented 1 year ago

What should the behavior be if you write to an output struct multiple times?

WGPUAdapterProperties properties{};

wgpuAdapterGetProperties(adapter, &properties);
wgpuAdapterGetProperties(adapter, &properties);
wgpuAdapterGetProperties(adapter, &properties);
wgpuAdapterGetProperties(adapter, &properties);

Each call will allocate strings for properties that must be freed with wgpuAdapterPropertiesFreeMembers(&properties);

I see two possibilties:

  1. successive calls like this will implicitly perform the call to wgpuAdapterPropertiesFreeMembers for you if the members are non-null - assuming that the developer did not free it.
  2. successive calls like this are the developer's problem. They should be performing a free in-between each.

(1) might be surprising for users of the C API, and would also result in invalid frees if the struct isn't zero initialized. Maybe that's fine though because it's C. High-level language bindings will zero init for you. I believe this approach also requires FreeMembers to null out fields so that we don't double-free.

I think (2) would be OK so long as:

kainino0x commented 1 year ago

I definitely prefer option 2. There does need to be a valid empty state for wgpu::AdapterProperties so you can create it before passing it to GetProperties. But I'm not sure the destructor actually needs to set it to the empty state in C++, since it's about to get freed? I think I don't quite understand the problem.

The chained structs make things a little messy, because even in C++ you have to make sure you don't touch the chain before freeing. Assorted thoughts:

kainino0x commented 6 months ago

Note that with #266, wgpuAdapterPropertiesFreeMembers will be replaced with wgpuAdapterInfoFreeMembers.