blob: 4903aa7eed8b5dc6db09d6792c1a6c4f7045e77c [file] [log] [blame] [edit]
// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/dawn/node/binding/GPU.h"
#include <algorithm>
#include <cstdlib>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "dawn/webgpu_cpp_print.h"
#include "src/dawn/node/binding/Converter.h"
#include "src/dawn/node/binding/GPUAdapter.h"
#include "src/dawn/node/binding/TogglesLoader.h"
#if defined(_WIN32)
#include <Windows.h>
#endif
namespace {
std::string GetEnvVar(const char* varName) {
#if defined(_WIN32)
// Use _dupenv_s to avoid unsafe warnings about std::getenv
char* value = nullptr;
_dupenv_s(&value, nullptr, varName);
if (value) {
std::string result = value;
free(value);
return result;
}
return "";
#else
if (auto* val = std::getenv(varName)) {
return val;
}
return "";
#endif
}
void SetDllDir(const char* dir) {
(void)dir;
#if defined(_WIN32)
::SetDllDirectory(dir);
#endif
}
struct BackendInfo {
const char* const name;
const char* const alias; // may be nullptr
wgpu::BackendType const backend;
};
constexpr BackendInfo kBackends[] = {
{"null", nullptr, wgpu::BackendType::Null}, //
{"webgpu", nullptr, wgpu::BackendType::WebGPU}, //
{"d3d11", nullptr, wgpu::BackendType::D3D11}, //
{"d3d12", "d3d", wgpu::BackendType::D3D12}, //
{"metal", nullptr, wgpu::BackendType::Metal}, //
{"vulkan", "vk", wgpu::BackendType::Vulkan}, //
{"opengl", "gl", wgpu::BackendType::OpenGL}, //
{"opengles", "gles", wgpu::BackendType::OpenGLES}, //
};
std::optional<wgpu::BackendType> ParseBackend(std::string_view name) {
for (auto& info : kBackends) {
if (info.name == name || (info.alias && info.alias == name)) {
return info.backend;
}
}
return std::nullopt;
}
const char* BackendName(wgpu::BackendType backend) {
for (auto& info : kBackends) {
if (info.backend == backend) {
return info.name;
}
}
return "<unknown>";
}
} // namespace
namespace wgpu::binding {
////////////////////////////////////////////////////////////////////////////////
// wgpu::bindings::GPU
////////////////////////////////////////////////////////////////////////////////
GPU::GPU(Flags flags) : flags_(std::move(flags)) {
// Setting the DllDir changes where we load adapter DLLs from (e.g. d3dcompiler_47.dll)
if (auto dir = flags_.Get("dlldir")) {
SetDllDir(dir->c_str());
}
// Set up the chained descriptor for the various instance extensions.
dawn::native::DawnInstanceDescriptor dawnDesc;
if (auto validate = flags_.Get("validate"); validate == "1" || validate == "true") {
dawnDesc.backendValidationLevel = dawn::native::BackendValidationLevel::Full;
}
TogglesLoader togglesLoader(flags_);
DawnTogglesDescriptor togglesDesc = togglesLoader.GetDescriptor();
togglesDesc.nextInChain = &dawnDesc;
wgpu::InstanceDescriptor desc;
desc.nextInChain = &togglesDesc;
instance_ = std::make_unique<dawn::native::Instance>(
reinterpret_cast<const WGPUInstanceDescriptor*>(&desc));
async_ = AsyncRunner::Create(instance_.get());
}
interop::Promise<std::optional<interop::Interface<interop::GPUAdapter>>> GPU::requestAdapter(
Napi::Env env,
interop::GPURequestAdapterOptions options) {
auto promise =
interop::Promise<std::optional<interop::Interface<interop::GPUAdapter>>>(env, PROMISE_INFO);
RequestAdapterOptions nativeOptions;
nativeOptions.forceFallbackAdapter = options.forceFallbackAdapter;
nativeOptions.compatibilityMode = options.compatibilityMode;
// Convert the power preference.
nativeOptions.powerPreference = PowerPreference::Undefined;
if (options.powerPreference.has_value()) {
switch (options.powerPreference.value()) {
case interop::GPUPowerPreference::kLowPower:
nativeOptions.powerPreference = PowerPreference::LowPower;
break;
case interop::GPUPowerPreference::kHighPerformance:
nativeOptions.powerPreference = PowerPreference::HighPerformance;
break;
}
}
// Choose the backend to use.
#if defined(_WIN32)
constexpr auto kDefaultBackendType = wgpu::BackendType::D3D12;
#elif defined(__linux__)
constexpr auto kDefaultBackendType = wgpu::BackendType::Vulkan;
#elif defined(__APPLE__)
constexpr auto kDefaultBackendType = wgpu::BackendType::Metal;
#else
#error "Unsupported platform"
#endif
nativeOptions.backendType = kDefaultBackendType;
// Check for backend override from env var / flag.
std::string forceBackend;
if (auto f = flags_.Get("backend")) {
forceBackend = *f;
} else if (std::string envVar = GetEnvVar("DAWNNODE_BACKEND"); !envVar.empty()) {
forceBackend = envVar;
}
std::transform(forceBackend.begin(), forceBackend.end(), forceBackend.begin(),
[](char c) { return std::tolower(c); });
if (!forceBackend.empty()) {
if (auto parsed = ParseBackend(forceBackend)) {
nativeOptions.backendType = parsed.value();
} else {
std::stringstream msg;
msg << "unrecognised backend '" << forceBackend << "'\nPossible backends : ";
for (auto& info : kBackends) {
if (&info != &kBackends[0]) {
msg << ", ";
}
msg << "'" << info.name << "'";
}
promise.Reject(msg.str());
return promise;
}
}
// Propagate toggles.
TogglesLoader togglesLoader(flags_);
DawnTogglesDescriptor togglesDescriptor = togglesLoader.GetDescriptor();
nativeOptions.nextInChain = &togglesDescriptor;
auto adapters = instance_->EnumerateAdapters(&nativeOptions);
if (adapters.empty()) {
promise.Resolve({});
return promise;
}
// Check for specific adapter device name.
// This was AdapterProperties.name, now it is AdapterInfo.device.
std::string deviceName;
if (auto f = flags_.Get("adapter")) {
deviceName = *f;
}
dawn::native::Adapter* adapter = nullptr;
for (auto& a : adapters) {
wgpu::AdapterInfo info;
a.GetInfo(&info);
if (!deviceName.empty() && info.device &&
std::string(info.device).find(deviceName) == std::string::npos) {
continue;
}
adapter = &a;
break;
}
if (!adapter) {
std::stringstream msg;
if (!forceBackend.empty() || deviceName.empty()) {
msg << "no adapter ";
if (!forceBackend.empty()) {
msg << "with backend '" << forceBackend << "'";
if (!deviceName.empty()) {
msg << " and name '" << deviceName << "'";
}
} else {
msg << " with name '" << deviceName << "'";
}
msg << " found";
} else {
msg << "no suitable backends found";
}
msg << "\nAvailable adapters:";
for (auto& a : adapters) {
wgpu::AdapterInfo info;
a.GetInfo(&info);
msg << "\n * backend: '" << BackendName(info.backendType) << "', name: '" << info.device
<< "'";
}
promise.Reject(msg.str());
return promise;
}
if (flags_.Get("verbose")) {
wgpu::AdapterInfo info;
adapter->GetInfo(&info);
printf("using GPU adapter: %s\n", info.device);
}
auto gpuAdapter = GPUAdapter::Create<GPUAdapter>(env, *adapter, flags_, async_);
promise.Resolve(std::optional<interop::Interface<interop::GPUAdapter>>(gpuAdapter));
return promise;
}
interop::GPUTextureFormat GPU::getPreferredCanvasFormat(Napi::Env) {
#if defined(__ANDROID__)
return interop::GPUTextureFormat::kRgba8Unorm;
#else
return interop::GPUTextureFormat::kBgra8Unorm;
#endif // defined(__ANDROID__)
}
interop::Interface<interop::WGSLLanguageFeatures> GPU::getWgslLanguageFeatures(Napi::Env env) {
using InteropWGSLFeatureSet = std::unordered_set<interop::WGSLFeatureName>;
struct Features : public interop::WGSLLanguageFeatures {
explicit Features(InteropWGSLFeatureSet features) : features_(features) {}
~Features() = default;
bool has(Napi::Env env, std::string name) {
interop::WGSLFeatureName feature;
if (!interop::Converter<interop::WGSLFeatureName>::FromString(name, feature)) {
return false;
}
return features_.count(feature);
}
std::vector<std::string> keys(Napi::Env env) {
std::vector<std::string> out;
out.reserve(features_.size());
for (auto feature : features_) {
out.push_back(interop::Converter<interop::WGSLFeatureName>::ToString(feature));
}
return out;
}
size_t getSize(Napi::Env env) { return features_.size(); }
InteropWGSLFeatureSet features_;
};
wgpu::Instance instance = instance_->Get();
size_t count = instance.EnumerateWGSLLanguageFeatures(nullptr);
std::vector<wgpu::WGSLFeatureName> features(count);
instance.EnumerateWGSLLanguageFeatures(features.data());
// Add all known WGSLLangaugeFeatures known by dawn.node but warn loudly when there are unknown
// ones.
InteropWGSLFeatureSet featureSet;
Converter conv(env);
for (auto feature : features) {
interop::WGSLFeatureName wgslFeature;
if (conv(wgslFeature, feature)) {
featureSet.emplace(wgslFeature);
} else {
LOG("Unknown WGSLFeatureName ", feature);
}
}
return interop::WGSLLanguageFeatures::Create<Features>(env, std::move(featureSet));
}
} // namespace wgpu::binding