123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- import { __decorate } from "../tslib.es6.js";
- import { serialize } from "../Misc/decorators.js";
- import { SerializationHelper } from "../Misc/decorators.serialization.js";
- import { RegisterClass } from "../Misc/typeStore.js";
- import { ComputeBindingType } from "../Engines/Extensions/engine.computeShader.js";
- import { Texture } from "../Materials/Textures/texture.js";
- import { UniqueIdGenerator } from "../Misc/uniqueIdGenerator.js";
- import { Logger } from "../Misc/logger.js";
- import { TextureSampler } from "../Materials/Textures/textureSampler.js";
- import { WebGPUPerfCounter } from "../Engines/WebGPU/webgpuPerfCounter.js";
- /**
- * The ComputeShader object lets you execute a compute shader on your GPU (if supported by the engine)
- */
- export class ComputeShader {
- /**
- * The options used to create the shader
- */
- get options() {
- return this._options;
- }
- /**
- * The shaderPath used to create the shader
- */
- get shaderPath() {
- return this._shaderPath;
- }
- /**
- * Instantiates a new compute shader.
- * @param name Defines the name of the compute shader in the scene
- * @param engine Defines the engine the compute shader belongs to
- * @param shaderPath Defines the route to the shader code in one of three ways:
- * * object: \{ compute: "custom" \}, used with ShaderStore.ShadersStoreWGSL["customComputeShader"]
- * * object: \{ computeElement: "HTMLElementId" \}, used with shader code in script tags
- * * object: \{ computeSource: "compute shader code string" \}, where the string contains the shader code
- * * string: try first to find the code in ShaderStore.ShadersStoreWGSL[shaderPath + "ComputeShader"]. If not, assumes it is a file with name shaderPath.compute.fx in index.html folder.
- * @param options Define the options used to create the shader
- */
- constructor(name, engine, shaderPath, options = {}) {
- this._bindings = {};
- this._samplers = {};
- this._contextIsDirty = false;
- /**
- * When set to true, dispatch won't call isReady anymore and won't check if the underlying GPU resources should be (re)created because of a change in the inputs (texture, uniform buffer, etc.)
- * If you know that your inputs did not change since last time dispatch was called and that isReady() returns true, set this flag to true to improve performance
- */
- this.fastMode = false;
- /**
- * Callback triggered when the shader is compiled
- */
- this.onCompiled = null;
- /**
- * Callback triggered when an error occurs
- */
- this.onError = null;
- this.name = name;
- this._engine = engine;
- this.uniqueId = UniqueIdGenerator.UniqueId;
- if (engine.enableGPUTimingMeasurements) {
- this.gpuTimeInFrame = new WebGPUPerfCounter();
- }
- if (!this._engine.getCaps().supportComputeShaders) {
- Logger.Error("This engine does not support compute shaders!");
- return;
- }
- if (!options.bindingsMapping) {
- Logger.Error("You must provide the binding mappings as browsers don't support reflection for wgsl shaders yet!");
- return;
- }
- this._context = engine.createComputeContext();
- this._shaderPath = shaderPath;
- this._options = {
- bindingsMapping: {},
- defines: [],
- ...options,
- };
- }
- /**
- * Gets the current class name of the material e.g. "ComputeShader"
- * Mainly use in serialization.
- * @returns the class name
- */
- getClassName() {
- return "ComputeShader";
- }
- /**
- * Binds a texture to the shader
- * @param name Binding name of the texture
- * @param texture Texture to bind
- * @param bindSampler Bind the sampler corresponding to the texture (default: true). The sampler will be bound just before the binding index of the texture
- */
- setTexture(name, texture, bindSampler = true) {
- const current = this._bindings[name];
- this._bindings[name] = {
- type: bindSampler ? ComputeBindingType.Texture : ComputeBindingType.TextureWithoutSampler,
- object: texture,
- indexInGroupEntries: current?.indexInGroupEntries,
- };
- this._contextIsDirty || (this._contextIsDirty = !current || current.object !== texture || current.type !== this._bindings[name].type);
- }
- /**
- * Binds a storage texture to the shader
- * @param name Binding name of the texture
- * @param texture Texture to bind
- */
- setStorageTexture(name, texture) {
- const current = this._bindings[name];
- this._contextIsDirty || (this._contextIsDirty = !current || current.object !== texture);
- this._bindings[name] = {
- type: ComputeBindingType.StorageTexture,
- object: texture,
- indexInGroupEntries: current?.indexInGroupEntries,
- };
- }
- /**
- * Binds an external texture to the shader
- * @param name Binding name of the texture
- * @param texture Texture to bind
- */
- setExternalTexture(name, texture) {
- const current = this._bindings[name];
- this._contextIsDirty || (this._contextIsDirty = !current || current.object !== texture);
- this._bindings[name] = {
- type: ComputeBindingType.ExternalTexture,
- object: texture,
- indexInGroupEntries: current?.indexInGroupEntries,
- };
- }
- /**
- * Binds a video texture to the shader (by binding the external texture attached to this video)
- * @param name Binding name of the texture
- * @param texture Texture to bind
- * @returns true if the video texture was successfully bound, else false. false will be returned if the current engine does not support external textures
- */
- setVideoTexture(name, texture) {
- if (texture.externalTexture) {
- this.setExternalTexture(name, texture.externalTexture);
- return true;
- }
- return false;
- }
- /**
- * Binds a uniform buffer to the shader
- * @param name Binding name of the buffer
- * @param buffer Buffer to bind
- */
- setUniformBuffer(name, buffer) {
- const current = this._bindings[name];
- this._contextIsDirty || (this._contextIsDirty = !current || current.object !== buffer);
- this._bindings[name] = {
- type: ComputeShader._BufferIsDataBuffer(buffer) ? ComputeBindingType.DataBuffer : ComputeBindingType.UniformBuffer,
- object: buffer,
- indexInGroupEntries: current?.indexInGroupEntries,
- };
- }
- /**
- * Binds a storage buffer to the shader
- * @param name Binding name of the buffer
- * @param buffer Buffer to bind
- */
- setStorageBuffer(name, buffer) {
- const current = this._bindings[name];
- this._contextIsDirty || (this._contextIsDirty = !current || current.object !== buffer);
- this._bindings[name] = {
- type: ComputeShader._BufferIsDataBuffer(buffer) ? ComputeBindingType.DataBuffer : ComputeBindingType.StorageBuffer,
- object: buffer,
- indexInGroupEntries: current?.indexInGroupEntries,
- };
- }
- /**
- * Binds a texture sampler to the shader
- * @param name Binding name of the sampler
- * @param sampler Sampler to bind
- */
- setTextureSampler(name, sampler) {
- const current = this._bindings[name];
- this._contextIsDirty || (this._contextIsDirty = !current || !sampler.compareSampler(current.object));
- this._bindings[name] = {
- type: ComputeBindingType.Sampler,
- object: sampler,
- indexInGroupEntries: current?.indexInGroupEntries,
- };
- }
- /**
- * Specifies that the compute shader is ready to be executed (the compute effect and all the resources are ready)
- * @returns true if the compute shader is ready to be executed
- */
- isReady() {
- let effect = this._effect;
- for (const key in this._bindings) {
- const binding = this._bindings[key], type = binding.type, object = binding.object;
- switch (type) {
- case ComputeBindingType.Texture:
- case ComputeBindingType.TextureWithoutSampler:
- case ComputeBindingType.StorageTexture: {
- const texture = object;
- if (!texture.isReady()) {
- return false;
- }
- break;
- }
- case ComputeBindingType.ExternalTexture: {
- const texture = object;
- if (!texture.isReady()) {
- return false;
- }
- break;
- }
- }
- }
- const defines = [];
- const shaderName = this._shaderPath;
- if (this._options.defines) {
- for (let index = 0; index < this._options.defines.length; index++) {
- defines.push(this._options.defines[index]);
- }
- }
- const join = defines.join("\n");
- if (this._cachedDefines !== join) {
- this._cachedDefines = join;
- effect = this._engine.createComputeEffect(shaderName, {
- defines: join,
- entryPoint: this._options.entryPoint,
- onCompiled: this.onCompiled,
- onError: this.onError,
- });
- this._effect = effect;
- }
- if (!effect.isReady()) {
- return false;
- }
- return true;
- }
- /**
- * Dispatches (executes) the compute shader
- * @param x Number of workgroups to execute on the X dimension
- * @param y Number of workgroups to execute on the Y dimension (default: 1)
- * @param z Number of workgroups to execute on the Z dimension (default: 1)
- * @returns True if the dispatch could be done, else false (meaning either the compute effect or at least one of the bound resources was not ready)
- */
- dispatch(x, y, z) {
- if (!this.fastMode && !this._checkContext()) {
- return false;
- }
- this._engine.computeDispatch(this._effect, this._context, this._bindings, x, y, z, this._options.bindingsMapping, this.gpuTimeInFrame);
- return true;
- }
- /**
- * Dispatches (executes) the compute shader.
- * @param buffer Buffer containing the number of workgroups to execute on the X, Y and Z dimensions
- * @param offset Offset in the buffer where the workgroup counts are stored (default: 0)
- * @returns True if the dispatch could be done, else false (meaning either the compute effect or at least one of the bound resources was not ready)
- */
- dispatchIndirect(buffer, offset = 0) {
- if (!this.fastMode && !this._checkContext()) {
- return false;
- }
- const dataBuffer = ComputeShader._BufferIsDataBuffer(buffer) ? buffer : buffer.getBuffer();
- this._engine.computeDispatchIndirect(this._effect, this._context, this._bindings, dataBuffer, offset, this._options.bindingsMapping, this.gpuTimeInFrame);
- return true;
- }
- _checkContext() {
- if (!this.isReady()) {
- return false;
- }
- // If the sampling parameters of a texture bound to the shader have changed, we must clear the compute context so that it is recreated with the updated values
- // Also, if the actual (gpu) buffer used by a uniform buffer has changed, we must clear the compute context so that it is recreated with the updated value
- for (const key in this._bindings) {
- const binding = this._bindings[key];
- if (!this._options.bindingsMapping[key]) {
- throw new Error("ComputeShader ('" + this.name + "'): No binding mapping has been provided for the property '" + key + "'");
- }
- switch (binding.type) {
- case ComputeBindingType.Texture: {
- const sampler = this._samplers[key];
- const texture = binding.object;
- if (!sampler || !texture._texture || !sampler.compareSampler(texture._texture)) {
- this._samplers[key] = new TextureSampler().setParameters(texture.wrapU, texture.wrapV, texture.wrapR, texture.anisotropicFilteringLevel, texture._texture.samplingMode, texture._texture?._comparisonFunction);
- this._contextIsDirty = true;
- }
- break;
- }
- case ComputeBindingType.ExternalTexture: {
- // we must recreate the bind groups each time if there's an external texture, because device.importExternalTexture must be called each frame
- this._contextIsDirty = true;
- break;
- }
- case ComputeBindingType.UniformBuffer: {
- const ubo = binding.object;
- if (ubo.getBuffer() !== binding.buffer) {
- binding.buffer = ubo.getBuffer();
- this._contextIsDirty = true;
- }
- break;
- }
- }
- }
- if (this._contextIsDirty) {
- this._contextIsDirty = false;
- this._context.clear();
- }
- return true;
- }
- /**
- * Waits for the compute shader to be ready and executes it
- * @param x Number of workgroups to execute on the X dimension
- * @param y Number of workgroups to execute on the Y dimension (default: 1)
- * @param z Number of workgroups to execute on the Z dimension (default: 1)
- * @param delay Delay between the retries while the shader is not ready (in milliseconds - 10 by default)
- * @returns A promise that is resolved once the shader has been sent to the GPU. Note that it does not mean that the shader execution itself is finished!
- */
- dispatchWhenReady(x, y, z, delay = 10) {
- return new Promise((resolve) => {
- const check = () => {
- if (!this.dispatch(x, y, z)) {
- setTimeout(check, delay);
- }
- else {
- resolve();
- }
- };
- check();
- });
- }
- /**
- * Serializes this compute shader in a JSON representation
- * @returns the serialized compute shader object
- */
- serialize() {
- const serializationObject = SerializationHelper.Serialize(this);
- serializationObject.options = this._options;
- serializationObject.shaderPath = this._shaderPath;
- serializationObject.bindings = {};
- serializationObject.textures = {};
- for (const key in this._bindings) {
- const binding = this._bindings[key];
- const object = binding.object;
- switch (binding.type) {
- case ComputeBindingType.Texture:
- case ComputeBindingType.TextureWithoutSampler:
- case ComputeBindingType.StorageTexture: {
- const serializedData = object.serialize();
- if (serializedData) {
- serializationObject.textures[key] = serializedData;
- serializationObject.bindings[key] = {
- type: binding.type,
- };
- }
- break;
- }
- case ComputeBindingType.UniformBuffer: {
- break;
- }
- }
- }
- return serializationObject;
- }
- /**
- * Creates a compute shader from parsed compute shader data
- * @param source defines the JSON representation of the compute shader
- * @param scene defines the hosting scene
- * @param rootUrl defines the root URL to use to load textures and relative dependencies
- * @returns a new compute shader
- */
- static Parse(source, scene, rootUrl) {
- const compute = SerializationHelper.Parse(() => new ComputeShader(source.name, scene.getEngine(), source.shaderPath, source.options), source, scene, rootUrl);
- for (const key in source.textures) {
- const binding = source.bindings[key];
- const texture = Texture.Parse(source.textures[key], scene, rootUrl);
- if (binding.type === ComputeBindingType.Texture) {
- compute.setTexture(key, texture);
- }
- else if (binding.type === ComputeBindingType.TextureWithoutSampler) {
- compute.setTexture(key, texture, false);
- }
- else {
- compute.setStorageTexture(key, texture);
- }
- }
- return compute;
- }
- static _BufferIsDataBuffer(buffer) {
- return buffer.underlyingResource !== undefined;
- }
- }
- __decorate([
- serialize()
- ], ComputeShader.prototype, "name", void 0);
- __decorate([
- serialize()
- ], ComputeShader.prototype, "fastMode", void 0);
- RegisterClass("BABYLON.ComputeShader", ComputeShader);
- //# sourceMappingURL=computeShader.js.map
|