Skip to content

Commit

Permalink
Merge pull request #946 from cloudflare/w4
Browse files Browse the repository at this point in the history
webgpu continued
  • Loading branch information
edevil authored Aug 8, 2023
2 parents c6f439c + d22e2cd commit 9f56120
Show file tree
Hide file tree
Showing 41 changed files with 1,552 additions and 172 deletions.
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ WhitespaceSensitiveMacros:
- JSG_TS_DEFINE
- JSG_STRUCT_TS_OVERRIDE
- JSG_STRUCT_TS_DEFINE
PointerAlignment: Left
1 change: 1 addition & 0 deletions src/workerd/api/global-scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ class ServiceWorkerGlobalScope: public WorkerGlobalScope {
// WebGPU
JSG_NESTED_TYPE_NAMED(api::gpu::GPUBufferUsage, GPUBufferUsage);
JSG_NESTED_TYPE_NAMED(api::gpu::GPUShaderStage, GPUShaderStage);
JSG_NESTED_TYPE_NAMED(api::gpu::GPUMapMode, GPUMapMode);
#endif

JSG_TS_ROOT();
Expand Down
16 changes: 16 additions & 0 deletions src/workerd/api/gpu/gpu-adapter-info.c++
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

#include "gpu-adapter-info.h"
#include "workerd/jsg/exception.h"

namespace workerd::api::gpu {

GPUAdapterInfo::GPUAdapterInfo(WGPUAdapterProperties properties)
: vendor_(kj::str(properties.vendorName)),
architecture_(kj::str(properties.architecture)),
device_(kj::str(properties.name)),
description_(kj::str(properties.driverDescription)) {}

} // namespace workerd::api::gpu
33 changes: 33 additions & 0 deletions src/workerd/api/gpu/gpu-adapter-info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

#pragma once

#include <webgpu/webgpu_cpp.h>
#include <workerd/jsg/jsg.h>

namespace workerd::api::gpu {

class GPUAdapterInfo : public jsg::Object {
public:
explicit GPUAdapterInfo(WGPUAdapterProperties);
JSG_RESOURCE_TYPE(GPUAdapterInfo) {
JSG_READONLY_PROTOTYPE_PROPERTY(vendor, getVendor);
JSG_READONLY_PROTOTYPE_PROPERTY(architecture, getArchitecture);
JSG_READONLY_PROTOTYPE_PROPERTY(device, getDevice);
JSG_READONLY_PROTOTYPE_PROPERTY(description, getDescription);
}

private:
kj::String vendor_;
kj::String architecture_;
kj::String device_;
kj::String description_;
kj::StringPtr getVendor() { return vendor_; };
kj::StringPtr getArchitecture() { return architecture_; };
kj::StringPtr getDevice() { return device_; };
kj::StringPtr getDescription() { return description_; };
};

} // namespace workerd::api::gpu
105 changes: 98 additions & 7 deletions src/workerd/api/gpu/gpu-adapter.c++
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,94 @@
// https://opensource.org/licenses/Apache-2.0

#include "gpu-adapter.h"
#include "gpu-adapter-info.h"
#include "gpu-supported-features.h"
#include "gpu-supported-limits.h"
#include "workerd/jsg/exception.h"

#define WGPU_FOR_EACH_LIMIT(X) \
X(maxTextureDimension1D) \
X(maxTextureDimension2D) \
X(maxTextureDimension3D) \
X(maxTextureArrayLayers) \
X(maxBindGroups) \
X(maxBindingsPerBindGroup) \
X(maxDynamicUniformBuffersPerPipelineLayout) \
X(maxDynamicStorageBuffersPerPipelineLayout) \
X(maxSampledTexturesPerShaderStage) \
X(maxSamplersPerShaderStage) \
X(maxStorageBuffersPerShaderStage) \
X(maxStorageTexturesPerShaderStage) \
X(maxUniformBuffersPerShaderStage) \
X(maxUniformBufferBindingSize) \
X(maxStorageBufferBindingSize) \
X(minUniformBufferOffsetAlignment) \
X(minStorageBufferOffsetAlignment) \
X(maxVertexBuffers) \
X(maxBufferSize) \
X(maxVertexAttributes) \
X(maxVertexBufferArrayStride) \
X(maxInterStageShaderComponents) \
X(maxColorAttachments) \
X(maxColorAttachmentBytesPerSample) \
X(maxComputeWorkgroupStorageSize) \
X(maxComputeInvocationsPerWorkgroup) \
X(maxComputeWorkgroupSizeX) \
X(maxComputeWorkgroupSizeY) \
X(maxComputeWorkgroupSizeZ) \
X(maxComputeWorkgroupsPerDimension)

namespace workerd::api::gpu {

void setLimit(wgpu::RequiredLimits& limits, kj::StringPtr name,
unsigned long long value) {

#define COPY_LIMIT(LIMIT) \
if (name == "#LIMIT") { \
limits.limits.LIMIT = value; \
return; \
}
WGPU_FOR_EACH_LIMIT(COPY_LIMIT)
#undef COPY_LIMIT

JSG_FAIL_REQUIRE(TypeError, "unknown limit", name);
}

jsg::Promise<jsg::Ref<GPUAdapterInfo>> GPUAdapter::requestAdapterInfo(
jsg::Lock& js, jsg::Optional<kj::Array<kj::String>> unmaskHints) {

WGPUAdapterProperties adapterProperties = {};
adapter_.GetProperties(&adapterProperties);
auto info = jsg::alloc<GPUAdapterInfo>(adapterProperties);
return js.resolvedPromise(kj::mv(info));
}

jsg::Promise<jsg::Ref<GPUDevice>>
GPUAdapter::requestDevice(jsg::Lock &js,
GPUAdapter::requestDevice(jsg::Lock& js,
jsg::Optional<GPUDeviceDescriptor> descriptor) {
wgpu::DeviceDescriptor desc{};
kj::Vector<wgpu::FeatureName> requiredFeatures;
wgpu::RequiredLimits limits;
KJ_IF_MAYBE (d, descriptor) {
KJ_IF_MAYBE(label, d->label) {
KJ_IF_MAYBE (label, d->label) {
desc.label = label->cStr();
}

KJ_IF_MAYBE (features, d->requiredFeatures) {
for (auto &required : *features) {
for (auto& required : *features) {
requiredFeatures.add(parseFeatureName(required));
}

desc.requiredFeaturesCount = requiredFeatures.size();
desc.requiredFeatures = requiredFeatures.begin();
}

KJ_IF_MAYBE (requiredLimits, d->requiredLimits) {
for (auto& f : requiredLimits->fields) {
setLimit(limits, f.name, f.value);
}
desc.requiredLimits = &limits;
}
}

struct UserData {
Expand All @@ -35,20 +102,44 @@ GPUAdapter::requestDevice(jsg::Lock &js,
adapter_.RequestDevice(
&desc,
[](WGPURequestDeviceStatus status, WGPUDevice cDevice,
const char *message, void *pUserData) {
const char* message, void* pUserData) {
JSG_REQUIRE(status == WGPURequestDeviceStatus_Success, Error, message);

UserData &userData = *reinterpret_cast<UserData *>(pUserData);
UserData& userData = *reinterpret_cast<UserData*>(pUserData);
userData.device = wgpu::Device::Acquire(cDevice);
userData.requestEnded = true;
},
(void *)&userData);
(void*)&userData);

KJ_ASSERT(userData.requestEnded);

jsg::Ref<GPUDevice> gpuDevice =
jsg::alloc<GPUDevice>(kj::mv(userData.device));
jsg::alloc<GPUDevice>(js, kj::mv(userData.device));
return js.resolvedPromise(kj::mv(gpuDevice));
}

jsg::Ref<GPUSupportedFeatures> GPUAdapter::getFeatures() {
wgpu::Adapter adapter(adapter_.Get());
size_t count = adapter.EnumerateFeatures(nullptr);
kj::Array<wgpu::FeatureName> features =
kj::heapArray<wgpu::FeatureName>(count);
adapter.EnumerateFeatures(&features[0]);
return jsg::alloc<GPUSupportedFeatures>(kj::mv(features));
}

jsg::Ref<GPUSupportedLimits> GPUAdapter::getLimits() {
WGPUSupportedLimits limits{};
JSG_REQUIRE(adapter_.GetLimits(&limits), TypeError,
"failed to get adapter limits");

// need to copy to the C++ version of the object
wgpu::SupportedLimits wgpuLimits{};

#define COPY_LIMIT(LIMIT) wgpuLimits.limits.LIMIT = limits.limits.LIMIT;
WGPU_FOR_EACH_LIMIT(COPY_LIMIT)
#undef COPY_LIMIT

return jsg::alloc<GPUSupportedLimits>(kj::mv(wgpuLimits));
}

} // namespace workerd::api::gpu
15 changes: 14 additions & 1 deletion src/workerd/api/gpu/gpu-adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

#pragma once

#include "gpu-adapter-info.h"
#include "gpu-device.h"
#include "gpu-supported-features.h"
#include "gpu-supported-limits.h"
#include "gpu-utils.h"
#include <dawn/native/DawnNative.h>
#include <webgpu/webgpu_cpp.h>
Expand All @@ -15,12 +18,22 @@ namespace workerd::api::gpu {
class GPUAdapter : public jsg::Object {
public:
explicit GPUAdapter(dawn::native::Adapter a) : adapter_(a){};
JSG_RESOURCE_TYPE(GPUAdapter) { JSG_METHOD(requestDevice); }
JSG_RESOURCE_TYPE(GPUAdapter) {
JSG_METHOD(requestDevice);
JSG_METHOD(requestAdapterInfo);
JSG_READONLY_PROTOTYPE_PROPERTY(features, getFeatures);
JSG_READONLY_PROTOTYPE_PROPERTY(limits, getLimits);
}

private:
jsg::Promise<jsg::Ref<GPUDevice>>
requestDevice(jsg::Lock &, jsg::Optional<GPUDeviceDescriptor>);
dawn::native::Adapter adapter_;
jsg::Promise<jsg::Ref<GPUAdapterInfo>>
requestAdapterInfo(jsg::Lock &js,
jsg::Optional<kj::Array<kj::String>> unmaskHints);
jsg::Ref<GPUSupportedFeatures> getFeatures();
jsg::Ref<GPUSupportedLimits> getLimits();
};

} // namespace workerd::api::gpu
44 changes: 44 additions & 0 deletions src/workerd/api/gpu/gpu-async-runner.c++
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

#include "gpu-async-runner.h"
#include "workerd/io/io-context.h"
#include <kj/common.h>
#include <kj/debug.h>

#define BUSY_LOOP_DELAY_MS 50

namespace workerd::api::gpu {

void AsyncRunner::Begin() {
KJ_ASSERT(count_ != std::numeric_limits<decltype(count_)>::max());
if (count_++ == 0) {
QueueTick();
}
}

void AsyncRunner::End() {
KJ_ASSERT(count_ > 0);
count_--;
}

void AsyncRunner::QueueTick() {
if (tick_queued_) {
return;
}
tick_queued_ = true;

IoContext::current().setTimeoutImpl(
timeoutIdGenerator, false,
[this](jsg::Lock &js) mutable {
this->tick_queued_ = false;
if (this->count_ > 0) {
this->device_.Tick();
QueueTick();
}
},
BUSY_LOOP_DELAY_MS);
}

} // namespace workerd::api::gpu
61 changes: 61 additions & 0 deletions src/workerd/api/gpu/gpu-async-runner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2017-2022 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0
// Based on the dawn node bindings

#pragma once

#include "workerd/io/io-context.h"
#include <kj/timer.h>
#include <webgpu/webgpu_cpp.h>

namespace workerd::api::gpu {

// AsyncRunner is used to poll a wgpu::Device with calls to Tick() while there
// are asynchronous tasks in flight.
class AsyncRunner : public kj::Refcounted {
public:
AsyncRunner(wgpu::Device device) : device_(device){};

// Begin() should be called when a new asynchronous task is started.
// If the number of executing asynchronous tasks transitions from 0 to 1, then
// a function will be scheduled on the main JavaScript thread to call
// wgpu::Device::Tick() whenever the thread is idle. This will be repeatedly
// called until the number of executing asynchronous tasks reaches 0 again.
void Begin();

// End() should be called once the asynchronous task has finished.
// Every call to Begin() should eventually result in a call to End().
void End();

private:
void QueueTick();
wgpu::Device const device_;
uint64_t count_ = 0;
bool tick_queued_ = false;
TimeoutId::Generator timeoutIdGenerator;
};

// AsyncTask is a RAII helper for calling AsyncRunner::Begin() on construction,
// and AsyncRunner::End() on destruction.
class AsyncTask {
public:
inline AsyncTask(AsyncTask &&) = default;

// Constructor.
// Calls AsyncRunner::Begin()
explicit inline AsyncTask(kj::Own<AsyncRunner> runner)
: runner_(std::move(runner)) {
runner_->Begin();
}

// Destructor.
// Calls AsyncRunner::End()
inline ~AsyncTask() { runner_->End(); }

private:
KJ_DISALLOW_COPY(AsyncTask);
kj::Own<AsyncRunner> runner_;
};

} // namespace workerd::api::gpu
Loading

0 comments on commit 9f56120

Please sign in to comment.