onnxruntime/js/web/lib/onnxjs/backends/webgl/texture-manager.ts
Ye Wang 83dc22585c
Second round cherry-pick to rel-1.9.0 (#9062)
* Adding async fetching for webgl backend (#8951)

* Adding async fetching for webgl backend

* fix PR comments and CI failure.

* fixing a bug

* adding a flag

* Enable linking in exception throwing support library when build onnxruntime wasm. (#8973)

* Enable linking in exception throwing support library when build onnxruntime webassembly containing onnxruntime-extensions.

* Add flag in build.py to enable linking exceptions throwing library.

* Update onnxruntime-extensions document and bind custom_ops build flag with use_extensions.

* Update doc.

* Update cgmanifest.json.

Co-authored-by: Zuwei Zhao <zuzhao@microsoft.com>

* Remove document text from error message in a couple of ops (#9003)

* do not add pkg wheel entry to the index html file if it already exists (#9004)

* do not add pkg wheel entry to the index html file if it already exists

* [js/web] fix ort web e2e test (#9025)

* Fix cmake POWER10 detection

Recent commit 60c98a8 changed variable mlas_common_srcs which affects
POWER10 detection.

* Fix Where op type reduction processing (#9033)

* Update type reduction script to track Where Op's second input type.

* Clean up op_kernel_type_control.h includes.

* Use more maintainable include.

* Fix ROCm wheels CI pipeline break by installing latest protobuf from source (#9047)

* install protobuf from source

* fix rm command in Dockerfile

* fix options on rm command

* fix cd into protobuf source directory

* try again

* remove strip step

* debug list the files

* ls on /usr

* more debug

* more debug

* adjust LD_LIBRARY_PATH

* try remove protobuf before ORT build

* [js/web] a bugfix and add tests for wasm proxy worker (#9048)

* [js/web] add tests for wasm proxy worker

* fix script src override

* Set onnxruntime_DISABLE_RTTI to default OFF (#9049)

Co-authored-by: Du Li <duli1@microsoft.com>
Co-authored-by: Zuwei Zhao <4123666+Zuwei-Zhao@users.noreply.github.com>
Co-authored-by: Zuwei Zhao <zuzhao@microsoft.com>
Co-authored-by: Hariharan Seshadri <shariharan91@gmail.com>
Co-authored-by: liqun Fu <liqfu@microsoft.com>
Co-authored-by: Yulong Wang <yulongw@microsoft.com>
Co-authored-by: Rajalakshmi Srinivasaraghavan <rajis@linux.ibm.com>
Co-authored-by: Edward Chen <18449977+edgchen1@users.noreply.github.com>
Co-authored-by: Suffian Khan <sukha@microsoft.com>
Co-authored-by: Changming Sun <chasun@microsoft.com>
2021-09-15 18:02:07 -07:00

220 lines
8.4 KiB
TypeScript

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {Logger, Profiler} from '../../instrument';
import {Tensor} from '../../tensor';
import {Encoder} from './texture-data-encoder';
import {TextureLayoutStrategy} from './texture-layout-strategy';
import {TextureData, TextureLayout} from './types';
import {WebGLContext} from './webgl-context';
export interface TextureManagerConfig {
reuseTextures?: boolean;
}
/**
* TextureManager is the mainly responsible for caching Textures
* Textures are cached in 2 levels:
* 1. the texures which are associated with a dataId (from Tensor)
* Caching these is crucial to performance. These are In-use Textures
* 2. textures which are not in use by any current ProgramInfo/Tensor
* These are called Free Textures
* TextureManager is also used to help creating textures. For this it
* uses WebGLContext and TextureLayoutStrategy
*/
export class TextureManager {
private readonly inUseTextures: Map<string, WebGLTexture[]>;
private readonly idleTextures: Map<string, WebGLTexture[]>;
private readonly textureLookup: Map<WebGLTexture, string>;
private readonly pendingRead: Map<Tensor.Id, Array<(arr: Tensor.NumberType) => void>> = new Map();
constructor(
public glContext: WebGLContext, public layoutStrategy: TextureLayoutStrategy, public profiler: Readonly<Profiler>,
private config: TextureManagerConfig) {
if (config.reuseTextures) {
this.inUseTextures = new Map();
this.idleTextures = new Map();
this.textureLookup = new Map();
}
}
createTextureFromLayout(
dataType: Tensor.DataType, layout: TextureLayout, data?: Tensor.NumberType, usage?: Encoder.Usage) {
const textureDataType = this.toEncoderType(dataType);
const encoder = this.glContext.getEncoder(textureDataType, layout.channels || 1, usage);
if (layout.isPacked && usage === Encoder.Usage.UploadOnly) {
throw new Error('not implemented');
}
const width = layout.width;
const height = layout.height;
let key: string|undefined;
let inUseTextures: WebGLTexture[]|undefined;
if (this.config.reuseTextures) {
key = `${width}x${height}_${encoder.format}_${encoder.internalFormat}_${encoder.textureType}`;
inUseTextures = this.inUseTextures.get(key);
if (!inUseTextures) {
inUseTextures = [];
this.inUseTextures.set(key, inUseTextures);
}
const idleTextures = this.idleTextures.get(key);
if (idleTextures && idleTextures.length > 0) {
const texture = idleTextures.pop()!;
inUseTextures.push(texture);
if (usage === Encoder.Usage.UploadOnly) {
this.glContext.updateTexture(texture, width, height, encoder, this.toTextureData(dataType, data)!);
}
return texture;
}
}
Logger.verbose('TextureManager', `Creating new texture of size ${layout.width}x${layout.height}`);
const texture = this.glContext.allocateTexture(width, height, encoder, this.toTextureData(dataType, data));
if (this.config.reuseTextures) {
inUseTextures!.push(texture);
this.textureLookup.set(texture, key!);
}
return texture;
}
readTexture(td: TextureData, dataType: Tensor.DataType, channels?: number): Tensor.NumberType {
if (!channels) {
channels = 1;
}
return this.profiler.event('backend', 'TextureManager.readTexture', () => {
const dataSize = td.shape.reduce((a, b) => a * b) * channels!;
const data = this.glContext.readTexture(
td.texture, td.width, td.height, dataSize, this.toEncoderType(dataType), channels!);
return this.toTensorData(dataType, data);
});
}
async readTextureAsync(td: TextureData, dataType: Tensor.DataType, channels?: number): Promise<Tensor.NumberType> {
const dataId = td.tensor.dataId;
if (!channels) {
channels = 1;
}
if (this.pendingRead.has(dataId)) {
const subscribers = this.pendingRead.get(dataId);
return new Promise<Tensor.NumberType>(resolve => subscribers?.push(resolve));
}
return this.profiler.event('backend', 'TextureManager.readTextureAsync', async () => {
this.pendingRead.set(dataId, []);
const dataSize = td.shape.reduce((a, b) => a * b) * channels!;
// add a fence waiting for the data to be ready
await this.glContext.createAndWaitForFence();
const data = this.glContext.readTexture(
td.texture, td.width, td.height, dataSize, this.toEncoderType(dataType), channels!);
const tensorData = this.toTensorData(dataType, data);
const subscribers = this.pendingRead.get(dataId);
this.pendingRead.delete(dataId);
subscribers?.forEach(resolve => resolve(tensorData));
return tensorData;
});
}
readUint8TextureAsFloat(td: TextureData): Float32Array {
return this.profiler.event('backend', 'TextureManager.readUint8TextureAsFloat', () => {
const dataSize = td.shape.reduce((a, b) => a * b);
const data = this.glContext.readTexture(td.texture, td.width, td.height, dataSize * 4, 'byte', 4);
return new Float32Array(data.buffer, data.byteOffset, dataSize);
});
}
releaseTexture(textureData: TextureData, deleteTexture?: boolean): void {
let key: string|undefined;
if (this.config.reuseTextures) {
key = this.textureLookup.get(textureData.texture);
if (key) {
if (deleteTexture) {
this.textureLookup.delete(key);
}
const inUseTextures = this.inUseTextures.get(key);
if (inUseTextures) {
const index = inUseTextures.indexOf(textureData.texture);
if (index !== -1) {
inUseTextures.splice(index, 1);
let idleTextures = this.idleTextures.get(key);
if (!idleTextures) {
idleTextures = [];
this.idleTextures.set(key, idleTextures);
}
idleTextures.push(textureData.texture);
}
}
}
}
if (!key || deleteTexture) {
Logger.verbose('TextureManager', `Deleting texture of size ${textureData.width}x${textureData.height}`);
this.glContext.deleteTexture(textureData.texture);
}
}
toTensorData(dataType: Tensor.DataType, data: Encoder.DataArrayType): Tensor.NumberType {
switch (dataType) {
case 'int16':
return data instanceof Int16Array ? data : Int16Array.from(data);
case 'int32':
return data instanceof Int32Array ? data : Int32Array.from(data);
case 'int8':
return data instanceof Int8Array ? data : Int8Array.from(data);
case 'uint16':
return data instanceof Uint16Array ? data : Uint16Array.from(data);
case 'uint32':
return data instanceof Uint32Array ? data : Uint32Array.from(data);
case 'uint8':
case 'bool':
return data instanceof Uint8Array ? data : Uint8Array.from(data);
case 'float32':
return data instanceof Float32Array ? data : Float32Array.from(data);
case 'float64':
return data instanceof Float64Array ? data : Float64Array.from(data);
default:
throw new Error(`TensorData type ${dataType} is not supported`);
}
}
toTextureData(dataType: Tensor.DataType, data: Tensor.NumberType|undefined): Encoder.DataArrayType|undefined {
if (!data) {
return undefined;
}
return (data instanceof Float32Array) ? data : new Float32Array(data);
/*
switch (dataType) {
case 'int16':
case 'int32':
case 'uint16':
case 'uint32':
return (data.constructor === Uint32Array) ? data as Uint32Array : new Uint32Array(data);
case 'int8':
case 'uint8':
case 'bool':
return (data.constructor === Uint8Array) ? data as Uint8Array : new Uint8Array(data);
case 'float32':
case 'float64':
return (data.constructor === Float32Array) ? data as Float32Array : new Float32Array(data);
default:
throw new Error(`TensorData type ${dataType} is not supported`);
}
*/
}
toEncoderType(_dataType: Tensor.DataType): Encoder.DataType {
return 'float';
// switch (dataType) {
// case 'int16':
// case 'int32':
// case 'uint16':
// case 'uint32':
// return 'int';
// case 'uint8':
// case 'bool':
// return 'byte';
// case 'float32':
// case 'float64':
// return 'float';
// default:
// throw new Error(`TensorData type ${dataType} is not supported`);
// }
}
clearActiveTextures(): void {
this.glContext.clearActiveTextures();
}
}