这是indexloc提供的服务,不要输入任何密码
Skip to content

Conversation

@Kangz
Copy link
Contributor

@Kangz Kangz commented Feb 20, 2018

No description provided.

// Buffer

callback WebGPUMappedMemorySuccessCallback = void (ArrayBuffer);
callback WebGPUMappedMemoryErrorCallback = void (DOMString);
Copy link

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

bool isPending();
ArrayBuffer getPointer();

void then(WebGPUMappedMemorySuccessCallback success,
Copy link

Choose a reason for hiding this comment

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

I can't find any references to a standard, but I believe then method of a thenable should return a new Promise.

Copy link
Contributor

Choose a reason for hiding this comment

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

On this point, if the caller only uses the pollable interface, could we confirm the creation of a Promise is avoided entirely?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dmikis I don't know for then-ables, but it looks to be the case for Promises (I'm sure you know already ^^) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

@grovesNL correct, the Promise wouldn't get created. The only overhead would be some unused members of WebGPUMappedMemory.

Copy link
Contributor

Choose a reason for hiding this comment

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

@dmikis While I don't think a Thenable strictly speaking has to return anything in particular, you're right that it would be useful if it did.

callback WebGPUMappedMemoryErrorCallback = void (DOMString);
interface WebGPUMappedMemory {
bool isPending();
ArrayBuffer getPointer();
Copy link

@dmikis dmikis Feb 21, 2018

Choose a reason for hiding this comment

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

ArrayBuffer? (i.e., nullable)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

- The buffer must have been created with the `WebGPUBufferUsage.TRANSFER_DST` usage flag.
- `offset + data.length` must not overflow and be at most the size of the buffer.
- Depending on the design of memory barriers, the buffer must be, or allowed to be in the `WebGPUBufferUsage.TRANSFER_DST` usage.
- In particuler the buffer must not be currently mapped.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: particular

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

# Buffer operations

This describes operations that are done on `WebGPUBuffer` objects directly and are not buffered inside a `WebGPUCommandBuffer`.
The two primitives we need to support are the CPU writing data inside the buffer for use by the GPU (uplaod) and the CPU reading data produced by the GPU (readback).
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: upload

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


### GC pressure

The `WebGPUMappedMemory` design makes each mapped region create two grabage collected objects. This could lead to some GC pressure.
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: garbage

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

### Side effects between mapped memory regions

What happens when `WebGPUMappedMemory` object's region in the buffer overlap?
Are write from one visiable from the other?
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: visible

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

bool isPending();
ArrayBuffer getPointer();

void then(WebGPUMappedMemorySuccessCallback success,
Copy link
Contributor

Choose a reason for hiding this comment

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

On this point, if the caller only uses the pollable interface, could we confirm the creation of a Promise is avoided entirely?

}
```

This operation acts as if it was done after all previous "device-level" commands and before all subsequent "device-level" commands.
Copy link

Choose a reason for hiding this comment

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

How does it know what commands are "previous" and what are "subsequent"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here previous and subsequent refer to the order of the calls in the Javascript program order.

Copy link

Choose a reason for hiding this comment

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

I think that's not very well-defined if we'll allow submission of commands from multiple workers.

IIRC, Vulkan does such "in-place" uploads via command buffers. Doing them the same way would make it a bit clearer what commands a before the upload and what are after (IIUC, not with multiple queues, though).

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we associate this with a particular point in the queue, perhaps by passing the queue as an argument, or making this a member function on the queue? It would make it clear what data would get clobbered and what wouldn't. Doing this would also solve the problem of what happens when this is called during the time when a buffer is mapped.

We've heard from Metal developers that submitting commands in an order different than they were recorded is an important use-case.

# Buffer operations

This describes operations that are done on `WebGPUBuffer` objects directly and are not buffered inside a `WebGPUCommandBuffer`.
The two primitives we need to support are the CPU writing data inside the buffer for use by the GPU (uplaod) and the CPU reading data produced by the GPU (readback).
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: "uplaod"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

This mechanism has to be asynchronous to ensure the GPU is done using the buffer before the application can look into the ArrayBuffer.
Otherwise on implementation where the ArrayBuffer is directly a pointer to the buffer memory, data races between the CPU and the GPU could occur.

We want the status of a map operation to act as both a promise, and something that's pollable as there advantages to both.
Copy link
Contributor

Choose a reason for hiding this comment

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

"as there are advantages"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Otherwise on implementation where the ArrayBuffer is directly a pointer to the buffer memory, data races between the CPU and the GPU could occur.

We want the status of a map operation to act as both a promise, and something that's pollable as there advantages to both.
`WebGPUMappedMemory` is an object that is then-able like a Promise but is pollable at the same time.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit - codify the terms: then-able, Promise

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


These operations return new `WebGPUMappedMemory` objects representing the current range of the buffer for writing or mapping.
The results are initialized in the "pending" state and transition at Javascript task boundary to the "available" state when the implementation can determine the GPU is done using the buffer.
The mapping operations put the buffer in the mapped state, when a buffer is in the mapped state, only the mapping and unmapping operations on it are allowed.
Copy link
Contributor

Choose a reason for hiding this comment

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

sentence is not clear, needs to be rewritten

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

The results are initialized in the "pending" state and transition at Javascript task boundary to the "available" state when the implementation can determine the GPU is done using the buffer.
The mapping operations put the buffer in the mapped state, when a buffer is in the mapped state, only the mapping and unmapping operations on it are allowed.
In particular a mapped buffer cannot be used in a `WebGPUCommandBuffer` given to `WebGPUQueue.submit`.
The following must be true or a validation error occurs for mapWrite (resp. mapRead):
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: codify method names

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

`isPending` return true if the object is in the pending state, false otherwise.
`getPointer` returns an ArrayBuffer representing the buffer data if the object is in the available state, null otherwise.

`WebGPUMappedMemory` is acts like a promise as in that it is then-able:
Copy link
Contributor

Choose a reason for hiding this comment

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

needs to be re-phrased

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


## Immediate data upload

Buffer mapping is the path with the least number of copies but it is often useful to upload data to a buffer *right now*, if only for debugging.
Copy link
Contributor

Choose a reason for hiding this comment

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

We often get this argument voiced (the need for immediate upload), but I don't think the API needs special handling for this case, as long as the sync behavior is achievable in general.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this proposal, mapping for writing is asynchronous too, so there is no way to have sync behavior without Buffer.setSubData. The alternative would be a ArrayBuffer? Buffer.mapWriteSync(u32 offset, u32 size); but I'm not sure this is better. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Mapping (local) mappable memory should be able to be synchronous if the mappable allocations are actually IPC shmem. Does this design run into issues with how we enqueue copies? It seems iffy, but ideal if it can work reliably synchronously.

```

This operation acts as if it was done after all previous "device-level" commands and before all subsequent "device-level" commands.
"Device level" commands are all commands not buffered in a `WebGPUCommandBuffer`, and include `WebGPUQueue.submit`.
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of inventing a new "Device level" term, we could just say that the operation acts as if it's done after all device/queue level commands.

Copy link
Contributor Author

@Kangz Kangz Feb 21, 2018

Choose a reason for hiding this comment

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

It is also done after previous Buffer.setSubData() calls and before subsquent ones. We need a name for these operations as they might not all be on the device and the queue (contrary to Vulkan). Do have suggestion on how to call this?

Copy link
Contributor

Choose a reason for hiding this comment

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

or we can move setSubData to the device, calling it like device.setSubData(buffer, data), which IMHO would make more sense from the implementation point of view (wait + map + unmap)


- The buffer must have been created with the `WebGPUBufferUsage.TRANSFER_DST` usage flag.
- `offset + data.length` must not overflow and be at most the size of the buffer.
- Depending on the design of memory barriers, the buffer must be, or allowed to be in the `WebGPUBufferUsage.TRANSFER_DST` usage.
Copy link
Contributor

Choose a reason for hiding this comment

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

similar concern here about the mismatch of usage versus state concepts. Are they merged in NXT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No but they are often confused there's the creation usage and the usage "state" (there's other state like the mapped state). So we never use the word "state" for usage, only "usage".

A resource is in a usage means it has that usage "state" value
A resource has a usage means it was created with this usage requested by the application.

We obviously we didn't spend enough time figuring out the naming of things so this is a bit confusing. I think the intent is clear here, what do you think of deferring the re-wording to when we decide what our memory barriers look like (if any)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I do see a bit of elegance in merging usages and access flags, but it's also confusing to differentiate is/has...

Copy link
Contributor

@RafaelCintron RafaelCintron Feb 23, 2018

Choose a reason for hiding this comment

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

I agree with @kvark. The distinction confused me as well.

Can buffers only be in more than one "is in" usage at a time? If so, then that usage should be a separate type that is not a bitfield. That makes it clear to the person calling functions that take "is in" usage that only one of them is valid.

If the set of "has a" usages contains members that are not valid for "is in" usage or vice versa, then that's also a reason to have separate types.

The description of "has a" vs. "is in" usages would be great explanatory text for the spec.


### GC pressure

The `WebGPUMappedMemory` design makes each mapped region create two grabage collected objects. This could lead to some GC pressure.
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: "grabage"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@Kangz
Copy link
Contributor Author

Kangz commented Feb 21, 2018

Thanks everyone for the comments, addressed most of them. Are there high-level concerns in addition to @kvark's comment on setSubData not being necessary?

partial interface WebGPUBuffer {
void setSubData(ArrayBuffer data, u32 offset);
}
```
Copy link
Contributor

Choose a reason for hiding this comment

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

Can a buffer be TRANSFER_DST as well as MAP_WRITE? If so, what happens if web developers call setSubDatawhile a buffer is mapped? What happens when a developer calls setSubData, then mapRead, then unmap? Will the ArrayBuffer returned in the WebGPUMappedMemory contain the contents of what the developer passed to setSubData?

To keep things simple for MVP, might be good to have buffers be either MAP_* or TRANSFER_* but not both.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A buffer cannot be TRANSFER_DST and MAP_WRITE because they are both writable usages. This is similar to the D3D12 state transition that doesn't allow two writable states at the same time.

While the buffer is mapped everything is disallowed but mapWrite, mapRead and unmap which means setSubData is disallowed (and you can't transition away from MAP_READ/WRITE either).

You really need to allow buffers that are TRANSFER_DST | MAP_READ because that's the only way to get data back from the GPU. But the restrictions on mapping and usage state make it so you can't have both at the same time.


It isn't clear yet what happens when a buffer gets garbage collected while it is mapped.
The simple answer is that the `WebGPUMappedMemory` objects get invalidated but that would allow the application to discover when the GC runs.

Copy link
Contributor

Choose a reason for hiding this comment

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

To avoid GC discoverability, the implementation of WebGPUMappedMemory should keep its corresponding WebGPUBuffer alive.

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 ony way, and the application can make sure the WebGPUMappedMemory don't keep the WebGPUBuffer alive by calling WebGPUBuffer.unmap which will invalidate the WebGPUMappedMemory objects and lose the references to the buffer.

Do you think there's something we could do along the lines of allowing the buffer and the expensive BAR0 allocation to disappear but making the buffer replace the ArrayBuffer for the WebGPUMappedMemory objects with a CPU copy of their content when the buffer was GCed? I'm not sure if the pointer in an ArrayBuffer is mutable.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure the pointer isn't mutable.

What happens when `WebGPUMappedMemory` object's region in the buffer overlap?
Are write from one visible from the other?
If they are, maybe `WebGPUMappedMemory.getPointer` should return an `ArrayBufferView` instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

One of the goals of mapping subregions of the buffer is to avoid having to ask for the whole thing from the underlying API. Since you can ask for the original ArrayBuffer from an ArrayBufferView (via the buffer attribute), returning an ArrayBufferView from getPointer seems to undermine that goal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks I didn't know that. This might mean that we need to require mapped ranges to be disjoint but that's a pretty big restriction. @jdashg's idea of more explicit "client buffers" become more attractive.

The content of `data` is only read during the call and can be modified by the application afterwards.
The following must be true or a validation error occurs:

- The buffer must have been created with the `WebGPUBufferUsage.TRANSFER_DST` usage flag.
Copy link
Contributor

Choose a reason for hiding this comment

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

Your document talks about MAP_READ, MAP_WRITE and TRANSFER_DST. What APIs apply to TRANSFER_SRC buffers? Is there a WebGPUBuffer.getSubData?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any WebGPUCommandBuffer copy operation that reads from a buffer requires TRANSFER_SRC (provided we have explicit barriers). In this proposal there is no plan for WebGPUBuffer.getSubData. The reason why WebGPUBuffer.setSubData requires TRANSFER_DST is because internally the implementation will always put the data in a staging buffer and enqueue a buffer to buffer copy operation.

Copy link
Contributor

@RafaelCintron RafaelCintron Feb 23, 2018

Choose a reason for hiding this comment

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

OK.

- `success` is called with the content of the memory as an argument.

If `success` hasn't been called when the WebGPUMappedMemory gets invalidated (meaning the object is still in the pending state), `error` is called instead. When `WebGPUMappedMemory` goes from the available state to the invalidated state, the `ArrayBuffer` for its content gets neutered. The return value of `then` acts like the return value of `Promise.then`.

Copy link
Contributor

Choose a reason for hiding this comment

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

Using promises for buffers that change state is tricky because you can't change the state of a promise once it has been resolved. A promise can have multiple .then callbacks hooked up. If the developer keeps the promise returned from WebGPUMappedMemory.then in a local variable and .then's the local hours later, the browser must callback the new .then with the same ArrayBuffer it did originally.

What is the validity lifetime of the ArrayBuffers given to promise revolves in the multiple .then case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I now remember we mentioned something like that in a meeting. A potential solution was for .then to act like a Promise<void> and just signal that the buffer is now in the available state? What do you think of something like this?

Copy link
Contributor

@kainino0x kainino0x Feb 23, 2018

Choose a reason for hiding this comment

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

It can also behave like Promise<WebGPUMappedMemory> (it just returns itself). Then getPointer can still return null (or neutered AB, or whatever).

Copy link
Contributor

Choose a reason for hiding this comment

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

Although if getPointer is going to return a neutered AB, there's no reason the Promise can't resolve to a neutered AB as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

My questions was more about what "validity" means in the context of buffers and the event loop. For readback, if multiple pieces of code .then the promise, will the ArrayBuffer only be non-neutered when the first .then is invoked or when all of them are invoked? When will WebGPUMappedMemory go from the available state to the invalidated state?

Copy link
Contributor

Choose a reason for hiding this comment

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

It never happens automatically, only on unmap.

Copy link
Contributor

@RafaelCintron RafaelCintron Mar 7, 2018

Choose a reason for hiding this comment

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

Thank you for the clarification, @kainino0x . That means if the developer calls .then on the WebGPUMappedMemory object multiple times and unmaps it on the first callback, all of the subsequent callbacks would get a neutered ArrayBuffer. I presume this is necessary because we want to unmap the buffer as soon as we can to keep the pipeline going. I guess this is a case of "Too bad, so sad" for the subsequent callbacks. :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps we could sidestep this problem by using callbacks directly. There is no timing problem because the earliest the callback could be run is at the next micro task boundary. It also solves the problem of code reusability (due to Promises not being able to be re-used once they are fulfilled).

Copy link
Contributor

Choose a reason for hiding this comment

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

I think there would be pushback from the TAG, but it's a possibility.
(I don't know that they would like our "thenable" thing any better.)


- The `WebGPUMappedMemory` goes in the available state.
- If the `WebGPUMappedMemory` was created via `WebGPUBuffer.mapWrite`, its content is cleared to 0.
- `success` is called with the content of the memory as an argument.
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the rationale for having the Promise give you an ArrayBuffer in the writing (mapWrite or setSubData) case? When would I want my own data given back to me?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The mapWrite ArrayBuffer is where you write the data and it is made available to the buffer when unmap is called. I'll edit the doc.

Copy link
Contributor

@RafaelCintron RafaelCintron Feb 23, 2018

Choose a reason for hiding this comment

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

Apologies if I didn't make myself clear in what I was asking.

Suppose I call mapWrite, .then the promise and return control back to Javascript. When the promise resolves, what am I meant to do with the ArrayBuffer I get in the promise? Will that be the same ArrayBuffer as the one returned from WebGPUMappedMemory.getPointer?

Similar question applies to the readback case. Will the Promise ArrayBuffer contain the same contents as the ArrayBuffer returned from getPointer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes both the promise's ArrayBuffer and the one from getPointer would be the same. @kainino0x suggested above that the promise could be a Promise<WebGPUMappedMemory> where the memory object could return itself. Maybe this would make it more clear what happens.

```

These operations return new `WebGPUMappedMemory` objects representing the current range of the buffer for writing or mapping.
The results are initialized in the "pending" state and transition at Javascript task boundary to the "available" state when the implementation can determine the GPU is done using the buffer.
Copy link
Contributor

Choose a reason for hiding this comment

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

For this or a subsequent pull request, I suggest making a state diagram that illustrates all of the states a WebGPUBuffer can be in (available, pending, mapped, unmapped) along with the operations that move it between states (calling unmap, calling map, GPU writes finished, JavaScript task boundary, etc)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See the state diagrams in this doc. I could include them here but they assume D3D12 style memory barriers.


`WebGPUMappedMemory` is an object representing a mapped region of a buffer that's both pollable and promise-like.

It can be in one of three states: pending, available and invalidated.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: On line 52, you mention that calling unmap puts the buffer into the "unmapped" state. However, here, "unmapped" is not listed as one of the three states.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are the states for the WebGPUMappedMemory. Mapped vs. unmapped is a WebGPUBuffer state. See the state diagrams linked in the other comment.


These operations return new `WebGPUMappedMemory` objects representing the current range of the buffer for writing or mapping.
The results are initialized in the "pending" state and transition at Javascript task boundary to the "available" state when the implementation can determine the GPU is done using the buffer.
Calling `mapRead` or `mapWrite` puts the buffer in the mapped state.
Copy link
Contributor

Choose a reason for hiding this comment

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

What? mapRead and mapWrite return the WebGPUMappedMemory object, and line 34 says it starts in the pending state. Does it start in the pending or state or the mapped state?

Copy link
Contributor

Choose a reason for hiding this comment

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

See #49 (comment) . mapRead/mapWrite move the WebGPUBuffer object from Unmapped to Mapped. They return a new WebGPUmappedMemory object which starts in the Pending state.


```
partial interface WebGPUBuffer {
void unmap();
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't seem to appear in the idl file.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this on the buffer and not on the WebGPUMappedMemory object? It looks like the API is designed to support many small mappings of a single resource, yet it looks like you can't remove just a single mapping and instead have to blow them all away.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Double checked, this correctly appears in the IDL or am I missing something?

Our original idea had both WebGPUBuffer and WebGPUMappedMemory have unmap we thought it would be simpler to only have the WebGPUBuffer one in this proposal but you are right, WebGPUMappedMemory.unmap would allow removing a single mapping which is useful if we don't allow overlapping mappings.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think WebGPUMappedMemory.unmap is needed if we do allow overlapping mappings.

The following must be true or a validation error occurs for `mapWrite` (resp. `mapRead`):

- The buffer must have been created with the `WebGPUBufferUsage.MAP_WRITE` (resp. `WebGPUBufferUsage.MAP_READ`) usage.
- `offset + size` must not overflow and be at most the size of the buffer
Copy link
Contributor

Choose a reason for hiding this comment

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

What about overlaps of previous mappings of the same resource?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is still unresolved; we started discussing it here: #49 (comment), at least.

};
```

This acts like a `Promise<ArrayBuffer>.then` that is resolved on the Javascript task boundary in which the implementation detects the GPU is done with the buffer.
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens when you mix these APIs? Like if you call map inside the .then() block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'm not sure what the problem is since WebGPUBuffer.map will return to you a new WebGPUMappedMemory that cannot be resolved before the end of this task.

Copy link
Contributor

Choose a reason for hiding this comment

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

The semantics here ought to be very similar to Promise's. @litherum, if you thought of a particular construction that might be ambiguous, can you provide a simple example?


```
partial interface WebGPUMappedMemory {
Promise then(WebGPUMappedMemorySuccessCallback success,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we make it extend Promise or something? Instead of copying a signature from Promise

Copy link
Contributor

Choose a reason for hiding this comment

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

I have been thinking about this. My first pass at the idea was that it may not be possible, because Promise is not an interface. But I didn't try running it through a validator.

Copy link
Contributor

Choose a reason for hiding this comment

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

interface WebGPUMappedMemory : Promise<void> {

Line 3: at 'Promise': expected identifier

My 30 second exploration seems to indicate it doesn't work.

### Persistently mapped buffer

Persistently mapped buffer are when the result of mapping the buffer can be kept by the application while the buffer is in use by the GPU.
We didn't find a way to have persistently mapped buffers and at the same time keep things data race free between the CPU and GPU.
Copy link
Contributor

Choose a reason for hiding this comment

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

If this section will be committed, you should state that "we" is "The WebGPU Community Group"


### NXT's MapReadAsync(callback);

Not a pollable interface.
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably is worth describing why a pollable interface is a requirement (something I'm still fuzzy on myself)

Copy link
Contributor

Choose a reason for hiding this comment

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

One major reason is that Promises don't exist in WebAssembly, and there are costs (latency and extra JS VM spinup) to converting Promises into something that's pollable by WebAssembly.
(Converting Promises to C callbacks is probably possible, but C callbacks can make things very messy for engine developers, in particular wrt memory management.)

Another is that the majority of game engine architectures are main-loop-based. Even in JS, converting Promises into pollable interfaces incurs latency for engines.

Copy link
Contributor

Choose a reason for hiding this comment

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

(Something more complete than my comment should be written and included in the markdown.)

### GC discoverability

It isn't clear yet what happens when a buffer gets garbage collected while it is mapped.
The simple answer is that the `WebGPUMappedMemory` objects get invalidated but that would allow the application to discover when the GC runs.
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't the simpler answer be "don't collect the object?" Either by bumping a reference count upon map, or by pinning the object?

Copy link
Contributor

@kainino0x kainino0x Mar 7, 2018

Choose a reason for hiding this comment

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

Good point, don't know why I didn't think of it.
I think the WebGPUMappedMemory should point to the buffer. It could even be in a public attribute. (Makes it easier to do the unmap operation on one-off mappings.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having buffer as an attribute of the WebGPUMappedMemory would work and is pretty simple 👍

### Interactions with workers

Can a buffer be mapped in multiple different workers?
If that's the case, the pointer should be represented with a `SharedArrayBufer`.
Copy link
Contributor

Choose a reason for hiding this comment

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

spelling


### Interactions with workers

Can a buffer be mapped in multiple different workers?
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably have a (another?) larger discussion about workers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

The Promise has to be Promise<any> because the user can return anything from the success callback.
@Kangz
Copy link
Contributor Author

Kangz commented Mar 28, 2018

Thanks everyone for the reviews. As discussed in the last several calls, we think this approach grew too complex to let applications map only ranges of the buffer. #50 is a better direction, so closing this PR.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants