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 { InferenceHandler } from '../../backend';
|
|
|
|
|
import { Logger } from '../../instrument';
|
|
|
|
|
import { Tensor } from '../../tensor';
|
|
|
|
|
import { ShapeUtil } from '../../util';
|
|
|
|
|
|
|
|
|
|
import { createPackProgramInfoLoader } from './ops/pack';
|
|
|
|
|
import { createPackedReshape3DProgramInfoLoader, isReshapeCheap, processDims3D } from './ops/reshape-packed';
|
|
|
|
|
import { encodeAsUint8 } from './ops/uint8-encode';
|
|
|
|
|
import { createUnpackProgramInfoLoader } from './ops/unpack';
|
|
|
|
|
import { WebGLSessionHandler } from './session-handler';
|
|
|
|
|
import { EncoderUsage } from './texture-data-encoder';
|
|
|
|
|
import {
|
|
|
|
|
calculateTextureWidthAndHeight,
|
|
|
|
|
createTextureLayoutFromShape,
|
|
|
|
|
createTextureLayoutFromTextureType,
|
|
|
|
|
} from './texture-layout';
|
|
|
|
|
import { Artifact, ProgramInfo, ProgramInfoLoader, TextureData, TextureLayout, TextureType } from './types';
|
|
|
|
|
|
|
|
|
|
const getProgramInfoUniqueKey = (
|
|
|
|
|
programInfo: ProgramInfo | ProgramInfoLoader,
|
|
|
|
|
inputTextureDatas: TextureData[],
|
|
|
|
|
): string => {
|
|
|
|
|
const inputs = inputTextureDatas
|
|
|
|
|
.map((texture) => `${texture.unpackedShape.join(',')};${texture.width}x${texture.height}`)
|
|
|
|
|
.join('_');
|
|
|
|
|
let key = programInfo.name;
|
|
|
|
|
if (programInfo.cacheHint) {
|
|
|
|
|
key += '[' + programInfo.cacheHint + ']';
|
|
|
|
|
}
|
|
|
|
|
key += ':' + inputs;
|
|
|
|
|
return key;
|
|
|
|
|
};
|
2021-04-27 07:04:25 +00:00
|
|
|
|
|
|
|
|
export class WebGLInferenceHandler implements InferenceHandler {
|
2021-05-03 22:03:25 +00:00
|
|
|
private packedTextureDataCache: Map<Tensor.Id, TextureData>;
|
|
|
|
|
private unpackedTextureDataCache: Map<Tensor.Id, TextureData>;
|
2021-04-27 07:04:25 +00:00
|
|
|
constructor(public session: WebGLSessionHandler) {
|
2021-05-03 22:03:25 +00:00
|
|
|
this.packedTextureDataCache = new Map();
|
|
|
|
|
this.unpackedTextureDataCache = new Map();
|
2021-08-12 19:30:49 +00:00
|
|
|
}
|
2021-05-03 22:03:25 +00:00
|
|
|
|
2021-08-12 19:30:49 +00:00
|
|
|
/**
|
|
|
|
|
* @returns [width, height]
|
|
|
|
|
*/
|
|
|
|
|
calculateTextureWidthAndHeight(shape: readonly number[], textureType: TextureType): [number, number] {
|
|
|
|
|
return calculateTextureWidthAndHeight(this.session.layoutStrategy, shape, textureType);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
2024-08-14 23:51:22 +00:00
|
|
|
executeProgram(program: ProgramInfo | ProgramInfoLoader, inputs: readonly Tensor[]): TextureData {
|
2021-08-12 19:30:49 +00:00
|
|
|
if (inputs.length < program.inputNames.length) {
|
|
|
|
|
throw new Error(`Input size mustn't be less than ${program.inputNames.length}.`);
|
|
|
|
|
}
|
|
|
|
|
if (program.inputNames.length !== program.inputTypes.length) {
|
|
|
|
|
throw new Error('input names size does not match input types');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// create texture info for input
|
|
|
|
|
const inputTextureDatas: TextureData[] = [];
|
|
|
|
|
for (let i = 0; i < program.inputNames.length; ++i) {
|
|
|
|
|
inputTextureDatas[i] = this.getOrCreateTextureData(inputs[i], program.inputTypes[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const key = getProgramInfoUniqueKey(program, inputTextureDatas);
|
|
|
|
|
let artifact = this.session.programManager.getArtifact(key);
|
2024-08-14 23:51:22 +00:00
|
|
|
const programInfo = artifact
|
|
|
|
|
? artifact.programInfo
|
|
|
|
|
: typeof (program as ProgramInfoLoader).get === 'function'
|
|
|
|
|
? (program as ProgramInfoLoader).get()
|
|
|
|
|
: (program as ProgramInfo);
|
2021-08-12 19:30:49 +00:00
|
|
|
|
|
|
|
|
// create texture info for output
|
|
|
|
|
const outputTextureLayout = createTextureLayoutFromTextureType(
|
2024-08-14 23:51:22 +00:00
|
|
|
this.session.layoutStrategy,
|
|
|
|
|
programInfo.output.dims,
|
|
|
|
|
programInfo.output.textureType,
|
|
|
|
|
);
|
2021-08-12 19:30:49 +00:00
|
|
|
const outputTextureData = this.createTextureData(outputTextureLayout, programInfo.output.type);
|
|
|
|
|
|
2021-04-27 07:04:25 +00:00
|
|
|
if (!artifact) {
|
2021-08-12 19:30:49 +00:00
|
|
|
artifact = this.session.programManager.build(programInfo, inputTextureDatas, outputTextureData);
|
|
|
|
|
this.session.programManager.setArtifact(key, artifact);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
2021-08-12 19:30:49 +00:00
|
|
|
|
|
|
|
|
this.runProgram(artifact, inputTextureDatas, outputTextureData);
|
|
|
|
|
return outputTextureData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
run(program: ProgramInfoLoader, inputs: readonly Tensor[]): Tensor {
|
|
|
|
|
const outputTextureData = this.executeProgram(program, inputs);
|
|
|
|
|
return outputTextureData.tensor;
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
2021-08-12 19:30:49 +00:00
|
|
|
private runProgram(artifact: Artifact, inputs: TextureData[], output: TextureData): void {
|
|
|
|
|
// input should match
|
|
|
|
|
for (let i = 0; i < inputs.length; ++i) {
|
|
|
|
|
if (!!inputs[i].isPacked !== (artifact.programInfo.inputTypes[i] === TextureType.packed)) {
|
|
|
|
|
throw new Error(`input[${i}] property packed inconsistent`);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
2021-05-03 22:03:25 +00:00
|
|
|
}
|
2021-04-27 07:04:25 +00:00
|
|
|
|
|
|
|
|
// output should match
|
2021-08-12 19:30:49 +00:00
|
|
|
if (!!output.isPacked !== (artifact.programInfo.output.textureType === TextureType.packed)) {
|
2021-04-27 07:04:25 +00:00
|
|
|
throw new Error('output property packed inconsistent');
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-12 19:30:49 +00:00
|
|
|
this.session.programManager.run(artifact, inputs, output);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a TextureData object from a tensor.
|
2023-10-06 20:37:37 +00:00
|
|
|
* Usage = EncoderUsage.UploadOnly.
|
2021-04-27 07:04:25 +00:00
|
|
|
* If a related texture data is found in cache, returns it;
|
|
|
|
|
* Otherwise:
|
|
|
|
|
* Creates a new texture layout if not provided;
|
|
|
|
|
* Creates WebGLTexture with the layout;
|
|
|
|
|
* Upload tensor data to the texture;
|
|
|
|
|
* Creates a texture data object associated with the given tensor.
|
|
|
|
|
* @param tensor the tensor with data to upload
|
|
|
|
|
*/
|
2021-08-12 19:30:49 +00:00
|
|
|
private getOrCreateTextureData(tensor: Tensor, textureType: TextureType) {
|
|
|
|
|
let td = this.getTextureData(tensor.dataId, textureType === TextureType.packed);
|
|
|
|
|
|
2021-04-27 07:04:25 +00:00
|
|
|
if (!td) {
|
2021-08-12 19:30:49 +00:00
|
|
|
// check if we have texture data in different type
|
|
|
|
|
td = this.getTextureData(tensor.dataId, textureType !== TextureType.packed);
|
|
|
|
|
if (td) {
|
|
|
|
|
if (textureType === TextureType.packed) {
|
|
|
|
|
return this.pack(td);
|
2021-05-03 22:03:25 +00:00
|
|
|
} else {
|
2021-08-12 19:30:49 +00:00
|
|
|
return this.unpack(td);
|
2021-05-03 22:03:25 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
2021-08-12 19:30:49 +00:00
|
|
|
if (!td) {
|
|
|
|
|
const layout = createTextureLayoutFromTextureType(this.session.layoutStrategy, tensor.dims, textureType);
|
|
|
|
|
|
|
|
|
|
if (textureType === TextureType.packedLastDimension) {
|
|
|
|
|
const group = 1;
|
|
|
|
|
const channels = 4;
|
|
|
|
|
const shape = tensor.dims;
|
|
|
|
|
if (shape.length === 4) {
|
|
|
|
|
// pre-processing for kernel data of Conv.
|
|
|
|
|
//
|
|
|
|
|
// TODO: currently this is a hacking to overwrite Conv's weight. The correct way to do this should be:
|
|
|
|
|
// 1. implement texture based const-folding
|
|
|
|
|
// 2. create a WebGL program "preprocessConvWeight" to do the same work as below
|
|
|
|
|
// 3. run the program before dotProduct.
|
|
|
|
|
//
|
|
|
|
|
const adjustedKernelShape = [shape[0], Math.ceil((shape[1] * shape[2] * shape[3]) / channels)];
|
2024-08-14 23:51:22 +00:00
|
|
|
const adjustedLayout = createTextureLayoutFromTextureType(
|
|
|
|
|
this.session.layoutStrategy,
|
|
|
|
|
adjustedKernelShape,
|
|
|
|
|
textureType,
|
|
|
|
|
);
|
2021-08-12 19:30:49 +00:00
|
|
|
let buffer = tensor.numberData;
|
2024-08-14 23:51:22 +00:00
|
|
|
if ((shape[1] * shape[2] * shape[3]) % channels !== 0) {
|
2021-08-12 19:30:49 +00:00
|
|
|
const numFeatureMaps = shape[0];
|
|
|
|
|
const oldRowSize = shape[1] * shape[2] * shape[3];
|
2024-08-14 23:51:22 +00:00
|
|
|
const newRowSize = Math.ceil((oldRowSize * group) / channels) * channels;
|
2021-08-12 19:30:49 +00:00
|
|
|
const newSize = numFeatureMaps * newRowSize;
|
|
|
|
|
buffer = new Float32Array(newSize);
|
|
|
|
|
for (let f = 0; f < numFeatureMaps; ++f) {
|
|
|
|
|
const oldOffset = f * oldRowSize;
|
2024-08-14 23:51:22 +00:00
|
|
|
const newOffset = f * newRowSize + (f % group) * oldRowSize;
|
2021-08-12 19:30:49 +00:00
|
|
|
buffer.set(tensor.numberData.subarray(oldOffset, oldOffset + oldRowSize), newOffset);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-06 20:37:37 +00:00
|
|
|
return this.createTextureData(adjustedLayout, tensor.type, buffer, tensor, EncoderUsage.UploadOnly);
|
2021-08-12 19:30:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (textureType === TextureType.packed) {
|
2024-08-14 23:51:22 +00:00
|
|
|
const unpackedTextureLayout = createTextureLayoutFromShape(this.session.layoutStrategy, tensor.dims, 1, [], {
|
|
|
|
|
reverseWH: true,
|
|
|
|
|
});
|
2021-08-12 19:30:49 +00:00
|
|
|
const unpackedTextureData = this.createTextureData(
|
2024-08-14 23:51:22 +00:00
|
|
|
unpackedTextureLayout,
|
|
|
|
|
tensor.type,
|
|
|
|
|
tensor.numberData,
|
|
|
|
|
tensor,
|
|
|
|
|
EncoderUsage.UploadOnly,
|
|
|
|
|
);
|
2021-08-12 19:30:49 +00:00
|
|
|
td = this.pack(unpackedTextureData);
|
|
|
|
|
} else {
|
2023-10-06 20:37:37 +00:00
|
|
|
td = this.createTextureData(layout, tensor.type, tensor.numberData, tensor, EncoderUsage.UploadOnly);
|
2021-08-12 19:30:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return td;
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a TextureData object using the given data and bind to the given tensor.
|
2023-10-06 20:37:37 +00:00
|
|
|
* Usage = EncoderUsage.UploadOnly.
|
2021-04-27 07:04:25 +00:00
|
|
|
* NOTE: this function is a hack for Conv implementation. should remove this function, after rewriting Conv
|
|
|
|
|
* implementation by Graph.Transformer
|
|
|
|
|
* @param dataType the tensor data type
|
|
|
|
|
* @param data the actual data to upload
|
|
|
|
|
* @param tensor the tensor to bind. tensor's data is ignored.
|
|
|
|
|
*/
|
|
|
|
|
createTextureDataFromLayoutBindTensor(
|
2024-08-14 23:51:22 +00:00
|
|
|
layout: TextureLayout,
|
|
|
|
|
dataType: Tensor.DataType,
|
|
|
|
|
data: Tensor.NumberType,
|
|
|
|
|
tensor: Tensor,
|
|
|
|
|
): TextureData {
|
2023-10-06 20:37:37 +00:00
|
|
|
return this.createTextureData(layout, dataType, data, tensor, EncoderUsage.UploadOnly);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private createTextureData(
|
2024-08-14 23:51:22 +00:00
|
|
|
layout: TextureLayout,
|
|
|
|
|
dataType: Tensor.DataType,
|
|
|
|
|
data?: Tensor.NumberType,
|
|
|
|
|
tensor?: Tensor,
|
|
|
|
|
usage?: EncoderUsage,
|
|
|
|
|
): TextureData {
|
2021-04-27 07:04:25 +00:00
|
|
|
Logger.verbose('InferenceHandler', `Creating TextureData: layout:[${JSON.stringify(layout)}]`);
|
|
|
|
|
const texture = this.session.textureManager.createTextureFromLayout(dataType, layout, data, usage);
|
|
|
|
|
return this.createTextureDataFromTexture(layout, dataType, texture, tensor);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-12 19:30:49 +00:00
|
|
|
reshapeUnpacked(input: Tensor, reshapedDims: readonly number[]): Tensor {
|
|
|
|
|
const inputTD = this.getOrCreateTextureData(input, TextureType.unpacked);
|
|
|
|
|
const newTextureLayout: TextureLayout = {
|
|
|
|
|
channels: inputTD.channels,
|
|
|
|
|
height: inputTD.height,
|
|
|
|
|
width: inputTD.width,
|
|
|
|
|
// handle reshaping into scalar Tensors
|
|
|
|
|
shape: reshapedDims.length !== 0 ? reshapedDims : [1],
|
|
|
|
|
strides: ShapeUtil.computeStrides(reshapedDims),
|
|
|
|
|
unpackedShape: reshapedDims,
|
|
|
|
|
};
|
|
|
|
|
const newTextureData = this.createTextureDataFromTexture(newTextureLayout, input.type, inputTD.texture);
|
|
|
|
|
return newTextureData.tensor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reshapePacked(input: Tensor, reshapedDims: readonly number[]): Tensor {
|
|
|
|
|
const inputTD = this.getOrCreateTextureData(input, TextureType.packed);
|
|
|
|
|
|
|
|
|
|
// check if the reshape is 'cheap'
|
|
|
|
|
if (isReshapeCheap(input.dims, reshapedDims)) {
|
|
|
|
|
const newTextureLayout: TextureLayout = {
|
|
|
|
|
channels: inputTD.channels,
|
|
|
|
|
height: inputTD.height,
|
|
|
|
|
width: inputTD.width,
|
|
|
|
|
// handle reshaping into scalar Tensors
|
|
|
|
|
shape: reshapedDims.length !== 0 ? reshapedDims : [1],
|
|
|
|
|
strides: ShapeUtil.computeStrides(reshapedDims),
|
|
|
|
|
unpackedShape: reshapedDims,
|
2024-08-14 23:51:22 +00:00
|
|
|
isPacked: true,
|
2021-08-12 19:30:49 +00:00
|
|
|
};
|
|
|
|
|
const newTextureData = this.createTextureDataFromTexture(newTextureLayout, input.type, inputTD.texture);
|
|
|
|
|
return newTextureData.tensor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const squeezedInputShape = processDims3D(input.dims);
|
|
|
|
|
const squeezedOutputShape = processDims3D(reshapedDims);
|
|
|
|
|
|
|
|
|
|
const squeezedInputTensor = this.reshapePacked(input, squeezedInputShape);
|
|
|
|
|
const squeezedOutputTensor = this.run(
|
2024-08-14 23:51:22 +00:00
|
|
|
createPackedReshape3DProgramInfoLoader(this, squeezedInputTensor, squeezedOutputShape),
|
|
|
|
|
[squeezedInputTensor],
|
|
|
|
|
);
|
2021-08-12 19:30:49 +00:00
|
|
|
const outputTensor = this.reshapePacked(squeezedOutputTensor, reshapedDims);
|
|
|
|
|
return outputTensor;
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
2021-10-14 23:29:37 +00:00
|
|
|
cast(input: Tensor, type: Tensor.DataType): Tensor {
|
|
|
|
|
const inputTD = this.getOrCreateTextureData(input, TextureType.unpacked);
|
|
|
|
|
const newTextureData = this.createTextureDataFromTexture(inputTD as TextureLayout, type, inputTD.texture);
|
|
|
|
|
return newTextureData.tensor;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 07:04:25 +00:00
|
|
|
private createTextureDataFromTexture(
|
2024-08-14 23:51:22 +00:00
|
|
|
layout: TextureLayout,
|
|
|
|
|
dataType: Tensor.DataType,
|
|
|
|
|
texture: WebGLTexture,
|
|
|
|
|
tensor?: Tensor,
|
|
|
|
|
tensorId?: Tensor.Id,
|
|
|
|
|
) {
|
2021-04-27 07:04:25 +00:00
|
|
|
const textureData: TextureData = {
|
|
|
|
|
...layout,
|
2024-08-14 23:51:22 +00:00
|
|
|
tensor:
|
|
|
|
|
tensor ||
|
|
|
|
|
new Tensor(
|
|
|
|
|
layout.unpackedShape,
|
|
|
|
|
dataType,
|
|
|
|
|
(_id: Tensor.Id) => this.readTexture(textureData),
|
|
|
|
|
async (_id: Tensor.Id) => this.readTextureAsync(textureData),
|
|
|
|
|
undefined,
|
|
|
|
|
tensorId,
|
|
|
|
|
),
|
|
|
|
|
texture,
|
2021-04-27 07:04:25 +00:00
|
|
|
};
|
2021-05-03 22:03:25 +00:00
|
|
|
this.setTextureData(textureData.tensor.dataId, textureData, layout.isPacked);
|
2021-04-27 07:04:25 +00:00
|
|
|
return textureData;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-14 23:51:22 +00:00
|
|
|
private getTextureData(tensorId: Tensor.Id, isPacked = false): TextureData | undefined {
|
|
|
|
|
return this.session.isInitializer(tensorId)
|
|
|
|
|
? this.session.getTextureData(tensorId, isPacked)
|
|
|
|
|
: isPacked
|
|
|
|
|
? this.packedTextureDataCache.get(tensorId)
|
|
|
|
|
: this.unpackedTextureDataCache.get(tensorId);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
2021-05-03 22:03:25 +00:00
|
|
|
setTextureData(tensorId: Tensor.Id, td: TextureData, isPacked = false): void {
|
2021-04-27 07:04:25 +00:00
|
|
|
if (this.session.isInitializer(tensorId)) {
|
2021-05-03 22:03:25 +00:00
|
|
|
this.session.setTextureData(tensorId, td, isPacked);
|
2021-04-27 07:04:25 +00:00
|
|
|
} else {
|
2021-05-03 22:03:25 +00:00
|
|
|
(isPacked ? this.packedTextureDataCache : this.unpackedTextureDataCache).set(tensorId, td);
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-05-03 22:03:25 +00:00
|
|
|
isTextureLayoutCached(tensor: Tensor, isPacked = false): boolean {
|
|
|
|
|
return !!this.getTextureData(tensor.dataId, isPacked);
|
|
|
|
|
}
|
2021-04-27 07:04:25 +00:00
|
|
|
|
|
|
|
|
dispose(): void {
|
|
|
|
|
this.session.textureManager.clearActiveTextures();
|
2024-08-14 23:51:22 +00:00
|
|
|
this.packedTextureDataCache.forEach((td) => this.session.textureManager.releaseTexture(td));
|
2021-05-03 22:03:25 +00:00
|
|
|
this.packedTextureDataCache = new Map();
|
2024-08-14 23:51:22 +00:00
|
|
|
this.unpackedTextureDataCache.forEach((td) => this.session.textureManager.releaseTexture(td));
|
2021-05-03 22:03:25 +00:00
|
|
|
this.unpackedTextureDataCache = new Map();
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
readTexture(textureData: TextureData): Tensor.NumberType {
|
|
|
|
|
if (textureData.isPacked) {
|
|
|
|
|
return this.readTexture(this.unpack(textureData));
|
|
|
|
|
}
|
|
|
|
|
if (!this.session.backend.glContext.isFloat32DownloadSupported) {
|
2021-08-12 19:30:49 +00:00
|
|
|
return this.session.textureManager.readUint8TextureAsFloat(encodeAsUint8(this, textureData));
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
return this.session.textureManager.readTexture(textureData, textureData.tensor.type, textureData.channels);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-10 05:17:42 +00:00
|
|
|
async readTextureAsync(textureData: TextureData): Promise<Tensor.NumberType> {
|
|
|
|
|
if (textureData.isPacked) {
|
|
|
|
|
return this.readTextureAsync(this.unpack(textureData));
|
|
|
|
|
}
|
|
|
|
|
if (!this.session.backend.glContext.isFloat32DownloadSupported) {
|
|
|
|
|
return this.session.textureManager.readUint8TextureAsFloat(encodeAsUint8(this, textureData));
|
|
|
|
|
}
|
|
|
|
|
return this.session.textureManager.readTextureAsync(textureData, textureData.tensor.type, textureData.channels);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 07:04:25 +00:00
|
|
|
pack(input: TextureData): TextureData {
|
2021-08-12 19:30:49 +00:00
|
|
|
const outputTextureData = this.executeProgram(createPackProgramInfoLoader(this, input.tensor), [input.tensor]);
|
|
|
|
|
return outputTextureData;
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unpack(input: TextureData): TextureData {
|
2021-08-12 19:30:49 +00:00
|
|
|
const outputTextureData = this.executeProgram(createUnpackProgramInfoLoader(this, input.tensor), [input.tensor]);
|
|
|
|
|
return outputTextureData;
|
2021-04-27 07:04:25 +00:00
|
|
|
}
|
|
|
|
|
}
|