Skip to content

Asynchronous shader compilation in Babylon Native #13587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Mar 8, 2023
1 change: 1 addition & 0 deletions packages/dev/core/src/Engines/Native/nativeInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface INativeEngine {
updateDynamicVertexBuffer(vertexBuffer: NativeData, bytes: ArrayBuffer, byteOffset: number, byteLength: number): void;

createProgram(vertexShader: string, fragmentShader: string): NativeProgram;
createProgramAsync(vertexShader: string, fragmentShader: string, onSuccess: () => void, onError: (error: Error) => void): NativeProgram;
getUniforms(shaderProgram: NativeProgram, uniformsNames: string[]): WebGLUniformLocation[];
getAttributes(shaderProgram: NativeProgram, attributeNames: string[]): number[];

Expand Down
20 changes: 17 additions & 3 deletions packages/dev/core/src/Engines/Native/nativePipelineContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@ import type { IPipelineContext } from "../IPipelineContext";
import type { NativeEngine } from "../nativeEngine";

export class NativePipelineContext implements IPipelineContext {
// TODO: async should be true?
public isAsync = false;
public isReady = false;
public isParallelCompiled: boolean = true;
public isCompiled: boolean = false;
public compilationError?: Error;

public get isAsync(): boolean {
return this.isParallelCompiled;
}

public get isReady(): boolean {
if (this.compilationError) {
const message = this.compilationError.message;
throw new Error("SHADER ERROR" + (typeof message === "string" ? "\n" + message : ""));
}
return this.isCompiled;
}

public onCompiled?: () => void;

public _getVertexShaderCode(): string | null {
return null;
Expand Down
58 changes: 46 additions & 12 deletions packages/dev/core/src/Engines/nativeEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,27 +646,44 @@ export class NativeEngine extends Engine {
}
}

/**
* @internal
*/
public _isRenderingStateCompiled(pipelineContext: IPipelineContext): boolean {
// TODO: support async shader compilcation
return true;
public isAsync(pipelineContext: IPipelineContext): boolean {
return !!(pipelineContext.isAsync && this._engine.createProgramAsync);
}

/**
* @internal
*/
public _executeWhenRenderingStateIsCompiled(pipelineContext: IPipelineContext, action: () => void) {
// TODO: support async shader compilcation
action();
const nativePipelineContext = pipelineContext as NativePipelineContext;

if (!this.isAsync(pipelineContext)) {
action();
return;
}

const oldHandler = nativePipelineContext.onCompiled;

if (oldHandler) {
nativePipelineContext.onCompiled = () => {
oldHandler!();
action();
};
} else {
nativePipelineContext.onCompiled = action;
}
}

public createRawShaderProgram(): WebGLProgram {
throw new Error("Not Supported");
}

public createShaderProgram(_pipelineContext: IPipelineContext, vertexCode: string, fragmentCode: string, defines: Nullable<string>): WebGLProgram {
public createShaderProgram(pipelineContext: IPipelineContext, vertexCode: string, fragmentCode: string, defines: Nullable<string>): WebGLProgram {
const nativePipelineContext = pipelineContext as NativePipelineContext;

if (nativePipelineContext.nativeProgram) {
throw new Error("Tried to create a second program in the same NativePipelineContext");
}

this.onBeforeShaderCompilationObservable.notifyObservers(this);

const vertexInliner = new ShaderCodeInliner(vertexCode);
Expand All @@ -680,9 +697,26 @@ export class NativeEngine extends Engine {
vertexCode = ThinEngine._ConcatenateShader(vertexCode, defines);
fragmentCode = ThinEngine._ConcatenateShader(fragmentCode, defines);

const program = this._engine.createProgram(vertexCode, fragmentCode);
this.onAfterShaderCompilationObservable.notifyObservers(this);
return program as WebGLProgram;
const onSuccess = () => {
nativePipelineContext.isCompiled = true;
nativePipelineContext.onCompiled?.();
this.onAfterShaderCompilationObservable.notifyObservers(this);
};

if (this.isAsync(pipelineContext)) {
return this._engine.createProgramAsync(vertexCode, fragmentCode, onSuccess, (error: Error) => {
nativePipelineContext.compilationError = error;
}) as WebGLProgram;
} else {
try {
const program = (nativePipelineContext.nativeProgram = this._engine.createProgram(vertexCode, fragmentCode));
onSuccess();
return program as WebGLProgram;
} catch (e: any) {
const message = e?.message;
throw new Error("SHADER ERROR" + (typeof message === "string" ? "\n" + message : ""));
}
}
}

/**
Expand Down