| // 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/GPUDevice.h" | |
| #include <memory> | |
| #include "src/dawn_node/binding/Converter.h" | |
| #include "src/dawn_node/binding/Errors.h" | |
| #include "src/dawn_node/binding/GPUBindGroup.h" | |
| #include "src/dawn_node/binding/GPUBindGroupLayout.h" | |
| #include "src/dawn_node/binding/GPUBuffer.h" | |
| #include "src/dawn_node/binding/GPUCommandBuffer.h" | |
| #include "src/dawn_node/binding/GPUCommandEncoder.h" | |
| #include "src/dawn_node/binding/GPUComputePipeline.h" | |
| #include "src/dawn_node/binding/GPUPipelineLayout.h" | |
| #include "src/dawn_node/binding/GPUQuerySet.h" | |
| #include "src/dawn_node/binding/GPUQueue.h" | |
| #include "src/dawn_node/binding/GPURenderBundleEncoder.h" | |
| #include "src/dawn_node/binding/GPURenderPipeline.h" | |
| #include "src/dawn_node/binding/GPUSampler.h" | |
| #include "src/dawn_node/binding/GPUShaderModule.h" | |
| #include "src/dawn_node/binding/GPUSupportedLimits.h" | |
| #include "src/dawn_node/binding/GPUTexture.h" | |
| #include "src/dawn_node/utils/Debug.h" | |
| namespace wgpu::binding { | |
| namespace { | |
| class DeviceLostInfo : public interop::GPUDeviceLostInfo { | |
| public: | |
| DeviceLostInfo(interop::GPUDeviceLostReason reason, std::string message) | |
| : reason_(reason), message_(message) { | |
| } | |
| std::variant<interop::GPUDeviceLostReason> getReason(Napi::Env env) override { | |
| return reason_; | |
| } | |
| std::string getMessage(Napi::Env) override { | |
| return message_; | |
| } | |
| private: | |
| interop::GPUDeviceLostReason reason_; | |
| std::string message_; | |
| }; | |
| class OOMError : public interop::GPUOutOfMemoryError {}; | |
| class ValidationError : public interop::GPUValidationError { | |
| public: | |
| ValidationError(std::string message) : message_(std::move(message)) { | |
| } | |
| std::string getMessage(Napi::Env) override { | |
| return message_; | |
| }; | |
| private: | |
| std::string message_; | |
| }; | |
| } // namespace | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // wgpu::bindings::GPUDevice | |
| //////////////////////////////////////////////////////////////////////////////// | |
| GPUDevice::GPUDevice(Napi::Env env, wgpu::Device device) | |
| : env_(env), device_(device), async_(std::make_shared<AsyncRunner>(env, device)) { | |
| device_.SetLoggingCallback( | |
| [](WGPULoggingType type, char const* message, void* userdata) { | |
| std::cout << type << ": " << message << std::endl; | |
| }, | |
| nullptr); | |
| device_.SetUncapturedErrorCallback( | |
| [](WGPUErrorType type, char const* message, void* userdata) { | |
| std::cout << type << ": " << message << std::endl; | |
| }, | |
| nullptr); | |
| device_.SetDeviceLostCallback( | |
| [](WGPUDeviceLostReason reason, char const* message, void* userdata) { | |
| auto r = interop::GPUDeviceLostReason::kDestroyed; | |
| switch (reason) { | |
| case WGPUDeviceLostReason_Force32: | |
| UNREACHABLE("WGPUDeviceLostReason_Force32"); | |
| break; | |
| case WGPUDeviceLostReason_Destroyed: | |
| case WGPUDeviceLostReason_Undefined: | |
| r = interop::GPUDeviceLostReason::kDestroyed; | |
| break; | |
| } | |
| auto* self = static_cast<GPUDevice*>(userdata); | |
| for (auto promise : self->lost_promises_) { | |
| promise.Resolve( | |
| interop::GPUDeviceLostInfo::Create<DeviceLostInfo>(self->env_, r, message)); | |
| } | |
| }, | |
| this); | |
| } | |
| GPUDevice::~GPUDevice() { | |
| } | |
| interop::Interface<interop::GPUSupportedFeatures> GPUDevice::getFeatures(Napi::Env env) { | |
| class Features : public interop::GPUSupportedFeatures { | |
| public: | |
| bool has(Napi::Env, std::string feature) override { | |
| UNIMPLEMENTED(); | |
| } | |
| std::vector<std::string> keys(Napi::Env) override { | |
| UNIMPLEMENTED(); | |
| } | |
| }; | |
| return interop::GPUSupportedFeatures::Create<Features>(env); | |
| } | |
| interop::Interface<interop::GPUSupportedLimits> GPUDevice::getLimits(Napi::Env env) { | |
| wgpu::SupportedLimits limits{}; | |
| if (!device_.GetLimits(&limits)) { | |
| Napi::Error::New(env, "failed to get device limits").ThrowAsJavaScriptException(); | |
| } | |
| return interop::GPUSupportedLimits::Create<GPUSupportedLimits>(env, limits); | |
| } | |
| interop::Interface<interop::GPUQueue> GPUDevice::getQueue(Napi::Env env) { | |
| // TODO(crbug.com/dawn/1144): Should probably return the same Queue JS object. | |
| return interop::GPUQueue::Create<GPUQueue>(env, device_.GetQueue(), async_); | |
| } | |
| void GPUDevice::destroy(Napi::Env env) { | |
| for (auto promise : lost_promises_) { | |
| promise.Resolve(interop::GPUDeviceLostInfo::Create<DeviceLostInfo>( | |
| env_, interop::GPUDeviceLostReason::kDestroyed, "device was destroyed")); | |
| } | |
| lost_promises_.clear(); | |
| device_.Release(); | |
| } | |
| interop::Interface<interop::GPUBuffer> GPUDevice::createBuffer( | |
| Napi::Env env, | |
| interop::GPUBufferDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::BufferDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || | |
| !conv(desc.mappedAtCreation, descriptor.mappedAtCreation) || | |
| !conv(desc.size, descriptor.size) || !conv(desc.usage, descriptor.usage)) { | |
| return {}; | |
| } | |
| return interop::GPUBuffer::Create<GPUBuffer>(env, device_.CreateBuffer(&desc), desc, | |
| device_, async_); | |
| } | |
| interop::Interface<interop::GPUTexture> GPUDevice::createTexture( | |
| Napi::Env env, | |
| interop::GPUTextureDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::TextureDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || !conv(desc.usage, descriptor.usage) || // | |
| !conv(desc.size, descriptor.size) || // | |
| !conv(desc.dimension, descriptor.dimension) || // | |
| !conv(desc.mipLevelCount, descriptor.mipLevelCount) || // | |
| !conv(desc.sampleCount, descriptor.sampleCount) || // | |
| !conv(desc.format, descriptor.format)) { | |
| return {}; | |
| } | |
| return interop::GPUTexture::Create<GPUTexture>(env, device_.CreateTexture(&desc)); | |
| } | |
| interop::Interface<interop::GPUSampler> GPUDevice::createSampler( | |
| Napi::Env env, | |
| interop::GPUSamplerDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::SamplerDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || // | |
| !conv(desc.addressModeU, descriptor.addressModeU) || // | |
| !conv(desc.addressModeV, descriptor.addressModeV) || // | |
| !conv(desc.addressModeW, descriptor.addressModeW) || // | |
| !conv(desc.magFilter, descriptor.magFilter) || // | |
| !conv(desc.minFilter, descriptor.minFilter) || // | |
| !conv(desc.mipmapFilter, descriptor.mipmapFilter) || // | |
| !conv(desc.lodMinClamp, descriptor.lodMinClamp) || // | |
| !conv(desc.lodMaxClamp, descriptor.lodMaxClamp) || // | |
| !conv(desc.compare, descriptor.compare) || // | |
| !conv(desc.maxAnisotropy, descriptor.maxAnisotropy)) { | |
| return {}; | |
| } | |
| return interop::GPUSampler::Create<GPUSampler>(env, device_.CreateSampler(&desc)); | |
| } | |
| interop::Interface<interop::GPUExternalTexture> GPUDevice::importExternalTexture( | |
| Napi::Env, | |
| interop::GPUExternalTextureDescriptor descriptor) { | |
| UNIMPLEMENTED(); | |
| } | |
| interop::Interface<interop::GPUBindGroupLayout> GPUDevice::createBindGroupLayout( | |
| Napi::Env env, | |
| interop::GPUBindGroupLayoutDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::BindGroupLayoutDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || | |
| !conv(desc.entries, desc.entryCount, descriptor.entries)) { | |
| return {}; | |
| } | |
| return interop::GPUBindGroupLayout::Create<GPUBindGroupLayout>( | |
| env, device_.CreateBindGroupLayout(&desc)); | |
| } | |
| interop::Interface<interop::GPUPipelineLayout> GPUDevice::createPipelineLayout( | |
| Napi::Env env, | |
| interop::GPUPipelineLayoutDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::PipelineLayoutDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || | |
| !conv(desc.bindGroupLayouts, desc.bindGroupLayoutCount, descriptor.bindGroupLayouts)) { | |
| return {}; | |
| } | |
| return interop::GPUPipelineLayout::Create<GPUPipelineLayout>( | |
| env, device_.CreatePipelineLayout(&desc)); | |
| } | |
| interop::Interface<interop::GPUBindGroup> GPUDevice::createBindGroup( | |
| Napi::Env env, | |
| interop::GPUBindGroupDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::BindGroupDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || !conv(desc.layout, descriptor.layout) || | |
| !conv(desc.entries, desc.entryCount, descriptor.entries)) { | |
| return {}; | |
| } | |
| return interop::GPUBindGroup::Create<GPUBindGroup>(env, device_.CreateBindGroup(&desc)); | |
| } | |
| interop::Interface<interop::GPUShaderModule> GPUDevice::createShaderModule( | |
| Napi::Env env, | |
| interop::GPUShaderModuleDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::ShaderModuleWGSLDescriptor wgsl_desc{}; | |
| wgpu::ShaderModuleDescriptor sm_desc{}; | |
| if (!conv(wgsl_desc.source, descriptor.code) || !conv(sm_desc.label, descriptor.label)) { | |
| return {}; | |
| } | |
| sm_desc.nextInChain = &wgsl_desc; | |
| return interop::GPUShaderModule::Create<GPUShaderModule>( | |
| env, device_.CreateShaderModule(&sm_desc), async_); | |
| } | |
| interop::Interface<interop::GPUComputePipeline> GPUDevice::createComputePipeline( | |
| Napi::Env env, | |
| interop::GPUComputePipelineDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::ComputePipelineDescriptor desc{}; | |
| if (!conv(desc, descriptor)) { | |
| return {}; | |
| } | |
| return interop::GPUComputePipeline::Create<GPUComputePipeline>( | |
| env, device_.CreateComputePipeline(&desc)); | |
| } | |
| interop::Interface<interop::GPURenderPipeline> GPUDevice::createRenderPipeline( | |
| Napi::Env env, | |
| interop::GPURenderPipelineDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::RenderPipelineDescriptor desc{}; | |
| if (!conv(desc, descriptor)) { | |
| return {}; | |
| } | |
| return interop::GPURenderPipeline::Create<GPURenderPipeline>( | |
| env, device_.CreateRenderPipeline(&desc)); | |
| } | |
| interop::Promise<interop::Interface<interop::GPUComputePipeline>> | |
| GPUDevice::createComputePipelineAsync(Napi::Env env, | |
| interop::GPUComputePipelineDescriptor descriptor) { | |
| using Promise = interop::Promise<interop::Interface<interop::GPUComputePipeline>>; | |
| Converter conv(env); | |
| wgpu::ComputePipelineDescriptor desc{}; | |
| if (!conv(desc, descriptor)) { | |
| Promise promise(env, PROMISE_INFO); | |
| promise.Reject(Errors::OperationError(env)); | |
| return promise; | |
| } | |
| struct Context { | |
| Napi::Env env; | |
| Promise promise; | |
| AsyncTask task; | |
| }; | |
| auto ctx = new Context{env, Promise(env, PROMISE_INFO), async_}; | |
| auto promise = ctx->promise; | |
| device_.CreateComputePipelineAsync( | |
| &desc, | |
| [](WGPUCreatePipelineAsyncStatus status, WGPUComputePipeline pipeline, | |
| char const* message, void* userdata) { | |
| auto c = std::unique_ptr<Context>(static_cast<Context*>(userdata)); | |
| switch (status) { | |
| case WGPUCreatePipelineAsyncStatus::WGPUCreatePipelineAsyncStatus_Success: | |
| c->promise.Resolve(interop::GPUComputePipeline::Create<GPUComputePipeline>( | |
| c->env, pipeline)); | |
| break; | |
| default: | |
| c->promise.Reject(Errors::OperationError(c->env)); | |
| break; | |
| } | |
| }, | |
| ctx); | |
| return promise; | |
| } | |
| interop::Promise<interop::Interface<interop::GPURenderPipeline>> | |
| GPUDevice::createRenderPipelineAsync(Napi::Env env, | |
| interop::GPURenderPipelineDescriptor descriptor) { | |
| using Promise = interop::Promise<interop::Interface<interop::GPURenderPipeline>>; | |
| Converter conv(env); | |
| wgpu::RenderPipelineDescriptor desc{}; | |
| if (!conv(desc, descriptor)) { | |
| Promise promise(env, PROMISE_INFO); | |
| promise.Reject(Errors::OperationError(env)); | |
| return promise; | |
| } | |
| struct Context { | |
| Napi::Env env; | |
| Promise promise; | |
| AsyncTask task; | |
| }; | |
| auto ctx = new Context{env, Promise(env, PROMISE_INFO), async_}; | |
| auto promise = ctx->promise; | |
| device_.CreateRenderPipelineAsync( | |
| &desc, | |
| [](WGPUCreatePipelineAsyncStatus status, WGPURenderPipeline pipeline, | |
| char const* message, void* userdata) { | |
| auto c = std::unique_ptr<Context>(static_cast<Context*>(userdata)); | |
| switch (status) { | |
| case WGPUCreatePipelineAsyncStatus::WGPUCreatePipelineAsyncStatus_Success: | |
| c->promise.Resolve(interop::GPURenderPipeline::Create<GPURenderPipeline>( | |
| c->env, pipeline)); | |
| break; | |
| default: | |
| c->promise.Reject(Errors::OperationError(c->env)); | |
| break; | |
| } | |
| }, | |
| ctx); | |
| return promise; | |
| } | |
| interop::Interface<interop::GPUCommandEncoder> GPUDevice::createCommandEncoder( | |
| Napi::Env env, | |
| interop::GPUCommandEncoderDescriptor descriptor) { | |
| wgpu::CommandEncoderDescriptor desc{}; | |
| return interop::GPUCommandEncoder::Create<GPUCommandEncoder>( | |
| env, device_.CreateCommandEncoder(&desc)); | |
| } | |
| interop::Interface<interop::GPURenderBundleEncoder> GPUDevice::createRenderBundleEncoder( | |
| Napi::Env env, | |
| interop::GPURenderBundleEncoderDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::RenderBundleEncoderDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || | |
| !conv(desc.colorFormats, desc.colorFormatsCount, descriptor.colorFormats) || | |
| !conv(desc.depthStencilFormat, descriptor.depthStencilFormat) || | |
| !conv(desc.sampleCount, descriptor.sampleCount)) { | |
| return {}; | |
| } | |
| return interop::GPURenderBundleEncoder::Create<GPURenderBundleEncoder>( | |
| env, device_.CreateRenderBundleEncoder(&desc)); | |
| } | |
| interop::Interface<interop::GPUQuerySet> GPUDevice::createQuerySet( | |
| Napi::Env env, | |
| interop::GPUQuerySetDescriptor descriptor) { | |
| Converter conv(env); | |
| wgpu::QuerySetDescriptor desc{}; | |
| if (!conv(desc.label, descriptor.label) || !conv(desc.type, descriptor.type) || | |
| !conv(desc.count, descriptor.count)) { | |
| return {}; | |
| } | |
| return interop::GPUQuerySet::Create<GPUQuerySet>(env, device_.CreateQuerySet(&desc)); | |
| } | |
| interop::Promise<interop::Interface<interop::GPUDeviceLostInfo>> GPUDevice::getLost( | |
| Napi::Env env) { | |
| auto promise = | |
| interop::Promise<interop::Interface<interop::GPUDeviceLostInfo>>(env, PROMISE_INFO); | |
| lost_promises_.emplace_back(promise); | |
| return promise; | |
| } | |
| void GPUDevice::pushErrorScope(Napi::Env env, interop::GPUErrorFilter filter) { | |
| wgpu::ErrorFilter f; | |
| switch (filter) { | |
| case interop::GPUErrorFilter::kOutOfMemory: | |
| f = wgpu::ErrorFilter::OutOfMemory; | |
| break; | |
| case interop::GPUErrorFilter::kValidation: | |
| f = wgpu::ErrorFilter::Validation; | |
| break; | |
| default: | |
| Napi::Error::New(env, "unhandled GPUErrorFilter value") | |
| .ThrowAsJavaScriptException(); | |
| return; | |
| } | |
| device_.PushErrorScope(f); | |
| } | |
| interop::Promise<std::optional<interop::GPUError>> GPUDevice::popErrorScope(Napi::Env env) { | |
| using Promise = interop::Promise<std::optional<interop::GPUError>>; | |
| struct Context { | |
| Napi::Env env; | |
| Promise promise; | |
| AsyncTask task; | |
| }; | |
| auto* ctx = new Context{env, Promise(env, PROMISE_INFO), async_}; | |
| auto promise = ctx->promise; | |
| bool ok = device_.PopErrorScope( | |
| [](WGPUErrorType type, char const* message, void* userdata) { | |
| auto c = std::unique_ptr<Context>(static_cast<Context*>(userdata)); | |
| auto env = c->env; | |
| switch (type) { | |
| case WGPUErrorType::WGPUErrorType_NoError: | |
| c->promise.Resolve({}); | |
| break; | |
| case WGPUErrorType::WGPUErrorType_OutOfMemory: | |
| c->promise.Resolve(interop::GPUOutOfMemoryError::Create<OOMError>(env)); | |
| break; | |
| case WGPUErrorType::WGPUErrorType_Unknown: | |
| case WGPUErrorType::WGPUErrorType_DeviceLost: | |
| case WGPUErrorType::WGPUErrorType_Validation: | |
| c->promise.Resolve( | |
| interop::GPUValidationError::Create<ValidationError>(env, message)); | |
| break; | |
| default: | |
| c->promise.Reject("unhandled error type"); | |
| break; | |
| } | |
| }, | |
| ctx); | |
| if (ok) { | |
| return promise; | |
| } | |
| delete ctx; | |
| promise.Reject(Errors::OperationError(env)); | |
| return promise; | |
| } | |
| std::optional<std::string> GPUDevice::getLabel(Napi::Env) { | |
| UNIMPLEMENTED(); | |
| }; | |
| void GPUDevice::setLabel(Napi::Env, std::optional<std::string> value) { | |
| UNIMPLEMENTED(); | |
| }; | |
| interop::Interface<interop::EventHandler> GPUDevice::getOnuncapturederror(Napi::Env) { | |
| UNIMPLEMENTED(); | |
| } | |
| void GPUDevice::setOnuncapturederror(Napi::Env, | |
| interop::Interface<interop::EventHandler> value) { | |
| UNIMPLEMENTED(); | |
| } | |
| void GPUDevice::addEventListener( | |
| Napi::Env, | |
| std::string type, | |
| std::optional<interop::Interface<interop::EventListener>> callback, | |
| std::optional<std::variant<interop::AddEventListenerOptions, bool>> options) { | |
| UNIMPLEMENTED(); | |
| } | |
| void GPUDevice::removeEventListener( | |
| Napi::Env, | |
| std::string type, | |
| std::optional<interop::Interface<interop::EventListener>> callback, | |
| std::optional<std::variant<interop::EventListenerOptions, bool>> options) { | |
| UNIMPLEMENTED(); | |
| } | |
| bool GPUDevice::dispatchEvent(Napi::Env, interop::Interface<interop::Event> event) { | |
| UNIMPLEMENTED(); | |
| } | |
| } // namespace wgpu::binding |