2026-05-28 12:21:52 +10:00
|
|
|
/********************************************************************************
|
|
|
|
|
* *
|
|
|
|
|
* This file is part of IfcOpenShell. *
|
|
|
|
|
* *
|
|
|
|
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
|
|
|
|
* it under the terms of the Lesser GNU General Public License as published by *
|
|
|
|
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
|
|
|
|
* (at your option) any later version. *
|
|
|
|
|
* *
|
|
|
|
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
|
|
|
* Lesser GNU General Public License for more details. *
|
|
|
|
|
* *
|
|
|
|
|
* You should have received a copy of the Lesser GNU General Public License *
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
|
|
|
* *
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
#include "BufferPool.h"
|
2026-05-28 12:21:52 +10:00
|
|
|
|
|
|
|
|
#include <cassert>
|
2026-06-04 18:20:37 +10:00
|
|
|
#include <cstdio>
|
2026-05-28 12:21:52 +10:00
|
|
|
#include <cstring>
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
BufferPool::~BufferPool() {
|
2026-05-28 12:21:52 +10:00
|
|
|
destroy();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
void BufferPool::configure(WGPUInstance instance, WGPUDevice device,
|
2026-05-28 14:08:16 +10:00
|
|
|
WGPUBufferUsage usage,
|
|
|
|
|
uint64_t per_sub_buffer_capacity,
|
|
|
|
|
const char* label_prefix) {
|
2026-05-28 12:21:52 +10:00
|
|
|
destroy();
|
2026-05-28 14:08:16 +10:00
|
|
|
instance_ = instance;
|
|
|
|
|
device_ = device;
|
|
|
|
|
usage_ = usage;
|
|
|
|
|
per_sub_buffer_capacity_ = per_sub_buffer_capacity;
|
2026-05-28 17:31:31 +10:00
|
|
|
last_growth_size_ = per_sub_buffer_capacity;
|
2026-05-28 14:08:16 +10:00
|
|
|
label_prefix_ = label_prefix ? label_prefix : "";
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
void BufferPool::destroy() {
|
2026-07-03 08:38:57 +10:00
|
|
|
for (auto& sub_pool : sub_pools_) {
|
|
|
|
|
if (sub_pool.buffer) wgpuBufferRelease(sub_pool.buffer);
|
2026-05-28 14:08:16 +10:00
|
|
|
}
|
|
|
|
|
sub_pools_.clear();
|
|
|
|
|
device_ = nullptr;
|
|
|
|
|
instance_ = nullptr;
|
|
|
|
|
usage_ = 0;
|
|
|
|
|
per_sub_buffer_capacity_ = 0;
|
2026-05-28 17:31:31 +10:00
|
|
|
last_growth_size_ = 0;
|
2026-05-28 14:08:16 +10:00
|
|
|
growth_disabled_ = false;
|
2026-06-29 12:13:34 +10:00
|
|
|
growth_pending_ = false;
|
2026-05-28 14:08:16 +10:00
|
|
|
label_prefix_.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
bool BufferPool::addSubBuffer() {
|
2026-05-28 14:08:16 +10:00
|
|
|
if (!device_ || per_sub_buffer_capacity_ == 0) return false;
|
|
|
|
|
if (growth_disabled_) return false;
|
|
|
|
|
|
2026-05-28 17:31:31 +10:00
|
|
|
// 64 MB floor: smaller sub-buffers aren't worth the per-allocation
|
|
|
|
|
// bookkeeping cost (one bind group per chunk, free-list overhead).
|
|
|
|
|
// If the driver won't grant even 64 MB the pool is genuinely at
|
|
|
|
|
// its ceiling; growth_disabled_ latches and future grow attempts
|
|
|
|
|
// skip the doomed retry.
|
|
|
|
|
constexpr uint64_t MIN_SUB_BUFFER_BYTES = 64ull * 1024 * 1024;
|
|
|
|
|
uint64_t try_size = last_growth_size_ > 0
|
|
|
|
|
? last_growth_size_
|
|
|
|
|
: per_sub_buffer_capacity_;
|
|
|
|
|
if (try_size < MIN_SUB_BUFFER_BYTES) try_size = MIN_SUB_BUFFER_BYTES;
|
|
|
|
|
|
2026-06-29 12:13:34 +10:00
|
|
|
#if defined(__EMSCRIPTEN__)
|
|
|
|
|
// Web can't synchronously learn whether createBuffer OOM'd: the
|
|
|
|
|
// desktop spin-wait that drains PopErrorScope would block the JS
|
|
|
|
|
// event loop so the resolving microtask never runs (page hangs), and
|
|
|
|
|
// Dawn returns a NON-NULL error buffer on OOM — so a plain
|
|
|
|
|
// `buf != nullptr` check silently accepts an invalid buffer, and
|
|
|
|
|
// every bind group built against it then fails ("BindGroup is
|
|
|
|
|
// invalid" spam). Instead add the sub-buffer as *provisional* (alloc
|
|
|
|
|
// skips it), then validate it through a non-blocking async error
|
|
|
|
|
// scope. resolveProvisionalGrowth() clears the flag once it's known
|
|
|
|
|
// good, or drops it and latches growth_disabled_ on a real OOM. Only
|
|
|
|
|
// one provisional grow is ever in flight (growth_pending_), so a hung
|
|
|
|
|
// validation can't spawn a pile of sub-buffers.
|
|
|
|
|
if (growth_pending_) return false;
|
|
|
|
|
|
|
|
|
|
char label[128];
|
|
|
|
|
std::snprintf(label, sizeof(label), "%s.sub%zu",
|
|
|
|
|
label_prefix_.c_str(), sub_pools_.size());
|
|
|
|
|
WGPUBufferDescriptor desc = {};
|
|
|
|
|
desc.usage = usage_;
|
|
|
|
|
desc.size = try_size;
|
|
|
|
|
desc.label.data = label;
|
|
|
|
|
desc.label.length = std::strlen(label);
|
|
|
|
|
|
|
|
|
|
wgpuDevicePushErrorScope(device_, WGPUErrorFilter_OutOfMemory);
|
|
|
|
|
WGPUBuffer buf = wgpuDeviceCreateBuffer(device_, &desc);
|
|
|
|
|
|
|
|
|
|
SubPool sp;
|
|
|
|
|
sp.buffer = buf;
|
|
|
|
|
sp.capacity = try_size;
|
|
|
|
|
sp.used = 0;
|
|
|
|
|
sp.provisional = true;
|
|
|
|
|
sp.free_ranges.push_back({0, try_size});
|
|
|
|
|
sub_pools_.push_back(std::move(sp));
|
|
|
|
|
last_growth_size_ = try_size;
|
|
|
|
|
growth_pending_ = true;
|
|
|
|
|
|
|
|
|
|
WGPUPopErrorScopeCallbackInfo pcb = {};
|
|
|
|
|
pcb.mode = WGPUCallbackMode_AllowSpontaneous;
|
|
|
|
|
pcb.callback = [](WGPUPopErrorScopeStatus, WGPUErrorType type,
|
|
|
|
|
WGPUStringView, void* ud1, void* /*ud2*/) {
|
|
|
|
|
static_cast<BufferPool*>(ud1)->resolveProvisionalGrowth(
|
|
|
|
|
type != WGPUErrorType_NoError);
|
|
|
|
|
};
|
|
|
|
|
pcb.userdata1 = this;
|
|
|
|
|
wgpuDevicePopErrorScope(device_, pcb);
|
|
|
|
|
|
|
|
|
|
// No usable space yet: the provisional sub-buffer isn't handed out
|
|
|
|
|
// until validated. alloc fails this frame and retries on a later one.
|
|
|
|
|
return false;
|
|
|
|
|
#else
|
2026-05-28 17:31:31 +10:00
|
|
|
while (try_size >= MIN_SUB_BUFFER_BYTES) {
|
|
|
|
|
char label[128];
|
|
|
|
|
std::snprintf(label, sizeof(label), "%s.sub%zu",
|
|
|
|
|
label_prefix_.c_str(), sub_pools_.size());
|
|
|
|
|
|
|
|
|
|
WGPUBufferDescriptor desc = {};
|
|
|
|
|
desc.usage = usage_;
|
|
|
|
|
desc.size = try_size;
|
|
|
|
|
desc.label.data = label;
|
|
|
|
|
desc.label.length = std::strlen(label);
|
2026-06-09 17:16:14 +10:00
|
|
|
|
|
|
|
|
// wgpu-native classifies "Not enough memory left" as Validation,
|
|
|
|
|
// not OutOfMemory. Nested scopes: OOM inner, Validation outer.
|
|
|
|
|
wgpuDevicePushErrorScope(device_, WGPUErrorFilter_Validation);
|
|
|
|
|
wgpuDevicePushErrorScope(device_, WGPUErrorFilter_OutOfMemory);
|
|
|
|
|
|
2026-05-28 17:31:31 +10:00
|
|
|
WGPUBuffer buf = wgpuDeviceCreateBuffer(device_, &desc);
|
|
|
|
|
|
|
|
|
|
struct PopResult { bool done = false; bool error = false; };
|
2026-07-03 08:38:57 +10:00
|
|
|
auto pop = [&](PopResult& pop_result) {
|
2026-05-28 17:31:31 +10:00
|
|
|
WGPUPopErrorScopeCallbackInfo pcb = {};
|
|
|
|
|
pcb.mode = WGPUCallbackMode_AllowProcessEvents;
|
|
|
|
|
pcb.callback = [](WGPUPopErrorScopeStatus, WGPUErrorType type,
|
|
|
|
|
WGPUStringView, void* ud1, void* /*ud2*/) {
|
|
|
|
|
auto* p = static_cast<PopResult*>(ud1);
|
|
|
|
|
p->done = true;
|
|
|
|
|
p->error = (type != WGPUErrorType_NoError);
|
|
|
|
|
};
|
2026-07-03 08:38:57 +10:00
|
|
|
pcb.userdata1 = &pop_result;
|
2026-05-28 17:31:31 +10:00
|
|
|
wgpuDevicePopErrorScope(device_, pcb);
|
2026-07-03 08:38:57 +10:00
|
|
|
while (!pop_result.done) wgpuInstanceProcessEvents(instance_);
|
2026-05-28 14:08:16 +10:00
|
|
|
};
|
2026-05-28 17:31:31 +10:00
|
|
|
PopResult oom_pop, validation_pop;
|
|
|
|
|
pop(oom_pop);
|
|
|
|
|
pop(validation_pop);
|
2026-06-09 17:16:14 +10:00
|
|
|
const bool ok = buf && !oom_pop.error && !validation_pop.error;
|
2026-05-28 17:31:31 +10:00
|
|
|
|
2026-06-09 17:16:14 +10:00
|
|
|
if (ok) {
|
2026-05-28 17:31:31 +10:00
|
|
|
SubPool sp;
|
|
|
|
|
sp.buffer = buf;
|
|
|
|
|
sp.capacity = try_size;
|
|
|
|
|
sp.used = 0;
|
|
|
|
|
sp.free_ranges.push_back({0, try_size});
|
|
|
|
|
sub_pools_.push_back(std::move(sp));
|
|
|
|
|
last_growth_size_ = try_size;
|
2026-06-04 18:20:37 +10:00
|
|
|
std::fprintf(stderr,
|
|
|
|
|
"[wgpu pool] added sub-buffer %zu (%llu MB); pool total now %llu MB\n",
|
|
|
|
|
sub_pools_.size() - 1,
|
|
|
|
|
(unsigned long long)(try_size / (1024 * 1024)),
|
|
|
|
|
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)));
|
2026-05-28 17:31:31 +10:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-05-28 14:08:16 +10:00
|
|
|
if (buf) wgpuBufferRelease(buf);
|
2026-05-28 17:31:31 +10:00
|
|
|
try_size /= 2;
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 18:20:37 +10:00
|
|
|
std::fprintf(stderr,
|
|
|
|
|
"[wgpu pool] driver refused growth even at %llu MB; "
|
|
|
|
|
"pool capped at %llu MB across %zu sub-buffer(s) — growth disabled\n",
|
|
|
|
|
(unsigned long long)(MIN_SUB_BUFFER_BYTES / (1024 * 1024)),
|
|
|
|
|
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)),
|
|
|
|
|
sub_pools_.size());
|
2026-05-28 17:31:31 +10:00
|
|
|
growth_disabled_ = true;
|
|
|
|
|
return false;
|
2026-06-29 12:13:34 +10:00
|
|
|
#endif // __EMSCRIPTEN__
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if defined(__EMSCRIPTEN__)
|
|
|
|
|
void BufferPool::resolveProvisionalGrowth(bool failed) {
|
|
|
|
|
growth_pending_ = false;
|
|
|
|
|
// The provisional sub-pool is the most recently added; locate it from
|
|
|
|
|
// the back (growth_pending_ guaranteed no others were appended).
|
|
|
|
|
for (size_t i = sub_pools_.size(); i-- > 0; ) {
|
|
|
|
|
if (!sub_pools_[i].provisional) continue;
|
|
|
|
|
if (failed) {
|
|
|
|
|
if (sub_pools_[i].buffer) wgpuBufferRelease(sub_pools_[i].buffer);
|
|
|
|
|
sub_pools_.erase(sub_pools_.begin() + i);
|
|
|
|
|
growth_disabled_ = true;
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
"[wgpu pool] sub-buffer grow OOM'd; pool capped at %llu MB "
|
|
|
|
|
"across %zu sub-buffer(s) — growth disabled\n",
|
|
|
|
|
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)),
|
|
|
|
|
sub_pools_.size());
|
|
|
|
|
} else {
|
2026-07-03 08:38:57 +10:00
|
|
|
SubPool& sub_pool = sub_pools_[i];
|
|
|
|
|
sub_pool.provisional = false;
|
2026-06-29 12:13:34 +10:00
|
|
|
std::fprintf(stderr,
|
|
|
|
|
"[wgpu pool] added sub-buffer %zu (%llu MB); pool total now %llu MB\n",
|
|
|
|
|
i,
|
2026-07-03 08:38:57 +10:00
|
|
|
(unsigned long long)(sub_pool.capacity / (1024 * 1024)),
|
2026-06-29 12:13:34 +10:00
|
|
|
(unsigned long long)(total_capacity_bytes() / (1024 * 1024)));
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
2026-06-29 12:13:34 +10:00
|
|
|
#endif // __EMSCRIPTEN__
|
2026-05-28 12:21:52 +10:00
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
|
2026-05-28 14:08:16 +10:00
|
|
|
Slice out;
|
|
|
|
|
if (size == 0 || align == 0) return out;
|
2026-05-28 12:21:52 +10:00
|
|
|
|
2026-05-28 14:08:16 +10:00
|
|
|
// First-fit across all sub-buffers. When none fits, try to grow by
|
|
|
|
|
// adding another sub-buffer and retry once.
|
|
|
|
|
for (int attempt = 0; attempt < 2; ++attempt) {
|
|
|
|
|
for (size_t sp_idx = 0; sp_idx < sub_pools_.size(); ++sp_idx) {
|
2026-07-03 08:38:57 +10:00
|
|
|
SubPool& sub_pool = sub_pools_[sp_idx];
|
2026-06-29 12:13:34 +10:00
|
|
|
// Web: never allocate out of a sub-buffer still awaiting OOM
|
|
|
|
|
// validation — its handle may be a Dawn error buffer.
|
2026-07-03 08:38:57 +10:00
|
|
|
if (sub_pool.provisional) continue;
|
|
|
|
|
for (size_t i = 0; i < sub_pool.free_ranges.size(); ++i) {
|
|
|
|
|
const FreeRange& free_range = sub_pool.free_ranges[i];
|
|
|
|
|
const uint64_t aligned = (free_range.offset + (align - 1)) & ~(align - 1);
|
|
|
|
|
const uint64_t alignment_padding = aligned - free_range.offset;
|
|
|
|
|
if (alignment_padding >= free_range.size) continue;
|
|
|
|
|
if (size > free_range.size - alignment_padding) continue;
|
2026-05-28 14:08:16 +10:00
|
|
|
|
|
|
|
|
const uint64_t post_off = aligned + size;
|
2026-07-03 08:38:57 +10:00
|
|
|
const uint64_t post_size = (free_range.offset + free_range.size) - post_off;
|
2026-05-28 12:21:52 +10:00
|
|
|
|
2026-07-03 08:38:57 +10:00
|
|
|
if (alignment_padding == 0 && post_size == 0) {
|
|
|
|
|
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
|
|
|
|
|
} else if (alignment_padding == 0) {
|
|
|
|
|
sub_pool.free_ranges[i] = {post_off, post_size};
|
2026-05-28 14:08:16 +10:00
|
|
|
} else if (post_size == 0) {
|
2026-07-03 08:38:57 +10:00
|
|
|
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
|
2026-05-28 14:08:16 +10:00
|
|
|
} else {
|
2026-07-03 08:38:57 +10:00
|
|
|
sub_pool.free_ranges[i] = {free_range.offset, alignment_padding};
|
|
|
|
|
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i + 1,
|
2026-05-28 14:08:16 +10:00
|
|
|
{post_off, post_size});
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 08:38:57 +10:00
|
|
|
sub_pool.used += size;
|
|
|
|
|
out.buffer = sub_pool.buffer;
|
2026-05-28 14:08:16 +10:00
|
|
|
out.offset = aligned;
|
|
|
|
|
out.size = size;
|
|
|
|
|
out.sub_idx = int(sp_idx);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Existing sub-buffers can't fit. Grow once before giving up.
|
|
|
|
|
if (attempt == 0) {
|
|
|
|
|
if (!addSubBuffer()) break;
|
|
|
|
|
}
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
2026-05-28 14:08:16 +10:00
|
|
|
return out;
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
void BufferPool::free(const Slice& s) {
|
2026-05-28 14:08:16 +10:00
|
|
|
if (!s.valid()) return;
|
|
|
|
|
if (s.sub_idx < 0 || size_t(s.sub_idx) >= sub_pools_.size()) return;
|
2026-07-03 08:38:57 +10:00
|
|
|
SubPool& sub_pool = sub_pools_[size_t(s.sub_idx)];
|
|
|
|
|
assert(s.offset + s.size <= sub_pool.capacity);
|
2026-05-28 12:21:52 +10:00
|
|
|
|
|
|
|
|
size_t i = 0;
|
2026-07-03 08:38:57 +10:00
|
|
|
while (i < sub_pool.free_ranges.size() && sub_pool.free_ranges[i].offset < s.offset) ++i;
|
|
|
|
|
sub_pool.free_ranges.insert(sub_pool.free_ranges.begin() + i, {s.offset, s.size});
|
|
|
|
|
sub_pool.used -= s.size;
|
|
|
|
|
|
|
|
|
|
if (i + 1 < sub_pool.free_ranges.size()
|
|
|
|
|
&& sub_pool.free_ranges[i].offset + sub_pool.free_ranges[i].size
|
|
|
|
|
== sub_pool.free_ranges[i + 1].offset) {
|
|
|
|
|
sub_pool.free_ranges[i].size += sub_pool.free_ranges[i + 1].size;
|
|
|
|
|
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i + 1);
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
|
|
|
|
if (i > 0
|
2026-07-03 08:38:57 +10:00
|
|
|
&& sub_pool.free_ranges[i - 1].offset + sub_pool.free_ranges[i - 1].size
|
|
|
|
|
== sub_pool.free_ranges[i].offset) {
|
|
|
|
|
sub_pool.free_ranges[i - 1].size += sub_pool.free_ranges[i].size;
|
|
|
|
|
sub_pool.free_ranges.erase(sub_pool.free_ranges.begin() + i);
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
uint64_t BufferPool::total_capacity_bytes() const {
|
2026-07-03 08:38:57 +10:00
|
|
|
uint64_t total_capacity = 0;
|
2026-06-29 12:13:34 +10:00
|
|
|
// Skip provisional sub-pools (web, awaiting OOM validation) — their
|
|
|
|
|
// capacity isn't usable yet, so counting it would mislead the
|
|
|
|
|
// evictor's "is there room?" heuristics.
|
2026-07-03 08:38:57 +10:00
|
|
|
for (const auto& sub_pool : sub_pools_) {
|
|
|
|
|
if (!sub_pool.provisional) total_capacity += sub_pool.capacity;
|
|
|
|
|
}
|
|
|
|
|
return total_capacity;
|
2026-05-28 14:08:16 +10:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
uint64_t BufferPool::total_used_bytes() const {
|
2026-07-03 08:38:57 +10:00
|
|
|
uint64_t total_used = 0;
|
|
|
|
|
for (const auto& sub_pool : sub_pools_) {
|
|
|
|
|
if (!sub_pool.provisional) total_used += sub_pool.used;
|
|
|
|
|
}
|
|
|
|
|
return total_used;
|
2026-05-28 14:08:16 +10:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 11:11:14 +10:00
|
|
|
uint64_t BufferPool::largest_free_run_bytes() const {
|
2026-07-03 08:38:57 +10:00
|
|
|
uint64_t largest_free_run = 0;
|
|
|
|
|
for (const auto& sub_pool : sub_pools_) {
|
|
|
|
|
if (sub_pool.provisional) continue;
|
|
|
|
|
for (const auto& free_range : sub_pool.free_ranges) {
|
|
|
|
|
if (free_range.size > largest_free_run) largest_free_run = free_range.size;
|
2026-05-28 14:08:16 +10:00
|
|
|
}
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
2026-07-03 08:38:57 +10:00
|
|
|
return largest_free_run;
|
2026-05-28 12:21:52 +10:00
|
|
|
}
|
2026-06-04 17:19:24 +10:00
|
|
|
|
|
|
|
|
void BufferPool::addSubBufferForTesting(WGPUBuffer fake_buffer, uint64_t capacity) {
|
|
|
|
|
SubPool sp;
|
|
|
|
|
sp.buffer = fake_buffer;
|
|
|
|
|
sp.capacity = capacity;
|
|
|
|
|
sp.used = 0;
|
|
|
|
|
sp.free_ranges.push_back({0, capacity});
|
|
|
|
|
sub_pools_.push_back(std::move(sp));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BufferPool::clearSubPoolsForTesting() {
|
|
|
|
|
// Skip wgpuBufferRelease — handles are fakes that would crash on deref.
|
|
|
|
|
sub_pools_.clear();
|
|
|
|
|
}
|