UNPKG

playcanvas

Version:

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

223 lines (221 loc) 6.71 kB
import { TRACEID_SHADER_COMPILE } from "../../../core/constants.js"; import { now } from "../../../core/time.js"; import { WebglShaderInput } from "./webgl-shader-input.js"; import { SHADERTAG_MATERIAL, semanticToLocation } from "../constants.js"; import { DeviceCache } from "../device-cache.js"; let _totalCompileTime = 0; const _vertexShaderBuiltins = /* @__PURE__ */ new Set([ "gl_VertexID", "gl_InstanceID", "gl_DrawID", "gl_BaseVertex", "gl_BaseInstance" ]); class CompiledShaderCache { // maps shader source to a compiled WebGL shader map = /* @__PURE__ */ new Map(); // destroy all created shaders when the device is destroyed destroy(device) { this.map.forEach((shader) => { device.gl.deleteShader(shader); }); } // just empty the cache when the context is lost loseContext(device) { this.map.clear(); } } const _vertexShaderCache = new DeviceCache(); const _fragmentShaderCache = new DeviceCache(); class WebglShader { compileDuration = 0; constructor(shader) { this.init(); this.compile(shader.device, shader); this.link(shader.device, shader); shader.device.shaders.push(shader); } destroy(shader) { if (this.glProgram) { shader.device.gl.deleteProgram(this.glProgram); this.glProgram = null; } } init() { this.uniforms = []; this.samplers = []; this.attributes = []; this.glProgram = null; this.glVertexShader = null; this.glFragmentShader = null; } loseContext() { this.init(); } restoreContext(device, shader) { this.compile(device, shader); this.link(device, shader); } compile(device, shader) { const definition = shader.definition; this.glVertexShader = this._compileShaderSource(device, definition.vshader, true); this.glFragmentShader = this._compileShaderSource(device, definition.fshader, false); } link(device, shader) { if (this.glProgram) { return; } const gl = device.gl; if (gl.isContextLost()) { return; } let startTime = 0; const glProgram = gl.createProgram(); this.glProgram = glProgram; gl.attachShader(glProgram, this.glVertexShader); gl.attachShader(glProgram, this.glFragmentShader); const definition = shader.definition; const attrs = definition.attributes; if (definition.useTransformFeedback) { let outNames = definition.feedbackVaryings; if (!outNames) { outNames = []; for (const attr in attrs) { if (attrs.hasOwnProperty(attr)) { outNames.push(`out_${attr}`); } } } gl.transformFeedbackVaryings(glProgram, outNames, gl.INTERLEAVED_ATTRIBS); } const locations = {}; for (const attr in attrs) { if (attrs.hasOwnProperty(attr)) { const semantic = attrs[attr]; const loc = semanticToLocation[semantic]; locations[loc] = attr; gl.bindAttribLocation(glProgram, loc, attr); } } gl.linkProgram(glProgram); } _compileShaderSource(device, src, isVertexShader) { const gl = device.gl; if (gl.isContextLost()) { return null; } const shaderDeviceCache = isVertexShader ? _vertexShaderCache : _fragmentShaderCache; const shaderCache = shaderDeviceCache.get(device, () => { return new CompiledShaderCache(); }); let glShader = shaderCache.map.get(src); if (!glShader) { glShader = gl.createShader(isVertexShader ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER); gl.shaderSource(glShader, src); gl.compileShader(glShader); shaderCache.map.set(src, glShader); } return glShader; } finalize(device, shader) { const gl = device.gl; if (gl.isContextLost()) { return true; } const glProgram = this.glProgram; const definition = shader.definition; let linkStartTime = 0; const linkStatus = gl.getProgramParameter(glProgram, gl.LINK_STATUS); if (!linkStatus) { if (!this._isCompiled(device, shader, this.glVertexShader, definition.vshader, "vertex")) { return false; } if (!this._isCompiled(device, shader, this.glFragmentShader, definition.fshader, "fragment")) { return false; } const message = `Failed to link shader program. Error: ${gl.getProgramInfoLog(glProgram)}`; console.error(message); return false; } const numAttributes = gl.getProgramParameter(glProgram, gl.ACTIVE_ATTRIBUTES); shader.attributes.clear(); for (let i = 0; i < numAttributes; i++) { const info = gl.getActiveAttrib(glProgram, i); const location = gl.getAttribLocation(glProgram, info.name); if (_vertexShaderBuiltins.has(info.name)) { continue; } if (definition.attributes[info.name] === void 0) { console.error(`Vertex shader attribute "${info.name}" is not mapped to a semantic in shader definition, shader [${shader.label}]`, shader); shader.failed = true; } else { shader.attributes.set(location, info.name); } } const samplerTypes = device._samplerTypes; const numUniforms = gl.getProgramParameter(glProgram, gl.ACTIVE_UNIFORMS); for (let i = 0; i < numUniforms; i++) { const info = gl.getActiveUniform(glProgram, i); const location = gl.getUniformLocation(glProgram, info.name); if (_vertexShaderBuiltins.has(info.name)) { continue; } const shaderInput = new WebglShaderInput(device, info.name, device.pcUniformType[info.type], location); if (samplerTypes.has(info.type)) { this.samplers.push(shaderInput); } else { this.uniforms.push(shaderInput); } } shader.ready = true; return true; } _isCompiled(device, shader, glShader, source, shaderType) { const gl = device.gl; if (!gl.getShaderParameter(glShader, gl.COMPILE_STATUS)) { const infoLog = gl.getShaderInfoLog(glShader); const [code, error] = this._processError(source, infoLog); const message = `Failed to compile ${shaderType} shader: ${infoLog} ${code} while rendering ${void 0}`; console.error(message); return false; } return true; } isLinked(device) { const { extParallelShaderCompile } = device; if (extParallelShaderCompile) { return device.gl.getProgramParameter(this.glProgram, extParallelShaderCompile.COMPLETION_STATUS_KHR); } return true; } _processError(src, infoLog) { const error = {}; let code = ""; if (src) { const lines = src.split("\n"); let from = 0; let to = lines.length; if (infoLog && infoLog.startsWith("ERROR:")) { const match = infoLog.match(/^ERROR:\s(\d+):(\d+):\s*(.+)/); if (match) { error.message = match[3]; error.line = parseInt(match[2], 10); from = Math.max(0, error.line - 6); to = Math.min(lines.length, error.line + 5); } } for (let i = from; i < to; i++) { const linePrefix = i + 1 === error.line ? "> " : " "; code += `${linePrefix}${i + 1}: ${lines[i]} `; } error.source = src; } return [code, error]; } } export { WebglShader };