{"action":"created","issue":{"url":"https://api.github.com/repos/gpuweb/gpuweb/issues/138","repository_url":"https://api.github.com/repos/gpuweb/gpuweb","labels_url":"https://api.github.com/repos/gpuweb/gpuweb/issues/138/labels{/name}","comments_url":"https://api.github.com/repos/gpuweb/gpuweb/issues/138/comments","events_url":"https://api.github.com/repos/gpuweb/gpuweb/issues/138/events","html_url":"https://github.com/gpuweb/gpuweb/issues/138","id":385846660,"node_id":"MDU6SXNzdWUzODU4NDY2NjA=","number":138,"title":"Proposals for buffer operations (immediate uploads, buffer mapping)","user":{"login":"Kangz","id":472810,"node_id":"MDQ6VXNlcjQ3MjgxMA==","avatar_url":"https://avatars3.githubusercontent.com/u/472810?v=4","gravatar_id":"","url":"https://api.github.com/users/Kangz","html_url":"https://github.com/Kangz","followers_url":"https://api.github.com/users/Kangz/followers","following_url":"https://api.github.com/users/Kangz/following{/other_user}","gists_url":"https://api.github.com/users/Kangz/gists{/gist_id}","starred_url":"https://api.github.com/users/Kangz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Kangz/subscriptions","organizations_url":"https://api.github.com/users/Kangz/orgs","repos_url":"https://api.github.com/users/Kangz/repos","events_url":"https://api.github.com/users/Kangz/events{/privacy}","received_events_url":"https://api.github.com/users/Kangz/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":27,"created_at":"2018-11-29T17:31:33Z","updated_at":"2018-12-14T14:30:36Z","closed_at":null,"author_association":"CONTRIBUTOR","body":"PTAL, this is basically #49 but as an investigation, and with additional alternatives.\r\n\r\nOur thoughts on this proposals are the following:\r\n\r\n - Buffer mapping 1: our preferred solution, but a bit complex\r\n - Buffer mapping 2: Mapping whole buffers seems too heavyweight for multi-process implementations\r\n - Immediate uploads 1: Goes very well with Buffer mapping 1\r\n - Immediate uploads 2: strongly in favor\r\n\r\n# Buffer operations\r\n\r\nThis describes `WebGPUBuffer` operations that are used by applications to interact directly with the content of the buffer's memory.\r\nThe two primitives we need to support are the CPU writing data inside the buffer for use by the GPU (upload) and the CPU reading data produced by the GPU (readback).\r\n\r\nDesign constraints are:\r\n\r\n - For the portability of the API, prevent data races between the CPU and the GPU.\r\n - For performance, minimize the number of times the data is copied around.\r\n - To make the API non-blocking, only allow asynchronous readbacks.\r\n - For performance on multi-process implementations, make an asynchronous upload path.\r\n\r\nTwo alternative proposals are described for buffer mapping, `WebGPUMappedMemory` and whole-buffer mapping.\r\nTwo other proposals are described for immediate data uploads that aren't mutually exclusive, one base one `mapReadSync` of `WebGPUMappedMemory` and another using `setSubData`.\r\n\r\n## Buffer mapping proposal 1\r\n\r\n### map[Write|Read]Async and unmap\r\n\r\nThe way to have the minimal number of copies for upload and readback is to provide a buffer mapping mechanism.\r\nThis mechanism has to be asynchronous to ensure the GPU is done using the buffer before the application can look into the ArrayBuffer.\r\nOtherwise on implementation where the ArrayBuffer is directly a pointer to the buffer memory, data races between the CPU and the GPU could occur.\r\n\r\nWe want the status of a map operation to act as both a promise, and something that's pollable as there are advantages to both.\r\n`WebGPUMappedMemory` is an object that is `then`-able, meaning that it acts like a Javascript `Promise` but is pollable at the same time.\r\n\r\nThe mapping operations for `WebGPUBuffer` are:\r\n\r\n```webidl\r\npartial interface WebGPUBuffer {\r\n    WebGPUMappedMemory mapWriteAsync(u32 offset, u32 size);\r\n    WebGPUMappedMemory mapReadAsync(u32 offset, u32 size);\r\n};\r\n```\r\n\r\nThese operations return new `WebGPUMappedMemory` objects representing the current range of the buffer for writing or mapping.\r\nThe 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.\r\nCalling `mapReadAsync` or `mapWriteAsync` puts the buffer in the mapped state.\r\nNo operations are allowed in a buffer in that state except additional calls to `mapReadiAsync` or `mapWriteAsync` and calls to `unmap`.\r\nIn particular a mapped buffer cannot be used in a `WebGPUCommandBuffer` given to `WebGPUQueue.submit`.\r\nThe following must be true or a validation error occurs for `mapWriteAsync` (resp. `mapReadAsync`):\r\n\r\n - The buffer must have been created with the `WebGPUBufferUsage.MAP_WRITE` (resp. `WebGPUBufferUsage.MAP_READ`) usage.\r\n - `offset + size` must not overflow and be at most the size of the buffer\r\n - The `[offset, offset + size)` range must not intersect the range of another `WebGPUMappedMemory` on the same buffer which hasn't been previously invalidated.\r\n - The buffer has been destroyed.\r\n\r\nThen a mapped buffer can be unmapped with:\r\n\r\n```webidl\r\npartial interface WebGPUBuffer {\r\n    void unmap();\r\n};\r\n```\r\n\r\nThis operation invalidates all the `WebGPUMappedMemory` created from the buffer and puts the buffer in the unmapped state.\r\nThe buffer must be in the mapped state otherwise a validation error occurs when `unmap` is called.\r\n\r\n### WebGPUMappedMemory\r\n\r\n`WebGPUMappedMemory` is an object representing a mapped region of a buffer that's both pollable and promise-like.\r\n\r\nIt can be in one of three states: pending, available and invalidated.\r\n\r\nThe pollable interface is:\r\n\r\n```webidl\r\npartial interface WebGPUMappedMemory {\r\n    bool isPending();\r\n    ArrayBuffer getPointer();\r\n};\r\n```\r\n\r\n`isPending` return true if the object is in the pending state, false otherwise.\r\n`getPointer` returns an ArrayBuffer representing the buffer data if the object is in the available state, null otherwise.\r\n\r\n`WebGPUMappedMemory` is also `then`-able, meaning that it acts like a Javascript `Promise`:\r\n\r\n```webidl\r\npartial interface WebGPUMappedMemory {\r\n    Promise then(WebGPUMappedMemorySuccessCallback success,\r\n                 optional WebGPUMappedMemoryErrorCallback error);\r\n};\r\n```\r\n\r\nThis 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.\r\nOn that boundary:\r\n\r\n - The `WebGPUMappedMemory` goes in the available state.\r\n - If the `WebGPUMappedMemory` was created via `WebGPUBuffer.mapWriteAsync`, its content is cleared to 0.\r\n - `success` is called with the content of the memory as an argument.\r\n\r\nIf `success` hasn't been called when the WebGPUMappedMemory gets invalidated (meaning the object is still in the pending state), `error` is called instead.\r\nWhen `WebGPUMappedMemory` goes from the available state to the invalidated state, the `ArrayBuffer` for its content gets neutered.\r\nThe return value of `then` acts like the return value of `Promise.then`.\r\n\r\nThe `ArrayBuffer` of a `WebGPUMappedMemory` created from a `mapWriteAsync` is where the application should write the data and its content is made available to the buffer when the `WebGPUMappedMemory` is invalidated (i.e. `WebGPUBuffer.unmap` is called).\r\n\r\n## Buffer mapping proposal 2\r\n\r\nIn this proposal a buffer is always mapped as a whole as an asynchronous operation.\r\nMapping for reading (resp.writing) is done using `WebGPUBuffer.mapRead` (resp `WebGPUBuffer.mapWrite`).\r\nThe mapping calls but the buffer in the \"mapped\" state.\r\nA Javascript error is thrown under these conditions:\r\n\r\n - The buffer hasn't been created with the `MAP_READ` (resp. `MAP_WRITE`) usage.\r\n - The buffer isn't in the unmapped state.\r\n - The buffer has been destroyed.\r\n\r\n```webidl\r\npartial interface WebGPUBuffer {\r\n    void mapRead();\r\n    void mapWrite();\r\n}\r\n```\r\n\r\nMapping is an asynchronous operation and after its resolution the buffer's `mapping` member will be updated to represent the content of the buffer (resp. filled with zero and ready to receive data from the application).\r\nResolution can only happen at Javascript task boundary, and after the implementation has determined it is safe to give access to the buffer to the CPU.\r\nResolution is guaranteed to complete before (or at the same time) as when all previously enqueued operations are finished executing (as can be observed with `WebGPUFence`).\r\n\r\n```webidl\r\npartial interface WebGPUBuffer {\r\n    readonly attribute ArrayBuffer? mapping;\r\n}\r\n```\r\n\r\nThe buffer is unmapped with a call to `unmap` which puts it in the unmapped state.\r\nIt is an error to call `unmap` while in the unmapped state.\r\nIn the mapped state it is an error to do operations in the buffer (such as `setSubdata` or enqueuing commands using the buffer).\r\n\r\n```webidl\r\npartial interface WebGPUBuffer {\r\n    void unmap();\r\n};\r\n```\r\n\r\n## Immediate data upload proposal 1\r\n\r\nWhen mapping for writing, the application doesn't see GPU state since the content is cleared to 0.\r\nThis means WebGPU can expose a `mapWriteSync` primitive that behaves exactly like `mapWriteAsync` except that the returned `WebGPUMappedMemory` object starts in the available state.\r\n\r\n```webidl\r\npartial interface WebGPUBuffer {\r\n    WebGPUMappedMemory mapWriteSync(u32 offset, u32 size);\r\n};\r\n```\r\n\r\n## Immediate data upload proposal 2\r\n\r\nBuffer 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.\r\nA `WebGPUBuffer` operation is provided that takes an ArrayBuffer and copies its content at an offset in the buffer.\r\n\r\n```webidl\r\npartial interface WebGPUBuffer {\r\n    void setSubData(ArrayBuffer data, u32 offset);\r\n}\r\n```\r\n\r\nThis operation acts as if it was done after all previous \"device-level\" commands and before all subsequent \"device-level\" commands.\r\n\"Device level\" commands are all commands not buffered in a `WebGPUCommandBuffer`, and include `WebGPUQueue.submit`.\r\nThe content of `data` is only read during the call and can be modified by the application afterwards.\r\nThe following must be true or a validation error occurs:\r\n\r\n - The buffer must have been created with the `WebGPUBufferUsage.TRANSFER_DST` usage flag.\r\n - `offset + data.length` must not overflow and be at most the size of the buffer.\r\n - The buffer must not be currently mapped.\r\n\r\n## Unused designs\r\n\r\n### Persistently mapped buffer\r\n\r\nPersistently 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.\r\nWe 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.\r\nBeing data race free would be possible if ArrayBuffer could be unneutered but this is not the case.\r\n\r\n### Promise<ArrayBuffer> readback();\r\n\r\nThis didn't have a pollable interface and forced an extra buffer-to-buffer copy to occur if the GPU execution could be resumed immediately.\r\n\r\n### Dawn's MapReadAsync(callback);\r\n\r\nNot a pollable interface.\r\n\r\n## Issues\r\n\r\n### GC discoverability\r\n\r\nIt isn't clear yet what happens when a buffer gets garbage collected while it is mapped.\r\nThe simple answer is that the `WebGPUMappedMemory` objects get invalidated but that would allow the application to discover when the GC runs.\r\n\r\n### GC pressure\r\n\r\nThe `WebGPUMappedMemory` design makes each mapped region create two garbage collected objects. This could lead to some GC pressure.\r\n\r\n### Side effects between mapped memory regions\r\n\r\nWhat happens when `WebGPUMappedMemory` object's region in the buffer overlap?\r\nAre write from one visible from the other?\r\nIf they are, maybe `WebGPUMappedMemory.getPointer` should return an `ArrayBufferView` instead.\r\n\r\n### Interactions with workers\r\n\r\nCan a buffer be mapped in multiple different workers?\r\nIf that's the case, the pointer should be represented with a `SharedArrayBuffer`."},"comment":{"url":"https://api.github.com/repos/gpuweb/gpuweb/issues/comments/447341920","html_url":"https://github.com/gpuweb/gpuweb/issues/138#issuecomment-447341920","issue_url":"https://api.github.com/repos/gpuweb/gpuweb/issues/138","id":447341920,"node_id":"MDEyOklzc3VlQ29tbWVudDQ0NzM0MTkyMA==","user":{"login":"devshgraphicsprogramming","id":6894321,"node_id":"MDQ6VXNlcjY4OTQzMjE=","avatar_url":"https://avatars3.githubusercontent.com/u/6894321?v=4","gravatar_id":"","url":"https://api.github.com/users/devshgraphicsprogramming","html_url":"https://github.com/devshgraphicsprogramming","followers_url":"https://api.github.com/users/devshgraphicsprogramming/followers","following_url":"https://api.github.com/users/devshgraphicsprogramming/following{/other_user}","gists_url":"https://api.github.com/users/devshgraphicsprogramming/gists{/gist_id}","starred_url":"https://api.github.com/users/devshgraphicsprogramming/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/devshgraphicsprogramming/subscriptions","organizations_url":"https://api.github.com/users/devshgraphicsprogramming/orgs","repos_url":"https://api.github.com/users/devshgraphicsprogramming/repos","events_url":"https://api.github.com/users/devshgraphicsprogramming/events{/privacy}","received_events_url":"https://api.github.com/users/devshgraphicsprogramming/received_events","type":"User","site_admin":false},"created_at":"2018-12-14T14:30:36Z","updated_at":"2018-12-14T14:30:36Z","author_association":"NONE","body":"> increased memory utilization because you cannot map a part of the buffer while the rest is in use by the GPU so you have to overallocate a bit.\r\n\r\nThat would be a serious understatement.\r\n\r\n> native applications using persistently mapped buffers would have to either emulate them on top of this abstraction (incurring an additional copy) or specialize their upload mechanisms for WebGPU.\r\n\r\nThey did that to be much faster than the usual OpenGL (ES) and DirectX11 methods, now they will have to be even slower than the old methods due to emulation.\r\n\r\n> The advantages are:\r\n\r\nYou'd keep all of them, except the \"guarantee of having no races\" if you went for a non-cached approach with explicit flushes and invalidations (as well as beautiful RenderDoc integration)."}}
{"actor":{"display_login":"devshgraphicsprogramming"}}