/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Device.h" #include "Adapter.h" #include "BindGroup.h" #include "Buffer.h" #include "CommandEncoder.h" #include "CompilationInfo.h" #include "ComputePipeline.h" #include "DeviceLostInfo.h" #include "ExternalTexture.h" #include "InternalError.h" #include "OutOfMemoryError.h" #include "PipelineLayout.h" #include "QuerySet.h" #include "Queue.h" #include "RenderBundleEncoder.h" #include "RenderPipeline.h" #include "Sampler.h" #include "SupportedFeatures.h" #include "SupportedLimits.h" #include "Texture.h" #include "TextureView.h" #include "Utility.h" #include "ValidationError.h" #include "ipc/WebGPUChild.h" #include "js/ArrayBuffer.h" #include "js/Value.h" #include "mozilla/Attributes.h" #include "mozilla/ErrorResult.h" #include "mozilla/Logging.h" #include "mozilla/RefPtr.h" #include "mozilla/dom/Console.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/VideoFrame.h" #include "mozilla/dom/WebGPUBinding.h" #include "mozilla/gfx/gfxVars.h" #include "nsGlobalWindowInner.h" namespace mozilla::webgpu { mozilla::LazyLogModule gWebGPULog("WebGPU"); NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(Device, DOMEventTargetHelper, mQueue, mFeatures, mLimits, mAdapterInfo, mLostPromise); NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(Device, DOMEventTargetHelper) GPU_IMPL_JS_WRAP(Device) /* static */ CheckedInt Device::BufferStrideWithMask( const gfx::IntSize& aSize, const gfx::SurfaceFormat& aFormat) { constexpr uint32_t kBufferAlignmentMask = 0xff; return CheckedInt(aSize.width) * gfx::BytesPerPixel(aFormat) + kBufferAlignmentMask; } Device::Device(Adapter* const aParent, RawId aDeviceId, RawId aQueueId, RefPtr aFeatures, RefPtr aLimits, RefPtr aAdapterInfo, RefPtr aLostPromise) : DOMEventTargetHelper(aParent->GetParentObject()), ObjectBase(aParent->GetChild(), aDeviceId, ffi::wgpu_client_drop_device), mFeatures(std::move(aFeatures)), mLimits(std::move(aLimits)), mAdapterInfo(std::move(aAdapterInfo)), mSupportSharedTextureInSwapChain( aParent->SupportSharedTextureInSwapChain()), mLostPromise(std::move(aLostPromise)), mQueue(new class Queue(this, aQueueId)) { GetChild()->RegisterDevice(this); KeepAliveIfHasListenersFor(nsGkAtoms::onuncapturederror); } Device::~Device() { GetChild()->UnregisterDevice(GetId()); } void Device::TrackBuffer(Buffer* aBuffer) { mTrackedBuffers.Insert(aBuffer); } void Device::UntrackBuffer(Buffer* aBuffer) { mTrackedBuffers.Remove(aBuffer); } dom::Promise* Device::GetLost(ErrorResult& aRv) { aRv = NS_OK; return mLostPromise; } void Device::ResolveLost(dom::GPUDeviceLostReason aReason, const nsAString& aMessage) { if (mLostPromise->State() != dom::Promise::PromiseState::Pending) { // The lost promise was already resolved or rejected. return; } RefPtr info = MakeRefPtr(GetParentObject(), aReason, aMessage); mLostPromise->MaybeResolve(info); } already_AddRefed Device::CreateBuffer( const dom::GPUBufferDescriptor& aDesc, ErrorResult& aRv) { return Buffer::Create(this, GetId(), aDesc, aRv); } already_AddRefed Device::CreateTextureForSwapChain( const dom::GPUCanvasConfiguration* const aConfig, const gfx::IntSize& aCanvasSize, layers::RemoteTextureOwnerId aOwnerId) { MOZ_ASSERT(aConfig); dom::GPUTextureDescriptor desc; desc.mDimension = dom::GPUTextureDimension::_2d; auto& sizeDict = desc.mSize.SetAsGPUExtent3DDict(); sizeDict.mWidth = aCanvasSize.width; sizeDict.mHeight = aCanvasSize.height; sizeDict.mDepthOrArrayLayers = 1; desc.mFormat = aConfig->mFormat; desc.mMipLevelCount = 1; desc.mSampleCount = 1; desc.mUsage = aConfig->mUsage | dom::GPUTextureUsage_Binding::COPY_SRC; desc.mViewFormats = aConfig->mViewFormats; return CreateTexture(desc, Some(aOwnerId)); } already_AddRefed Device::CreateTexture( const dom::GPUTextureDescriptor& aDesc) { return CreateTexture(aDesc, /* aOwnerId */ Nothing()); } already_AddRefed Device::CreateTexture( const dom::GPUTextureDescriptor& aDesc, Maybe aOwnerId) { ffi::WGPUTextureDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); if (aDesc.mSize.IsRangeEnforcedUnsignedLongSequence()) { const auto& seq = aDesc.mSize.GetAsRangeEnforcedUnsignedLongSequence(); desc.size.width = seq.Length() > 0 ? seq[0] : 1; desc.size.height = seq.Length() > 1 ? seq[1] : 1; desc.size.depth_or_array_layers = seq.Length() > 2 ? seq[2] : 1; } else if (aDesc.mSize.IsGPUExtent3DDict()) { const auto& dict = aDesc.mSize.GetAsGPUExtent3DDict(); desc.size.width = dict.mWidth; desc.size.height = dict.mHeight; desc.size.depth_or_array_layers = dict.mDepthOrArrayLayers; } else { MOZ_CRASH("Unexpected union"); } desc.mip_level_count = aDesc.mMipLevelCount; desc.sample_count = aDesc.mSampleCount; desc.dimension = ffi::WGPUTextureDimension(aDesc.mDimension); desc.format = ConvertTextureFormat(aDesc.mFormat); desc.usage = aDesc.mUsage; AutoTArray viewFormats; for (auto format : aDesc.mViewFormats) { viewFormats.AppendElement(ConvertTextureFormat(format)); } desc.view_formats = {viewFormats.Elements(), viewFormats.Length()}; Maybe ownerId; if (aOwnerId.isSome()) { ownerId = Some(ffi::WGPUSwapChainId{aOwnerId->mId}); } RawId id = ffi::wgpu_client_create_texture(GetClient(), GetId(), &desc, ownerId.ptrOr(nullptr)); RefPtr texture = new Texture(this, id, aDesc); texture->SetLabel(aDesc.mLabel); return texture.forget(); } already_AddRefed Device::ImportExternalTexture( const dom::GPUExternalTextureDescriptor& aDesc, ErrorResult& aRv) { if (!gfx::gfxVars::AllowWebGPUExternalTexture()) { aRv.ThrowNotSupportedError("WebGPU external textures are disabled"); return nullptr; } RefPtr externalTexture = mExternalTextureCache.GetOrCreate(this, aDesc, aRv); switch (aDesc.mSource.GetType()) { case dom::OwningHTMLVideoElementOrVideoFrame::Type::eHTMLVideoElement: { // Add the texture to the list of textures to be expired in the next // automatic expiry task, scheduling the task if required. // Using RunInStableState ensures it runs after any microtasks that may // be scheduled during the current task. if (mExternalTexturesToExpire.IsEmpty()) { nsContentUtils::RunInStableState( NewRunnableMethod("webgpu::Device::ExpireExternalTextures", this, &Device::ExpireExternalTextures)); } mExternalTexturesToExpire.AppendElement(externalTexture); } break; case dom::OwningHTMLVideoElementOrVideoFrame::Type::eVideoFrame: { // Ensure the VideoFrame knows about the external texture, so that it can // expire it when the VideoFrame is closed. const auto& videoFrame = aDesc.mSource.GetAsVideoFrame(); videoFrame->TrackWebGPUExternalTexture(externalTexture.get()); } break; } return externalTexture.forget(); } void Device::ExpireExternalTextures() { MOZ_ASSERT(!mExternalTexturesToExpire.IsEmpty(), "Task should not have been scheduled if there are no external " "textures to expire"); for (const auto& weakExternalTexture : mExternalTexturesToExpire) { if (auto* externalTexture = weakExternalTexture.get()) { externalTexture->Expire(); } } mExternalTexturesToExpire.Clear(); } already_AddRefed Device::CreateSampler( const dom::GPUSamplerDescriptor& aDesc) { ffi::WGPUSamplerDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); desc.address_modes[0] = ffi::WGPUAddressMode(aDesc.mAddressModeU); desc.address_modes[1] = ffi::WGPUAddressMode(aDesc.mAddressModeV); desc.address_modes[2] = ffi::WGPUAddressMode(aDesc.mAddressModeW); desc.mag_filter = ffi::WGPUFilterMode(aDesc.mMagFilter); desc.min_filter = ffi::WGPUFilterMode(aDesc.mMinFilter); desc.mipmap_filter = ffi::WGPUMipmapFilterMode(aDesc.mMipmapFilter); desc.lod_min_clamp = aDesc.mLodMinClamp; desc.lod_max_clamp = aDesc.mLodMaxClamp; desc.max_anisotropy = aDesc.mMaxAnisotropy; ffi::WGPUCompareFunction comparison = ffi::WGPUCompareFunction_Sentinel; if (aDesc.mCompare.WasPassed()) { comparison = ConvertCompareFunction(aDesc.mCompare.Value()); desc.compare = &comparison; } RawId id = ffi::wgpu_client_create_sampler(GetClient(), GetId(), &desc); RefPtr sampler = new Sampler(this, id); sampler->SetLabel(aDesc.mLabel); return sampler.forget(); } already_AddRefed Device::CreateCommandEncoder( const dom::GPUCommandEncoderDescriptor& aDesc) { ffi::WGPUCommandEncoderDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); RawId id = ffi::wgpu_client_create_command_encoder(GetClient(), GetId(), &desc); RefPtr encoder = new CommandEncoder(this, id); encoder->SetLabel(aDesc.mLabel); return encoder.forget(); } already_AddRefed Device::CreateRenderBundleEncoder( const dom::GPURenderBundleEncoderDescriptor& aDesc) { auto id = ffi::wgpu_client_make_render_bundle_encoder_id(GetClient()); RefPtr encoder = new RenderBundleEncoder(this, id, aDesc); encoder->SetLabel(aDesc.mLabel); return encoder.forget(); } already_AddRefed Device::CreateQuerySet( const dom::GPUQuerySetDescriptor& aDesc, ErrorResult& aRv) { ffi::WGPURawQuerySetDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); ffi::WGPURawQueryType type; switch (aDesc.mType) { case dom::GPUQueryType::Occlusion: type = ffi::WGPURawQueryType_Occlusion; break; case dom::GPUQueryType::Timestamp: type = ffi::WGPURawQueryType_Timestamp; if (!mFeatures->Features().count(dom::GPUFeatureName::Timestamp_query)) { aRv.ThrowTypeError( "requested query set of type `timestamp`, but the " "`timestamp-query` feature is not enabled on the device"); return nullptr; } break; }; desc.ty = type; desc.count = aDesc.mCount; RawId id = ffi::wgpu_client_create_query_set(GetClient(), GetId(), &desc); RefPtr querySet = new QuerySet(this, aDesc, id); querySet->SetLabel(aDesc.mLabel); return querySet.forget(); } already_AddRefed Device::CreateBindGroupLayout( const dom::GPUBindGroupLayoutDescriptor& aDesc) { struct OptionalData { ffi::WGPUTextureViewDimension dim; ffi::WGPURawTextureSampleType type; ffi::WGPUTextureFormat format; }; nsTArray optional(aDesc.mEntries.Length()); for (const auto& entry : aDesc.mEntries) { OptionalData data = {}; if (entry.mTexture.WasPassed()) { const auto& texture = entry.mTexture.Value(); data.dim = ffi::WGPUTextureViewDimension(texture.mViewDimension); switch (texture.mSampleType) { case dom::GPUTextureSampleType::Float: data.type = ffi::WGPURawTextureSampleType_Float; break; case dom::GPUTextureSampleType::Unfilterable_float: data.type = ffi::WGPURawTextureSampleType_UnfilterableFloat; break; case dom::GPUTextureSampleType::Uint: data.type = ffi::WGPURawTextureSampleType_Uint; break; case dom::GPUTextureSampleType::Sint: data.type = ffi::WGPURawTextureSampleType_Sint; break; case dom::GPUTextureSampleType::Depth: data.type = ffi::WGPURawTextureSampleType_Depth; break; } } if (entry.mStorageTexture.WasPassed()) { const auto& texture = entry.mStorageTexture.Value(); data.dim = ffi::WGPUTextureViewDimension(texture.mViewDimension); data.format = ConvertTextureFormat(texture.mFormat); } optional.AppendElement(data); } nsTArray entries(aDesc.mEntries.Length()); for (size_t i = 0; i < aDesc.mEntries.Length(); ++i) { const auto& entry = aDesc.mEntries[i]; ffi::WGPUBindGroupLayoutEntry e = {}; e.binding = entry.mBinding; e.visibility = entry.mVisibility; if (entry.mBuffer.WasPassed()) { switch (entry.mBuffer.Value().mType) { case dom::GPUBufferBindingType::Uniform: e.ty = ffi::WGPURawBindingType_UniformBuffer; break; case dom::GPUBufferBindingType::Storage: e.ty = ffi::WGPURawBindingType_StorageBuffer; break; case dom::GPUBufferBindingType::Read_only_storage: e.ty = ffi::WGPURawBindingType_ReadonlyStorageBuffer; break; } e.has_dynamic_offset = entry.mBuffer.Value().mHasDynamicOffset; e.min_binding_size = entry.mBuffer.Value().mMinBindingSize; } if (entry.mTexture.WasPassed()) { e.ty = ffi::WGPURawBindingType_SampledTexture; e.view_dimension = &optional[i].dim; e.texture_sample_type = &optional[i].type; e.multisampled = entry.mTexture.Value().mMultisampled; } if (entry.mStorageTexture.WasPassed()) { switch (entry.mStorageTexture.Value().mAccess) { case dom::GPUStorageTextureAccess::Write_only: { e.ty = ffi::WGPURawBindingType_WriteonlyStorageTexture; break; } case dom::GPUStorageTextureAccess::Read_only: { e.ty = ffi::WGPURawBindingType_ReadonlyStorageTexture; break; } case dom::GPUStorageTextureAccess::Read_write: { e.ty = ffi::WGPURawBindingType_ReadWriteStorageTexture; break; } default: { MOZ_ASSERT_UNREACHABLE(); } } e.view_dimension = &optional[i].dim; e.storage_texture_format = &optional[i].format; } if (entry.mSampler.WasPassed()) { e.ty = ffi::WGPURawBindingType_Sampler; switch (entry.mSampler.Value().mType) { case dom::GPUSamplerBindingType::Filtering: e.sampler_filter = true; break; case dom::GPUSamplerBindingType::Non_filtering: break; case dom::GPUSamplerBindingType::Comparison: e.sampler_compare = true; break; } } if (entry.mExternalTexture.WasPassed()) { e.ty = ffi::WGPURawBindingType_ExternalTexture; } entries.AppendElement(e); } ffi::WGPUBindGroupLayoutDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); desc.entries = {entries.Elements(), entries.Length()}; RawId id = ffi::wgpu_client_create_bind_group_layout(GetClient(), GetId(), &desc); RefPtr object = new BindGroupLayout(this, id); object->SetLabel(aDesc.mLabel); return object.forget(); } already_AddRefed Device::CreatePipelineLayout( const dom::GPUPipelineLayoutDescriptor& aDesc) { nsTArray bindGroupLayouts( aDesc.mBindGroupLayouts.Length()); for (const auto& layout : aDesc.mBindGroupLayouts) { bindGroupLayouts.AppendElement(layout->GetId()); } ffi::WGPUPipelineLayoutDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); desc.bind_group_layouts = {bindGroupLayouts.Elements(), bindGroupLayouts.Length()}; RawId id = ffi::wgpu_client_create_pipeline_layout(GetClient(), GetId(), &desc); RefPtr object = new PipelineLayout(this, id); object->SetLabel(aDesc.mLabel); return object.forget(); } already_AddRefed Device::CreateBindGroup( const dom::GPUBindGroupDescriptor& aDesc) { nsTArray entries(aDesc.mEntries.Length()); CanvasContextArray canvasContexts; nsTArray> externalTextures; for (const auto& entry : aDesc.mEntries) { ffi::WGPUBindGroupEntry e = {}; e.binding = entry.mBinding; auto setTextureViewBinding = [&e, &canvasContexts](const TextureView& texture_view) { e.texture_view = texture_view.GetId(); auto context = texture_view.GetTargetContext(); if (context) { canvasContexts.AppendElement(context); } }; if (entry.mResource.IsGPUBuffer()) { const auto& buffer = entry.mResource.GetAsGPUBuffer(); if (!buffer->GetId()) { NS_WARNING("Buffer has no id -- ignoring."); continue; } e.buffer = buffer->GetId(); e.offset = 0; e.size = 0; } else if (entry.mResource.IsGPUBufferBinding()) { const auto& bufBinding = entry.mResource.GetAsGPUBufferBinding(); if (!bufBinding.mBuffer->GetId()) { NS_WARNING("Buffer binding has no id -- ignoring."); continue; } e.buffer = bufBinding.mBuffer->GetId(); e.offset = bufBinding.mOffset; e.size = bufBinding.mSize.WasPassed() ? bufBinding.mSize.Value() : 0; } else if (entry.mResource.IsGPUTexture()) { auto texture = entry.mResource.GetAsGPUTexture(); const dom::GPUTextureViewDescriptor defaultDesc{}; RefPtr texture_view = texture->CreateView(defaultDesc); setTextureViewBinding(*texture_view); } else if (entry.mResource.IsGPUTextureView()) { auto texture_view = entry.mResource.GetAsGPUTextureView(); setTextureViewBinding(texture_view); } else if (entry.mResource.IsGPUSampler()) { e.sampler = entry.mResource.GetAsGPUSampler()->GetId(); } else if (entry.mResource.IsGPUExternalTexture()) { const RefPtr externalTexture = entry.mResource.GetAsGPUExternalTexture(); e.external_texture = externalTexture->GetId(); externalTextures.AppendElement(externalTexture); } else { // Not a buffer, nor a texture view, nor a sampler, nor an external // texture. If we pass this to wgpu_client, it'll panic. Log a warning // instead and ignore this entry. NS_WARNING("Bind group entry has unknown type."); continue; } entries.AppendElement(e); } ffi::WGPUBindGroupDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); desc.layout = aDesc.mLayout->GetId(); desc.entries = {entries.Elements(), entries.Length()}; RawId id = ffi::wgpu_client_create_bind_group(GetClient(), GetId(), &desc); RefPtr object = new BindGroup(this, id, std::move(canvasContexts), std::move(externalTextures)); object->SetLabel(aDesc.mLabel); return object.forget(); } void reportCompilationMessagesToConsole( const RefPtr& aShaderModule, const nsTArray& aMessages) { auto* global = aShaderModule->GetParentObject(); dom::AutoJSAPI api; if (!api.Init(global)) { return; } const auto& cx = api.cx(); dom::GlobalObject globalObj(cx, global->GetGlobalJSObject()); dom::Sequence args; dom::SequenceRooter msgArgsRooter(cx, &args); auto SetSingleStrAsArgs = [&](const nsString& message, dom::Sequence* args) MOZ_CAN_RUN_SCRIPT { args->Clear(); JS::Rooted jsStr( cx, JS_NewUCStringCopyN(cx, message.Data(), message.Length())); if (!jsStr) { return; } JS::Rooted val(cx, JS::StringValue(jsStr)); if (!args->AppendElement(val, fallible)) { return; } }; nsString label; aShaderModule->GetLabel(label); auto appendNiceLabelIfPresent = [&label](nsString* buf) MOZ_CAN_RUN_SCRIPT { if (!label.IsEmpty()) { buf->AppendLiteral(u" \""); buf->Append(label); buf->AppendLiteral(u"\""); } }; // We haven't actually inspected a message for severity, but // it doesn't actually matter, since we don't do anything at // this level. auto highestSeveritySeen = WebGPUCompilationMessageType::Info; uint64_t errorCount = 0; uint64_t warningCount = 0; uint64_t infoCount = 0; for (const auto& message : aMessages) { bool higherThanSeen = static_cast>( message.messageType) < static_cast>( highestSeveritySeen); if (higherThanSeen) { highestSeveritySeen = message.messageType; } switch (message.messageType) { case WebGPUCompilationMessageType::Error: errorCount += 1; break; case WebGPUCompilationMessageType::Warning: warningCount += 1; break; case WebGPUCompilationMessageType::Info: infoCount += 1; break; } } switch (highestSeveritySeen) { case WebGPUCompilationMessageType::Info: // shouldn't happen, but :shrug: break; case WebGPUCompilationMessageType::Warning: { nsString msg( u"Encountered one or more warnings while creating shader module"); appendNiceLabelIfPresent(&msg); SetSingleStrAsArgs(msg, &args); dom::Console::Warn(globalObj, args); break; } case WebGPUCompilationMessageType::Error: { nsString msg( u"Encountered one or more errors while creating shader module"); appendNiceLabelIfPresent(&msg); SetSingleStrAsArgs(msg, &args); dom::Console::Error(globalObj, args); break; } } nsString header; header.AppendLiteral(u"WebGPU compilation info for shader module"); appendNiceLabelIfPresent(&header); header.AppendLiteral(u" ("); header.AppendInt(errorCount); header.AppendLiteral(u" error(s), "); header.AppendInt(warningCount); header.AppendLiteral(u" warning(s), "); header.AppendInt(infoCount); header.AppendLiteral(u" info)"); SetSingleStrAsArgs(header, &args); dom::Console::GroupCollapsed(globalObj, args); for (const auto& message : aMessages) { SetSingleStrAsArgs(message.message, &args); switch (message.messageType) { case WebGPUCompilationMessageType::Error: dom::Console::Error(globalObj, args); break; case WebGPUCompilationMessageType::Warning: dom::Console::Warn(globalObj, args); break; case WebGPUCompilationMessageType::Info: dom::Console::Info(globalObj, args); break; } } dom::Console::GroupEnd(globalObj); } already_AddRefed Device::CreateShaderModule( const dom::GPUShaderModuleDescriptor& aDesc, ErrorResult& aRv) { RefPtr promise = dom::Promise::Create(GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } webgpu::StringHelper label(aDesc.mLabel); RawId moduleId = ffi::wgpu_client_create_shader_module( GetClient(), GetId(), label.Get(), &aDesc.mCode); RefPtr shaderModule = new ShaderModule(this, moduleId, promise); shaderModule->SetLabel(aDesc.mLabel); auto pending_promise = WebGPUChild::PendingCreateShaderModulePromise{ RefPtr(promise), RefPtr(this), RefPtr(shaderModule)}; GetChild()->mPendingCreateShaderModulePromises.push_back( std::move(pending_promise)); return shaderModule.forget(); } RawId CreateComputePipelineImpl(RawId deviceId, WebGPUChild* aChild, const dom::GPUComputePipelineDescriptor& aDesc, bool isAsync) { ffi::WGPUComputePipelineDescriptor desc = {}; nsCString entryPoint; nsTArray constantKeys; nsTArray constants; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); if (aDesc.mLayout.IsGPUAutoLayoutMode()) { desc.layout = 0; } else if (aDesc.mLayout.IsGPUPipelineLayout()) { desc.layout = aDesc.mLayout.GetAsGPUPipelineLayout()->GetId(); } else { MOZ_ASSERT_UNREACHABLE(); } desc.stage.module = aDesc.mCompute.mModule->GetId(); if (aDesc.mCompute.mEntryPoint.WasPassed()) { CopyUTF16toUTF8(aDesc.mCompute.mEntryPoint.Value(), entryPoint); desc.stage.entry_point = entryPoint.get(); } else { desc.stage.entry_point = nullptr; } if (aDesc.mCompute.mConstants.WasPassed()) { const auto& descConstants = aDesc.mCompute.mConstants.Value().Entries(); constantKeys.SetCapacity(descConstants.Length()); constants.SetCapacity(descConstants.Length()); for (const auto& entry : descConstants) { ffi::WGPUConstantEntry constantEntry = {}; nsCString key = NS_ConvertUTF16toUTF8(entry.mKey); constantKeys.AppendElement(key); constantEntry.key = key.get(); constantEntry.value = entry.mValue; constants.AppendElement(constantEntry); } desc.stage.constants = {constants.Elements(), constants.Length()}; } RawId id = ffi::wgpu_client_create_compute_pipeline(aChild->GetClient(), deviceId, &desc, isAsync); return id; } RawId CreateRenderPipelineImpl(RawId deviceId, WebGPUChild* aChild, const dom::GPURenderPipelineDescriptor& aDesc, bool isAsync) { // A bunch of stack locals that we can have pointers into nsTArray vertexBuffers; nsTArray vertexAttributes; ffi::WGPURenderPipelineDescriptor desc = {}; nsCString vsEntry, fsEntry; nsTArray vsConstantKeys, fsConstantKeys; nsTArray vsConstants, fsConstants; ffi::WGPUIndexFormat stripIndexFormat = ffi::WGPUIndexFormat_Uint16; ffi::WGPUFace cullFace = ffi::WGPUFace_Front; ffi::WGPUVertexState vertexState = {}; ffi::WGPUFragmentState fragmentState = {}; nsTArray colorStates; nsTArray blendStates; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); if (aDesc.mLayout.IsGPUAutoLayoutMode()) { desc.layout = 0; } else if (aDesc.mLayout.IsGPUPipelineLayout()) { desc.layout = aDesc.mLayout.GetAsGPUPipelineLayout()->GetId(); } else { MOZ_ASSERT_UNREACHABLE(); } { const auto& stage = aDesc.mVertex; vertexState.stage.module = stage.mModule->GetId(); if (stage.mEntryPoint.WasPassed()) { CopyUTF16toUTF8(stage.mEntryPoint.Value(), vsEntry); vertexState.stage.entry_point = vsEntry.get(); } else { vertexState.stage.entry_point = nullptr; } if (stage.mConstants.WasPassed()) { const auto& descConstants = stage.mConstants.Value().Entries(); vsConstantKeys.SetCapacity(descConstants.Length()); vsConstants.SetCapacity(descConstants.Length()); for (const auto& entry : descConstants) { ffi::WGPUConstantEntry constantEntry = {}; nsCString key = NS_ConvertUTF16toUTF8(entry.mKey); vsConstantKeys.AppendElement(key); constantEntry.key = key.get(); constantEntry.value = entry.mValue; vsConstants.AppendElement(constantEntry); } vertexState.stage.constants = {vsConstants.Elements(), vsConstants.Length()}; } for (const auto& vertex_desc : stage.mBuffers) { ffi::WGPUVertexBufferLayout vb_desc = {}; if (!vertex_desc.IsNull()) { const auto& vd = vertex_desc.Value(); vb_desc.array_stride = vd.mArrayStride; vb_desc.step_mode = ffi::WGPUVertexStepMode(vd.mStepMode); // Note: we are setting the length but not the pointer vb_desc.attributes = {nullptr, vd.mAttributes.Length()}; for (const auto& vat : vd.mAttributes) { ffi::WGPUVertexAttribute ad = {}; ad.offset = vat.mOffset; ad.format = ConvertVertexFormat(vat.mFormat); ad.shader_location = vat.mShaderLocation; vertexAttributes.AppendElement(ad); } } vertexBuffers.AppendElement(vb_desc); } // Now patch up all the pointers to attribute lists. size_t numAttributes = 0; for (auto& vb_desc : vertexBuffers) { vb_desc.attributes.data = vertexAttributes.Elements() + numAttributes; numAttributes += vb_desc.attributes.length; } vertexState.buffers = {vertexBuffers.Elements(), vertexBuffers.Length()}; desc.vertex = &vertexState; } if (aDesc.mFragment.WasPassed()) { const auto& stage = aDesc.mFragment.Value(); fragmentState.stage.module = stage.mModule->GetId(); if (stage.mEntryPoint.WasPassed()) { CopyUTF16toUTF8(stage.mEntryPoint.Value(), fsEntry); fragmentState.stage.entry_point = fsEntry.get(); } else { fragmentState.stage.entry_point = nullptr; } if (stage.mConstants.WasPassed()) { const auto& descConstants = stage.mConstants.Value().Entries(); fsConstantKeys.SetCapacity(descConstants.Length()); fsConstants.SetCapacity(descConstants.Length()); for (const auto& entry : descConstants) { ffi::WGPUConstantEntry constantEntry = {}; nsCString key = NS_ConvertUTF16toUTF8(entry.mKey); fsConstantKeys.AppendElement(key); constantEntry.key = key.get(); constantEntry.value = entry.mValue; fsConstants.AppendElement(constantEntry); } fragmentState.stage.constants = {fsConstants.Elements(), fsConstants.Length()}; } // Note: we pre-collect the blend states into a different array // so that we can have non-stale pointers into it. for (const auto& colorState : stage.mTargets) { ffi::WGPUColorTargetState desc = {}; desc.format = ConvertTextureFormat(colorState.mFormat); desc.write_mask = colorState.mWriteMask; colorStates.AppendElement(desc); ffi::WGPUBlendState bs = {}; if (colorState.mBlend.WasPassed()) { const auto& blend = colorState.mBlend.Value(); bs.alpha = ConvertBlendComponent(blend.mAlpha); bs.color = ConvertBlendComponent(blend.mColor); } blendStates.AppendElement(bs); } for (size_t i = 0; i < colorStates.Length(); ++i) { if (stage.mTargets[i].mBlend.WasPassed()) { colorStates[i].blend = &blendStates[i]; } } fragmentState.targets = {colorStates.Elements(), colorStates.Length()}; desc.fragment = &fragmentState; } { const auto& prim = aDesc.mPrimitive; desc.primitive.topology = ffi::WGPUPrimitiveTopology(prim.mTopology); if (prim.mStripIndexFormat.WasPassed()) { stripIndexFormat = ffi::WGPUIndexFormat(prim.mStripIndexFormat.Value()); desc.primitive.strip_index_format = &stripIndexFormat; } desc.primitive.front_face = ffi::WGPUFrontFace(prim.mFrontFace); if (prim.mCullMode != dom::GPUCullMode::None) { cullFace = prim.mCullMode == dom::GPUCullMode::Front ? ffi::WGPUFace_Front : ffi::WGPUFace_Back; desc.primitive.cull_mode = &cullFace; } desc.primitive.unclipped_depth = prim.mUnclippedDepth; } desc.multisample = ConvertMultisampleState(aDesc.mMultisample); ffi::WGPUDepthStencilState depthStencilState = {}; if (aDesc.mDepthStencil.WasPassed()) { depthStencilState = ConvertDepthStencilState(aDesc.mDepthStencil.Value()); desc.depth_stencil = &depthStencilState; } RawId id = ffi::wgpu_client_create_render_pipeline(aChild->GetClient(), deviceId, &desc, isAsync); return id; } already_AddRefed Device::CreateComputePipeline( const dom::GPUComputePipelineDescriptor& aDesc) { RawId pipelineId = CreateComputePipelineImpl(GetId(), GetChild(), aDesc, false); RefPtr object = new ComputePipeline(this, pipelineId); object->SetLabel(aDesc.mLabel); return object.forget(); } already_AddRefed Device::CreateRenderPipeline( const dom::GPURenderPipelineDescriptor& aDesc) { RawId pipelineId = CreateRenderPipelineImpl(GetId(), GetChild(), aDesc, false); RefPtr object = new RenderPipeline(this, pipelineId); object->SetLabel(aDesc.mLabel); return object.forget(); } already_AddRefed Device::CreateComputePipelineAsync( const dom::GPUComputePipelineDescriptor& aDesc, ErrorResult& aRv) { RefPtr promise = dom::Promise::Create(GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RawId pipelineId = CreateComputePipelineImpl(GetId(), GetChild(), aDesc, true); auto pending_promise = WebGPUChild::PendingCreatePipelinePromise{ RefPtr(promise), RefPtr(this), false, pipelineId, aDesc.mLabel}; GetChild()->mPendingCreatePipelinePromises.push_back( std::move(pending_promise)); return promise.forget(); } already_AddRefed Device::CreateRenderPipelineAsync( const dom::GPURenderPipelineDescriptor& aDesc, ErrorResult& aRv) { RefPtr promise = dom::Promise::Create(GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RawId pipelineId = CreateRenderPipelineImpl(GetId(), GetChild(), aDesc, true); auto pending_promise = WebGPUChild::PendingCreatePipelinePromise{ RefPtr(promise), RefPtr(this), true, pipelineId, aDesc.mLabel}; GetChild()->mPendingCreatePipelinePromises.push_back( std::move(pending_promise)); return promise.forget(); } already_AddRefed Device::InitSwapChain( const dom::GPUCanvasConfiguration* const aConfig, const layers::RemoteTextureOwnerId aOwnerId, bool aUseSharedTextureInSwapChain, gfx::SurfaceFormat aFormat, gfx::IntSize aCanvasSize) { MOZ_ASSERT(aConfig); // Check that aCanvasSize and aFormat will generate a texture stride // within limits. const auto bufferStrideWithMask = BufferStrideWithMask(aCanvasSize, aFormat); if (!bufferStrideWithMask.isValid()) { return nullptr; } const layers::RGBDescriptor rgbDesc(aCanvasSize, aFormat); ffi::wgpu_client_create_swap_chain( GetClient(), GetId(), mQueue->GetId(), rgbDesc.size().Width(), rgbDesc.size().Height(), (int8_t)rgbDesc.format(), aOwnerId.mId, aUseSharedTextureInSwapChain); // TODO: `mColorSpace`: // TODO: `mAlphaMode`: return CreateTextureForSwapChain(aConfig, aCanvasSize, aOwnerId); } bool Device::CheckNewWarning(const nsACString& aMessage) { return mKnownWarnings.EnsureInserted(aMessage); } void Device::Destroy() { // Unmap all buffers from this device, as specified by // https://gpuweb.github.io/gpuweb/#dom-gpudevice-destroy. dom::AutoJSAPI jsapi; if (jsapi.Init(GetOwnerGlobal())) { IgnoredErrorResult rv; for (const auto& buffer : mTrackedBuffers) { buffer->Unmap(jsapi.cx(), rv); } mTrackedBuffers.Clear(); } ffi::wgpu_client_destroy_device(GetClient(), GetId()); if (mLostPromise->State() != dom::Promise::PromiseState::Pending) { return; } RefPtr pending_promise = mLostPromise; GetChild()->mPendingDeviceLostPromises.insert( {GetId(), std::move(pending_promise)}); } void Device::PushErrorScope(const dom::GPUErrorFilter& aFilter) { ffi::wgpu_client_push_error_scope(GetClient(), GetId(), (uint8_t)aFilter); } already_AddRefed Device::PopErrorScope(ErrorResult& aRv) { RefPtr promise = dom::Promise::Create(GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } ffi::wgpu_client_pop_error_scope(GetClient(), GetId()); auto pending_promise = WebGPUChild::PendingPopErrorScopePromise{RefPtr(promise), RefPtr(this)}; GetChild()->mPendingPopErrorScopePromises.push_back( std::move(pending_promise)); return promise.forget(); } } // namespace mozilla::webgpu