UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

208 lines (207 loc) 6.95 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug, DebugHelper } from "../../../core/debug.js"; import { StringIds } from "../../../core/string-ids.js"; import { SHADERLANGUAGE_WGSL } from "../constants.js"; import { DebugGraphics } from "../debug-graphics.js"; import { ShaderProcessorGLSL } from "../shader-processor-glsl.js"; import { WebgpuDebug } from "./webgpu-debug.js"; import { WebgpuShaderProcessorWGSL } from "./webgpu-shader-processor-wgsl.js"; const computeShaderIds = new StringIds(); class WebgpuShader { /** * @param {Shader} shader - The shader. */ constructor(shader) { /** * Transpiled vertex shader code. * * @type {string|null} */ __publicField(this, "_vertexCode", null); /** * Transpiled fragment shader code. * * @type {string|null} */ __publicField(this, "_fragmentCode", null); /** * Compute shader code. * * @type {string|null} */ __publicField(this, "_computeCode", null); /** * Cached content-based key for compute shader. * * @type {number|undefined} * @private */ __publicField(this, "_computeKey"); /** * Name of the vertex entry point function. */ __publicField(this, "vertexEntryPoint", "main"); /** * Name of the fragment entry point function. */ __publicField(this, "fragmentEntryPoint", "main"); /** * Name of the compute entry point function. */ __publicField(this, "computeEntryPoint", "main"); this.shader = shader; const definition = shader.definition; Debug.assert(definition); if (definition.shaderLanguage === SHADERLANGUAGE_WGSL) { if (definition.cshader) { this._computeCode = definition.cshader ?? null; this.computeUniformBufferFormats = definition.computeUniformBufferFormats; this.computeBindGroupFormat = definition.computeBindGroupFormat; if (definition.computeEntryPoint) { this.computeEntryPoint = definition.computeEntryPoint; } } else { this.vertexEntryPoint = "vertexMain"; this.fragmentEntryPoint = "fragmentMain"; if (definition.processingOptions) { this.processWGSL(); } else { this._vertexCode = definition.vshader ?? null; this._fragmentCode = definition.fshader ?? null; shader.meshUniformBufferFormat = definition.meshUniformBufferFormat; shader.meshBindGroupFormat = definition.meshBindGroupFormat; } } shader.ready = true; } else { if (definition.processingOptions) { this.processGLSL(); } } } /** * Free the WebGPU resources associated with a shader. * * @param {Shader} shader - The shader to free. */ destroy(shader) { this._vertexCode = null; this._fragmentCode = null; } createShaderModule(code, shaderType) { const device = this.shader.device; const wgpu = device.wgpu; WebgpuDebug.validate(device); const shaderModule = wgpu.createShaderModule({ code }); DebugHelper.setLabel(shaderModule, `${shaderType}:${this.shader.label}`); WebgpuDebug.endShader(device, shaderModule, code, 6, { shaderType, source: code, shader: this.shader }); return shaderModule; } getVertexShaderModule() { return this.createShaderModule(this._vertexCode, "Vertex"); } getFragmentShaderModule() { return this.createShaderModule(this._fragmentCode, "Fragment"); } getComputeShaderModule() { return this.createShaderModule(this._computeCode, "Compute"); } processGLSL() { const shader = this.shader; const processed = ShaderProcessorGLSL.run(shader.device, shader.definition, shader); Debug.call(() => { this.processed = processed; }); this._vertexCode = this.transpile(processed.vshader, "vertex", shader.definition.vshader); this._fragmentCode = this.transpile(processed.fshader, "fragment", shader.definition.fshader); if (!(this._vertexCode && this._fragmentCode)) { shader.failed = true; } else { shader.ready = true; } shader.meshUniformBufferFormat = processed.meshUniformBufferFormat; shader.meshBindGroupFormat = processed.meshBindGroupFormat; shader.attributes = processed.attributes; } processWGSL() { const shader = this.shader; const processed = WebgpuShaderProcessorWGSL.run(shader.device, shader.definition, shader); Debug.call(() => { this.processed = processed; }); this._vertexCode = processed.vshader; this._fragmentCode = processed.fshader; shader.meshUniformBufferFormat = processed.meshUniformBufferFormat; shader.meshBindGroupFormat = processed.meshBindGroupFormat; shader.attributes = processed.attributes; } transpile(src, shaderType, originalSrc) { const device = this.shader.device; if (!device.glslang || !device.twgsl) { console.error(`Cannot transpile shader [${this.shader.label}] - shader transpilers (glslang/twgsl) are not available. Make sure to provide glslangUrl and twgslUrl when creating the device.`, { shader: this.shader }); return null; } try { const spirv = device.glslang.compileGLSL(src, shaderType); const wgsl = device.twgsl.convertSpirV2WGSL(spirv); return wgsl; } catch (err) { console.error(`Failed to transpile webgl ${shaderType} shader [${this.shader.label}] to WebGPU while rendering ${DebugGraphics.toString()}, error: [${err.stack}]`, { processed: src, original: originalSrc, shader: this.shader, error: err, stack: err.stack }); } } get vertexCode() { Debug.assert(this._vertexCode); return this._vertexCode; } get fragmentCode() { Debug.assert(this._fragmentCode); return this._fragmentCode; } /** * Content-based key for compute shader caching. Returns the same key for identical * shader code and entry point combinations, regardless of how many Shader instances exist. * * @type {number} * @ignore */ get computeKey() { if (this._computeKey === void 0) { const keyString = `${this._computeCode}|${this.computeEntryPoint}`; this._computeKey = computeShaderIds.get(keyString); } return this._computeKey; } /** * Dispose the shader when the context has been lost. */ loseContext() { } /** * Restore shader after the context has been obtained. * * @param {GraphicsDevice} device - The graphics device. * @param {Shader} shader - The shader to restore. */ restoreContext(device, shader) { } } export { WebgpuShader };