/** * @license * Copyright 2017 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================================= */ import { env } from '@tensorflow/tfjs-core'; import { getInternalFormatForFloat16MatrixTexture, getInternalFormatForFloat16PackedMatrixTexture, getInternalFormatForFloat32MatrixTexture, getInternalFormatForPackedMatrixTexture, getInternalFormatForUnsignedBytesMatrixTexture } from './gpgpu_util'; import { getPackedMatrixTextureShapeWidthHeight, getUnpackedMatrixTextureShapeWidthHeight, PhysicalTextureType, TextureUsage } from './tex_util'; export class TextureManager { constructor(gpgpu) { this.gpgpu = gpgpu; this.numUsedTextures = 0; this.numFreeTextures = 0; this._numBytesAllocated = 0; // Number of bytes that have been allocated and available for reuse. this._numBytesFree = 0; this.freeTextures = {}; this.usedTextures = {}; this.logEnabled = false; } acquireTexture(shapeRC, usage, isPacked) { const physicalTexType = getPhysicalFromLogicalTextureType(usage, isPacked); const shapeKey = getKeyFromTextureShape(shapeRC, physicalTexType, isPacked); if (!(shapeKey in this.freeTextures)) { this.freeTextures[shapeKey] = []; } if (!(shapeKey in this.usedTextures)) { this.usedTextures[shapeKey] = []; } const texBytes = computeBytes(shapeRC, physicalTexType, this.gpgpu.gl, this.gpgpu.textureConfig, isPacked); if (this.freeTextures[shapeKey].length > 0) { this.numFreeTextures--; this.numUsedTextures++; this._numBytesFree -= texBytes; this.log(); const newTexture = this.freeTextures[shapeKey].pop(); this.usedTextures[shapeKey].push(newTexture); return newTexture; } let newTexture; if (physicalTexType === PhysicalTextureType.PACKED_2X2_FLOAT32) { newTexture = this.gpgpu.createPackedMatrixTexture(shapeRC[0], shapeRC[1]); } else if (physicalTexType === PhysicalTextureType.PACKED_2X2_FLOAT16) { newTexture = this.gpgpu.createFloat16PackedMatrixTexture(shapeRC[0], shapeRC[1]); } else if (physicalTexType === PhysicalTextureType.UNPACKED_FLOAT32) { newTexture = this.gpgpu.createFloat32MatrixTexture(shapeRC[0], shapeRC[1]); } else if (physicalTexType === PhysicalTextureType.UNPACKED_FLOAT16) { newTexture = this.gpgpu.createFloat16MatrixTexture(shapeRC[0], shapeRC[1]); } else if (physicalTexType === PhysicalTextureType.PACKED_4X1_UNSIGNED_BYTE) { newTexture = this.gpgpu.createUnsignedBytesMatrixTexture(shapeRC[0], shapeRC[1]); } this.usedTextures[shapeKey].push(newTexture); this.numUsedTextures++; this._numBytesAllocated += texBytes; this.log(); return newTexture; } releaseTexture(texture, shape, logicalTexType, isPacked) { if (this.freeTextures == null) { // Already disposed. return; } const physicalTexType = getPhysicalFromLogicalTextureType(logicalTexType, isPacked); const shapeKey = getKeyFromTextureShape(shape, physicalTexType, isPacked); if (!(shapeKey in this.freeTextures)) { this.freeTextures[shapeKey] = []; } const texBytes = computeBytes(shape, physicalTexType, this.gpgpu.gl, this.gpgpu.textureConfig, isPacked); const deleteTexThreshold = env() .getNumber('WEBGL_DELETE_TEXTURE_THRESHOLD'); if (deleteTexThreshold !== -1 && this._numBytesAllocated > deleteTexThreshold) { this.gpgpu.deleteMatrixTexture(texture.texture); this._numBytesAllocated -= texBytes; } else { this.freeTextures[shapeKey].push(texture); this.numFreeTextures++; this._numBytesFree += texBytes; } this.numUsedTextures--; const texList = this.usedTextures[shapeKey]; const texIndex = texList && texList.indexOf(texture); if (texIndex == null || texIndex < 0) { throw new Error('Cannot release a texture that was never provided by this ' + 'texture manager'); } texList[texIndex] = texList[texList.length - 1]; texList.pop(); this.log(); } log() { if (!this.logEnabled) { return; } const total = this.numFreeTextures + this.numUsedTextures; console.log('Free/Used', `${this.numFreeTextures} / ${this.numUsedTextures}`, `(${total})`); const freeRatio = this._numBytesFree / this._numBytesAllocated; console.log(`Bytes allocated: ${this._numBytesAllocated}`); console.log(`Bytes unused: ${this._numBytesFree} (${Math.round(100 * freeRatio)}%)`); } get numBytesAllocated() { return this._numBytesAllocated; } get numBytesFree() { return this._numBytesFree; } getNumUsedTextures() { return this.numUsedTextures; } getNumFreeTextures() { return this.numFreeTextures; } dispose() { if (this.freeTextures == null) { // Already disposed. return; } for (const texShape in this.freeTextures) { this.freeTextures[texShape].forEach(tex => { this.gpgpu.deleteMatrixTexture(tex.texture); }); } for (const texShape in this.usedTextures) { this.usedTextures[texShape].forEach(tex => { this.gpgpu.deleteMatrixTexture(tex.texture); }); } // TODO: Assign non-null value (empty object) to textures after disposed. this.freeTextures = null; this.usedTextures = null; this.numUsedTextures = 0; this.numFreeTextures = 0; this._numBytesAllocated = 0; this._numBytesFree = 0; } } function numBytesForInternalFormat(gl, internalFormat) { // tslint:disable-next-line:no-any const glany = gl; if (internalFormat === glany.R32F) { return 4; } else if (internalFormat === glany.R16F) { return 2; } else if (internalFormat === glany.RGBA32F) { return 16; } else if (internalFormat === gl.RGBA) { return 16; } else if (internalFormat === glany.RGBA16F) { return 8; } else if (internalFormat === glany.RGBA8) { return 4; } throw new Error(`Unknown internal format ${internalFormat}`); } export function computeBytes(shape, physicalTexType, gl, textureConfig, isPacked) { // It is not possible to infer packed status from the texture type because // depending on the textureConfig, different texture types may resolve to the // same internal format (e.g. in WebGL1, the internal format for // UNPACKED_FLOAT16 textures is gl.RGBA). Therefore we pass in `isPacked` // explicitly. const internalFormat = internalFormatForPhysicalTexType(physicalTexType, textureConfig); let numElements; if (isPacked) { const [packedWidth, packedHeight] = getPackedMatrixTextureShapeWidthHeight(shape[0], shape[1]); numElements = packedWidth * packedHeight; } else { const [width, height] = getUnpackedMatrixTextureShapeWidthHeight(shape[0], shape[1]); numElements = width * height; } const bytesPerElement = numBytesForInternalFormat(gl, internalFormat); return numElements * bytesPerElement; } function internalFormatForPhysicalTexType(physicalTexType, textureConfig) { switch (physicalTexType) { case PhysicalTextureType.PACKED_2X2_FLOAT32: return getInternalFormatForPackedMatrixTexture(textureConfig); case PhysicalTextureType.PACKED_2X2_FLOAT16: return getInternalFormatForFloat16PackedMatrixTexture(textureConfig); case PhysicalTextureType.UNPACKED_FLOAT32: return getInternalFormatForFloat32MatrixTexture(textureConfig); case PhysicalTextureType.UNPACKED_FLOAT16: return getInternalFormatForFloat16MatrixTexture(textureConfig); case PhysicalTextureType.PACKED_4X1_UNSIGNED_BYTE: return getInternalFormatForUnsignedBytesMatrixTexture(textureConfig); default: throw new Error(`Unknown physical texture type ${physicalTexType}`); } } function getPhysicalTextureForRendering(isPacked) { if (env().getBool('WEBGL_RENDER_FLOAT32_ENABLED')) { if (isPacked) { return PhysicalTextureType.PACKED_2X2_FLOAT32; } return PhysicalTextureType.UNPACKED_FLOAT32; } if (isPacked) { return PhysicalTextureType.PACKED_2X2_FLOAT16; } return PhysicalTextureType.UNPACKED_FLOAT16; } function getPhysicalFromLogicalTextureType(logicalTexType, isPacked) { if (logicalTexType === TextureUsage.UPLOAD) { return PhysicalTextureType.PACKED_2X2_FLOAT32; } else if (logicalTexType === TextureUsage.RENDER || logicalTexType == null) { return getPhysicalTextureForRendering(isPacked); } else if (logicalTexType === TextureUsage.DOWNLOAD || logicalTexType === TextureUsage.PIXELS) { return PhysicalTextureType.PACKED_4X1_UNSIGNED_BYTE; } throw new Error(`Unknown logical texture type ${logicalTexType}`); } function getKeyFromTextureShape(shapeRowsCol, physicalTexType, isPacked) { return `${shapeRowsCol[0]}_${shapeRowsCol[1]}_${physicalTexType}_${isPacked}`; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGV4dHVyZV9tYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vdGZqcy1iYWNrZW5kLXdlYmdsL3NyYy90ZXh0dXJlX21hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBRUgsT0FBTyxFQUFDLEdBQUcsRUFBQyxNQUFNLHVCQUF1QixDQUFDO0FBRzFDLE9BQU8sRUFBQyx3Q0FBd0MsRUFBRSw4Q0FBOEMsRUFBRSx3Q0FBd0MsRUFBRSx1Q0FBdUMsRUFBRSw4Q0FBOEMsRUFBQyxNQUFNLGNBQWMsQ0FBQztBQUN6UCxPQUFPLEVBQUMsc0NBQXNDLEVBQUUsd0NBQXdDLEVBQUUsbUJBQW1CLEVBQTBCLFlBQVksRUFBQyxNQUFNLFlBQVksQ0FBQztBQUV2SyxNQUFNLE9BQU8sY0FBYztJQVV6QixZQUE2QixLQUFtQjtRQUFuQixVQUFLLEdBQUwsS0FBSyxDQUFjO1FBVHhDLG9CQUFlLEdBQUcsQ0FBQyxDQUFDO1FBQ3BCLG9CQUFlLEdBQUcsQ0FBQyxDQUFDO1FBQ3BCLHVCQUFrQixHQUFHLENBQUMsQ0FBQztRQUMvQixvRUFBb0U7UUFDNUQsa0JBQWEsR0FBRyxDQUFDLENBQUM7UUFDbEIsaUJBQVksR0FBOEIsRUFBRSxDQUFDO1FBQzdDLGlCQUFZLEdBQThCLEVBQUUsQ0FBQztRQUM3QyxlQUFVLEdBQUcsS0FBSyxDQUFDO0lBRXdCLENBQUM7SUFFcEQsY0FBYyxDQUNWLE9BQXlCLEVBQUUsS0FBbUIsRUFDOUMsUUFBaUI7UUFDbkIsTUFBTSxlQUFlLEdBQUcsaUNBQWlDLENBQUMsS0FBSyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRTNFLE1BQU0sUUFBUSxHQUFHLHNCQUFzQixDQUFDLE9BQU8sRUFBRSxlQUFlLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDNUUsSUFBSSxDQUFDLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsRUFBRTtZQUNwQyxJ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QUM5QyxDQUFDO0FBRUQsU0FBUyxpQ0FBaUMsQ0FDdEMsY0FBNEIsRUFBRSxRQUFpQjtJQUNqRCxJQUFJLGNBQWMsS0FBSyxZQUFZLENBQUMsTUFBTSxFQUFFO1FBQzFDLE9BQU8sbUJBQW1CLENBQUMsa0JBQWtCLENBQUM7S0FDL0M7U0FBTSxJQUFJLGNBQWMsS0FBSyxZQUFZLENBQUMsTUFBTSxJQUFJLGNBQWMsSUFBSSxJQUFJLEVBQUU7UUFDM0UsT0FBTyw4QkFBOEIsQ0FBQyxRQUFRLENBQUMsQ0FBQztLQUNqRDtTQUFNLElBQ0gsY0FBYyxLQUFLLFlBQVksQ0FBQyxRQUFRO1FBQ3hDLGNBQWMsS0FBSyxZQUFZLENBQUMsTUFBTSxFQUFFO1FBQzFDLE9BQU8sbUJBQW1CLENBQUMsd0JBQXdCLENBQUM7S0FDckQ7SUFDRCxNQUFNLElBQUksS0FBSyxDQUFDLGdDQUFnQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO0FBQ3BFLENBQUM7QUFFRCxTQUFTLHNCQUFzQixDQUMzQixZQUE4QixFQUFFLGVBQW9DLEVBQ3BFLFFBQWlCO0lBQ25CLE9BQU8sR0FBRyxZQUFZLENBQUMsQ0FBQyxDQUFDLElBQUksWUFBWSxDQUFDLENBQUMsQ0FBQyxJQUFJLGVBQWUsSUFBSSxRQUFRLEVBQUUsQ0FBQztBQUNoRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IDIwMTcgR29vZ2xlIExMQy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSBcIkxpY2Vuc2VcIik7XG4gKiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuXG4gKiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXRcbiAqXG4gKiBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjBcbiAqXG4gKiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlXG4gKiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiBcIkFTIElTXCIgQkFTSVMsXG4gKiBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC5cbiAqIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmRcbiAqIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLlxuICogPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiAqL1xuXG5pbXBvcnQge2Vudn0gZnJvbSAnQHRlbnNvcmZsb3cvdGZqcy1jb3JlJztcblxuaW1wb3J0IHtHUEdQVUNvbnRleHR9IGZyb20gJy4vZ3BncHVfY29udGV4dCc7XG5pbXBvcnQge2dldEludGVybmFsRm9ybWF0Rm9yRmxvYXQxNk1hdHJpeFRleHR1cmUsIGdldEludGVybmFsRm9ybWF0Rm9yRmxvYXQxNlBhY2tlZE1hdHJpeFRleHR1cmUsIGdldEludGVybmFsRm9ybWF0Rm9yRmxvYXQzMk1hdHJpeFRleHR1cmUsIGdldEludGVybmFsRm9ybWF0Rm9yUGFja2VkTWF0cml4VGV4dHVyZSwgZ2V0SW50ZXJuYWxGb3JtYXRGb3JVbnNpZ25lZEJ5dGVzTWF0cml4VGV4dHVyZX0gZnJvbSAnLi9ncGdwdV91dGlsJztcbmltcG9ydCB7Z2V0UGFja2VkTWF0cml4VGV4dHVyZVNoYXBlV2lkdGhIZWlnaHQsIGdldFVucGFja2VkTWF0cml4VGV4dHVyZVNoYXBlV2lkdGhIZWlnaHQsIFBoeXNpY2FsVGV4dHVyZVR5cGUsIFRleHR1cmUsIFRleHR1cmVDb25maWcsIFRleHR1cmVVc2FnZX0gZnJvbSAnLi90ZXhfdXRpbCc7XG5cbmV4cG9ydCBjbGFzcyBUZXh0dXJlTWFuYWdlciB7XG4gIHByaXZhdGUgbnVtVXNlZFRleHR1cmVzID0gMDtcbiAgcHJpdmF0ZSBudW1GcmVlVGV4dHVyZXMgPSAwO1xuICBwcml2YXRlIF9udW1CeXRlc0FsbG9jYXRlZCA9IDA7XG4gIC8vIE51bWJlciBvZiBieXRlcyB0aGF0IGhhdmUgYmVlbiBhbGxvY2F0ZWQgYW5kIGF2YWlsYWJsZSBmb3IgcmV1c2UuXG4gIHByaXZhdGUgX251bUJ5dGVzRnJlZSA9IDA7XG4gIHByaXZhdGUgZnJlZVRleHR1cmVzOiBSZWNvcmQ8c3RyaW5nLCBUZXh0dXJlW10+ID0ge307XG4gIHByaXZhdGUgdXNlZFRleHR1cmVzOiBSZWNvcmQ8c3RyaW5nLCBUZXh0dXJlW10+ID0ge307XG4gIHByaXZhdGUgbG9nRW5hYmxlZCA9IGZhbHNlO1xuXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgcmVhZG9ubHkgZ3BncHU6IEdQR1BVQ29udGV4dCkge31cblxuICBhY3F1aXJlVGV4dHVyZShcbiAgICAgIHNoYXBlUkM6IFtudW1iZXIsIG51bWJlcl0sIHVzYWdlOiBUZXh0dXJlVXNhZ2UsXG4gICAgICBpc1BhY2tlZDogYm9vbGVhbik6IFRleHR1cmUge1xuICAgIGNvbnN0IHBoeXNpY2FsVGV4VHlwZSA9IGdldFBoeXNpY2FsRnJvbUxvZ2ljYWxUZXh0dXJlVHlwZSh1c2FnZSwgaXNQYWNrZWQpO1xuXG4gICAgY29uc3Qgc2hhcGVLZXkgPSBnZXRLZXlGcm9tVGV4dHVyZVNoYXBlKHNoYXBlUkMsIHBoeXNpY2FsVGV4VHlwZSwgaXNQYWNrZWQpO1xuICAgIGlmICghKHNoYXBlS2V5IGluIHRoaXMuZnJlZVRleHR1cmVzKSkge1xuICAgICAgdGhpcy5mcmVlVGV4dHVyZXNbc2hhcGVLZXldID0gW107XG4gICAgfVxuICAgIGlmICghKHNoYXBlS2V5IGluIHRoaXMudXNlZFRleHR1cmVzKSkge1xuICAgICAgdGhpcy51c2VkVGV4dHVyZXNbc2hhcGVLZXldID0gW107XG4gICAgfVxuXG4gICAgY29uc3QgdGV4Qnl0ZXMgPSBjb21wdXRlQnl0ZXMoXG4gICAgICAgIHNoYXBlUkMsIHBoeXNpY2FsVGV4VHlwZSwgdGhpcy5ncGdwdS5nbCwgdGhpcy5ncGdwdS50ZXh0dXJlQ29uZmlnLFxuICAgICAgICBpc1BhY2tlZCk7XG5cbiAgICBpZiAodGhpcy5mcmVlVGV4dHVyZXNbc2hhcGVLZXldLmxlbmd0aCA+IDApIHtcbiAgICAgIHRoaXMubnVtRnJlZVRleHR1cmVzLS07XG4gICAgICB0aGlzLm51bVVzZWRUZXh0dXJlcysrO1xuICAgICAgdGhpcy5fbnVtQnl0ZXNGcmVlIC09IHRleEJ5dGVzO1xuICAgICAgdGhpcy5sb2coKTtcbiAgICAgIGNvbnN0IG5ld1RleHR1cmUgPSB0aGlzLmZyZWVUZXh0dXJlc1tzaGFwZUtleV0ucG9wKCk7XG4gICAgICB0aGlzLnVzZWRUZXh0dXJlc1tzaGFwZUtleV0ucHVzaChuZXdUZXh0dXJlKTtcbiAgICAgIHJldHVybiBuZXdUZXh0dXJlO1xuICAgIH1cblxuICAgIGxldCBuZXdUZXh0dXJlOiBUZXh0dXJlO1xuICAgIGlmIChwaHlzaWNhbFRleFR5cGUgPT09IFBoeXNpY2FsVGV4dHVyZVR5cGUuUEFDS0VEXzJYMl9GTE9BVDMyKSB7XG4gICAgICBuZXdUZXh0dXJlID0gdGhpcy5ncGdwdS5jcmVhdGVQYWNrZWRNYXRyaXhUZXh0dXJlKHNoYXBlUkNbMF0sIHNoYXBlUkNbMV0pO1xuICAgIH0gZWxzZSBpZiAocGh5c2ljYWxUZXhUeXBlID09PSBQaHlzaWNhbFRleHR1cmVUeXBlLlBBQ0tFRF8yWDJfRkxPQVQxNikge1xuICAgICAgbmV3VGV4dHVyZSA9XG4gICAgICAgICAgdGhpcy5ncGdwdS5jcmVhdGVGbG9hdDE2UGFja2VkTWF0cml4VGV4dHVyZShzaGFwZVJDWzBdLCBzaGFwZVJDWzFdKTtcbiAgICB9IGVsc2UgaWYgKHBoeXNpY2FsVGV4VHlwZSA9PT0gUGh5c2ljYWxUZXh0dXJlVHlwZS5VTlBBQ0tFRF9GTE9BVDMyKSB7XG4gICAgICBuZXdUZXh0dXJlID1cbiAgICAgICAgICB0aGlzLmdwZ3B1LmNyZWF0ZUZsb2F0MzJNYXRyaXhUZXh0dXJlKHNoYXBlUkNbMF0sIHNoYXBlUkNbMV0pO1xuICAgIH0gZWxzZSBpZiAocGh5c2ljYWxUZXhUeXBlID09PSBQaHlzaWNhbFRleHR1cmVUeXBlLlVOUEFDS0VEX0ZMT0FUMTYpIHtcbiAgICAgIG5ld1RleHR1cmUgPVxuICAgICAgICAgIHRoaXMuZ3BncHUuY3JlYXRlRmxvYXQxNk1hdHJpeFRleHR1cmUoc2hhcGVSQ1swXSwgc2hhcGVSQ1sxXSk7XG4gICAgfSBlbHNlIGlmIChcbiAgICAgICAgcGh5c2ljYWxUZXhUeXBlID09PSBQaHlzaWNhbFRleHR1cmVUeXBlLlBBQ0tFRF80WDFfVU5TSUdORURfQllURSkge1xuICAgICAgbmV3VGV4dHVyZSA9XG4gICAgICAgICAgdGhpcy5ncGdwdS5jcmVhdGVVbnNpZ25lZEJ5dGVzTWF0cml4VGV4dHVyZShzaGFwZVJDWzBdLCBzaGFwZVJDWzFdKTtcbiAgICB9XG4gICAgdGhpcy51c2VkVGV4dHVyZXNbc2hhcGVLZXldLnB1c2gobmV3VGV4dHVyZSk7XG5cbiAgICB0aGlzLm51bVVzZWRUZXh0dXJlcysrO1xuICAgIHRoaXMuX251bUJ5dGVzQWxsb2NhdGVkICs9IHRleEJ5dGVzO1xuICAgIHRoaXMubG9nKCk7XG5cbiAgICByZXR1cm4gbmV3VGV4dHVyZTtcbiAgfVxuXG4gIHJlbGVhc2VUZXh0dXJlKFxuICAgICAgdGV4dHVyZTogVGV4dHVyZSwgc2hhcGU6IFtudW1iZXIsIG51bWJlcl0sIGxvZ2ljYWxUZXhUeXBlOiBUZXh0dXJlVXNhZ2UsXG4gICAgICBpc1BhY2tlZDogYm9vbGVhbik6IHZvaWQge1xuICAgIGlmICh0aGlzLmZyZWVUZXh0dXJlcyA9PSBudWxsKSB7XG4gICAgICAvLyBBbHJlYWR5IGRpc3Bvc2VkLlxuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBwaHlzaWNhbFRleFR5cGUgPVxuICAgICAgICBnZXRQaHlzaWNhbEZyb21Mb2dpY2FsVGV4dHVyZVR5cGUobG9naWNhbFRleFR5cGUsIGlzUGFja2VkKTtcbiAgICBjb25zdCBzaGFwZUtleSA9IGdldEtleUZyb21UZXh0dXJlU2hhcGUoc2hhcGUsIHBoeXNpY2FsVGV4VHlwZSwgaXNQYWNrZWQpO1xuICAgIGlmICghKHNoYXBlS2V5IGluIHRoaXMuZnJlZVRleHR1cmVzKSkge1xuICAgICAgdGhpcy5mcmVlVGV4dHVyZXNbc2hhcGVLZXldID0gW107XG4gICAgfVxuXG4gICAgY29uc3QgdGV4Qnl0ZXMgPSBjb21wdXRlQnl0ZXMoXG4gICAgICAgIHNoYXBlLCBwaHlzaWNhbFRleFR5cGUsIHRoaXMuZ3BncHUuZ2wsIHRoaXMuZ3BncHUudGV4dHVyZUNvbmZpZyxcbiAgICAgICAgaXNQYWNrZWQpO1xuICAgIGNvbnN0IGRlbGV0ZVRleFRocmVzaG9sZCA9IGVudigpXG4gICAgICAgIC5nZXROdW1iZXIoJ1dFQkdMX0RFTEVURV9URVhUVVJFX1RIUkVTSE9MRCcpO1xuICAgIGlmIChkZWxldGVUZXhUaHJlc2hvbGQgIT09IC0xICYmXG4gICAgICAgIHRoaXMuX251bUJ5dGVzQWxsb2NhdGVkID4gZGVsZXRlVGV4VGhyZXNob2xkKSB7XG4gICAgICB0aGlzLmdwZ3B1LmRlbGV0ZU1hdHJpeFRleHR1cmUodGV4dHVyZS50ZXh0dXJlKTtcbiAgICAgIHRoaXMuX251bUJ5dGVzQWxsb2NhdGVkIC09IHRleEJ5dGVzO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmZyZWVUZXh0dXJlc1tzaGFwZUtleV0ucHVzaCh0ZXh0dXJlKTtcbiAgICAgIHRoaXMubnVtRnJlZVRleHR1cmVzKys7XG4gICAgICB0aGlzLl9udW1CeXRlc0ZyZWUgKz0gdGV4Qnl0ZXM7XG4gICAgfVxuXG4gICAgdGhpcy5udW1Vc2VkVGV4dHVyZXMtLTtcblxuICAgIGNvbnN0IHRleExpc3QgPSB0aGlzLnVzZWRUZXh0dXJlc1tzaGFwZUtleV07XG4gICAgY29uc3QgdGV4SW5kZXggPSB0ZXhMaXN0ICYmIHRleExpc3QuaW5kZXhPZih0ZXh0dXJlKTtcbiAgICBpZiAodGV4SW5kZXggPT0gbnVsbCB8fCB0ZXhJbmRleCA8IDApIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAnQ2Fubm90IHJlbGVhc2UgYSB0ZXh0dXJlIHRoYXQgd2FzIG5ldmVyIHByb3ZpZGVkIGJ5IHRoaXMgJyArXG4gICAgICAgICAgJ3RleHR1cmUgbWFuYWdlcicpO1xuICAgIH1cbiAgICB0ZXhMaXN0W3RleEluZGV4XSA9IHRleExpc3RbdGV4TGlzdC5sZW5ndGggLSAxXTtcbiAgICB0ZXhMaXN0LnBvcCgpO1xuICAgIHRoaXMubG9nKCk7XG4gIH1cblxuICBwcml2YXRlIGxvZygpIHtcbiAgICBpZiAoIXRoaXMubG9nRW5hYmxlZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCB0b3RhbCA9IHRoaXMubnVtRnJlZVRleHR1cmVzICsgdGhpcy5udW1Vc2VkVGV4dHVyZXM7XG4gICAgY29uc29sZS5sb2coXG4gICAgICAgICdGcmVlL1VzZWQnLCBgJHt0aGlzLm51bUZyZWVUZXh0dXJlc30gLyAke3RoaXMubnVtVXNlZFRleHR1cmVzfWAsXG4gICAgICAgIGAoJHt0b3RhbH0pYCk7XG4gICAgY29uc3QgZnJlZVJhdGlvID0gdGhpcy5fbnVtQnl0ZXNGcmVlIC8gdGhpcy5fbnVtQnl0ZXNBbGxvY2F0ZWQ7XG4gICAgY29uc29sZS5sb2coYEJ5dGVzIGFsbG9jYXRlZDogJHt0aGlzLl9udW1CeXRlc0FsbG9jYXRlZH1gKTtcbiAgICBjb25zb2xlLmxvZyhgQnl0ZXMgdW51c2VkOiAke3RoaXMuX251bUJ5dGVzRnJlZX0gKCR7XG4gICAgICAgIE1hdGgucm91bmQoMTAwICogZnJlZVJhdGlvKX0lKWApO1xuICB9XG5cbiAgZ2V0IG51bUJ5dGVzQWxsb2NhdGVkKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMuX251bUJ5dGVzQWxsb2NhdGVkO1xuICB9XG5cbiAgZ2V0IG51bUJ5dGVzRnJlZSgpOiBudW1iZXIge1xuICAgIHJldHVybiB0aGlzLl9udW1CeXRlc0ZyZWU7XG4gIH1cblxuICBnZXROdW1Vc2VkVGV4dHVyZXMoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gdGhpcy5udW1Vc2VkVGV4dHVyZXM7XG4gIH1cblxuICBnZXROdW1GcmVlVGV4dHVyZXMoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gdGhpcy5udW1GcmVlVGV4dHVyZXM7XG4gIH1cblxuICBkaXNwb3NlKCkge1xuICAgIGlmICh0aGlzLmZyZWVUZXh0dXJlcyA9PSBudWxsKSB7XG4gICAgICAvLyBBbHJlYWR5IGRpc3Bvc2VkLlxuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBmb3IgKGNvbnN0IHRleFNoYXBlIGluIHRoaXMuZnJlZVRleHR1cmVzKSB7XG4gICAgICB0aGlzLmZyZWVUZXh0dXJlc1t0ZXhTaGFwZV0uZm9yRWFjaCh0ZXggPT4ge1xuICAgICAgICB0aGlzLmdwZ3B1LmRlbGV0ZU1hdHJpeFRleHR1cmUodGV4LnRleHR1cmUpO1xuICAgICAgfSk7XG4gICAgfVxuICAgIGZvciAoY29uc3QgdGV4U2hhcGUgaW4gdGhpcy51c2VkVGV4dHVyZXMpIHtcbiAgICAgIHRoaXMudXNlZFRleHR1cmVzW3RleFNoYXBlXS5mb3JFYWNoKHRleCA9PiB7XG4gICAgICAgIHRoaXMuZ3BncHUuZGVsZXRlTWF0cml4VGV4dHVyZSh0ZXgudGV4dHVyZSk7XG4gICAgICB9KTtcbiAgICB9XG4gICAgLy8gVE9ETzogQXNzaWduIG5vbi1udWxsIHZhbHVlIChlbXB0eSBvYmplY3QpIHRvIHRleHR1cmVzIGFmdGVyIGRpc3Bvc2VkLlxuICAgIHRoaXMuZnJlZVRleHR1cmVzID0gbnVsbDtcbiAgICB0aGlzLnVzZWRUZXh0dXJlcyA9IG51bGw7XG4gICAgdGhpcy5udW1Vc2VkVGV4dHVyZXMgPSAwO1xuICAgIHRoaXMubnVtRnJlZVRleHR1cmVzID0gMDtcbiAgICB0aGlzLl9udW1CeXRlc0FsbG9jYXRlZCA9IDA7XG4gICAgdGhpcy5fbnVtQnl0ZXNGcmVlID0gMDtcbiAgfVxufVxuXG5mdW5jdGlvbiBudW1CeXRlc0ZvckludGVybmFsRm9ybWF0KFxuICAgIGdsOiBXZWJHTFJlbmRlcmluZ0NvbnRleHQsIGludGVybmFsRm9ybWF0OiBudW1iZXIpOiBudW1iZXIge1xuICAvLyB0c2xpbnQ6ZGlzYWJsZS1uZXh0LWxpbmU6bm8tYW55XG4gIGNvbnN0IGdsYW55ID0gZ2wgYXMgYW55O1xuICBpZiAoaW50ZXJuYWxGb3JtYXQgPT09IGdsYW55LlIzMkYpIHtcbiAgICByZXR1cm4gNDtcbiAgfSBlbHNlIGlmIChpbnRlcm5hbEZvcm1hdCA9PT0gZ2xhbnkuUjE2Rikge1xuICAgIHJldHVybiAyO1xuICB9IGVsc2UgaWYgKGludGVybmFsRm9ybWF0ID09PSBnbGFueS5SR0JBMzJGKSB7XG4gICAgcmV0dXJuIDE2O1xuICB9IGVsc2UgaWYgKGludGVybmFsRm9ybWF0ID09PSBnbC5SR0JBKSB7XG4gICAgcmV0dXJuIDE2O1xuICB9IGVsc2UgaWYgKGludGVybmFsRm9ybWF0ID09PSBnbGFueS5SR0JBMTZGKSB7XG4gICAgcmV0dXJuIDg7XG4gIH0gZWxzZSBpZiAoaW50ZXJuYWxGb3JtYXQgPT09IGdsYW55LlJHQkE4KSB7XG4gICAgcmV0dXJuIDQ7XG4gIH1cbiAgdGhyb3cgbmV3IEVycm9yKGBVbmtub3duIGludGVybmFsIGZvcm1hdCAke2ludGVybmFsRm9ybWF0fWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gY29tcHV0ZUJ5dGVzKFxuICAgIHNoYXBlOiBbbnVtYmVyLCBudW1iZXJdLCBwaHlzaWNhbFRleFR5cGU6IFBoeXNpY2FsVGV4dHVyZVR5cGUsXG4gICAgZ2w6IFdlYkdMUmVuZGVyaW5nQ29udGV4dCwgdGV4dHVyZUNvbmZpZzogVGV4dHVyZUNvbmZpZyxcbiAgICBpc1BhY2tlZDogYm9vbGVhbik6IG51bWJlciB7XG4gIC8vIEl0IGlzIG5vdCBwb3NzaWJsZSB0byBpbmZlciBwYWNrZWQgc3RhdHVzIGZyb20gdGhlIHRleHR1cmUgdHlwZSBiZWNhdXNlXG4gIC8vIGRlcGVuZGluZyBvbiB0aGUgdGV4dHVyZUNvbmZpZywgZGlmZmVyZW50ICB0ZXh0dXJlIHR5cGVzIG1heSByZXNvbHZlIHRvIHRoZVxuICAvLyBzYW1lIGludGVybmFsIGZvcm1hdCAoZS5nLiBpbiBXZWJHTDEsIHRoZSBpbnRlcm5hbCBmb3JtYXQgZm9yXG4gIC8vIFVOUEFDS0VEX0ZMT0FUMTYgdGV4dHVyZXMgaXMgZ2wuUkdCQSkuIFRoZXJlZm9yZSB3ZSBwYXNzIGluIGBpc1BhY2tlZGBcbiAgLy8gZXhwbGljaXRseS5cbiAgY29uc3QgaW50ZXJuYWxGb3JtYXQgPVxuICAgICAgaW50ZXJuYWxGb3JtYXRGb3JQaHlzaWNhbFRleFR5cGUocGh5c2ljYWxUZXhUeXBlLCB0ZXh0dXJlQ29uZmlnKTtcblxuICBsZXQgbnVtRWxlbWVudHM6IG51bWJlcjtcbiAgaWYgKGlzUGFja2VkKSB7XG4gICAgY29uc3QgW3BhY2tlZFdpZHRoLCBwYWNrZWRIZWlnaHRdID1cbiAgICAgICAgZ2V0UGFja2VkTWF0cml4VGV4dHVyZVNoYXBlV2lkdGhIZWlnaHQoc2hhcGVbMF0sIHNoYXBlWzFdKTtcbiAgICBudW1FbGVtZW50cyA9IHBhY2tlZFdpZHRoICogcGFja2VkSGVpZ2h0O1xuXG4gIH0gZWxzZSB7XG4gICAgY29uc3QgW3dpZHRoLCBoZWlnaHRdID1cbiAgICAgICAgZ2V0VW5wYWNrZWRNYXRyaXhUZXh0dXJlU2hhcGVXaWR0aEhlaWdodChzaGFwZVswXSwgc2hhcGVbMV0pO1xuICAgIG51bUVsZW1lbnRzID0gd2lkdGggKiBoZWlnaHQ7XG4gIH1cblxuICBjb25zdCBieXRlc1BlckVsZW1lbnQgPSBudW1CeXRlc0ZvckludGVybmFsRm9ybWF0KGdsLCBpbnRlcm5hbEZvcm1hdCk7XG4gIHJldHVybiBudW1FbGVtZW50cyAqIGJ5dGVzUGVyRWxlbWVudDtcbn1cblxuZnVuY3Rpb24gaW50ZXJuYWxGb3JtYXRGb3JQaHlzaWNhbFRleFR5cGUoXG4gICAgcGh5c2ljYWxUZXhUeXBlOiBQaHlzaWNhbFRleHR1cmVUeXBlLFxuICAgIHRleHR1cmVDb25maWc6IFRleHR1cmVDb25maWcpOiBudW1iZXIge1xuICBzd2l0Y2ggKHBoeXNpY2FsVGV4VHlwZSkge1xuICAgIGNhc2UgUGh5c2ljYWxUZXh0dXJlVHlwZS5QQUNLRURfMlgyX0ZMT0FUMzI6XG4gICAgICByZXR1cm4gZ2V0SW50ZXJuYWxGb3JtYXRGb3JQYWNrZWRNYXRyaXhUZXh0dXJlKHRleHR1cmVDb25maWcpO1xuICAgIGNhc2UgUGh5c2ljYWxUZXh0dXJlVHlwZS5QQUNLRURfMlgyX0ZMT0FUMTY6XG4gICAgICByZXR1cm4gZ2V0SW50ZXJuYWxGb3JtYXRGb3JGbG9hdDE2UGFja2VkTWF0cml4VGV4dHVyZSh0ZXh0dXJlQ29uZmlnKTtcbiAgICBjYXNlIFBoeXNpY2FsVGV4dHVyZVR5cGUuVU5QQUNLRURfRkxPQVQzMjpcbiAgICAgIHJldHVybiBnZXRJbnRlcm5hbEZvcm1hdEZvckZsb2F0MzJNYXRyaXhUZXh0dXJlKHRleHR1cmVDb25maWcpO1xuICAgIGNhc2UgUGh5c2ljYWxUZXh0dXJlVHlwZS5VTlBBQ0tFRF9GTE9BVDE2OlxuICAgICAgcmV0dXJuIGdldEludGVybmFsRm9ybWF0Rm9yRmxvYXQxNk1hdHJpeFRleHR1cmUodGV4dHVyZUNvbmZpZyk7XG4gICAgY2FzZSBQaHlzaWNhbFRleHR1cmVUeXBlLlBBQ0tFRF80WDFfVU5TSUdORURfQllURTpcbiAgICAgIHJldHVybiBnZXRJbnRlcm5hbEZvcm1hdEZvclVuc2lnbmVkQnl0ZXNNYXRyaXhUZXh0dXJlKHRleHR1cmVDb25maWcpO1xuICAgIGRlZmF1bHQ6XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gcGh5c2ljYWwgdGV4dHVyZSB0eXBlICR7cGh5c2ljYWxUZXhUeXBlfWApO1xuICB9XG59XG5cbmZ1bmN0aW9uIGdldFBoeXNpY2FsVGV4dHVyZUZvclJlbmRlcmluZyhpc1BhY2tlZDogYm9vbGVhbik6XG4gICAgUGh5c2ljYWxUZXh0dXJlVHlwZSB7XG4gIGlmIChlbnYoKS5nZXRCb29sKCdXRUJHTF9SRU5ERVJfRkxPQVQzMl9FTkFCTEVEJykpIHtcbiAgICBpZiAoaXNQYWNrZWQpIHtcbiAgICAgIHJldHVybiBQaHlzaWNhbFRleHR1cmVUeXBlLlBBQ0tFRF8yWDJfRkxPQVQzMjtcbiAgICB9XG4gICAgcmV0dXJuIFBoeXNpY2FsVGV4dHVyZVR5cGUuVU5QQUNLRURfRkxPQVQzMjtcbiAgfVxuXG4gIGlmIChpc1BhY2tlZCkge1xuICAgIHJldHVybiBQaHlzaWNhbFRleHR1cmVUeXBlLlBBQ0tFRF8yWDJfRkxPQVQxNjtcbiAgfVxuICByZXR1cm4gUGh5c2ljYWxUZXh0dXJlVHlwZS5VTlBBQ0tFRF9GTE9BVDE2O1xufVxuXG5mdW5jdGlvbiBnZXRQaHlzaWNhbEZyb21Mb2dpY2FsVGV4dHVyZVR5cGUoXG4gICAgbG9naWNhbFRleFR5cGU6IFRleHR1cmVVc2FnZSwgaXNQYWNrZWQ6IGJvb2xlYW4pOiBQaHlzaWNhbFRleHR1cmVUeXBlIHtcbiAgaWYgKGxvZ2ljYWxUZXhUeXBlID09PSBUZXh0dXJlVXNhZ2UuVVBMT0FEKSB7XG4gICAgcmV0dXJuIFBoeXNpY2FsVGV4dHVyZVR5cGUuUEFDS0VEXzJYMl9GTE9BVDMyO1xuICB9IGVsc2UgaWYgKGxvZ2ljYWxUZXhUeXBlID09PSBUZXh0dXJlVXNhZ2UuUkVOREVSIHx8IGxvZ2ljYWxUZXhUeXBlID09IG51bGwpIHtcbiAgICByZXR1cm4gZ2V0UGh5c2ljYWxUZXh0dXJlRm9yUmVuZGVyaW5nKGlzUGFja2VkKTtcbiAgfSBlbHNlIGlmIChcbiAgICAgIGxvZ2ljYWxUZXhUeXBlID09PSBUZXh0dXJlVXNhZ2UuRE9XTkxPQUQgfHxcbiAgICAgIGxvZ2ljYWxUZXhUeXBlID09PSBUZXh0dXJlVXNhZ2UuUElYRUxTKSB7XG4gICAgcmV0dXJuIFBoeXNpY2FsVGV4dHVyZVR5cGUuUEFDS0VEXzRYMV9VTlNJR05FRF9CWVRFO1xuICB9XG4gIHRocm93IG5ldyBFcnJvcihgVW5rbm93biBsb2dpY2FsIHRleHR1cmUgdHlwZSAke2xvZ2ljYWxUZXhUeXBlfWApO1xufVxuXG5mdW5jdGlvbiBnZXRLZXlGcm9tVGV4dHVyZVNoYXBlKFxuICAgIHNoYXBlUm93c0NvbDogW251bWJlciwgbnVtYmVyXSwgcGh5c2ljYWxUZXhUeXBlOiBQaHlzaWNhbFRleHR1cmVUeXBlLFxuICAgIGlzUGFja2VkOiBib29sZWFuKTogc3RyaW5nIHtcbiAgcmV0dXJuIGAke3NoYXBlUm93c0NvbFswXX1fJHtzaGFwZVJvd3NDb2xbMV19XyR7cGh5c2ljYWxUZXhUeXBlfV8ke2lzUGFja2VkfWA7XG59XG4iXX0=