| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| const stats = { |
| allocBytes: 0, |
| freedBytes: 0, |
| liveBytes: 0, |
| highWaterBytes: 0, |
| liveBuffers: 0, |
| retiredBytes: 0, |
| retiredBuffers: 0, |
| submittedHighWaterBytes: 0, |
| }; |
|
|
| export function getArenaStats() { |
| return { |
| ...stats, |
| submittedBytes: stats.liveBytes + stats.retiredBytes, |
| submittedBuffers: stats.liveBuffers + stats.retiredBuffers, |
| }; |
| } |
|
|
| |
| |
| export function resetArenaPeak() { |
| stats.highWaterBytes = stats.liveBytes; |
| stats.submittedHighWaterBytes = stats.liveBytes + stats.retiredBytes; |
| } |
|
|
| function trackAlloc(size) { |
| stats.allocBytes += size; |
| stats.liveBytes += size; |
| stats.liveBuffers += 1; |
| if (stats.liveBytes > stats.highWaterBytes) stats.highWaterBytes = stats.liveBytes; |
| const submitted = stats.liveBytes + stats.retiredBytes; |
| if (submitted > stats.submittedHighWaterBytes) { |
| stats.submittedHighWaterBytes = submitted; |
| } |
| } |
|
|
| function trackFree(size) { |
| stats.freedBytes += size; |
| stats.liveBytes -= size; |
| stats.liveBuffers -= 1; |
| } |
|
|
| function trackRetire(size) { |
| stats.liveBytes -= size; |
| stats.liveBuffers -= 1; |
| stats.retiredBytes += size; |
| stats.retiredBuffers += 1; |
| } |
|
|
| function trackDrained(size) { |
| stats.retiredBytes -= size; |
| stats.retiredBuffers -= 1; |
| stats.freedBytes += size; |
| } |
|
|
| export function createArena(device) { |
| const buffers = []; |
| |
| return { |
| |
| buf(byteLength, usage, label) { |
| const size = Math.ceil(byteLength / 4) * 4; |
| const buffer = device.createBuffer({ label, size, usage }); |
| buffers.push([buffer, size]); |
| trackAlloc(size); |
| return buffer; |
| }, |
| |
| uniform(view, label) { |
| const size = Math.max(16, Math.ceil(view.byteLength / 16) * 16); |
| const buffer = device.createBuffer({ |
| label, |
| size, |
| usage: GPUBufferUsage.UNIFORM, |
| mappedAtCreation: true, |
| }); |
| new Uint8Array(buffer.getMappedRange()) |
| .set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)); |
| buffer.unmap(); |
| buffers.push([buffer, size]); |
| trackAlloc(size); |
| return buffer; |
| }, |
| destroy() { |
| for (const [b, size] of buffers) { |
| b.destroy(); |
| trackFree(size); |
| } |
| buffers.length = 0; |
| }, |
| |
| |
| |
| |
| destroyDeferred(drained) { |
| if (!drained || typeof drained.then !== 'function') { |
| throw new Error('arena.destroyDeferred needs a queue-drain promise'); |
| } |
| if (buffers.length === 0) return Promise.resolve(); |
| const retired = buffers.splice(0); |
| for (const [b, size] of retired) { |
| b.destroy(); |
| trackRetire(size); |
| } |
| const release = () => { |
| for (const [, size] of retired) trackDrained(size); |
| }; |
| return Promise.resolve(drained).then(release, release); |
| }, |
| }; |
| } |
|
|
| |
| |
| |
| |
| export function createSplitArena(device) { |
| const retained = createArena(device); |
| const scratch = createArena(device); |
| return { |
| retained, |
| scratch, |
| destroyScratch() { scratch.destroy(); }, |
| retireScratch(drained) { return scratch.destroyDeferred(drained); }, |
| destroy() { |
| scratch.destroy(); |
| retained.destroy(); |
| }, |
| }; |
| } |
|
|