playcanvas
Version:
PlayCanvas WebGL game engine
197 lines (194 loc) • 9.51 kB
JavaScript
import { Debug } from '../../core/debug.js';
import { TRACEID_BINDGROUP_ALLOC } from '../../core/constants.js';
import { UNIFORM_BUFFER_DEFAULT_SLOT_NAME } from './constants.js';
import { DebugGraphics } from './debug-graphics.js';
import { getBuiltInTexture } from './built-in-textures.js';
/**
* @import { BindGroupFormat } from './bind-group-format.js'
* @import { GraphicsDevice } from './graphics-device.js'
* @import { StorageBuffer } from './storage-buffer.js'
* @import { Texture } from './texture.js'
* @import { UniformBuffer } from './uniform-buffer.js'
*/ let id = 0;
/**
* Data structure to hold a bind group and its offsets. This is used by {@link UniformBuffer#update}
* to return a dynamic bind group and offset for the uniform buffer.
*
* @ignore
*/ class DynamicBindGroup {
constructor(){
this.offsets = [];
}
}
/**
* A bind group represents a collection of {@link UniformBuffer}, {@link Texture} and
* {@link StorageBuffer} instanced, which can be bind on a GPU for rendering.
*
* @ignore
*/ class BindGroup {
/**
* Create a new Bind Group.
*
* @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this uniform buffer.
* @param {BindGroupFormat} format - Format of the bind group.
* @param {UniformBuffer} [defaultUniformBuffer] - The default uniform buffer. Typically a bind
* group only has a single uniform buffer, and this allows easier access.
*/ constructor(graphicsDevice, format, defaultUniformBuffer){
/**
* A render version the bind group was last updated on.
*
* @type {number}
* @private
*/ this.renderVersionUpdated = -1;
/**
* An array of offsets for each uniform buffer in the bind group. This is the offset in the
* buffer where the uniform buffer data starts.
*
* @type {number[]}
*/ this.uniformBufferOffsets = [];
this.id = id++;
this.device = graphicsDevice;
this.format = format;
this.dirty = true;
this.impl = graphicsDevice.createBindGroupImpl(this);
this.textures = [];
this.storageTextures = [];
this.storageBuffers = [];
this.uniformBuffers = [];
/** @type {UniformBuffer} */ this.defaultUniformBuffer = defaultUniformBuffer;
if (defaultUniformBuffer) {
this.setUniformBuffer(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, defaultUniformBuffer);
}
Debug.trace(TRACEID_BINDGROUP_ALLOC, `Alloc: Id ${this.id}`, this, format);
}
/**
* Frees resources associated with this bind group.
*/ destroy() {
this.impl.destroy();
this.impl = null;
this.format = null;
this.defaultUniformBuffer = null;
}
/**
* Assign a uniform buffer to a slot.
*
* @param {string} name - The name of the uniform buffer slot
* @param {UniformBuffer} uniformBuffer - The Uniform buffer to assign to the slot.
*/ setUniformBuffer(name, uniformBuffer) {
const index = this.format.bufferFormatsMap.get(name);
Debug.assert(index !== undefined, `Setting a uniform [${name}] on a bind group with id ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
if (this.uniformBuffers[index] !== uniformBuffer) {
this.uniformBuffers[index] = uniformBuffer;
this.dirty = true;
}
}
/**
* Assign a storage buffer to a slot.
*
* @param {string} name - The name of the storage buffer slot.
* @param {StorageBuffer} storageBuffer - The storage buffer to assign to the slot.
*/ setStorageBuffer(name, storageBuffer) {
const index = this.format.storageBufferFormatsMap.get(name);
Debug.assert(index !== undefined, `Setting a storage buffer [${name}] on a bind group with id: ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
if (this.storageBuffers[index] !== storageBuffer) {
this.storageBuffers[index] = storageBuffer;
this.dirty = true;
}
}
/**
* Assign a texture to a named slot.
*
* @param {string} name - The name of the texture slot.
* @param {Texture} texture - Texture to assign to the slot.
*/ setTexture(name, texture) {
const index = this.format.textureFormatsMap.get(name);
Debug.assert(index !== undefined, `Setting a texture [${name}] on a bind group with id: ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
if (this.textures[index] !== texture) {
this.textures[index] = texture;
this.dirty = true;
} else if (this.renderVersionUpdated < texture.renderVersionDirty) {
// if the texture properties have changed
this.dirty = true;
}
}
/**
* Assign a storage texture to a named slot.
*
* @param {string} name - The name of the texture slot.
* @param {Texture} texture - Texture to assign to the slot.
*/ setStorageTexture(name, texture) {
const index = this.format.storageTextureFormatsMap.get(name);
Debug.assert(index !== undefined, `Setting a storage texture [${name}] on a bind group with id: ${this.id} which does not contain it, while rendering [${DebugGraphics.toString()}]`, this);
if (this.storageTextures[index] !== texture) {
this.storageTextures[index] = texture;
this.dirty = true;
} else if (this.renderVersionUpdated < texture.renderVersionDirty) {
// if the texture properties have changed
this.dirty = true;
}
}
/**
* Updates the uniform buffers in this bind group.
*/ updateUniformBuffers() {
for(let i = 0; i < this.uniformBuffers.length; i++){
this.uniformBuffers[i].update();
}
}
/**
* Applies any changes made to the bind group's properties. Note that the content of used
* uniform buffers needs to be updated before calling this method.
*/ update() {
// TODO: implement faster version of this, which does not call SetTexture, which does a map lookup
const { textureFormats, storageTextureFormats, storageBufferFormats } = this.format;
for(let i = 0; i < textureFormats.length; i++){
const textureFormat = textureFormats[i];
let value = textureFormat.scopeId.value;
// custom error handling for known global textures
if (!value) {
if (textureFormat.name === 'uSceneDepthMap') {
Debug.errorOnce(`A uSceneDepthMap texture is used by the shader but a scene depth texture is not available. Use CameraComponent.requestSceneDepthMap / enable Depth Grabpass on the Camera Component to enable it. Rendering [${DebugGraphics.toString()}]`);
value = getBuiltInTexture(this.device, 'white');
}
if (textureFormat.name === 'uSceneColorMap') {
Debug.errorOnce(`A uSceneColorMap texture is used by the shader but a scene color texture is not available. Use CameraComponent.requestSceneColorMap / enable Color Grabpass on the Camera Component to enable it. Rendering [${DebugGraphics.toString()}]`);
value = getBuiltInTexture(this.device, 'pink');
}
// missing generic texture
if (!value) {
Debug.errorOnce(`Texture ${textureFormat.name} is required for rendering but was not set. Rendering [${DebugGraphics.toString()}]`);
value = getBuiltInTexture(this.device, 'pink');
}
}
this.setTexture(textureFormat.name, value);
}
for(let i = 0; i < storageTextureFormats.length; i++){
const storageTextureFormat = storageTextureFormats[i];
const value = storageTextureFormat.scopeId.value;
Debug.assert(value, `Value was not set when assigning storage texture slot [${storageTextureFormat.name}] to a bind group, while rendering [${DebugGraphics.toString()}]`, this);
this.setStorageTexture(storageTextureFormat.name, value);
}
for(let i = 0; i < storageBufferFormats.length; i++){
const storageBufferFormat = storageBufferFormats[i];
const value = storageBufferFormat.scopeId.value;
Debug.assert(value, `Value was not set when assigning storage buffer slot [${storageBufferFormat.name}] to a bind group, while rendering [${DebugGraphics.toString()}]`, this);
this.setStorageBuffer(storageBufferFormat.name, value);
}
// update uniform buffer offsets
this.uniformBufferOffsets.length = this.uniformBuffers.length;
for(let i = 0; i < this.uniformBuffers.length; i++){
const uniformBuffer = this.uniformBuffers[i];
// offset
this.uniformBufferOffsets[i] = uniformBuffer.offset;
// test if any of the uniform buffers have changed (not their content, but the buffer container itself)
if (this.renderVersionUpdated < uniformBuffer.renderVersionDirty) {
this.dirty = true;
}
}
if (this.dirty) {
this.dirty = false;
this.renderVersionUpdated = this.device.renderVersion;
this.impl.update(this);
}
}
}
export { BindGroup, DynamicBindGroup };