// 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 #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 async) : buffer_(std::move(buffer)), desc_(desc), device_(std::move(device)), async_(std::move(async)) { if (desc.mappedAtCreation) { state_ = State::MappedAtCreation; } } interop::Promise GPUBuffer::mapAsync(Napi::Env env, interop::GPUMapModeFlags mode, interop::GPUSize64 offset, std::optional size) { wgpu::MapMode md{}; Converter conv(env); if (!conv(md, mode)) { interop::Promise promise(env, PROMISE_INFO); promise.Reject(Errors::OperationError(env)); return promise; } if (state_ != State::Unmapped) { interop::Promise 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 promise; AsyncTask task; State& state; }; auto ctx = new Context{env, interop::Promise(env, PROMISE_INFO), AsyncTask(async_), state_}; auto promise = ctx->promise; uint64_t s = size.has_value() ? size.value().value : (desc_.size - offset); state_ = State::MappingPending; buffer_.MapAsync( md, offset, s, [](WGPUBufferMapAsyncStatus status, void* userdata) { auto c = std::unique_ptr(static_cast(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 size) { if (state_ != State::Mapped && state_ != State::MappedAtCreation) { Errors::OperationError(env).ThrowAsJavaScriptException(); return {}; } uint64_t s = size.has_value() ? size.value().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(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) { buffer_.Unmap(); if (state_ != State::Destroyed && state_ != State::Unmapped) { DetachMappings(); state_ = State::Unmapped; } } void GPUBuffer::destroy(Napi::Env) { if (state_ == State::Destroyed) { return; } if (state_ != State::Unmapped) { DetachMappings(); } buffer_.Destroy(); state_ = State::Destroyed; } void GPUBuffer::DetachMappings() { for (auto& mapping : mapped_) { mapping.buffer.Value().Detach(); } mapped_.clear(); } std::variant GPUBuffer::getLabel(Napi::Env) { UNIMPLEMENTED(); } void GPUBuffer::setLabel(Napi::Env, std::variant value) { UNIMPLEMENTED(); } } // namespace wgpu::binding