Instance.cpp (7666B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "Instance.h" 7 8 #include <optional> 9 #include <string_view> 10 11 #include "Adapter.h" 12 #include "ipc/WebGPUChild.h" 13 #include "ipc/WebGPUTypes.h" 14 #include "js/Value.h" 15 #include "mozilla/Assertions.h" 16 #include "mozilla/ErrorResult.h" 17 #include "mozilla/StaticPrefs_dom.h" 18 #include "mozilla/dom/Promise.h" 19 #include "mozilla/dom/WorkerPrivate.h" 20 #include "mozilla/gfx/CanvasManagerChild.h" 21 #include "mozilla/gfx/Logging.h" 22 #include "mozilla/gfx/gfxVars.h" 23 #include "mozilla/webgpu/ffi/wgpu.h" 24 #include "nsDebug.h" 25 #include "nsIGlobalObject.h" 26 #include "nsString.h" 27 #include "nsStringFwd.h" 28 29 namespace mozilla::webgpu { 30 31 GPU_IMPL_CYCLE_COLLECTION(WGSLLanguageFeatures, mParent) 32 33 GPU_IMPL_CYCLE_COLLECTION(Instance, mOwner, mWgslLanguageFeatures) 34 35 static inline nsDependentCString ToCString(const std::string_view s) { 36 return {s.data(), s.length()}; 37 } 38 39 /* static */ bool Instance::PrefEnabled(JSContext* aCx, JSObject* aObj) { 40 if (!StaticPrefs::dom_webgpu_enabled()) { 41 return false; 42 } 43 44 if (NS_IsMainThread()) { 45 return true; 46 } 47 48 dom::WorkerPrivate* wp = dom::GetCurrentThreadWorkerPrivate(); 49 if (wp && wp->IsServiceWorker()) { 50 return StaticPrefs::dom_webgpu_service_workers_enabled(); 51 } 52 53 return true; 54 } 55 56 /* static */ bool Instance::ExternalTexturePrefEnabled(JSContext* aCx, 57 JSObject* aObj) { 58 return StaticPrefs::dom_webgpu_external_texture_enabled_AtStartup(); 59 } 60 61 /*static*/ 62 already_AddRefed<Instance> Instance::Create(nsIGlobalObject* aOwner) { 63 RefPtr<Instance> result = new Instance(aOwner); 64 return result.forget(); 65 } 66 67 Instance::Instance(nsIGlobalObject* aOwner) 68 : mOwner(aOwner), mWgslLanguageFeatures(new WGSLLanguageFeatures(this)) { 69 // Populate `mWgslLanguageFeatures`. 70 IgnoredErrorResult rv; 71 nsCString wgslFeature; 72 for (size_t i = 0;; ++i) { 73 wgslFeature.Truncate(0); 74 ffi::wgpu_client_instance_get_wgsl_language_feature(&wgslFeature, i); 75 if (wgslFeature.IsEmpty()) { 76 break; 77 } 78 NS_ConvertASCIItoUTF16 feature{wgslFeature}; 79 this->mWgslLanguageFeatures->Add(feature, rv); 80 if (rv.Failed()) { 81 if (rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)) { 82 // This is fine; something went wrong with the JS scope we're in, and we 83 // can just let that happen. 84 NS_WARNING( 85 "`Instance::Instance`: failed to append WGSL language feature: got " 86 "`NS_ERROR_UNEXPECTED`"); 87 } else { 88 MOZ_CRASH_UNSAFE_PRINTF( 89 "`Instance::Instance`: failed to append WGSL language feature: %d", 90 rv.ErrorCodeAsInt()); 91 } 92 } 93 } 94 } 95 96 JSObject* Instance::WrapObject(JSContext* cx, 97 JS::Handle<JSObject*> givenProto) { 98 return dom::GPU_Binding::Wrap(cx, this, givenProto); 99 } 100 101 already_AddRefed<dom::Promise> Instance::RequestAdapter( 102 const dom::GPURequestAdapterOptions& aOptions, ErrorResult& aRv) { 103 RefPtr<dom::Promise> promise = dom::Promise::Create(mOwner, aRv); 104 if (NS_WARN_IF(aRv.Failed())) { 105 return nullptr; 106 } 107 108 if (NS_IsMainThread()) { 109 JSObject* obj = mOwner->GetGlobalJSObject(); 110 if (obj) { 111 dom::SetUseCounter(obj, eUseCounter_custom_WebgpuRequestAdapter); 112 } 113 } else { 114 dom::SetUseCounter(UseCounterWorker::Custom_WebgpuRequestAdapter); 115 } 116 117 // - 118 // Check if we should allow the request. 119 120 std::optional<std::string_view> rejectionMessage = {}; 121 const auto rejectIf = [&rejectionMessage](bool condition, 122 const char* message) { 123 if (condition && !rejectionMessage.has_value()) { 124 rejectionMessage = message; 125 } 126 }; 127 128 rejectIf(!gfx::gfxVars::AllowWebGPU(), "WebGPU is disabled by blocklist."); 129 rejectIf(!StaticPrefs::dom_webgpu_enabled(), 130 "WebGPU is disabled because the `dom.webgpu.enabled` pref. is set " 131 "to `false`."); 132 #ifdef WIN32 133 # ifndef MOZ_DXCOMPILER 134 rejectIf(true, 135 "WebGPU is disabled because dxcompiler is unavailable with this " 136 "build configuration"); 137 # endif 138 #endif 139 140 // Check if WebGPU is blocked for this global's domain. 141 { 142 const auto prefLock = mozilla::StaticPrefs::dom_webgpu_blocked_domains(); 143 rejectIf(nsContentUtils::IsURIInList(mOwner->GetBaseURI(), *prefLock), 144 "WebGPU is blocked for this domain by the " 145 "`dom.webgpu.blocked-domains` pref."); 146 } 147 148 if (rejectionMessage) { 149 promise->MaybeRejectWithNotSupportedError(ToCString(*rejectionMessage)); 150 return promise.forget(); 151 } 152 153 // - 154 // Make the request. 155 156 auto* const canvasManager = gfx::CanvasManagerChild::Get(); 157 if (!canvasManager) { 158 promise->MaybeRejectWithInvalidStateError( 159 "Failed to create CanvasManagerChild"); 160 return promise.forget(); 161 } 162 163 RefPtr<WebGPUChild> child = canvasManager->GetWebGPUChild(); 164 if (!child) { 165 promise->MaybeRejectWithInvalidStateError("Failed to create WebGPUChild"); 166 return promise.forget(); 167 } 168 169 if (aOptions.mFeatureLevel.EqualsASCII("core")) { 170 // Good! That's all we support. 171 } else if (aOptions.mFeatureLevel.EqualsASCII("compatibility")) { 172 dom::AutoJSAPI api; 173 if (api.Init(mOwner)) { 174 JS::WarnUTF8(api.cx(), 175 "User requested a WebGPU adapter with `featureLevel: " 176 "\"compatibility\"`, which is not yet supported; returning " 177 "a \"core\"-defaulting adapter for now. Subscribe to " 178 "<https://bugzilla.mozilla.org/show_bug.cgi?id=1905951>" 179 " for updates on its development in Firefox."); 180 } 181 } else { 182 NS_ConvertUTF16toUTF8 featureLevel(aOptions.mFeatureLevel); 183 dom::AutoJSAPI api; 184 if (api.Init(mOwner)) { 185 JS::WarnUTF8(api.cx(), 186 "expected one of `\"core\"` or `\"compatibility\"` for " 187 "`GPUAdapter.featureLevel`, got %s", 188 featureLevel.get()); 189 } 190 promise->MaybeResolve(JS::NullValue()); 191 return promise.forget(); 192 } 193 194 if (aOptions.mXrCompatible) { 195 dom::AutoJSAPI api; 196 if (api.Init(mOwner)) { 197 JS::WarnUTF8( 198 api.cx(), 199 "User requested a WebGPU adapter with `xrCompatible: true`, " 200 "but WebXR sessions are not yet supported in WebGPU. Returning " 201 "a regular adapter for now. Subscribe to " 202 "<https://bugzilla.mozilla.org/show_bug.cgi?id=1963829>" 203 " for updates on its development in Firefox."); 204 } 205 } 206 207 ffi::WGPUPowerPreference power_preference; 208 if (aOptions.mPowerPreference.WasPassed()) { 209 switch (aOptions.mPowerPreference.Value()) { 210 case dom::GPUPowerPreference::Low_power: 211 power_preference = ffi::WGPUPowerPreference_LowPower; 212 break; 213 case dom::GPUPowerPreference::High_performance: 214 power_preference = ffi::WGPUPowerPreference_HighPerformance; 215 break; 216 default: 217 MOZ_CRASH("Unexpected `dom::GPUPowerPreference`"); 218 } 219 } else { 220 power_preference = ffi::WGPUPowerPreference_None; 221 } 222 223 RawId adapter_id = ffi::wgpu_client_request_adapter( 224 child->GetClient(), power_preference, aOptions.mForceFallbackAdapter); 225 226 auto pending_promise = WebGPUChild::PendingRequestAdapterPromise{ 227 RefPtr(promise), RefPtr(this), adapter_id}; 228 child->mPendingRequestAdapterPromises.push_back(std::move(pending_promise)); 229 230 return promise.forget(); 231 } 232 233 } // namespace mozilla::webgpu