@itwin/core-frontend
Version:
iTwin.js frontend components
637 lines • 32.6 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module WebGL
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Texture2DDataUpdater = exports.TextureCubeHandle = exports.ExternalTextureLoader = exports.Texture2DHandle = exports.TextureHandle = exports.Texture = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const ImageUtil_1 = require("../../../common/ImageUtil");
const IModelApp_1 = require("../../../IModelApp");
const GL_1 = require("./GL");
const RenderFlags_1 = require("./RenderFlags");
const System_1 = require("./System");
function computeBytesUsed(width, height, format, dataType) {
const bytesPerComponent = GL_1.GL.Texture.DataType.UnsignedByte === dataType ? 1 : 4;
let componentsPerPixel = 1;
switch (format) {
case GL_1.GL.Texture.Format.Rgb:
componentsPerPixel = 3;
break;
case GL_1.GL.Texture.Format.Rgba:
componentsPerPixel = 4;
break;
}
return width * height * componentsPerPixel * bytesPerComponent;
}
/** Associate texture data with a WebGLTexture from a canvas, image, OR a bitmap. */
function loadTexture2DImageData(handle, params, bytes, source) {
handle.bytesUsed = undefined !== bytes ? bytes.byteLength : computeBytesUsed(params.width, params.height, params.format, params.dataType);
const tex = (0, core_bentley_1.expectDefined)(handle.getHandle());
const gl = System_1.System.instance.context;
// Use tightly packed data
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
// Bind the texture object; make sure we do not interfere with other active textures
System_1.System.instance.activateTexture2d(RenderFlags_1.TextureUnit.Zero, tex);
// Figure out the internal format. For all but WebGL2 float/half-float datatypes it is just same as format.
// TODO: probably need to just support internal format types in Texture2DCreateParams.
let internalFormat = params.format;
const context2 = System_1.System.instance.context;
if (GL_1.GL.Texture.Format.Rgba === params.format) {
if (GL_1.GL.Texture.DataType.Float === params.dataType)
internalFormat = context2.RGBA32F;
else if (context2.HALF_FLOAT === params.dataType)
internalFormat = context2.RGBA16F;
}
else if (GL_1.GL.Texture.Format.DepthStencil === params.format)
internalFormat = context2.DEPTH24_STENCIL8;
// send the texture data
if (undefined !== source) {
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, params.format, params.dataType, source);
}
else {
const pixelData = undefined !== bytes ? bytes : null;
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, params.width, params.height, 0, params.format, params.dataType, pixelData);
}
if (params.useMipMaps) {
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
}
else {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, params.interpolate ? gl.LINEAR : gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, params.interpolate ? gl.LINEAR : gl.NEAREST);
}
if (params.anisotropicFilter) {
System_1.System.instance.setMaxAnisotropy(params.anisotropicFilter);
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, params.wrapMode);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, params.wrapMode);
System_1.System.instance.bindTexture2d(RenderFlags_1.TextureUnit.Zero, undefined);
}
function loadTextureFromBytes(handle, params, bytes) {
loadTexture2DImageData(handle, params, bytes);
}
/** Associate cube texture data with a WebGLTexture from an image. */
function loadTextureCubeImageData(handle, params, images) {
handle.bytesUsed = computeBytesUsed(params.dim * 6, params.dim, params.format, params.dataType);
const tex = (0, core_bentley_1.expectDefined)(handle.getHandle());
const gl = System_1.System.instance.context;
// Use tightly packed data
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
// Bind the texture object; make sure we do not interfere with other active textures
System_1.System.instance.activateTextureCubeMap(RenderFlags_1.TextureUnit.Zero, tex);
const cubeTargets = [GL_1.GL.Texture.Target.CubeMapPositiveX, GL_1.GL.Texture.Target.CubeMapNegativeX, GL_1.GL.Texture.Target.CubeMapPositiveY, GL_1.GL.Texture.Target.CubeMapNegativeY, GL_1.GL.Texture.Target.CubeMapPositiveZ, GL_1.GL.Texture.Target.CubeMapNegativeZ];
for (let i = 0; i < 6; i++) {
gl.texImage2D(cubeTargets[i], 0, params.format, params.format, params.dataType, images[i]);
}
gl.texParameteri(GL_1.GL.Texture.Target.CubeMap, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(GL_1.GL.Texture.Target.CubeMap, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(GL_1.GL.Texture.Target.CubeMap, gl.TEXTURE_WRAP_S, params.wrapMode);
gl.texParameteri(GL_1.GL.Texture.Target.CubeMap, gl.TEXTURE_WRAP_T, params.wrapMode);
// gl.texParameteri(GL.Texture.Target.CubeMap, gl.TEXTURE_WRAP_R, params.wrapMode); // Unavailable in GLES2
System_1.System.instance.bindTextureCubeMap(RenderFlags_1.TextureUnit.Zero, undefined);
}
/** Wrapper class for a WebGL texture handle and parameters specific to an individual texture.
* @internal
*/
class Texture extends core_common_1.RenderTexture {
texture;
ownership;
transparency;
get bytesUsed() { return this.texture.bytesUsed; }
get hasOwner() { return undefined !== this.ownership; }
get key() {
return typeof this.ownership !== "string" && typeof this.ownership?.key === "string" ? this.ownership.key : undefined;
}
constructor(params) {
super(params.type);
this.ownership = params.ownership;
this.texture = params.handle;
this.transparency = params.handle.format === GL_1.GL.Texture.Format.Rgba ? params.transparency : core_common_1.TextureTransparency.Opaque;
}
get isDisposed() { return this.texture.isDisposed; }
/** Free this object in the WebGL wrapper. */
dispose() {
(0, core_bentley_1.dispose)(this.texture);
}
}
exports.Texture = Texture;
function getDataType(data) {
return data instanceof Float32Array ? GL_1.GL.Texture.DataType.Float : GL_1.GL.Texture.DataType.UnsignedByte;
}
/** Parameters used internally to define how to create a texture for use with WebGL. */
class Texture2DCreateParams {
width;
height;
format;
dataType;
wrapMode;
loadImageData;
useMipMaps;
interpolate;
anisotropicFilter;
dataBytes;
constructor(width, height, format, dataType, wrapMode, loadImageData, useMipMaps, interpolate, anisotropicFilter, dataBytes) {
this.width = width;
this.height = height;
this.format = format;
this.dataType = dataType;
this.wrapMode = wrapMode;
this.loadImageData = loadImageData;
this.useMipMaps = useMipMaps;
this.interpolate = interpolate;
this.anisotropicFilter = anisotropicFilter;
this.dataBytes = dataBytes;
}
static createForData(width, height, data, preserveData = false, wrapMode = GL_1.GL.Texture.WrapMode.ClampToEdge, format = GL_1.GL.Texture.Format.Rgba) {
const bytes = (preserveData && data instanceof Uint8Array) ? data : undefined;
return new Texture2DCreateParams(width, height, format, getDataType(data), wrapMode, (tex, params) => loadTextureFromBytes(tex, params, data), undefined, undefined, undefined, bytes);
}
static createForImageBuffer(image, type) {
const props = this.getImageProperties(type);
if (core_common_1.ImageBufferFormat.Rgb === image.format)
props.format = GL_1.GL.Texture.Format.Rgb;
return new Texture2DCreateParams(image.width, image.height, props.format, GL_1.GL.Texture.DataType.UnsignedByte, props.wrapMode, (tex, params) => loadTextureFromBytes(tex, params, image.data), props.useMipMaps, props.interpolate);
}
static createForAttachment(width, height, format, dataType) {
return new Texture2DCreateParams(width, height, format, dataType, GL_1.GL.Texture.WrapMode.ClampToEdge, (tex, params) => loadTextureFromBytes(tex, params), undefined, undefined);
}
static createForImage(image, type) {
const props = this.getImageProperties(type);
let targetWidth = image.naturalWidth;
let targetHeight = image.naturalHeight;
if (core_common_1.RenderTexture.Type.Glyph === type) {
targetWidth = (0, core_common_1.nextHighestPowerOfTwo)(targetWidth);
targetHeight = (0, core_common_1.nextHighestPowerOfTwo)(targetHeight);
}
else if (!System_1.System.instance.supportsNonPowerOf2Textures && (!(0, core_common_1.isPowerOfTwo)(targetWidth) || !(0, core_common_1.isPowerOfTwo)(targetHeight))) {
if (GL_1.GL.Texture.WrapMode.ClampToEdge === props.wrapMode) {
// NPOT are supported but not mipmaps
// Probably on poor hardware so I choose to disable mipmaps for lower memory usage over quality. If quality is required we need to resize the image to a pow of 2.
// Above comment is not necessarily true - WebGL doesn't support NPOT mipmapping, only supporting base NPOT caps
props.useMipMaps = undefined;
}
else if (GL_1.GL.Texture.WrapMode.Repeat === props.wrapMode) {
targetWidth = (0, core_common_1.nextHighestPowerOfTwo)(targetWidth);
targetHeight = (0, core_common_1.nextHighestPowerOfTwo)(targetHeight);
}
}
// Cap texture dimensions to system WebGL capabilities
const maxTexSize = System_1.System.instance.maxTextureSize;
targetWidth = Math.min(targetWidth, maxTexSize);
targetHeight = Math.min(targetHeight, maxTexSize);
let element = image;
if (targetWidth !== image.naturalWidth || targetHeight !== image.naturalHeight) {
// Resize so dimensions are powers-of-two
const canvas = document.createElement("canvas");
canvas.width = targetWidth;
canvas.height = targetHeight;
const context = (0, core_bentley_1.expectNotNull)(canvas.getContext("2d"));
context.drawImage(image, 0, 0, canvas.width, canvas.height);
element = canvas;
}
return new Texture2DCreateParams(targetWidth, targetHeight, props.format, GL_1.GL.Texture.DataType.UnsignedByte, props.wrapMode, (tex, params) => loadTexture2DImageData(tex, params, undefined, element), props.useMipMaps, props.interpolate, props.anisotropicFilter);
}
static createForImageBitmap(image, type) {
const props = this.getImageProperties(type);
let targetWidth = image.width;
let targetHeight = image.height;
if (core_common_1.RenderTexture.Type.Glyph === type) {
targetWidth = (0, core_common_1.nextHighestPowerOfTwo)(targetWidth);
targetHeight = (0, core_common_1.nextHighestPowerOfTwo)(targetHeight);
}
else if (!System_1.System.instance.supportsNonPowerOf2Textures && (!(0, core_common_1.isPowerOfTwo)(targetWidth) || !(0, core_common_1.isPowerOfTwo)(targetHeight))) {
if (GL_1.GL.Texture.WrapMode.ClampToEdge === props.wrapMode) {
// NPOT are supported but not mipmaps
// Probably on poor hardware so I choose to disable mipmaps for lower memory usage over quality. If quality is required we need to resize the image to a pow of 2.
// Above comment is not necessarily true - WebGL doesn't support NPOT mipmapping, only supporting base NPOT caps
props.useMipMaps = undefined;
}
else if (GL_1.GL.Texture.WrapMode.Repeat === props.wrapMode) {
targetWidth = (0, core_common_1.nextHighestPowerOfTwo)(targetWidth);
targetHeight = (0, core_common_1.nextHighestPowerOfTwo)(targetHeight);
}
}
// If we have to resize, use a canvas
let source = image;
if (image.width !== targetWidth || image.height !== targetHeight) {
const canvas = document.createElement("canvas");
canvas.width = targetWidth;
canvas.height = targetHeight;
const context = (0, core_bentley_1.expectNotNull)(canvas.getContext("2d"));
context.drawImage(image, 0, 0, canvas.width, canvas.height);
source = canvas;
}
return new Texture2DCreateParams(targetWidth, targetHeight, props.format, GL_1.GL.Texture.DataType.UnsignedByte, props.wrapMode, (tex, params) => loadTexture2DImageData(tex, params, undefined, source), props.useMipMaps, props.interpolate, props.anisotropicFilter);
}
static getImageProperties(type) {
const isSky = core_common_1.RenderTexture.Type.SkyBox === type;
const isTile = core_common_1.RenderTexture.Type.TileSection === type;
const isThematic = core_common_1.RenderTexture.Type.ThematicGradient === type;
const isFilteredTile = core_common_1.RenderTexture.Type.FilteredTileSection === type;
const maxAnisotropicFilterLevel = 16;
const wrapMode = core_common_1.RenderTexture.Type.Normal === type ? GL_1.GL.Texture.WrapMode.Repeat : GL_1.GL.Texture.WrapMode.ClampToEdge;
const useMipMaps = (!isSky && !isTile && !isFilteredTile && !isThematic) ? true : undefined;
const interpolate = isThematic ? undefined : true;
const anisotropicFilter = isFilteredTile ? maxAnisotropicFilterLevel : undefined;
// Always use RGBA. RGB is much slower and almost certainly does not actually save any GPU memory.
const format = GL_1.GL.Texture.Format.Rgba;
return { format, wrapMode, useMipMaps, interpolate, anisotropicFilter };
}
static placeholderParams = new Texture2DCreateParams(1, 1, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte, GL_1.GL.Texture.WrapMode.ClampToEdge, (_tex, _params) => undefined);
}
class TextureCubeCreateParams {
dim;
format;
dataType;
wrapMode;
loadImageData;
constructor(dim, format, dataType, wrapMode, loadImageData) {
this.dim = dim;
this.format = format;
this.dataType = dataType;
this.wrapMode = wrapMode;
this.loadImageData = loadImageData;
}
static createForCubeImages(posX, negX, posY, negY, posZ, negZ) {
const targetDim = posX.naturalWidth;
if (posX.naturalHeight !== targetDim) // Cube texture dimensions must match (width must equal height)
return undefined;
const images = [posX, negX, posY, negY, posZ, negZ];
for (let i = 1; i < images.length; i++) { // Dimensions of all six sides must match each other
if (images[i].naturalWidth !== targetDim || images[i].naturalHeight !== targetDim)
return undefined;
}
return new TextureCubeCreateParams(targetDim, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte, GL_1.GL.Texture.WrapMode.ClampToEdge, (tex, params) => loadTextureCubeImageData(tex, params, images));
}
}
/** Wraps a WebGLTextureHandle
* @internal
*/
class TextureHandle {
_glTexture;
_bytesUsed = 0;
get bytesUsed() { return this._bytesUsed; }
set bytesUsed(bytesUsed) {
// assert(0 === this.bytesUsed);
this._bytesUsed = bytesUsed;
}
/** Get the WebGLTexture for this TextureHandle. */
getHandle() { return this._glTexture; }
get isDisposed() { return this._glTexture === undefined; }
[Symbol.dispose]() {
if (!this.isDisposed) {
System_1.System.instance.disposeTexture((0, core_bentley_1.expectDefined)(this._glTexture));
this._glTexture = undefined;
this.bytesUsed = 0;
}
}
/** Create a 2D texture for use as a color attachment for rendering */
static createForAttachment(width, height, format, dataType) {
return Texture2DHandle.createForAttachment(width, height, format, dataType);
}
/** Create a 2D texture to hold non-image data */
static createForData(width, height, data, wantPreserveData = false, wrapMode = GL_1.GL.Texture.WrapMode.ClampToEdge, format = GL_1.GL.Texture.Format.Rgba) {
return Texture2DHandle.createForData(width, height, data, wantPreserveData, wrapMode, format);
}
/** Create a 2D texture from a bitmap */
static createForImageBuffer(image, type) {
return Texture2DHandle.createForImageBuffer(image, type);
}
/** Create a 2D texture from an HTMLImageElement. */
static createForImage(image, type) {
return Texture2DHandle.createForImage(image, type);
}
/** Create a 2D texture from an ImageBitmap. */
static createForImageBitmap(image, type) {
return Texture2DHandle.createForImageBitmap(image, type);
}
/** Create a cube map texture from six HTMLImageElement objects. */
static createForCubeImages(posX, negX, posY, negY, posZ, negZ) {
return TextureCubeHandle.createForCubeImages(posX, negX, posY, negY, posZ, negZ);
}
static createForElement(id, imodel, type, format, onLoaded) {
return Texture2DHandle.createForElement(id, imodel, type, format, onLoaded);
}
constructor(glTexture) {
this._glTexture = glTexture;
}
/** For debugging purposes, open a new window containing this texture as an image. */
showDebugImage() {
const gl = System_1.System.instance.context;
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, (0, core_bentley_1.expectDefined)(this.getHandle()), 0);
if (gl.FRAMEBUFFER_COMPLETE === gl.checkFramebufferStatus(gl.FRAMEBUFFER)) {
const w = this.width;
const h = this.height;
const pixels = new Uint8Array(w * h * 4);
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
const buffer = core_common_1.ImageBuffer.create(pixels, core_common_1.ImageBufferFormat.Rgba, w);
const url = (0, ImageUtil_1.imageBufferToPngDataUrl)(buffer, false);
(0, ImageUtil_1.openImageDataUrlInNewWindow)((0, core_bentley_1.expectDefined)(url), "Classifiers");
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.deleteFramebuffer(fbo);
}
}
exports.TextureHandle = TextureHandle;
/** @internal */
class Texture2DHandle extends TextureHandle {
_width;
_height;
_format;
_dataType;
_dataBytes;
get width() { return this._width; }
get height() { return this._height; }
get format() { return this._format; }
get dataType() { return this._dataType; }
get dataBytes() { return this._dataBytes; }
/** Bind specified texture handle to specified texture unit. */
static bindTexture(texUnit, glTex) {
(0, core_bentley_1.assert)(!(glTex instanceof TextureHandle));
System_1.System.instance.bindTexture2d(texUnit, glTex);
}
/** Bind the specified texture to a uniform sampler2D */
static bindSampler(uniform, tex, unit) {
(0, core_bentley_1.assert)(!(tex instanceof TextureHandle));
this.bindTexture(unit, tex);
uniform.setUniform1i(unit - RenderFlags_1.TextureUnit.Zero);
}
/** Bind texture handle (if available) associated with an instantiation of this class to specified texture unit. */
bind(texUnit) {
if (undefined === this._glTexture)
return false;
Texture2DHandle.bindTexture(texUnit, this._glTexture);
return true;
}
/** Bind this texture to a uniform sampler2D */
bindSampler(uniform, unit) {
if (undefined !== this._glTexture)
Texture2DHandle.bindSampler(uniform, this._glTexture, unit);
}
/** Update the 2D texture contents. */
update(updater) {
if (0 === this.width || 0 === this.height || undefined === this._dataBytes || 0 === this._dataBytes.length) {
(0, core_bentley_1.assert)(false);
return false;
}
if (!updater.modified)
return false;
return this.replaceTextureData(this._dataBytes);
}
/** Replace the 2D texture contents. */
replaceTextureData(data) {
(0, core_bentley_1.assert)((GL_1.GL.Texture.DataType.Float === this._dataType) === (data instanceof Float32Array));
const tex = (0, core_bentley_1.expectDefined)(this.getHandle());
if (undefined === tex)
return false;
const gl = System_1.System.instance.context;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
// Go through System to ensure we don't interfere with currently-bound textures!
System_1.System.instance.activateTexture2d(RenderFlags_1.TextureUnit.Zero, tex);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, this.height, this._format, this._dataType, data);
System_1.System.instance.bindTexture2d(RenderFlags_1.TextureUnit.Zero, undefined);
return true;
}
static create(params) {
const glTex = System_1.System.instance.context.createTexture();
return null !== glTex ? new Texture2DHandle(glTex, params) : undefined;
}
/** Create a texture for use as a color attachment for rendering */
static createForAttachment(width, height, format, dataType) {
return this.create(Texture2DCreateParams.createForAttachment(width, height, format, dataType));
}
/** Create a texture to hold non-image data */
static createForData(width, height, data, wantPreserveData = false, wrapMode = GL_1.GL.Texture.WrapMode.ClampToEdge, format = GL_1.GL.Texture.Format.Rgba) {
return this.create(Texture2DCreateParams.createForData(width, height, data, wantPreserveData, wrapMode, format));
}
/** Create a texture from a bitmap */
static createForImageBuffer(image, type) {
if (core_common_1.RenderTexture.Type.TileSection !== type && core_common_1.RenderTexture.Type.ThematicGradient !== type)
(0, core_bentley_1.assert)((0, core_common_1.isPowerOfTwo)(image.width) && (0, core_common_1.isPowerOfTwo)(image.height), "###TODO: Resize image dimensions to powers-of-two if necessary");
return this.create(Texture2DCreateParams.createForImageBuffer(image, type));
}
/** Create a 2D texture from an HTMLImageElement. */
static createForImage(image, type) {
return this.create(Texture2DCreateParams.createForImage(image, type));
}
/** Create a 2D texture from an ImageBitmap. */
static createForImageBitmap(image, type) {
return this.create(Texture2DCreateParams.createForImageBitmap(image, type));
}
static _placeHolderTextureData = new Uint8Array([128, 128, 128, 255]);
static createForElement(id, imodel, type, format, onLoaded) {
// set a placeholder texture while we wait for the external texture to load
const handle = this.createForData(1, 1, this._placeHolderTextureData, undefined, undefined, GL_1.GL.Texture.Format.Rgba);
if (undefined === handle)
return undefined;
// kick off loading the texture from the backend
ExternalTextureLoader.instance.loadTexture(handle, id, imodel, type, format, onLoaded);
return handle;
}
reload(params) {
this._width = params.width;
this._height = params.height;
this._format = params.format;
this._dataType = params.dataType;
this._dataBytes = params.dataBytes;
params.loadImageData(this, params);
}
constructor(glTexture, params) {
super(glTexture);
this._width = params.width;
this._height = params.height;
this._format = params.format;
this._dataType = params.dataType;
this._dataBytes = params.dataBytes;
params.loadImageData(this, params);
}
}
exports.Texture2DHandle = Texture2DHandle;
/** @internal */
class ExternalTextureLoader {
static instance = new ExternalTextureLoader(2);
onTexturesLoaded = new core_bentley_1.BeEvent();
_maxActiveRequests;
_activeRequests = [];
_pendingRequests = [];
_convertRequests = [];
_convertPending = false;
get numActiveRequests() { return this._activeRequests.length; }
get numPendingRequests() { return this._pendingRequests.length; }
get maxActiveRequests() { return this._maxActiveRequests; }
constructor(maxActiveRequests) {
this._maxActiveRequests = maxActiveRequests;
}
async _nextRequest(prevReq) {
this._activeRequests.splice(this._activeRequests.indexOf(prevReq), 1);
if (this._activeRequests.length < this._maxActiveRequests && this._pendingRequests.length > 0) {
// length is verified to be > 0, so shift will not return undefined
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const req = this._pendingRequests.shift();
await this._activateRequest(req);
}
if (this._activeRequests.length < 1 && this._pendingRequests.length < 1)
this.onTexturesLoaded.raiseEvent();
}
async _activateRequest(req) {
if (req.imodel.isClosed)
return;
this._activeRequests.push(req);
try {
if (!req.imodel.isClosed) {
const maxTextureSize = System_1.System.instance.maxTexSizeAllow;
const texData = await req.imodel.queryTextureData({ name: req.name, maxTextureSize });
if (undefined !== texData) {
const cnvReq = { req, texData };
this._convertRequests.push(cnvReq);
// _convertPending is used to prevent overlapping calls to _convertTexture (from overlapping calls to _activateRequest)
// it has been put on the list, so if it doesn't get converted here it will get converted by the loop that is converting the current one
do {
if (!this._convertPending)
await this._convertTexture();
} while (!this._convertPending && this._convertRequests.length > 0);
if (!req.imodel.isClosed) {
IModelApp_1.IModelApp.tileAdmin.invalidateAllScenes();
if (undefined !== req.onLoaded)
req.onLoaded(req, texData);
}
}
}
}
catch { }
return this._nextRequest(req);
}
async _convertTexture() {
this._convertPending = true;
try {
const cnvReq = this._convertRequests.shift();
if (undefined !== cnvReq) {
const imageSource = new core_common_1.ImageSource(cnvReq.texData.bytes, cnvReq.texData.format);
if (System_1.System.instance.supportsCreateImageBitmap) {
const blob = new Blob([imageSource.data], { type: (0, ImageUtil_1.getImageSourceMimeType)(imageSource.format) });
const image = await createImageBitmap(blob, 0, 0, cnvReq.texData.width, cnvReq.texData.height);
if (!cnvReq.req.imodel.isClosed) {
cnvReq.req.handle.reload(Texture2DCreateParams.createForImageBitmap(image, cnvReq.req.type));
}
}
else {
const image = await (0, ImageUtil_1.imageElementFromImageSource)(imageSource);
if (!cnvReq.req.imodel.isClosed) {
cnvReq.req.handle.reload(Texture2DCreateParams.createForImage(image, cnvReq.req.type));
}
}
}
}
catch { }
this._convertPending = false;
}
_requestExists(reqToCheck) {
for (const r of this._activeRequests)
if (reqToCheck.name === r.name && reqToCheck.imodel === r.imodel)
return true;
for (const r of this._pendingRequests)
if (reqToCheck.name === r.name && reqToCheck.imodel === r.imodel)
return true;
return false;
}
loadTexture(handle, name, imodel, type, format, onLoaded) {
const req = { handle, name, imodel, type, format, onLoaded };
if (this._requestExists(req))
return;
if (this._activeRequests.length + 1 > this._maxActiveRequests) {
this._pendingRequests.push(req);
}
else
this._activateRequest(req); // eslint-disable-line @typescript-eslint/no-floating-promises
}
}
exports.ExternalTextureLoader = ExternalTextureLoader;
/** @internal */
class TextureCubeHandle extends TextureHandle {
_dim; // Cubemap texture height and width must match. This must be the same for each of the six faces.
_format; // Format must be the same for each of the six faces.
_dataType; // Type must be the same for each of the six faces.
get width() { return this._dim; }
get height() { return this._dim; }
get format() { return this._format; }
get dataType() { return this._dataType; }
get dataBytes() { return undefined; }
/** Bind specified cubemap texture handle to specified texture unit. */
static bindTexture(texUnit, glTex) {
(0, core_bentley_1.assert)(!(glTex instanceof TextureHandle));
System_1.System.instance.bindTextureCubeMap(texUnit, glTex);
}
/** Bind the specified texture to a uniform sampler2D */
static bindSampler(uniform, tex, unit) {
(0, core_bentley_1.assert)(!(tex instanceof TextureHandle));
this.bindTexture(unit, tex);
uniform.setUniform1i(unit - RenderFlags_1.TextureUnit.Zero);
}
/** Bind texture handle (if available) associated with an instantiation of this class to specified texture unit. */
bind(texUnit) {
if (undefined === this._glTexture)
return false;
TextureCubeHandle.bindTexture(texUnit, this._glTexture);
return true;
}
/** Bind this texture to a uniform sampler2D */
bindSampler(uniform, unit) {
if (undefined !== this._glTexture)
TextureCubeHandle.bindSampler(uniform, this._glTexture, unit);
}
static create(params) {
const glTex = System_1.System.instance.context.createTexture();
return null !== glTex ? new TextureCubeHandle(glTex, params) : undefined;
}
/** Create a cube map texture from six HTMLImageElement objects. */
static createForCubeImages(posX, negX, posY, negY, posZ, negZ) {
const params = TextureCubeCreateParams.createForCubeImages(posX, negX, posY, negY, posZ, negZ);
return params !== undefined ? this.create(params) : undefined;
}
constructor(glTexture, params) {
super(glTexture);
this._dim = params.dim;
this._format = params.format;
this._dataType = params.dataType;
params.loadImageData(this, params);
}
}
exports.TextureCubeHandle = TextureCubeHandle;
/** @internal */
class Texture2DDataUpdater {
data;
modified = false;
constructor(data) { this.data = data; }
setByteAtIndex(index, byte) {
(0, core_bentley_1.assert)(index < this.data.length);
if (byte !== this.data[index]) {
this.data[index] = byte;
this.modified = true;
}
}
setOvrFlagsAtIndex(index, value) {
(0, core_bentley_1.assert)(index < this.data.length - 1);
(0, core_bentley_1.assert)(value < 0xffff);
this.setByteAtIndex(index, value & 0xff);
this.setByteAtIndex(index + 1, (value & 0xff00) >> 8);
}
getByteAtIndex(index) {
(0, core_bentley_1.assert)(index < this.data.length);
return this.data[index];
}
getOvrFlagsAtIndex(index) {
const lo = this.getByteAtIndex(index);
const hi = this.getByteAtIndex(index + 1);
return lo | (hi << 8);
}
}
exports.Texture2DDataUpdater = Texture2DDataUpdater;
//# sourceMappingURL=Texture.js.map