UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

205 lines 12.3 kB
/* eslint-disable babylonjs/available */ /* eslint-disable jsdoc/require-jsdoc */ import { Logger } from "../../Misc/logger.js"; /** * Sampler hash codes are 19 bits long, so using a start value of 2^20 for buffer ids will ensure we can't have any collision with the sampler hash codes */ const bufferIdStart = 1 << 20; /** * textureIdStart is added to texture ids to ensure we can't have any collision with the buffer ids / sampler hash codes. * 2^35 for textureIdStart means we can have: * - 2^(35-20) = 2^15 = 32768 possible buffer ids * - 2^(53-35) = 2^18 = 524288 possible texture ids */ const textureIdStart = 2 ** 35; class WebGPUBindGroupCacheNode { constructor() { this.values = {}; } } /** @internal */ export class WebGPUCacheBindGroups { static get Statistics() { return { totalCreated: WebGPUCacheBindGroups.NumBindGroupsCreatedTotal, lastFrameCreated: WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame, lookupLastFrame: WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame, noLookupLastFrame: WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame, }; } static ResetCache() { WebGPUCacheBindGroups._Cache = new WebGPUBindGroupCacheNode(); WebGPUCacheBindGroups.NumBindGroupsCreatedTotal = 0; WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = 0; WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = 0; WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = 0; WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0; WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0; WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0; } constructor(device, cacheSampler, engine) { this.disabled = false; this._device = device; this._cacheSampler = cacheSampler; this._engine = engine; } endFrame() { WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame; WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame; WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame; WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0; WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0; WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0; } /** * Cache is currently based on the uniform/storage buffers, samplers and textures used by the binding groups. * Note that all uniform buffers have an offset of 0 in Babylon and we don't have a use case where we would have the same buffer used with different capacity values: * that means we don't need to factor in the offset/size of the buffer in the cache, only the id * @param webgpuPipelineContext * @param drawContext * @param materialContext * @returns a bind group array */ getBindGroups(webgpuPipelineContext, drawContext, materialContext) { let bindGroups = undefined; let node = WebGPUCacheBindGroups._Cache; const cacheIsDisabled = this.disabled || materialContext.forceBindGroupCreation; if (!cacheIsDisabled) { if (!drawContext.isDirty(materialContext.updateId) && !materialContext.isDirty) { WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame++; return drawContext.bindGroups; } for (const bufferName of webgpuPipelineContext.shaderProcessingContext.bufferNames) { const uboId = (drawContext.buffers[bufferName]?.uniqueId ?? 0) + bufferIdStart; let nextNode = node.values[uboId]; if (!nextNode) { nextNode = new WebGPUBindGroupCacheNode(); node.values[uboId] = nextNode; } node = nextNode; } for (const samplerName of webgpuPipelineContext.shaderProcessingContext.samplerNames) { const samplerHashCode = materialContext.samplers[samplerName]?.hashCode ?? 0; let nextNode = node.values[samplerHashCode]; if (!nextNode) { nextNode = new WebGPUBindGroupCacheNode(); node.values[samplerHashCode] = nextNode; } node = nextNode; } for (const textureName of webgpuPipelineContext.shaderProcessingContext.textureNames) { const textureId = (materialContext.textures[textureName]?.texture?.uniqueId ?? 0) + textureIdStart; let nextNode = node.values[textureId]; if (!nextNode) { nextNode = new WebGPUBindGroupCacheNode(); node.values[textureId] = nextNode; } node = nextNode; } bindGroups = node.bindGroups; } drawContext.resetIsDirty(materialContext.updateId); materialContext.isDirty = false; if (bindGroups) { drawContext.bindGroups = bindGroups; WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame++; return bindGroups; } bindGroups = []; drawContext.bindGroups = bindGroups; if (!cacheIsDisabled) { node.bindGroups = bindGroups; } WebGPUCacheBindGroups.NumBindGroupsCreatedTotal++; WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame++; const bindGroupLayouts = webgpuPipelineContext.bindGroupLayouts[materialContext.textureState]; for (let i = 0; i < webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries.length; i++) { const setDefinition = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries[i]; const entries = webgpuPipelineContext.shaderProcessingContext.bindGroupEntries[i]; for (let j = 0; j < setDefinition.length; j++) { const entry = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntries[i][j]; const entryInfo = webgpuPipelineContext.shaderProcessingContext.bindGroupLayoutEntryInfo[i][entry.binding]; const name = entryInfo.nameInArrayOfTexture ?? entryInfo.name; if (entry.sampler) { const bindingInfo = materialContext.samplers[name]; if (bindingInfo) { const sampler = bindingInfo.sampler; if (!sampler) { if (this._engine.dbgSanityChecks) { Logger.Error(`Trying to bind a null sampler! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50); } continue; } entries[j].resource = this._cacheSampler.getSampler(sampler, false, bindingInfo.hashCode, sampler.label); } else { Logger.Error(`Sampler "${name}" could not be bound. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => key === "texture" || key === "sampler" ? "<no dump>" : value)}`, 50); } } else if (entry.texture || entry.storageTexture) { const bindingInfo = materialContext.textures[name]; if (bindingInfo) { if (this._engine.dbgSanityChecks && bindingInfo.texture === null) { Logger.Error(`Trying to bind a null texture! name="${name}", entry=${JSON.stringify(entry)}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50); continue; } const hardwareTexture = bindingInfo.texture._hardwareTexture; if (this._engine.dbgSanityChecks && (!hardwareTexture || (entry.texture && !hardwareTexture.view) || (entry.storageTexture && !hardwareTexture.viewForWriting))) { Logger.Error(`Trying to bind a null gpu texture or view! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, isReady=${bindingInfo.texture?.isReady}, materialContext.uniqueId=${materialContext.uniqueId}`, 50); continue; } entries[j].resource = entry.storageTexture ? hardwareTexture.viewForWriting : hardwareTexture.view; } else { Logger.Error(`Texture "${name}" could not be bound. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => key === "texture" || key === "sampler" ? "<no dump>" : value)}`, 50); } } else if (entry.externalTexture) { const bindingInfo = materialContext.textures[name]; if (bindingInfo) { if (this._engine.dbgSanityChecks && bindingInfo.texture === null) { Logger.Error(`Trying to bind a null external texture! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, materialContext.uniqueId=${materialContext.uniqueId}`, 50); continue; } const externalTexture = bindingInfo.texture.underlyingResource; if (this._engine.dbgSanityChecks && !externalTexture) { Logger.Error(`Trying to bind a null gpu external texture! entry=${JSON.stringify(entry)}, name=${name}, bindingInfo=${JSON.stringify(bindingInfo, (key, value) => (key === "texture" ? "<no dump>" : value))}, isReady=${bindingInfo.texture?.isReady}, materialContext.uniqueId=${materialContext.uniqueId}`, 50); continue; } entries[j].resource = this._device.importExternalTexture({ source: externalTexture }); } else { Logger.Error(`Texture "${name}" could not be bound. entry=${JSON.stringify(entry)}, materialContext=${JSON.stringify(materialContext, (key, value) => key === "texture" || key === "sampler" ? "<no dump>" : value)}`, 50); } } else if (entry.buffer) { const dataBuffer = drawContext.buffers[name]; if (dataBuffer) { const webgpuBuffer = dataBuffer.underlyingResource; entries[j].resource.buffer = webgpuBuffer; entries[j].resource.size = dataBuffer.capacity; } else { Logger.Error(`Can't find buffer "${name}". entry=${JSON.stringify(entry)}, buffers=${JSON.stringify(drawContext.buffers)}, drawContext.uniqueId=${drawContext.uniqueId}`, 50); } } } const groupLayout = bindGroupLayouts[i]; bindGroups[i] = this._device.createBindGroup({ layout: groupLayout, entries, }); } return bindGroups; } } WebGPUCacheBindGroups.NumBindGroupsCreatedTotal = 0; WebGPUCacheBindGroups.NumBindGroupsCreatedLastFrame = 0; WebGPUCacheBindGroups.NumBindGroupsLookupLastFrame = 0; WebGPUCacheBindGroups.NumBindGroupsNoLookupLastFrame = 0; WebGPUCacheBindGroups._Cache = new WebGPUBindGroupCacheNode(); WebGPUCacheBindGroups._NumBindGroupsCreatedCurrentFrame = 0; WebGPUCacheBindGroups._NumBindGroupsLookupCurrentFrame = 0; WebGPUCacheBindGroups._NumBindGroupsNoLookupCurrentFrame = 0; //# sourceMappingURL=webgpuCacheBindGroups.js.map