UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

1,380 lines (1,055 loc) 54.4 kB
import _defineProperty from '@babel/runtime/helpers/defineProperty'; import _slicedToArray from '@babel/runtime/helpers/slicedToArray'; import Constants from './Texture/Constants.js'; import HalfFloat from '../../Common/Core/HalfFloat.js'; import { newInstance as newInstance$1, obj, set, setGet, get, moveToProtected, newTypedArray, vtkDebugMacro as vtkDebugMacro$1, vtkErrorMacro as vtkErrorMacro$1, vtkWarningMacro as vtkWarningMacro$1 } from '../../macros.js'; import vtkDataArray from '../../Common/Core/DataArray.js'; import { Q as isPowerOfTwo, M as nearestPowerOfTwo } from '../../Common/Core/Math/index.js'; import vtkViewNode from '../SceneGraph/ViewNode.js'; import { registerOverride } from './ViewNodeFactory.js'; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } var Wrap = Constants.Wrap, Filter = Constants.Filter; var VtkDataTypes = vtkDataArray.VtkDataTypes; var vtkDebugMacro = vtkDebugMacro$1, vtkErrorMacro = vtkErrorMacro$1, vtkWarningMacro = vtkWarningMacro$1; var toHalf = HalfFloat.toHalf; // ---------------------------------------------------------------------------- // vtkOpenGLTexture methods // ---------------------------------------------------------------------------- function vtkOpenGLTexture(publicAPI, model) { // Set our className model.classHierarchy.push('vtkOpenGLTexture'); // Renders myself publicAPI.render = function () { var renWin = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; if (renWin) { model._openGLRenderWindow = renWin; } else { model._openGLRenderer = publicAPI.getFirstAncestorOfType('vtkOpenGLRenderer'); // sync renderable properties model._openGLRenderWindow = model._openGLRenderer.getParent(); } model.context = model._openGLRenderWindow.getContext(); if (model.renderable.getInterpolate()) { if (model.generateMipmap) { publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR); } else { publicAPI.setMinificationFilter(Filter.LINEAR); } publicAPI.setMagnificationFilter(Filter.LINEAR); } else { publicAPI.setMinificationFilter(Filter.NEAREST); publicAPI.setMagnificationFilter(Filter.NEAREST); } if (model.renderable.getRepeat()) { publicAPI.setWrapR(Wrap.REPEAT); publicAPI.setWrapS(Wrap.REPEAT); publicAPI.setWrapT(Wrap.REPEAT); } // clear image if input data is set if (model.renderable.getInputData()) { model.renderable.setImage(null); } // create the texture if it is not done already if (!model.handle || model.renderable.getMTime() > model.textureBuildTime.getMTime()) { // if we have an Image if (model.renderable.getImage() !== null) { if (model.renderable.getInterpolate()) { model.generateMipmap = true; publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR); } // Have an Image which may not be complete if (model.renderable.getImage() && model.renderable.getImageLoaded()) { publicAPI.create2DFromImage(model.renderable.getImage()); publicAPI.activate(); publicAPI.sendParameters(); model.textureBuildTime.modified(); } } // if we have a canvas if (model.renderable.getCanvas() !== null) { if (model.renderable.getInterpolate()) { model.generateMipmap = true; publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR); } var canvas = model.renderable.getCanvas(); publicAPI.create2DFromRaw(canvas.width, canvas.height, 4, VtkDataTypes.UNSIGNED_CHAR, canvas, true); publicAPI.activate(); publicAPI.sendParameters(); model.textureBuildTime.modified(); } // if we have jsImageData if (model.renderable.getJsImageData() !== null) { var jsid = model.renderable.getJsImageData(); if (model.renderable.getInterpolate()) { model.generateMipmap = true; publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR); } publicAPI.create2DFromRaw(jsid.width, jsid.height, 4, VtkDataTypes.UNSIGNED_CHAR, jsid.data, true); publicAPI.activate(); publicAPI.sendParameters(); model.textureBuildTime.modified(); } // if we have InputData var input = model.renderable.getInputData(0); if (input && input.getPointData().getScalars()) { var ext = input.getExtent(); var inScalars = input.getPointData().getScalars(); // do we have a cube map? Six inputs var data = []; for (var i = 0; i < model.renderable.getNumberOfInputPorts(); ++i) { var indata = model.renderable.getInputData(i); var scalars = indata ? indata.getPointData().getScalars().getData() : null; if (scalars) { data.push(scalars); } } if (model.renderable.getInterpolate() && inScalars.getNumberOfComponents() === 4) { model.generateMipmap = true; publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR); } if (data.length % 6 === 0) { publicAPI.createCubeFromRaw(ext[1] - ext[0] + 1, ext[3] - ext[2] + 1, inScalars.getNumberOfComponents(), inScalars.getDataType(), data); } else { publicAPI.create2DFromRaw(ext[1] - ext[0] + 1, ext[3] - ext[2] + 1, inScalars.getNumberOfComponents(), inScalars.getDataType(), inScalars.getData()); } publicAPI.activate(); publicAPI.sendParameters(); model.textureBuildTime.modified(); } } if (model.handle) { publicAPI.activate(); } }; //---------------------------------------------------------------------------- publicAPI.destroyTexture = function () { // deactivate it first publicAPI.deactivate(); if (model.context && model.handle) { model.context.deleteTexture(model.handle); } model.handle = 0; model.numberOfDimensions = 0; model.target = 0; model.components = 0; model.width = 0; model.height = 0; model.depth = 0; publicAPI.resetFormatAndType(); }; //---------------------------------------------------------------------------- publicAPI.createTexture = function () { // reuse the existing handle if we have one if (!model.handle) { model.handle = model.context.createTexture(); if (model.target) { model.context.bindTexture(model.target, model.handle); // See: http://www.openmodel.context..org/wiki/Common_Mistakes#Creating_a_complete_texture // turn off mip map filter or set the base and max level correctly. here // both are done. model.context.texParameteri(model.target, model.context.TEXTURE_MIN_FILTER, publicAPI.getOpenGLFilterMode(model.minificationFilter)); model.context.texParameteri(model.target, model.context.TEXTURE_MAG_FILTER, publicAPI.getOpenGLFilterMode(model.magnificationFilter)); model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_S, publicAPI.getOpenGLWrapMode(model.wrapS)); model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_T, publicAPI.getOpenGLWrapMode(model.wrapT)); if (model._openGLRenderWindow.getWebgl2()) { model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_R, publicAPI.getOpenGLWrapMode(model.wrapR)); } model.context.bindTexture(model.target, null); } } }; //--------------------------------------------------------------------------- publicAPI.getTextureUnit = function () { if (model._openGLRenderWindow) { return model._openGLRenderWindow.getTextureUnitForTexture(publicAPI); } return -1; }; //--------------------------------------------------------------------------- publicAPI.activate = function () { // activate a free texture unit for this texture model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.bind(); }; //--------------------------------------------------------------------------- publicAPI.deactivate = function () { if (model._openGLRenderWindow) { model._openGLRenderWindow.deactivateTexture(publicAPI); } }; //--------------------------------------------------------------------------- publicAPI.releaseGraphicsResources = function (rwin) { if (rwin && model.handle) { rwin.activateTexture(publicAPI); rwin.deactivateTexture(publicAPI); model.context.deleteTexture(model.handle); model.handle = 0; model.numberOfDimensions = 0; model.target = 0; model.internalFormat = 0; model.format = 0; model.openGLDataType = 0; model.components = 0; model.width = 0; model.height = 0; model.depth = 0; } if (model.shaderProgram) { model.shaderProgram.releaseGraphicsResources(rwin); model.shaderProgram = null; } }; //---------------------------------------------------------------------------- publicAPI.bind = function () { model.context.bindTexture(model.target, model.handle); if (model.autoParameters && publicAPI.getMTime() > model.sendParametersTime.getMTime()) { publicAPI.sendParameters(); } }; //---------------------------------------------------------------------------- publicAPI.isBound = function () { var result = false; if (model.context && model.handle) { var target = 0; switch (model.target) { case model.context.TEXTURE_2D: target = model.context.TEXTURE_BINDING_2D; break; default: vtkWarningMacro('impossible case'); break; } var oid = model.context.getIntegerv(target); result = oid === model.handle; } return result; }; //---------------------------------------------------------------------------- publicAPI.sendParameters = function () { model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_S, publicAPI.getOpenGLWrapMode(model.wrapS)); model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_T, publicAPI.getOpenGLWrapMode(model.wrapT)); if (model._openGLRenderWindow.getWebgl2()) { model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_R, publicAPI.getOpenGLWrapMode(model.wrapR)); } model.context.texParameteri(model.target, model.context.TEXTURE_MIN_FILTER, publicAPI.getOpenGLFilterMode(model.minificationFilter)); model.context.texParameteri(model.target, model.context.TEXTURE_MAG_FILTER, publicAPI.getOpenGLFilterMode(model.magnificationFilter)); if (model._openGLRenderWindow.getWebgl2()) { model.context.texParameteri(model.target, model.context.TEXTURE_BASE_LEVEL, model.baseLevel); model.context.texParameteri(model.target, model.context.TEXTURE_MAX_LEVEL, model.maxLevel); } // model.context.texParameterf(model.target, model.context.TEXTURE_MIN_LOD, model.minLOD); // model.context.texParameterf(model.target, model.context.TEXTURE_MAX_LOD, model.maxLOD); model.sendParametersTime.modified(); }; //---------------------------------------------------------------------------- publicAPI.getInternalFormat = function (vtktype, numComps) { if (!model._forceInternalFormat) { model.internalFormat = publicAPI.getDefaultInternalFormat(vtktype, numComps); } if (!model.internalFormat) { vtkDebugMacro("Unable to find suitable internal format for T=".concat(vtktype, " NC= ").concat(numComps)); } return model.internalFormat; }; //---------------------------------------------------------------------------- publicAPI.getDefaultInternalFormat = function (vtktype, numComps) { var result = 0; // try default next result = model._openGLRenderWindow.getDefaultTextureInternalFormat(vtktype, numComps, model.oglNorm16Ext, model.useHalfFloat); if (result) { return result; } if (!result) { vtkDebugMacro('Unsupported internal texture type!'); vtkDebugMacro("Unable to find suitable internal format for T=".concat(vtktype, " NC= ").concat(numComps)); } return result; }; //---------------------------------------------------------------------------- publicAPI.setInternalFormat = function (iFormat) { model._forceInternalFormat = true; if (iFormat !== model.internalFormat) { model.internalFormat = iFormat; publicAPI.modified(); } }; //---------------------------------------------------------------------------- publicAPI.getFormat = function (vtktype, numComps) { model.format = publicAPI.getDefaultFormat(vtktype, numComps); return model.format; }; //---------------------------------------------------------------------------- publicAPI.getDefaultFormat = function (vtktype, numComps) { if (model._openGLRenderWindow.getWebgl2()) { switch (numComps) { case 1: return model.context.RED; case 2: return model.context.RG; case 3: return model.context.RGB; case 4: return model.context.RGBA; default: return model.context.RGB; } } else { // webgl1 switch (numComps) { case 1: return model.context.LUMINANCE; case 2: return model.context.LUMINANCE_ALPHA; case 3: return model.context.RGB; case 4: return model.context.RGBA; default: return model.context.RGB; } } }; //---------------------------------------------------------------------------- publicAPI.resetFormatAndType = function () { model.format = 0; model.internalFormat = 0; model._forceInternalFormat = false; model.openGLDataType = 0; }; //---------------------------------------------------------------------------- publicAPI.getDefaultDataType = function (vtkScalarType) { // DON'T DEAL with VTK_CHAR as this is platform dependent. if (model._openGLRenderWindow.getWebgl2()) { switch (vtkScalarType) { // case VtkDataTypes.SIGNED_CHAR: // return model.context.BYTE; case VtkDataTypes.UNSIGNED_CHAR: return model.context.UNSIGNED_BYTE; // prefer norm16 since that is accurate compared to // half float which is not case model.oglNorm16Ext && !model.useHalfFloat && VtkDataTypes.SHORT: return model.context.SHORT; case model.oglNorm16Ext && !model.useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: return model.context.UNSIGNED_SHORT; // use half float type case model.useHalfFloat && VtkDataTypes.SHORT: return model.context.HALF_FLOAT; case model.useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: return model.context.HALF_FLOAT; // case VtkDataTypes.INT: // return model.context.INT; // case VtkDataTypes.UNSIGNED_INT: // return model.context.UNSIGNED_INT; case VtkDataTypes.FLOAT: case VtkDataTypes.VOID: // used for depth component textures. default: return model.context.FLOAT; } } switch (vtkScalarType) { // case VtkDataTypes.SIGNED_CHAR: // return model.context.BYTE; case VtkDataTypes.UNSIGNED_CHAR: return model.context.UNSIGNED_BYTE; // case VtkDataTypes.SHORT: // return model.context.SHORT; // case VtkDataTypes.UNSIGNED_SHORT: // return model.context.UNSIGNED_SHORT; // case VtkDataTypes.INT: // return model.context.INT; // case VtkDataTypes.UNSIGNED_INT: // return model.context.UNSIGNED_INT; case VtkDataTypes.FLOAT: case VtkDataTypes.VOID: // used for depth component textures. default: if (model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear')) { return model.context.FLOAT; } { var halfFloat = model.context.getExtension('OES_texture_half_float'); if (halfFloat && model.context.getExtension('OES_texture_half_float_linear')) { return halfFloat.HALF_FLOAT_OES; } } return model.context.UNSIGNED_BYTE; } }; //---------------------------------------------------------------------------- publicAPI.getOpenGLDataType = function (vtkScalarType) { var forceUpdate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (!model.openGLDataType || forceUpdate) { model.openGLDataType = publicAPI.getDefaultDataType(vtkScalarType); } return model.openGLDataType; }; publicAPI.getShiftAndScale = function () { var shift = 0.0; var scale = 1.0; // for all float type internal formats switch (model.openGLDataType) { case model.context.BYTE: scale = 127.5; shift = scale - 128.0; break; case model.context.UNSIGNED_BYTE: scale = 255.0; shift = 0.0; break; case model.context.SHORT: scale = 32767.5; shift = scale - 32768.0; break; case model.context.UNSIGNED_SHORT: scale = 65536.0; shift = 0.0; break; case model.context.INT: scale = 2147483647.5; shift = scale - 2147483648.0; break; case model.context.UNSIGNED_INT: scale = 4294967295.0; shift = 0.0; break; case model.context.FLOAT: } return { shift: shift, scale: scale }; }; //---------------------------------------------------------------------------- publicAPI.getOpenGLFilterMode = function (emode) { switch (emode) { case Filter.NEAREST: return model.context.NEAREST; case Filter.LINEAR: return model.context.LINEAR; case Filter.NEAREST_MIPMAP_NEAREST: return model.context.NEAREST_MIPMAP_NEAREST; case Filter.NEAREST_MIPMAP_LINEAR: return model.context.NEAREST_MIPMAP_LINEAR; case Filter.LINEAR_MIPMAP_NEAREST: return model.context.LINEAR_MIPMAP_NEAREST; case Filter.LINEAR_MIPMAP_LINEAR: return model.context.LINEAR_MIPMAP_LINEAR; default: return model.context.NEAREST; } }; //---------------------------------------------------------------------------- publicAPI.getOpenGLWrapMode = function (vtktype) { switch (vtktype) { case Wrap.CLAMP_TO_EDGE: return model.context.CLAMP_TO_EDGE; case Wrap.REPEAT: return model.context.REPEAT; case Wrap.MIRRORED_REPEAT: return model.context.MIRRORED_REPEAT; default: return model.context.CLAMP_TO_EDGE; } }; //---------------------------------------------------------------------------- function updateArrayDataType(dataType, data) { var depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; var pixData = []; var pixCount = model.width * model.height * model.components; if (depth) { pixCount *= model.depth; } // if the opengl data type is float // then the data array must be float if (dataType !== VtkDataTypes.FLOAT && model.openGLDataType === model.context.FLOAT) { for (var idx = 0; idx < data.length; idx++) { if (data[idx]) { var dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx]; pixData.push(new Float32Array(dataArrayToCopy)); } else { pixData.push(null); } } } // if the opengl data type is ubyte // then the data array must be u8, we currently simply truncate the data if (dataType !== VtkDataTypes.UNSIGNED_CHAR && model.openGLDataType === model.context.UNSIGNED_BYTE) { for (var _idx = 0; _idx < data.length; _idx++) { if (data[_idx]) { var _dataArrayToCopy = data[_idx].length > pixCount ? data[_idx].subarray(0, pixCount) : data[_idx]; pixData.push(new Uint8Array(_dataArrayToCopy)); } else { pixData.push(null); } } } // if the opengl data type is half float // then the data array must be u16 var halfFloat = false; if (model._openGLRenderWindow.getWebgl2()) { halfFloat = model.openGLDataType === model.context.HALF_FLOAT; } else { var halfFloatExt = model.context.getExtension('OES_texture_half_float'); halfFloat = halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES; } if (halfFloat) { for (var _idx2 = 0; _idx2 < data.length; _idx2++) { if (data[_idx2]) { var newArray = new Uint16Array(pixCount); var src = data[_idx2]; for (var i = 0; i < pixCount; i++) { newArray[i] = toHalf(src[i]); } pixData.push(newArray); } else { pixData.push(null); } } } // The output has to be filled if (pixData.length === 0) { for (var _i = 0; _i < data.length; _i++) { pixData.push(data[_i]); } } return pixData; } //---------------------------------------------------------------------------- function scaleTextureToHighestPowerOfTwo(data) { if (model._openGLRenderWindow.getWebgl2()) { // No need if webGL2 return data; } var pixData = []; var width = model.width; var height = model.height; var numComps = model.components; if (data && (!isPowerOfTwo(width) || !isPowerOfTwo(height))) { // Scale up the texture to the next highest power of two dimensions. var halfFloat = model.context.getExtension('OES_texture_half_float'); var newWidth = nearestPowerOfTwo(width); var newHeight = nearestPowerOfTwo(height); var pixCount = newWidth * newHeight * model.components; for (var idx = 0; idx < data.length; idx++) { if (data[idx] !== null) { var newArray = null; var jFactor = height / newHeight; var iFactor = width / newWidth; var usingHalf = false; if (model.openGLDataType === model.context.FLOAT) { newArray = new Float32Array(pixCount); } else if (halfFloat && model.openGLDataType === halfFloat.HALF_FLOAT_OES) { newArray = new Uint16Array(pixCount); usingHalf = true; } else { newArray = new Uint8Array(pixCount); } for (var j = 0; j < newHeight; j++) { var joff = j * newWidth * numComps; var jidx = j * jFactor; var jlow = Math.floor(jidx); var jhi = Math.ceil(jidx); if (jhi >= height) { jhi = height - 1; } var jmix = jidx - jlow; var jmix1 = 1.0 - jmix; jlow = jlow * width * numComps; jhi = jhi * width * numComps; for (var i = 0; i < newWidth; i++) { var ioff = i * numComps; var iidx = i * iFactor; var ilow = Math.floor(iidx); var ihi = Math.ceil(iidx); if (ihi >= width) { ihi = width - 1; } var imix = iidx - ilow; ilow *= numComps; ihi *= numComps; for (var c = 0; c < numComps; c++) { if (usingHalf) { newArray[joff + ioff + c] = HalfFloat.toHalf(HalfFloat.fromHalf(data[idx][jlow + ilow + c]) * jmix1 * (1.0 - imix) + HalfFloat.fromHalf(data[idx][jlow + ihi + c]) * jmix1 * imix + HalfFloat.fromHalf(data[idx][jhi + ilow + c]) * jmix * (1.0 - imix) + HalfFloat.fromHalf(data[idx][jhi + ihi + c]) * jmix * imix); } else { newArray[joff + ioff + c] = data[idx][jlow + ilow + c] * jmix1 * (1.0 - imix) + data[idx][jlow + ihi + c] * jmix1 * imix + data[idx][jhi + ilow + c] * jmix * (1.0 - imix) + data[idx][jhi + ihi + c] * jmix * imix; } } } } pixData.push(newArray); model.width = newWidth; model.height = newHeight; } else { pixData.push(null); } } } // The output has to be filled if (pixData.length === 0) { for (var _i2 = 0; _i2 < data.length; _i2++) { pixData.push(data[_i2]); } } return pixData; } //---------------------------------------------------------------------------- function useTexStorage(dataType) { if (model._openGLRenderWindow) { var _model$renderable; if (model.resizable || (_model$renderable = model.renderable) !== null && _model$renderable !== void 0 && _model$renderable.getResizable()) { // Cannot use texStorage if the texture is supposed to be resizable. return false; } if (model._openGLRenderWindow.getWebgl2()) { var webGLInfo = model._openGLRenderWindow.getGLInformations(); if (webGLInfo.RENDERER.value.match(/WebKit/gi) && navigator.platform.match(/Mac/gi) && model.oglNorm16Ext && (dataType === VtkDataTypes.UNSIGNED_SHORT || dataType === VtkDataTypes.SHORT)) { // Cannot use texStorage with EXT_texture_norm16 textures on Mac M1 GPU. // No errors reported but the texture is unusable. return false; } // Use texStorage for WebGL2 return true; } return false; } return false; } //---------------------------------------------------------------------------- publicAPI.create2DFromRaw = function (width, height, numComps, dataType, data) { var flip = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; // Now determine the texture parameters using the arguments. publicAPI.getOpenGLDataType(dataType, true); publicAPI.getInternalFormat(dataType, numComps); publicAPI.getFormat(dataType, numComps); if (!model.internalFormat || !model.format || !model.openGLDataType) { vtkErrorMacro('Failed to determine texture parameters.'); return false; } model.target = model.context.TEXTURE_2D; model.components = numComps; model.width = width; model.height = height; model.depth = 1; model.numberOfDimensions = 2; model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.createTexture(); publicAPI.bind(); // Create an array of texture with one texture var dataArray = [data]; var pixData = updateArrayDataType(dataType, dataArray); var scaledData = scaleTextureToHighestPowerOfTwo(pixData); // Source texture data from the PBO. model.context.pixelStorei(model.context.UNPACK_FLIP_Y_WEBGL, flip); model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1); if (useTexStorage(dataType)) { model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height); if (scaledData[0] != null) { model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, scaledData[0]); } } else { model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, scaledData[0]); } if (model.generateMipmap) { model.context.generateMipmap(model.target); } // always reset the flip if (flip) { model.context.pixelStorei(model.context.UNPACK_FLIP_Y_WEBGL, false); } publicAPI.deactivate(); return true; }; //---------------------------------------------------------------------------- publicAPI.createCubeFromRaw = function (width, height, numComps, dataType, data) { // Now determine the texture parameters using the arguments. publicAPI.getOpenGLDataType(dataType); publicAPI.getInternalFormat(dataType, numComps); publicAPI.getFormat(dataType, numComps); if (!model.internalFormat || !model.format || !model.openGLDataType) { vtkErrorMacro('Failed to determine texture parameters.'); return false; } model.target = model.context.TEXTURE_CUBE_MAP; model.components = numComps; model.width = width; model.height = height; model.depth = 1; model.numberOfDimensions = 2; model._openGLRenderWindow.activateTexture(publicAPI); model.maxLevel = data.length / 6 - 1; publicAPI.createTexture(); publicAPI.bind(); var pixData = updateArrayDataType(dataType, data); var scaledData = scaleTextureToHighestPowerOfTwo(pixData); // invert the data because opengl is messed up with cube maps // and uses the old renderman standard with Y going down // even though it is completely at odds with OpenGL standards var invertedData = []; var widthLevel = model.width; var heightLevel = model.height; for (var i = 0; i < scaledData.length; i++) { if (i % 6 === 0 && i !== 0) { widthLevel /= 2; heightLevel /= 2; } invertedData[i] = newTypedArray(dataType, heightLevel * widthLevel * model.components); for (var y = 0; y < heightLevel; ++y) { var row1 = y * widthLevel * model.components; var row2 = (heightLevel - y - 1) * widthLevel * model.components; invertedData[i].set(scaledData[i].slice(row2, row2 + widthLevel * model.components), row1); } } // Source texture data from the PBO. model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1); if (useTexStorage(dataType)) { model.context.texStorage2D(model.target, 6, model.internalFormat, model.width, model.height); } // We get the 6 images for (var _i3 = 0; _i3 < 6; _i3++) { // For each mipmap level var j = 0; var w = model.width; var h = model.height; while (w >= 1 && h >= 1) { // In webgl 1, all levels need to be defined. So if the latest level size is // 8x8, we have to add 3 more null textures (4x4, 2x2, 1x1) // In webgl 2, the attribute maxLevel will be use. var tempData = null; if (j <= model.maxLevel) { tempData = invertedData[6 * j + _i3]; } if (useTexStorage(dataType)) { if (tempData != null) { model.context.texSubImage2D(model.context.TEXTURE_CUBE_MAP_POSITIVE_X + _i3, j, 0, 0, w, h, model.format, model.openGLDataType, tempData); } } else { model.context.texImage2D(model.context.TEXTURE_CUBE_MAP_POSITIVE_X + _i3, j, model.internalFormat, w, h, 0, model.format, model.openGLDataType, tempData); } j++; w /= 2; h /= 2; } } // generateMipmap must not be called here because we manually upload all levels // if it is called, all levels will be overwritten publicAPI.deactivate(); return true; }; //---------------------------------------------------------------------------- publicAPI.createDepthFromRaw = function (width, height, dataType, data) { // Now determine the texture parameters using the arguments. publicAPI.getOpenGLDataType(dataType); model.format = model.context.DEPTH_COMPONENT; if (model._openGLRenderWindow.getWebgl2()) { if (dataType === VtkDataTypes.FLOAT) { model.internalFormat = model.context.DEPTH_COMPONENT32F; } else { model.internalFormat = model.context.DEPTH_COMPONENT16; } } else { model.internalFormat = model.context.DEPTH_COMPONENT; } if (!model.internalFormat || !model.format || !model.openGLDataType) { vtkErrorMacro('Failed to determine texture parameters.'); return false; } model.target = model.context.TEXTURE_2D; model.components = 1; model.width = width; model.height = height; model.depth = 1; model.numberOfDimensions = 2; model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.createTexture(); publicAPI.bind(); // Source texture data from the PBO. // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1); if (useTexStorage(dataType)) { model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height); if (data != null) { model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, data); } } else { model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, data); } if (model.generateMipmap) { model.context.generateMipmap(model.target); } publicAPI.deactivate(); return true; }; //---------------------------------------------------------------------------- publicAPI.create2DFromImage = function (image) { // Now determine the texture parameters using the arguments. publicAPI.getOpenGLDataType(VtkDataTypes.UNSIGNED_CHAR); publicAPI.getInternalFormat(VtkDataTypes.UNSIGNED_CHAR, 4); publicAPI.getFormat(VtkDataTypes.UNSIGNED_CHAR, 4); if (!model.internalFormat || !model.format || !model.openGLDataType) { vtkErrorMacro('Failed to determine texture parameters.'); return false; } model.target = model.context.TEXTURE_2D; model.components = 4; model.depth = 1; model.numberOfDimensions = 2; model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.createTexture(); publicAPI.bind(); // Source texture data from the PBO. // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1); // Scale up the texture to the next highest power of two dimensions (if needed) and flip y. var needNearestPowerOfTwo = !model._openGLRenderWindow.getWebgl2() && (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height)); var canvas = document.createElement('canvas'); canvas.width = needNearestPowerOfTwo ? nearestPowerOfTwo(image.width) : image.width; canvas.height = needNearestPowerOfTwo ? nearestPowerOfTwo(image.height) : image.height; model.width = canvas.width; model.height = canvas.height; var ctx = canvas.getContext('2d'); ctx.translate(0, canvas.height); ctx.scale(1, -1); ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); var safeImage = canvas; if (useTexStorage(VtkDataTypes.UNSIGNED_CHAR)) { model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height); if (safeImage != null) { model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, safeImage); } } else { model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, safeImage); } if (model.generateMipmap) { model.context.generateMipmap(model.target); } publicAPI.deactivate(); return true; }; // Compute scale and offset per component from min and max per component function computeScaleOffsets(min, max, numComps) { var offset = new Array(numComps); var scale = new Array(numComps); for (var c = 0; c < numComps; ++c) { offset[c] = min[c]; scale[c] = max[c] - min[c] || 1.0; } return { scale: scale, offset: offset }; } // HalfFloat only represents numbers between [-2048, 2048] exactly accurate, // for numbers outside of this range there is a precision limitation function hasExactHalfFloat(offset, scale) { // Per Component for (var c = 0; c < offset.length; c++) { var min = offset[c]; var max = scale[c] + min; if (min < -2048 || min > 2048 || max < -2048 || max > 2048) { return false; } } return true; } function setUseHalfFloat(dataType, offset, scale, preferSizeOverAccuracy) { publicAPI.getOpenGLDataType(dataType); var useHalfFloat = false; if (model._openGLRenderWindow.getWebgl2()) { useHalfFloat = model.openGLDataType === model.context.HALF_FLOAT; } else { var halfFloatExt = model.context.getExtension('OES_texture_half_float'); useHalfFloat = halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES; } // Don't consider halfFloat and convert back to Float when the range of data does not generate an accurate halfFloat // AND it is not preferable to have a smaller texture than an exact texture. var isHalfFloat = useHalfFloat && (hasExactHalfFloat(offset, scale) || preferSizeOverAccuracy); model.useHalfFloat = isHalfFloat; } function processDataArray(dataArray, preferSizeOverAccuracy) { var numComps = dataArray.getNumberOfComponents(); var dataType = dataArray.getDataType(); var data = dataArray.getData(); // Compute min max from array // Using the vtkDataArray.getRange() enables caching var minArray = new Array(numComps); var maxArray = new Array(numComps); for (var c = 0; c < numComps; ++c) { var _dataArray$getRange = dataArray.getRange(c), _dataArray$getRange2 = _slicedToArray(_dataArray$getRange, 2), min = _dataArray$getRange2[0], max = _dataArray$getRange2[1]; minArray[c] = min; maxArray[c] = max; } var scaleOffsets = computeScaleOffsets(minArray, maxArray, numComps); // preferSizeOverAccuracy will override norm16 due to bug with norm16 implementation // https://bugs.chromium.org/p/chromium/issues/detail?id=1408247 setUseHalfFloat(dataType, scaleOffsets.offset, scaleOffsets.scale, preferSizeOverAccuracy); // since our default is to use half float, in case that we can't use it // we need to use another type if (!model.useHalfFloat) { publicAPI.getOpenGLDataType(dataType, true); } return { numComps: numComps, dataType: dataType, data: data, scaleOffsets: scaleOffsets }; } publicAPI.create2DFilterableFromRaw = function (width, height, numberOfComponents, dataType, values) { var preferSizeOverAccuracy = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; return publicAPI.create2DFilterableFromDataArray(width, height, vtkDataArray.newInstance({ numberOfComponents: numberOfComponents, dataType: dataType, values: values }), preferSizeOverAccuracy); }; publicAPI.create2DFilterableFromDataArray = function (width, height, dataArray) { var preferSizeOverAccuracy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; var _processDataArray = processDataArray(dataArray, preferSizeOverAccuracy), numComps = _processDataArray.numComps, dataType = _processDataArray.dataType, data = _processDataArray.data; publicAPI.create2DFromRaw(width, height, numComps, dataType, data); }; //---------------------------------------------------------------------------- publicAPI.create3DFromRaw = function (width, height, depth, numComps, dataType, data) { // Permit OpenGLDataType to be half float, if applicable, for 3D publicAPI.getOpenGLDataType(dataType); // Now determine the texture parameters using the arguments. publicAPI.getInternalFormat(dataType, numComps); publicAPI.getFormat(dataType, numComps); if (!model.internalFormat || !model.format || !model.openGLDataType) { vtkErrorMacro('Failed to determine texture parameters.'); return false; } model.target = model.context.TEXTURE_3D; model.components = numComps; model.width = width; model.height = height; model.depth = depth; model.numberOfDimensions = 3; model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.createTexture(); publicAPI.bind(); // Create an array of texture with one texture var dataArray = [data]; var is3DArray = true; var pixData = updateArrayDataType(dataType, dataArray, is3DArray); var scaledData = scaleTextureToHighestPowerOfTwo(pixData); // Source texture data from the PBO. // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1); // openGLDataType if (useTexStorage(dataType)) { model.context.texStorage3D(model.target, 1, model.internalFormat, model.width, model.height, model.depth); if (scaledData[0] != null) { model.context.texSubImage3D(model.target, 0, 0, 0, 0, model.width, model.height, model.depth, model.format, model.openGLDataType, scaledData[0]); } } else { model.context.texImage3D(model.target, 0, model.internalFormat, model.width, model.height, model.depth, 0, model.format, model.openGLDataType, scaledData[0]); } if (model.generateMipmap) { model.context.generateMipmap(model.target); } publicAPI.deactivate(); return true; }; //---------------------------------------------------------------------------- // This method simulates a 3D texture using 2D // Prefer create3DFilterableFromDataArray to enable caching of min and max values publicAPI.create3DFilterableFromRaw = function (width, height, depth, numberOfComponents, dataType, values) { var preferSizeOverAccuracy = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false; return publicAPI.create3DFilterableFromDataArray(width, height, depth, vtkDataArray.newInstance({ numberOfComponents: numberOfComponents, dataType: dataType, values: values }), preferSizeOverAccuracy); }; //---------------------------------------------------------------------------- // This method create a 3D texture from dimensions and a DataArray publicAPI.create3DFilterableFromDataArray = function (width, height, depth, dataArray) { var preferSizeOverAccuracy = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var _processDataArray2 = processDataArray(dataArray, preferSizeOverAccuracy), numComps = _processDataArray2.numComps, dataType = _processDataArray2.dataType, data = _processDataArray2.data, scaleOffsets = _processDataArray2.scaleOffsets; var numPixelsIn = width * height * depth; var offset = []; var scale = []; for (var c = 0; c < numComps; ++c) { offset[c] = 0.0; scale[c] = 1.0; } // store the information, we will need it later // offset and scale are the offset and scale required to get // the texture value back to data values ala // data = texture * scale + offset // and texture = (data - offset)/scale model.volumeInfo = { scale: scale, offset: offset, dataComputedScale: scaleOffsets.scale, dataComputedOffset: scaleOffsets.offset, width: width, height: height, depth: depth }; // Create a copy of scale and offset to avoid aliasing issues // Original is read only, copy is read/write // Use the copy as volumeInfo.scale and volumeInfo.offset var scaleOffsetsCopy = structuredClone(scaleOffsets); // WebGL2 path, we have 3d textures etc if (model._openGLRenderWindow.getWebgl2()) { if (model.oglNorm16Ext && !model.useHalfFloat && dataType === VtkDataTypes.SHORT) { for (var _c = 0; _c < numComps; ++_c) { model.volumeInfo.scale[_c] = 32767.0; } return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data); } if (model.oglNorm16Ext && !model.useHalfFloat && dataType === VtkDataTypes.UNSIGNED_SHORT) { for (var _c2 = 0; _c2 < numComps; ++_c2) { model.volumeInfo.scale[_c2] = 65535.0; } return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data); } if (dataType === VtkDataTypes.FLOAT || model.useHalfFloat && (dataType === VtkDataTypes.SHORT || dataType === VtkDataTypes.UNSIGNED_SHORT)) { return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data); } if (dataType === VtkDataTypes.UNSIGNED_CHAR) { for (var _c3 = 0; _c3 < numComps; ++_c3) { model.volumeInfo.scale[_c3] = 255.0; } return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data); } // otherwise convert to float var _newArray = new Float32Array(numPixelsIn * numComps); // use computed scale and offset model.volumeInfo.offset = scaleOffsetsCopy.offset; model.volumeInfo.scale = scaleOffsetsCopy.scale; var count = 0; var scaleInverse = scaleOffsetsCopy.scale.map(function (s) { return 1 / s; }); for (var i = 0; i < numPixelsIn; i++) { for (var nc = 0; nc < numComps; nc++) { _newArray[count] = (data[count] - scaleOffsetsCopy.offset[nc]) * scaleInverse[nc]; count++; } } return publicAPI.create3DFromRaw(width, height, depth, numComps, VtkDataTypes.FLOAT, _newArray); } // not webgl2, deal with webgl1, no 3d textures // and maybe no float textures var volCopyData = function volCopyData(outArray, outIdx, inValue, smin, smax) { outArray[outIdx] = inValue; }; var dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR; // unsigned char gets used as is if (dataType === VtkDataTypes.UNSIGNED_CHAR) { for (var _c4 = 0; _c4 < numComps; ++_c4) { scaleOffsetsCopy.offset[_c4] = 0.0; scaleOffsetsCopy.scale[_c4] = 255.0; } } else if (model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear')) { // use float textures scaled to 0.0 to 1.0 dataTypeToUse = VtkDataTypes.FLOAT; volCopyData = function volCopyData(outArray, outIdx, inValue, soffset, sscale) { outArray[outIdx] = (inValue - soffset) / sscale; }; } else { // worst case, scale data to uchar dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR; volCopyData = function volCopyData(outArray, outIdx, inValue, soffset, sscale) { outArray[outIdx] = 255.0 * (inValue - soffset) / sscale; }; } // Now determine the texture parameters using the arguments. publicAPI.getOpenGLDataType(dataTypeToUse); publicAPI.getInternalFormat(dataTypeToUse, numComps); publicAPI.getFormat(dataTypeToUse, numComps); if (!model.internalFormat || !model.format || !model.openGLDataType) { vtkErrorMacro('Failed to determine texture parameters.'); return false; } // have to pack this 3D texture into pot 2D texture model.target = model.context.TEXTURE_2D; model.components = numComps; model.depth = 1; model.numberOfDimensions = 2; // MAX_TEXTURE_SIZE gives the max dimensions that can be supported by the GPU, // but it doesn't mean it will fit in memory. If we have to use a float data type // or 4 components, there are good chances that the texture size will blow up // and could not fit in the GPU memory. Use a smaller texture size in that case, // which will force a downsampling of the dataset. // That problem does not occur when using webGL2 since we can pack the data in // denser textures based on our data type. // TODO: try to fit in the biggest supported texture, catch the gl error if it // does not fix (OUT_OF_MEMORY), then attempt with smaller texture var maxTexDim = model.context.getParameter(model.context.MAX_TEXTURE_SIZE); if (maxTexDim > 4096 && (dataTypeToUse === VtkDataTypes.FLOAT || numComps >= 3)) { maxTexDim = 4096; } // compute estimate for XY subsample var xstride = 1; var ystride = 1; if (numPixelsIn > maxTexDim * maxTexDim) { xstride = Math.ceil(Math.sqrt(numPixelsIn / (maxTexDim * maxTexDim))); ystride = xstride; } var targetWidth = Math.sqrt(numPixelsIn) / xstride; targetWidth = nearestPowerOfTwo(targetWidth); // determine X reps var xreps = Math.floor(targetWidth * xstride / width); var yreps = Math.ceil(depth / xreps); var targetHeight = nearestPowerOfTwo(height * yreps / ystride); model.width = targetWidth; model.height = targetHeight; model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.createTexture(); publicAPI.bind(); // store the information, we will need it later model.volumeInfo.xreps = xreps; model.volumeInfo.yreps = yreps; model.volumeInfo.xstride = xstride; model.volumeInfo.ystride = ystride; model.volumeInfo.offset = scaleOffsetsCopy.offset; model.volumeInfo.scale = scaleOffsetsCopy.scale; // OK stuff the data into the 2d TEXTURE // first allocate the new texture var newArray; var pixCount = targetWidth * targetHeight * numComps; if (dataTypeToUse === VtkDataTypes.FLOAT) { newArray = new Float32Array(pixCount); } else { newArray = new Uint8Array(pixCount); } // then stuff the data into it, nothing fancy right now // for stride var outIdx = 0; var tileWidth = Math.floor(width / xstride); var tileHeight = Math.floor(height / ystride); for (var yRep = 0; yRep < yreps; yRep++) { var xrepsThisRow = Math.min(xreps, depth - yRep * xreps); var outXContI