2021-04-27 07:04:25 +00:00
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
|
// Licensed under the MIT License.
|
|
|
|
|
|
2024-08-14 23:51:22 +00:00
|
|
|
import { env } from 'onnxruntime-common';
|
2022-05-04 06:41:36 +00:00
|
|
|
|
2024-08-14 23:51:22 +00:00
|
|
|
import { Logger, Profiler } from '../../instrument';
|
2021-04-27 07:04:25 +00:00
|
|
|
|
2024-08-14 23:51:22 +00:00
|
|
|
import { GlslPreprocessor } from './glsl-preprocessor';
|
|
|
|
|
import { getVertexShaderSource } from './glsl-source';
|
|
|
|
|
import { TextureLayoutStrategy } from './texture-layout-strategy';
|
|
|
|
|
import { Artifact, ProgramInfo, ProgramVariable, TextureData, TextureLayout, VariableInfo } from './types';
|
|
|
|
|
import { WebGLContext } from './webgl-context';
|
2021-04-27 07:04:25 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ProgramManager is the main class behind running computations
|
|
|
|
|
* It builds ProgramInfo's into Artifacts
|
|
|
|
|
* It compiles given ProgramInfo's into WebGL Prorams (cached as Artifacts)
|
|
|
|
|
* Uses the artifact to run the computation by calling Draw on
|
|
|
|
|
* the WebGL drawing buffer
|
|
|
|
|
* ProgramManager automatically maps (binds) input variables to their
|
|
|
|
|
* corresponding Location's in the binary program
|
|
|
|
|
*/
|
|
|
|
|
export class ProgramManager {
|
2024-08-14 23:51:22 +00:00
|
|
|
repo: Map<unknown, Artifact>; // this should be per-session object
|
2021-04-27 07:04:25 +00:00
|
|
|
vertexShader: WebGLShader;
|
|
|
|
|
attributesBound: boolean;
|
|
|
|
|
|
|
|
|
|
constructor(
|
2024-08-14 23:51:22 +00:00
|
|
|
public profiler: Readonly<Profiler>,
|
|
|
|
|
public glContext: WebGLContext,
|
|
|
|
|
public textureLayoutStrategy: TextureLayoutStrategy,
|
|
|
|
|
) {
|
2021-04-27 07:04:25 +00:00
|
|
|
this.repo = new Map();
|
|
|
|
|
this.attributesBound = false;
|
|
|
|
|
}
|
2024-08-14 23:51:22 +00:00
|
|
|
getArtifact(key: unknown): Artifact | undefined {
|
2021-04-27 07:04:25 +00:00
|
|
|
return this.repo.get(key);
|
|
|
|
|
}
|
|
|
|
|
setArtifact(key: unknown, artifact: Artifact): void {
|
|
|
|
|
this.repo.set(key, artifact);
|
|
|
|
|
}
|
2021-08-12 19:30:49 +00:00
|
|
|
run(buildArtifact: Artifact, inputs: TextureData[], output: TextureData): void {
|
2024-08-14 23:51:22 +00:00
|
|
|
this.profiler.event(
|
|
|
|
|
'op',
|
|
|
|
|
`ProgramManager.run ${buildArtifact.programInfo.name ?? 'unknown kernel'}`,
|
|
|
|
|
() => {
|
|
|
|
|
const gl = this.glContext.gl;
|
|
|
|
|
const program = buildArtifact.program;
|
|
|
|
|
gl.useProgram(program);
|
|
|
|
|
try {
|
|
|
|
|
this.bindOutput(output);
|
|
|
|
|
if (!this.attributesBound) {
|
|
|
|
|
this.bindAttributes(buildArtifact.attribLocations);
|
|
|
|
|
}
|
|
|
|
|
this.bindUniforms(buildArtifact.uniformLocations, buildArtifact.programInfo.variables ?? [], inputs);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
Logger.error('ProgramManager', buildArtifact.programInfo.shaderSource);
|
|
|
|
|
throw err;
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
2024-08-14 23:51:22 +00:00
|
|
|
this.profiler.event('backend', 'GlContext.draw()', () => {
|
|
|
|
|
this.glContext.draw();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
this.glContext,
|
|
|
|
|
);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
dispose(): void {
|
|
|
|
|
if (this.vertexShader) {
|
|
|
|
|
this.glContext.deleteShader(this.vertexShader);
|
|
|
|
|
}
|
2024-08-14 23:51:22 +00:00
|
|
|
this.repo.forEach((a) => this.glContext.deleteProgram(a.program));
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
2021-08-12 19:30:49 +00:00
|
|
|
build(programInfo: ProgramInfo, inputTextureLayouts: TextureLayout[], outputTextureLayout: TextureLayout): Artifact {
|
2021-04-27 07:04:25 +00:00
|
|
|
return this.profiler.event('backend', 'ProgramManager.build', () => {
|
2021-08-12 19:30:49 +00:00
|
|
|
const preprocessor = new GlslPreprocessor(this.glContext, programInfo, inputTextureLayouts, outputTextureLayout);
|
2021-04-27 07:04:25 +00:00
|
|
|
const fragScript = preprocessor.preprocess();
|
|
|
|
|
const program = this.compile(fragScript);
|
|
|
|
|
const artifact = {
|
|
|
|
|
programInfo,
|
|
|
|
|
program,
|
|
|
|
|
uniformLocations: this.getUniformLocations(
|
2024-08-14 23:51:22 +00:00
|
|
|
program,
|
|
|
|
|
preprocessor.context.programInfo.inputNames,
|
|
|
|
|
preprocessor.context.programInfo.variables,
|
|
|
|
|
),
|
|
|
|
|
attribLocations: this.getAttribLocations(program),
|
2021-04-27 07:04:25 +00:00
|
|
|
};
|
|
|
|
|
return artifact;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
protected compile(fragShaderScript: string): WebGLProgram {
|
|
|
|
|
if (!this.vertexShader) {
|
|
|
|
|
Logger.verbose('ProrgramManager', 'Compiling and caching Vertex shader for the first time');
|
|
|
|
|
const vertexShaderScript = getVertexShaderSource(this.glContext.version);
|
|
|
|
|
this.vertexShader = this.glContext.compileShader(vertexShaderScript, this.glContext.gl.VERTEX_SHADER);
|
|
|
|
|
}
|
|
|
|
|
if (env.debug) {
|
2024-08-14 23:51:22 +00:00
|
|
|
Logger.verbose(
|
|
|
|
|
'ProrgramManager',
|
|
|
|
|
`FragShader:
|
2021-04-27 07:04:25 +00:00
|
|
|
${fragShaderScript}
|
2024-08-14 23:51:22 +00:00
|
|
|
`,
|
|
|
|
|
);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
const fragShader = this.glContext.compileShader(fragShaderScript, this.glContext.gl.FRAGMENT_SHADER);
|
|
|
|
|
const program = this.glContext.createProgram(this.vertexShader, fragShader);
|
|
|
|
|
this.glContext.deleteShader(fragShader);
|
|
|
|
|
return program;
|
|
|
|
|
}
|
|
|
|
|
bindOutput(td: TextureData): void {
|
2021-05-03 22:03:25 +00:00
|
|
|
const width = td.width;
|
|
|
|
|
const height = td.height;
|
2021-04-27 07:04:25 +00:00
|
|
|
Logger.verbose(
|
2024-08-14 23:51:22 +00:00
|
|
|
'ProrgramManager',
|
|
|
|
|
`Binding output texture to Framebuffer: w/h=${width}/${height}, shape=${td.shape}, type=${td.tensor.type}`,
|
|
|
|
|
);
|
2021-05-03 22:03:25 +00:00
|
|
|
this.glContext.attachFramebuffer(td.texture, width, height);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
bindAttributes(attribLocations: Artifact.AttribLocations): void {
|
|
|
|
|
const positionHandle = attribLocations.position;
|
|
|
|
|
const textureCoordHandle = attribLocations.textureCoord;
|
|
|
|
|
this.glContext.setVertexAttributes(positionHandle, textureCoordHandle);
|
|
|
|
|
this.attributesBound = true;
|
|
|
|
|
}
|
2024-08-14 23:51:22 +00:00
|
|
|
bindUniforms(
|
|
|
|
|
uniformLocations: Artifact.UniformLocations,
|
|
|
|
|
variables: ProgramVariable[],
|
|
|
|
|
textures: TextureData[],
|
|
|
|
|
): void {
|
2021-04-27 07:04:25 +00:00
|
|
|
const gl = this.glContext.gl;
|
|
|
|
|
let texturePosition = 0;
|
2024-08-14 23:51:22 +00:00
|
|
|
for (const { name, type, location, arrayLength } of uniformLocations) {
|
|
|
|
|
const value = variables.find((v) => v.name === name)?.data;
|
2021-08-12 19:30:49 +00:00
|
|
|
if (type !== 'sampler2D' && !value) {
|
|
|
|
|
throw new Error(`variable '${name}' does not have data defined in program info`);
|
|
|
|
|
}
|
2021-04-27 07:04:25 +00:00
|
|
|
switch (type) {
|
|
|
|
|
case 'sampler2D':
|
|
|
|
|
this.bindTexture(textures[texturePosition], location, texturePosition);
|
|
|
|
|
texturePosition++;
|
|
|
|
|
break;
|
|
|
|
|
case 'float':
|
|
|
|
|
if (arrayLength) {
|
2021-08-12 19:30:49 +00:00
|
|
|
gl.uniform1fv(location, value as number[]);
|
2021-04-27 07:04:25 +00:00
|
|
|
} else {
|
2021-08-12 19:30:49 +00:00
|
|
|
gl.uniform1f(location, value as number);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'int':
|
|
|
|
|
if (arrayLength) {
|
2021-08-12 19:30:49 +00:00
|
|
|
gl.uniform1iv(location, value as number[]);
|
2021-04-27 07:04:25 +00:00
|
|
|
} else {
|
2021-08-12 19:30:49 +00:00
|
|
|
gl.uniform1i(location, value as number);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Uniform not implemented: ${type}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bindTexture(td: TextureData, uniformHandle: WebGLUniformLocation, position: number): void {
|
|
|
|
|
this.glContext.bindTextureToUniform(td.texture, position, uniformHandle);
|
|
|
|
|
}
|
|
|
|
|
getAttribLocations(program: WebGLProgram): Artifact.AttribLocations {
|
|
|
|
|
return {
|
|
|
|
|
position: this.getAttribLocation(program, 'position'),
|
2024-08-14 23:51:22 +00:00
|
|
|
textureCoord: this.getAttribLocation(program, 'textureCoord'),
|
2021-04-27 07:04:25 +00:00
|
|
|
};
|
|
|
|
|
}
|
2024-08-14 23:51:22 +00:00
|
|
|
getUniformLocations(
|
|
|
|
|
program: WebGLProgram,
|
|
|
|
|
samplers?: string[],
|
|
|
|
|
variables?: VariableInfo[],
|
|
|
|
|
): Artifact.UniformLocations {
|
2021-04-27 07:04:25 +00:00
|
|
|
const uniformLocations: Artifact.UniformLocations = [];
|
|
|
|
|
if (samplers) {
|
|
|
|
|
for (const sampler of samplers) {
|
2024-08-14 23:51:22 +00:00
|
|
|
uniformLocations.push({
|
|
|
|
|
name: sampler,
|
|
|
|
|
type: 'sampler2D',
|
|
|
|
|
location: this.getUniformLocation(program, sampler),
|
|
|
|
|
});
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (variables) {
|
|
|
|
|
for (const variable of variables) {
|
2024-08-14 23:51:22 +00:00
|
|
|
uniformLocations.push({ ...variable, location: this.getUniformLocation(program, variable.name) });
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return uniformLocations;
|
|
|
|
|
}
|
|
|
|
|
getUniformLocation(program: WebGLProgram, name: string): WebGLUniformLocation {
|
|
|
|
|
const gl = this.glContext.gl;
|
|
|
|
|
const reference = gl.getUniformLocation(program, name);
|
|
|
|
|
if (reference === null) {
|
|
|
|
|
throw new Error(`Uniform ${name} not found.`);
|
|
|
|
|
}
|
|
|
|
|
return reference;
|
|
|
|
|
}
|
|
|
|
|
getAttribLocation(program: WebGLProgram, name: string): number {
|
|
|
|
|
const gl = this.glContext.gl;
|
|
|
|
|
const attributeLocation: number = gl.getAttribLocation(program, name);
|
|
|
|
|
return attributeLocation;
|
|
|
|
|
}
|
|
|
|
|
}
|