/* 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 "Instance.h"
#include <optional>
#include <string_view>
#include "Adapter.h"
#include "ipc/WebGPUChild.h"
#include "ipc/WebGPUTypes.h"
#include "js/Value.h"
#include "mozilla/Assertions.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/gfx/CanvasManagerChild.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/webgpu/ffi/wgpu.h"
#include "nsDebug.h"
#include "nsIGlobalObject.h"
#include "nsString.h"
#include "nsStringFwd.h"
namespace mozilla::webgpu {
GPU_IMPL_CYCLE_COLLECTION(WGSLLanguageFeatures, mParent)
GPU_IMPL_CYCLE_COLLECTION(Instance, mGlobal, mWgslLanguageFeatures)
/* static */ bool Instance::PrefEnabled() {
if (!StaticPrefs::dom_webgpu_enabled()) {
return false;
}
if (NS_IsMainThread()) {
return true;
}
dom::WorkerPrivate* wp = dom::GetCurrentThreadWorkerPrivate();
if (wp && wp->IsServiceWorker()) {
return StaticPrefs::dom_webgpu_service_workers_enabled();
}
return true;
}
/* static */ bool Instance::ExternalTexturePrefEnabled(JSContext* aCx,
JSObject* aObj) {
return StaticPrefs::dom_webgpu_external_texture_enabled_AtStartup();
}
/*static*/
already_AddRefed<Instance> Instance::Create(nsIGlobalObject* aGlobal) {
RefPtr<Instance> result =
new Instance(aGlobal);
return result.forget();
}
Instance::Instance(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal), mWgslLanguageFeatures(
new WGSLLanguageFeatures(this)) {
// Populate `mWgslLanguageFeatures`.
IgnoredErrorResult rv;
nsCString wgslFeature;
for (size_t i =
0;; ++i) {
wgslFeature.Truncate(
0);
ffi::wgpu_client_instance_get_wgsl_language_feature(&wgslFeature, i);
if (wgslFeature.IsEmpty()) {
break;
}
NS_ConvertASCIItoUTF16 feature{wgslFeature};
this->mWgslLanguageFeatures->Add(feature, rv);
if (rv.Failed()) {
if (rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)) {
// This is fine; something went wrong with the JS scope we're in, and we
// can just let that happen.
NS_WARNING(
"`Instance::Instance`: failed to append WGSL language feature: got "
"`NS_ERROR_UNEXPECTED`");
}
else {
MOZ_CRASH_UNSAFE_PRINTF(
"`Instance::Instance`: failed to append WGSL language feature: %d",
rv.ErrorCodeAsInt());
}
}
}
}
JSObject* Instance::WrapObject(JSContext* cx,
JS::Handle<JSObject*> givenProto) {
return dom::GPU_Binding::Wrap(cx, this, givenProto);
}
already_AddRefed<dom::Promise> Instance::RequestAdapter(
const dom::GPURequestAdapterOptions& aOptions, ErrorResult& aRv) {
RefPtr<dom::Promise> promise = dom::Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
if (NS_IsMainThread()) {
JSObject* obj = mGlobal->GetGlobalJSObject();
if (obj) {
dom::SetUseCounter(obj, eUseCounter_custom_WebgpuRequestAdapter);
}
}
else {
dom::SetUseCounter(UseCounterWorker::Custom_WebgpuRequestAdapter);
}
// -
// Check if we should allow the request.
std::optional<std::string_view> rejectionMessage = {};
const auto rejectIf = [&rejectionMessage, &promise, this](
bool condition,
const char* message) {
if (condition && !rejectionMessage.has_value()) {
rejectionMessage = message;
promise->MaybeResolve(JS::NullValue());
dom::AutoJSAPI api;
if (api.Init(mGlobal)) {
JS::WarnUTF8(api.cx(),
"%s", rejectionMessage.value().data());
}
}
};
rejectIf(!gfx::gfxVars::AllowWebGPU(),
"WebGPU is disabled by blocklist.");
rejectIf(!StaticPrefs::dom_webgpu_enabled(),
"WebGPU is disabled because the `dom.webgpu.enabled` pref. is set "
"to `false`.");
#ifndef HAVE_64BIT_BUILD
rejectIf(true,
"WebGPU is only available on 64-bit architectures.");
#endif
#ifdef WIN32
# ifndef MOZ_DXCOMPILER
rejectIf(true,
"WebGPU is disabled because dxcompiler is unavailable with this "
"build configuration");
# endif
#endif
// Check if WebGPU is blocked for this global's domain.
{
const auto prefLock = mozilla::StaticPrefs::dom_webgpu_blocked_domains();
rejectIf(nsContentUtils::IsURIInList(mGlobal->GetBaseURI(), *prefLock),
"WebGPU is blocked for this domain by the "
"`dom.webgpu.blocked-domains` pref.");
}
if (rejectionMessage) {
return promise.forget();
}
// -
// Make the request.
auto*
const canvasManager = gfx::CanvasManagerChild::Get();
rejectIf(!canvasManager,
"Failed to create CanvasManagerChild");
if (rejectionMessage) {
return promise.forget();
}
RefPtr<WebGPUChild> child = canvasManager->GetWebGPUChild();
rejectIf(!child,
"Failed to create WebGPUChild");
if (rejectionMessage) {
return promise.forget();
}
if (aOptions.mFeatureLevel.EqualsASCII(
"core")) {
// Good! That's all we support.
}
else if (aOptions.mFeatureLevel.EqualsASCII(
"compatibility")) {
dom::AutoJSAPI api;
if (api.Init(mGlobal)) {
JS::WarnUTF8(api.cx(),
"User requested a WebGPU adapter with `featureLevel: "
"\"compatibility\
"`, which is not yet supported; returning "
"a \"core\
"-defaulting adapter for now. Subscribe to "
"<https://bugzilla.mozilla.org/show_bug.cgi?id=1905951>"
" for updates on its development in Firefox.");
}
}
else {
NS_ConvertUTF16toUTF8 featureLevel(aOptions.mFeatureLevel);
dom::AutoJSAPI api;
if (api.Init(mGlobal)) {
JS::WarnUTF8(api.cx(),
"expected one of `\"core\
"` or `\"compatibility\
"` for "
"`GPUAdapter.featureLevel`, got %s",
featureLevel.get());
}
promise->MaybeResolve(JS::NullValue());
return promise.forget();
}
if (aOptions.mXrCompatible) {
dom::AutoJSAPI api;
if (api.Init(mGlobal)) {
JS::WarnUTF8(
api.cx(),
"User requested a WebGPU adapter with `xrCompatible: true`, "
"but WebXR sessions are not yet supported in WebGPU. Returning "
"a regular adapter for now. Subscribe to "
"<https://bugzilla.mozilla.org/show_bug.cgi?id=1963829>"
" for updates on its development in Firefox.");
}
}
ffi::WGPUPowerPreference power_preference;
if (aOptions.mPowerPreference.WasPassed()) {
switch (aOptions.mPowerPreference.Value()) {
case dom::GPUPowerPreference::Low_power:
power_preference = ffi::WGPUPowerPreference_LowPower;
break;
case dom::GPUPowerPreference::High_performance:
power_preference = ffi::WGPUPowerPreference_HighPerformance;
break;
default:
MOZ_CRASH(
"Unexpected `dom::GPUPowerPreference`");
}
}
else {
power_preference = ffi::WGPUPowerPreference_None;
}
RawId adapter_id = ffi::wgpu_client_request_adapter(
child->GetClient(), power_preference, aOptions.mForceFallbackAdapter);
child->EnqueueRequestAdapterPromise(
PendingRequestAdapterPromise{promise, this, adapter_id});
return promise.forget();
}
}
// namespace mozilla::webgpu