UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

309 lines (306 loc) 13 kB
import { Debug } from '../../core/debug.js'; import { uniformTypeToName, UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC2, UNIFORMTYPE_VEC3, UNIFORMTYPE_VEC4, UNIFORMTYPE_INT, UNIFORMTYPE_IVEC2, UNIFORMTYPE_IVEC3, UNIFORMTYPE_IVEC4, UNIFORMTYPE_MAT2, UNIFORMTYPE_MAT3, UNIFORMTYPE_FLOATARRAY, UNIFORMTYPE_VEC2ARRAY, UNIFORMTYPE_VEC3ARRAY, UNIFORMTYPE_UINT, UNIFORMTYPE_UVEC2, UNIFORMTYPE_UVEC3, UNIFORMTYPE_UVEC4, UNIFORMTYPE_INTARRAY, UNIFORMTYPE_BOOLARRAY, UNIFORMTYPE_UINTARRAY, UNIFORMTYPE_IVEC2ARRAY, UNIFORMTYPE_BVEC2ARRAY, UNIFORMTYPE_UVEC2ARRAY, UNIFORMTYPE_IVEC3ARRAY, UNIFORMTYPE_BVEC3ARRAY, UNIFORMTYPE_UVEC3ARRAY } from './constants.js'; import { DebugGraphics } from './debug-graphics.js'; import { DynamicBufferAllocation } from './dynamic-buffers.js'; /** * @import { DynamicBindGroup } from './bind-group.js' * @import { GraphicsDevice } from './graphics-device.js' * @import { UniformBufferFormat } from './uniform-buffer-format.js' * @import { UniformFormat } from './uniform-buffer-format.js' */ // Uniform buffer set functions - only implemented for types for which the default // array to buffer copy does not work, or could be slower. const _updateFunctions = []; _updateFunctions[UNIFORMTYPE_FLOAT] = function(uniformBuffer, value, offset) { const dst = uniformBuffer.storageFloat32; dst[offset] = value; }; _updateFunctions[UNIFORMTYPE_VEC2] = (uniformBuffer, value, offset)=>{ const dst = uniformBuffer.storageFloat32; dst[offset] = value[0]; dst[offset + 1] = value[1]; }; _updateFunctions[UNIFORMTYPE_VEC3] = (uniformBuffer, value, offset)=>{ const dst = uniformBuffer.storageFloat32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 2] = value[2]; }; _updateFunctions[UNIFORMTYPE_VEC4] = (uniformBuffer, value, offset)=>{ const dst = uniformBuffer.storageFloat32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 2] = value[2]; dst[offset + 3] = value[3]; }; _updateFunctions[UNIFORMTYPE_INT] = function(uniformBuffer, value, offset) { const dst = uniformBuffer.storageInt32; dst[offset] = value; }; _updateFunctions[UNIFORMTYPE_IVEC2] = function(uniformBuffer, value, offset) { const dst = uniformBuffer.storageInt32; dst[offset] = value[0]; dst[offset + 1] = value[1]; }; _updateFunctions[UNIFORMTYPE_IVEC3] = function(uniformBuffer, value, offset) { const dst = uniformBuffer.storageInt32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 2] = value[2]; }; _updateFunctions[UNIFORMTYPE_IVEC4] = function(uniformBuffer, value, offset) { const dst = uniformBuffer.storageInt32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 2] = value[2]; dst[offset + 3] = value[3]; }; // convert from continuous array to vec2[3] with padding to vec4[2] _updateFunctions[UNIFORMTYPE_MAT2] = (uniformBuffer, value, offset)=>{ const dst = uniformBuffer.storageFloat32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 4] = value[2]; dst[offset + 5] = value[3]; dst[offset + 8] = value[4]; dst[offset + 9] = value[5]; }; // convert from continuous array to vec3[3] with padding to vec4[3] _updateFunctions[UNIFORMTYPE_MAT3] = (uniformBuffer, value, offset)=>{ const dst = uniformBuffer.storageFloat32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 2] = value[2]; dst[offset + 4] = value[3]; dst[offset + 5] = value[4]; dst[offset + 6] = value[5]; dst[offset + 8] = value[6]; dst[offset + 9] = value[7]; dst[offset + 10] = value[8]; }; _updateFunctions[UNIFORMTYPE_FLOATARRAY] = function(uniformBuffer, value, offset, count) { const dst = uniformBuffer.storageFloat32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i]; } }; _updateFunctions[UNIFORMTYPE_VEC2ARRAY] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageFloat32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i * 2]; dst[offset + i * 4 + 1] = value[i * 2 + 1]; } }; _updateFunctions[UNIFORMTYPE_VEC3ARRAY] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageFloat32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i * 3]; dst[offset + i * 4 + 1] = value[i * 3 + 1]; dst[offset + i * 4 + 2] = value[i * 3 + 2]; } }; _updateFunctions[UNIFORMTYPE_UINT] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageUint32; dst[offset] = value; }; _updateFunctions[UNIFORMTYPE_UVEC2] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageUint32; dst[offset] = value[0]; dst[offset + 1] = value[1]; }; _updateFunctions[UNIFORMTYPE_UVEC3] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageUint32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 2] = value[2]; }; _updateFunctions[UNIFORMTYPE_UVEC4] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageUint32; dst[offset] = value[0]; dst[offset + 1] = value[1]; dst[offset + 2] = value[2]; dst[offset + 3] = value[3]; }; _updateFunctions[UNIFORMTYPE_INTARRAY] = function(uniformBuffer, value, offset, count) { const dst = uniformBuffer.storageInt32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i]; } }; _updateFunctions[UNIFORMTYPE_BOOLARRAY] = _updateFunctions[UNIFORMTYPE_INTARRAY]; _updateFunctions[UNIFORMTYPE_UINTARRAY] = function(uniformBuffer, value, offset, count) { const dst = uniformBuffer.storageUint32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i]; } }; _updateFunctions[UNIFORMTYPE_IVEC2ARRAY] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageInt32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i * 2]; dst[offset + i * 4 + 1] = value[i * 2 + 1]; } }; _updateFunctions[UNIFORMTYPE_BVEC2ARRAY] = _updateFunctions[UNIFORMTYPE_IVEC2ARRAY]; _updateFunctions[UNIFORMTYPE_UVEC2ARRAY] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageUint32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i * 2]; dst[offset + i * 4 + 1] = value[i * 2 + 1]; } }; _updateFunctions[UNIFORMTYPE_IVEC3ARRAY] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageInt32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i * 3]; dst[offset + i * 4 + 1] = value[i * 3 + 1]; dst[offset + i * 4 + 2] = value[i * 3 + 2]; } }; _updateFunctions[UNIFORMTYPE_BVEC3ARRAY] = _updateFunctions[UNIFORMTYPE_IVEC3ARRAY]; _updateFunctions[UNIFORMTYPE_UVEC3ARRAY] = (uniformBuffer, value, offset, count)=>{ const dst = uniformBuffer.storageUint32; for(let i = 0; i < count; i++){ dst[offset + i * 4] = value[i * 3]; dst[offset + i * 4 + 1] = value[i * 3 + 1]; dst[offset + i * 4 + 2] = value[i * 3 + 2]; } }; /** * A uniform buffer represents a GPU memory buffer storing the uniforms. * * @ignore */ class UniformBuffer { /** * Create a new UniformBuffer instance. * * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this uniform * buffer. * @param {UniformBufferFormat} format - Format of the uniform buffer. * @param {boolean} [persistent] - Whether the buffer is persistent. Defaults to true. */ constructor(graphicsDevice, format, persistent = true){ /** * A render version used to track the last time the properties requiring bind group to be * updated were changed. * * @type {number} */ this.renderVersionDirty = 0; this.device = graphicsDevice; this.format = format; this.persistent = persistent; Debug.assert(format); if (persistent) { this.impl = graphicsDevice.createUniformBufferImpl(this); const storage = new ArrayBuffer(format.byteSize); this.assignStorage(new Int32Array(storage)); graphicsDevice._vram.ub += this.format.byteSize; // TODO: register with the device and handle lost context // this.device.buffers.push(this); } else { this.allocation = new DynamicBufferAllocation(); } } /** * Frees resources associated with this uniform buffer. */ destroy() { if (this.persistent) { // stop tracking the vertex buffer // TODO: remove the buffer from the list on the device (lost context handling) const device = this.device; this.impl.destroy(device); device._vram.ub -= this.format.byteSize; } } get offset() { return this.persistent ? 0 : this.allocation.offset; } /** * Assign a storage to this uniform buffer. * * @param {Int32Array} storage - The storage to assign to this uniform buffer. */ assignStorage(storage) { this.storageInt32 = storage; this.storageUint32 = new Uint32Array(storage.buffer, storage.byteOffset, storage.byteLength / 4); this.storageFloat32 = new Float32Array(storage.buffer, storage.byteOffset, storage.byteLength / 4); } /** * Called when the rendering context was lost. It releases all context related resources. */ loseContext() { this.impl?.loseContext(); } /** * Assign a value to the uniform specified by its format. This is the fast version of assigning * a value to a uniform, avoiding any lookups. * * @param {UniformFormat} uniformFormat - The format of the uniform. * @param {any} value - The value to assign to the uniform. */ setUniform(uniformFormat, value) { Debug.assert(uniformFormat); const offset = uniformFormat.offset; if (value !== null && value !== undefined) { const updateFunction = _updateFunctions[uniformFormat.updateType]; if (updateFunction) { updateFunction(this, value, offset, uniformFormat.count); } else { this.storageFloat32.set(value, offset); } } else { Debug.warnOnce(`Value was not set when assigning to uniform [${uniformFormat.name}]` + `, expected type ${uniformTypeToName[uniformFormat.type]} while rendering ${DebugGraphics.toString()}`); } } /** * Assign a value to the uniform specified by name. * * @param {string} name - The name of the uniform. * @param {any} value - The value to assign to the uniform. */ set(name, value) { const uniformFormat = this.format.map.get(name); Debug.assert(uniformFormat, `Uniform name [${name}] is not part of the Uniform buffer.`); if (uniformFormat) { this.setUniform(uniformFormat, value); } } startUpdate(dynamicBindGroup) { if (!this.persistent) { // allocate memory from dynamic buffer for this frame const allocation = this.allocation; const oldGpuBuffer = allocation.gpuBuffer; this.device.dynamicBuffers.alloc(allocation, this.format.byteSize); this.assignStorage(allocation.storage); // get info about bind group we can use for this non-persistent UB for this frame if (dynamicBindGroup) { dynamicBindGroup.bindGroup = allocation.gpuBuffer.getBindGroup(this); dynamicBindGroup.offsets[0] = allocation.offset; } // buffer has changed, update the render version to force bind group to be updated if (oldGpuBuffer !== allocation.gpuBuffer) { this.renderVersionDirty = this.device.renderVersion; } } } endUpdate() { if (this.persistent) { // Upload the new data this.impl.unlock(this); } else { this.storageFloat32 = null; this.storageInt32 = null; } } /** * @param {DynamicBindGroup} [dynamicBindGroup] - The function fills in the info about the * dynamic bind group for this frame, which uses this uniform buffer. Only used if the uniform * buffer is non-persistent. This allows the uniform buffer to be used without having to create * a bind group for it. Note that the bind group can only contains this single uniform buffer, * and no other resources. */ update(dynamicBindGroup) { this.startUpdate(dynamicBindGroup); // set new values const uniforms = this.format.uniforms; for(let i = 0; i < uniforms.length; i++){ const value = uniforms[i].scopeId.value; this.setUniform(uniforms[i], value); } this.endUpdate(); } } export { UniformBuffer };