UNPKG

@itwin/core-frontend

Version:
618 lines 27.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module WebGL */ import { assert } from "@itwin/core-bentley"; import { DebugShaderFile } from "../RenderSystemDebugControl"; import { ShaderProgramParams } from "./DrawCommand"; import { GL } from "./GL"; import { UniformHandle } from "./UniformHandle"; import { System } from "./System"; import { TechniqueFlags } from "./TechniqueFlags"; /** Describes the location of a uniform variable within a shader program. * @internal */ export class Uniform { _name; _handle; constructor(name) { this._name = name; } compile(prog) { assert(!this.isValid); if (undefined !== prog.glProgram) { this._handle = UniformHandle.create(prog, this._name); } return this.isValid; } get isValid() { return undefined !== this._handle; } } /** * Describes the location of a uniform variable within a shader program, the value of which does not change while the program is active. * The supplied binding function will be invoked once each time the shader becomes active to set the value of the uniform. * @internal */ export class ProgramUniform extends Uniform { _bind; constructor(name, bind) { super(name); this._bind = bind; } bind(params) { if (undefined !== this._handle) { this._bind(this._handle, params); } } } /** * Describes the location of a uniform variable within a shader program, the value of which is dependent upon the graphic primitive * currently being rendered by the program. The supplied binding function will be invoked once for each graphic primitive submitted * to the program to set the value of the uniform. * @internal */ export class GraphicUniform extends Uniform { _bind; constructor(name, bind) { super(name); this._bind = bind; } bind(params) { if (undefined !== this._handle) { this._bind(this._handle, params); } } } /** @internal */ export class ShaderProgram { vertSource; fragSource; _glProgram; _inUse = false; _status = 2 /* CompileStatus.Uncompiled */; _programUniforms = new Array(); _graphicUniforms = new Array(); _attrMap; // for debugging purposes... description; _fragDescription; _vertGNdx = -1; _fragGNdx = -1; _vertHNdx = -1; _fragHNdx = -1; outputsToPick; constructor(gl, vertSource, fragSource, attrMap, description, fragDescription) { this.description = description; this.outputsToPick = description.includes("Overrides") || description.includes("Pick"); this._fragDescription = fragDescription; this.vertSource = vertSource; this.fragSource = fragSource; this._attrMap = attrMap; const glProgram = gl.createProgram(); this._glProgram = (null === glProgram) ? undefined : glProgram; } get isDisposed() { return this._glProgram === undefined; } [Symbol.dispose]() { if (!this.isDisposed) { assert(!this._inUse); System.instance.context.deleteProgram(this._glProgram); this._glProgram = undefined; this._status = 2 /* CompileStatus.Uncompiled */; } } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */ dispose() { this[Symbol.dispose](); } get glProgram() { return this._glProgram; } get isUncompiled() { return 2 /* CompileStatus.Uncompiled */ === this._status; } get isCompiled() { return 0 /* CompileStatus.Success */ === this._status; } compileShader(type) { const gl = System.instance.context; const shader = gl.createShader(type); if (null === shader) return undefined; const src = GL.ShaderType.Vertex === type ? this.vertSource : this.fragSource; gl.shaderSource(shader, src); gl.compileShader(shader); const succeeded = gl.getShaderParameter(shader, GL.ShaderParameter.CompileStatus); if (!succeeded) { const compileLog = `${GL.ShaderType.Vertex === type ? "Vertex" : "Fragment"} shader failed to compile. Errors: ${gl.getShaderInfoLog(shader)} Program description: ${this.description}`; throw new Error(compileLog); } if (System.instance.options.debugShaders) { const isVS = GL.ShaderType.Vertex === type; const desc = isVS ? this.description : this._fragDescription; this.saveShaderCode(isVS, desc, src, shader); } return shader; } linkProgram(vert, frag) { assert(undefined !== this.glProgram); if (undefined === this._glProgram || null === this._glProgram) // because WebGL APIs used Thing|null, not Thing|undefined... return false; const gl = System.instance.context; gl.attachShader(this._glProgram, vert); gl.attachShader(this._glProgram, frag); // bind attribute locations before final linking if (this._attrMap !== undefined) { this._attrMap.forEach((attr, key) => { gl.bindAttribLocation(this._glProgram, attr.location, key); }); } gl.linkProgram(this._glProgram); const linkLog = gl.getProgramInfoLog(this._glProgram); gl.validateProgram(this._glProgram); const succeeded = gl.getProgramParameter(this._glProgram, GL.ProgramParameter.LinkStatus); if (!succeeded) { const validateLog = gl.getProgramInfoLog(this._glProgram); const msg = `Shader program failed to link. Link errors: ${linkLog} Validation errors: ${validateLog} Program description: ${this.description}`; throw new Error(msg); } // Debug output const debugUniforms = false; if (debugUniforms) { const dbgLog = (x) => console.log(x); // eslint-disable-line no-console const outSrc = false; // true for source out, false for just varying info if (this._fragDescription) { let tStr = ""; if (!outSrc) { tStr = `${this._fragDescription}`; } const numUniforms = gl.getProgramParameter(this._glProgram, GL.ProgramParameter.ActiveUniforms); const tStr2 = `Active Uniforms: ${numUniforms}`; if (tStr2) { if (outSrc) { dbgLog("//==============================================================================================================="); dbgLog(this.vertSource); dbgLog("//========= Num Active Uniforms ========="); dbgLog(`${tStr2}\n`); dbgLog(this.fragSource); } else { dbgLog(`\n${tStr2} ${tStr}\n`); } } } } return true; } compile(forUse = false) { if (System.instance.options.debugShaders && forUse && this._status === 0 /* CompileStatus.Success */) this.setDebugShaderUsage(); switch (this._status) { case 1 /* CompileStatus.Failure */: return 1 /* CompileStatus.Failure */; case 0 /* CompileStatus.Success */: return 0 /* CompileStatus.Success */; default: { if (this.isDisposed) { this._status = 1 /* CompileStatus.Failure */; return 1 /* CompileStatus.Failure */; } break; } } this._status = 1 /* CompileStatus.Failure */; const vert = this.compileShader(GL.ShaderType.Vertex); const frag = this.compileShader(GL.ShaderType.Fragment); if (undefined !== vert && undefined !== frag) if (this.linkProgram(vert, frag) && this.compileUniforms(this._programUniforms) && this.compileUniforms(this._graphicUniforms)) this._status = 0 /* CompileStatus.Success */; if (System.instance.options.debugShaders && forUse && this._status === 0 /* CompileStatus.Success */) this.setDebugShaderUsage(); if (true !== System.instance.options.preserveShaderSourceCode) this.vertSource = this.fragSource = ""; return this._status; } use(params) { if (this.compile(true) !== 0 /* CompileStatus.Success */) return false; assert(undefined !== this._glProgram); if (null === this._glProgram || undefined === this._glProgram) return false; assert(!this._inUse); this._inUse = true; params.context.useProgram(this._glProgram); for (const uniform of this._programUniforms) uniform.bind(params); return true; } endUse() { this._inUse = false; System.instance.context.useProgram(null); } draw(params) { assert(this._inUse); for (const uniform of this._graphicUniforms) uniform.bind(params); params.geometry.draw(); } addProgramUniform(name, binding) { assert(this.isUncompiled); this._programUniforms.push(new ProgramUniform(name, binding)); } addGraphicUniform(name, binding) { assert(this.isUncompiled); this._graphicUniforms.push(new GraphicUniform(name, binding)); } compileUniforms(uniforms) { for (const uniform of uniforms) { if (!uniform.compile(this)) return false; } return true; } setDebugShaderUsage() { if (!System.instance.options.debugShaders) return; const shaderFiles = System.instance.debugShaderFiles; if (this._vertGNdx >= 0) shaderFiles[this._vertGNdx].isUsed = true; if (this._fragGNdx >= 0) shaderFiles[this._fragGNdx].isUsed = true; if (this._vertHNdx >= 0) shaderFiles[this._vertHNdx].isUsed = true; if (this._fragHNdx >= 0) shaderFiles[this._fragHNdx].isUsed = true; } saveShaderCode(isVS, desc, src, shader) { // save glsl and hlsl (from Angle and fixed up) in DebugShaderFile if (!System.instance.options.debugShaders) return; const shaderFiles = System.instance.debugShaderFiles; let sname; if (desc) { sname = desc.split(isVS ? "//!V! " : "//!F! ").join(""); sname = sname.split(": ").join("-"); sname = sname.split("; ").join("-"); } else { // need to investigate shaders with no comments to derive names, for now come up with unique name sname = `noname-${shaderFiles.length}`; } sname += isVS ? "_VS" : "_FS"; const fname = `${sname}.glsl`; let dsfNdx = shaderFiles.push(new DebugShaderFile(fname, src, isVS, true, false)); if (isVS) this._vertGNdx = dsfNdx - 1; else this._fragGNdx = dsfNdx - 1; const ext2 = System.instance.context.getExtension("WEBGL_debug_shaders"); if (!ext2) return; const srcH = ext2.getTranslatedShaderSource(shader); if (!srcH) return; // TODO: implement WebGL2 specific inputs for gl_VertexID and gl_InstanceID if ever used // parse and edit srcH to make it compilable const fnameH = `${sname}.hlsl`; let numTargets = 0; // for gl_Color cases let haveGLpos = false; let haveGLpntsz = false; let haveGLDepth = false; let haveGLFrontFacing = false; let haveGLPointCoord = false; let haveGLFragCoord = false; let haveGLFragColorOnly = false; // for only 1 output const haveGLFragColor = [false, false, false, false, false, false, false, false]; const attrs = new Array(); const varyings = new Array(); const lines = srcH.split("\n"); let toss = true; for (let ndx = 0; ndx < lines.length;) { let line = lines[ndx]; if (line.indexOf("// INITIAL HLSL END") >= 0) toss = true; if (toss) lines.splice(ndx, 1); if (line.indexOf("// INITIAL HLSL BEGIN") >= 0) { toss = false; } else if (!toss) { // look for lines that need editing if (line.indexOf("Varyings") >= 0) { // save off varyings in either case while (ndx + 1 < lines.length && lines[ndx + 1].indexOf("static") >= 0) { ++ndx; line = lines[ndx].substring(6).trimStart(); varyings.push(line.substring(0, line.indexOf("="))); } } if (isVS) { if (line.indexOf("Attributes") >= 0) { // save off attributes while (ndx + 1 < lines.length && lines[ndx + 1].indexOf("static") >= 0) { ++ndx; line = lines[ndx].substring(6).trimStart(); attrs.push(line.substring(0, line.indexOf("="))); } } else if (line.indexOf("static float4 gl_Position") >= 0) { haveGLpos = true; } else if (line.indexOf("static float gl_PointSize") >= 0) { haveGLpntsz = true; } else if (line.indexOf("@@ VERTEX ATTRIBUTES @@") >= 0) { lines[ndx] = "// @@ VERTEX ATTRIBUTES @@"; } else if (line.indexOf("@@ MAIN PROLOGUE @@") >= 0) { lines[ndx] = "// @@ MAIN PROLOGUE @@\ngetInput(input);"; } else if (line.indexOf("@@ VERTEX OUTPUT @@") >= 0) { // have to create a VS_OUTPUT struct and a generateOutput function from varyings lines[ndx] = "// @@ VERTEX OUTPUT @@\nstruct VS_INPUT\n {"; let aNdx = 0; for (const tstr of attrs) { ++ndx; lines.splice(ndx, 0, ` ${tstr}: TEXCOORD${aNdx};`); ++aNdx; } ++ndx; lines.splice(ndx, 0, " };\nvoid getInput(VS_INPUT input) {"); for (const tstr of attrs) { let t = tstr.indexOf("_a"); let vName = tstr.substring(t); t = vName.indexOf(" "); vName = vName.substring(0, t); ++ndx; lines.splice(ndx, 0, ` ${vName} = input.${vName};`); } ++ndx; lines.splice(ndx, 0, "}\nstruct VS_OUTPUT\n {"); if (haveGLpos) { ++ndx; lines.splice(ndx, 0, " float4 _v_position : SV_Position;"); } if (haveGLpntsz) { ++ndx; lines.splice(ndx, 0, " float gl_PointSize : PointSize;"); } let vNdx = 0; for (const tstr of varyings) { ++ndx; lines.splice(ndx, 0, ` ${tstr}: TEXCOORD${vNdx};`); ++vNdx; } ++ndx; lines.splice(ndx, 0, " };\nVS_OUTPUT generateOutput(VS_INPUT input) {\n VS_OUTPUT output;"); if (haveGLpos) { ++ndx; lines.splice(ndx, 0, " output._v_position = gl_Position;"); } if (haveGLpntsz) { ++ndx; lines.splice(ndx, 0, " output.gl_PointSize = gl_PointSize;"); } for (const tstr of varyings) { let t = tstr.indexOf("_v"); let vName = tstr.substring(t); t = vName.indexOf(" "); vName = vName.substring(0, t); ++ndx; lines.splice(ndx, 0, ` output.${vName} = ${vName};`); } ++ndx; lines.splice(ndx, 0, " return output;\n}"); } } else { // fragment shader let tNdx = 0; if (line.indexOf("static float4 gl_Color[") >= 0) { // } else if (line.indexOf("gl_Color[0] =") >= 0) { if (numTargets < 1) numTargets = 1; } else if (line.indexOf("gl_Color[1] =") >= 0) { if (numTargets < 2) numTargets = 2; } else if (line.indexOf("gl_Color[2] =") >= 0) { if (numTargets < 3) numTargets = 3; } else if (line.indexOf("gl_Color[3] =") >= 0) { numTargets = 4; } else if (line.indexOf("gl_Depth") >= 0) { haveGLDepth = true; } else if (line.indexOf("gl_FrontFacing") >= 0) { haveGLFrontFacing = true; } else if (line.indexOf("gl_PointCoord") >= 0) { haveGLPointCoord = true; } else if (line.indexOf("gl_FragCoord") >= 0) { haveGLFragCoord = true; } else if ((tNdx = line.indexOf("out_FragColor")) >= 0) { const c = line.substring(tNdx + 13, tNdx + 13 + 1); if (c === " " || c === "=") haveGLFragColorOnly = true; else { tNdx = +c; haveGLFragColor[tNdx] = true; } } else if (line.indexOf("@@ PIXEL OUTPUT @@") >= 0) { // have to create a VS_OUTPUT struct, a getInputs function (both from varyings), // a PS_OUTPUT struct, and a generateOutput function (both based on numTargets or haveGLFragColor) lines[ndx] = "// @@ PIXEL OUTPUT @@\nstruct VS_OUTPUT\n {"; if (haveGLFragCoord) { ++ndx; lines.splice(ndx, 0, " float4 gl_FragCoord : SV_POSITION;"); } let vNdx = 0; for (const tstr of varyings) { ++ndx; lines.splice(ndx, 0, ` ${tstr}: TEXCOORD${vNdx};`); ++vNdx; } if (haveGLFrontFacing) { ++ndx; lines.splice(ndx, 0, " bool gl_FrontFacing : SV_IsFrontFace;"); } if (haveGLPointCoord) { ++ndx; lines.splice(ndx, 0, " float2 gl_PointCoord : PointCoord;"); } ++ndx; lines.splice(ndx, 0, " };\nvoid getInputs(VS_OUTPUT input) {"); if (haveGLFragCoord) { ++ndx; lines.splice(ndx, 0, " gl_FragCoord = input.gl_FragCoord;"); } for (const tstr of varyings) { let t = tstr.indexOf("_v"); let vName = tstr.substring(t); t = vName.indexOf(" "); vName = vName.substring(0, t); ++ndx; lines.splice(ndx, 0, ` ${vName} = input.${vName};`); } if (haveGLFrontFacing) { ++ndx; lines.splice(ndx, 0, " gl_FrontFacing = input.gl_FrontFacing;"); } if (haveGLPointCoord) { ++ndx; lines.splice(ndx, 0, " gl_PointCoord = input.gl_PointCoord;"); } ++ndx; lines.splice(ndx, 0, "}\nstruct PS_OUTPUT\n {"); let cNdx = 0; while (cNdx < numTargets) { ++ndx; lines.splice(ndx, 0, ` float4 col${cNdx} : SV_TARGET${cNdx};`); ++cNdx; } if (haveGLFragColorOnly) { ++ndx; lines.splice(ndx, 0, " float4 out_FragColor : SV_TARGET;"); } else { for (cNdx = 0; cNdx < haveGLFragColor.length; ++cNdx) { if (haveGLFragColor[cNdx]) { ++ndx; lines.splice(ndx, 0, ` float4 out_FragColor${cNdx} : SV_TARGET${cNdx};`); } } } if (haveGLDepth) { ++ndx; lines.splice(ndx, 0, " float gl_Depth : SV_Depth;"); } ++ndx; lines.splice(ndx, 0, " };\nPS_OUTPUT generateOutput () {\n PS_OUTPUT output;"); cNdx = 0; while (cNdx < numTargets) { ++ndx; lines.splice(ndx, 0, ` output.col${cNdx} = gl_Color[${cNdx}];`); ++cNdx; } if (haveGLFragColorOnly) { ++ndx; lines.splice(ndx, 0, " output.out_FragColor = out_FragColor;"); } else { for (cNdx = 0; cNdx < haveGLFragColor.length; ++cNdx) { if (haveGLFragColor[cNdx]) { ++ndx; lines.splice(ndx, 0, ` output.out_FragColor${cNdx} = out_FragColor${cNdx};`); } } } if (haveGLDepth) { ++ndx; lines.splice(ndx, 0, " output.gl_Depth = gl_Depth;"); } ++ndx; lines.splice(ndx, 0, " return output;\n}"); } else if (line.indexOf("PS_OUTPUT main") >= 0) { lines[ndx] = `// ${line}\nPS_OUTPUT main(VS_OUTPUT input){`; } else if (line.indexOf("@@ MAIN PROLOGUE @@") >= 0) { lines[ndx] = `// ${line}\ngetInputs(input);`; } } ++ndx; } } const srcH2 = lines.join("\n"); dsfNdx = shaderFiles.push(new DebugShaderFile(fnameH, srcH2, isVS, false, false)); if (isVS) this._vertHNdx = dsfNdx - 1; else this._fragHNdx = dsfNdx - 1; } } /** Context in which ShaderPrograms are executed. Avoids switching shaders unnecessarily. * Ensures shader programs are compiled before use and un-bound when scope is disposed. * Instances of this class must *only* be declared with the `using` keyword! * @internal */ export class ShaderProgramExecutor { _program; static _params; constructor(target, pass, program) { this.params.init(target, pass); this.changeProgram(program); } static freeParams() { this._params = undefined; } _isDisposed = false; get isDisposed() { return this._isDisposed; } /** Clears the current program to be executed. This does not free WebGL resources, since those are owned by Techniques. */ [Symbol.dispose]() { this.changeProgram(undefined); ShaderProgramExecutor.freeParams(); this._isDisposed = true; } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */ dispose() { this[Symbol.dispose](); } setProgram(program) { return this.changeProgram(program); } get isValid() { return undefined !== this._program; } get target() { return this.params.target; } get renderPass() { return this.params.renderPass; } get params() { if (undefined === ShaderProgramExecutor._params) ShaderProgramExecutor._params = new ShaderProgramParams(); return ShaderProgramExecutor._params; } draw(params) { assert(this.isValid); if (undefined !== this._program) { this._program.draw(params); } } drawInterrupt(params) { assert(params.target === this.params.target); const tech = params.target.techniques.getTechnique(params.geometry.techniqueId); const program = tech.getShader(TechniqueFlags.defaults); if (this.setProgram(program)) { this.draw(params); } } pushBranch(branch) { this.target.pushBranch(branch); } popBranch() { this.target.popBranch(); } pushBatch(batch) { this.target.pushBatch(batch); } popBatch() { this.target.popBatch(); } changeProgram(program) { if (this._program === program) { return true; } else if (undefined !== this._program) { this._program.endUse(); } this._program = program; if (undefined !== program && !program.use(this.params)) { this._program = undefined; return false; } return true; } } //# sourceMappingURL=ShaderProgram.js.map