| // Copyright 2021 The Dawn Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "src/dawn_node/binding/GPUBuffer.h" | |
| #include <memory> | |
| #include "src/dawn_node/binding/Converter.h" | |
| #include "src/dawn_node/binding/Errors.h" | |
| #include "src/dawn_node/utils/Debug.h" | |
| namespace wgpu::binding { | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // wgpu::bindings::GPUBuffer | |
| // TODO(crbug.com/dawn/1134): We may be doing more validation here than necessary. Once CTS is | |
| // robustly passing, pull out validation and see what / if breaks. | |
| //////////////////////////////////////////////////////////////////////////////// | |
| GPUBuffer::GPUBuffer(wgpu::Buffer buffer, | |
| wgpu::BufferDescriptor desc, | |
| wgpu::Device device, | |
| std::shared_ptr<AsyncRunner> async) | |
| : buffer_(std::move(buffer)), | |
| desc_(desc), | |
| device_(std::move(device)), | |
| async_(std::move(async)) { | |
| if (desc.mappedAtCreation) { | |
| state_ = State::MappedAtCreation; | |
| } | |
| } | |
| interop::Promise<void> GPUBuffer::mapAsync(Napi::Env env, | |
| interop::GPUMapModeFlags mode, | |
| interop::GPUSize64 offset, | |
| std::optional<interop::GPUSize64> size) { | |
| wgpu::MapMode md{}; | |
| Converter conv(env); | |
| if (!conv(md, mode)) { | |
| interop::Promise<void> promise(env, PROMISE_INFO); | |
| promise.Reject(Errors::OperationError(env)); | |
| return promise; | |
| } | |
| if (state_ != State::Unmapped) { | |
| interop::Promise<void> promise(env, PROMISE_INFO); | |
| promise.Reject(Errors::OperationError(env)); | |
| device_.InjectError(wgpu::ErrorType::Validation, | |
| "mapAsync called on buffer that is not in the unmapped state"); | |
| return promise; | |
| } | |
| struct Context { | |
| Napi::Env env; | |
| interop::Promise<void> promise; | |
| AsyncTask task; | |
| State& state; | |
| }; | |
| auto ctx = new Context{env, interop::Promise<void>(env, PROMISE_INFO), async_, state_}; | |
| auto promise = ctx->promise; | |
| uint64_t s = size.has_value() ? size.value() : (desc_.size - offset); | |
| state_ = State::MappingPending; | |
| buffer_.MapAsync( | |
| md, offset, s, | |
| [](WGPUBufferMapAsyncStatus status, void* userdata) { | |
| auto c = std::unique_ptr<Context>(static_cast<Context*>(userdata)); | |
| c->state = State::Unmapped; | |
| switch (status) { | |
| case WGPUBufferMapAsyncStatus_Force32: | |
| UNREACHABLE("WGPUBufferMapAsyncStatus_Force32"); | |
| break; | |
| case WGPUBufferMapAsyncStatus_Success: | |
| c->promise.Resolve(); | |
| c->state = State::Mapped; | |
| break; | |
| case WGPUBufferMapAsyncStatus_Error: | |
| c->promise.Reject(Errors::OperationError(c->env)); | |
| break; | |
| case WGPUBufferMapAsyncStatus_UnmappedBeforeCallback: | |
| case WGPUBufferMapAsyncStatus_DestroyedBeforeCallback: | |
| c->promise.Reject(Errors::AbortError(c->env)); | |
| break; | |
| case WGPUBufferMapAsyncStatus_Unknown: | |
| case WGPUBufferMapAsyncStatus_DeviceLost: | |
| // TODO: The spec is a bit vague around what the promise should do | |
| // here. | |
| c->promise.Reject(Errors::UnknownError(c->env)); | |
| break; | |
| } | |
| }, | |
| ctx); | |
| return promise; | |
| } | |
| interop::ArrayBuffer GPUBuffer::getMappedRange(Napi::Env env, | |
| interop::GPUSize64 offset, | |
| std::optional<interop::GPUSize64> size) { | |
| if (state_ != State::Mapped && state_ != State::MappedAtCreation) { | |
| Errors::OperationError(env).ThrowAsJavaScriptException(); | |
| return {}; | |
| } | |
| uint64_t s = size.has_value() ? size.value() : (desc_.size - offset); | |
| uint64_t start = offset; | |
| uint64_t end = offset + s; | |
| for (auto& mapping : mapped_) { | |
| if (mapping.Intersects(start, end)) { | |
| Errors::OperationError(env).ThrowAsJavaScriptException(); | |
| return {}; | |
| } | |
| } | |
| auto* ptr = (desc_.usage & wgpu::BufferUsage::MapWrite) | |
| ? buffer_.GetMappedRange(offset, s) | |
| : const_cast<void*>(buffer_.GetConstMappedRange(offset, s)); | |
| if (!ptr) { | |
| Errors::OperationError(env).ThrowAsJavaScriptException(); | |
| return {}; | |
| } | |
| auto array_buffer = Napi::ArrayBuffer::New(env, ptr, s); | |
| // TODO(crbug.com/dawn/1135): Ownership here is the wrong way around. | |
| mapped_.emplace_back(Mapping{start, end, Napi::Persistent(array_buffer)}); | |
| return array_buffer; | |
| } | |
| void GPUBuffer::unmap(Napi::Env env) { | |
| if (state_ == State::Destroyed) { | |
| device_.InjectError(wgpu::ErrorType::Validation, | |
| "unmap() called on a destroyed buffer"); | |
| return; | |
| } | |
| for (auto& mapping : mapped_) { | |
| mapping.buffer.Value().Detach(); | |
| } | |
| mapped_.clear(); | |
| buffer_.Unmap(); | |
| state_ = State::Unmapped; | |
| } | |
| void GPUBuffer::destroy(Napi::Env) { | |
| buffer_.Destroy(); | |
| state_ = State::Destroyed; | |
| } | |
| std::optional<std::string> GPUBuffer::getLabel(Napi::Env) { | |
| UNIMPLEMENTED(); | |
| } | |
| void GPUBuffer::setLabel(Napi::Env, std::optional<std::string> value) { | |
| UNIMPLEMENTED(); | |
| } | |
| } // namespace wgpu::binding |