7#if defined(TESS_ENABLE_WEBGPU)
10#error "tess/gpu/webgpu_backend.h requires <webgpu/webgpu.h> first"
13#include <tess/gpu/backend.h>
29 std::uint64_t max_buffer_bytes = std::uint64_t{256} * 1024u * 1024u;
30 std::uint64_t max_dispatch_chunks = 65535;
33 std::uint32_t max_dispatch_workgroups_x = 65535;
34 std::uint64_t max_inflight_readback_bytes = std::uint64_t{4} * 1024u * 1024u;
35 std::size_t field_capacity = 16;
36 std::size_t product_capacity = 64;
37 bool allow_full_field_readback =
false;
41enum class WebGpuReadbackStatus : std::uint8_t {
55using WebGpuReadbackCallback = void (*)(GpuProductHandle, WebGpuReadbackStatus,
56 const void*, std::size_t,
70 std::uint64_t product_key = 0;
71 std::uint32_t input_field_index = 0;
72 WGPUComputePipeline pipeline =
nullptr;
73 WGPUBindGroup bind_group =
nullptr;
74 WGPUBuffer readback_source =
nullptr;
75 std::uint64_t readback_source_offset = 0;
76 std::uint64_t readback_byte_size = 0;
77 WebGpuReadbackCallback readback_callback =
nullptr;
78 void* readback_userdata =
nullptr;
83struct WebGpuSharedState {
84 std::atomic<bool> available{
true};
85 std::atomic<std::uint64_t> inflight_readback_bytes{0};
86 std::uint64_t max_inflight_readback_bytes = 0;
89struct WebGpuReadbackOperation {
90 WGPUBuffer staging =
nullptr;
91 GpuProductHandle handle{};
92 std::size_t byte_size = 0;
93 WebGpuReadbackCallback callback =
nullptr;
94 void* userdata =
nullptr;
95 std::shared_ptr<WebGpuSharedState> shared;
98template <
typename Size>
99[[nodiscard]]
constexpr bool fits_size(std::uint64_t value)
noexcept {
100 static_assert(std::is_integral_v<Size> && std::is_unsigned_v<Size>);
101 if constexpr (std::numeric_limits<Size>::digits >=
102 std::numeric_limits<std::uint64_t>::digits) {
106 static_cast<std::uint64_t
>(std::numeric_limits<Size>::max());
110inline void webgpu_readback_complete(WGPUMapAsyncStatus status, WGPUStringView,
111 void* userdata1,
void*)
noexcept {
112 auto* operation =
static_cast<WebGpuReadbackOperation*
>(userdata1);
113 const void* data =
nullptr;
114 auto result = WebGpuReadbackStatus::Failed;
118 const auto device_available =
119 operation->shared->available.load(std::memory_order_acquire);
120 if (status == WGPUMapAsyncStatus_Success && device_available) {
121 data = wgpuBufferGetConstMappedRange(operation->staging, 0,
122 operation->byte_size);
123 if (data !=
nullptr) {
124 result = WebGpuReadbackStatus::Complete;
127 operation->callback(operation->handle, result, data,
128 data ==
nullptr ? 0 : operation->byte_size,
129 operation->userdata);
130 if (status == WGPUMapAsyncStatus_Success) {
131 wgpuBufferUnmap(operation->staging);
133 wgpuBufferRelease(operation->staging);
134 operation->shared->inflight_readback_bytes.fetch_sub(
135 operation->byte_size, std::memory_order_relaxed);
139[[nodiscard]]
inline bool reserve_readback_bytes(
140 const std::shared_ptr<WebGpuSharedState>& shared,
141 std::uint64_t bytes)
noexcept {
143 shared->inflight_readback_bytes.load(std::memory_order_relaxed);
145 if (bytes > shared->max_inflight_readback_bytes - current) {
148 if (shared->inflight_readback_bytes.compare_exchange_weak(
149 current, current + bytes, std::memory_order_relaxed)) {
174 shared_(std::make_shared<detail::WebGpuSharedState>()) {
175 shared_->max_inflight_readback_bytes = config_.max_inflight_readback_bytes;
176 fields_.reserve(config_.field_capacity);
177 products_.reserve(config_.product_capacity);
178 if (device_ ==
nullptr || config_.max_buffer_bytes == 0 ||
179 config_.max_dispatch_chunks == 0 ||
180 config_.max_dispatch_workgroups_x == 0) {
181 shared_->available.store(false, std::memory_order_relaxed);
186 wgpuDeviceAddRef(device_);
187 queue_ = wgpuDeviceGetQueue(device_);
188 if (queue_ ==
nullptr) {
189 shared_->available.store(
false, std::memory_order_relaxed);
200 for (
auto& product : products_) {
201 release_product(product);
203 for (
auto& field : fields_) {
204 if (field.buffer !=
nullptr) {
205 wgpuBufferRelease(field.buffer);
208 if (queue_ !=
nullptr) {
209 wgpuQueueRelease(queue_);
211 if (device_ !=
nullptr) {
212 wgpuDeviceRelease(device_);
224 .async_dispatch =
true,
225 .async_readback =
true,
226 .max_buffer_bytes = config_.max_buffer_bytes,
227 .max_dispatch_chunks = config_.max_dispatch_chunks,
228 .buffer_alignment = 4,
236 fields_.size() >= config_.field_capacity ||
237 find_field(desc.field_index) !=
nullptr) {
240 const auto bytes = aligned_size(desc.
total_bytes());
241 if (!bytes.has_value() || *bytes > config_.max_buffer_bytes) {
244 auto buffer_desc = WGPU_BUFFER_DESCRIPTOR_INIT;
245 buffer_desc.usage = WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst;
246 buffer_desc.size = *bytes;
247 auto buffer = wgpuDeviceCreateBuffer(device_, &buffer_desc);
248 if (buffer ==
nullptr) {
251 fields_.push_back(FieldSlot{desc, buffer, *bytes});
256 [[nodiscard]]
auto field_buffer(std::uint32_t field_index)
const noexcept
258 const auto* field = find_field(field_index);
259 return field ==
nullptr ? nullptr : field->buffer;
264 -> std::optional<GpuProductHandle> {
265 if (!available() || desc.product_key == 0 || desc.pipeline ==
nullptr ||
266 desc.bind_group ==
nullptr ||
267 find_product(desc.product_key) !=
nullptr) {
270 if (desc.readback_source ==
nullptr) {
274 if (desc.readback_byte_size != 0 || desc.readback_source_offset != 0 ||
275 desc.readback_callback !=
nullptr ||
276 desc.readback_userdata !=
nullptr) {
280 if (desc.readback_byte_size == 0 || desc.readback_callback ==
nullptr ||
281 (wgpuBufferGetUsage(desc.readback_source) &
282 WGPUBufferUsage_CopySrc) == 0 ||
283 (desc.readback_source_offset & 3u) != 0 ||
284 (desc.readback_byte_size & 3u) != 0 ||
285 desc.readback_source_offset >
286 wgpuBufferGetSize(desc.readback_source) ||
287 desc.readback_byte_size > wgpuBufferGetSize(desc.readback_source) -
288 desc.readback_source_offset) {
292 ProductSlot* slot =
nullptr;
293 for (
auto& candidate : products_) {
294 if (!candidate.active) {
299 if (slot ==
nullptr) {
300 if (products_.size() >= config_.product_capacity) {
303 products_.emplace_back();
304 slot = &products_.back();
306 wgpuComputePipelineAddRef(desc.pipeline);
307 wgpuBindGroupAddRef(desc.bind_group);
308 if (desc.readback_source !=
nullptr) {
309 wgpuBufferAddRef(desc.readback_source);
312 slot->generation = ++generation_clock_;
313 if (slot->generation == 0) {
314 slot->generation = ++generation_clock_;
322 auto* product = find_product(handle.key);
323 if (product ==
nullptr || product->generation != handle.generation) {
326 release_product(*product);
332 const auto* product = find_product(handle.key);
333 return product !=
nullptr && product->generation == handle.generation;
338 const auto* field = find_field(
upload.field_index);
339 if (!available() || field ==
nullptr ||
upload.data ==
nullptr ||
340 upload.byte_size == 0 || (
upload.buffer_offset & 3u) != 0 ||
341 (
upload.byte_size & 3u) != 0 ||
342 !detail::fits_size<std::size_t>(
upload.byte_size) ||
343 upload.buffer_offset > field->allocated_bytes ||
344 upload.byte_size > field->allocated_bytes -
upload.buffer_offset) {
347 wgpuQueueWriteBuffer(queue_, field->buffer,
upload.buffer_offset,
349 static_cast<std::size_t
>(
upload.byte_size));
355 const auto* product = find_product(
dispatch.handle.key);
356 if (!available() || product ==
nullptr ||
357 find_field(
dispatch.input_field_index) ==
nullptr ||
358 product->generation !=
dispatch.handle.generation ||
359 product->desc.input_field_index !=
dispatch.input_field_index ||
361 dispatch.chunk_count > config_.max_dispatch_chunks ||
363 config_.max_dispatch_workgroups_x /
dispatch.workgroups_per_chunk) {
366 auto encoder_desc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT;
367 auto encoder = wgpuDeviceCreateCommandEncoder(device_, &encoder_desc);
368 if (encoder ==
nullptr) {
371 auto pass_desc = WGPU_COMPUTE_PASS_DESCRIPTOR_INIT;
372 auto pass = wgpuCommandEncoderBeginComputePass(encoder, &pass_desc);
373 if (pass ==
nullptr) {
374 wgpuCommandEncoderRelease(encoder);
377 wgpuComputePassEncoderSetPipeline(pass, product->desc.pipeline);
378 wgpuComputePassEncoderSetBindGroup(pass, 0, product->desc.bind_group, 0,
380 wgpuComputePassEncoderDispatchWorkgroups(
382 static_cast<std::uint32_t
>(
dispatch.chunk_count) *
385 wgpuComputePassEncoderEnd(pass);
386 wgpuComputePassEncoderRelease(pass);
387 const auto submitted = submit(encoder);
388 wgpuCommandEncoderRelease(encoder);
399 const auto* product = find_product(
readback.handle.key);
400 if (!available() || product ==
nullptr ||
401 product->generation !=
readback.handle.generation ||
402 readback.policy == ReadbackPolicy::None ||
403 (
readback.policy == ReadbackPolicy::FullField &&
404 !config_.allow_full_field_readback) ||
406 product->desc.readback_source ==
nullptr ||
407 product->desc.readback_callback ==
nullptr ||
408 readback.byte_size > product->desc.readback_byte_size ||
409 !detail::fits_size<std::size_t>(
readback.byte_size) ||
410 !detail::reserve_readback_bytes(shared_,
readback.byte_size)) {
413 const auto size =
static_cast<std::size_t
>(
readback.byte_size);
414 auto buffer_desc = WGPU_BUFFER_DESCRIPTOR_INIT;
415 buffer_desc.usage = WGPUBufferUsage_MapRead | WGPUBufferUsage_CopyDst;
416 buffer_desc.size =
readback.byte_size;
417 auto staging = wgpuDeviceCreateBuffer(device_, &buffer_desc);
418 if (staging ==
nullptr) {
419 release_readback_bytes(size);
422 auto* operation =
new (std::nothrow) detail::WebGpuReadbackOperation{
426 product->desc.readback_callback,
427 product->desc.readback_userdata,
430 if (operation ==
nullptr) {
431 wgpuBufferRelease(staging);
432 release_readback_bytes(size);
436 auto encoder_desc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT;
437 auto encoder = wgpuDeviceCreateCommandEncoder(device_, &encoder_desc);
438 if (encoder ==
nullptr) {
439 abandon_readback(operation);
442 wgpuCommandEncoderCopyBufferToBuffer(encoder, product->desc.readback_source,
443 product->desc.readback_source_offset,
445 const auto submitted = submit(encoder);
446 wgpuCommandEncoderRelease(encoder);
448 abandon_readback(operation);
451 auto callback = WGPU_BUFFER_MAP_CALLBACK_INFO_INIT;
457 callback.mode = WGPUCallbackMode_AllowSpontaneous;
458 callback.callback = detail::webgpu_readback_complete;
459 callback.userdata1 = operation;
461 wgpuBufferMapAsync(staging, WGPUMapMode_Read, 0, size, callback);
465 if (future.id == 0) {
466 abandon_readback(operation);
475 shared_->available.store(
false, std::memory_order_release);
489 shared_->available.store(
false, std::memory_order_release);
495 WGPUBuffer buffer =
nullptr;
496 std::uint64_t allocated_bytes = 0;
501 std::uint64_t generation = 0;
502 WebGpuProductDesc desc{};
505 [[nodiscard]]
bool available() const noexcept {
506 return device_ !=
nullptr && queue_ !=
nullptr &&
507 shared_->available.load(std::memory_order_acquire);
510 [[nodiscard]]
static auto aligned_size(std::uint64_t size)
noexcept
511 -> std::optional<std::uint64_t> {
512 if (size > std::numeric_limits<std::uint64_t>::max() - 3) {
515 return (size + 3) & ~std::uint64_t{3};
518 [[nodiscard]]
auto find_field(std::uint32_t field_index)
noexcept
520 for (
auto& field : fields_) {
521 if (field.desc.field_index == field_index) {
528 [[nodiscard]]
auto find_field(std::uint32_t field_index)
const noexcept
529 ->
const FieldSlot* {
530 for (
const auto& field : fields_) {
531 if (field.desc.field_index == field_index) {
538 [[nodiscard]]
auto find_product(std::uint64_t key)
noexcept -> ProductSlot* {
539 for (
auto& product : products_) {
540 if (product.active && product.desc.product_key == key) {
547 [[nodiscard]]
auto find_product(std::uint64_t key)
const noexcept
548 ->
const ProductSlot* {
549 for (
const auto& product : products_) {
550 if (product.active && product.desc.product_key == key) {
557 static void release_product(ProductSlot& product)
noexcept {
558 if (!product.active) {
561 wgpuComputePipelineRelease(product.desc.pipeline);
562 wgpuBindGroupRelease(product.desc.bind_group);
563 if (product.desc.readback_source !=
nullptr) {
564 wgpuBufferRelease(product.desc.readback_source);
566 product.active =
false;
570 [[nodiscard]]
bool submit(WGPUCommandEncoder encoder) {
571 auto command_desc = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT;
572 auto command = wgpuCommandEncoderFinish(encoder, &command_desc);
573 if (command ==
nullptr) {
576 wgpuQueueSubmit(queue_, 1, &command);
577 wgpuCommandBufferRelease(command);
581 void release_readback_bytes(std::size_t size)
noexcept {
582 shared_->inflight_readback_bytes.fetch_sub(size, std::memory_order_relaxed);
585 void abandon_readback(detail::WebGpuReadbackOperation* operation)
noexcept {
586 wgpuBufferRelease(operation->staging);
587 release_readback_bytes(operation->byte_size);
591 WGPUDevice device_ =
nullptr;
592 WGPUQueue queue_ =
nullptr;
593 WebGpuBackendConfig config_{};
594 std::shared_ptr<detail::WebGpuSharedState> shared_;
595 std::vector<FieldSlot> fields_;
596 std::vector<ProductSlot> products_;
597 std::uint64_t generation_clock_ = 0;
auto register_product(WebGpuProductDesc desc) -> std::optional< GpuProductHandle >
Definition webgpu_backend.h:263
bool valid(GpuProductHandle handle) const noexcept
Definition webgpu_backend.h:331
bool register_field(FieldMirrorDesc desc)
Definition webgpu_backend.h:233
bool unregister_product(GpuProductHandle handle) noexcept
Definition webgpu_backend.h:321
~WebGpuBackend()
Definition webgpu_backend.h:199
bool readback(const ReadbackDesc &readback)
Definition webgpu_backend.h:398
bool dispatch(const DispatchDesc &dispatch)
Definition webgpu_backend.h:354
void notify_device_error() noexcept
Definition webgpu_backend.h:488
auto field_buffer(std::uint32_t field_index) const noexcept -> WGPUBuffer
Definition webgpu_backend.h:256
auto capabilities() const noexcept -> GpuCapabilities
Definition webgpu_backend.h:218
void notify_device_lost() noexcept
Definition webgpu_backend.h:474
bool upload(const UploadDesc &upload)
Definition webgpu_backend.h:337
WebGpuBackend(WGPUDevice device, WebGpuBackendConfig config={})
Definition webgpu_backend.h:171
Definition descriptors.h:182
Definition descriptors.h:78
constexpr auto total_bytes_fits() const noexcept -> bool
Definition descriptors.h:87
constexpr auto total_bytes() const noexcept -> std::uint64_t
Definition descriptors.h:100
Definition descriptors.h:154
Definition descriptors.h:200
Definition descriptors.h:145
Definition webgpu_backend.h:28
Definition webgpu_backend.h:69