@itwin/core-frontend
Version:
iTwin.js frontend components
618 lines • 27.2 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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