UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

127 lines (124 loc) 4.78 kB
import { Debug } from '../../core/debug.js'; import { math } from '../../core/math/math.js'; /** * @import { DynamicBuffer } from './dynamic-buffer.js' * @import { GraphicsDevice } from './graphics-device.js' */ /** * A container for storing the used areas of a pair of staging and gpu buffers. * * @ignore */ class UsedBuffer { } /** * A container for storing the return values of an allocation function. * * @ignore */ class DynamicBufferAllocation { } /** * The DynamicBuffers class provides a dynamic memory allocation system for uniform buffer data, * particularly for non-persistent uniform buffers. This class utilizes a bump allocator to * efficiently allocate aligned memory space from a set of large buffers managed internally. To * utilize this system, the user writes data to CPU-accessible staging buffers. When submitting * command buffers that require these buffers, the system automatically uploads the data to the GPU * buffers. This approach ensures efficient memory management and smooth data transfer between the * CPU and GPU. * * @ignore */ class DynamicBuffers { /** * Create the system of dynamic buffers. * * @param {GraphicsDevice} device - The graphics device. * @param {number} bufferSize - The size of the underlying large buffers. * @param {number} bufferAlignment - Alignment of each allocation. */ constructor(device, bufferSize, bufferAlignment){ /** * Internally allocated gpu buffers. * * @type {DynamicBuffer[]} */ this.gpuBuffers = []; /** * Internally allocated staging buffers (CPU writable) * * @type {DynamicBuffer[]} */ this.stagingBuffers = []; /** * @type {UsedBuffer[]} */ this.usedBuffers = []; /** * @type {UsedBuffer|null} */ this.activeBuffer = null; this.device = device; this.bufferSize = bufferSize; this.bufferAlignment = bufferAlignment; } /** * Destroy the system of dynamic buffers. */ destroy() { this.gpuBuffers.forEach((gpuBuffer)=>{ gpuBuffer.destroy(this.device); }); this.gpuBuffers = null; this.stagingBuffers.forEach((stagingBuffer)=>{ stagingBuffer.destroy(this.device); }); this.stagingBuffers = null; this.usedBuffers = null; this.activeBuffer = null; } /** * Allocate an aligned space of the given size from a dynamic buffer. * * @param {DynamicBufferAllocation} allocation - The allocation info to fill. * @param {number} size - The size of the allocation. */ alloc(allocation, size) { // if we have active buffer without enough space if (this.activeBuffer) { const alignedStart = math.roundUp(this.activeBuffer.size, this.bufferAlignment); const space = this.bufferSize - alignedStart; if (space < size) { // we're done with this buffer, schedule it for submit this.scheduleSubmit(); } } // if we don't have an active buffer, allocate new one if (!this.activeBuffer) { // gpu buffer let gpuBuffer = this.gpuBuffers.pop(); if (!gpuBuffer) { gpuBuffer = this.createBuffer(this.device, this.bufferSize, false); } // staging buffer let stagingBuffer = this.stagingBuffers.pop(); if (!stagingBuffer) { stagingBuffer = this.createBuffer(this.device, this.bufferSize, true); } this.activeBuffer = new UsedBuffer(); this.activeBuffer.stagingBuffer = stagingBuffer; this.activeBuffer.gpuBuffer = gpuBuffer; this.activeBuffer.offset = 0; this.activeBuffer.size = 0; } // allocate from active buffer const activeBuffer = this.activeBuffer; const alignedStart = math.roundUp(activeBuffer.size, this.bufferAlignment); Debug.assert(alignedStart + size <= this.bufferSize, `The allocation size of ${size} is larger than the buffer size of ${this.bufferSize}`); allocation.gpuBuffer = activeBuffer.gpuBuffer; allocation.offset = alignedStart; allocation.storage = activeBuffer.stagingBuffer.alloc(alignedStart, size); // take the allocation from the buffer activeBuffer.size = alignedStart + size; } scheduleSubmit() { if (this.activeBuffer) { this.usedBuffers.push(this.activeBuffer); this.activeBuffer = null; } } submit() { // schedule currently active buffer for submit this.scheduleSubmit(); } } export { DynamicBufferAllocation, DynamicBuffers };