/* -*- 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 "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/WebGPUBinding.h" #include "Device.h" #include "CommandEncoder.h" #include "BindGroup.h" #include "Adapter.h" #include "Buffer.h" #include "CompilationInfo.h" #include "ComputePipeline.h" #include "DeviceLostInfo.h" #include "InternalError.h" #include "OutOfMemoryError.h" #include "PipelineLayout.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 "ValidationError.h" #include "ipc/WebGPUChild.h" #include "Utility.h" #include "nsGlobalWindowInner.h" namespace mozilla::webgpu { mozilla::LazyLogModule gWebGPULog("WebGPU"); GPU_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(Device, DOMEventTargetHelper, mBridge, mQueue, mFeatures, mLimits, 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; } RefPtr Device::GetBridge() { return mBridge; } Device::Device(Adapter* const aParent, RawId aId, const ffi::WGPULimits& aRawLimits) : DOMEventTargetHelper(aParent->GetParentObject()), mId(aId), // features are filled in Adapter::RequestDevice mFeatures(new SupportedFeatures(aParent)), mLimits(new SupportedLimits(aParent, aRawLimits)), mBridge(aParent->mBridge), mQueue(new class Queue(this, aParent->mBridge, aId)) { mBridge->RegisterDevice(this); } Device::~Device() { Cleanup(); } void Device::Cleanup() { if (!mValid) { return; } mValid = false; if (mBridge) { mBridge->UnregisterDevice(mId); } } void Device::CleanupUnregisteredInParent() { if (mBridge) { mBridge->FreeUnregisteredInParentDevice(mId); } mValid = false; } bool Device::IsLost() const { return !mBridge || !mBridge->CanSend() || (mLostPromise && (mLostPromise->State() != dom::Promise::PromiseState::Pending)); } bool Device::IsBridgeAlive() const { return mBridge && mBridge->CanSend(); } // Generate an error on the Device timeline for this device. // // aMessage is interpreted as UTF-8. void Device::GenerateValidationError(const nsCString& aMessage) { if (!IsBridgeAlive()) { return; // Just drop it? } mBridge->SendGenerateError(Some(mId), dom::GPUErrorFilter::Validation, aMessage); } void Device::TrackBuffer(Buffer* aBuffer) { mTrackedBuffers.Insert(aBuffer); } void Device::UntrackBuffer(Buffer* aBuffer) { mTrackedBuffers.Remove(aBuffer); } void Device::GetLabel(nsAString& aValue) const { aValue = mLabel; } void Device::SetLabel(const nsAString& aLabel) { mLabel = aLabel; } dom::Promise* Device::GetLost(ErrorResult& aRv) { aRv = NS_OK; if (!mLostPromise) { mLostPromise = dom::Promise::Create(GetParentObject(), aRv); if (mLostPromise && !mBridge->CanSend()) { auto info = MakeRefPtr(GetParentObject(), u"WebGPUChild destroyed"_ns); mLostPromise->MaybeResolve(info); } } return mLostPromise; } void Device::ResolveLost(Maybe aReason, const nsAString& aMessage) { IgnoredErrorResult rv; dom::Promise* lostPromise = GetLost(rv); if (!lostPromise) { // Promise doesn't exist? Maybe out of memory. return; } if (!lostPromise->PromiseObj()) { // The underlying JS object is gone. return; } if (lostPromise->State() != dom::Promise::PromiseState::Pending) { // lostPromise was already resolved or rejected. return; } RefPtr info; if (aReason.isSome()) { info = MakeRefPtr(GetParentObject(), *aReason, aMessage); } else { info = MakeRefPtr(GetParentObject(), aMessage); } lostPromise->MaybeResolve(info); } already_AddRefed Device::CreateBuffer( const dom::GPUBufferDescriptor& aDesc, ErrorResult& aRv) { return Buffer::Create(this, mId, 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}); } ipc::ByteBuf bb; RawId id = ffi::wgpu_client_create_texture( mBridge->GetClient(), mId, &desc, ownerId.ptrOr(nullptr), ToFFI(&bb)); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr texture = new Texture(this, id, aDesc); return texture.forget(); } 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::WGPUFilterMode(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; } ipc::ByteBuf bb; RawId id = ffi::wgpu_client_create_sampler(mBridge->GetClient(), mId, &desc, ToFFI(&bb)); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr sampler = new Sampler(this, id); return sampler.forget(); } already_AddRefed Device::CreateCommandEncoder( const dom::GPUCommandEncoderDescriptor& aDesc) { ffi::WGPUCommandEncoderDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); ipc::ByteBuf bb; RawId id = ffi::wgpu_client_create_command_encoder(mBridge->GetClient(), mId, &desc, ToFFI(&bb)); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr encoder = new CommandEncoder(this, mBridge, id); return encoder.forget(); } already_AddRefed Device::CreateRenderBundleEncoder( const dom::GPURenderBundleEncoderDescriptor& aDesc) { RefPtr encoder = new RenderBundleEncoder(this, mBridge, aDesc); return encoder.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; } 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; } } entries.AppendElement(e); } ffi::WGPUBindGroupLayoutDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); desc.entries = entries.Elements(); desc.entries_length = entries.Length(); ipc::ByteBuf bb; RawId id = ffi::wgpu_client_create_bind_group_layout(mBridge->GetClient(), mId, &desc, ToFFI(&bb)); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr object = new BindGroupLayout(this, id, true); 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->mId); } ffi::WGPUPipelineLayoutDescriptor desc = {}; webgpu::StringHelper label(aDesc.mLabel); desc.label = label.Get(); desc.bind_group_layouts = bindGroupLayouts.Elements(); desc.bind_group_layouts_length = bindGroupLayouts.Length(); ipc::ByteBuf bb; RawId id = ffi::wgpu_client_create_pipeline_layout(mBridge->GetClient(), mId, &desc, ToFFI(&bb)); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr object = new PipelineLayout(this, id); return object.forget(); } already_AddRefed Device::CreateBindGroup( const dom::GPUBindGroupDescriptor& aDesc) { nsTArray entries(aDesc.mEntries.Length()); for (const auto& entry : aDesc.mEntries) { ffi::WGPUBindGroupEntry e = {}; e.binding = entry.mBinding; if (entry.mResource.IsGPUBufferBinding()) { const auto& bufBinding = entry.mResource.GetAsGPUBufferBinding(); if (!bufBinding.mBuffer->mId) { NS_WARNING("Buffer binding has no id -- ignoring."); continue; } e.buffer = bufBinding.mBuffer->mId; e.offset = bufBinding.mOffset; e.size = bufBinding.mSize.WasPassed() ? bufBinding.mSize.Value() : 0; } else if (entry.mResource.IsGPUTextureView()) { e.texture_view = entry.mResource.GetAsGPUTextureView()->mId; } else if (entry.mResource.IsGPUSampler()) { e.sampler = entry.mResource.GetAsGPUSampler()->mId; } else { // Not a buffer, nor a texture view, nor a sampler. 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->mId; desc.entries = entries.Elements(); desc.entries_length = entries.Length(); ipc::ByteBuf bb; RawId id = ffi::wgpu_client_create_bind_group(mBridge->GetClient(), mId, &desc, ToFFI(&bb)); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr object = new BindGroup(this, id); return object.forget(); } MOZ_CAN_RUN_SCRIPT 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(); ErrorResult rv; RefPtr console = nsGlobalWindowInner::Cast(global->GetAsInnerWindow())->GetConsole(cx, rv); if (rv.Failed()) { return; } 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); 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); 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); console->GroupCollapsed(globalObj, args); for (const auto& message : aMessages) { SetSingleStrAsArgs(message.message, &args); switch (message.messageType) { case WebGPUCompilationMessageType::Error: console->Error(globalObj, args); break; case WebGPUCompilationMessageType::Warning: console->Warn(globalObj, args); break; case WebGPUCompilationMessageType::Info: console->Info(globalObj, args); break; } } console->GroupEnd(globalObj); } already_AddRefed Device::CreateShaderModule( JSContext* aCx, const dom::GPUShaderModuleDescriptor& aDesc, ErrorResult& aRv) { Unused << aCx; RefPtr promise = dom::Promise::Create(GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RawId moduleId = ffi::wgpu_client_make_shader_module_id(mBridge->GetClient(), mId); RefPtr shaderModule = new ShaderModule(this, moduleId, promise); shaderModule->SetLabel(aDesc.mLabel); RefPtr device = this; if (mBridge->CanSend()) { mBridge ->SendDeviceCreateShaderModule(mId, moduleId, aDesc.mLabel, aDesc.mCode) ->Then( GetCurrentSerialEventTarget(), __func__, [promise, device, shaderModule](nsTArray&& messages) MOZ_CAN_RUN_SCRIPT { if (!messages.IsEmpty()) { reportCompilationMessagesToConsole(shaderModule, std::cref(messages)); } RefPtr infoObject( new CompilationInfo(device)); infoObject->SetMessages(messages); promise->MaybeResolve(infoObject); }, [promise](const ipc::ResponseRejectReason& aReason) { promise->MaybeRejectWithNotSupportedError("IPC error"); }); } else { promise->MaybeRejectWithNotSupportedError("IPC error"); } return shaderModule.forget(); } RawId CreateComputePipelineImpl(PipelineCreationContext* const aContext, WebGPUChild* aBridge, const dom::GPUComputePipelineDescriptor& aDesc, ipc::ByteBuf* const aByteBuf) { 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()->mId; } else { MOZ_ASSERT_UNREACHABLE(); } desc.stage.module = aDesc.mCompute.mModule->mId; 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(); desc.stage.constants_length = constants.Length(); } RawId implicit_bgl_ids[WGPUMAX_BIND_GROUPS] = {}; RawId id = ffi::wgpu_client_create_compute_pipeline( aBridge->GetClient(), aContext->mParentId, &desc, ToFFI(aByteBuf), &aContext->mImplicitPipelineLayoutId, implicit_bgl_ids); for (const auto& cur : implicit_bgl_ids) { if (!cur) break; aContext->mImplicitBindGroupLayoutIds.AppendElement(cur); } return id; } RawId CreateRenderPipelineImpl(PipelineCreationContext* const aContext, WebGPUChild* aBridge, const dom::GPURenderPipelineDescriptor& aDesc, ipc::ByteBuf* const aByteBuf) { // 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()->mId; } else { MOZ_ASSERT_UNREACHABLE(); } { const auto& stage = aDesc.mVertex; vertexState.stage.module = stage.mModule->mId; 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(); vertexState.stage.constants_length = 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_length = vd.mAttributes.Length(); for (const auto& vat : vd.mAttributes) { ffi::WGPUVertexAttribute ad = {}; ad.offset = vat.mOffset; ad.format = ffi::WGPUVertexFormat(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 = vertexAttributes.Elements() + numAttributes; numAttributes += vb_desc.attributes_length; } vertexState.buffers = vertexBuffers.Elements(); vertexState.buffers_length = vertexBuffers.Length(); desc.vertex = &vertexState; } if (aDesc.mFragment.WasPassed()) { const auto& stage = aDesc.mFragment.Value(); fragmentState.stage.module = stage.mModule->mId; 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(); fragmentState.stage.constants_length = 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(); fragmentState.targets_length = 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 implicit_bgl_ids[WGPUMAX_BIND_GROUPS] = {}; RawId id = ffi::wgpu_client_create_render_pipeline( aBridge->GetClient(), aContext->mParentId, &desc, ToFFI(aByteBuf), &aContext->mImplicitPipelineLayoutId, implicit_bgl_ids); for (const auto& cur : implicit_bgl_ids) { if (!cur) break; aContext->mImplicitBindGroupLayoutIds.AppendElement(cur); } return id; } already_AddRefed Device::CreateComputePipeline( const dom::GPUComputePipelineDescriptor& aDesc) { PipelineCreationContext context = {mId}; ipc::ByteBuf bb; RawId id = CreateComputePipelineImpl(&context, mBridge, aDesc, &bb); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr object = new ComputePipeline(this, id, context.mImplicitPipelineLayoutId, std::move(context.mImplicitBindGroupLayoutIds)); return object.forget(); } already_AddRefed Device::CreateRenderPipeline( const dom::GPURenderPipelineDescriptor& aDesc) { PipelineCreationContext context = {mId}; ipc::ByteBuf bb; RawId id = CreateRenderPipelineImpl(&context, mBridge, aDesc, &bb); if (mBridge->CanSend()) { mBridge->SendDeviceAction(mId, std::move(bb)); } RefPtr object = new RenderPipeline(this, id, context.mImplicitPipelineLayoutId, std::move(context.mImplicitBindGroupLayoutIds)); 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; } std::shared_ptr context( new PipelineCreationContext()); context->mParentId = mId; ipc::ByteBuf bb; RawId pipelineId = CreateComputePipelineImpl(context.get(), mBridge, aDesc, &bb); if (mBridge->CanSend()) { mBridge->SendDeviceActionWithAck(mId, std::move(bb)) ->Then( GetCurrentSerialEventTarget(), __func__, [self = RefPtr{this}, context, pipelineId, promise](bool aDummy) { Unused << aDummy; RefPtr object = new ComputePipeline( self, pipelineId, context->mImplicitPipelineLayoutId, std::move(context->mImplicitBindGroupLayoutIds)); promise->MaybeResolve(object); }, [promise](const ipc::ResponseRejectReason&) { promise->MaybeRejectWithOperationError( "Internal communication error"); }); } else { promise->MaybeRejectWithOperationError("Internal communication error"); } 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; } std::shared_ptr context( new PipelineCreationContext()); context->mParentId = mId; ipc::ByteBuf bb; RawId pipelineId = CreateRenderPipelineImpl(context.get(), mBridge, aDesc, &bb); if (mBridge->CanSend()) { mBridge->SendDeviceActionWithAck(mId, std::move(bb)) ->Then( GetCurrentSerialEventTarget(), __func__, [self = RefPtr{this}, context, promise, pipelineId](bool aDummy) { Unused << aDummy; RefPtr object = new RenderPipeline( self, pipelineId, context->mImplicitPipelineLayoutId, std::move(context->mImplicitBindGroupLayoutIds)); promise->MaybeResolve(object); }, [promise](const ipc::ResponseRejectReason&) { promise->MaybeRejectWithOperationError( "Internal communication error"); }); } else { promise->MaybeRejectWithOperationError("Internal communication error"); } return promise.forget(); } already_AddRefed Device::InitSwapChain( const dom::GPUCanvasConfiguration* const aConfig, const layers::RemoteTextureOwnerId aOwnerId, bool aUseExternalTextureInSwapChain, gfx::SurfaceFormat aFormat, gfx::IntSize aCanvasSize) { MOZ_ASSERT(aConfig); if (!mBridge->CanSend()) { return nullptr; } // 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); // buffer count doesn't matter much, will be created on demand const size_t maxBufferCount = 10; mBridge->DeviceCreateSwapChain(mId, rgbDesc, maxBufferCount, aOwnerId, aUseExternalTextureInSwapChain); // TODO: `mColorSpace`: // TODO: `mAlphaMode`: return CreateTextureForSwapChain(aConfig, aCanvasSize, aOwnerId); } bool Device::CheckNewWarning(const nsACString& aMessage) { return mKnownWarnings.EnsureInserted(aMessage); } void Device::Destroy() { if (IsLost()) { return; } // 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(); } mBridge->SendDeviceDestroy(mId); } void Device::PushErrorScope(const dom::GPUErrorFilter& aFilter) { if (!IsBridgeAlive()) { return; } mBridge->SendDevicePushErrorScope(mId, aFilter); } already_AddRefed Device::PopErrorScope(ErrorResult& aRv) { /* https://www.w3.org/TR/webgpu/#errors-and-debugging: > After a device is lost (described below), errors are no longer surfaced. > At this point, implementations do not need to run validation or error tracking: > popErrorScope() and uncapturederror stop reporting errors, > and the validity of objects on the device becomes unobservable. */ RefPtr promise = dom::Promise::Create(GetParentObject(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } if (!IsBridgeAlive()) { WebGPUChild::JsWarning( GetOwnerGlobal(), "popErrorScope resolving to null because device is already lost."_ns); promise->MaybeResolve(JS::NullHandleValue); return promise.forget(); } auto errorPromise = mBridge->SendDevicePopErrorScope(mId); errorPromise->Then( GetCurrentSerialEventTarget(), __func__, [self = RefPtr{this}, promise](const PopErrorScopeResult& aResult) { RefPtr error; switch (aResult.resultType) { case PopErrorScopeResultType::NoError: promise->MaybeResolve(JS::NullHandleValue); return; case PopErrorScopeResultType::DeviceLost: WebGPUChild::JsWarning( self->GetOwnerGlobal(), "popErrorScope resolving to null because device was lost."_ns); promise->MaybeResolve(JS::NullHandleValue); return; case PopErrorScopeResultType::ThrowOperationError: promise->MaybeRejectWithOperationError(aResult.message); return; case PopErrorScopeResultType::OutOfMemory: error = new OutOfMemoryError(self->GetParentObject(), aResult.message); break; case PopErrorScopeResultType::ValidationError: error = new ValidationError(self->GetParentObject(), aResult.message); break; case PopErrorScopeResultType::InternalError: error = new InternalError(self->GetParentObject(), aResult.message); break; } promise->MaybeResolve(std::move(error)); }, [self = RefPtr{this}, promise](const ipc::ResponseRejectReason&) { // Device was lost. WebGPUChild::JsWarning( self->GetOwnerGlobal(), "popErrorScope resolving to null because device was just lost."_ns); promise->MaybeResolve(JS::NullHandleValue); }); return promise.forget(); } } // namespace mozilla::webgpu