import Constants from 'vtk.js/Sources/Rendering/OpenGL/Texture/Constants'; import HalfFloat from 'vtk.js/Sources/Common/Core/HalfFloat'; import * as macro from 'vtk.js/Sources/macros'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode';
import { registerOverride } from 'vtk.js/Sources/Rendering/OpenGL/ViewNodeFactory';
const { Wrap, Filter } = Constants; const { VtkDataTypes } = vtkDataArray; const { vtkDebugMacro, vtkErrorMacro, vtkWarningMacro } = macro; const { toHalf } = HalfFloat;
function vtkOpenGLTexture(publicAPI, model) { model.classHierarchy.push('vtkOpenGLTexture'); publicAPI.render = (renWin = null) => { if (renWin) { model._openGLRenderWindow = renWin; } else { model._openGLRenderer = publicAPI.getFirstAncestorOfType('vtkOpenGLRenderer'); model._openGLRenderWindow = model._openGLRenderer.getLastAncestorOfType( 'vtkOpenGLRenderWindow' ); } 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); } if (model.renderable.getInputData()) { model.renderable.setImage(null); } if ( !model.handle || model.renderable.getMTime() > model.textureBuildTime.getMTime() ) { if (model.renderable.getImage() !== null) { if (model.renderable.getInterpolate()) { model.generateMipmap = true; publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR); } if (model.renderable.getImage() && model.renderable.getImageLoaded()) { publicAPI.create2DFromImage(model.renderable.getImage()); publicAPI.activate(); publicAPI.sendParameters(); model.textureBuildTime.modified(); } } if (model.renderable.getCanvas() !== null) { if (model.renderable.getInterpolate()) { model.generateMipmap = true; publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR); } const canvas = model.renderable.getCanvas(); publicAPI.create2DFromRaw( canvas.width, canvas.height, 4, VtkDataTypes.UNSIGNED_CHAR, canvas, true ); publicAPI.activate(); publicAPI.sendParameters(); model.textureBuildTime.modified(); } if (model.renderable.getJsImageData() !== null) { const 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(); } const input = model.renderable.getInputData(0); if (input && input.getPointData().getScalars()) { const ext = input.getExtent(); const inScalars = input.getPointData().getScalars();
const data = []; for (let i = 0; i < model.renderable.getNumberOfInputPorts(); ++i) { const indata = model.renderable.getInputData(i); const 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 = () => { 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 = () => { if (!model.handle) { model.handle = model.context.createTexture();
if (model.target) { model.context.bindTexture(model.target, model.handle);
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 = () => { if (model._openGLRenderWindow) { return model._openGLRenderWindow.getTextureUnitForTexture(publicAPI); } return -1; };
publicAPI.activate = () => { model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.bind(); };
publicAPI.deactivate = () => { if (model._openGLRenderWindow) { model._openGLRenderWindow.deactivateTexture(publicAPI); } };
publicAPI.releaseGraphicsResources = (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; model.allocatedGPUMemoryInBytes = 0; } if (model.shaderProgram) { model.shaderProgram.releaseGraphicsResources(rwin); model.shaderProgram = null; } };
publicAPI.bind = () => { model.context.bindTexture(model.target, model.handle); if ( model.autoParameters && publicAPI.getMTime() > model.sendParametersTime.getMTime() ) { publicAPI.sendParameters(); } };
publicAPI.isBound = () => { let result = false; if (model.context && model.handle) { let target = 0; switch (model.target) { case model.context.TEXTURE_2D: target = model.context.TEXTURE_BINDING_2D; break; default: vtkWarningMacro('impossible case'); break; } const oid = model.context.getIntegerv(target); result = oid === model.handle; } return result; };
publicAPI.sendParameters = () => { 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.sendParametersTime.modified(); };
publicAPI.getInternalFormat = (vtktype, numComps) => { if (!model._forceInternalFormat) { model.internalFormat = publicAPI.getDefaultInternalFormat( vtktype, numComps ); }
if (!model.internalFormat) { vtkDebugMacro( `Unable to find suitable internal format for T=${vtktype} NC= ${numComps}` ); }
if ( [ model.context.R32F, model.context.RG32F, model.context.RGB32F, model.context.RGBA32F, ].includes(model.internalFormat) && !model.context.getExtension('OES_texture_float_linear') ) { vtkWarningMacro( 'Failed to load OES_texture_float_linear. Texture filtering is not available for *32F internal formats.' ); }
return model.internalFormat; };
publicAPI.getDefaultInternalFormat = (vtktype, numComps) => { let result = 0; result = model._openGLRenderWindow.getDefaultTextureInternalFormat( vtktype, numComps, model.oglNorm16Ext, publicAPI.useHalfFloat() ); if (result) { return result; }
if (!result) { vtkDebugMacro('Unsupported internal texture type!'); vtkDebugMacro( `Unable to find suitable internal format for T=${vtktype} NC= ${numComps}` ); }
return result; };
publicAPI.useHalfFloat = () => model.enableUseHalfFloat && model.canUseHalfFloat;
publicAPI.setInternalFormat = (iFormat) => { model._forceInternalFormat = true; if (iFormat !== model.internalFormat) { model.internalFormat = iFormat; publicAPI.modified(); } };
publicAPI.getFormat = (vtktype, numComps) => { model.format = publicAPI.getDefaultFormat(vtktype, numComps); return model.format; };
publicAPI.getDefaultFormat = (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 { 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 = () => { model.format = 0; model.internalFormat = 0; model._forceInternalFormat = false; model.openGLDataType = 0; };
publicAPI.getDefaultDataType = (vtkScalarType) => { const useHalfFloat = publicAPI.useHalfFloat(); if (model._openGLRenderWindow.getWebgl2()) { switch (vtkScalarType) { case VtkDataTypes.UNSIGNED_CHAR: return model.context.UNSIGNED_BYTE; case model.oglNorm16Ext && !useHalfFloat && VtkDataTypes.SHORT: return model.context.SHORT; case model.oglNorm16Ext && !useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: return model.context.UNSIGNED_SHORT; case useHalfFloat && VtkDataTypes.SHORT: return model.context.HALF_FLOAT; case useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: return model.context.HALF_FLOAT; case VtkDataTypes.FLOAT: case VtkDataTypes.VOID: default: return model.context.FLOAT; } }
switch (vtkScalarType) { case VtkDataTypes.UNSIGNED_CHAR: return model.context.UNSIGNED_BYTE; case VtkDataTypes.FLOAT: case VtkDataTypes.VOID: default: if ( model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear') ) { return model.context.FLOAT; } { const 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 = (vtkScalarType, forceUpdate = false) => { if (!model.openGLDataType || forceUpdate) { model.openGLDataType = publicAPI.getDefaultDataType(vtkScalarType); } return model.openGLDataType; };
publicAPI.getShiftAndScale = () => { let shift = 0.0; let scale = 1.0;
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: default: break; } return { shift, scale }; };
publicAPI.getOpenGLFilterMode = (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 = (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; } };
publicAPI.updateArrayDataTypeForGL = (dataType, data, depth = false) => { const pixData = []; let pixCount = model.width * model.height * model.components; if (depth) { pixCount *= model.depth; }
if ( dataType !== VtkDataTypes.FLOAT && model.openGLDataType === model.context.FLOAT ) { for (let idx = 0; idx < data.length; idx++) { if (data[idx]) { const dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx]; pixData.push(new Float32Array(dataArrayToCopy)); } else { pixData.push(null); } } }
if ( dataType !== VtkDataTypes.UNSIGNED_CHAR && model.openGLDataType === model.context.UNSIGNED_BYTE ) { for (let idx = 0; idx < data.length; idx++) { if (data[idx]) { const dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx]; pixData.push(new Uint8Array(dataArrayToCopy)); } else { pixData.push(null); } } }
let halfFloat = false; if (model._openGLRenderWindow.getWebgl2()) { halfFloat = model.openGLDataType === model.context.HALF_FLOAT; } else { const halfFloatExt = model.context.getExtension('OES_texture_half_float'); halfFloat = halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES; }
if (halfFloat) { for (let idx = 0; idx < data.length; idx++) { if (data[idx]) { const newArray = new Uint16Array(pixCount); const src = data[idx]; for (let i = 0; i < pixCount; i++) { newArray[i] = toHalf(src[i]); } pixData.push(newArray); } else { pixData.push(null); } } }
if (pixData.length === 0) { for (let i = 0; i < data.length; i++) { pixData.push(data[i]); } }
return pixData; };
function scaleTextureToHighestPowerOfTwo(data) { if (model._openGLRenderWindow.getWebgl2()) { return data; } const pixData = []; const width = model.width; const height = model.height; const numComps = model.components; if ( data && (!vtkMath.isPowerOfTwo(width) || !vtkMath.isPowerOfTwo(height)) ) { const halfFloat = model.context.getExtension('OES_texture_half_float'); const newWidth = vtkMath.nearestPowerOfTwo(width); const newHeight = vtkMath.nearestPowerOfTwo(height); const pixCount = newWidth * newHeight * model.components; for (let idx = 0; idx < data.length; idx++) { if (data[idx] !== null) { let newArray = null; const jFactor = height / newHeight; const iFactor = width / newWidth; let 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 (let j = 0; j < newHeight; j++) { const joff = j * newWidth * numComps; const jidx = j * jFactor; let jlow = Math.floor(jidx); let jhi = Math.ceil(jidx); if (jhi >= height) { jhi = height - 1; } const jmix = jidx - jlow; const jmix1 = 1.0 - jmix; jlow = jlow * width * numComps; jhi = jhi * width * numComps; for (let i = 0; i < newWidth; i++) { const ioff = i * numComps; const iidx = i * iFactor; let ilow = Math.floor(iidx); let ihi = Math.ceil(iidx); if (ihi >= width) { ihi = width - 1; } const imix = iidx - ilow; ilow *= numComps; ihi *= numComps; for (let 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); } } }
if (pixData.length === 0) { for (let i = 0; i < data.length; i++) { pixData.push(data[i]); } }
return pixData; }
function useTexStorage(dataType) { if (model._openGLRenderWindow) { if (model.resizable || model.renderable?.getResizable()) { return false; } if (model._openGLRenderWindow.getWebgl2()) { const 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) ) { return false; } return true; } return false; } return false; }
publicAPI.create2DFromRaw = ( width, height, numComps, dataType, data, flip = false ) => { 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();
const dataArray = [data]; const pixData = publicAPI.updateArrayDataTypeForGL(dataType, dataArray); const scaledData = scaleTextureToHighestPowerOfTwo(pixData);
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); }
if (flip) { model.context.pixelStorei(model.context.UNPACK_FLIP_Y_WEBGL, false); }
model.allocatedGPUMemoryInBytes = model.width * model.height * model.depth * numComps * model._openGLRenderWindow.getDefaultTextureByteSize( dataType, model.oglNorm16Ext, publicAPI.useHalfFloat() ); publicAPI.deactivate(); return true; };
publicAPI.createCubeFromRaw = (width, height, numComps, dataType, data) => { 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();
const pixData = publicAPI.updateArrayDataTypeForGL(dataType, data); const scaledData = scaleTextureToHighestPowerOfTwo(pixData);
const invertedData = []; let widthLevel = model.width; let heightLevel = model.height; for (let i = 0; i < scaledData.length; i++) { if (i % 6 === 0 && i !== 0) { widthLevel /= 2; heightLevel /= 2; } invertedData[i] = macro.newTypedArray( dataType, heightLevel * widthLevel * model.components ); for (let y = 0; y < heightLevel; ++y) { const row1 = y * widthLevel * model.components; const row2 = (heightLevel - y - 1) * widthLevel * model.components; invertedData[i].set( scaledData[i].slice(row2, row2 + widthLevel * model.components), row1 ); } }
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
if (useTexStorage(dataType)) { model.context.texStorage2D( model.target, 6, model.internalFormat, model.width, model.height ); } for (let i = 0; i < 6; i++) { let j = 0; let w = model.width; let h = model.height; while (w >= 1 && h >= 1) { let tempData = null; if (j <= model.maxLevel) { tempData = invertedData[6 * j + i]; } if (useTexStorage(dataType)) { if (tempData != null) { model.context.texSubImage2D( model.context.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, w, h, model.format, model.openGLDataType, tempData ); } } else { model.context.texImage2D( model.context.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, model.internalFormat, w, h, 0, model.format, model.openGLDataType, tempData ); } j++; w /= 2; h /= 2; } }
model.allocatedGPUMemoryInBytes = model.width * model.height * model.depth * numComps * model._openGLRenderWindow.getDefaultTextureByteSize( dataType, model.oglNorm16Ext, publicAPI.useHalfFloat() );
publicAPI.deactivate(); return true; };
publicAPI.createDepthFromRaw = (width, height, dataType, data) => { 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();
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); }
model.allocatedGPUMemoryInBytes = model.width * model.height * model.depth * model.components * model._openGLRenderWindow.getDefaultTextureByteSize( dataType, model.oglNorm16Ext, publicAPI.useHalfFloat() );
publicAPI.deactivate(); return true; };
publicAPI.create2DFromImage = (image) => { 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();
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
const needNearestPowerOfTwo = !model._openGLRenderWindow.getWebgl2() && (!vtkMath.isPowerOfTwo(image.width) || !vtkMath.isPowerOfTwo(image.height)); const canvas = document.createElement('canvas'); canvas.width = needNearestPowerOfTwo ? vtkMath.nearestPowerOfTwo(image.width) : image.width; canvas.height = needNearestPowerOfTwo ? vtkMath.nearestPowerOfTwo(image.height) : image.height;
model.width = canvas.width; model.height = canvas.height;
const 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 ); const 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); }
model.allocatedGPUMemoryInBytes = model.width * model.height * model.depth * model.components * model._openGLRenderWindow.getDefaultTextureByteSize( VtkDataTypes.UNSIGNED_CHAR, model.oglNorm16Ext, publicAPI.useHalfFloat() );
publicAPI.deactivate(); return true; };
function computeScaleOffsets(min, max, numComps) { const offset = new Array(numComps); const scale = new Array(numComps); for (let c = 0; c < numComps; ++c) { offset[c] = min[c]; scale[c] = max[c] - min[c] || 1.0; } return { scale, offset }; }
function hasExactHalfFloat(offset, scale) { for (let c = 0; c < offset.length; c++) { const min = offset[c]; const max = scale[c] + min;
if (min < -2048 || min > 2048 || max < -2048 || max > 2048) { return false; } } return true; }
function setCanUseHalfFloat(dataType, offset, scale, preferSizeOverAccuracy) { publicAPI.getOpenGLDataType(dataType);
const isExactHalfFloat = hasExactHalfFloat(offset, scale) || preferSizeOverAccuracy;
let useHalfFloat = false; if (model._openGLRenderWindow.getWebgl2()) { const forceHalfFloat = model.openGLDataType === model.context.FLOAT && model.context.getExtension('OES_texture_float_linear') === null && isExactHalfFloat; useHalfFloat = forceHalfFloat || model.openGLDataType === model.context.HALF_FLOAT; } else { const halfFloatExt = model.context.getExtension('OES_texture_half_float'); useHalfFloat = halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES; }
model.canUseHalfFloat = useHalfFloat && isExactHalfFloat; }
function processDataArray(dataArray, preferSizeOverAccuracy) { const numComps = dataArray.getNumberOfComponents(); const dataType = dataArray.getDataType(); const data = dataArray.getData();
const minArray = new Array(numComps); const maxArray = new Array(numComps); for (let c = 0; c < numComps; ++c) { const [min, max] = dataArray.getRange(c); minArray[c] = min; maxArray[c] = max; }
const scaleOffsets = computeScaleOffsets(minArray, maxArray, numComps);
setCanUseHalfFloat( dataType, scaleOffsets.offset, scaleOffsets.scale, preferSizeOverAccuracy );
if (!publicAPI.useHalfFloat()) { publicAPI.getOpenGLDataType(dataType, true); }
return { numComps, dataType, data, scaleOffsets, }; }
publicAPI.create2DFilterableFromRaw = ( width, height, numberOfComponents, dataType, values, preferSizeOverAccuracy = false ) => publicAPI.create2DFilterableFromDataArray( width, height, vtkDataArray.newInstance({ numberOfComponents, dataType, values, }), preferSizeOverAccuracy );
publicAPI.create2DFilterableFromDataArray = ( width, height, dataArray, preferSizeOverAccuracy = false ) => { const { numComps, dataType, data } = processDataArray( dataArray, preferSizeOverAccuracy );
publicAPI.create2DFromRaw(width, height, numComps, dataType, data); };
publicAPI.updateVolumeInfoForGL = (dataType, numComps) => { let isScalingApplied = false; const useHalfFloat = publicAPI.useHalfFloat();
if (!model.volumeInfo?.scale || !model.volumeInfo?.offset) { model.volumeInfo = { scale: new Array(numComps), offset: new Array(numComps), }; }
for (let c = 0; c < numComps; ++c) { model.volumeInfo.scale[c] = 1.0; model.volumeInfo.offset[c] = 0.0; }
if ( model.oglNorm16Ext && !useHalfFloat && dataType === VtkDataTypes.SHORT ) { for (let c = 0; c < numComps; ++c) { model.volumeInfo.scale[c] = 32767.0; } isScalingApplied = true; }
if ( model.oglNorm16Ext && !useHalfFloat && dataType === VtkDataTypes.UNSIGNED_SHORT ) { for (let c = 0; c < numComps; ++c) { model.volumeInfo.scale[c] = 65535.0; } isScalingApplied = true; }
if (dataType === VtkDataTypes.UNSIGNED_CHAR) { for (let c = 0; c < numComps; ++c) { model.volumeInfo.scale[c] = 255.0; } isScalingApplied = true; }
if ( dataType === VtkDataTypes.FLOAT || (useHalfFloat && (dataType === VtkDataTypes.SHORT || dataType === VtkDataTypes.UNSIGNED_SHORT)) ) { isScalingApplied = true; }
return isScalingApplied; };
publicAPI.create3DFromRaw = ( width, height, depth, numComps, dataType, data ) => { let dataTypeToUse = dataType; let dataToUse = data;
if ( !publicAPI.updateVolumeInfoForGL(dataTypeToUse, numComps) && dataToUse ) { const numPixelsIn = width * height * depth; const scaleOffsetsCopy = structuredClone(model.volumeInfo); const newArray = new Float32Array(numPixelsIn * numComps); model.volumeInfo.offset = scaleOffsetsCopy.offset; model.volumeInfo.scale = scaleOffsetsCopy.scale; let count = 0; const scaleInverse = scaleOffsetsCopy.scale.map((s) => 1 / s); for (let i = 0; i < numPixelsIn; i++) { for (let nc = 0; nc < numComps; nc++) { newArray[count] = (dataToUse[count] - scaleOffsetsCopy.offset[nc]) * scaleInverse[nc]; count++; } }
dataTypeToUse = VtkDataTypes.FLOAT; dataToUse = newArray; }
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; }
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(); const dataArray = [dataToUse]; const is3DArray = true; const pixData = publicAPI.updateArrayDataTypeForGL( dataTypeToUse, dataArray, is3DArray ); const scaledData = scaleTextureToHighestPowerOfTwo(pixData);
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
if (useTexStorage(dataTypeToUse)) { 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); }
model.allocatedGPUMemoryInBytes = model.width * model.height * model.depth * model.components * model._openGLRenderWindow.getDefaultTextureByteSize( dataTypeToUse, model.oglNorm16Ext, publicAPI.useHalfFloat() );
publicAPI.deactivate(); return true; };
publicAPI.create3DFilterableFromRaw = ( width, height, depth, numberOfComponents, dataType, values, preferSizeOverAccuracy = false ) => publicAPI.create3DFilterableFromDataArray( width, height, depth, vtkDataArray.newInstance({ numberOfComponents, dataType, values, }), preferSizeOverAccuracy );
publicAPI.create3DFilterableFromDataArray = ( width, height, depth, dataArray, preferSizeOverAccuracy = false ) => { const { numComps, dataType, data, scaleOffsets } = processDataArray( dataArray, preferSizeOverAccuracy );
const offset = []; const scale = []; for (let c = 0; c < numComps; ++c) { offset[c] = 0.0; scale[c] = 1.0; }
model.volumeInfo = { scale, offset, dataComputedScale: scaleOffsets.scale, dataComputedOffset: scaleOffsets.offset, width, height, depth, };
if (model._openGLRenderWindow.getWebgl2()) { return publicAPI.create3DFromRaw( width, height, depth, numComps, dataType, data ); }
const numPixelsIn = width * height * depth; const scaleOffsetsCopy = structuredClone(scaleOffsets);
let volCopyData = (outArray, outIdx, inValue, smin, smax) => { outArray[outIdx] = inValue; }; let dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR; if (dataType === VtkDataTypes.UNSIGNED_CHAR) { for (let c = 0; c < numComps; ++c) { scaleOffsetsCopy.offset[c] = 0.0; scaleOffsetsCopy.scale[c] = 255.0; } } else if ( model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear') ) { dataTypeToUse = VtkDataTypes.FLOAT; volCopyData = (outArray, outIdx, inValue, soffset, sscale) => { outArray[outIdx] = (inValue - soffset) / sscale; }; } else { dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR; volCopyData = (outArray, outIdx, inValue, soffset, sscale) => { outArray[outIdx] = (255.0 * (inValue - soffset)) / sscale; }; }
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; }
model.target = model.context.TEXTURE_2D; model.components = numComps; model.depth = 1; model.numberOfDimensions = 2;
let maxTexDim = model.context.getParameter(model.context.MAX_TEXTURE_SIZE); if ( maxTexDim > 4096 && (dataTypeToUse === VtkDataTypes.FLOAT || numComps >= 3) ) { maxTexDim = 4096; }
let xstride = 1; let ystride = 1; if (numPixelsIn > maxTexDim * maxTexDim) { xstride = Math.ceil(Math.sqrt(numPixelsIn / (maxTexDim * maxTexDim))); ystride = xstride; } let targetWidth = Math.sqrt(numPixelsIn) / xstride; targetWidth = vtkMath.nearestPowerOfTwo(targetWidth); const xreps = Math.floor((targetWidth * xstride) / width); const yreps = Math.ceil(depth / xreps); const targetHeight = vtkMath.nearestPowerOfTwo((height * yreps) / ystride);
model.width = targetWidth; model.height = targetHeight; model._openGLRenderWindow.activateTexture(publicAPI); publicAPI.createTexture(); publicAPI.bind();
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;
let newArray; const pixCount = targetWidth * targetHeight * numComps; if (dataTypeToUse === VtkDataTypes.FLOAT) { newArray = new Float32Array(pixCount); } else { newArray = new Uint8Array(pixCount); }
let outIdx = 0;
const tileWidth = Math.floor(width / xstride); const tileHeight = Math.floor(height / ystride);
for (let yRep = 0; yRep < yreps; yRep++) { const xrepsThisRow = Math.min(xreps, depth - yRep * xreps); const outXContIncr = numComps * (model.width - xrepsThisRow * Math.floor(width / xstride)); for (let tileY = 0; tileY < tileHeight; tileY++) { for (let xRep = 0; xRep < xrepsThisRow; xRep++) { const inOffset = numComps * ((yRep * xreps + xRep) * width * height + ystride * tileY * width);
for (let tileX = 0; tileX < tileWidth; tileX++) { for (let nc = 0; nc < numComps; nc++) { volCopyData( newArray, outIdx, data[inOffset + xstride * tileX * numComps + nc], scaleOffsetsCopy.offset[nc], scaleOffsetsCopy.scale[nc] ); outIdx++; } } } outIdx += outXContIncr; } }
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
if (useTexStorage(dataTypeToUse)) { model.context.texStorage2D( model.target, 1, model.internalFormat, model.width, model.height ); if (newArray != null) { model.context.texSubImage2D( model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, newArray ); } } else { model.context.texImage2D( model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, newArray ); }
publicAPI.deactivate(); return true; };
publicAPI.setOpenGLRenderWindow = (rw) => { if (model._openGLRenderWindow === rw) { return; } publicAPI.releaseGraphicsResources(); model._openGLRenderWindow = rw; model.context = null; if (rw) { model.context = model._openGLRenderWindow.getContext(); } };
publicAPI.getMaximumTextureSize = (ctx) => { if (ctx && ctx.isCurrent()) { return ctx.getIntegerv(ctx.MAX_TEXTURE_SIZE); }
return -1; };
publicAPI.enableUseHalfFloat = (use) => { model.enableUseHalfFloat = use; }; }
const DEFAULT_VALUES = { _openGLRenderWindow: null, _forceInternalFormat: false, context: null, handle: 0, sendParametersTime: null, textureBuildTime: null, numberOfDimensions: 0, target: 0, format: 0, openGLDataType: 0, components: 0, width: 0, height: 0, depth: 0, autoParameters: true, wrapS: Wrap.CLAMP_TO_EDGE, wrapT: Wrap.CLAMP_TO_EDGE, wrapR: Wrap.CLAMP_TO_EDGE, minificationFilter: Filter.NEAREST, magnificationFilter: Filter.NEAREST, minLOD: -1000.0, maxLOD: 1000.0, baseLevel: 0, maxLevel: 1000, generateMipmap: false, oglNorm16Ext: null, allocatedGPUMemoryInBytes: 0, enableUseHalfFloat: true, canUseHalfFloat: false, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
vtkViewNode.extend(publicAPI, model, initialValues);
model.sendParametersTime = {}; macro.obj(model.sendParametersTime, { mtime: 0 });
model.textureBuildTime = {}; macro.obj(model.textureBuildTime, { mtime: 0 });
macro.set(publicAPI, model, ['format', 'openGLDataType']);
macro.setGet(publicAPI, model, [ 'keyMatrixTime', 'minificationFilter', 'magnificationFilter', 'wrapS', 'wrapT', 'wrapR', 'generateMipmap', 'oglNorm16Ext', ]);
macro.get(publicAPI, model, [ 'width', 'height', 'volumeInfo', 'components', 'handle', 'target', 'allocatedGPUMemoryInBytes', ]); macro.moveToProtected(publicAPI, model, ['openGLRenderWindow']);
vtkOpenGLTexture(publicAPI, model); }
export const newInstance = macro.newInstance(extend, 'vtkOpenGLTexture');
export default { newInstance, extend, ...Constants };
registerOverride('vtkTexture', newInstance);
|